👨‍💻
Hong's 前端笔记
  • 🌈About
  • JavaScript
    • 作用域
    • 闭包
    • this
      • bind()
    • 原型
      • Object.create()
      • 模拟“类”
      • ES6 Class
      • Function
    • 对象
    • 类型 & 值
      • 原生函数
      • 数组
      • 字符串
    • 异步
    • JavaScript 相关概念
      • Event Loop 事件循环
      • Prototype 原型
      • Context 执行上下文
      • this
      • Promise
    • JavaScript 常见问题
      • 手写实现
      • 看代码输出
  • React
    • 核心概念
    • 深入理解
      • State
      • 如何管理 State
      • React 渲染过程
      • 处理 DOM 事件
      • Pure 组件
      • Context
      • Key 属性
      • React.lazy()
      • Render Props
    • Hooks
      • useState
      • useRef
      • useEffect
        • Some details
      • 相关概念
      • 自定义 Hook
      • useEffect vs useLayoutEffect
    • React 相关问题
      • Class vs Function
  • React Libraries
    • Redux
      • Quick Start
      • 如何持久化 Redux 数据
      • 异步操作
      • 性能优化
      • Dive into immutability
    • Route v6
      • Quick Start
  • Reactivity
    • Immer
  • Vue
    • 深入理解
      • Composition vs Options
      • Reactivity
  • Network
    • Cookies
    • HTTP
    • HTTPS
    • CORS 跨域资源共享
    • 认证手段
    • 安全相关
    • 网络相关知识
  • Browser
    • DOM 操作
    • Events 事件
    • XHR & Fetch
    • 性能优化
    • HTML 相关概念
    • 浏览器相关概念
  • CSS
    • 盒模型
    • Layout 布局
    • Styles 样式
    • CSS 相关概念
    • CSS 相关技巧
      • 移动端适配
      • Flex
      • 动画
  • TypeScript
    • Quick Peek
    • Types in TypeScript
    • Narrowing
    • Functions in TypeScript
  • Workflow
    • Webpack
      • Webpack 概念
      • 资源管理
      • 管理输出
      • 开发环境配置
      • 生产环境配置
      • 优化代码运行性能
  • Others
    • 小程序与原生 Web 的区别
  • SSO
    • About
    • API Doc
    • 接入指南
Powered by GitBook
On this page
  • Code Split
  • 入口起点
  • 防止重复
  • 动态导入
  • Cache
  • 修改输出的文件名
  • 提取引导模板 (extracting boilerplate)
  • 模块标识符*
  • Tree Shaking
  • side-effect-free
  • lazy load
Edit on GitHub
  1. Workflow
  2. Webpack

优化代码运行性能

Last updated 3 years ago

Code Split

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

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

  • 入口起点: 在 entry 中手动分离(见 output.md)

  • 防止重复:使用 Entry dependencies 或 SplitChunksPppplugin 去重分离

  • 动态导入:通过模块的内联函数来分离代码

入口起点

见 ,这种方法存在一些隐患:

  • 如果 entry 中多个 chunk 包含同一个模块,那这个模块会被重复引入

  • 不够灵活,不能动态地将核心应用逻辑中的代码拆分出来

防止重复

入口依赖

略,详见:

SplitChunksPlugin

将公共的依赖模块提取出来

webpack.config.js

module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'all',
    },
  },
}

假设 index.js 和 print.js 都引入了 lodash,打包后,第三方库 lodash 已经被单独提取出来

asset index.0c57e77abda314a84ed1.js 17.8 KiB
asset print.16746bd586ccb72489d3.js 17.4 KiB
vendors-node_modules_lodash_lodash_js.196895f8828735c59bdd.js 1.37 MiB 

对比不提取时候的体积

asset index.2374a1266dfc8e3e369f.js 1.38 MiB
asset print.fd5a45164084432521fd.js 1.38 MiB 

动态导入

使用 import()

webpack.config.js

module.exports = {
  entry: './src/index.js'
}

index.js

async function component() {
  const element = document.createElement('div')
  const { default: _ } = await import('lodash')   // 使用 Promise 动态的 import
  element.innerHTML = _.join(['Hello', 'Webpack'], ' ')
  const btn = document.createElement('button')
  return element
}
component().then(component => document.body.appendChild(component))

预获取/预加载模块

Cache

若打包的 bundle 文件名不改变,浏览器就会命中缓存

修改输出的文件名

使用文件内容的 hash 值来命名 bundle,使得在 bundle 不变时,hash 值也不改变

webpack.config.js

output: {
  filename: '[name].[contenthash].js'
},

输出的文件:

asset main.f4fb0c69a973e7ecc535.js 1.38 MiB [emitted] [immutable] (name: main)

💡 然而这样的方法并不能完全确保在文件内容不改变时保持 hash 值不变,因为入口 chunk 中可能包含了某些引导模板

提取引导模板 (extracting boilerplate)

使用 SplitChunksPlugin 可以将模块分离到单独的 bundle 中。利用 optimization.runtimeChunk 将 runtime 代码拆分为一个单独的 chunk

webpack.config.js

module.exports = {
  entry: './src/index.js',
  optimization: {
    runtimeChunk: 'single',
  },
};

构建后,runtime 代码已经被提取出来

asset runtime.bf1d01aa56a316de9c1b.js 15.5 KiB [emitted] [immutable] (name: runtime)

接着,我们还可以将第三方库(如 react、lodash)的代码提取到单独的 vendor chunk 中

webpack.config.js

optimization: {
  runtimeChunk: 'single',
  splitChunks: {
    chunks: 'all'
  }
}

再次构建,不仅 runtime 代码被提取出来,第三方库也被提取了出来

asset runtime.bf1d01aa56a316de9c1b.js 15.5 KiB
asset index.217c743aea23034fef65.js 1.98 KiB 
asset print.fce4bfb7ed7994ea07c8.js 1.67 KiB 
vendors-node_modules_lodash_lodash_js.196895f8828735c59bdd.js 1.37 MiB

模块标识符*

vendor bundle 会随着自身的 module.id 的变化,而发生变化,但这不符合需求

optimization: {
  moduleIds: 'deterministic'
}

Tree Shaking

Tree Shaking 是一种通过消除最终文件中未使用的代码来优化体积的方法。

你可以将应用程序想象成一棵树。绿色表示实际用到的 source code(源码) 和 library(库),是树上活的树叶。灰色表示未引用代码,是秋天树上枯萎的树叶。为了除去死去的树叶,你必须摇动这棵树,使它们落下。

webpack.config.js

optimization: {
  usedExports: true
}

math.js

export function square(x) {			// 不会被调用的函数
  return x * x;
}

export function cube(x) {
  return x * x * x;
}

bundle.js

// 没有被引用到的函数会被打上注释标签(development环境下)
/* unused harmony export square */
function square(x) {
  return x * x;
}

side-effect-free

side-effect-free 即 没有副作用,是指 tree shaking 不会错误地把代码给剔除掉。但是真实中的项目不是所有的代码都是这么的纯粹,是 side-effect-free 的。

因此,要在 package.json 中,指定那些不纯粹即不是side-effect-free的文件

"sideEffects": ["xxx.js", "*.css"]	// css 文件就是 none-side-effect-free 的

当然,如果所有的文件都是 side-effect-free,那么在 package.json 中直接指定

"sideEffects": false

最后,要将 mode 切换为 production,才会真正的将不需要的代码给剔除

总结

  • 必须依赖 ES6 的 import export 特性

  • 确保编译器没有将 ES6 转换为 CommonJS

  • 要添加 sideEffects 属性

  • 使用 production mode

lazy load

button.onclick = e => import(/* webpackChunkName: "print" */ './print').then(module => {
	const print = module.default;
  print();
}

略,详见:

output.md
代码分离 | webpack 中文文档 (docschina.org)
代码分离 | webpack 中文文档 (docschina.org)