Webpack高级系列(二)

341 阅读3分钟

这是我参与11月更文挑战的第12天,活动详情查看:2021最后一次更文挑战

本文讲述webpack更高级的系列,如代码分割(Code Splitting)等。

代码分割(Code Splitting)

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

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

  • 入口起点:使用 entry 配置手动地分离代码。
  • 防止重复:使用 Entry dependencies 或者 SplitChunksPlugin 去重和分离 chunk。
  • 动态导入:通过模块的内联函数调用来分离代码。

入口分离

看一个例子,我们在venors.js、index.js中都引入了lodash进行打包

// venors.js
import _ from 'lodash';

console.log(_.join(['Another', 'module', 'loaded!'], ' '));
// index.js
import _ from 'lodash';

import { add } from './math'

const result = add(1, 2);
console.log(result);
// webpack.config.js
mode: 'development',
entry: {
    index: './src/index.js',
    venors: './src/venors.js',
},

在多入口打包时,我们的入口文件中引入了重复的模块,那些重复模块都会被引入到各个 bundle 中,无疑增大了我们的代码文件大小,我们需要来进行剥离。

防止重复(删除重复模块代码)

入口依赖

通过配置 dependOn option 选项,这样可以在多个 chunk 之间共享模块:

    entry: {
        index: {
            import: './src/index.js',
            dependOn: 'shared',
        },
        venors: {
            import: './src/venors.js',
            dependOn: 'shared',
        },
        shared: 'lodash'
    },

再次打包后会生成三个文件index.bundle.js、venors.bundle.js、share.bundle.js,我们公共模块lodash会被打包到share.bundle.js中,就不会重复打包了,满足了我们的需求。此时如果我们要在一个 HTML 页面上使用多个入口时,还需设置 optimization.runtimeChunk: 'single',否则还会遇到这里所述的麻烦。

//  webpack.config.js
optimization: {
    runtimeChunk: 'single',
  },

添加这项配置后,我们会多生成一个runtime.bundle.js文件,将代码分得更细。

SplitChunksPlugin

除了上面说的入口依赖的方法,我们还可以利用SplitChunksPlugin插件可以将公共的依赖模块提取到已有的入口 chunk 中,或者提取到一个新生成的 chunk来。

 //  webpack.config.js
const path = require('path');

  module.exports = {
    mode: 'development',
    entry: {
      index: './src/index.js',
      venors: './src/venors.js',
    },
    output: {
      filename: '[name].bundle.js',
      path: path.resolve(__dirname, 'dist'),
    },
   optimization: {
     splitChunks: {
       chunks: 'all',
     },
   },
  };

使用 optimization.splitChunks 配置选项之后,现在应该可以看出,index.bundle.jsanother.bundle.js 中已经移除了重复的依赖模块。需要注意的是,插件将 lodash 分离到单独的 chunk,并且将其从 main bundle 中移除,减轻了大小。执行 npm run build 查看效果。

动态导入

当涉及到动态代码拆分时,webpack 提供了两个类似的技术。第一种,也是推荐选择的方式是,使用符合 ECMAScript 提案import() 语法 来实现动态导入。第二种,则是 webpack 的遗留功能,使用 webpack 特定的 require.ensure。让我们先尝试使用第一种。

将index.js代码改成如下所示,并将webpack.config.js改成单入口的index.js

// index.js

import { add } from './math'
import './venors'

const result = add(1, 2);
console.log(result);

(function () {
    import('lodash')
        .then(({ default: _ }) => {
            console.log(_.join(['Another', 'module', 'loaded!'], ' '));

        })
})()

运行npm run build后我们的lodash也会被单独打包出来。

预获取/预加载模块(prefetch/preload module)

在声明 import 时,使用下面这些内置指令,可以让 webpack 输出 "resource hint(资源提示)",来告知浏览器:

  • prefetch(预获取):将来某些导航下可能需要的资源。浏览器在闲置时间预取资源,只要父 chunk 完成加载,webpack 就会添加 prefetch hint(预取提示)。
  • preload(预加载):当前导航下可能需要资源

与 prefetch 指令相比,preload 指令有许多不同之处:

  • preload chunk 会在父 chunk 加载时,以并行方式开始加载。prefetch chunk 会在父 chunk 加载结束后开始加载。
  • preload chunk 具有中等优先级,并立即下载。prefetch chunk 在浏览器闲置时下载。
  • preload chunk 会在父 chunk 中立即请求,用于当下时刻。prefetch chunk 会用于未来的某个时刻。
  • 浏览器支持程度不同。