为什么使用 npm 脚本?

Avatar of Damon Bauer
Damon Bauer

DigitalOcean 为您旅程的每个阶段提供云产品。 立即开始使用 $200 免费额度!

越来越多的观点(例如)认为,直接使用 node 包及其提供的命令行接口是一个不错的选择,而不是将功能抽象到任务运行器后面。部分原因是:您无论如何都在使用 npm,npm 提供了脚本功能,为什么不直接使用它呢?但它不仅仅如此。让我们一起了解这种想法,以及如何在前端开发构建过程中完成许多最重要的任务。

在过去的六个月左右,我开始在我的项目中使用 npm 脚本。在此之前,我使用的是 Gulp,再之前是 Grunt。它们为我提供了良好的服务,并通过自动化许多我过去手动执行的操作,帮助我更快、更高效地完成工作。然而,我开始觉得我是在与工具作斗争,而不是专注于我自己的代码。

Grunt、Gulp、Broccoli、Brunch 等都需要您将任务适配到它们的范式和配置中。每个工具都有自己的语法、特性和需要学习的陷阱。这增加了代码复杂性和构建复杂性,并使您专注于修复工具而不是编写代码。

这些构建工具依赖于包装核心命令行工具的插件。这在核心工具之上创建了另一层抽象,这意味着发生不良情况的可能性更大。

以下是我多次遇到的三个问题

  • 如果您想要使用的命令行工具没有相应的插件,那么您将很不幸(除非您自己编写)。
  • 您尝试使用的插件包装了您想要使用的工具的旧版本。您使用的插件与核心工具的当前版本之间,功能和文档并不总是匹配的。
  • 错误并不总是得到很好的处理。如果插件失败,它可能不会传递核心工具的错误,从而导致挫败感,并且您实际上不知道如何调试问题。

但是,请记住……

让我说清楚:**如果您对当前的构建系统感到满意,并且它满足了您所有的需求,那么您可以继续使用它!** 仅仅因为 npm 脚本变得越来越流行并不意味着您应该放弃它。继续专注于编写代码,而不是学习更多工具。如果您开始感觉自己在与工具作斗争,那么我建议您考虑使用 npm 脚本。

如果您决定要调查或开始使用 npm 脚本,请继续阅读!您将在本文的其余部分找到大量示例任务。此外,我还创建了 npm-build-boilerplate,其中包含所有这些任务,您可以将其用作起点。让我们开始吧!

编写 npm 脚本

我们将大部分时间花费在 `package.json` 文件中。这是我们所有依赖项和脚本都将存在的地方。以下是我样板项目中简化后的版本

{
  "name": "npm-build-boilerplate",
  "version": "1.0.0",
  "scripts": {
    ...
  },
  "devDependencies": {
    ...
  }
}

在继续的过程中,我们将构建我们的 `package.json` 文件。我们的脚本将进入 `scripts` 对象,我们想要使用的任何工具都将被安装并放入 `devDependencies` 对象中。

在开始之前,以下是我将在本文中参考的项目的示例结构

将 SCSS 编译为 CSS

我是一个 SCSS 的重度用户,所以这就是我将要使用的。为了将 SCSS 编译为 CSS,我转向了 node-sass。首先,我们需要安装 `node-sass`;通过在命令行中运行以下命令来执行此操作

npm install --save-dev node-sass

这将在您的当前目录中安装 `node-sass`,并将其添加到 `package.json` 中的 `devDependencies` 对象中。当其他人运行您的项目时,这尤其有用,因为他们将拥有运行项目所需的一切。安装完成后,我们可以在命令行中使用它

node-sass --output-style compressed -o dist/css src/scss

让我们分解一下此命令的作用。从最后开始,它表示:在 `src/scss` 文件夹中查找任何 SCSS 文件;将编译后的 CSS 输出(`-o` 标志)到 `dist/css`;压缩输出(使用 `--output-style` 标志,并将“compressed”作为选项)。

现在我们已经在命令行中使其工作,让我们将其移动到 npm 脚本中。在您的 `package.json` 的 `scripts` 对象中,添加如下内容

"scripts": {
  "scss": "node-sass --output-style compressed -o dist/css src/scss"
}

现在,返回命令行并运行

npm run scss

您将看到与在命令行中直接运行 `node-sass` 命令相同的输出。

在本帖的剩余部分中,每当我们创建 npm 脚本时,您都可以使用类似上述的命令来运行它。

只需将 `scss` 替换为您想要运行的任务的名称。

正如您将看到的,我们使用的许多命令行工具都提供了许多选项,您可以使用这些选项来完全按照您的需要进行配置。例如,以下是 (node-sass 选项) 的列表。以下是一个不同的设置示例,展示了如何传递多个选项

"scripts": {
  "scss": "node-sass --output-style nested --indent-type tab --indent-width 4 -o dist/css src/scss"
}

使用 PostCSS 为 CSS 添加前缀

现在我们已经将 Scss 编译为 CSS,我们可以使用 Autoprefixer 和 PostCSS 自动添加供应商前缀。我们可以同时安装多个模块,用空格隔开它们

npm install --save-dev postcss-cli autoprefixer

我们安装了两个模块,因为 PostCSS 本身不执行任何操作。它依赖于其他插件(如 Autoprefixer)来操作您提供的 CSS。

安装必要的工具并将其保存到 `devDependencies` 后,在您的 `scripts` 对象中添加一个新任务

"scripts": {
  ...
  "autoprefixer": "postcss -u autoprefixer -r dist/css/*"
}

此任务表示:嘿 `postcss`,使用(`-u` 标志)`autoprefixer` 替换(`-r` 标志)`dist/css` 中的任何 `.css` 文件,并使用供应商前缀代码。就是这样!需要更改 autoprefixer 的默认浏览器支持?很容易添加到脚本中

"autoprefixer": "postcss -u autoprefixer --autoprefixer.browsers '> 5%, ie 9' -r dist/css/*"

同样,您可以使用许多选项来配置自己的构建:postcss-cliautoprefixer

检查 JavaScript 代码

在编写代码时保持标准的格式和风格对于最大程度地减少错误并提高开发人员效率非常重要。“代码检查”可以帮助我们自动执行此操作,因此让我们使用 eslint 添加 JavaScript 代码检查。

再次安装软件包;这次,让我们使用快捷方式

npm i -D eslint

这与以下命令相同

npm install --save-dev eslint

安装完成后,我们将设置一些基本规则,使用 `eslint` 对我们的代码进行检查。运行以下命令以启动向导

eslint --init

我建议选择“回答有关您的样式的问题”,并回答它提出的问题。这将在您的项目根目录中生成一个新文件,`eslint` 将根据该文件检查您的代码。

现在,让我们在 `package.json` 的 `scripts` 对象中添加一个代码检查任务

"scripts": {
  ...
  "lint": "eslint src/js"
}

我们的代码检查任务只有 13 个字符长!它在 `src/js` 文件夹中查找任何 JavaScript 文件,并根据之前生成的配置对其进行检查。当然,您可以 使用各种选项

压缩 JavaScript 文件

让我们开始合并和压缩我们的 JavaScript 文件,我们可以使用 uglify-js 来完成。我们首先需要安装 `uglify-js`

npm i -D uglify-js

然后,我们可以在 `package.json` 中设置我们的压缩任务

"scripts": {
  ...
  "uglify": "mkdir -p dist/js && uglifyjs src/js/*.js -m -o dist/js/app.js"
}

npm 脚本的一大优点是,它们本质上是您想要反复运行的命令行任务的别名。这意味着您可以在脚本中直接使用标准的命令行代码!此任务使用了两个标准的命令行功能,`mkdir` 和 `&&`。

此任务的前半部分,mkdir -p dist/js 表示:创建文件夹结构(mkdir),但仅在该文件夹不存在时创建(-p 标记)。一旦成功完成,就会运行 uglifyjs 命令。&& 允许您将多个命令链接在一起,如果前一个命令成功完成,则依次运行每个命令。

此任务的后半部分告诉 uglifyjssrc/js/ 中的所有 JS 文件(`*.js`)开始,应用“混淆”命令(-m 标记),并将结果输出到 dist/js/app.js。再次强调,请查看相关工具的文档以获取完整的选项列表

让我们更新我们的 uglify 任务以创建 dist/js/app.js 的压缩版本。链接另一个 uglifyjs 命令并传递“压缩”(-c)标记。

"scripts": {
  ...
  "uglify": "mkdir -p dist/js && uglifyjs src/js/*.js -m -o dist/js/app.js && uglifyjs src/js/*.js -m -c -o dist/js/app.min.js"
}

压缩图片

现在让我们将注意力转向压缩图片。根据httparchive.org,互联网上排名前 1000 个 URL 的平均页面大小为 1.9mb,其中图片占了 1.1mb。提高页面速度的最佳方法之一是减小图片的大小。

安装imagemin-cli

npm i -D imagemin-cli

Imagemin 非常棒,因为它可以压缩大多数类型的图片,包括 GIF、JPG、PNG 和 SVG。您可以向它传递一个包含图片的文件夹,它会压缩其中的所有图片,如下所示

"scripts": {
  ...
  "imagemin": "imagemin src/images dist/images -p",
}

此任务告诉 imagemin 查找并压缩 src/images 中的所有图片,并将它们放在 dist/images 中。传递 -p 标记是为了在可能的情况下创建“渐进式”图片。查看所有可用选项的文档。

SVG 雪碧图

围绕 SVG 的热议在过去几年有所增加,这是有充分理由的。它们在所有设备上都清晰锐利,可以使用 CSS 编辑,并且对屏幕阅读器友好。但是,SVG 编辑软件通常会留下多余且不必要的代码。幸运的是,svgo 可以通过删除所有这些代码来提供帮助(我们将在下面安装它)。

您还可以自动化组合和雪碧化 SVG 以制作单个 SVG 文件的过程(此处了解更多关于此技术的信息)。为了自动化此过程,我们可以安装svg-sprite-generator

npm i -D svgo svg-sprite-generator

模式您现在可能已经熟悉了:安装后,在您的 package.json scripts 对象中添加一个任务

"scripts": {
  ...
  "icons": "svgo -f src/images/icons && mkdir -p dist/images && svg-sprite-generate -d src/images/icons -o dist/images/icons.svg"
}

请注意,icons 任务基于两个 && 指令执行三件事。首先,我们使用 svgo,传递一个包含 SVG 的文件夹(-f 标记);这将压缩文件夹内的所有 SVG。其次,如果 dist/images 文件夹不存在,我们将创建它(使用 mkdir -p 命令)。最后,我们使用 svg-sprite-generator,传递一个包含 SVG 的文件夹(-d 标记)和我们希望 SVG 雪碧图输出的路径(-o 标记)。

使用 BrowserSync 启动服务器并自动注入更改

拼图的最后几块之一是BrowserSync。它可以执行一些操作:启动本地服务器,自动将更新的文件注入到任何连接的浏览器中,以及在浏览器之间同步点击和滚动。安装它并添加一个任务

npm i -D browser-sync
"scripts": {
  ...
  "serve": "browser-sync start --server --files 'dist/css/*.css, dist/js/*.js'"
}

我们的 BrowserSync 任务使用当前路径作为默认根目录启动服务器(--server 标记)。--files 标记告诉 BrowserSync 监视 dist 文件夹中的任何 CSS 或 JS 文件;每当其中的内容发生更改时,就会自动将更改的文件注入到页面中。

您可以打开多个浏览器(甚至在不同的设备上),它们都将实时获取更新的文件更改!

组合任务

使用上面所有的任务,我们能够

  • 将 SCSS 编译成 CSS 并自动添加供应商前缀
  • 检查和压缩 JavaScript
  • 压缩图片
  • 将一个包含 SVG 的文件夹转换为单个 SVG 雪碧图
  • 启动本地服务器并自动将更改注入到连接到服务器的任何浏览器中

我们不要止步于此!

组合 CSS 任务

让我们添加一个组合两个 CSS 相关任务(预处理 Sass 和运行 Autoprefixer)的任务,这样我们就不必分别运行每个任务。

"scripts": {
  ...
  "build:css": "npm run scss && npm run autoprefixer"
}

当您运行 npm run build:css 时,它会告诉命令行运行 npm run scss;当它成功完成时,它将(&&)运行 npm run autoprefixer

就像我们的 build:css 任务一样,我们可以将 JavaScript 任务链接在一起,以便更容易运行

组合 JavaScript 任务

"scripts": {
  ...
  "build:js": "npm run lint && npm run uglify"
}

现在,我们可以调用 npm run build:js 来一步完成 JavaScript 的检查、连接和压缩!

组合剩余的任务

我们也可以对图片任务执行相同操作,以及一个将所有构建任务组合成一个的任务

"scripts": {
  ...
  "build:images": "npm run imagemin && npm run icons",
  "build:all": "npm run build:css && npm run build:js && npm run build:images",
}

监视更改

到目前为止,我们的任务需要修改文件,然后切换回命令行并运行相应的任务。我们可以做的最有用的一件事是添加监视更改的任务,当文件更改时自动运行任务。为此,我建议使用onchange。照常安装

npm i -D onchange

让我们为 CSS 和 JavaScript 设置监视任务

"scripts": {
  ...
  "watch:css": "onchange 'src/scss/*.scss' -- npm run build:css",
  "watch:js": "onchange 'src/js/*.js' -- npm run build:js",
}

以下是这些任务的细分:onchange 期望您将要监视的文件的路径作为字符串传递。我们将传递我们的源 SCSS 和 JS 文件以进行监视。我们想要运行的命令位于 -- 之后,并且它将在添加、更改或删除给定路径中的文件时运行。

让我们再添加一个监视命令来完成我们的 npm 脚本构建过程。

安装另一个包,parallelshell

npm i -D parallelshell

再次,在 scripts 对象中添加一个新任务

"scripts": {
  ...
  "watch:all": "parallelshell 'npm run serve' 'npm run watch:css' 'npm run watch:js'"
}

parallelshell 接收多个字符串,我们将传递多个 npm run 任务来运行。

为什么使用 parallelshell 来组合多个任务而不是像以前的任务那样使用 &&?起初,我尝试过这样做。问题在于 && 将命令链接在一起,并在启动下一个命令之前等待每个命令成功完成。但是,由于我们正在运行 watch 命令,因此它们永远不会结束!我们将陷入无限循环中。

因此,使用 parallelshell 使我们能够同时运行多个 watch 命令。

此任务使用 npm run serve 任务启动带有 BrowserSync 的服务器。然后,它开始我们针对 CSS 和 JavaScript 文件的监视命令。每当 CSS 或 JavaScript 文件发生更改时,监视任务都会执行相应的构建任务;由于 BrowserSync 设置为监视 dist 文件夹中的更改,因此它会自动将新文件注入到连接到其 URL 的任何浏览器中。太棒了!

其他有用的任务

npm 带有许多内置任务,您可以将其挂钩。让我们再编写一个利用这些内置脚本之一的任务。

"scripts": {
  ...
  "postinstall": "npm run watch:all"
}

postinstall 命令会在你运行命令行中的 npm install 后立即执行。这在团队协作时非常有用;当有人克隆你的项目并运行 npm install 时,我们的 watch:all 任务会立即启动。他们会自动启动服务器,打开浏览器窗口,并监控文件更改。

总结

呼!我们做到了!希望你能够学习到一些关于使用 npm 脚本作为构建流程以及命令行的知识。

以防万一你错过了,我创建了一个包含所有这些任务的 npm-build-boilerplate 项目,你可以将其用作起点。如果你有任何问题或意见,请 在 Twitter 上联系我 或在下方留言。我非常乐意提供帮助!