基于npm scripts的前端构建工具
在前端开发工作中,为了避免重复的工作,我们通常会借助一些任务运行器去完成常见的前端任务,例如文件合并,脚本压缩,生成sprite图。世面上有很多这样的工具,比较成熟的有 grunt
,gulp
,webpack
,shell
,ant
。使用这些工具可以大大的提高我们的日常产出,让我们把开发的重心放在项目本身的业务上,而不必在常见任务上浪费过多的时间。我们不去讨论这些工具之间的优势和差异,一千个读者就有一千个哈姆雷特。在工作中,我使用过的前端任务工具包括但不限于 grunt
,gulp
,他们的确给我带来了很多的帮助,当我发现 npm scripts
命令就能很好的完成这些任务时,我不得不跟他们告别。相较于 grunt
和 gulp
, npm scripts
具有更大的优势,使用 npm scripts
执行构建自动化是一种更简单的方式。
问题
- 对插件作者的依赖
- 令人沮丧的调试
- 过时的文档
为何会忽略掉 npm scripts
- 认为
npm scripts
需要强大的命令行技巧 - 认为
npm scripts
不够强大 - 认为
Gulp
的流对于快速构建来说是不可或缺的 - 认为
npm scripts
无法实现跨平台运行
npm scripts
npm
会在项目的 package.json
文件中寻找 scripts
区域,其中包括 npm test
和 npm start
等命令。事实上,npm test
和 npm start
是 npm run test
和 npm run start
的简写,你可以使用 npm run
来运行 scripts
里的任何条目。
使用 npm run
的方便之处在于,npm
会自动把 node_modules/.bin
加入 $PATH
,当脚本内容结束,则子shell关闭,回到父shell中,这样你可以直接运行依赖和开发依赖程序,不需要全局安装了,只要 npm
上的包提供命令行接口,你就可以直接使用它们。
常见任务
package.json
模板
{ "name": "npm-scripts-boilerplate", "version": "1.0.0", "scripts": { ... }, "devDependencies": { ... } }
我们的构建脚本命令放在 scripts
对象里,对于想使用的工具依赖库都放在 devDependencies
对象里。
项目结构
├── CHANGELOG.md ├── README.md ├── build │ ├── images │ │ └── sprites.png │ └── js │ ├── bundle.js │ └── bundle.min.js ├── docs ├── package.json ├── src │ ├── config.rb │ ├── css │ │ ├── main.css │ │ ├── main.css.map │ │ └── spirtes.css │ ├── images │ │ ├── icons │ │ └── sprites.png │ ├── index.html │ ├── js │ │ └── app.js │ ├── media │ └── sass │ ├── _landscape.scss │ ├── _mediaquery.scss │ ├── _mixins.scss │ ├── _normalize.scss │ ├── _utilities.scss │ ├── _variables.scss │ ├── main.scss │ └── mixins ├── test
编译 SCSS
将 scss
编译为 css
,使用 node-sass.
安装 node-sass
命令行输入:
npm install --save-dev node-sass
或者 npm install -D node-sass
命令行脚本
安装完成后,我们可以使用 node-sass
工具来构建我的任务: node-sass --output-style compressed -o dist/css src/sass
上面的命令行功能比较容易理解,就是 src/sass
下的 *scss
文件编译成 css
文件,并输出到 dist/css
目录下,文件内容的输出风格为 compressed
,输出风格可以任意选择,可以使用 node-sass help
查看帮助。
写入 package.json
的 scripts
对象里
把上面的代码放到 package.json
文件里,package.json
的内容变成:
package.json
"scripts": { "scss": "node-sass --output-style compressed -o dist/css src/sass" }
运行任务
npm run scss
使用 PostCSS
自动补全 CSS
浏览器厂商前缀
用到的库 postcss-cli,autoprefixer
安装 postcss-cli
, autoprefixer
npm install -D postcss-cli autoprefixer
写入 package.json 的 scripts 对象里
package.json
"scripts": { ... "autoprefixer": "postcss -u autoprefixer -r dist/css/*" }
可以通过设置 autoprefixer
的相关参数来改变 autoprefixer
默认浏览器支持。
校验 javascript
eslint" class="reference-link">安装 eslint
npm i -D eslint
或者使用 npm install --save-dev eslint
,前者是后者的简写方式。
运行命令,初始化一些 hint
的基本规则
eslint --init
写入 package.json
的 scripts
对象里
package.json
"scripts": { ... "lint": "eslint src/js" }
运行任务
npm run lint
压缩/混淆 javascript
依赖库 uglify-js
安装
npm i -D uglify-js
写入 package.json
的 scripts
对象里
package.json
"scripts": { ... "uglify": "mkdir -p dist/js && uglifyjs src/js/*.js -m -o dist/js/app.js" }
运行任务
npm run uglify
压缩图片
依赖库 imagemin-cli
安装
npm i -D imagemin-cli
写入 package.json
的 scripts
对象里
package.json
"scripts": { ... "imagemin": "imagemin src/images dist/images -p", }
运行任务
npm run imagemin
开启http静态服务器
依赖库 BrowserSync
安装
npm i -D browser-sync
写入 package.json
的 scripts
对象里
package.json
"scripts": { ... "serve": "browser-sync start --server --files 'dist/css/*.css, dist/js/*.js'" }
监听css和javascript的改变
依赖库: onchange
安装
npm i -D onchange
写入 package.json
的 scripts
对象里
package.json
"scripts": { ... "watch:css": "onchange 'src/scss/*.scss' -- npm run build:css", "watch:js": "onchange 'src/js/*.js' -- npm run build:js", }
使用 npm scripts
钩子创建任务
pre-
和 post-
脚本命令,npm run
为每条命令提供了 pre-
和 post-
两个钩子(hook)。以 npm run install
为例,如果我们的 scripts
字段定义了 postinstall
:
package.json
"scripts": { ... "postinstall": "npm run watch:all" }
执行 npm run install
时,任务 postinstall
也会立即执行,在团队里,这是一种非常好的协作方式,当有人克隆我们的代码后,执行npm install
,我们的 watch:all
任务也会跟着执行,它会自动的开启一个服务器并打开默认的浏览器,同时也会监听文件的修改。
使用内部变量
scripts
字段可以使用一些内部变量,主要是 package.json
的各种字段。内部变量的主要特征是 $npm_package_key
。
比如,package.json
的内容是
{ "name":"mobile-scaffold", "version":"1.0.0" }
那么变量 $npm_package_name
的值是mobile-scaffold
,变量 npm_package_version
的值是 1.0.0
。
{ "version": "1.0.0", "scripts": { ... "bundle": "mkdir -p build/$npm_package_version/" } }
运行 npm run bundle
以后,将会生成 build/1.0.0/
子目录。
config
字段也可以用于设置内部字段。
{ "name": "mobile-scaffold", "config": { "port": "3000" }, "scripts": { ... "serve": "http-server -p $npm_package_config_port" } }
上面代码中,变量 npm_package_config_port
对应的就是 3000
。
使 npm run
命令可以接收参数
在一些任务里,我们需要根据不同的情形来传递参数,最常见的例子就是提交 git commit -m
需要填写一些信息,这时,npm run
能接收参数就显得非常重要了。
package.json
的内容是:
{ "name": "mobile-scaffold", "scripts": { ... "preci": "git add -A", "ci": "git commit -m", "postci": "git push origin master" } }
运行 npm run ci -- "add git command task"
,这条命令执行后,相继发生:
preci
任务执行ci
任务执行postci
任务执行
痛点及解决之道
显然,使用 npm scripts
也存在着一些问题: JSON
规范并不支持注释,因此无法在 package.json
中添加注释。不过有一些办法可以突破这个限制:
- 编写小巧、命名良好、单一目的的脚本
- 分离文档与脚本(比如说放在README.md中)
- 调用单独的.js文件