vuecli3项目中webpack4配置(三)代码分割

为了不一次加载过大的文件和充分利用浏览器缓存,咱们须要将不常常被修改的文件单独打包。即对项目中的代码进行分割。webpack支持两种代码分割的方式。html

1.同步引入文件vue

index.js代码:node

import _ from 'lodash';
console.log(_.join(['Hello', 'webpack'], ' '));
复制代码

若是直接打包index.js会发现业务代码和loadsh库打包到了一个bundle.js文件中。可见webpack默认不会处理同步引入的文件。此时修改webpack的配置,增长optimization.splitChunks的配置项:webpack

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

module.exports = {
  mode: 'development',
  entry: {
	index: './src/index.js'
  },
  output: {
	filename: 'bundle.js',
	chunkFilename: '[name].bundle.js',
	path: path.resolve(__dirname, 'dist')
  },
  plugins: [
	new CleanWebpackPlugin(),
	new HtmlWebpackPlugin()
  ],
  optimization: {
	splitChunks: {
	  chunks: 'all'
	}
  }
}
复制代码

再次打包发现除了bundle.js文件还生成了vendors~index.bundle.js。咱们成功的将lodash分割了出来。git

2.异步引入文件github

修改index.js代码:web

function getComponent() {
	return import(/* webpackChunkName: "lodash" */ 'lodash').then(({default: _}) => {
		const element = document.createElement("div");
		element.innerHTML = _.join(['Hello', 'webpack'], ' ');
		return element;
	}).catch(error => 'An error occurred while loading the component');
}
getComponent().then(component => {
	document.body.appendChild(component);
})
复制代码

删除掉webpack中optimization.splitChunks的配置项后再进行打包,结果和上面的一致。loadsh被提取到了vendors~lodash.bundle.js文件中。因此webpack默认是仅对异步加载的块进行分割。咱们须要default的缘由是,因为Webpack4在导入CommonJS模块时,导入将再也不解析为module.exports的值,而是为CommonJS模块建立一我的工命名空间对象。vue-cli

咱们还能够用async函数对上面的代码进行简化:npm

async function getComponent() {
	const {default: _} = await import(/* webpackChunkName: "lodash" */ 'lodash');
	const element = document.createElement("div");
	element.innerHTML = _.join(['Hello', 'webpack'], ' ');
	return element;
}
getComponent().then(component => {
	document.body.appendChild(component);
})
复制代码

打包结果一样将lodash分割了出来。element-ui

可是不论是同步引入模块仍是异步引入都会受splitChunks配置项的影响(例如分割出的lodash名称为vendors~lodash.bundle.js)。下面咱们来详细的说一下webpack中splitChunks默认配置:

module.exports = {
  optimization: {
    splitChunks: {
      chunks: 'async',//表示将选择哪些块进行优化。提供字符串时,有效值为all、async和initial,默认是仅对异步加载的块进行分割。
      minSize: 30000,//模块大于minSize时才会被分割出来。
      maxSize: 0,//生成的块的最大大小,若是超过了这个限制,大块会被拆分红多个小块。
      minChunks: 1,//拆分前必须共享模块的最小块数。
      maxAsyncRequests: 5,//按需加载时并行请求的最大数目。
      maxInitialRequests: 3,//入口点的最大并行请求数
      automaticNameDelimiter: '~',//默认状况下,webpack将使用块的来源和名称(例如vendors~main.js)生成名称。此选项容许您指定要用于生成的名称的分隔符。
      automaticNameMaxLength: 30,//容许为SplitChunksPlugin生成的块名称的最大长度
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,//控制此缓存组选择的模块。省略它将选择全部模块。它能够匹配绝对模块资源路径或块名称。匹配块名称时,将选择块中的全部模块。
          priority: -10//一个模块能够属于多个缓存组,模块出如今优先级最高的缓存组中
        },
        default: {
          minChunks: 2,
          priority: -20,
          reuseExistingChunk: true//若是当前块包含已经从主包中分离出来的模块,那么该模块将被重用,而不是生成新的模块
        }
      }
    }
  }
};
复制代码

这些配置共同控制打包流程,例如仍是以前同步引入lodash的例子

index.js

import _ from 'lodash';
console.log(_.join(['Hello', 'webpack'], ' '));
复制代码

修改webpack的配置:

optimization: {
	splitChunks: {
	    chunks: 'all',
	    cacheGroups: {
		vendors: false,
	        default: false
	    }
	}
}
复制代码

再次打包发现lodash并无被分割出来,chunks虽然设置成了'all',可是webpack还会继续查看cacheGroups中的配置,发现都为false,webpack就不知道要把lodash打包到哪里,致使分割失败。

那么继续修改下配置:

optimization: {
	splitChunks: {
		chunks: 'all',
		cacheGroups: {
		    vendors: {
	                test: /[\\/]node_modules[\\/]/,
	                priority: -10
	             },
		     default: false
	         }
	}
}
复制代码

再次打包,由于lodash是属于node_modules模块的,因此lodash被提取到了vendors~index.bundle.js文件中,表明来源于vendors缓存组而且是从index.js模块分割出来的。

继续修改配置:

optimization: {
	splitChunks: {
		chunks: 'all',
		cacheGroups: {
			vendors: false,
			default: {
	                    minChunks: 2,
	                    priority: -20,
	                    reuseExistingChunk: true
	                 }
	        }
	}
}
复制代码

再次打包发现lodash并无被分割出来,default不该该是默认匹配全部的么,仔细查看配置发现minChunks默认为2,意思是lodash至少须要被引用两次才会被分割出来,咱们的代码中lodash只被index.js引用了一次。因此咱们修改minChunks为1,再次打包,lodash被打包到了default~index.bundle.js文件中。说明此时被匹配到了default缓存组里面。若是一个模块被同时匹配到了多个缓存组,就要根据priority来决定模块被分配到哪一个缓存组了,priority越大优先级越高。因此代码分隔是受splitChunks多个配置项共同控制的。

vue-cli3对cacheGroups的初始配置(@vue\cli-service\lib\config\prod.js)为:

webpackConfig
    .optimization.splitChunks({
      cacheGroups: {
        vendors: {
          name: `chunk-vendors`,
          test: /[\\/]node_modules[\\/]/,
          priority: -10,
          chunks: 'initial'
        },
        common: {
          name: `chunk-common`,
          minChunks: 2,
          priority: -20,
          chunks: 'initial',
          reuseExistingChunk: true
        }
      }
    })
复制代码

在咱们的项目中要引入不少的第三方组件库例如element-ui,还有公司内部的一些组件库,若是咱们使用默认配置不作任何修改,vendors缓存组中的内容会不少,页面引用时耗时就会较长,因此将缓存组中的内容进行拆分是颇有必要的。

例如咱们将element-ui分割成一个独立的chunk,cacheGroups中配置以下:

cacheGroups: {
          elementUI: {
            priority: 20,
            name: "element-ui",
            test: /element-ui/,
            reuseExistingChunk: true
          },
          vendors: {
            chunks: 'all',
            test: /[\\/]node_modules[\\/]/,
            priority: 0,
            minChunks: 2,
            name: 'vendors',
            reuseExistingChunk: true
          }
}
复制代码

elementUI缓存组的test我直接用的模块名称,至于为何不是:

test: /[\\/]node_modules[\\/]element-ui[\\/]/
复制代码

是由于项目若是是用npm install安装的没有问题,element-ui能够被分割出来。可是假如是用cnpm install安装的,安装完的element-ui模块名称为_element-ui@版本号@element-ui,就不能匹配到elementUI缓存组中了,使用模块名称能够兼容两种安装方式。

再说一下optimization.runtimeChunk它的配置很简单:

config.optimization.runtimeChunk = {
    name:'manifest'
};
复制代码

做用却很大,它是将包含chunks 映射关系的 list单独从 app.js里提取出来,由于每个 chunk 的 id 基本都是基于内容 hash 出来的,因此你每次改动都会影响它,若是不将它提取出来的话,等于app.js每次都会改变。缓存就失效了。

若是要分析本身项目中的打包结果,可使用webpack-bundle-analyzer插件,来对大的包或者app.js进行优化。

相关文章
相关标签/搜索