一、Yarn脚本执行顺序的基本规则

Yarn作为JavaScript生态中常用的包管理工具,它的脚本执行顺序其实就像做菜的步骤清单。当你运行yarn installyarn add时,Yarn会按照特定顺序执行package.json中定义的脚本。

举个实际例子(技术栈:Node.js):

// package.json示例
{
  "scripts": {
    "preinstall": "echo '准备安装依赖...'",
    "install": "echo '正在安装主依赖...'",
    "postinstall": "echo '依赖安装完成!'",
    "pretest": "echo '准备运行测试...'",
    "test": "echo '运行测试用例'",
    "posttest": "echo '测试结果分析完成'"
  }
}

当你运行yarn test时,控制台会依次输出:

  1. 准备运行测试...
  2. 运行测试用例
  3. 测试结果分析完成

这种自动化的前后钩子机制,就像三明治的夹心层,主命令是肉片,pre/post脚本就是面包片。

二、生命周期钩子的完整执行链条

Yarn的脚本执行顺序远比表面看到的复杂。整个生命周期就像地铁线路图,有主干线也有支线。以下是完整的执行链条示例:

// 完整生命周期示例(技术栈:Node.js)
{
  "scripts": {
    "preprepare": "echo '准备阶段前奏'",
    "prepare": "echo '正式准备阶段'",
    "postprepare": "echo '准备阶段收尾'",
    
    "prepack": "echo '打包前检查'",
    "pack": "echo '执行打包'",
    "postpack": "echo '打包后处理'",
    
    "prepublishOnly": "echo '发布前专属检查'",
    "prepublish": "echo '传统发布前脚本'"
  }
}

当运行yarn publish时,实际执行顺序是:

  1. prepublishOnly
  2. prepublish
  3. prepare
  4. prepack
  5. pack
  6. postpack
  7. publish
  8. postpublish

这个顺序就像多米诺骨牌,前一个触发后一个,环环相扣。需要注意的是,从Yarn 2.x开始,部分传统钩子如prepublish的行为有所变化。

三、典型应用场景与实战示例

让我们看几个实际开发中的典型用例(技术栈:Node.js):

// 实战场景示例
{
  "scripts": {
    // 1. 自动构建场景
    "prebuild": "rimraf ./dist",
    "build": "webpack --config webpack.prod.js",
    "postbuild": "node ./scripts/notify.js",
    
    // 2. 多环境部署
    "predocker:build": "npm run build",
    "docker:build": "docker build -t my-app .",
    "postdocker:build": "docker image prune -f",
    
    // 3. 数据库迁移
    "premigrate": "node ./scripts/check-env.js",
    "migrate": "knex migrate:latest",
    "postmigrate": "node ./scripts/log-migration.js"
  }
}

注释说明:

  1. 构建场景:先清理旧文件 → 执行构建 → 发送通知
  2. Docker部署:先构建应用 → 构建镜像 → 清理缓存
  3. 数据库迁移:检查环境 → 执行迁移 → 记录日志

这些场景展示了如何利用pre/post脚本构建自动化工作流,就像工厂的流水线,每个环节自动衔接。

四、常见问题与解决方案

在实践中我们常遇到这些问题:

  1. 脚本执行顺序错乱
// 问题示例(技术栈:Node.js)
{
  "scripts": {
    "preserve": "echo 'A'",
    "serve": "echo 'B' & echo 'C'", // 并行执行导致混乱
    "postserve": "echo 'D'"
  }
}

解决方案是使用&&替代&确保串行执行:

"serve": "echo 'B' && echo 'C'"
  1. 环境变量继承问题 pre/post脚本默认继承父进程环境,但要注意:
# 错误用法
"prestart": "export NODE_ENV=development",
"start": "node app.js" # 这里NODE_ENV不会生效

# 正确做法(使用cross-env)
"prestart": "cross-env NODE_ENV=development",
"start": "cross-env-shell \"node app.js\""
  1. 循环触发陷阱
// 危险示例
{
  "scripts": {
    "prebuild": "npm run test", // 可能导致无限循环
    "build": "echo 'building...'"
  }
}

应该改为独立任务:

"ci": "npm run test && npm run build"

五、进阶技巧与最佳实践

  1. 条件式执行
// 条件执行示例(技术栈:Node.js)
{
  "scripts": {
    "predeploy": "if [ \"$ENV\" = \"production\" ]; then echo '校验生产环境'; fi",
    "deploy": "node deploy.js"
  }
}
  1. 多项目协同 Monorepo中的特殊处理:
// lerna + yarn workspace示例
{
  "scripts": {
    "postinstall": "lerna bootstrap --hoist",
    "build": "lerna run build --stream"
  }
}
  1. 性能优化技巧
// 缓存优化示例
{
  "scripts": {
    "postinstall": "node ./scripts/skip-if-exists.js || heavy-operation"
  }
}

其中skip-if-exists.js内容:

// 检查缓存是否存在
if(fs.existsSync('./.cache')) process.exit(0) 
else process.exit(1)

六、技术对比与选型建议

与npm脚本的对比:

  1. Yarn的执行速度更快(并行安装依赖)
  2. Yarn的--ignore-scripts选项更严格
  3. Yarn 2+的Plug'n'Play模式会改变传统node_modules行为

与pnpm的对比:

  1. pnpm的依赖结构更节省空间
  2. pnpm的pre/post脚本执行逻辑略有不同
  3. pnpm对monorepo支持更原生

选型建议:

  • 小型项目:任意选择
  • 大型项目:Yarn workspace + Lerna
  • 极致性能:pnpm

七、总结与注意事项

关键要点总结:

  1. pre/post脚本就像书挡,总是成对出现
  2. 生命周期钩子顺序是固定的"准备-执行-收尾"
  3. 复杂场景可以组合多个钩子

注意事项:

  1. 避免在脚本中做耗时操作(如大文件拷贝)
  2. 生产环境记得使用--production跳过devDependencies
  3. 团队协作时保持脚本跨平台兼容
  4. Yarn 2+版本变化较大,需要特别测试

最后记住,这些脚本就像烹饪食谱,合理的顺序安排能让你的项目"大餐"更加美味可口!