◆

npm 生命周期钩子 pre 与 post 用法

2023-02-02 01:370

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 会出现如下警告 npm install 时终端输出的 prepublish 钩子弃用警告

这里额外提一下三个钩子的分工,免得改的时候选错: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 折叠掉的。

💬留言板0

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

PAGE LOAD / LIVEnpm 生命周期钩子 pre 与 post 用法加载中