npm scripts的高级玩法:pre、post和--,你真的会用吗?

我们每天的开发,可能都是从一个npm run dev开始的。npm scripts对我们来说,天天用它,但很少去思考它。

不信,你看看你项目里的package.json,是不是长这样👇:

JSON 复制代码
"scripts": {
  "dev": "vite",
  "build": "rm -rf dist && tsc && vite build", // 嘿,眼熟吗?
  "lint": "eslint .",
  "lint:fix": "eslint . --fix",
  "test": "vitest",
  "test:watch": "vitest --watch",
  "preview": "vite preview"
}

这能用吗?当然能用。

但这专业吗?在我看来,未必!

一个好的scripts,应该是原子化的、跨平台的 。而上面这个,一个build命令就不行,而且rm -rf在Windows上还得装特定环境才能跑🤷‍♂️。

今天,我就来聊聊,如何用prepost--,把你的脚本,升级成专业的脚本。


prepost:命令的生命周期钩子

prepost,是npm内置的一种钩子机制。

它的规则很简单:

  • 当你执行npm run xyz时,npm自动 先去找,有没有一个叫prexyz的脚本,有就先执行它。
  • xyz执行成功后,npm自动 再去找,有没有一个叫postxyz的脚本,有就最后再执行它。

这个自动的特性,就是神一般的存在。

我们来改造那个前面👆提到的build脚本。

业余写法 (用&&手动编排)

JSON 复制代码
"scripts": {
  "clean": "rimraf dist", // rimraf 解决跨平台删除问题
  "lint": "eslint .",
  "build:tsc": "tsc",
  "build:vite": "vite build",
  "build": "npm run clean && npm run lint && npm run build:tsc && npm run build:vite"
}

你的build脚本,它必须记住所有的前置步骤。如果哪天你想在build前,再加一个test,你还得去修改build的定义。这违反了单一职责

专业写法 (用pre自动触发)

JSON 复制代码
"scripts": {
  "clean": "rimraf dist",
  "lint": "eslint .",
  "test": "vitest run",
  "build:tsc": "tsc",
  "build:vite": "vite build",

  // build的前置钩子
  "prebuild": "npm run clean && npm run lint && npm run test", 
  
  // build的核心命令
  "build": "npm run build:tsc && npm run build:vite",
  
  // build的后置钩子
  "postbuild": "echo 'Build complete! Check /dist folder.'"
}

看到区别了吗?

现在,当我只想构建时,我依然执行npm run build。

npm会自动帮我执行prebuild(清理、Lint、测试)👉 然后执行build(编译、打包)👉 最后执行postbuild(打印日志)。

我的build脚本,只关心构建这件事 。而prebuild脚本,只关心前置检查这件事

这就是单一职责和关注点分离。

你甚至可以利用这个特性,搞点骚操作😁:

JSON 复制代码
"scripts": {
  // 当你执行npm start时,它会自动先执行npm run build
  "prestart": "npm run build", 
  "start": "node dist/server.js"
}

-- (双短线):脚本参数

--是我最爱的一个特性。它是一个参数分隔符

它的作用是:告诉npm,我的npm参数到此为止了,后面所有的东西,都原封不动地,传给我要执行的那个底层命令。"

我们来看开头👆那个脚本:

JSON 复制代码
"scripts": {
  "test": "vitest",
  "test:watch": "vitest --watch"
}

为了一个--watch参数,你复制了一个几乎一模一样的脚本。如果明天你还想要--coverage呢?再加一个test:coverage?这叫垃圾代码💩

专业写法 (用--动态传参)

JSON 复制代码
"scripts": {
  "test": "vitest"
}

就这一行,够了。

等等,那我怎么跑watch和coverage?

答案,就是用--🤷‍♂️:

Bash 复制代码
# 1. 只跑一次
$ npm run test -- --run
# 实际执行: vitest --run

# 2. 跑watch模式
$ npm run test -- --watch
# 实际执行: vitest --watch

# 3. 跑覆盖率
$ npm run test -- --coverage
# 实际执行: vitest --coverage

# 4. 跑某个特定文件
$ npm run test -- src/my-component.test.ts
# 实际执行: vitest src/my-component.test.ts

--就像一个参数隧道 ,它把你在命令行里,跟在--后面的所有参数,原封不动地扔给了vitest命令。


一个专业的CI/CD脚本

好了,我们把pre/post--结合起来,看看一个专业的package.json是长什么样子👇。

JSON 复制代码
"scripts": {
  // 1. Lint
  "lint": "eslint .",
  "lint:fix": "eslint . --fix",

  // 2. Test
  "test": "vitest",
  "pretest": "npm run lint", // 在test前,必须先lint

  // 3. Build
  "build": "tsc && vite build",
  "prebuild": "npm run test -- --run", // 在build前,必须先test通过
  
  // 4. Publish (发布的前置钩子)
  // prepublishOnly 是一个npm内置的、比prepublish更安全的钩子
  // 它只在 npm publish 时执行,而在 npm install 时不执行
  "prepublishOnly": "npm run build" // 在发布前,必须先build
}

看看我们构建了怎样一条自动化脚本:

  1. 你兴高采烈地敲下npm publish,准备发布。
  2. npm一看,有个prepublishOnly,于是它先去执行npm run build
  3. npm一看,build有个prebuild,于是它又先去执行npm run test -- --run
  4. npm一看,test有个pretest,于是它又双叒叕先去执行npm run lint

最终的执行流是:Lint -> Test -> Build -> Publish

这些脚本,被pre钩子,自动地、强制地串联了起来。你作为开发者,根本没有机会犯错。你不可能发布一个连Lint都没过或者测试未通过的包😁。


npm scripts,它不是一个简单的脚本快捷方式。它是一个工作流(Workflow)的定义

prepost,定义了你工作流的执行顺序依赖 ,保证了代码检查 等功能,而--是确保你工作流中的脚本参数

现在,马上去打开你项目的package.json,看看它,是专业的,还是业余的呢?🤣

相关推荐
程序员码歌2 分钟前
OpenSpec 到 Superpowers:AI 编码从说清到做对
android·前端·人工智能
爱编程的小新☆2 分钟前
LangGraph4j工作流框架
前端·数据库·ai·langchain·langgraph4j
北风toto14 分钟前
为什么 IntelliJ IDEA Community 无法开发 Vue?——附解决方案
java·vue.js·intellij-idea
@PHARAOH18 分钟前
HOW - 构建一个轻量前后端一体服务
前端·微服务·服务端
无限进步_29 分钟前
【C++】C++11的类功能增强与STL变化
java·前端·数据结构·c++·后端·算法
一只小小Java31 分钟前
Echarts单表多图实现
前端·javascript·echarts
跟着珅聪学java31 分钟前
Element UI 的 Tabs 标签页开发教程
javascript·vue.js·elementui
dunky40 分钟前
Spring AI 深度解析:把 LLM 抽象成 Spring Bean 的底层逻辑
前端
星栈41 分钟前
Rust WASM 文件上传全链路:从浏览器到 S3,一个字节都不能少
前端·前端框架·开源
濮水大叔41 分钟前
告别 Django Admin!这个 NodeJS 全栈框架让你在 DTO 中直接配置 Table/Form 渲染
前端·typescript·node.js