ホームページ  >  記事  >  ウェブフロントエンド  >  npm をビルドツールとして使用する方法_html/css_WEB-ITnose

npm をビルドツールとして使用する方法_html/css_WEB-ITnose

WBOY
WBOYオリジナル
2016-06-24 11:22:212137ブラウズ

先月、この記事「Grunt と Gulp の使用をやめるべき理由」で、npm のスクリプト構成を代替手段として使用することを提案しました。npm のスクリプト構成は、これらのビルド ツールのすべての機能を実現でき、より簡潔です。より効率的で、モジュールの依存関係とメンテナンスのオーバーヘッドが少なくなります。この記事の最初の草稿は約 6,000 ワードの長さで、代替手段として npm を使用する方法について詳しく説明していましたが、その記事はチュートリアルというよりも主に私の意見を表明することを目的としていました。しかし、読者からのフィードバックは非常に強く、npm ではこれらのビルド ツールが提供する機能を完全には実装できないとのことで、読者の中には Gruntfile を直接提供して、「npm を使用してそのようなビルドを実装する方法」と質問した人もいました。解決?" ?そこで、npm を使用して一般的なビルド タスクを完了する方法を共有することに重点を置き、この記事を入門チュートリアルとしてさらに更新することにしました。

npm は、いくつかの優れた機能を提供する優れたツールであり、NodeJS の中核です。実際、私を含む多くの人が npm を毎日使用しています。実際、私の Bash の歴史では、npm は git の 2 番目に使用されているのと同じくらいです。 npm も迅速に更新され、npm を強力なモジュール管理ツールにすることを目指しています。さらに、npm には、いくつかのタスクを実行することでモジュールのライフサイクルを維持できる機能のサブセットが備わっています。つまり、強力なビルド ツールでもあります。

スクリプト構成

まず、npm を使用してビルド スクリプトを管理する方法を理解する必要があります。コア コマンドの 1 つである npm run-script コマンド (略して npm run) は、package.json からスクリプト オブジェクトを解析し、そのオブジェクトのキーを npm run の最初のパラメーターとして使用して表示されます。オペレーティング システムのデフォルトのターミナルで、このキーに対応するコマンドを実行するには、mocha test/ を実行します。 npm run xxxを実行すると、ターミナルのPATH環境変数にnode_modules/.binが追加されるため、依存関係としてインストールされたバイナリモジュールを直接実行できるようになります。つまり、「./node_modules/.bin/jshint」は必要ありません。 ** .js」または「$(npm bin)/jshint **.js」でコマンドのパスを指定します。パラメーターを指定せずに npm run コマンドを実行すると、現在実行可能なコマンドがリストされます:

{  "name": "myproject",  "devDependencies": {    "jshint": "latest",    "browserify": "latest",    "mocha": "latest"  },  "scripts": {    "lint": "jshint **.js",    "test": "mocha test/"  }}

ショートカット コマンド

npm は、いくつかのコマンドのショートカットを提供します: npm test、npm start、npm stop。たとえば、npm test はnpm run test のショートカット コマンド。ショートカット コマンドが存在する理由は次の 2 つです:

これらはほとんどのプロジェクトで使用される一般的なタスクであるため、毎回それほど多くの文字を入力する必要はありません。

さらに重要なのは、これにより、モジュールのテスト、開始、停止に対応する標準インターフェイスが提供されることです。一部の継続的統合ツール (Travis など) はこの機能を最大限に活用し、NodeJS モジュールのデフォルト コマンドとして npm test を使用します。これにより、開発者は、npm test などのコマンドを実行できるかどうかを知るためにドキュメントを読む必要がなくなるため、新しいプロジェクトに参加することが容易になります。
  1. フック
  2. もう 1 つの優れた機能は、実行可能なコマンドのスクリプト内で事前フックと事後フックを指定できることです。たとえば、npm run lint を実行する場合、対応するプレコマンドがスクリプトで定義されていない場合でも、npm は最初に npm run prelint を実行し、次に npm run lint を実行し、最後に npm run postlint を実行します。

このルールは、npm test (npm run pretest、npm run test、npm run posttest) を含むすべてのコマンドに適用されます。これらのコマンドは終了コードを認識します。つまり、プレテスト コマンドが終了時にゼロ以外の終了コードを返した場合、後続のテスト コマンドとポストテスト コマンドは実行を継続しません。フックはネストできず、prepretest などのコマンドは無視されることに注意してください。

npm は、いくつかの組み込みコマンド (インストール、アンインストール、公開、および更新) のフックも提供します。ユーザーはこれらの組み込みコマンドの動作をオーバーライドできませんが、フックを通じてこれらのコマンドの動作に影響を与えることができます:

Available scripts in the user-service package:    lint     jshint **.js  test    mocha test/

パラメータを渡す

npm 2.0.0 以降では、次の例を参照してください:

"scripts": {    "lint": "jshint **.js",    "build": "browserify index.js > myproject.min.js",    "test": "mocha test/",    "prepublish": "npm run build # also runs npm run prebuild",        "prebuild": "npm run test # also runs npm run pretest",    "pretest": "npm run lint"  }

npm run test (mocha test/) を直接実行することもできます。 - npm run test などの定義されたパラメーターから渡すコマンドの後 -- anothertest.js は mocha test/anothertest.js を実行します。より実用的な例は、mocha test を実行する npm run test -- --grep parser です。 / --grep パーサー。これにより、いくつかのコマンドを組み合わせて、高度な構成オプションを提供できるようになります。

カスタム変数

package.json ファイルの構成に任意の数の変数を指定でき、これらの変数を環境変数のようにスクリプトで使用できます:

"scripts": {    "test": "mocha test/",    "test:xunit": "npm run test -- --reporter xunit"  }

在 config 中的所有属性都将加上 npm_package_config_ 前缀暴露到环境变量中,在上面的 config 对象中有一个值为 xunit 的 reporter 属性,所以运行 npm run test 时,将执行 mocha test/ --reporter xunit。

可以通过如下两种方式来覆盖变量的值:

  1. 和上例中的 test:dev 一样,可以通过 --fooproject:reporter=spec 将 reporter 变量的值指定为 spec。具体使用时,你需要将 fooproject 替换为你自己的项目名,同时将 reporter 替换为你需要替换的变量名。
  2. 通过用户配置来覆盖,通过运行 npm config set fooproject:reporter spec 将会在 ~/.npmrc 文件中添加 fooproject:reporter=spec 项,运行 npm 时将动态读取这些配置并且替换 npm_package_config_reporter 变量的值,这意味着运行 npm run test 将执行 mocha test/ --reporter spec。可以通过运行 npm config delete fooproject:reporter 来删除这些个人配置项。比较优雅的方式是在 package.json 文件中为变量指定一些默认值,同时用户可以在 ~/.npmrc 文件中自定义某些变量的值。

老实说,我并不喜欢对这种定义和使用变量的方式,而且还有一个缺陷,那就是在 Windows 中引用变量是通过 % 加变量名,如果 scripts 中定义的是 NodeJS 脚本,并不会有什么问题,然而对于 shell 脚本却不兼容。

Windows 的问题

继续深入之前,我们先聊一个题外话。npm 依赖操作系统的 shell 作为其脚本运行的环境,Linux、Solaris、BSD 和 Mac OSX 都内置了 Bash 作为他们的默认 shell,而 Windows 却没有,在 Windows 中,npm 将使用 Windows 的命令行工具作为其运行环境。

但这也算不上什么大问题,Bash 和 Windows 中的许多语法都一样:

  • && 连续执行多个命令,前面的命令执行成功后才执行后面的命令
  • & 连续执行多个命令,不管前面命令执行成功没有,后面的命令将继续执行
  • 使命令从文件读入
  • > 把命令的输出重定向到文件中
  • | 把命令的输出重定向到下一个命令

最大的问题在于,某些命令的命名不同(cp 和 Windows 中的 COPY)和变量的引用方式(Windows 中使用 % 引用变量,而 Bash 却是使用 $)。但这些问题都是可以解的:

  1. 对于某些特殊的命令,我们可以不使用系统内置的命令,而是使用具有相同功能的 npm 模块。例如我们可以使用 rimraf 这个模块来替代内置的 rm 命令。
  2. 只使用那些跨平台兼容的语法,即便是仅仅使用 &&,>,| 和 这些语法就可以完成很多令人惊讶的功能。环境变量的引用只是冰山一角。

如何替换构建工具

现在我们回归正题,如果我们想要替换 Grunt 和 Gulp 这样的构建工具,我们需要实现这些构建工具及其插件的对等功能。我从各种项目和上篇文章的评论中收集了一些最流行的构建任务,下面我将演示如何通过 npm 来实现这些任务。

多文件处理

在上一篇文章的评论中有几个人提到:构建工具的一个优势是可以使用 *.js, *.min.css 或 assets/*/* 这样的 globs 语法来进行多文件处理。事实上这个特性的灵感来源于 Bash 中的 glob 命令。 Shell 会将命令参数(如 *.js)中的星号解析为通配符,使用连续两个星号表示跨目录递归查询。如果你正在使用 Mac 或 Linux,你可以在终端中玩一下,比如 ls *.js。

现在的问题是,Windows 的命令行并不支持该特性。新运的是,Windows 会将参数(如 *.js)逐字完整地传递给命令,这样就可以为 Windows 安装对应的兼容库就可以实现 glob 语法。在 npm 中有两个最流行的 glob 包 minimatch 和 glob,已经被 1500 多个项目依赖,包括 JSHint,JSCS,Mocha,Jade,Stylus,Node-Sass…等等,而且这个数量还在增长。

这样你就可以在 scripts 中直接使用 glob 语法了:

"devDependencies": {  "jshint": "latest"},"scripts": {  "lint": "jshint *.js"}

执行多任务

在 Grunt 和 Gulp 中可以将一些任务组合起来成为一个新的命令,尤其是在构建或测试时非常实用。在 npm 中有两种方式可以解这个问题:一是通过 pre- 和 post- 钩子,如果在执行某个任务之前需要执行某个任务(如压缩之前合并文件),这是个不错的选择;另外你还可以实用 && 这个命令连接符:

"devDependencies": {  "jshint": "latest",  "stylus": "latest",  "browserify": "latest"},"scripts": {  "lint": "jshint **",  "build:css": "stylus assets/styles/main.styl > dist/main.css",  "build:js": "browserify assets/scripts/main.js > dist/main.js",  "build": "npm run build:css && npm run build:js",  "prebuild:js": "npm run lint"}

上例中 build 包含了 build:css 和 build:js 两个任务,并且在执行 build:js 前将先执行 lint 任务。独立执行 build:css 或 build:js 也是可行的,单独执行 build:js 前也会先执行 lint。所以我们可以像这样来组合我们的任务,并且这是 Windows 兼容的。

使用数据流

Gulp 一个最大的特性是使用流将一个任务的输出 pipe 到下一个任务(Grunt 需要频繁地读取和保存文件)。在 Bash 和 Windows 的命令行中都有 | 这个管道操作符,可以用来将一个命令的输出(stdout)作为下一个命令的输入(stdin)。比方说对一个 CSS 文件,你想先通过 Autoprefixer 处理,然后 CSSMin,最后保存到文件:

"devDependencies": {  "autoprefixer": "latest",  "cssmin": "latest"},"scripts": {  "build:css": "autoprefixer -b 'last 2 versions' < assets/styles/main.css | cssmin > dist/main.css"}

就像你看到的那样,首先通过 autoprefixer 为我们的 CSS 添加浏览器厂商前缀,然后将其输出 pipe 到 cssmin 来压缩我们的 CSS,最后将整个输出保存到 dist/main.css 文件。绝大多数工具都支持 stdin 和 stdout,而且上述代码可以在 Windows,Mac 和 Linux 平台下完美兼容。

版本号

版本号管理是 Grunt 和 Gulp 中的一个常见任务,可以方便地将 package.json 中的版本号加一,为项目打 Tag 和 Commit。

npm 的一个核心功能就是版本管理,运行 npm version patch 就可以增加修订版本号:1.1.1 -> 1.1.2,运行 npm version minor 可以增加次要版本号:1.1.1 -> 1.2.0,运行 npm version major 可以增加主版本号:1.1.1 -> 2.0.0,这几个命令将自动为你的项目打 Tag 和 Commit,就剩下 git push 和 npm publish 了。

还可以自定义这几个命令的行为。如果不想为项目打 Tag,你可以在命令后面加上 --git-tag-version=false,或者通过 npm config set git-tag-version false 将其设置为默认项。如果想自定义提交信息呢?可以这样 npm version patch -m "Bumped to %s",或直接设置为默认项 npm config set message "Bumped to %s"。甚至可以通过 --sign-git-tag=true 为 Tag 签名,也可以通过 npm config set sign-git-tag true 将其设置为默认项。

清理

很多构建工具都会有一个 clean 任务,用来清理构建过程或构建后生成的文件,在 Bash 中自带了一个清理命令 rm,在命令后面加上 -r 参数可以递归删除目录。这个命令再简单不过了:

"scripts": {  "clean": "rm -r dist/*"}

如果想兼容 Windows 可以使用 rimraf 这个平台无关的兼容模块:

"devDependencies": {  "rimraf": "latest"},"scripts": {  "clean": "rimraf dist"}

文件名 Hash 化

在 Grunt 和 Gulp 分别有 grunt-hash 和 gulp-hash 两个插件,用来根据文件的内容生成一个 hash 化后的文件名。要用已有的命令来实现这个功能还是比较难,我搜索了 npm 模块,也没有找到具有相同功能的模块,所以最后我自己实现了一个 – hashmark。该支持流操作,可以作为某些 Grunt/Gulp 插件的依赖项。继续之前的例子,我们可以将构建结果 pipe 到一个具有 hash 值文件名的文件中:

"devDependencies": {  "autoprefixer": "latest",  "cssmin": "latest"},"scripts": {  "build:css": "autoprefixer -b '> 5%' < assets/styles/main.css | cssmin | hashmark -l 8 'dist/main.#.css'"}

现在执行 build:css 任务将得到一个类似 dist/main.3ecfca12.css 这样的文件。

Watch

这也是 Grunt/Gulp 备受欢迎的原因之一,很多构建工具都支持监视文件系统的变化然后执行相应的构建或刷新任务,这在开发过程中非常实用。这也是在上篇文章中许多开发者关注的问题之一,他们认为如果没有 watch 类似的任务就黯然失色了。

好吧,其实很多工具自身就提供了这个选项,可以用于监听复杂的文件系统。比如 Mocha 就提供了 -w 选项,还有 Stylus、Node-Sass、Jade 和 Karma 等等。你可以这样使用:

"devDependencies": {  "mocha": "latest",  "stylus": "latest"},"scripts": {  "test": "mocha test/",  "test:watch": "npm run test -- -w",  "css": "stylus assets/styles/main.styl > dist/main.css",  "css:watch": "npm run css -- -w"}

当然,并不是所有工具都提供了该选项,就算都有这个选项,有时候你还希望在文件变化时触发某个任务集合,不用担心,有很多模块可以监视文件变化,并在文件变化是触发某个命令,比如 watch、onchange、 dirwatch 这些模块,甚至可以用 nodemon:

"devDependencies": {  "stylus": "latest",  "jade": "latest",  "browserify": "latest",  "watch": "latest",},"scripts": {  "build:js": "browserify assets/scripts/main.js > dist/main.js",  "build:css": "stylus assets/styles/main.styl > dist/main.css",  "build:html": "jade assets/html/index.jade > dist/index.html",  "build": "npm run build:js && npm run build:css && npm run build:html",  "build:watch": "watch 'npm run build' .",}

就是这么简单,仅仅 13 行配置就可以监视整个项目文件,当任何文件改变时就自动执行构建 HTML、CSS 和 JS 的任务,直接执行 npm run build:watch 就可以开始无痛开发了。使用一个我写的模块 Parallelshell,用于并发执行多个命令,我们还可以做一些优化:

"devDependencies": {  "stylus": "latest",  "jade": "latest",  "browserify": "latest",  "watch": "latest",  "parallelshell": "latest"},"scripts": {  "build:js": "browserify assets/scripts/main.js > dist/main.js",  "watch:js": "watch 'npm run build:js' assets/scripts/",  "build:css": "stylus assets/styles/main.styl > dist/main.css",  "watch:css": "watch 'npm run build:css' assets/styles/",  "build:html": "jade index.jade > dist/index.html",  "watch:html": "watch 'npm run build:html' assets/html",  "build": "npm run build:js && npm run build:css && npm run build:html",  "build:watch": "parallelshell 'npm run watch:js' 'npm run watch:css' 'npm run watch:html'",}

运行 npm run build:watch 时将通过 Parallelshell 分别运行独立的监视任务,如果只有 CSS 文件发生了变化,那么将只执行 CSS 构建任务。Parallelshell 将每个任务的输出(stdout 和 stderr)连接到主进程,并监听了 exitCode 来确保构建任务的日志输出(这与 & 这个命令连接符不同)。

LiveReload

LiveReload 也是一个很受欢迎的特性:当文件变化时自动刷新浏览器中的页面,live-reload 这个 npm 模块可以实现这个功能,看下面例子:

"devDependencies": {  "live-reload": "latest",},"scripts": {  "livereload": "live-reload --port 9091 dist/",}

<!-- In your HTML file -->  <script src="//localhost:9091"></script>

执行 npm run livereload 后,dist/ 目录下的任何改变都将通知到你访问的 HTML 页面,并触发页面自动刷新。

自定义脚本

那么如果一个模块并没有提供相应的命令行工具,如 favicon,该怎么办呢?我们可以自己写一段 JavaScript 脚本来执行相应的功能,这也正是 Grunt/Gulp 插件所做的事情,还可以给模块维护者提交 PullRequest 让他们提供一个命令行工具:

// scripts/favicon.jsvar favicons = require('favicons');  var path = require('path');  favicons({      source: path.resolve('../assets/images/logo.png'),    dest: path.resolve('../dist/'),});

"devDependencies": {  "favicons": "latest",},"scripts": {  "build:favicon": "node scripts/favicon.js",}

一个相对复杂的例子

在上篇文章的评论中有些人说我忽视构建工具的关键点:构建工具不仅仅是用于执行单个任务,更重要的是它们可以将单个任务连接起来成为复杂的构建流程。所以这里我就将上面演示过的例子组合起来成为一个复杂的构建任务,这和具有上百行代码的 Gruntfile 所做的事情一样。在本例中我想完成以下构建任务:

  • Lint、Test 和编译 JS 文件,生成对应的 sourcemap,hash 化文件名,最后上传到 S3
  • 将 Stylus 编译为一个独立的 Hash 化的 CSS 文件,生成对应的 sourcemap,并上传到 S3
  • 为编译后测试添加 watcher
  • 启动一个静态服务器,用于浏览和测试编译结果
  • 为 CSS 和 JS 文件添加 livereload
  • 设计一个与构建环境相关的总任务,将所有相关任务包括进来,这样就可以运行这个简单的命令来完成复杂的构建过程
  • 自动打开浏览器并访问我们的测试页面

我将本例的完整代码放在 npm-scripts-example 这个代码库中,下面是我们最关注的部分:

"scripts": {    "clean": "rimraf dist/*",    "prebuild": "npm run clean -s",    "build": "npm run build:scripts -s && npm run build:styles -s && npm run build:markup -s",    "build:scripts": "browserify -d assets/scripts/main.js -p [minifyify --compressPath . --map main.js.map --output dist/main.js.map] | hashmark -n dist/main.js -s -l 8 -m assets.json 'dist/{name}{hash}{ext}'",    "build:styles": "stylus assets/styles/main.styl -m -o dist/ && hashmark -s -l 8 -m assets.json dist/main.css 'dist/{name}{hash}{ext}'",    "build:markup": "jade assets/markup/index.jade --obj assets.json -o dist",    "test": "karma start --singleRun",    "watch": "parallelshell 'npm run watch:test -s' 'npm run watch:build -s'",    "watch:test": "karma start",    "watch:build": "nodemon -q -w assets/ --ext '.' --exec 'npm run build'",    "open:prod": "opener http://example.com",    "open:stage": "opener http://staging.example.internal",    "open:dev": "opener http://localhost:9090",    "deploy:prod": "s3-cli sync ./dist/ s3://example-com/prod-site/",    "deploy:stage": "s3-cli sync ./dist/ s3://example-com/stage-site/",    "serve": "http-server -p 9090 dist/",    "live-reload": "live-reload --port 9091 dist/",    "dev": "npm run open:dev -s & parallelshell 'npm run live-reload -s' 'npm run serve -s' 'npm run watch -s'"  }

 

上面的 -s 是禁止 npm 输出任何日志信息,你可以尝试删除这个选项来看看有什么不同。

如果用 Grunt 来完成相同的构建任务,则需要上百行的 Gruntfile 代码,并且还需要十多个额外的模块。就可读性而言,npm 的 scripts 虽然表面上可读性并不是那么高,但就我而言我可以脚本语言更加容易被理解,每个任务所做的事情也更加清楚。

总结

希望通过本文你了解到了 npm 在构建方面的能力,当需要构建一个项目时 Grunt/Gulp 并不一定是首选工具,或许 npm 就能满足你的需求。

参考阅读

  • Why we should stop using Grunt & Gulp
声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。