CD's blog CD's blog
首页
  • HTMLCSS
  • JavaScript
  • Vue
  • TypeScript
  • React
  • Node
  • Webpack
  • Git
  • Nestjs
  • 小程序
  • 浏览器网络
  • 学习笔记

    • 《TypeScript 从零实现 axios》
    • Webpack笔记
  • JS/TS教程

    • 《现代JavaScript》教程
🔧工具方法
  • 网站
  • 资源
  • Vue资源
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

CD_wOw

内卷的行情,到不了的梦
首页
  • HTMLCSS
  • JavaScript
  • Vue
  • TypeScript
  • React
  • Node
  • Webpack
  • Git
  • Nestjs
  • 小程序
  • 浏览器网络
  • 学习笔记

    • 《TypeScript 从零实现 axios》
    • Webpack笔记
  • JS/TS教程

    • 《现代JavaScript》教程
🔧工具方法
  • 网站
  • 资源
  • Vue资源
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 配置

  • 指南(实例)

    • 安装
    • 起步
    • 管理资源
    • 管理输出
    • 开发
    • 模块热替换
    • tree shaking
    • 生产环境构建
    • 代码分离
      • 入口起点(entry points)
      • 防止重复(prevent duplication)
      • 动态导入(dynamic imports)
      • bundle 分析(bundle analysis)
    • 懒加载
    • 缓存
    • 创建 library
    • shimming
    • 渐进式网络应用程序
    • TypeScript
    • 迁移到新版本
    • 使用环境变量
    • 构建性能
    • 内容安全策略
    • 开发 - Vagrant
    • 管理依赖
    • 公共路径(public path)
    • 集成(integrations)
  • 《Webpack》笔记
  • 指南(实例)
CD_wOw
2020-12-15
目录

代码分离

本指南继续沿用起步 (opens new window)和管理输出 (opens new window)中的代码示例。。请确保你至少已熟悉其中提供的示例。

代码分离是 webpack 中最引人注目的特性之一。此特性能够把代码分离到不同的 bundle 中,然后可以按需加载或并行加载这些文件。代码分离可以用于获取更小的 bundle,以及控制资源加载优先级,如果使用合理,会极大影响加载时间。

有三种常用的代码分离方法:

  • 入口起点:使用 entry (opens new window) 配置手动地分离代码。
  • 防止重复:使用 CommonsChunkPlugin (opens new window) 去重和分离 chunk。
  • 动态导入:通过模块的内联函数调用来分离代码。

# 入口起点(entry points)

这是迄今为止最简单、最直观的分离代码的方式。不过,这种方式手动配置较多,并有一些陷阱,我们将会解决这些问题。先来看看如何从 main bundle 中分离另一个模块:

project

webpack-demo
|- package.json
|- webpack.config.js
|- /dist
|- /src
  |- index.js
+ |- another-module.js
|- /node_modules
1
2
3
4
5
6
7
8

another-module.js

import _ from 'lodash';

console.log(
  _.join(['Another', 'module', 'loaded!'], ' ')
);
1
2
3
4
5

webpack.config.js

const path = require('path');
const HTMLWebpackPlugin = require('html-webpack-plugin');

module.exports = {
  entry: {
    index: './src/index.js',
    another: './src/another-module.js'
  },
  plugins: [
    new HTMLWebpackPlugin({
      title: 'Code Splitting'
    })
  ],
  output: {
    filename: '[name].bundle.js',
    path: path.resolve(__dirname, 'dist')
  }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18

这将生成如下构建结果:

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

正如前面提到的,这种方法存在一些问题:

  • 如果入口 chunks 之间包含重复的模块,那些重复模块都会被引入到各个 bundle 中。
  • 这种方法不够灵活,并且不能将核心应用程序逻辑进行动态拆分代码。

以上两点中,第一点对我们的示例来说无疑是个问题,因为之前我们在 ./src/index.js 中也引入过 lodash,这样就在两个 bundle 中造成重复引用。接着,我们通过使用 CommonsChunkPlugin 来移除重复的模块。

# 防止重复(prevent duplication)

CommonsChunkPlugin (opens new window) 插件可以将公共的依赖模块提取到已有的入口 chunk 中,或者提取到一个新生成的 chunk。让我们使用这个插件,将之前的示例中重复的 lodash 模块去除:

webpack.config.js

  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
13
14
15
16
17
18
19
20
21
22
23

这里我们使用 CommonsChunkPlugin (opens new window) 之后,现在应该可以看出,index.bundle.js 中已经移除了重复的依赖模块。需要注意的是,CommonsChunkPlugin 插件将 lodash 分离到单独的 chunk,并且将其从 main bundle 中移除,减轻了大小。执行 npm run build 查看效果:

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
2
3
4
5
6
7
8
9
10
11
12

以下是由社区提供的,一些对于代码分离很有帮助的插件和 loaders:

  • ExtractTextPlugin (opens new window): 用于将 CSS 从主应用程序中分离。
  • bundle-loader (opens new window): 用于分离代码和延迟加载生成的 bundle。
  • promise-loader (opens new window): 类似于 bundle-loader ,但是使用的是 promises。

CommonsChunkPlugin (opens new window) 插件还可以通过使用显式的 vendor chunks (opens new window) 功能,从应用程序代码中分离 vendor 模块。

# 动态导入(dynamic imports)

当涉及到动态代码拆分时,webpack 提供了两个类似的技术。对于动态导入,第一种,也是优先选择的方式是,使用符合 ECMAScript 提案 (opens new window) 的 import() 语法 (opens new window)。第二种,则是使用 webpack 特定的 require.ensure (opens new window)。让我们先尝试使用第一种……

import() 调用会在内部用到 promises (opens new window)。如果在旧有版本浏览器中使用 import(),记得使用 一个 polyfill 库(例如 es6-promise (opens new window) 或 promise-polyfill (opens new window)),来 shim Promise。

在我们开始本节之前,先从配置中移除掉多余的 entry (opens new window) 和 CommonsChunkPlugin (opens new window),因为接下来的演示中并不需要它们:

webpack.config.js

  const path = require('path');
- const webpack = require('webpack');
  const HTMLWebpackPlugin = require('html-webpack-plugin');

  module.exports = {
    entry: {
+     index: './src/index.js'
-     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',
+     chunkFilename: '[name].bundle.js',
      path: path.resolve(__dirname, 'dist')
    }
  };
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

注意,这里使用了 chunkFilename,它决定非入口 chunk 的名称。想了解 chunkFilename 更多信息,请查看 output 相关文档 (opens new window)。接着,更新我们的项目,移除掉那些现在不会用到的文件:

project

webpack-demo
|- package.json
|- webpack.config.js
|- /dist
|- /src
  |- index.js
- |- another-module.js
|- /node_modules
1
2
3
4
5
6
7
8

现在,我们不再使用静态导入 lodash,而是通过使用动态导入来分离一个 chunk:

src/index.js

- import _ from 'lodash';
-
- function component() {
+ function getComponent() {
-   var element = document.createElement('div');
-
-   // Lodash, now imported by this script
-   element.innerHTML = _.join(['Hello', 'webpack'], ' ');
+   return import(/* webpackChunkName: "lodash" */ 'lodash').then(_ => {
+     var element = document.createElement('div');
+
+     element.innerHTML = _.join(['Hello', 'webpack'], ' ');
+
+     return element;
+
+   }).catch(error => 'An error occurred while loading the component');
  }

- document.body.appendChild(component());
+ getComponent().then(component => {
+   document.body.appendChild(component);
+ })
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

注意,在注释中使用了 webpackChunkName。这样做会导致我们的 bundle 被命名为 lodash.bundle.js ,而不是 [id].bundle.js 。想了解更多关于 webpackChunkName 和其他可用选项,请查看 import() 相关文档 (opens new window)。让我们执行 webpack,查看 lodash 是否会分离到一个单独的 bundle:

Hash: a27e5bf1dd73c675d5c9
Version: webpack 2.6.1
Time: 544ms
           Asset     Size  Chunks                    Chunk Names
lodash.bundle.js   541 kB       0  [emitted]  [big]  lodash
 index.bundle.js  6.35 kB       1  [emitted]         index
   [0] ./~/lodash/lodash.js 540 kB {0} [built]
   [1] ./src/index.js 377 bytes {1} [built]
   [2] (webpack)/buildin/global.js 509 bytes {0} [built]
   [3] (webpack)/buildin/module.js 517 bytes {0} [built]
1
2
3
4
5
6
7
8
9
10

由于 import() 会返回一个 promise,因此它可以和 async 函数 (opens new window)一起使用。但是,需要使用像 Babel 这样的预处理器和Syntax Dynamic Import Babel Plugin (opens new window)。下面是如何通过 async 函数简化代码:

src/index.js

- function getComponent() {
+ async function getComponent() {
-   return import(/* webpackChunkName: "lodash" */ 'lodash').then(_ => {
-     var element = document.createElement('div');
-
-     element.innerHTML = _.join(['Hello', 'webpack'], ' ');
-
-     return element;
-
-   }).catch(error => 'An error occurred while loading the component');
+   var element = document.createElement('div');
+   const _ = await import(/* webpackChunkName: "lodash" */ 'lodash');
+
+   element.innerHTML = _.join(['Hello', 'webpack'], ' ');
+
+   return element;
  }

  getComponent().then(component => {
    document.body.appendChild(component);
  });
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# bundle 分析(bundle analysis)

如果我们以分离代码作为开始,那么就以检查模块作为结束,分析输出结果是很有用处的。官方分析工具 (opens new window) 是一个好的初始选择。下面是一些社区支持(community-supported)的可选工具:

  • webpack-chart (opens new window): webpack 数据交互饼图。
  • webpack-visualizer (opens new window): 可视化并分析你的 bundle,检查哪些模块占用空间,哪些可能是重复使用的。
  • webpack-bundle-analyzer (opens new window): 一款分析 bundle 内容的插件及 CLI 工具,以便捷的、交互式、可缩放的树状图形式展现给用户。
编辑 (opens new window)
#Webpack
上次更新: 2021/08/22, 01:09:59
生产环境构建
懒加载

← 生产环境构建 懒加载→

最近更新
01
gsap动画库学习笔记 - 持续~
06-05
02
远程组件加载方案笔记
05-03
03
小程序使用笔记
03-29
更多文章>
Theme by Vdoing | Copyright © 2020-2023 CD | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式