上篇:webpack笔记(一)
前端中的tree-shaking可以理解为通过工具"摇"我们的JS文件,将其中用不到的代码"摇"掉,是一个性能优化的范畴。具体来说,在 webpack 项目中,有一个入口文件,相当于一棵树的主干,入口文件有很多依赖的模块,相当于树枝。实际情况中,虽然依赖了某个模块,但其实只使用其中的某些功能。通过 tree-shaking,将没有使用的模块摇掉,这样来达到删除无用代码的目的。
Tree Shaking 只支持ES Module
在开发环境
// webpack.config.js
optimization: {
usedExports: true
}
// package.json
"sideEffect": false,
// "sideEffect": ["*.css"] 不希望tree-shaking的配置在这里
在生产环境,自动配置好tree-shaking
在开发环境和生成环境,有source-map,DevServer、HMR、压缩等的区别。
package.json
{
"scripts": {
"dev": "webpack-dev-server --config webpack.dev.js",
"build": "webpack --config webpack.prod.js"
},
}
新建两个文件webpack.dev.js和webpack.prod.js
但是有很多相同的代码!
提取公共部分到webpack.common.js
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const {
CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
entry: {
main: './src/index.js'
},
output: {
publicPath: '/',
filename: '[name].js',
path: path.join(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.css$/,
use: ['style-loader', 'css-loader']
},
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
targets: {
edge: '17',
firefox: '60',
chrome: '67',
safari: '11.1'
}
}
]
]
}
}
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: 'src/index.html'
}),
new CleanWebpackPlugin()
],
};
安装 webpack-merge
npm install webpack-merge -D
改写webpack.prod.js
const {
merge } = require('webpack-merge');
const commonConfig = require('./webpack.common.js');
const prodConfig = {
mode: 'production'
};
module.exports = merge(commonConfig, prodConfig);
改写webpack.dev.js
const webpack = require('webpack');
const {
merge } = require('webpack-merge');
const commonConfig = require('./webpack.common.js');
const devConfig = {
mode: 'development',
devtool: 'source-map',
devServer: {
contentBase: './dist'
// hot: true,
// hotOnly: true
},
plugins: [new webpack.HotModuleReplacementPlugin()],
optimization: {
usedExports: true
}
};
module.exports = merge(commonConfig, devConfig);
代码分割
在以前,为了减少 HTTP 请求,通常地,我们都会把所有的代码都打包成一个单独的 JS 文件。但是,如果这个 JS 文件体积很大的话,那就得不偿失了。
这时,我们不妨把所有代码分成一块一块,需要某块代码的时候再去加载它;还可以利用浏览器的缓存,下次用到它的话,直接从缓存中读取。很显然,这种做法可以加快我们网页的加载速度,美滋滋!
所以说,Code Splitting 其实就是把代码分成很多很多块( chunk)咯。
optimization: {
splitChunks: {
chunks: 'all'
}
}
然后会生成main.js和vendors~main.js
异步代码
function getComponent() {
return import('lodash').then(({
default: _ }) => {
var element = document.createElement('div');
element.innerHTML = _.join(['Dell', 'Lee'], '-');
return element;
});
}
getComponent().then(element => {
document.body.appendChild(element);
});
代码分割和webpack无关,webpack中实现代码分割,两种方式
1.同步代码:只需要在webpack.common.js中做optimization的配置即可
2.异步代码(import):异步代码,无需做任何配置,会自动进行代码分割
SplitChunkPlugin配置
return import(/* webpackChunkName: "lodash" */'lodash')
这样会生成vendors~lodash.js这个文件
打包生成文件名和指定名称相同
optimization: {
splitChunks: {
cacheGroups: {
vendors: false,
default: false
}
}
}
默认配置
module.exports = {
//...
optimization: {
splitChunks: {
// chunks:async只对异步代码进行代码分割,all会同步异步都分割
// 我们打包一个同步代码块,会寻找vendors配置,如果在node_modules中,会进行分割
chunks: 'async',
// 大于30kb,进行代码分割
minSize: 30000,
maxSize: 0,
// 至少用几次才会被分割
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
automaticNameDelimiter: '~',
name: true,
// =>缓存组
cacheGroups: {
// 不同的组
vendors: {
test: /[\\/]node_modules[\\/]/,
// 优先级
priority: -10
},
default: {
minChunks: 2,
priority: -20,
// 重用现有模块
reuseExistingChunk: true
}
}
}
}
};
import()返回一个Promise类型
function getComponent() {
return import(/* webpackChunkName: "lodash-dj" */ 'lodash').then(
({
default: _ }) => {
var element = document.createElement('div');
element.innerHTML = _.join(['Dell', 'Lee'], '-');
return element;
}
);
}
document.addEventListener('click', () => {
getComponent().then(element => {
document.body.appendChild(element);
});
});
async await改写
async function getComponent() {
const {
default: _ } = await import(
/* webpackChunkName: "lodash-dj" */ 'lodash'
);
var element = document.createElement('div');
element.innerHTML = _.join(['Dell', 'Lee'], '-');
return element;
}
document.addEventListener('click', async () => {
const element = await getComponent();
document.body.appendChild(element);
})
打包只生成的每一个js文件都称为一个chunk
// 简易配置代码分割,交给webpack自动处理
optimization: {
splitChunks: {
chunks: 'all',
}
}
github webpack/analyse
# 输出打包信息到 stats.json
webpack --profile --json > stats.json --config webpack.dev.js
webpack-bundle-analyzer工具使用
Install
npm install --save-dev webpack-bundle-analyzer
Usage (as a plugin)
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer')
plugins: [
new BundleAnalyzerPlugin()
]
打包完成之后自动打开127.0.0.1:8888查看分析
参考原文:聊一聊webpack中的preloading和prefetching
官方文档:预取/预加载模块(prefetch/preload module)
在浏览器中 按 Ctrl + Shift + P
,然后在弹出的对话框中输入 coverage
从红色的方框中可以看出当前加载的文件中在当前页面中的利用率
通过异步加载,提升代码利用率,在加载当前页的时候,异步加载不必要的代码
现在我们就看出来 webopack 为什么要使用 chunks: 'async'
这样的默认配置了。
webpack 优化的侧重点是代码的使用率而不是缓存,只是使用缓存的方式来优化意义是不大的,通过异步的方式提高代码的利用率才能比较大程度地提高网站的性能。
有些小伙伴可能会想,能不能在加载完页面网络空闲的时候先把这些文件加载进来呀,真聪明,这就是接下来要讲的 Preloading 和 Prefetching。
Prefetching
使用方法也比较简单,就是在要异步加载的文件前面加上 /* webpackPrefetch: true */
这个 magic comment 即可。
我们把需要交互才能用到的代码,完全可以写在异步组件里。
document.addEventListener('click', () => {
import(/* webpackPrefetch: true */ './click.js').then(({
default: func}) => {
func();
})
});
生成的 0.js 是 click.js 打包之后,可以看出在页面加载完之后的空闲时间还没有点击页面时已经加载了 0.js ,当点击页面时,0.js 直接从缓存中读取,因此耗时非常短。
Preloading 和 Prefetching 有什么区别?
两者的最大区别在于,Prefetching 是在核心代码加载完成之后带宽空闲的时候再去加载,而 Preloading 是和核心代码文件一起去加载的。
因此,使用 Prefetching 的方式去加载异步文件更合适一些。