npm 生命周期钩子 pre 与 post 用法
pre 和 post 是怎么工作的
npm 的生命周期钩子基于一个很简单的命名约定:执行 npm run xxx 时,npm 会先看 scripts 里有没有 prexxx,有就先跑它;然后跑 xxx;最后看有没有 postxxx,有就再跑一遍。三者是串行的,前一个失败(退出码非 0)后面就不会执行。
这个约定不需要任何配置,只要脚本名字对上就自动生效。所以它常被用来做「做正事之前先准备、做完之后再收尾」这类工作,比如构建前清空产物目录、发布前跑一遍测试和 lint、打完包顺手生成一份体积报告。相比把命令用 && 串成一长串,拆成 pre/post 的好处是每一步还能单独执行,调试时不用改脚本。
需要注意钩子的触发条件是「这个命令被执行」,而不是「这个命令成功」。post 钩子只在主命令成功后才跑,主命令失败就直接中断了。
默认支持的钩子
下面这些是 npm 内置命令自带的钩子,也就是说即使不通过 npm run 而是直接执行 npm install、npm publish,对应的 pre/post 脚本一样会被触发:
npm 默认钩子
prepublish,postpublish
preinstall,postinstall
preuninstall,postuninstall
preversion,postversion
pretest,posttest
prestop,poststop
prestart,poststart
prerestart,postrestart
这里面 postinstall 是实际项目里最常见的一个,很多包用它在安装完成后编译原生模块或者下载二进制文件。也正因为它会自动执行任意脚本,它是供应链攻击的常见入口,安装陌生依赖时值得留意。
自定义的脚本命令也可以加上 pre 和 post 钩子。比如,myscript 这个脚本命令,也有 premyscript 和 postmyscript 钩子。
不过有一点容易踩:钩子的匹配是纯字符串拼接,不认识命名里的连字符。如果脚本叫 build:prod,它的前置钩子就得叫 prebuild:prod,写成 pre:build:prod 是不生效的。
注意,prepublish 这个钩子不仅会在 npm publish 命令之前运行,还会在 npm install(不带任何参数)命令之前运行。 这种行为很容易让用户感到困惑,所以 npm 4 引入了一个新的钩子 prepare,行为等同于 prepublish,而从 npm 5 开始,prepublish 将只在 npm publish 命令之前。
这个变化带来一个现实问题:老项目里写的 prepublish 脚本,在新版 npm 下会收到弃用警告。下图是在一个仍然使用 prepublish 的项目里执行 npm install 时终端的输出,重点看那行 npm WARN 开头的提示——它说明 npm 已经不再把 prepublish 当作 install 阶段的钩子,并建议改用 prepare(构建产物、需要在发布和本地安装时都执行)或者 prepublishOnly(只在发布前执行,比如跑测试)。
如下图,运行 npm install 会出现如下警告 
这里额外提一下三个钩子的分工,免得改的时候选错:prepare 在打包发布前、以及本地 npm install(不带参数)时都会执行,适合放编译步骤,因为从 git 地址安装依赖时也需要先编译;prepublishOnly 只在 npm publish 时执行,适合放测试和检查这类不该在安装时跑的东西;prepack 在打 tarball 之前执行。
具体到 antd 这个例子,改法是把脚本名换掉,让它不再落在 prepublish 这个被弃用的名字上:
更改:
'prepublish': 'node --max_old_space_size=6144 antd-tools/cli/run.js guard'
为
'preparepublish': 'node --max_old_space_size=6144 antd-tools/cli/run.js guard'
改成 preparepublish 之后警告就消失了,因为这个名字 npm 不认识,它不再是内置钩子,而变成了一个普通的自定义脚本——只有显式执行 npm run preparepublish 才会跑。所以这个改法本质上是「把自动触发改成手动触发」,警告没了,但原本在 install 时自动执行的检查也一并没了。如果那个检查确实需要在发布前跑,更合适的名字是 prepublishOnly;如果需要在安装时也跑,用 prepare。选哪个取决于这段脚本到底想在什么时候生效。
顺带说个调试技巧:想确认某个钩子有没有被触发、执行顺序对不对,可以在脚本里加一句 echo 打印标记,或者执行时加 --foreground-scripts 让子脚本的输出直接显示在终端里,默认情况下这些输出是被 npm 折叠掉的。

还没有留言,来发表第一条吧~