# 代码分割
安装一个 lodash 插件
yarn add lodash
在 index.js 使用 lodash
import _ from "lodash"
console.log(_.join([1, 2, 3], "***"));
执行打包,发现我们的 app.js 有 1.39 MiB
很明显,我们什么都没有写,仅仅只有几行代码,下次如果更新,我们的app.js 会再次打包也是这么大,这肯定不如我们所愿.
# 拆分 js
新建 lodash.js 并移除 index.js lodash的引入
import _ from 'lodash'
window._ = _;
修改 webpack 配置 entry
entry: {
lodash: './src/lodash.js',
app: './src/index.js',
}
再次执行打包,发现 dist 目录下生成 app.js 和 lodash.js, 这是我们的 app.js 只有 31.8kb, lodash.js 有1.39m
lodash 作为一个库文件肯定是不会经常变更的。对于我们的业务代码 app.js 来说,如果下次更新了,用户只需要拉去最新的 app.js 代码即可, lodash 则根据浏览器的强缓存 而不需要再次请求服务器,大大的提升了性能
# 使用 splitChunks 自动拆分
上面的代码分割是我们自己手动进行的,其实 webpack splitChunks 会自动进行,下面我们来进行配置下.
删除 lodash.js,index.js 还是正常引入 lodash,修改下 webpack 配置
entry:'./src/index.js',
...
optimization: {
splitChunks: {
chunks: 'all'
}
},
再次执行打包,发现 webpack 打包出来了一个 vendor-main.js 自动帮我们进行了代码分割
# SplitChunksPlugin
默认配置如下:
splitChunks: {
chunks: "async",
minSize: 30000,
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
automaticNameDelimiter: '~',
name: true,
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
- async 只会对异步才进行代码分割,对同步无效,需要设置为 all
- cacheGroups 缓存组 抽取公共模块那个地方的
- priority 优先级权重
cacheGroups: {
vendor: {
name: "vendor",
test: /[\\/]node_modules[\\/]/,
chunks: "all",
priority: 10 // 优先级
},
common: {
name: "common",
test: /[\\/]src[\\/]/,
minSize: 1024,
chunks: "all",
priority: 5
}
}
如上配置了两个缓存组 vendor 和 common
- vendor: 抽取来自 node_modules 文件夹下的第三方代码,优先级权重为10
- common: 抽取来自 src 文件夹下的代码,优先级权重为5
- vendor比common的优先级权重高,所以先提取 vendor,再提取 common
# chunks 为什么默认是 async
我们现在需要做这样一个需求,点击 body 创建一个div
import _ from "lodash"
function getComponent() {
var element = document.createElement('div')
element.innerHTML = _.join([1, 2, 3], "***")
return element
}
document.addEventListener('click', () => {
const component = getComponent()
document.body.appendChild(component)
})
讨论这个问题之前,我们首先应该检查一下 页面代码的 Coverage, 在 Chorem 中 找到 Coverage 进行页面录制,发现页面代码使用率
很明显 getComponent里面的代码在首次页面渲染得时候根本用不上,因此代码使用率不高,我们懒加载再来测试一下
document.addEventListener('click', () => {
import(/* webpackChunkName: "click" */ './click.js').then(({ default: func }) => {
const component = func()
document.body.appendChild(component)
})
})
click.js
import _ from "lodash"
function getComponent() {
var element = document.createElement('div')
element.innerHTML = _.join([1, 2, 3], "***")
return element
}
export default getComponent
执行打包 发现报错,提示不识别 /* webpackChunkName: "click" */ 这样得写法,安装插件
npm install --save-dev @babel/plugin-syntax-dynamic-import
配置 .babelrc
"plugins": ["@babel/plugin-syntax-dynamic-import"]
打开 Network 我们发现 首次只加载了 main.js 执行点击后分别加载了 vendors.js 和 click.js
webpack 希望我们是使用异步懒加载来进行编程,因此 chunks 默认 也就是 async. 对于webpack来说 同步提取意义也不是很大
# 利用 prefetch 进行优化
上面我们虽然对代码进行了分割懒加载,我们在点击得时候才会去加载对应得 js 文件,那么这个时候就会给人一种反应 很慢得感觉。 那能不能在进入首页后,在浏览器的空闲时间提前下好我们页面需要的js 文件呢
webpack 给我们提供了 prefetch ,会在浏览器空闲时,下载相应文件,我们再次修改下代码
import(/* webpackPrefetch: true */ './click.js')
# 对 CSS 进行代码分割
安装插件
npm install --save-dev mini-css-extract-plugin
配置 webpack.prod.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module: {
rules: [
{
test: /\.css$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader'
]
}
]
}
plugins: [new MiniCssExtractPlugin({
filename: '[name].css',
chunkFilename: '[id].css',
})],
在 index.js 中引入 index.css 和 index1.css 执行 prod 打包。发现 会生成一个 main.css
# Production 压缩
webpack5可能会内置CSS 压缩器,webpack4需要自己使用压缩器,可以使用 optimize-css-assets-webpack-plugin 插件
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin');
optimization: {
minimizer: [
new OptimizeCSSAssetsPlugin({}),
],
},
再次打包 发现 css 文件被压缩合并了
# 把 CSS 提取到一个文件
可以利用 splitChunks.cacheGroups 将css提取到一个CSS中
optimization: {
...
splitChunks: {
cacheGroups: {
styles: {
name: 'styles',
test: /\.css$/,
chunks: 'all',
enforce: true,
},
},
},
}
# 根据 entry 提取 CSS
可以根据 webpack 的entry name来提取CSS,这对动态引入路由,并且动态加载对于的css这种情况十分有用.
const path = require('path');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
function recursiveIssuer(m) {
if (m.issuer) {
return recursiveIssuer(m.issuer);
} else if (m.name) {
return m.name;
} else {
return false;
}
}
module.exports = {
entry: {
foo: path.resolve(__dirname, 'src/foo'),
bar: path.resolve(__dirname, 'src/bar'),
},
optimization: {
splitChunks: {
cacheGroups: {
fooStyles: {
name: 'foo',
test: (m, c, entry = 'foo') =>
m.constructor.name === 'CssModule' && recursiveIssuer(m) === entry,
chunks: 'all',
enforce: true,
},
barStyles: {
name: 'bar',
test: (m, c, entry = 'bar') =>
m.constructor.name === 'CssModule' && recursiveIssuer(m) === entry,
chunks: 'all',
enforce: true,
},
},
},
},
plugins: [
new MiniCssExtractPlugin({
filename: '[name].css',
}),
],
module: {
rules: [
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader'],
},
],
},
};
# shimming
shimming 垫片的使用
//现在有两个JS文件:
//一个index.js,内容如下
import _ from 'lodash';
import $ from 'jquery';
import {ui} from './jquery-ui';
ui();
/**其他业务代码*/
//一个我们模仿的第三方模块jqurey-ui.js文件:
import $ from 'jquery';
export function ui() {
$('body').css('background', 'red');
}
两个文件的关系:jqurey-ui.js是一个第三方模块,jqurey-ui.js模块依赖于jQuery,
但是这个模块中并没有导入jQuery;我们在index.js中导入了这个模块,
并且在index.js中我们还引入了lodash模块和jquery模块;
此时,我们去执行导入的UI函数可以运行吗?
答案是不可以执行,会报错,这是因为前段模块化存在变量隔离的原因,
在index.js中导入的jQuery并不能在jquery-ui.js中生效;
也就是本模块导入的变量、类、函数,只在本模块生效,不会对导入模块生效;
这个时候我们可以用到 webpack.ProvidePlugin
const webpack = require('webpack');
plugins: [
new webpack.ProvidePlugin({
'_': 'lodash',
})
]
# 使用imports-loader实现垫片
现在我们有这样一个场景,想让所有的 this 都指向 windows 这个时候可以使用 imports-loader
npm install imports-loader --save-dev
webpack 配置
{
test: /\.js$/,
exclude: /node_modules/,
use: [
{ loader: "babel-loader" },
{
loader: "imports-loader? ",
options: "this=>window"
}
],
}