日常开发中经常使用使用webpack对我们的代码进行打包编译。平时这方面我都是直接cv大法伺候,没怎么去理解。这篇文章主要是记录我对webpack的学习理解。
打包构建速度
HotModuleReplacement
每次我们改动了代码时,webpack会对整个模块进行重新打包编译,这导致速度很慢,而HotModuleReplacement(HMR) 也称热模块替换,能在程序运行中,对模块删除,替换,而不是重新加载整个模块。在webpack 5是默认开启的
devServer: {
// 端口号
port: 3000,
// 域名
host: "localhost",
// 自动打开浏览器
open: true,
// 热更新
hot:false,
},
当我们hot改成false时候,对代码修改会导致整个页面重新加载(在webpack5中默认开启)
我们在入口文件main.js写上这样一句
if(module.hot){ //判断是否支持热模块替换
module.hot.accept("./js/cout")
}
这里就表示在cout文件下单js改动就只会触发cout的更新(需要那个文件热更新就将路径放入accept函数即可),这样就会让打包更快。当然在当下,有vue-loader,react-hot-loader这些loader帮我们处理相关的模块,我们不用一个个文件写入accept中。accept提供第二个可选参数,可以传一个函数,如果该文件改变就会执行这个函数,
module.hot.accept("./js/cout",function(){
console.log("qwe")
})
这样每次更改cout.js就会触发函数;
oneOf
在webpack中 配置加载其module.rules,会一个个的匹配文件是否命中,无论命中与否,他都会把所有规则匹配完毕。有的时候这是没必要的`,比如一个a.css文件在下面匹配规则中第一个命中了不会停下来,会继续匹配所有规则。但是这是没必要的,我们想要他在第一个命中后就停止匹配,这样打包的效率就会高一点,这时候我们就可以去配置oneOf
module: {
rules: [
{
test: /\.css$/i,
use: ["style-loader", "css-loader"], //从右到左,从下到上的加载
},
{
test: /\.s[ac]ss$/i,
use: [
// 将 JS 字符串生成为 style 节点
"style-loader",
// 将 CSS 转化成 CommonJS 模块
"css-loader",
// 将 Sass 编译成 CSS
"sass-loader",
],
},
{
test: /\.(png|gif)$/,
type: "asset",
parser: {
dataUrlCondition: {
//小于4kb 的图片转base64
maxSize: 4 * 1024, // 4kb
},
},
generator: {
//输出图片名称
filename: "static/image/[hash:10][ext][query]",
},
},
{
test: /\.m?js$/,
exclude: /(node_modules)/, //排除node_modules文件不处理
use: {
loader: "babel-loader",
options: {
presets: ["@babel/preset-env"],
},
},
},
],
在以上配置代码中,把配置放入oneOf里面即可,当命中一个的时候就不会继续向下匹配,当项目庞大的时候可以缩减一定的打包时间
//加载器
module: {
rules: [
{
oneOf: [ //命中一个之后就不会继续匹配
{
test: /\.css$/i,
use: ["style-loader", "css-loader"], //从右到左,从下到上的加载
},
{
test: /\.s[ac]ss$/i,
use: [
// 将 JS 字符串生成为 style 节点
"style-loader",
// 将 CSS 转化成 CommonJS 模块
"css-loader",
// 将 Sass 编译成 CSS
"sass-loader",
],
},
{
test: /\.(png|gif)$/,
type: "asset",
parser: {
dataUrlCondition: {
//小于4kb 的图片转base64
maxSize: 4 * 1024, // 4kb
},
},
generator: {
//输出图片名称
filename: "static/image/[hash:10][ext][query]",
},
},
{
test: /\.m?js$/,
exclude: /(node_modules)/, //排除node_modules文件不处理
use: {
loader: "babel-loader",
options: {
presets: ["@babel/preset-env"],
},
},
},
],
},
],
},
include/exclude
在项目中,如果我们不想要某些文件不接受特定处理或者只接受某些文件处理,就可以使用exclude(排除)/include(只接受) 字段,比如当我们使用第三方库的时候,基本上文件都下载到了node_modules文件中去了。有时候这是完全没有不要的,这时候就需要用到exclude来过滤了 比如
{
test: /\.m?js$/,
exclude: /(node_modules)/, //排除node_modules文件不处理
use: {
loader: "babel-loader",
options: {
presets: ["@babel/preset-env"],
},
},
},
这里就排除node_modules下的匹配babel
//插件
plugins: [
new ESLintPlugin({
context: path.resolve(__dirname, "../src"),
exclude: "node_modules" //这里不写默认也是会排除node_modules的
}),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "../public/index.html"),
}),
],
cache
每次打包的时候,如果我们配置了eslint和Bable编译,那每次打包都会走一遍这个编译流程,速度比较慢。这时候就可以利用cache来缓存第一次的编译结果,让下一次编译的更快了,cacheDirectory 设置为true 即可开启缓存,cacheCompression 默认为true 表示压缩缓存,但是会有解压压缩过程,让打包变慢,这里缓存的体积占用没多大关系,所以把他关闭了,看需求设置。
{
test: /\.m?js$/,
exclude: /(node_modules)/, //排除node_modules文件不处理
loader: "babel-loader",
options: {
presets: ["@babel/preset-env"],
cacheDirectory:true,//开启babel缓存
cacheCompression:true,//关闭缓存文件压缩,这样可以跳过压缩和解压的过程,从而提高速度
},
},
在eslint也能开启缓存,将cache设置true,并且cacheLocation设置缓存路径
//插件
plugins: [
new ESLintPlugin({
context: path.resolve(__dirname, "../src"),
exclude: "node_modules", //这里不写默认也是会排除node_modules的
cache:true,//开启缓存
cacheLocation:path.resolve(__dirname,"../node_modules/.cache/eslint") //设置缓存路径
}),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "../public/index.html"),
}),
],
多线程打包
当项目特别大的时候,打包会变得越来越慢,这时候我们可以开启多线程同时来处理js的打包,一般是eslint,babel, terser对js处理是比较多的,所以可以开启多线程处理他们的打包速度
eslint多线程配置
new ESLintPlugin({
context: path.resolve(__dirname, "../src"),
exclude: "node_modules", //这里不写默认也是会排除node_modules的
cache: true, //开启缓存
cacheLocation: path.resolve(__dirname, "../node_modules/.cache/eslint"), //设置缓存路径
threads //开启多线程和设置进程数量
}),
babel-loader配置
{
test: /\.m?js$/,
exclude: /(node_modules)/, //排除node_modules文件不处理
use: [
{
loader: "thread-loader",
options: {
works: threads,//进程数量
},
},
{
loader: "babel-loader",
options: {
presets: ["@babel/preset-env"],
cacheDirectory: true, //开启babel缓存
cacheCompression: true, //关闭缓存文件压缩,这样可以跳过压缩和解压的过程,从而提高速度
},
},
],
},
TerserWebpackPlugin 开启多线程
//插件
plugins: [
new ESLintPlugin({
context: path.resolve(__dirname, "../src"),
exclude: "node_modules", //这里不写默认也是会排除node_modules的
cache: true, //开启缓存
cacheLocation: path.resolve(__dirname, "../node_modules/.cache/eslint"), //设置缓存路径
threads //开启多线程和设置进程数量
}),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "../public/index.html"),
}),
new TerserWebpackPlugin({
parallel:threads //开启多线程和设置进程数量压缩js
})
],
需要注意的是开启线程也是需要时间的,如果项目体积不大,使用多线程打包可能会反让打包时间变成,这里要注意以下,不能盲目开启多线程打包
减少代码体积
three Shaking
开发中我们经常引入第三方的函数库,但是可能一个函数库中我们仅仅需要部分函数,其他的不需要,如果不处理的话,就会把全部都打包,体积就会特别大,webpack默认的配置好了 只有我们引用的函数才会被打包,不需要我们做配置。
babel
balel默认会为每个文件插入辅助代码,是的代码体积过大,
babel对一些公共方法都是用了辅助代码,默认情况下也会被添加到每一个需要他的文件。 可以将这些辅助代码作为一个独立的模块,来避免重复的注入;
我们可以引入@babel/plugin-transform-runtime 插件,禁止对每个文件的注入,而是引入@babel/plugin-transform-runtime,并且使所有辅助代码从这里引用。这样就可以减少打包的体积。
{
loader: "babel-loader",
options: {
presets: ["@babel/preset-env"],
cacheDirectory: true, //开启babel缓存
cacheCompression: true, //关闭缓存文件压缩,这样可以跳过压缩和解压的过程,从而提高速度
plugins: ['@babel/plugin-transform-runtime'] //减少代码体积
},
},
图片压缩
如果项目中引入过多的图片,那么图片体积如果过大,会导致请求变慢。这时候可以使用压缩的方式来压缩图片体积,达到更快的加载速度。
这里可以引用一个插件image-minimizer-webpack-plugin
可以选用无损压缩 imagemin-gifsicle imagemin-jpegtran imagemin-optipng imagemin-svgo 配置方式按照官网就可以了
plugins: [
new ImageMinimizerPlugin({
minimizerOptions: {
// Lossless optimization with custom option
// Feel free to experiment with options for better result for you
plugins: [
["gifsicle", { interlaced: true }],
["jpegtran", { progressive: true }],
["optipng", { optimizationLevel: 5 }],
// Svgo configuration here https://github.com/svg/svgo#configuration
[
"svgo",
{
plugins: extendDefaultPlugins([
{
name: "removeViewBox",
active: false,
},
{
name: "addAttributesToSVGElement",
params: {
attributes: [{ xmlns: "http://www.w3.org/2000/svg" }],
},
},
]),
},
],
],
},
}),
],
优化运行性能
按需加载
在开发中,如果我们在一个页面有很多功能模块,webpack会在开始就默认全部加载,这回导致页面白屏时间过长,但是我们未必会使用到这些功能,比如你首页有一个按钮,点击后会触发一个函数,但是在进入首页的时候我们不需要去加载这个函数的文件,只有当我们点击他的时候才去真正的加载模块,这时候我们就需要动态加载了。
动态加载我们需要用到动态加载的语法,以下
我们在首页定义一个按钮`
<button id="button">点击我</button>
再绑定man.js中绑定这个函数
document.getElementById("button").onclick = function(){
import("./js/out").then(res=>{
let {out} = res
out();
})
}
在out.js中定义函数
export function out() {
console.log("qqqqqq");
}
那么每次点击按钮的时候,才回去引入out.js文件,而不会在进入页面的时候就直接引入
preload/prefetch
preload和prefetch是提示浏览器加载以后可能会需要的资源,也就是对资源进行预加载不执行,这样在真正需要的时候就可以直接使用,速度更快。preload只能加载的当前页面的资源,如果到下一个页面则会失效,而prefetch则可以保留到下一个页面。prefetch的加载优先度低,是在浏览器空闲的时候去加载,而preload会立即加载,preload/prefetch的兼容性不是特别好,所以使用的时候要注意。
const PreloadWebpackPlugin = require('@vue/preload-webpack-plugin');
new PreloadWebpackPlugin({
rel: 'preload',//preload或者prefetch
as: 'script'
})
使用魔法注释
document.getElementById("button").onclick = function(){
import(
/* webpackPrefetch: true */ "./js/out").then(res=>{
let {out} = res
out();
})
}