webpack-代码拆分

一、为何要进行代码拆分?

咱们先引入一个应用场景,而后对这个场景进行分析,了解为何须要拆分代码。
首先,安装第三方库lodash,而后在代码分割.js中引入并编写业务代码。html

// 导入第三方库
const _ = require('lodash')

// 业务逻辑代码
console.log(_.join(['a', 'b', 'c']))

接着,配置webpack.config.js进行打包node

const path = require('path')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')

module.exports = {
  entry: './代码分割.js',
  output: {
    path: path.resolve(__dirname, 'build'), // 打包文件的输出目录
    filename: '[name].bundle.js', // 代码打包后的文件名
    publicPath: __dirname + '/build/', // 引用的路径或者 CDN 地址
    chunkFilename: '[name].js' // 代码拆分后的文件名
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader'
        }
      }
    ],
    
  },
  plugins: [
    new CleanWebpackPlugin() // 会删除上次构建的文件,而后从新构建
  ]
}

打包完后会生成一个名为main.bundle.js文件,咱们在index.html中引入,打开浏览器执行。
7.pngwebpack

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <script src="./build/main.bundle.js"></script>
</body>
</html>

浏览器控制台会输出a,b,c
若是咱们改动index.js中的业务代码,改成:git

// 导入第三方库
const _ = require('lodash')

// 业务逻辑代码
console.log(_.join(['a', 'b', 'c'], '***'))

控制台结果变为:a\*\*\*b\*\*\*cgithub

如今咱们抛出一个问题,当咱们把引入的第三方库和业务代码放在一块儿打包,这样会有什么问题?web

假设上面第三方库lodash大小为1M,业务代码为1M,假设打包后的main.bundle.js2Mnpm

浏览器每次打开index.html时,都须要去加载main.bundle.js,而后执行业务代码。加载2M代码是很耗时的,可是浏览器有缓存机制,第二次加载同一文件时会从缓存中读取,刷新页面时网页加载速度更快。可是事与愿违,咱们的业务代码更新很频繁,致使不管是首次加载仍是再次加载都会很慢,那如何去解决这个问题呢?segmentfault

答案很明显,第三方库lodash代码基本上是不会变的,若是咱们可以将业务代码和第三方库代码分开加载,那么第三方库的加载就可用到缓存机制,整个页面的加载时间也会缩短。浏览器

在多个js文件都引入了一样的库或者代码的场景下也是能够进行拆分,避免重复加载。缓存

webpack4 以前是使用 commonsChunkPlugin 来拆分公共代码,v4 以后被废弃,并使用 splitChunksPlugins

在使用 splitChunksPlugins 以前,首先要知道 splitChunksPluginswebpack 主模块中的一个细分模块,无需 npm 引入,只须要配置便可。

const path = require('path')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')

module.exports = {
  entry: './代码分割.js',
  output: {
    path: path.resolve(__dirname, 'build'), // 打包文件的输出目录
    filename: '[name].bundle.js', // 代码打包后的文件名
    publicPath: __dirname + '/build/', // 引用的路径或者 CDN 地址
    chunkFilename: '[name].js' // 代码拆分后的文件名
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader'
        }
      }
    ]
  },
  // 拆分代码配置项
  optimization: {
    splitChunks: {
      chunks: 'all'
    }
  },
  plugins: [
    new CleanWebpackPlugin() // 会删除上次构建的文件,而后从新构建
  ]
}

咱们使用cnpm run dev在开发模式下打包文件,开发模式下打包不会压缩文件,方便查看
8.png
能够看到代码拆分红了两个文件,打开main.bundle.js文件能够看到里卖弄存放的都是业务代码,没有lodash的代码
9.png
打开vendors~main.js文件能够看到lodash代码都在里面
10.png
这样就完成了代码拆分,拆分完的两个文件都要引入到index.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>
<body>
  <script src="./build/vendors~main.js"></script>
  <script src="./build/main.bundle.js"></script>
</body>
</html>

浏览器执行index.html文件控制台结果:a\*\*\*b\*\*\*c

上面采用拆分代码的模式是all,另外还有async、initial,咱们来了解一下

  • async: 只优化动态加载的代码,其余类型的代码正常打包。
  • initial: 针对原始 bundle 代码进行优化。
  • all: 针对全部代码进行优化。
  • function(chunk)自定义拆分函数

详情能够参考本人另外一篇文章:
http://www.javashuo.com/article/p-nzqmzxip-ct.html

分割出来的文件名为vendors~main.js,怎么来的,咱们来分析一下:

当在splitChunks配置项中没有添加cacheGroups对象中的name属性时,默认会在文件名前面加上vendors字段。如今咱们来配置一下name属性更改分割的文件名。

// 拆分代码配置项
  optimization: {
    splitChunks: {
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          chunks: 'all',
          name: 'test' // 定义分割文件名
        }
      }
    }
  },

6.png
写到这,有细心的人可能会问cacheGroups对象的做用是干吗的?

cacheGroups is a plain object with key being the name of chunk and value being some configuration of that chunk. By default, Webpack ships with vendors and default cacheGroups but let’s turn those off by setting their value to false, else it will just confuse you to understand code splitting.

主要概念就是cacheGroups对象中的key是分割块的名称,value是分割块的相关配置。

二、如何拆分代码?

如今有以下代码:

// a,js
import './common'
console.log('A')
export default 'A'

// b.js
import './common'
console.log('B')
export default 'B'

// common.js
console.log('公共模块')
export default 'common'

// index.js
// 异步代码
import(/* webpackChunkName: 'a'*/ './a').then(function(a) {
  console.log(a)
})

import(/* webpackChunkName: 'b'*/ './b').then(function(b) {
  console.log(b)
})

function getComponent() {
  // 使用异步的形式导入 lodash,default: _ 表示用 _ 代指 lodash
  return import('lodash').then(({ default: _ }) => {
    var element = document.createElement('div')
    element.innerHTML = _.join(['hello', 'world'], '-')
    return element
  })
}

getComponent().then(element => {
  document.body.appendChild(element)
})

目录结构为:
7.png
webpack配置:

const path = require('path')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin

module.exports = {
  entry: {
    main: './code_split_test/index.js'
  },
  output: {
    path: path.resolve(__dirname, 'build'), // 打包文件的输出目录
    filename: '[name].bundle.js', // 代码打包后的文件名
    publicPath: __dirname + '/build/', // 引用的路径或者 CDN 地址
    chunkFilename: '[name].js' // 代码拆分后的文件名
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader'
        }
      }
    ]
  },
  // 拆分代码配置项
  optimization: {
    splitChunks: {
      chunks: 'all',
      minSize: 30000,
      maxSize: 0,
      minChunks: 1,
      maxAsyncRequests: 5,
      maxInitialRequests: 3,
      automaticNameDelimiter: '~',
      name: true,
      cacheGroups: {
        lodash: {
          name: 'lodash',
          test: /[\\/]node_modules[\\/]/,
          priority: 10
        },
        common: {
          name: 'common',
          minSize: 0, //表示在压缩前的最小模块大小,默认值是 30kb,若是没设置为0,common模块就不会抽离为公共模块,由于原始大小小于30kb
          minChunks: 2, // 最小公用次数
          priority: 5, // 优先级
          reuseExistingChunk: true // 公共模块必开启
        },
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          priority: -10
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true
        }
      }
    }
  },
  plugins: [
    new CleanWebpackPlugin(), // 会删除上次构建的文件,而后从新构建
    new BundleAnalyzerPlugin()
  ]
}

打包结果:
8.png

咱们来分析一下文件之间的依赖关系:
10.png
index.js依赖于a.js、b.js、lodash.js,而且是动态加载,a.jsb.js都依赖common.js,是同步加载。

很明显,若是咱们采用aysnc模式拆分,分割出的a.jsb.js里面都会存在common.jscommon.js模块不会被提取成公共模块,得不到复用。
所以,咱们能够采用allcommon.js模块会被提取成共享模块。
9.png

如今将打包后的主文件main.bundle.js引入到index.html中,奇怪的是主文件的引入并无使得分割后的文件自动引入
1.png
而后我去查看了一下主文件里面是否含有引入分割文件的脚本代码,发现是有的
2.png

3.png
这就很无语了,可是当我把ES6转译有关配置注释掉再打包执行index.html文件,发现是可行的

// module: {
  //   rules: [
  //     {
  //       test: /\.js$/,
  //       exclude: /node_modules/,
  //       use: {
  //         loader: 'babel-loader'
  //       }
  //     }
  //   ]
  // },

4.png
这个问题究竟是为何呢?直到如今我也尚未搞懂,但愿哪位大佬注意到后能帮我解决一下疑惑。

参考文章:
https://itxiaohao.github.io/passages/webpack4-code-splitting/

相关文章
相关标签/搜索