前言

webpack 当下最流行的前端架构工具。

Webpack 是一个打包模块化 JavaScript 的工具,在 Webpack 里一切文件皆模块,通过 Loader 转换文件,通过 Plugin 注入钩子,最后输出由多个模块组合成的文件。Webpack 专注于构建模块化项目。

一切文件:JavaScript、CSS、SCSS、图片、模板,在 Webpack 眼中都是一个个模块,这样的好处是能清晰的描述出各个模块之间的依赖关系,以方便 Webpack 对模块进行组合和打包。 经过 Webpack 的处理,最终会输出浏览器能使用的静态资源。

webpack
webpack

为什么需要模块化

模块化是指把一个复杂的系统分解到多个模块以方便编码。
在没有出现模块化概念时,解决 js 代码组织问题,比较流行的是命名空间的方式,如 JQuery。
但无法解决以下问题:

  1. 命名空间冲突,两个库可能会使用同一个名称,例如 Zepto 也被放在 window.\$ 下;
  2. 无法合理地管理项目的依赖和版本;
  3. 无法方便地控制依赖的加载顺序。

当项目特别庞大并依赖特别多时,加载的类库就会显得特别笨重,在显示上会出现页面不流畅的情况,在代码上,会出现难以维护的现象。

而技术的发展就是为了编写更好易懂的代码,减少人为规范,而出现模块化开发,象征的就是 requirejs、CommonJS、seaJs 等等,而后随着 js 发展,ECMA 提出的 JavaScript 模块化规范 ES6 模块化,它将逐渐取代 CommonJS 和 AMD 规范,成为浏览器和服务器通用的模块解决方案。

ES6 模块化例子:

1
2
3
4
5
6
7
// 导出
export const method = ()=>"ES6 模块化";
export default {
es6:"es6 代码"
}
// 导入
import React, { useState } from "react";

ES6 模块化虽然是模块化解决方案,但浏览器支持力不高,所以需要架构工具帮助转义为浏览器可识别的代码。

工具对比

scripts

npm 是在安装 Node.js 时附带的包管理器,Npm Script 则是 Npm 内置的一个功能,允许在 package.json 文件里面使用 scripts 字段定义任务。

1
2
3
4
5
6
{
"scripts":{
"dev": "webpack-dev-server",
"start":"node script/index"
}
}

里面的 scripts 字段是一个对象,每个属性对应一段 Shell 脚本,以上代码定义了两个任务 dev 和 pub。 其底层实现原理是通过调用 Shell 去运行脚本命令,如执行npm run start,执行的是node script/index node 程序,但这个功能很单一,没法使用与复杂场景。

Gulp

Gulp 是一个基于流的自动化构建工具。 除了可以管理和执行任务,还支持监听文件、读写文件。Gulp 被设计得非常简单,只通过下面 5 个方法就可以胜任几乎所有构建场景:

1
2
3
4
5
通过 gulp.task 注册一个任务;
通过 gulp.run 执行任务;
通过 gulp.watch 监听文件变化;
通过 gulp.src 读取文件;
通过 gulp.dest 写文件。

它的特点流的方式,而且使用也比较简单,把需要编译内容分成一个个任务gulp.task编写即可,而在任务中使用gulp.src读取文件,而它可以正则,使用方式如 glob,然后使用pipe传递加载到文件内容给插件,而 gulp 的插件编写也比较简单,基本上都是对 node 的使用,最后经过 pipe 传递给gulp.dest进行输出操作,这些是 gulp 基本操作,以下为一个例子:

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
// 引入 Gulp
var gulp = require('gulp');
// 引入插件
var jshint = require('gulp-jshint');
var sass = require('gulp-sass');
var concat = require('gulp-concat');
var uglify = require('gulp-uglify');

// 编译 SCSS 任务
gulp.task('sass', function() {
// 读取文件通过管道传给插件
gulp.src('./scss/*.scss')
// SCSS 插件把 scss 文件编译成 CSS 文件
.pipe(sass())
// 输出文件
.pipe(gulp.dest('./css'));
});

// 合并压缩 JS
gulp.task('scripts', function() {
gulp.src('./js/*.js')
.pipe(concat('all.js'))
.pipe(uglify())
.pipe(gulp.dest('./dist'));
});

// 监听文件变化
gulp.task('watch', function(){
// 当 scss 文件被编辑时执行 SCSS 任务
gulp.watch('./scss/*.scss', ['sass']);
gulp.watch('./js/*.js', ['scripts']);
});

Gulp 的优点是好用又不失灵活,既可以单独完成构建也可以和其它工具搭配使用。其缺点是集成度不高,要写很多配置后才可以用,无法做到开箱即用。
还一点就是不会像 webpack 那样把所有文件看作模块打包成一个整体,它是把当前处理的文件看成一个整体,所以处理多少个文件就输入多少个文件。

Rollup

Rollup 是一个和 Webpack 很类似但专注于 ES6 的模块打包工具。 Rollup 的亮点在于能针对 ES6 源码进行 Tree Shaking 以去除那些已被定义但没被使用的代码,以及 Scope Hoisting 以减小输出文件大小提升运行性能。 然而 Rollup 的这些亮点随后就被 Webpack 模仿和实现。 由于 Rollup 的使用和 Webpack 差不多,而它们的差别是:

1
2
3
4
Rollup 是在 Webpack 流行后出现的替代品;
Rollup 生态链还不完善,体验不如 Webpack;
Rollup 功能不如 Webpack 完善,但其配置和使用更加简单;
Rollup 不支持 Code Spliting,但好处是打包出来的代码中没有 Webpack 那段模块的加载、执行和缓存的代码。

Rollup 在用于打包 JavaScript 库时比 Webpack 更加有优势,因为其打包出来的代码更小更快。 但功能不够完善,很多场景都找不到现成的解决方案。

webpack

前言以对它进行简述了。
Webpack 的优点是:

1
2
3
4
5
专注于处理模块化的项目,能做到开箱即用一步到位;
通过 Plugin 扩展,完整好用又不失灵活;
使用场景不仅限于 Web 开发;
社区庞大活跃,经常引入紧跟时代发展的新特性,能为大多数场景找到已有的开源扩展;
良好的开发体验。

Webpack 的缺点是只能用于采用模块化开发的项目。

webpack 的简单使用

安装
webpack 安装

1
npm i webpack webpack-cli -D

babel 安装

1
2
3
4
5
6
7
npm i -D @babel/cli @babel/core babel-loader

// babel 插件
npm i -D @babel/plugin-transform-arrow-functions @babel/plugin-transform-arrow-functions

// babel presets
npm i -D @babel/preset-env @babel/preset-react

css 处理安装

1
npm i -D css-loader postcss-loader postcss-normalize postcss-preset-env postcss-flexbugs-fixes mini-css-extract-plugin style-loader

当打包时使用 mini-css-extract-plugin 分离 css 为单独文件,而开发时使用 style-loader 在项目热加载模式下使用,因为 mini-css-extract-plugin 在热加载使用不了。

html 处理

1
npm i -D html-webpack-plugin

完整 package.json

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
{
"name": "webpack01",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"w": "webpack"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/cli": "^7.8.0",
"@babel/core": "^7.8.0",
"@babel/plugin-proposal-class-properties": "^7.8.0",
"@babel/plugin-transform-arrow-functions": "^7.8.0",
"@babel/preset-env": "^7.8.0",
"@babel/preset-react": "^7.8.0",
"babel-loader": "^8.0.6",
"css-loader": "^3.4.2",
"html-webpack-plugin": "^3.2.0",
"mini-css-extract-plugin": "^0.9.0",
"postcss-flexbugs-fixes": "^4.1.0",
"postcss-loader": "^3.0.0",
"postcss-normalize": "^8.0.1",
"postcss-preset-env": "^6.7.0",
"webpack": "^4.41.5",
"webpack-cli": "^3.3.10"
},
"dependencies": {
"@babel/polyfill": "^7.8.0",
"react": "^16.12.0",
"react-dom": "^16.12.0"
}
}

完整 webpack.config.js

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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const postcssNormalize = require("postcss-normalize");

const babel = require("./babel.config");

const moduleFileExtensions = [
"web.mjs",
"mjs",
"web.js",
"js",
"json",
"web.jsx",
"jsx"
];

module.exports = {
entry: "./src/index.jsx",
output: {
// path: '/dist',
path: __dirname + "/dist",
filename: "bundle.js"
},
resolve: {
extensions: moduleFileExtensions.map(ext => `.${ext}`)
},
module: {
rules: [
{
test: /\.jsx$/,
exclude: /node_modules/,
use: {
loader: "babel-loader",
options: {
...babel
}
}
},

{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
"css-loader",
{
loader: require.resolve("postcss-loader"),
options: {
ident: "postcss",
plugins: () => [
require("postcss-flexbugs-fixes"),
require("postcss-preset-env")({
autoprefixer: {
flexbox: "no-2009"
},
stage: 3
}),
postcssNormalize()
],
sourceMap: true
}
}
]
}
]
},
plugins: [
new MiniCssExtractPlugin({
filename: "[name].[chunkhash:8].css",
chunkFilename: "[id].css"
}),
new HtmlWebpackPlugin({
inject: true,
template: "public/index.html"
})
]
};

babel.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const presets = [
"@babel/preset-react",
[
"@babel/preset-env",
{
useBuiltIns: "entry"
}
]
];
const plugins = [
"@babel/plugin-transform-arrow-functions",
"@babel/plugin-proposal-class-properties"
];

module.exports = { presets, plugins };