Nodejs实战 —— 前端构建系统
读 《node.js实战2.0》,进行学习记录总结。
前端构建系统
本章将介绍如何使用 npm 脚本、Gulp 和 Webpack 搭建易于维护的代码。因为可以使用 Babel 转译,所以无须担心浏览器对ES2015的支持。
了解基于 Node 的前端开发
npm 可以用于前端模块,例如 React, 也可以用于后台代码,比如 Express。lodash作为通用库,既可以用在 Node 中,也可以用在浏览器中。
也有专门针对客户端的模块系统,比如 Bower, 也可以接着使用这些工具,但作为 Node 开发人员,应该优先考虑 npm。
另外比如像 Babel这种能将 ES2015转换成支持更广泛的 ES5 代码的转译器。还有像 UglifyJS这样的缩码器,以及像 ESLint这样的用来检验代码正确性的剪毛器也都越来越被前端开发人员所青睐。
为了可以更方便地构建环境,会使用 npm 脚本、Gulp或 Webpack。
用 npm 运行脚本
Node 有 npm , 而 npm 能运行脚本。因此,用户要能够调用 npm start 和 npm test 之类的命令,在项目的 package.json 文件中,有个 scripts 属性,可以在那里指定自己的 npm start 命令:
{
...
"scripts":{
"start": "node server.js"
},
...
}
node server.js 是默认的 start 命令,记得要创建 server.js 文件。我们一般会定义 test 属性,因为可以把测试框架作为依赖项,然后用 npm test 来运行测试脚本。例如,选择 Mocha 来测试,并且安装:
npm i -S -D
如果在 package.json 中添加下面的语句,就不用全局安装了:
{
...
"scripts":{
"test":"./node_modules/.bin/mocha_test/*.js"
}
}
上例中的参数是传递给了 Mocha, 也可以运行 npm 脚本时用两个连字符传入参数:
npm test -- 'test/*.js'
下表是常用的 npm 命令:
| 命令 | package.json 属性 | 应用案例 | | ------------------- '| ----------------------------------- | ------------------------------------------------------------ |' | start | scripts.start | 启动Web 应用服务器或 Electron 程序 | | stop | scripts.stop | 停掉 Web 应用服务器 | | restart | | 运行 stop,然后运行 restart | | install, postinstall | scripts.install, scripts.postinstall | 在安装了包之后运行本地构建命令,注意,postinstall 只能通过 npm run postinstall 运行 |
创建定制的 npm
npm run 命令等于 npm run-script,用 npm run script-name 可以运行任何脚本。用 Babel 构建客户端脚本的命令:
新建项目,然后安装必要的依赖项:
npm init -y
npm i --S -D babel-cli babel-preset-es2015
echo '{ "presets": ["es2015"] }' > .babelrc
现在有了一个具有基本 Babel ES2015工具和插件的 Node项目。接下来打package.json,在 scripts 下面添加 babel 属性。它应该安装到了 node_modules/.bin 文件夹下的脚本:
"babel": "./node_modules/.bin/babel browser.js -d build/"
下面是 ES2015 语法写的代码,存为 browser.js 文件:
class Example {
render() {
return '<h1>Example</h1>';
}
}
const example = new Example();
console.log(example.render());
运行 npm run babel , 会出现一个 build 文件夹,里面有转义的 brower.js ,如下:
'use strict';
var _createClass = (function () {
function defineProperties(target, props) {
for (var i = 0; i < props.length; i++) {
var descriptor = props[i];
descriptor.enumerable = descriptor.enumerable || false;
descriptor.configurable = true;
if ('value' in descriptor) descriptor.writable = true;
Object.defineProperty(target, descriptor.key, descriptor);
}
}
return function (Constructor, protoProps, staticProps) {
if (protoProps) defineProperties(Constructor.prototype, protoProps);
if (staticProps) defineProperties(Constructor, staticProps);
return Constructor;
};
})();
function _classCallCheck(instance, Constructor) {
if (!(instance instanceof Constructor)) {
throw new TypeError('Cannot call a class as a function');
}
}
var Example = (function () {
function Example() {
_classCallCheck(this, Example);
}
_createClass(Example, [
{
key: 'render',
value: function render() {
return '<h1>Example</h1>';
}
}
]);
return Example;
})();
var example = new Example();
console.log(example.render());
如果构建项目时,只需要做这件事,那么可以将这个任务的名称改为 build,但一般会加上 UglifyJS:
npm i -S-D uglify-es
同样的在 package.json 中 添加脚本来调用 node_modules/.bin/uglifyJS
"uglify": "./node_modules/.bin/uglifyjs build/browser.js -o build/browser.min.js"
然后,可以运行 npm run uglify. 也可以组合命令,合并到 build 命令中
"build": "npm run babel && npm run uglify"
现在,运行 npm run build 就会执行那两个脚本。Babel 支持配置文件,可以在 .babelrc 文件中实现更复杂的行为。
配置前端构建工具
使用 npm 脚本时,通常有三种配置前端构建工具的方法。
- '指定命令行参数,比如 ./node_modules/.bin/ uglify --source-map 。'
- '针对项目创建 配置文件,将参数放在这个文件中,Babel和 ESLint'
- '将配置参数添加到 package.json 中。'
如果构建的过程复杂,要做到文件的复制、合并和转移。可以创建一个 shell 脚本,然后用 npm 脚本调用它。很多构建系统都提供了 JavaScript API 以实现自动化构建。
用Gulp 实现自动化
Gulp 是基于流的构建系统。可以通过对流的引导来创建构建过程,除了转译和缩码还可以做很多事情。Gulp 之所以能实现高度重用,主要归功于两项技术:使用插件和自定义构建任务。
把 Gulp 添加到项目中
添加Gulp需要用 npm 安装 gulp-cli 和 gulp 两个包,很多人会把 gulp-cli 安装在全局环境中,这样只需要输入 gulp 就可以运行 Gulp 了。新建文件夹,全局安装gulp-cli,创建一个带有Gulp 的 Node 项目:
npm init -y
npm i -S-D gulp
创建 gulpfile.js.
打开这个文件,用 Gulp 构建一个小型的 React 项目,这里会用到 gulp-babel/gulp-sourcemaps/gulp-concat:
npm i -S-D gulp-sourcemaps gulp-babel babel-preset-es2015
npm i -S-D gulp-concat react react-dom babel-preset-react
往项目中添加 Gulp 插件时,把 npm 命令中的 参数 --save 换成 --save-dev ,如果为了试验新的插件并想把它们卸掉,可以使用 npm uninstall --save-dev ,把它们从 ./node_modules 里删掉,同时更新 package.json 文件。
Gulp 任务的创建以及运行
创建 Gulp 任务需要在 gulpfile.js 中编写 Node 代码,调用 Gulp 的 API . Gulp 的 API 可以做很多事,比如查找文件,把对文件进行某种转换的插件拼到一起等。
下面的例子,打开 gulpfile.js 构建一个任务,用 gulp.src 查找 JSX 文件,用Babel 处理 ES2015 和 React , 然后把文件拼在一起。
const gulp = require('gulp');
const sourcemaps = require('gulp-sourcemaps');
const babel = require('gulp-babel');
const concat = require('gulp-concat');
gulp.task('default', () => {
return (
gulp
.src('app/*.jsx')
// 开始监测源文件,为调用构建源码映射
.pipe(sourcemaps.init())
// 使用 ES2015 和 React(JSX) 配置 gulp-babel
.pipe(
babel({
presets: ['es2015', 'react']
})
)
.pipe(concat('all.js'))
// 单独写入源码映射文件
.pipe(sourcemaps.write('.'))
// 将所有文件放到dist/目录下
.pipe(gulp.dest('dist'))
);
});
在终端运行 gulp 就可以运行这个任务。创建一个 app/index.jsx 文件进行试验:
import React from 'react';
import ReactDOM from 'react-dom';
ReactDOM.render(
<h1>Hello,World!</h1>,
document.getElementById('example')
);
在 Gulp中,用 JavaScript表示构建阶段很容易。并且我们可以用 gulp.task() 往这个文件里添加自己的任务。这些任务通常都遵循相同的模式。
(1) 源文件——收集输入文件。 (2) 转译——让它们依次通过一个个对它们进行转换的插件。 (3) 合并——把这些文件合到一起,创建一个整体构建文件。 (4) 输出——设定文件的目标地址或移动输出文件。
在前面那个例子中, sourcemaps 是个特例,因为它需要两次 pipe :第一次是配置,最后一次是输出文件。这是因为源码映射需要把最初的代码行数映射到应该转译构建后的代码行数上。
监测变化
精简构建过程最简单的办法就是用 Gulp 插件监测文件系统的变化,但也有备选方案。有些库跟热重载配合很好,并且通用的DOM 和基于 CSS 的项目也会很适合 LiveReload 项目。
作为实例,可以将 gulp-watch 添加到项目中:
npm i -S-D gulp-watch
添加检测任务,让它调用前面的那个例子中的默认任务:
const watch = require('gulp-watch');
gulp.task('watch', () => {
watch('app/**.js', () => gulp.setMaxListeners('default'));
});
这段代码定义了一个名为 watch 的任务,然后用 watch() 监测 React JSX 文件的变化。只 要有文件发生了变化,默认的构建任务就会运行。只需稍稍修改,这个处方就可以用来构建SASS文件、优化图片,以及做需要在前端项目上做的很多事情。
在大项目中把任务分散到不同文件中
项目规模变大后,一般会需要更多的 Gulp 任务。最终会出现一个大到难以理解的长文件, 如果把代码分解成不同的模块,就可以解决这个问题。 你已经看到了,Gulp是用 Node的模块系统来加载插件的。没有特殊的插件加载系统,就是 标准模块。我们也可以用 Node 的模块系统分割超长的 gulpfile 文件,以便于维护。可以按如下步骤来使用分散的文件。
(1) 创建一个名为 gulp的文件夹以及一个名为 tasks的子目录。 (2) 在各个文件中用 gulp.task() 语法定义任务,最好是每个任务放一个文件。 (3) 创建一个名为 gulp/index.js的文件,在其中加载所有的 Gulp任务文件。 (4) 在 gulpfile.js中引入这个 gulp/index.js文件。
Gulp 是一个通用的项目自动化工具。它适合管理项目里的跨平台清理脚本,比如运行复杂 的客户端测试或者为数据库提供固定的测试环境。尽管它也可以构建客户端资产,但不如专门做这些事情的工具,也就是说相较之下,Gulp 需要更多的代码和配置来定义那些任务。Webpack就是这样的工具,专注于打包 JavaScript和 CSS模块。
用 Webpack 构建 Web 程序
Webpack是专门用来构建 Web程序的。用 Gulp时,写 JavaScript 代码是为了驱动构建系统,所以会涉及写 gulpfile 和构建任务。而用 Webpack时,写的是配置文件,用插件和加载器添加新功能。有时候不需要额外的配置:在命令行里输入webpack ,将源文件的路径作为参数,它就能构建项目。
Webpack的优势之一是更容易快速搭建出一个支持增量式构建的构建系统。如果配置成文件发生变化时自动构建,Webpack不会因为一个文件发生变化而重新构建整个项目。所以它的构建更快,也更好理解。
使用打包器和插件
Webpack插件是用来改变构建过程的行为的。这些行为包括自动将静态资源上传到Amazon S3或去掉输出中重复的文件等。
加载器是用来转换资源文件的。比如将 SASS 转换为 CSS,或者将 ES2015 转换为 ES5。加载器是函数,负责将输入的源文本转换为特定的文本输出。它们既可以是异步的,也可以是同步的。插件是可以挂接到 Webpack更底层 API的类的实例。
如果需要转换 React 代码、CoffeeScript、SASS 或其他转译语言,就用加载器。如果需要调整 JavaScript,或用某种方式处理文件,就用插件。
配置和运行 Webpack
新建文件夹
npm init -y
npm install --save react react-dom
npm install --save-dev webpack babel-loader babel-core
npm install --save-dev babel-preset-es2015 babel-preset-react
最后一条命令安装了 Babel 的 ES2015 插件和用于 Babel 的 React 转换器, 创建webpack.config.js. 我们要在这个文件里告诉 Webpack 去哪里找输入文件,把输出写到哪里,以及用哪些加载器。我们要对 React使用 babel-loader,还要对它做些额外的配置。代码如下:
const path = require('path');
const webpack = require('webpack');
module.exports = {
entry: './app/index.jsx',
output: {
path: __dirname,
filename: 'dist/bundle.js'
},
module: {
loaders: [
{
test: /.jsx?$/,
loader: 'babel-loader',
exclude: /node_modules/,
query: {
presets: ['es2015', 'react']
}
}
]
}
};
这个配置文件包含了成功构建一个以 ES2015 写的 React 程序所需的一切。里面的配置都很 直白:定义一个 entry ,同时加载程序的主文件。然后指定输出应该写到哪里。如果这个文件不存在,Webpack会创建它。接着定义一个加载器,并用 test 把它关联到一个文件聚集搜索上。最后,设定加载器的选项。在这个例子中,这些选项加载了 ES2015和 React Babel插件。
用 Webpack 开发服务器
Express 服务器会在文件发生变化后运行 Webpack,然后将变化后的资源文件提供给浏览器。为了不跟主服务器冲突,你应该把它跑在另外一个端口上,也就是说在开发过程中, script 标签要指向这个开发服务器提供的 URL 上。这个服务器会构建资源文件,但输出会放在内存里,而不是 Webpack 的输出文件夹里。webpack-dev-server 也可以用来做模块热加载,这与 LiveReload服务器的用法类似。
按下面的步骤把 webpack-dev-server添加到项目中。
(1) 用 npm i --save-dev webpack-dev-server@ 1.14.1 安装 webpack-dev-server。 (2) 在 webpack.config.js的 output 属性中添加一个 publicPath 选项。 (3) 在构建目录下添加 index.html文件,在这个文件中加载打包后的 JavaScript和 CSS文件, 注意 URL中的端口是下一步中指定的那个端口。 (4) 带着你想要用的选项运行服务器。比如 webpack-dev-server --hot --inline--content -base dist/ --port 3001 。 (5) 访问 http://localhost:3001/ 加载这个程序。
Webpack 具体的可以自行百度
总结
- 'npm脚本是实现简单任务自动化和脚本调用的最佳选择。'
- 'Gulp可以用 JavaScript编写更加复杂的任务,并且它是跨平台的。'
- '如果 gulpfiles变得太长了,可以把代码分解到多个文件中。'
- 'Webpack可以用来生成客户端打包文件。'
- '如果只需要构建客户端打包文件,用 Webpack可能比用 Gulp更省事儿。'
- ' Webpack支持热重载,也就是说刷新浏览器就能看出代码的变化。'