配置文件的分离

一个项目可以根据不同的环境,比如生产环境和开发环境,进行不同的配置,有两种实现方式

方案一:所有webpack配置都在同一个文件中,根据执行时传入的环境变量判断环境

  1. // 执行build和serve指令接收不同的环境变量
  2. "scripts": {
  3. "build": "webpack --config ./config/webpack.common.js --env production",
  4. "serve": "webpack serve --config ./config/webpack.common.js --env development"
  5. },
  1. // 这里通过判断传入的 env 值来判断环境
  2. const path = require('path');
  3. module.exports = function (env) {
  4. const isProduction = env.production;
  5. return {
  6. entry: './src/main.js',
  7. output: {
  8. path: path.resolve(__dirname, '../build'),
  9. },
  10. };
  11. };

方案二:针对不同的环境创建不同的配置文件,提取公共配置在common文件中

  1. // 执行build和serve指令调用不同的文件
  2. "scripts": {
  3. "build": "webpack --config ./config/webpack.prod.js",
  4. "serve": "webpack serve --config ./config/webpack.dev.js",
  5. },

代码分离

代码分离(Code Splitting)是webpack一个非常重要的特性:

  • 它主要的目的是将代码分离到不同的bundle中,之后我们可以按需加载,或者并行加载这些文件
  • 比如默认情况下,所有的JavaScript代码(业务代码、第三方依赖、暂时没有用到的模块)在首页全部都加载就会影响首页的加载速度
  • 代码分离可以分出更小的bundle,以及控制资源加载优先级,提供代码的加载性能

webpack中常用的代码分离有三种:

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

    第一种

    入口手动分离代码

    Untitled (2).png
    上面的代码虽然在同一个文件中,但是代码逻辑毫无关联,所以我们手动将它分离为两个文件
    Untitled (3).png
    在webpack的config中设置两个文件的入口,再通过[name]的方式命名打包后的文件
    Untitled (4).png
    最终打包的结果如下
    Untitled (5).png
    这种方法一看就很扯,项目大起来根本分离代码就能把人累死,所以根本没人用

    Entry Dependencies(入口依赖)

假如我们的index.js和main.js都依赖两个库:lodash、dayjs

  • 如果我们单纯的进行入口分离,那么打包后的两个bundle都会有一份相同的lodash和dayjs
  • 事实上我们可以对他们进行共享

假如我们的两个文件都引入了lodash和dayjs这两个库

我们可以如下操作实现

引入webpack原生的一个plugin插件

const TerserPlugin = require('terser-webpack-plugin');

然后在entry中如下配置

  1. entry: {
  2. main: { import: './src/main.js', dependOn: ['lodash', 'dayjs'] },
  3. index: { import: './src/index.js', dependOn: ['lodash', 'dayjs'] },
  4. lodash: 'lodash',
  5. dayjs: 'dayjs',
  6. },

最终打包效果如下
Untitled (6).png
还可以用这种方法简化

  1. entry: {
  2. main: { import: './src/main.js', dependOn: 'shared' },
  3. index: { import: './src/index.js', dependOn: 'shared' },
  4. shared: ['lodash', 'dayjs'],
  5. },

不过还是稍显复杂

第二种:SplitChunks

另外一种分包的模式是splitChunk,它是使用SplitChunksPlugin来实现的:

  • 因为该插件webpack已经默认安装和集成,所以我们并不需要单独安装和直接使用该插件
  • 只需要提供SplitChunksPlugin相关的配置信息即可

Webpack提供了SplitChunksPlugin默认的配置,我们以可以手动来修改它的配置:

  • 比如默认配置中,chunks仅仅针对于异步(async)请求,我们可以设置为initial或者all

具体配置:

  1. entry: {
  2. main: './src/main.js',
  3. index: './src/index.js',
  4. // main: { import: './src/main.js', dependOn: 'shared' },
  5. // index: { import: './src/index.js', dependOn: 'shared' },
  6. // shared: ['lodash', 'dayjs'],
  7. },

首先,entry中不需要额外设置

  1. optimization: {
  2. ...
  3. splitChunks: {
  4. // async异步导入
  5. // initial同步代码
  6. // all异步同步都导入
  7. chunks: 'all',
  8. },
  9. },

然后,在optimization中添加splitChunks对象,对象中属性值chunks设置为all表示不论是异步代码还是同步代码全部做分包处理,即不同模块加载的相同模块只打包一次,两个模块分别引入,而不是分别打包到两个文件中

minSize

最小尺寸,如果拆分出来一个包,那么拆分出来的这个包的大小最小为minSize

  1. splitChunks: {
  2. chunks: 'all',
  3. // 最小尺寸:如果拆分出来一个,那么拆分出来的这个包的大小最小为minSize
  4. minSize: 20000
  5. },

maxSize

将大于maxSize的包拆分成不小于minSize的包

  1. splitChunks: {
  2. chunks: 'all',
  3. // 将大于maxSize的包,拆分成不小于minSize的包
  4. maxSize: 30000,
  5. },

minChunks

表示引入的包,至少被导入了几次

  1. splitChunks: {
  2. chunks: 'all',
  3. // minChunks只拆分被打包minChunks及以上次数的文件
  4. minChunks: 2,
  5. },

第三种:动态导入

另外一个代码拆分的方式是动态导入,webpack提供了两种实现动态导入的方式:

  • 第一种,使用ECMAScript中的import()语法来完成,也是目前推荐的方式
  • 第二种,使用webpack一流的require.ensure,目前已经不推荐使用
  1. // 只要是异步导入的代码,webpack都会进行代码分离
  2. import('./foo').then((res) => {
  3. console.log(res);
  4. });

魔法注释

magic comments,可以给动态引入的包打包后的文件命名

输出文件的命名规则

output: {
    ...
  chunkFilename: '[name].chunk.js',
},

在要使用magic comments的文件中这样注释

// magic comments
import(/* webpackChunkName:"foo" */ './foo').then((res) => {
  console.log(res);
});

import(/* webpackChunkName:"foo_02" */ './foo_02').then((res) => {
  console.log(res);
});

这里的 / xxx / 内容会被上面的 chunkFilename 中的 name 替换掉

最终创建的文件如下图
Untitled (7).png