webpack二刷之五、生产环境优化(4.Code Splitting 代码分包 / 代码分割)

Code Splitting 代码分包 / 代码分割

webpack默认打包依然存在一些弊端。

就是所有代码最终都会被打包到一起(一个bundle文件中)。

如果项目中代码比较复杂,模块比较多,打包结果就会很大(bundle体积过大),很容易超过2、3MB。

而实际情况是,项目启动时,并不是每个模块都需要加载进来。

但是这些模块又被全部打包到一起,需要任何一个模块都必须整体加载进来后才能使用。

项目运行在浏览器端,这样就会浪费掉很多的流量和带宽。

更为合理的方案,就是「分包」「按需加载」

  • 分包:将打包结果,按照一定的规则,分离到多个bundle当中。
  • 按需加载:根据应用的运行需要,按需加载这些模块。

这样就会大大提高项目的响应速度和运行效率。

webpack 合并 与 分离

前面说webpack就是将代码中散落的模块合并到一起,从而提高运行效率。

现在又要求将它们分离开,这是「物极必反」的结果。资源太大不行,太碎了也不行。

项目中划分模块的颗粒度一般非常的细。

很多时候一个模块只是提供了一个小小的工具函数,并不能形成一个完整的功能单元。

如何不将这些散落的模块合并到一起,在运行一个小小的功能时,就会加载很多的模块。

而当前HTTP1.1版本有很多缺陷,例如:

  1. 同域并行请求限制:不能同时对同一个域名发起多次请求
  2. 每次请求都会有一定的延迟
  3. 每次请求除了传输具体的内容外,还会有额外的请求头和响应头,当有大量请求时,这些请求头和响应头加在一起,也是很大的浪费

综上所述,模块打包(合并)是有必要的。

不过在应用越来越大之后,也要慢慢学会变通。

Code Splitting

为了解决打包文件过大的问题,webpack支持「代码分包」的功能,也可以称为「代码分割」。

它通过把打包的模块按照我们设计的规则打包到不同的bundle当中,从而提高应用的响应速度。

目前webpack实现 「分包」的方式主要有两种:

  1. 多入口打包
    1. 根据业务配置不同的打包入口
    2. 配置多个入口同时打包,最终输出多个打包结果
  2. 动态导入
    1. 采用ESM的动态导入的功能import(),实现模块的按需加载。
    2. webpack会将这些动态导入的模块,单独输出到一个bundle当中。
多入口打包 Multi Entry

多入口打包,一般适用于传统的多页应用程序。

最常见的划分规则就是:

  1. 一个页面对应一个打包入口。
  2. 对于不同页面公共的部分,单独提取到一个公共的结果中。

webpack配置文件的entry属性配置类型:

  • 字符串:一个入口文件
  • 字符串数组:多个入口文件打包到一起,相当于一个入口打包
  • 对象:多个入口文件分别打包
    • key:入口的名称
    • value:入口对应的文件路径

每个打包入口会形成一个独立的chunk,入口名称就是这个chunk的name(默认是main)。

一旦配置为多入口,输出的文件名也需要修改:

使用[name]占位符,动态输出文件名,[name]最终替换为入口的名称,即entry的key。

const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
  entry: {
    index: './src/index.js',
    album: './src/album.js',
  },
  output: {
    // 使用[name]占位符,动态输出文件名
    // [name]最终替换为入口的名称,即entry的key
    filename: '[name].bundle.js',
  },
  plugins: [
    new CleanWebpackPlugin(),
    new HtmlWebpackPlugin({
      filename: 'index.html',
      template: './src/index.html',
    }),
    new HtmlWebpackPlugin({
      filename: 'album.html',
      template: './src/album.html',
    }),
  ],
}

此时打包会输出两个html和两个bundle文件,但是发现html文件中将两个bundle文件全部引用了。

这是因为html-webpack-plugin插件会自动生成一个注入所有打包结果的html。

如果需要指定输出的html所使用的bundle,可以使用插件的chunks属性配置:

new HtmlWebpackPlugin({
  filename: 'index.html',
  template: './src/index.html',
  chunks: ['index'],
}),
new HtmlWebpackPlugin({
  filename: 'album.html',
  template: './src/album.html',
  chunks: ['album'],
}),

每个打包入口,都会形成一个独立的chunk

而插件的chunks属性,通过chunk的名称,指定需要注入哪些chunk


多入口打包缺陷:

不同入口中肯定会有公共的模块,根据现在多入口打包的方式,就会出现在不同的打包结果中,会有相同的模块出现。

例如上面的index.js和album.js都导入了一个公共模块或css模块。

解决方案:

将这些公共的模块单独提取输出到一个公共的bundle中。

提取公共模块 Split Chunks

webpack可以在optimization优化配置中开启splitChunks功能,实现提取公共模块打包。

optimization: {
  splitChunks: {
    // all 表示将所有的公共模块都提取到单独的bundle当中
    chunks: 'all'
  }
}

打包后,就会多生成一个album~index.bundle.js文件。

它存放的就是index.js和album.js中公共的模块。

动态导入 Dynamic Imports

「按需加载」是开发浏览器应用中常见的需求。

一般指的按需加载数据。

这里的按需加载指的是:应用运行过程中,需要哪个模块时,才会加载那个模块。

这个方式可以极大的节省带宽和流量。

webpack支持使用动态导入的方式,实现模块的按需加载。

而且所有动态导入的模块,都会被自动分包(形成一个单独的chunk,提取到单独的bundle当中)。

相比多入口方式,动态导入更为灵活。

可以通过代码的逻辑控制是否需要加载某个模块,或何时加载。

使用ESM的import()方法导入指定的模块,它返回一个promise。

例如:

// 入口文件 /src/index.js
const render = (query) => {
  if (query == '#posts') {
    import('./posts/posts')
    	.then(({default: posts}) => {
      	console.log(posts())
	    })
  } else if (query == 'album') {
    import('./album/album')
    	.then(({default: album}) => {
      	console.log(album())
	    })
  }
}
// PS:假设posts和album模块中都使用了相同的公共模块

打包会多生成3个文件,文件名为[number].bundle.js

分别是posts和album模块,另一个是从二者提取出来的公共模块。

这三个文件就是有动态导入、自动分包生成的。

整个过程不需要配置任何地方,例如不需要配置optimization.splitChunks就可以实现提供公共模块。

只需要按照ESM动态导入成员的方式导入模块即可。

webpack内部会自动处理分包、按需加载以及提取公共模块。

如果使用的是单页应用开发框架(如vue,react),项目中的路由映射组件,就可以通过这种动态导入的方式,实现按需加载。

魔法注释 Magic Comments

默认通过动态导入产生的bundle文件,它的名称是一个序号,文件名为[number].bundle.js

可以通过webpack特有的魔法注释,给它们定义名称。

具体使用就是,在import()的参数位置(前后都可以),添加一个特定格式的行内注释:

// 格式:/*webpackChunkName:'<name>'*/
import(/* webpackChunkName: 'posts' */'./posts/posts').then(() => {})
import('./posts/posts'/* webpackChunkName: 'album' */).then(() => {})

生成文件:

  • posts.bundle.js
  • album.bundle.js
  • album~posts.bundle.js 提供公共模块的文件也同步变化

如果多个模块使用的相同的chunkName,那它们最终会被打包到一起,自然不需要提取公共模块,最终只会生成一个文件。

借助这个特点,就可以根据情况,灵活组织动态导入的模块所输出的文件。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值