介绍

本章介绍了webpack和相关技术的核心概念与使用,以及相关的扩展:懒加载、tree shaking、代码分离、缓存的方法~

webpack是什么?

具体的介绍可见官网:https://www.webpackjs.com/concepts/

本质上,webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。

把所有依赖打包成一个或多个 bundle 文件,通过代码分割成单元片段并按需加载。

与 gulp 和 grunt的区别

三者都是前端构建工具,grunt和gulp在早期比较流行,现在webpack相对来说比较主流,不过一些轻量化的任务还是会用gulp来处理,比如单独打包CSS文件等。

grunt和gulp是基于任务和流(Task、Stream)的。类似jQuery,找到一个(或一类)文件,对其做一系列链式操作,更新流上的数据, 整条链式操作构成了一个任务,多个任务就构成了整个web的构建流程。

webpack是基于入口的。webpack会自动地递归解析入口所需要加载的所有资源文件,然后用不同的Loader来处理不同的文件,用Plugin来扩展webpack功能。

所以总结一下:

  • 从构建思路来说
    gulp和grunt需要开发者将整个前端构建过程拆分成多个Task,并合理控制所有Task的调用关系
    webpack需要开发者找到入口,并需要清楚对于不同的资源应该使用什么Loader做何种解析和加工

  • 对于知识背景来说
    gulp更像后端开发者的思路,需要对于整个流程了如指掌 webpack更倾向于前端开发者的思路

其他类似的工具

同样是基于入口的打包工具还有以下几个主流的:

  • webpack
  • rollup
  • parcel

webpack适用于大型复杂的前端站点构建;rollup适用于基础库的打包,如vue、react;parcel适用于简单的实验性项目,他可以满足低门槛的快速看到效果

webpack 与 rollup

webpack 发起之初主要是为了解决以下两个问题:

  • 代码拆分(Code Splitting): 可以将应用程序分解成可管理的代码块,可以按需加载,这样用户便可快速与应用交互,而不必等到整个应用程序下载和解析完成才能使用,以此构建复杂的单页应用程序(SPA);
  • 静态资源(Static Assets): 可以将所有的静态资源,如 js、css、图片、字体等,导入到应用程序中,然后由 webpack 使用 hash 重命名需要的资源文件,而无需为文件 URL 增添 hash 而使用 hack 脚本,并且一个资源还能依赖其他资源。
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
/******/ (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;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, {
/******/ configurable: false,
/******/ enumerable: true,
/******/ get: getter
/******/ });
/******/ }
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, __webpack_exports__, __webpack_require__) {

"use strict";
Object.defineProperty(__webpack_exports__, "__esModule", { value: true });

/* harmony default export */ __webpack_exports__["default"] = (str => str);


/***/ })
/******/ ]);

把 export default str => str; 这段代码用 webpack 打包就会得到上面的结果。

这在以下的一些情境中就不太高效,需要寻求更好的解决方案:

需要 js 高效运行。因为 webpack 对子模块定义和运行时的依赖处理(webpack_require),不仅导致文件体积增大,还会大幅拉低性能;
项目(特别是类库)只有 js,而没有其他的静态资源文件,使用 webpack 就有点大才小用了,因为 webpack bundle 文件的体积略大,运行略慢,可读性略低。

rollup 相对 webpack 而言,要小巧、干净利落一些,但不具备 webpack 的一些强大的功能,如热更新,代码分割,公共依赖提取等。

webpack 核心概念

核心概念有:入口、输出、loader、plugins

  1. 入口起点(entry point)指示 webpack 应该使用哪个模块,来作为构建其内部依赖图的开始。进入入口起点后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的。
  2. output 属性告诉 webpack 在哪里输出它所创建的 bundles,以及如何命名这些文件,默认值为 ./dist。【基本上,整个应用程序结构,都会被编译到你指定的输出路径的文件夹中!!!!!!!】
  3. loader 让 webpack 能够去处理那些非 JavaScript 文件(webpack 自身只理解 JavaScript)。loader 可以将所有类型的文件转换为 webpack 能够处理的有效模块,然后你就可以利用 webpack 的打包能力,对它们进行处理。

    在 webpack 的配置中 loader 有两个目标:
    test 属性,用于标识出应该被对应的 loader 进行转换的某个或某些文件。
    use 属性,表示进行转换时,应该使用哪个 loader。
    
1
2
3
4
5
6
module: {
rules: [{
test: /\.css$/,
use: ["style-loader", 'css-loader']
}]
},
  1. loader 被用于转换某些类型的模块,而插件则可以用于执行范围更广的任务。插件的范围包括,【从打包优化和压缩,一直到重新定义环境中的变量】。插件接口功能极其强大,可以用来处理各种各样的任务。
    想要使用一个插件,你只需要 require() 它,然后把它添加到 plugins 数组中。多数插件可以通过选项(option)自定义。你也可以在一个配置文件中因为不同目的而多次使用同一个插件,这时需要通过使用 new 操作符来创建它的一个实例。

  2. 模式:通过选择 development 或 production 之中的一个,来设置 mode 参数,你可以启用相应模式下的 webpack 内置的优化

    1
    2
    3
    module.exports = {
    mode: 'production'
    };

入口

向 entry 属性传入「文件路径(file path)数组」将创建“多个主入口(multi-main entry)”。在你想要多个依赖文件一起注入,并且将它们的依赖导向(graph)到一个“chunk”时,传入数组的方式就很有用。

1
2
3
4
5
6
7
8
9
10
11
12
13
const config = {
entry: './path/to/my/entry/file.js'
};

module.exports = config;

// 等同于

const config = {
entry: {
main: './path/to/my/entry/file.js'
}
};

或者使用对象语言:

1
2
3
4
5
6
const config = {
entry: {
app: './src/app.js',
vendors: './src/vendors.js'
}
};

分离了应用程序app和第三方库vendor入口,这告诉我们 webpack 从 app.js 和 vendors.js 开始创建依赖图。这些依赖图是彼此完全分离、互相独立的。vendors选项中的引用可以使用 CommonsChunkPlugin 从整体bundle中提取 vendor bundle,并把引用 vendor 的部分替换为 webpack_require() 调用。

多页面:

1
2
3
4
5
6
7
const config = {
entry: {
pageOne: './src/pageOne/index.js',
pageTwo: './src/pageTwo/index.js',
pageThree: './src/pageThree/index.js'
}
};

则每个键(key)会是 chunk 的名称,该值描述了 chunk 的入口起点。

输出

即使可以存在多个入口起点,但只指定一个输出配置。

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
// 普通输出
const config = {
output: {
filename: 'bundle.js',
path: '/home/proj/public/assets'
}
};

module.exports = config;

//多页面输出
{
entry: {
app: './src/app.js',
search: './src/search.js'
},
output: {
filename: '[name].js',
path: __dirname + '/dist'
}
}

//其他选项
output: {
filename: '[name].js', //对应于entry里面生成出来的文件名
chunkFilename: 'lib/[name].js'//就是未被列在entry中,但有些场景需要被打包出来的文件命名配置。
// 比如按需加载(异步)模块的时候 require.ensure
// 不是在ensure方法中引入的模块,此属性不会生效,只能用CommonsChunkPlugin插件来提取
}

filename: 决定了每个输出 bundle 的名称。这些 bundle 将写入到 output.path 选项指定的目录下。

loaders

在你的应用程序中,有三种使用 loader 的方式:

配置(推荐):在 webpack.config.js 文件中指定 loader。
内联:在每个 import 语句中显式指定 loader。
CLI:在 shell 命令中指定它们。

1
2
// 内联方式
import Styles from 'style-loader!css-loader?modules!./styles.css';

使用 ! 将资源中的 loader 分开。分开的每个部分都相对于当前目录解析。

特性:

loader 支持链式传递。能够对资源使用流水线(pipeline)。一组链式的 loader 将按照相反的顺序执行。loader 链中的第一个 loader 返回值给下一个 loader。在最后一个 loader,返回 webpack 所预期的 JavaScript。
loader 可以是同步的,也可以是异步的。
loader 运行在 Node.js 中,并且能够执行任何可能的操作。
loader 接收查询参数。用于对 loader 传递配置。
loader 也能够使用 options 对象进行配置。
除了使用 package.json 常见的 main 属性,还可以将普通的 npm 模块导出为 loader,做法是在 package.json 里定义一个 loader 字段。
插件(plugin)可以为 loader 带来更多特性。
loader 能够产生额外的任意文件。

插件

插件目的在于解决 loader 无法实现的其他事。
在 Webpack 运行的生命周期中会广播出许多事件,Plugin 可以监听这些事件,在合适的时机通过 Webpack 提供的 API 改变输出结果。
webpack 插件是一个具有 apply 属性的 JavaScript 对象。apply 属性会被 webpack compiler 调用,并且 compiler 对象可在整个编译生命周期访问。

1
2
3
4
5
6
7
8
9
10
//ConsoleLogOnBuildWebpackPlugin.js
const pluginName = 'ConsoleLogOnBuildWebpackPlugin';

class ConsoleLogOnBuildWebpackPlugin {
apply(compiler) {
compiler.hooks.run.tap(pluginName, compilation => {
console.log("webpack 构建过程开始!");
});
}
}

webpack解析规则

resolver 是一个库(library),用于帮助找到模块的绝对路径。
resolver 帮助 webpack 找到 bundle 中需要引入的模块代码,这些代码在包含在每个 require/import 语句中。 当打包模块时,webpack 使用 enhanced-resolve 来解析文件路径。

使用 enhanced-resolve,webpack 能够解析三种文件路径:

  • 绝对路径:不用解析了
  • 相对路径:使用 import 或 require 的资源文件(resource file)所在的目录被认为是上下文目录(context directory)。在 import/require 中给定的相对路径,会添加此上下文路径(context path),以产生模块的绝对路径(absolute path)。
1
2
import "../src/file1";
import "./file2";
  • 模块路径:模块将在 resolve.modules 中指定的所有目录内搜索。 你可以替换初始模块路径,此替换路径通过使用 resolve.alias 配置选项来创建一个别名。如果路径指向一个文件:如果路径具有文件扩展名,则被直接将文件打包。否则,将使用 [resolve.extensions] 选项作为文件扩展名来解析,此选项告诉解析器在解析中能够接受哪些扩展名(例如 .js, .jsx)。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    import "module";

    // webpack.config.js中配置
    resolve: {
    modules: [
    "node_modules",
    path.resolve(__dirname, "src")
    ],
    extensions: [".js", ".jsx", ".json", ".css", ".scss"],
    alias: {
    xyz$: "./dir/file.js"
    }//创建 import 或 require 的别名,来确保模块引入变得更简单。
    },

生产环境构建和manifest

source map:用来调试代码,生产环境下一般使用:
(none)(省略 devtool 选项) - 不生成 source map。这是一个不错的选择。
source-map - 整个 source map 作为一个单独的文件生成。它为 bundle 添加了一个引用注释,以便开发工具知道在哪里可以找到它。

runtime,以及伴随的 manifest 数据,主要是指:在浏览器运行时,webpack 用来连接模块化的应用程序的所有代码。
当编译器(compiler)开始执行、解析和映射应用程序时,它会保留所有模块的详细要点。这个数据集合称为 “Manifest”,当完成打包并发送到浏览器时,会在运行时通过 Manifest 来解析和加载模块。通过使用 manifest 中的数据,runtime 将能够查询模块标识符,检索出背后对应的模块。

模块热替换

hmr————hot module replacement:会在应用程序运行过程中替换、添加或删除模块,而无需进行完全刷新。


代码变化后自动编译代码,有三种实现方式:
webpack’s Watch Mode / webpack-dev-derver / webpack-dev-middleware,后两者都需要在配置里面加入devServer配置项

第一种:package.json文件的script指令:”watch”: “webpack –watch”,但是需要刷新浏览器
第二种:webpack.config.js的devServer(开发服务器)修改,运行命令:”dev”: “webpack-dev-server –progress –hot”

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
+   devServer: {
+ contentBase: './dist', //在哪儿查找文件,把 dist 目录下的文件,作为可访问文件。
+ historyApiFallback: true, //当使用 HTML5 History API 时,任意的 404 响应都可能需要被替代为 index.html。
+ host: 'http://localhost:8055', //指定端口
+ hot: true //启用 webpack 的模块热替换特性, 需要webpack或者webpack-dev-server 指令添加 --hot 属性,
// 这样webpack.HotModuleReplacementPlugin 会自动添加
+ proxy: {
+ "/api": "http://localhost:3000", // 请求到 /api/users 现在会被代理到请求 http://localhost:3000/api/users
+ "/video": {
target: "http://localhost:3000", //请求到 /videos/users 现在会被代理到请求 http://localhost:3000/users
pathRewrite: {"^/video" : ""},
secure: false //默认为true, false为接受运行在 HTTPS 上,且使用了无效证书的后端服务器。
}
+ },
publicPath: "/assets/" //如果我们输出的文件名为bundle.js,可以在浏览器中通过 http://localhost:8080/assets/bundle.js 访问
+ }

第三种:是一个容器(wrapper),它可以把 webpack 处理后的文件传递给一个服务器(server)。 webpack-dev-server 在内部使用了它,同时,它也可以作为一个单独的包来使用,以便进行更多自定义设置来实现更多的需求。


启动HMR:

1
2
3
4
5
6
7
8
9
10
11
12
    devServer: {
contentBase: './dist',
+ hot: true
},
plugins: [
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({
title: 'Hot Module Replacement'
}),
+ new webpack.NamedModulesPlugin(), //以便更容易查看要修补(patch)的依赖。
+ new webpack.HotModuleReplacementPlugin()
],

原理:(参考:https://zhuanlan.zhihu.com/p/30669007)
首先要知道server端和client端都做了处理工作

  1. 第一步,在 webpack 的 watch 模式下,文件系统中某一个文件发生修改,webpack 监听到文件变化,根据配置文件对模块重新编译打包,并将打包后的代码通过简单的 JavaScript 对象保存在内存中。
  2. 第二步是 webpack-dev-server 和 webpack 之间的接口交互,而在这一步,主要是 dev-server 的中间件 webpack-dev-middleware 和 webpack 之间的交互,webpack-dev-middleware 调用 webpack 暴露的 API对代码变化进行监控,并且告诉 webpack,将代码打包到内存中。
  3. 第三步是 webpack-dev-server 对文件变化的一个监控,这一步不同于第一步,并不是监控代码变化重新打包。当我们在配置文件中配置了devServer.watchContentBase 为 true 的时候,Server 会监听这些配置文件夹中静态文件的变化,变化后会通知浏览器端对应用进行 live reload。注意,这儿是浏览器刷新,和 HMR 是两个概念。
  4. 第四步也是 webpack-dev-server 代码的工作,该步骤主要是通过 sockjs(webpack-dev-server 的依赖)在浏览器端和服务端之间建立一个 websocket 长连接,将 webpack 编译打包的各个阶段的状态信息告知浏览器端,同时也包括第三步中 Server 监听静态文件变化的信息。浏览器端根据这些 socket 消息进行不同的操作。当然服务端传递的最主要信息还是新模块的 hash 值,后面的步骤根据这一 hash 值来进行模块热替换。
  5. webpack-dev-server/client 端并不能够请求更新的代码,也不会执行热更模块操作,而把这些工作又交回给了 webpack,webpack/hot/dev-server 的工作就是根据 webpack-dev-server/client 传给它的信息以及 dev-server 的配置决定是刷新浏览器呢还是进行模块热更新。当然如果仅仅是刷新浏览器,也就没有后面那些步骤了。
  6. HotModuleReplacement.runtime 是客户端 HMR 的中枢,它接收到上一步传递给他的新模块的 hash 值,它通过 JsonpMainTemplate.runtime 向 server 端发送 Ajax 请求,服务端返回一个 json,该 json 包含了所有要更新的模块的 hash 值,获取到更新列表后,该模块再次通过 jsonp 请求,获取到最新的模块代码。这就是上图中 7、8、9 步骤。
  7. 而第 10 步是决定 HMR 成功与否的关键步骤,在该步骤中,HotModulePlugin 将会对新旧模块进行对比,决定是否更新模块,在决定更新模块后,检查模块之间的依赖关系,更新模块的同时更新模块间的依赖引用。
  8. 最后一步,当 HMR 失败后,回退到 live reload 操作,也就是进行浏览器刷新来获取最新打包代码。

扩展

webpack的 hash 和 缓存

可以通过命中缓存,以降低网络流量,使网站加载速度更快,然而,如果我们在部署新版本时不更改资源的文件名,浏览器可能会认为它没有被更新,就会使用它的缓存版本。由于缓存的存在,当你需要获取新的代码时,就会显得很棘手。

  1. 使用输出文件的文件名 filename: ‘[name].[chunkhash].js’,但是我们不做修改,这个hash也会改变!!!!【因为webpack 在入口 chunk 中,包含了某些样板(boilerplate),特别是 runtime 和 manifest。】
  2. CommonsChunkPlugin 可以用于将模块分离到单独的文件中。然而 CommonsChunkPlugin 有一个较少有人知道的功能是,能够在每次修改后的构建结果中,将 webpack 的样板(boilerplate)和 manifest 提取出来。将第三方库(library)(例如 lodash 或 react)提取到单独的 vendor chunk 文件中,是比较推荐的做法,这是因为,它们很少像本地的源代码那样频繁修改。因此通过实现以上步骤,利用客户端的长效缓存机制,可以通过命中缓存来消除请求,并减少向服务器获取资源,同时还能保证客户端代码和服务器端代码版本一致。
  3. 当改变时,vendor也会改变,。这是因为每个 module.id 会基于默认的解析顺序(resolve order)进行增量。也就是说,当解析顺序发生变化,ID 也会随之改变。可以用 NamedModulesPlugin,将使用模块的路径,而不是数字标识符;或者 HashedModuleIdsPlugin,推荐用于生产环境构建。

webpack有各种hash值,包括每次项目构建hash,不同入口的chunkhash、文件的内容contenthash,这么多hash,它们有什么区别呢?

  • hash是跟整个webpack构建项目相关的,每次项目构建hash对应的值都是不同的,即使项目文件没有做“任何修改”。
  • chunkhash,从字面上就能猜出它是跟webpack打包的chunk相关的。具体来说webpack是根据入口entry配置文件来分析其依赖项并由此来构建该entry的chunk,并生成对应的hash值。不同的chunk会有不同的hash值。一般在项目中把公共的依赖库和程序入口文件隔离并进行单独打包构建,用chunkhash来生成hash值,只要依赖公共库不变,那么其对应的chunkhash就不会变,从而达到缓存的目的。
  • contenthash表示由文件内容产生的hash值,内容不同产生的contenthash值也不一样。在项目中,通常做法是把项目中css都抽离出对应的css文件来加以引用。

tree shaking

通常用于描述移除 JavaScript 上下文中的未引用代码(dead-code)。它依赖于 ES2015 模块系统中的静态结构特性,例如 import 和 export。
新的 webpack 4 正式版本,扩展了这个检测能力,通过 package.json 的 “sideEffects” 属性作为标记,向 compiler 提供提示,表明项目中的哪些文件是 “pure(纯的 ES2015 模块)”,由此可以安全地删除文件中未使用的部分。

一个js文件两个方法:add()和square(),只有add()被使用了,square()就要被去除,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
//package.json
{
"name": "your-project",
"sideEffects": false //如果所有代码都不包含副作用,我们就可以简单地将该属性标记为 false,来告知 webpack,
//它可以安全地删除未用到的 export 导出。
//比如polyfill,影响全局作用域,不提供export,这就是副作用
}
//或者
{
"name": "your-project",
"sideEffects": [
"./src/some-side-effectful-file.js"
]
}

通过如上方式,我们已经可以通过 import 和 export 语法,找出那些需要删除的“未使用代码”,然而,我们不只是要找出,还需要在 bundle 中删除它们。为此,我们将使用 -p(production) 这个 webpack 编译标记,来启用 uglifyjs 压缩插件。
从 webpack 4 开始,也可以通过 “mode” 配置选项轻松切换到压缩输出,只需设置为 “production”。

总结:

  • 使用 ES2015 模块语法(即 import 和 export)。
  • 在项目 package.json 文件中,添加一个 “sideEffects” 入口。
  • 引入一个能够删除未引用代码(dead code)的压缩工具(minifier)(例如 UglifyJSPlugin)。

代码分离

  1. 如果我们的项目有多个入口,入口之间包含重复的模块,那些重复模块都会被引入到各个 bundle 中。可以通过通过使用 CommonsChunkPlugin 来移除重复的模块。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
      const path = require('path');
    const webpack = require('webpack');
    const HTMLWebpackPlugin = require('html-webpack-plugin');

    module.exports = {
    entry: {
    index: './src/index.js',
    another: './src/another-module.js'
    },
    plugins: [
    new HTMLWebpackPlugin({
    title: 'Code Splitting'
    + }),
    + new webpack.optimize.CommonsChunkPlugin({
    + name: 'common' // 指定公共 bundle 的名称。
    + })
    ],
    output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist')
    }
    };

输出:

1
2
3
4
5
6
7
8
9
10
11
12
Hash: 70a59f8d46ff12575481
Version: webpack 2.6.1
Time: 510ms
Asset Size Chunks Chunk Names
index.bundle.js 665 bytes 0 [emitted] index
another.bundle.js 537 bytes 1 [emitted] another
common.bundle.js 547 kB 2 [emitted] [big] common
[0] ./~/lodash/lodash.js 540 kB {2} [built]
[1] (webpack)/buildin/global.js 509 bytes {2} [built]
[2] (webpack)/buildin/module.js 517 bytes {2} [built]
[3] ./src/another-module.js 87 bytes {1} [built]
[4] ./src/index.js 216 bytes {0} [built]

  1. 使用ExtractTextPlugin: 将css从主应用中分离

  2. 对于动态导入,第一种,也是优先选择的方式是,使用符合 ECMAScript 提案 的 import() 语法。
    第二种,则是使用 webpack 特定的 require.ensure。

    1
    2
    3
    4
    5
    import('react-image-crop').then(ReactCrop => {

    })

    // 可以搭配await一起使用, 但需要 babel 和 plugin syntax-dynamic-import

懒加载或者按需加载

先把你的代码在一些逻辑断点处分离开,然后在一些代码块中完成某些操作后,立即引用或即将引用另外一些新的代码块。这样加快了应用的初始加载速度,减轻了它的总体体积,因为某些代码块可能永远不会被加载。

上面的动态导入已经产生一个分离的代码块,不要没有交互使用懒加载,这样做并没有对我们有很多帮助,还会对性能产生负面影响。
最好是用户有操作交互后,再进行懒加载。

1
2
3
4
5
+   button.onclick = e => import(/* webpackChunkName: "print" */ './print').then(module => {
+ var print = module.default;
+
+ print();
+ });

externals

如果我们想引用一个库,但是又不想让webpack打包,并且又不影响我们在程序中以CMD、AMD或者window/global全局等方式进行使用,那就可以通过配置externals。

使用React

1
import React from 'react';

配置externals

1
2
3
externals: {
"react": React
}