理解Webpack4用分块策略(common chunk strategy) webpack4 splitchunks

2,490 阅读5分钟

本文已参与「掘力星计划」,赢取创作大礼包,挑战创作激励金。

理解Webpack4用分块策略(common chunk strategy) webpack4 splitchunks

  • 最初,chunks(以及内部导入的模块)是通过内部 webpack 图谱中的父子关系关联的。CommonsChunkPlugin 曾被用来避免他们之间的重复依赖,但是不可能再做进一步的优化。从 webpack v4 开始,移除了 CommonsChunkPlugin,取而代之的是 optimization.splitChunks。 webpack
module.exports =  {
  mode: 'production',
  entry: path.resolve(__dirname, '../src/entry1.js'),
  output: {
    path: path.resolve(__dirname, '../dist'),
    filename: '[name].[chunkhash:8].js',
    chunkFilename: '[name].[chunkhash:8].chunk.js',
  },
  module: {
    rules: [
      {
        test: /.js$/,
        exclude: /node_modules/,
        use: 'babel-loader'
      }
    ]
  },
optimization: {
    splitChunks: {
        chunks: 'all',   // 拆分范围:  三种模式 async (不拆分入口文件)| initial (只拆分入口文件) | all (拆分所有文件) 默认async
        minSize: 30000,  // 生成 chunk 的最小体积(以 bytes 为单位)。
        minChunks: 1,   //  包被引用 >=1 次, 就会进行chunk模块进行拆分, (默认的minChunks=1)
        maxAsyncRequests: 5, // 异步模块(非入口模块)内部的并行最大请求数的 ()
        maxInitialRequests: 3,  //入口模块并行加载最大请求数 (入口文件拆分不超过3个文件, 拆分出太多模块导致请求数量过多而得不偿失)
        automaticNameDelimiter: '~',  // 生成的名称的分隔符
    //   name: true, // 当chunk没有名字时,通过splitChunks分出的模块的名字用id替代,当然你也可以通过name属性自定义
      // name: '拆分出来的',  //可以对拆分出来的文件合并然后重命名
        cacheGroups: {
            vendors: {
            // minChunks: 1,  //  第三方包被引用 >=1 次, 就会进行chunk模块进行拆分, (默认的minChunks=1)
            test: /[\\/]node_modules[\\/]/,  // 只筛选从node_modules文件夹下引入的模块
            priority: -10,  // 权重
            },
            default: {
            minChunks: 2,   // 本地包被引用 >=2 次, 就会进行chunk模块进行拆分,, 权重小于vendors (对比priority)
            priority: -20,   // 权重, 先vendors引用包, 再找本地包,  因为default 权重小于vendors 
            reuseExistingChunk: true,
            }
        }
    },
  },
  plugins: [
    new CleanWebpackPlugin(['dist'], {
      root: process.cwd()
    }),
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, '../public/index.html')
    }),
    new BundleAnalyzerPlugin(),
  ]
}

启动默认 async 模式, 单文件入口

npm run default

启动默认 async 模式, 多文件入口

npm run entry2

测试maxInitialRequests

npm run maxInitialRequests

测试maxAsyncRequests

npm run maxAsyncRequests

输出结果

one-entry

当父chunk和子chunk同时引入相同的module时?

  • 当父chunk和子chunk同时引入相同的module时,并不会将其分割出来而是删除掉子chunk里面共同的module,保留父chunk的module,这个是因为 optimization.removeAvaliableModules 默认是true

通用vendors如何提取到一个vendors(指定的)?

  • todo

默认cacheGroups, 第三方包被引用 >= 1次则会被拆分, 本地包被引用本超过 >=2 则会被拆分,

cacheGroups: {
    vendors: {
        // minChunks: 1,  // 第三方包被引用 >=1 次, 就会进行chunk模块进行拆分, (默认的minChunks=1)
        test: /[\\/]node_modules[\\/]/,  // 只筛选从node_modules文件夹下引入的模块
        priority: -10
    },
    default: {
        minChunks: 2,   // 本地包被引用 >=2 次, 就会进行chunk模块进行拆分,, 权重小于vendors (对比priority)
        priority: -20,  // 权重, 先vendors引用包, 再找本地包,  因为default 权重小于vendors 
        reuseExistingChunk: true  //先vendors引用包, 再找本地包
    }
}

1.打包文件重命名?

  • 当chunk没有名字时,通过splitChunks分出的模块的名字用id替代,当然你也可以通过name属性自定义

在为不同的拆分 chunk 分配相同的名称时,所有 vendor 模块都放在一个共享的 chunk 中,尽管不建议这样做,因为这可能会导致下载更多代码。

splitChunks.usedExports

optimization: {
    splitChunks: {
        ...
      - name: true, // 当chunk没有名字时,通过splitChunks分出的模块的名字用id替代,当然你也可以通过name属性自定义 ~~
      + name: '拆分出来的',  //可以对拆分出来的文件合并然后重命名 
        cacheGroups: {
            vendors: {
                ...
            },
            default: {
                ...
            }
        }
    },
},

2.通用vendors如何提取到一个入口vendors~xxx.chunk.js中?

  • maxAsyncRequests: 5, // 异步模块(非入口模块)内部的并行最大请求数的 ()
    maxInitialRequests: 3,  //入口模块并行加载最大请求数 (入口文件拆分不超过3个文件, 拆分出太多模块导致请求数量过多而得不偿失)
    
splitChunks: {
      // chunks可以为三种值:async,initial,all;决定代码满足条件后是否进行拆分。
      // initial表示只考虑非import()异步导入代码进行拆分,async表示只拆分异步代码块,而all表示同异步都加入拆分范畴。
      chunks: 'async',
      
      // 满足尺寸才发生拆分
      // 例如导入10kb的依赖包小于30kb便不会拆分代码块
      minSize: 30000,
      
      // 当bundle达到maxSize,必须进行拆分
      // 例如jquery与lodash合并成了140kb,maxSize定位80kb,便会拆分两个依赖
      maxSize: 0,
      
      // 最少被引用的chunk个数
      // 例如一个入口块和一个异步块都引用了lodash,minChunks大于2时就不会添加新chunk来装lodash
      minChunks: 1,
      
      // 异步代码块最多可拆分次数
      // 假设某个import()模块有2MB,maxSize设定为500kb,如果此属性为1,模块最多就只能拆分一个bundle出去。
      maxAsyncRequests: 5,
      
      // 这属性和maxAsyncRequests道理一致,不过是作用与initial模块的
      maxInitialRequests: 3,
      
      // bundle自动命名使用的连接字符
      automaticNameDelimiter: '~',
      
      // bundle自动命名时名称长度限制
      automaticNameMaxLength: 30,
      
      // 可为bool、string类型,true是会使用默认命名,否则使用序号命名;string指定文件名称
      name: true,
      
      // 自定义拆分组
      cacheGroups: {
      
        // 每个属性就是一个分组
        vendors: {
        
          // 导入路径的正则匹配,这为所有node_modules引用的模块代码
          test: /[\\/]node_modules[\\/]/,
          
          // 优先级默认为0,如果两个组引用了同一个模块,优先级高的组可以获得此模块
          priority: -10
          
        },
        default: {
          minChunks: 2,
          priority: -20,
          
          // 是否复用其他chunk内已拥有的模块
          // 默认为false,关闭表示拆分出复用部分的模块,给双方引用
          reuseExistingChunk: true
        }
      }
    }

一些提示

  • 默认配置在大多数场景下都有不错的表现
  • webpack的externals更适合把第三方库移到CDN上
  • test匹配规则也可以是一个方法,分组更灵活

更多相关的资料

webpack 4: 代码拆分、代码块关系图及优化插件splitChunks 为 splitChunks 添加 maxSize 选择 不清楚 splitChunks.maxAsyncRequest 有什么用 reuseExistingChunk 的使用例子

欢迎在评论区讨论,掘金官方将在掘力星计划活动结束后,在评论区抽送100份掘金周边,抽奖详情见活动文章」