Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Webpack性能优化系列之 code splitting (代码分割,优化资源加载) #42

Open
yuanyuanbyte opened this issue Nov 19, 2021 · 0 comments
Assignees

Comments

@yuanyuanbyte
Copy link
Owner

yuanyuanbyte commented Nov 19, 2021

本系列的主题是 Webpack,每期讲解一个技术要点。如果你还不了解各系列内容,文末点击查看全部文章,点我跳转到文末

如果觉得本系列不错,欢迎 Star,你的支持是我创作分享的最大动力。

code splitting

code splitting(代码分割)是 webpack 中最引人注目的特性之一。此特性能够把代码分离到不同的 bundle 中,然后可以按需加载或并行加载这些文件。

作用:代码分割可以用于获取更小的 bundle,以及控制资源加载优先级,如果使用合理,会极大影响加载时间。

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

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

多入口手动分离代码

这是迄今为止最简单直观的分离代码的方式,如何从 main bundle 中分离 another module(另一个模块),即配置多入口:

project

webpack-demo
|- package.json
|- webpack.config.js
|- /dist
|- /src
  |- index.js
+ |- another-module.js
|- /node_modules

another-module.js

import _ from 'lodash';

console.log(_.join(['Another', 'module', 'loaded!'], ' '));

webpack.config.js

 const path = require('path');

 module.exports = {
+  mode: 'development',
+  entry: {
+    index: './src/index.js',
+    another: './src/another-module.js',
+  },
   output: {
+    filename: '[name].[contenthash:10].bundle.js',
     path: path.resolve(__dirname, 'dist'),
   },
 };

这将生成如下构建结果:

...
[webpack-cli] Compilation finished
asset index.bundle.js 553 KiB [emitted] (name: index)
asset another.bundle.js 553 KiB [emitted] (name: another)
runtime modules 2.49 KiB 12 modules
cacheable modules 530 KiB
  ./src/index.js 257 bytes [built] [code generated]
  ./src/another-module.js 84 bytes [built] [code generated]
  ./node_modules/lodash/lodash.js 530 KiB [built] [code generated]
webpack 5.4.0 compiled successfully in 245 ms

这种方式存在一些隐患:

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

上述隐患将造成重复引用。

解决方法:

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

webpack.config.js

 const path = require('path');

 module.exports = {
   mode: 'development',
   entry: {
+    index: {
+      import: './src/index.js',
+      dependOn: 'shared',
+    },
+    another: {
+      import: './src/another-module.js',
+      dependOn: 'shared',
+    },
+    shared: 'lodash',
   },
   output: {
     filename: '[name].bundle.js',
     path: path.resolve(__dirname, 'dist'),
   },
 };

如果我们要在一个 HTML 页面上使用多个入口时,还需设置 optimization.runtimeChunk: 'single',否则还会遇到这里所述的麻烦。

webpack.config.js

 const path = require('path');

 module.exports = {
   mode: 'development',
   entry: {
     index: {
       import: './src/index.js',
       dependOn: 'shared',
     },
     another: {
       import: './src/another-module.js',
       dependOn: 'shared',
     },
     shared: 'lodash',
   },
   output: {
     filename: '[name].bundle.js',
     path: path.resolve(__dirname, 'dist'),
   },
+  optimization: {
+    runtimeChunk: 'single',
+  },
 };

构建结果如下:

...
[webpack-cli] Compilation finished
asset shared.bundle.js 549 KiB [compared for emit] (name: shared)
asset runtime.bundle.js 7.79 KiB [compared for emit] (name: runtime)
asset index.bundle.js 1.77 KiB [compared for emit] (name: index)
asset another.bundle.js 1.65 KiB [compared for emit] (name: another)
Entrypoint index 1.77 KiB = index.bundle.js
Entrypoint another 1.65 KiB = another.bundle.js
Entrypoint shared 557 KiB = runtime.bundle.js 7.79 KiB shared.bundle.js 549 KiB
runtime modules 3.76 KiB 7 modules
cacheable modules 530 KiB
  ./node_modules/lodash/lodash.js 530 KiB [built] [code generated]
  ./src/another-module.js 84 bytes [built] [code generated]
  ./src/index.js 257 bytes [built] [code generated]
webpack 5.4.0 compiled successfully in 249 ms

由上可知,除了生成 shared.bundle.js,index.bundle.js 和 another.bundle.js 之外,还生成了一个 runtime.bundle.js 文件。

SplitChunksPlugin

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

webpack.config.js

  const path = require('path');

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

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

总结:

1.可以将node_modules中代码单独打包一个chunk最终输出
2.自动分析多入口chunk中,有没有公共的文件。如果有会打包成单独一个chunk

构建结果如下:

...
[webpack-cli] Compilation finished
asset vendors-node_modules_lodash_lodash_js.bundle.js 549 KiB [compared for emit] (id hint: vendors)
asset index.bundle.js 8.92 KiB [compared for emit] (name: index)
asset another.bundle.js 8.8 KiB [compared for emit] (name: another)
Entrypoint index 558 KiB = vendors-node_modules_lodash_lodash_js.bundle.js 549 KiB index.bundle.js 8.92 KiB
Entrypoint another 558 KiB = vendors-node_modules_lodash_lodash_js.bundle.js 549 KiB another.bundle.js 8.8 KiB
runtime modules 7.64 KiB 14 modules
cacheable modules 530 KiB
  ./src/index.js 257 bytes [built] [code generated]
  ./src/another-module.js 84 bytes [built] [code generated]
  ./node_modules/lodash/lodash.js 530 KiB [built] [code generated]
webpack 5.4.0 compiled successfully in 241 ms

动态导入

当涉及到动态代码拆分时,可以使用符合 ECMAScript 提案 的 import() 语法 来实现动态导入。

先从上述示例的配置中移除掉多余的 entryoptimization.splitChunks,因为接下来的演示中并不需要它们:

webpack.config.js

 const path = require('path');

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

project

在这里插入图片描述

index.js

function sum(...args) {
  return args.reduce((p, c) => p + c, 0);
}

/*
  通过js代码,让某个文件被单独打包成一个chunk
  import动态导入语法:能将某个文件单独打包
*/
import(/* webpackChunkName: 'test' */'./test')
  .then(({ mul, count }) => {
    // 文件加载成功~
    // eslint-disable-next-line
    console.log(mul(2, 5));
  })
  .catch(() => {
    // eslint-disable-next-line
    console.log('文件加载失败~');
  });

// eslint-disable-next-line
console.log(sum(1, 2, 3, 4));

构建结果如下:

在这里插入图片描述

参考

查看全部文章

博文系列目录

  • JavaScript 深入系列
  • JavaScript 专题系列
  • JavaScript 基础系列
  • 网络系列
  • 浏览器系列
  • Webpack 系列
  • Vue 系列
  • 性能优化与网络安全系列
  • HTML 应知应会系列
  • CSS 应知应会系列

交流

各系列文章汇总:https://github.com/yuanyuanbyte/Blog

我是圆圆,一名深耕于前端开发的攻城狮。

weixin

@yuanyuanbyte yuanyuanbyte self-assigned this Nov 19, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant