webpack基础使用
webpack作为当下热门的打包工具之一,得到了很广泛的使用。其诞生更多的是为了解决单页面应用资源管理的问题,其将所有资源都视为模块,并通过JS来统一管理。
基本概念
- entry : 入口。页面的入口JS文件,同时也是webpack打包的起始点,webpack自entry文件开始解析其依赖,形成一棵依赖树,最终将所有资源打包输出
- loader : 资源 - 模块 的转换器。loader 将前端各种资源转换为js可以直接处理的模块,实现资源的集中管理,如:css-loader 将css处理为js模块等
- Plugins : 插件。插件可以切入到webpack的处理流中,截获数据并进行一定的修改,拓展webpack的功能,如:修改文件hash等
简单配置
webpack默认的配置文件为 ./webpack.config.js,可以通过 --config 参数指定配置文件位置。
webpack会默认查找当前目录下的 webpack.config.js 作为配置文件
$ webpack
指定配置文件
$ webpack –config ./config/webpack.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
77
78
79
80
81
82
83
84
85 const path = require('path');
module.exports = {
// 入口文件,在该文件开始查找依赖
entry: {
app : "./app/entry",
},
output: {
// 资源(打包后的文件)输出的文件夹
path: path.resolve(__dirname, "dist"),
// entry输出的格式,允许使用占位符
// name : entry 的 key 或 main
// hash : 一次构建整个的hash,所有文件的 [hash] 都一致
// chunkhash : 单个文件的hash,每个文件不一致
filename: "[name].[chunkhash].js",
}
}
```
执行 `webpack`,`./app/entry` 及其依赖模块会被全部打包到一起,并输出打包文件 `./dist/app.2376f6b4433ee1ebc62e.js` 。
终端执行`webpack`可能会提示找不到命令,可以尝试使用 `node_modules/webpack/bin/webpack.js`。
## 进阶配置
webpack通过JS来管理所有模块,你可以在 js 中 require('base.css') 来将CSS文件引入页面,但是webpack本身是不能识别css文件的,这时候就需要使用 loader。
* loader : 资源 -> 模块 的转换器。loader 将前端各种资源转换为js可以直接处理的模块,实现资源的集中管理,如:css-loader 将css处理为js模块等
每种资源对应一个转换的loader,这里我们引入css文件,所以使用 `css-loader`,将css文件转换为js模块,并嵌入到JS文件中。
同时,插件可以让我们可以很轻松的去拓展webpack的功能,使优化打包文件成为可能。
```js
const path = require('path');
module.exports = {
// 入口文件,在该文件开始查找依赖
entry: {
// 三方库chunk
jquery : 'jquery',
vue : 'vue',
// 应用entry
app : "./app/entry",
},
// 上下文,entry 和 loader 相对于此目录解析
context: __dirname,
module: {
// 映射文件和loader,匹配的文件会用对应的loader处理
rules: [
{
// 匹配模块名的正则表达式
test: /\.js$/,
use: [{
loader: 'css-loader',
}]
},
],
},
plugins: [
// 抽取CSS到单独文件
new ExtractTextPlugin({
filename: '[name].[contenthash].css',
}),
// 抽取第三方库
new webpack.optimize.CommonsChunkPlugin({
names : ['jquery', 'vue', 'manifest'],
filename: "[name].[chunkhash].js",
minChunks: Infinity,
}),
// 固定模块ID
new webpack.HashedModuleIdsPlugin()
],
output: {
// 资源(打包后的文件)输出的文件夹
path: path.resolve(__dirname, "dist"),
// entry输出的格式,允许使用占位符
// name : entry 的 key 或 main
// hash : 一次构建整个的hash,所有文件的hash都一致
// chunkhash : 单个文件的hash,每个文件不一致
filename: "[name].[chunkhash].js",
}
}
常用插件
webpack提供了很强大的代码打包能力,但其做的只是到了可以用的地步,很多地方还是过于粗糙,比如:CSS文件嵌入到JS文件中;构建一次大部分文件的hash都会改变等。这对于苛刻的前端开发人员显然是不能接受的,好在webpack提供了对外的接口 plugin,允许你控制webpack的打包流。
- Plugins : 插件。插件可以切入到webpack的处理流中,截获数据并进行一定的修改,拓展webpack的功能,如:修改文件hash等
extract-text-webpack-plugin
- 痛点 : webpack统一用JS管理资源,即:所有的资源都会被转换为JS模块,放在同一个JS文件中,这会生成一个比较大的js文件,也不符合前端的资源隔离原则
- 插件作用 : 将require的CSS文件抽离成单独的样式文件
效果 : 实现资源类型的分离;CSS文件可以并行加载;CSS模块的导入不再占用JS执行时间
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23const path = require('path');
// 引入第三方插件
var ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
module: {
rules: [
{
test: /\.css$/,
// ExtractTextPlugin封装进自己的loader
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: "css-loader"
})
},
]
},
plugins: [
// 抽离css
new ExtractTextPlugin({
filename: '[name].[chunkhash].css',
}),
]
}contenthash
webpack中有两种hash占位符:- hash : 整个构建过程中所有文件的hash值,是全局的,即如果使用该占位符,所有文件的[hash]都是一样的
chunkhash : 单个chunk的hash,每个文件的[chunkhash]都不一样,可以更好的利用缓存
ExtractTextPlugin在使用
[chunkhash]时,其实使用的是抽离了css的js文件的hash,css文件 和 js文件 的[chunkhash]是一样的。
导致的问题就是:css文件修改时,因为js文件没有被修改,其[chunkhash]是不会改变的,可能使用旧的缓存;js文件修改时,虽然css文件没有修改,但是其[chunkhash]还是会跟着js文件改变,导致缓存失效。
为了解决这个问题,ExtractTextPlugin 引入了第三种hash类型:contenthash : css文件抽离后,单独计算该文件的hash,使js文件和css文件互不影响。
CommonsChunkPlugin
- 痛点 : webpack默认是将所有引入的包都添加到 bundle 中,例如:两个 entry 都引用了 jquery,这两个 entry 对应的 bundle 里面各有一份 jquery 源码,造成代码冗余。
- 作用 : CommonsChunkPlugin 插件用于将 bundle 中公共的部分抽离出来形成单独文件,而不是每个 bundle 都引入一份源代码。
效果 : 将公共库和运行代码抽离成公共模块,减少代码冗余。
示例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22module.exports = {
entry: {
// 三方库
vendor: ["jquery"],
index: './src/index.js',
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
// common chunk的名字
names: ['vendor', 'manifest'],
// common chunk的文件名
filename : '[name].[chunkhash].js',
// 仅在这些 chunk 中查找重复模块,不指定则是全部的chunk
chunks : []
// chunk 至少被引用的次数,可以为函数 ( module, count ) => {}
minChunks : 4,
// 公共模块大小的最小值,如果小于该值,不进行导出
// B?
// minSize: 1000,
})
],
}vendor : 抽离第三方库,使用长效缓存
webpack打包文件时,打包出的每个模块都会有一部分运行时代码(模块ID映射,每个模块的hash,require和import的polyfill),其中模块hash变动是比较频繁的,一个模块的改动会导致其hash变化,进而改变运行时文件,进而影响所有模块,缓存利用效率很低。
manifest : 抽离了运行时代码,减小冗余的同时,将模块的hash提取出来,模块的改动只会影响 改动的模块 和 manifest 文件
HashedModuleIdsPlugin
- 痛点 : webpack使用一个自增的ID来标识每个模块,但是内部的排序并不稳定。例如:某一次编译中,jquery被指定的模块ID为0,entry1 引用了该模块,模块ID - 0就被硬编码到entry1中,下次编译时,因为引入了新的库 loadash,导致 jquery 的模块ID变为了 1,硬编码在entry1中的模块ID则需要更新,导致entry1的hash改变,缓存失效。
- 作用 : HashedModuleIdsPlugin使用模块的相对路径hash作为模块ID,比内部排序更加可靠。
效果 : 固定模块ID,引入和删除库文件时,不会导致其他模块hash变化
示例:
1
2
3
4
5
6
7
8
9module.exports = {
plugins: [
new webpack.HashedModuleIdsPlugin({
hashFunction: 'sha256',
hashDigest: 'hex',
hashDigestLength: 20
})
],
}
小结
| 插件 | 是否内置 | 优化效果 |
|---|---|---|
| extract-text-webpack-plugin | 否 | 抽离CSS,可以并发加载;减小JS体积 |
| CommonsChunkPlugin | 是 | 抽离公共模块,减少冗余;抽离运行时文件,减少文件变动导致的缓存失效 |
| HashedModuleIdsPlugin | 是 | 固定模块ID,减少文件变动导致的缓存失效 |
相关技术
webpack2内置了很多开箱即用的功能,如 tree shaking,code splitting等。
tree shaking
tree shaking的概念由 rollup 提出,主要作用是剔除js中无用的export,配合代码压缩工具,去除更多无用代码。
maths.js1
2
3
4
5
6
7// 未使用的export
export function square(x) {
return x * x;
}
export function cube(x) {
return x * x * x;
}
main.js1
2import {cube} from './maths.js';
console.log(cube(5)); // 125
打包后的代码中 square 的export已经被标记清除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/* ... webpackBootstrap ... */
/******/ ([
/* 0 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
;
/* unused harmony export square */
/* harmony export (immutable) */ __webpack_exports__["a"] = cube;
// 未使用的export
function square(x) {
return x * x;
}
function cube(x) {
return x * x * x;
}
/***/ }),
/* 1 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
;
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });
/* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__maths_js__ = __webpack_require__(0);
console.log(__webpack_require__.i(__WEBPACK_IMPORTED_MODULE_0__maths_js__["a" /* cube */])(5)); // 125
/***/ })
生产环境下使用 UglifyJS 进行压缩,因为 square 函数没有被使用,会被剔除掉。1
2
3
4/* ... */
function(e,t,n){"use strict";function r(e){return e*e*e}t.a=r}
/* ... */
function(e,t,n){"use strict";Object.defineProperty(t,"__esModule",{value:!0});var r=n(0);console.log(n.i(r.a)(5))}
code splitting
webpack默认的打包方式是将依赖的代码全部打包到一个文件中,无论是单页面应用还是多页面应用都会遇到 bundle 越来越大的问题,webpack提供了两个解决方法:公共模块抽取 和 动态加载 来分割代码,对应的解决方案分别为 CommonsChunkPlugin 和 require.ensure。
- CommonsChunkPlugin : 见插件
- require.ensure : todo