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 小时前
Day 12:09. 基于Nuxt开发博客项目-使用NuxtContent构建博客模块
前端·后端·程序员
合作小小程序员小小店2 小时前
web网页开发,在线短视频管理系统,基于Idea,html,css,jQuery,java,springboot,mysql。
java·前端·spring boot·mysql·vue·intellij-idea
n***29322 小时前
前端动画性能优化,减少重绘重排
前端·性能优化
mCell2 小时前
React 如何处理高频的实时数据?
前端·javascript·react.js
随笔记2 小时前
HbuilderX载入项目,运行后唤起微信开发者工具,提示:Error: Fail to open IDE,唤醒不起来怎么办
javascript·微信小程序·uni-app
Lsx_2 小时前
一文读懂 Uniapp 小程序登录流程
前端·微信小程序·uni-app
吃饺子不吃馅2 小时前
面试过别人后,我对面试祛魅了
前端·面试·github
论迹2 小时前
【JavaEE】-- Spring Web MVC入门
前端·spring·java-ee
uhakadotcom2 小时前
fastapi的最新版本,提供了哪些新api可供使用
前端·面试·github