Webpack入门

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等
  1. 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
      23
      const 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文件互不影响。

  2. 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
      22
      module.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 文件

  3. 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
      9
      module.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.js

1
2
3
4
5
6
7
// 未使用的export
export function square(x) {
return x * x;
}
export function cube(x) {
return x * x * x;
}

main.js

1
2
import {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__) {

"use strict";
/* 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__) {

"use strict";
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提供了两个解决方法:公共模块抽取 和 动态加载 来分割代码,对应的解决方案分别为 CommonsChunkPluginrequire.ensure

  • CommonsChunkPlugin : 见插件
  • require.ensure : todo

参考资料

坚持原创技术分享,您的支持将鼓励我继续创作!