构建工具

一、构建工具是什么?

构建工具是一个把源代码生成最终可执行代码的自动化工具。

简单点说,就是把 es6、less、模板等这样的源代码,转换成可以执行的 JavaScript、CSS、HTML 代码。包括如下内容:

  • 代码转换:将 TypeScript 编译成 JavaScript、将 SCSS 编译成 CSS等。
  • 文件优化:压缩 JavaScript、CSS、HTML 代码,压缩合并图片等。
  • 代码分割:提取多个页面的公共代码,提取首屏不需要执行部分代码让其异步加载。
  • 在采用模块化的项目里会有很多个模块和文件,需要通过构建功能将模块分类合并成一个文件。
  • 自动刷新:监听本地源代码变化,自动重新构建、刷新浏览器。
  • 代码校验:在代码被提交到仓库前需要校验代码是否符合规范,以及单元测试是否通过。
  • 自动发布:更新代码后,自动构建出线上发布代码并传输给发布系统。

目前已有的构建工具包括:Gulp、FIS、Webpack、Rollup、Parcel等。

下面将笼统的介绍下它们的构建流程。

二、构建流程

以上列出的目前流行的构建工具中,构建流程大体分两类。

1. “流式”流程(代码构建方式)

典型的就是gulp,它的构建流程看代码就能直观的了解。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
var gulp = require('gulp');
var pug = require('gulp-pug');
var less = require('gulp-less');
var minifyCSS = require('gulp-csso');

gulp.task('html', function(){
return gulp.src('client/templates/*.pug')
.pipe(pug())
.pipe(gulp.dest('build/html'))
});

gulp.task('css', function(){
return gulp.src('client/templates/*.less')
.pipe(less())
.pipe(minifyCSS())
.pipe(gulp.dest('build/css'))
});

gulp.task('default', [ 'html', 'css' ]);

这种构建方式优点是简单、灵活,但对于模块化开发、复杂项目却不是那么友好,需要写一大堆构建代码。

2. “插件式”流程(配置构建方式)

典型的就是webpack,先看下它的配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
module.exports = {
mode: 'development',
devtool: 'cheap-module-eval-source-map',
//页面入口文件配置
entry: {
index : './src/js/page/index.js'
},
//入口文件输出配置
output: {
path: 'dist/js/page',
filename: '[name].js'
},
//文件解析配置
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
vue$: 'vue/dist/vue.esm.js',
'@pages': resolve('src/pages'),
'@components': resolve('src/components'),
}
},
// 定义如何处理不同类型的模块
module: {
rules:[
loaders: [
{ test: /\.css$/, loader: 'style-loader!css-loader' },
{ test: /\.js$/, loader: 'jsx-loader?harmony' },
{ test: /\.scss$/, loader: 'style!css!sass?sourceMap'},
{ test: /\.(png|jpg)$/, loader: 'url-loader?limit=8192'}
]
]
},
//插件项
plugins: [
// extract css into its own file
new ExtractTextPlugin({
filename: utils.assetsPath('css/[name].[contenthash].css'),
}),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false,
},
}),
};

这样的配置文件一般会有entry、output、plugin等配置项。

先看副图,感受下webpack构建流程:
复杂流程

webpack的核心就是其插件系统,简单来说,就是webpack在整个构建过程中埋了很多个事件,我们可以事先在配置文件中(plugins)配置所需要的钩子函数,当webpack构建执行到相应事件时,会触发这个事件对应的钩子函数,执行自定义流程。这种插件系统最大的优势就是非常灵活,方便系统的拓展。

简单来看构建流程,大概是这样:
简单流程

如果简化为单文件流程,可简化为这样:

1
resolve -> compile -> emit

下面我们将介绍 js 单文件的编译流程

三、js编译流程

一个 js 文件会经过 babel、uglify-js 等工具的处理,核心是将源码转化为 ast,然后进行相应的处理。

下面我们将通过一个小栗子,感受下代码转换过程:
代码
测试用例

看完代码转换,我们再看下webpack模块系统的实现感受下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
// a.js
exports.done = 'result:a';
console.log('我在执行a.js');

// main.js
var a = require('./a.js');
console.log('在 main.js 之中, a.done=', a.done);

(function (modules) { // webpackBootstrap
// The module cache
var installedModules = {};

// The require function
function __webpack_require__(moduleId) {

// Check if module is in cache
if (installedModules[moduleId])
return installedModules[moduleId].exports;

// Create a new module (and put it into the cache)
var module = installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {}

};

// Execute the module function
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

// Flag the module as loaded
module.l = true;

// Return the exports of the module
return module.exports;

}


// Load entry module and return exports
return __webpack_require__(__webpack_require__.s = 1);

})
/************************************************************************/
([
/* 0 */
(function (module, exports) {
exports.done = 'result:a';
console.log('我在执行a.js');
}),
/* 1 */
(function (module, exports, __webpack_require__) {
var a = __webpack_require__(0);
console.log('在 main.js 之中, a.done=', a.done);
})
]);

了解了代码转换后,再看下其它联想:比如去掉代码中所有的console?
UglifyJS配置

四、总结

当我们了解了编译流程后,我们就能更清晰的了解整个前端代码从源码到最终可执行代码的变换。理论上所有的构建过程前端都可以介入,在需要的时候可以自定义更改构建流程。

参考