webpack的性能优化(一)——分包优化

发布时间:2024年01月08日

1.什么是分包?为什么要分包?

????????默认情况下,Webpack 会将所有代码构建成一个单独的包,这在小型项目通常不会有明显的性能问题,但伴随着项目的推进,包体积逐步增长可能会导致应用的响应耗时越来越长。归根结底这种将所有资源打包成一个文件的方式存在两个弊端:

  • 资源冗余:客户端必须等待整个应用的代码包都加载完毕才能启动运行,但可能用户当下访问的内容只需要使用其中一部分代码
  • 缓存失效:将所有资源达成一个包后,所有改动 —— 即使只是修改了一个字符,客户端都需要重新下载整个代码包,缓存命中率极低

????????这些问题都可以通过代码分离解决,例如?node_modules?中的资源通常变动较少,可以抽成一个独立的包,那么业务代码的频繁变动不会导致这部分第三方库资源被无意义地重复加载。

代码分离(Code Splitting) 是webpack一个非常重要的特性:
  • 它主要的目的是将代码分离到不同的bundle中,之后我们可以按需加载,或者并行加载这些文件; 比如默认情况下,所有的JavaScript代码(业务代码、第三方依赖、暂时没有用到的模块)在首页全部都加载, 就会影响首页的加载速度;
  • 代码分离可以分出出更小的bundle,以及控制资源加载优先级,提供代码的加载性能;?
Webpack中常用的代码分离有三种:
  • 入口起点:使用entry配置手动分离代码;
  • 防止重复:使用Entry Dependencies或者SplitChunksPlugin去重和分离代码;
  • 动态导入:通过模块的内联函数调用来分离代码;

2.配置多入口

入口起点的含义非常简单,就是配置多入口:
  • 比如配置一个index.js和main.js的入口;
  • 他们分别有自己的代码逻辑;
const path = require('path'); 
module.exports = { 
    entry: { 
        main: './src/main.js', // 第一个入口起点 
        app: './src/app.js' // 第二个入口起点 
    }, 
   output: { 
       filename: '[name].bundle.js', // 使用[name]占位符将生成的文件名与入口起点名称对应 
       path: path.resolve(__dirname, 'build') 
    } ,
   };
  • 假如我们的index.js和main.js都依赖两个库:lodash、dayjs
  • 如果我们单纯的进行入口分离,那么打包后的两个bunlde都有会有一份lodash和dayjs;
事实上我们可以对他们进行共享;
const path = require('path'); 
module.exports = { 
    entry: { 
        main: { import: './src/main.js', dependOn: 'shared' }, // 第一个入口起点 
        app:  { import: './src/app.js', dependOn: 'shared' }, // 第二个入口起点 
        shared: ['dayjs', 'lodash'] // 共享的库
    }, 
   output: { 
       filename: '[name].bundle.js', // 使用[name]占位符将生成的文件名与入口起点名称对应 
       path: path.resolve(__dirname, 'dist') } 
   };

3.SplitChunks

Webpack 提供了?SplitChunkPlugin?进行分包优化。SplitChunksPlugin 插件可以将应用程序中共享的代码拆分成单独的块,以便将其从应用程序代码中分离出来,从而提高性能和加载速度。它的优化原理如下:

3.1 SplitChunks做了什么?

分析模块之间的依赖关系

SplitChunksPlugin 会分析模块之间的依赖关系,并根据这些关系确定哪些模块可以组成一个共享块。这样可以确保代码被正确地分离,而不会出现意外的行为。

根据配置项生成共享块

SplitChunksPlugin 根据配置项生成共享块。配置项包括?minSize(指定共享块的最小大小)、maxSize(指定共享块的最大大小)、minChunks(指定一个模块至少被使用的次数才会被拆分成共享块)等。

将共享块提取出来

在分析和生成共享块后,SplitChunksPlugin 会将共享块提取出来,并创建新的?chunk(即打包后的文件),将这些共享块放入新的 chunk 中。这样,每个共享块只需被下载一次,而不必重复下载多次,从而提高了应用程序的加载速度。

将共享块缓存起来

为了进一步提高性能,SplitChunksPlugin 会将共享块缓存起来,并在后续的构建中重复使用它们。这样,如果某个共享块已经存在于缓存中,就不必再重新生成它,从而节省了构建时间。

4.动态导入

????????当代码中存在不确定会被使用的模块时,最佳做法是将其分离为一个独立的 JavaScript 文件。

  • 这样可以确保在不需要该模块时,浏览器不会加载或处理该文件的 JavaScript 代码。
  • 我们平时使用的路由懒加载的就是这个原理,都是为了优化性能而延迟加载资源。

实现动态导入的方式是使用ES6的import()语法来完成。

注意:使用动态导入bar.js:
  • 在webpack中,通过动态导入获取到一个对象
  • 真正导出的内容,在该对象的default属性中,所以我们需要做一个简单的解构;

4.1 路由懒加载

动态导入最常见的使用场景就是路由懒加载

// main.js文件中
const homeBtn = document.createElement('button')
const aboutBtn = document.createElement('button')
homeBtn.textContent = '加载home文件'
aboutBtn.textContent = '加载about文件'

document.body.appendChild(homeBtn)
document.body.appendChild(aboutBtn)

homeBtn.addEventListener('click', () => {
    import('./views/home.js')
})

aboutBtn.addEventListener('click', () => {
    import('./views/about.js')
})

打包之后的资源:?

4.2 配置打包文件的名称

但是我们会发现一个问题,从包名中无法区分是哪个文件构建后的包,我们可以通过以下方式修改打包之后的文件名:

  • output.chunkFilename
const { resolve } = require('path');

module.exports = {
  entry: './src/main.js',
  output: {
    filename: 'bundle.js',
    path: resolve(__dirname, 'build'),
    chunkFilename: 'chunk_[name]_[id].js',
  },
};

默认情况下我们获取到的 [name][id] 的名称保持一致的,如果我们希望修改name的值,可以通过magic comments(魔法注释)的方式;

  • webpackChunkName 魔法注释
// main.js
homeBtn.addEventListener('click', () => {
    import(/* webpackChunkName: "home" */'./views/home.js') // 让webpack读取的魔法注释,固定写法
})

aboutBtn.addEventListener('click', () => {
    import(/* webpackChunkName: "about" */'./views/about.js')
})

打包之后的资源,可以从名称看出原始文件是哪一个?

4.3?prefetch

4.4 preload

5.runtime代码的分包

6.将css提取到一个独立的css文件

参考:

Webpack 优化实操(十一):页面分包优化 - 知乎 (zhihu.com)

webpack性能优化(一):分包 - 掘金 (juejin.cn)

文章来源:https://blog.csdn.net/ICanWin_lll/article/details/135429948
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。