我们的目标如下:
开发环境性能优化
1.优化打包速度
- HMR(热模块替换)
2.优化代码调试
- source-map
生产环境性能打包
1.优化代码打包速度
- oneof:提高loader的匹配效率
- babel缓存,判断是否以及打包过
- 多进程打包
- externals(外部扩展):他可以规定那个文件不需要打包,从而让期通过CDN引入
- dll:动态链接库,防止某一些引用库的多次引用,从而导致多次打包
2.优化代码运行性能
- 缓存
- tree-shaking
- code split (代码分割)
- 懒加载和预加载
- pwa(离线处理)
HMR(热模块替换)
作用:一个模块发生变化,只会重新打包这一个模块(而不是打包所有模块) 极大提升构建速度
开启HMR
devServer: {
// 开启HMR功能
// 当修改了webpack配置,新配置要想生效,必须重新webpack服务
hot: true
}
样式文件
可以使用HMR功能:因为style-loader内部实现了~
js文件
默认不能使用HMR功能 --> 需要修改js代码,添加支持HMR功能的代码
注意:HMR功能对js的处理,只能处理非入口js文件的其他文件。
src/index.js入口文件,来控制./print.js文件发生改变时候入口文件的处理
// src/index.js
import print from './print';
print();
if (module.hot) {
// 一旦 module.hot 为true,说明开启了HMR功能。 --> 让HMR功能代码生效
module.hot.accept('./print.js', function() {
// 方法会监听 print.js 文件的变化,一旦发生变化,其他模块不会重新打包构建。
// 会执行后面的回调函数
print();
});
}
html文件:
默认不能使用HMR功能.同时会导致问题:html文件不能热更新了~ (不用做HMR功能)
解决:修改entry入口,将html文件引入???????????????????????????????????(这里我还不知道为什么)
entry: ['./src/js/index.js', './src/index.html'],
oneOf
设置oneOf数组添加loader进去,那么文件对于oneOf中的loader 只要匹配里面的了其中一次就不会在遍历其他的loader了,就可以加快遍历速度
注意:不能有两个配置处理同一种类型文件
缓存
babel缓存
作用:让第二次打包构建速度更快
vue-cli中是自己写的cache-loader
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader',
options: {
presets: [
[
'@babel/preset-env',
{
useBuiltIns: 'usage',
corejs: { version: 3 },
targets: {
chrome: '60',
firefox: '50'
}
}
]
],
// 开启babel缓存
// 第二次构建时,会读取之前的缓存
cacheDirectory: true
}
},
文件资源缓存
我们weback打包后会有命名,一般来说都是一样的,如果文字一样那么浏览器就会对相同名字的文件缓存,那么如果我们修改了还是同样的名字的话就会导致浏览器可以没办法及时更新,所以下面有3个字段选择
hash: 每次wepack构建时会生成一个唯一的hash值。
问题: 因为js和css同时使用一个hash值。如果重新打包,会导致所有缓存失效。(可能我却只改动一个css文件,那么其他用了[hash]的都改了)
chunkhash:根据chunk生成的hash值。如果打包来源于同一个chunk,那么hash值就一样
问题: js和css的hash值还是一样的因为css是在js中被引入的,所以同属于一个chunk
contenthash: 根据文件的内容生成hash值。不同文件hash值一定不一样
不过vue-cli中只是简单的使用[name],所以我猜测就是服务器那边如果文件发生替换,那么服务器也就不会让浏览器缓存了。
// 输出的js文件
output: {
filename: 'js/built.[contenthash:10].js',
path: resolve(__dirname, 'build')
},
// 输出的CSS文件
new MiniCssExtractPlugin({
filename: 'css/built.[contenthash:10].css'
}),
懒加载和预加载
懒加载~:当文件需要使用时才加载~,使用import来首先
自定义当前chunk的name: webpackChunkName: 'test'
import(/* webpackChunkName: 'test'./test').then(({ mul }) => {
console.log(mul(4, 5));
});
预加载 prefetch:会在使用之前,提前加载js文件 ,等其他资源加载完毕,浏览器空闲了,再偷偷加载资源
webpackPrefetch: true
那么规定了同一个名字的chunk,就会在某一个更新了同chunk就会同事更新(vur-Router就是用这个来对路由懒加载,和代码分块(chunk))
import(/* webpackChunkName: 'test', webpackPrefetch: true */'./test').then(({ mul }) => {
console.log(mul(4, 5));
});
多进程打包
下载 thread-loader 开启多进场打包
注意:进程启动大概为600ms,进程通信也有开销。只有工作消耗时间比较长,才需要多进程打包(所以在vue-cli中只有在对js进行babel-loader的时候才会进行多进程打包)
externals(外部扩展)
当我们需要通过CDN引用某一个包的时候,所以不希望webpack帮我们打包的时候可以使用
externals: {
// 拒绝jQuery被打包进来
jquery: 'jQuery'
}
DLL
使用dll技术,对某些库(第三方库:jquery、react、vue...)进行单独打包,防止多次引用,造成多次打包的后果
1.需要新建webpack.dll.js文件
const { resolve } = require('path');
const webpack = require('webpack');
module.exports = {
entry: {
// 最终打包生成的[name] --> jquery
// ['jquery'] --> 要打包的库是jquery
jquery: ['jquery'],
},
output: {
filename: '[name].js',
path: resolve(__dirname, 'dll'),
library: '[name]_[hash]' // 打包的库里面向外暴露出去的内容叫什么名字
},
plugins: [
// 打包生成一个 manifest.json --> 提供和jquery映射
new webpack.DllPlugin({
name: '[name]_[hash]', // 映射库的暴露的内容名称
path: resolve(__dirname, 'dll/manifest.json') // 输出文件路径
})
],
mode: 'production'
};
2.运行 webpack --config webpack.dll.js 生成 manifest.json文件(也就是映射文件)
// webpack.config.js
3. 下载 add-asset-html-webpack-plugin 插件进行对manifest.json进行映射,
JSON中的资源都会自动引入出口的HTML文件
const { resolve } = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
const AddAssetHtmlWebpackPlugin = require('add-asset-html-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
filename: 'built.js',
path: resolve(__dirname, 'build')
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html'
}),
// 告诉webpack哪些库不参与打包,同时使用时的名称也得变~
new webpack.DllReferencePlugin({
manifest: resolve(__dirname, 'dll/manifest.json')
}),
// 将某个文件打包输出去,并在html中自动引入该资源
new AddAssetHtmlWebpackPlugin({
filepath: resolve(__dirname, 'dll/jquery.js')
})
],
mode: 'production'
};
重定向,文件后缀补全
resolve: {
// 重定向
alias: {
$css: resolve(__dirname, 'src/css')
},
// 文件的补全
extensions: ['.js', '.json', '.jsx', '.css'],
// node_modules 去什么地方找
modules: [resolve(__dirname, '../../node_modules'), 'node_modules']
},
代码分割
1.对entry进行多入口,那么就可以多chunk
2.在JS中使用import('')异步加载,webpack也会进行单独打包 还可以通过 /* webpackChunkName: 'test' */ 来规定chunk的名字,目前的规定的为test
3.使用optimization(最优化)来执行上面情况会,什么代码会分为同一个chunk
// optimization 配置
optimization: {
splitChunks: {
chunks: 'all' // 同步,异步都可以分割
// 默认值,可以不写~
/* minSize: 30 * 1024, // 分割的chunk最小为30kb
maxSiza: 0, // 最大没有限制 「如果有设置值,可能会二次拆分」
minChunks: 1, // 要提取的chunk最少被引用1次
maxAsyncRequests: 5, // 按需加载时并行加载的文件的最大数量
maxInitialRequests: 3, // 入口js文件最大并行请求数量
automaticNameDelimiter: '~', // 名称连接符
name: true, // 可以使用命名规则
cacheGroups: {
// 分割chunk的组 「vendors」「default」
// node_modules文件会被打包到 vendors 组的chunk中。--> vendors~xxx.js
// 满足上面的公共规则,如:大小超过30kb,至少被引用一次。
vendors: {
test: /[\\/]node_modules[\\/]/,
// 优先级
priority: -10 // 当组条件都满足的时候看这个值的大小
},
default: {
// 要提取的chunk最少被引用2次
minChunks: 2,
// 优先级
priority: -20,
// 如果当前要打包的模块,和之前已经被提取的模块是同一个,就会复用,而不是重新打包模块
reuseExistingChunk: true
}
}*/
},
// 将当前模块的记录其他模块的hash单独打包为一个文件 runtime
// 解决:修改a文件导致b文件的contenthash变化
runtimeChunk: {
name: entrypoint => `runtime-${entrypoint.name}`
},
minimizer: [
// 配置生产环境的压缩方案:js和css
new TerserWebpackPlugin({
// 开启缓存
cache: true,
// 开启多进程打包
parallel: true,
// 启动source-map
sourceMap: true // 编译后代码对源码的映射
})
]
}
tree shaking
- 必须使用ES6模块化
- 开启production环境
作用: 减少代码体积,去除无用代码
注意:
在package.json中配置
"sideEffects": false 所有代码都没有副作用(都可以进行tree shaking)
问题:可能会把css / @babel/polyfill (副作用)文件干掉 都是引用没事使用
"sideEffects": ["*.css", "*.less"]