Code Split(多入口 & 单入口)

为什么需要Code Split

  • 打包代码时会将所有 js 文件打包到一个文件中,体积太大了。我们如果只要渲染首页,就应该只加载首页的 js 文件,其他文件不应该加载。

  • 所以我们需要将打包生成的文件进行代码分割,生成多个 js 文件,渲染哪个页面就只加载某个 js 文件,这样加载的资源就少,速度就更快。

Code Split是什么

代码分割(Code Split)主要做了两件事:

  1. 分割文件:将打包生成的文件进行分割,生成多个 js 文件。

  2. 按需加载:需要哪个文件就加载哪个文件。

使用方法

  • 代码分割实现方式有不同的方式,为了更加方便体现它们之间的差异,我们会分别创建新的文件来演示

1. Code Split - 多入口

  1. 文件目录
├── public
├── src
| ├── app.js
| └── main.js
├── package.json
└── webpack.config.js
  1. 初始化工程以及下载包
npm init -y

npm i webpack webpack-cli html-webpack-plugin -D
  1. 新建文件
  • 内容无关紧要,主要观察打包输出的结果

  • app.js

console.log("hello! app");
  • main.js
console.log("hello! main");
  1. 配置webpack.config.js
// webpack.config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
// 单入口
// entry: './src/main.js',
// 多入口
entry: {//两个入口文件
main: "./src/main.js",
app: "./src/app.js",
},
output: {//输出文件使用webpack命名语法以解决命名冲突问题
path: path.resolve(__dirname, "./dist"),
// [name]是webpack命名规则,使用chunk的name作为输出的文件名。
// 什么是chunk?打包的资源就是chunk,输出出去叫bundle。
// chunk的name是啥呢? 比如: entry中xxx: "./src/xxx.js", name就是xxx。注意是前面的xxx,和文件名无关。
// 为什么需要这样命名呢?如果还是之前写法main.js,那么打包生成两个js文件都会叫做main.js会发生覆盖。(实际上会直接报错的)
filename: "js/[name].js",
clear: true,
},
/*
因为无css,style样式故省略 loader 的配置
*/
plugins: [//
new HtmlWebpackPlugin({
template: "./public/index.html",//指定输出html的结构模板
}),
],
mode: "production",//生产模式和开发模式均可
};
  1. 运行指令
npx webpack
  • 此时在 dist 目录我们能看到输出了两个 js 文件。
    image

  • 浏览器也能成功运行
    image

总结:配置了几个入口,至少输出几个 js 文件。

2. Code Split - 提取重复代码

  • 如果多入口文件中都引用了同一份代码,我们不希望这份代码被打包到两个文件中,导致代码重复,体积更大。

  • 我们需要提取多入口的重复代码,只打包生成一个 js 文件,其他文件引用它就好。

  1. 修改文件
  • app.js
import { sum } from "./math";

console.log("hello! app");
console.log(sum(1, 2, 3, 4));
  • main.js
import { sum } from "./math";

console.log("hello! main");
console.log(sum(1, 2, 3, 4, 5));
  • math.js
export const sum = (...args) => {
return args.reduce((p, c) => p + c, 0);
};
  1. 修改配置文件webpack.config.js
// webpack.config.js
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
// 单入口
// entry: './src/main.js',
// 多入口
entry: {//两个入口文件
main: "./src/main.js",
app: "./src/app.js",
},
output: {//输出文件使用webpack命名语法以解决命名冲突问题
path: path.resolve(__dirname, "./dist"),
// [name]是webpack命名规则,使用chunk的name作为输出的文件名。
// 什么是chunk?打包的资源就是chunk,输出出去叫bundle。
// chunk的name是啥呢? 比如: entry中xxx: "./src/xxx.js", name就是xxx。注意是前面的xxx,和文件名无关。
// 为什么需要这样命名呢?如果还是之前写法main.js,那么打包生成两个js文件都会叫做main.js会发生覆盖。(实际上会直接报错的)
filename: "js/[name].js",
// clear: true,
},
/*
因为无css,style样式故省略 loader 的配置
*/
plugins: [//
new HtmlWebpackPlugin({
template: "./public/index.html",//指定输出html的结构模板
}),
],
mode: "production",//生产模式和开发模式均可

// 代码压缩优化区域
optimization: {
// 代码分割配置*******************************************************************************************
splitChunks: {
chunks: "all", // 对所有模块都进行分割
// 以下是默认值
// minSize: 20000, // 分割代码最小的大小
// minRemainingSize: 0, // 类似于minSize,最后确保提取的文件大小不能为0
// minChunks: 1, // 至少被引用的次数,满足条件才会代码分割
// maxAsyncRequests: 30, // 按需加载时并行加载的文件的最大数量
// maxInitialRequests: 30, // 入口js文件最大并行请求数量
// enforceSizeThreshold: 50000, // 超过50kb一定会单独打包(此时会忽略minRemainingSize、maxAsyncRequests、maxInitialRequests)
// cacheGroups: { // 组,哪些模块要打包到一个组
// defaultVendors: { // 组名
// test: /[\\/]node_modules[\\/]/, // 需要打包到一起的模块
// priority: -10, // 权重(越大越高)
// reuseExistingChunk: true, // 如果当前 chunk 包含已从主 bundle 中拆分出的模块,则它将被重用,而不是生成新的模块
// },
// default: { // 其他没有写的配置会使用上面的默认值
// minChunks: 2, // 这里的minChunks权重更大
// priority: -20,
// reuseExistingChunk: true,
// },
// },
// 修改配置
cacheGroups: {
// 组,哪些模块要打包到一个组
// defaultVendors: { // 组名
// test: /[\\/]node_modules[\\/]/, // 需要打包到一起的模块
// priority: -10, // 权重(越大越高)
// reuseExistingChunk: true, // 如果当前 chunk 包含已从主 bundle 中拆分出的模块,则它将被重用,而不是生成新的模块
// },
default: {
// 其他没有写的配置会使用上面的默认值
minSize: 0, // 我们定义的文件体积太小了,所以要改打包的最小文件体积
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
},
},
// *********************************************************************************************
},
};

实际开发中,组的配置使用用默认即可,这里因为是项目的体积太小,想要看到效果故意重新设置的!

结果展示:

  • 提取重复代码前的打包
    image
    image

  • 使用提取重复代码打包后
    image
    image

3. Code Split - 多入口按需加载动态导入

为什么需要按需加载呢?

  • 试想一下这样的场景,我们在加载一个页面时,该页面内容存在一个按钮, 但是我们一进入页面时,并不需要加载该按钮的交互(js),我们只需要加载页面的交互即可,当我们点击按钮时,再加载对应的js,这岂不是更好?
  • 这就涉及了按需加载,动态导入了,想要实现按需加载,动态导入模块。就需要额外配置
  1. 设置一个新的js文件(sayhi.js)用于动态导入
console.log('hi! 按需加载成功!!!');
  1. sayhi.jsmain.js中导入
import { sum } from "./math";

console.log("hello! main");
console.log(sum(1, 2, 3, 4, 5));

document.getElementById('btn').onclick = function(){
// 动态导入 --> 实现按需加载
// 即使只被引用了一次,也会代码分割
// 切记一定要使用import动态导入
import("./sayhi.js").then((res)=>{
console.log(res);
})
}
  1. index.html模板设置一个按钮
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Code Split</title>
</head>
<body>
<h1>hello! Code Split</h1>
<button id="btn">点击弹出</button>
</body>
</html>
  1. 运行指令执行打包
npx webpack
  • 我们可以发现,一旦通过 import 动态导入语法导入模块,模块就被代码分割,同时也能按需加载了。

结果展示:

image

  • 由此我们可以看到,只有当我们点击了按钮,才会加载396.js资源
    image

4. Code Split - 单入口

  • 开发时我们可能是单页面应用(SPA),只有一个入口(单入口)。那么我们需要这样配置(生产模式):(webpack.config.pro.js)
// 使用的是commonjs 的语法格式[node.js]
const path = require("path");//node.js中的核心模块,专门用于处理路径问题
// 获取os内置模块
const os = require("os");
// 引入eslint
const ESLintWebpackPlugin = require("eslint-webpack-plugin");
// 引入HtmlWebpackPlugin插件
const HtmlWebpackPlugin = require("html-webpack-plugin");
// 引入MiniCssExtractPlugin插件
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
// 引入CssMinimizerPlugin插件
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
// 引入Terser内置插件
const TerserPlugin = require("terser-webpack-plugin");
// 引入图片压缩插件
const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");

// 获取cpu核数
const threads = os.cpus().length;

// 设置一个函数用来获取样式处理的loader(提高代码复用率)
function getStyleLoader(pre){//pre为其他的loader,如less-loader
return [
// *************************************************************************
MiniCssExtractPlugin.loader, //将style-loder改成MiniCssExtractPlugin.loader
// ************************************************************************
"css-loader",//将css资源编译成common.js的模块到js当中

// postcss-loader处理css样式的兼容性问题(放在cssloder后面,lessloder前面)*****
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: [
"postcss-preset-env", // 能解决大多数样式兼容性问题
],
},
},
},
// ************************************************************************
pre,
].filter(Boolean)//设置一个布尔值的filter来过滤掉undefined(存css样式不需要pre[即其他的loader])
}

module.exports = {
// 入口
// 相对路径和绝对路径都行
// 单入口
entry: "./src/main.js",//相对路径
// 多入口
// entry: {
// main: "./src/main.js",
// app: "./src/app.js",
// },
// 输出
output:{
// 所有文件的输出目录,必须是绝对路径
// path.resolve()方法返回一个绝对路径
// __dirname为node.js中的变量,代表当前文件的文件夹名(就是这个文件夹的webpack_code)
path: path.resolve(__dirname , "../dist"),//相较于开发模式,生产模式需要输出
// js文件(入口文件)输出的文件名(打包后在输出路径当中生成的文件夹名)
filename:"static/js/main.js",//所以我们这里改成js资源就输出到一个js文件夹当中
clean: true,//需要输出就需要clean(自动清空上次打包内容) 原理:在打包前,将path整个目录内容清空,在进行打包
},
// 加载器
module:{
rules:[
// loder的配置
{
// oneOf配置, 每个文件只能被其中一个loader处理(第一个遇到的)************************
oneOf: [
// 1.处理css资源
{
// 用来匹配 .css 结尾的文件
test: /\.css$/,
// use 数组里面 Loader 执行顺序是从右到左
use: getStyleLoader()
},
// 2.处理less资源
{
test: /\.less$/,
// loader:xxx => 自能使用一个loader
use: getStyleLoader("less-loader")
},
// 3.处理图片资源
{
test: /\.(png|jpe?g|gif|webp)$/,//正则判断图片后缀
type: "asset",
parser:{
dataUrlCondition:{
// 将小于10kb的图片转化为base64
// 优点:减小请求数量 缺点:原图片的体积会变大(故大体积突变不会使用这种方法)
maxSize: 10*1024//10kb(大体积图片不会使用这种方法)
}
},
generator: {
// 将图片文件输出到 static/imgs 目录中
// 将图片文件命名 [hash:8][ext][query]
// [hash:8]: hash值取8位
// [ext]: 使用之前的文件扩展名
// [query]: 添加之前的query参数
filename: 'static/imgs/[hash:8][ext][query]'
}
},
// 4.处理字体资源
{
test: /\.(ttf|woff2?)$/,
type: "asset/resource",//这里的是改成"asset/resource"
generator: {
filename: "static/media/[hash:8][ext][query]",
},
},
// 配置babel***********************************************
{
test: /\.js$/,//检查匹配以js结尾的文件
// Include/Exclude*******************************************
// exclude: /node_modules/, // 排除node_modules代码不编译
include: path.resolve(__dirname, "../src"), // 也可以用包含(两者只能用其一)
// **********************************************************

use: [
{//开启多线程编译打包***************************
loader: "thread-loader", // 开启多进程
options: {
workers: threads, // 数量
},
// *****************************************
},
{
loader: "babel-loader",
options: {
cacheDirectory: true, // 开启babel编译缓存
},
},
],
// ********************************************************
},
]
// ****************************************************************************
}
],
},
// 插件
plugins:[
// plugin的配置

// eslint配置
new ESLintWebpackPlugin({
// 指定检查文件的根目录(src目录下的所有文件的语法)
context: path.resolve(__dirname, "../src"),
// Include/Exclude******************
exclude: "node_modules", // 默认值
// ********************************
cache: true, // 开启缓存
// 缓存目录
cacheLocation: path.resolve(
__dirname,
"../node_modules/.cache/.eslintcache"
),
// 开启多线程打包编译***************
threads, // 开启多进程
// *******************************
}),

// HtmlWebpackPlugin配置*******************************************************
new HtmlWebpackPlugin({
// 以 public/index.html 为模板创建文件
// 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源
template: path.resolve(__dirname, "../public/index.html"),
}),
// ****************************************************************************

// 提取css成单独文件
new MiniCssExtractPlugin({
// 定义输出文件名和输出路径
filename: "static/css/main.css",
}),
// css压缩************************************************************************
// new CssMinimizerPlugin(),
// ******************************************************************************
],

// 开启多线程需要重新手写 Terser 内置模块*********************************************
// webpack5一般指定压缩地方为这里,上面也可以
optimization: {
minimize: true,
minimizer: [
// css压缩
// css压缩也可以写到optimization.minimizer里面,效果一样的
new CssMinimizerPlugin(),//压缩css

//js压缩
// 当生产模式会默认开启TerserPlugin,但是我们需要进行其他配置,就要重新写了
new TerserPlugin({//内置模块压缩js
parallel: threads // 开启多进程
}),

//图片压缩*******************************************
new ImageMinimizerPlugin({
minimizer: {
implementation: ImageMinimizerPlugin.imageminGenerate,
options: {
plugins: [
["gifsicle", { interlaced: true }],
["jpegtran", { progressive: true }],
["optipng", { optimizationLevel: 5 }],
[
"svgo",
{
plugins: [
"preset-default",
"prefixIds",
{
name: "sortAttrs",
params: {
xmlnsOrder: "alphabetical",
},
},
],
},
],
],
},
},
}),
// *************************************************
],

// 代码分割配置
splitChunks: {
// 默认分割即可(spa单页面应用)
chunks: "all", // 对所有模块都进行分割
// 以下是默认值
// minSize: 20000, // 分割代码最小的大小
// minRemainingSize: 0, // 类似于minSize,最后确保提取的文件大小不能为0
// minChunks: 1, // 至少被引用的次数,满足条件才会代码分割
// maxAsyncRequests: 30, // 按需加载时并行加载的文件的最大数量
// maxInitialRequests: 30, // 入口js文件最大并行请求数量
// enforceSizeThreshold: 50000, // 超过50kb一定会单独打包(此时会忽略minRemainingSize、maxAsyncRequests、maxInitialRequests)
// cacheGroups: { // 组,哪些模块要打包到一个组
// defaultVendors: { // 组名
// test: /[\\/]node_modules[\\/]/, // 需要打包到一起的模块
// priority: -10, // 权重(越大越高)
// reuseExistingChunk: true, // 如果当前 chunk 包含已从主 bundle 中拆分出的模块,则它将被重用,而不是生成新的模块
// },
// default: { // 其他没有写的配置会使用上面的默认值
// minChunks: 2, // 这里的minChunks权重更大
// priority: -20,
// reuseExistingChunk: true,
// },
// },
},
},
// **********************************************************************************
/*
生产模式不需要开发服务器(devServer)
*/
// 模式
mode:"production",//生产模式
devtool: "source-map",//sourceMap生产模式
};

这里存在一个小问题,就是eslint是不识别import动态导入语法的,需要我们为其添加plugins(插件)来解决语法问题

image

因此我们需要在项目的Eslint配置文件当中设置以下代码:(.eslintrc.js的完整代码)

module.exports = {
// 继承 Eslint的默认规则
extends: ["eslint:recommended"],
env: {
node: true, // 启用node中全局变量
browser: true, // 启用浏览器中全局变量(这样就可以使用console.log等输出)
},
parserOptions: {
ecmaVersion: 6, //es6语法
sourceType: "module", //es模块化
},
rules: {
"no-var": 2, // 不能使用 var 定义变量(0:关闭规则,1:开启规则(警告),2:开启(错误))
},

// 使其支持 import 的动态导入语法**********************
plugins: ["import"],//使其支持 import 动态导入语法
// **************************************************
};

结果展示:

image

5.Code Split - 给模块命名

  • 以往我们没有给模块命名时 , 打包生成的模块多是以数字组合 , 没有逻辑 , 那么如何给 模块 命名呢?
    image

给动态导入的文件命名

  1. 修改配置项 main.js
// 引入js文件
import count from './js/count'
import sum from './js/sum'
// 引入css资源
import './css/index.css'
// 引入less资源
import './less/index.less'
// 引入字体样式(切记是字体样式并非字体)
import './css/iconfont.css'

console.log(count(3 , 1));
console.log(sum(1,2,3,4,5,6));

// 按钮点击事件实现 按需加载 ,动态导入
document.getElementById('btn').onclick = function(){
// 动态导入 --> 实现按需加载
// 即使只被引用了一次,也会代码分割
// 切记一定要使用import动态导入

// eslint会对动态导入语法报错,需要修改eslint配置文件 , 向上翻阅本博客查找
// webpackChunkName: "sayhi":这是webpack动态导入模块命名的方式
// "sayhi"将来就会作为[name]的值显示。
import( /*webpackChunkName: "sayhi"*/ "./js/sayhi.js").then((res)=>{
console.log(res);
})
}

// 判断是否支持HMR功能
if (module.hot) {
module.hot.accept("./js/count.js");//开启对应路径中js的热模替换
module.hot.accept("./js/sum.js");//开启对应路径中js的热模替换
}
  1. 在输出的webpack.config.pro.js中设置chunkFilename属性
// 使用的是commonjs 的语法格式[node.js]
const path = require("path");//node.js中的核心模块,专门用于处理路径问题
// 获取os内置模块
const os = require("os");
// 引入eslint
const ESLintWebpackPlugin = require("eslint-webpack-plugin");
// 引入HtmlWebpackPlugin插件
const HtmlWebpackPlugin = require("html-webpack-plugin");
// 引入MiniCssExtractPlugin插件
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
// 引入CssMinimizerPlugin插件
const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
// 引入Terser内置插件
const TerserPlugin = require("terser-webpack-plugin");
// 引入图片压缩插件
const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");

// 获取cpu核数
const threads = os.cpus().length;

// 设置一个函数用来获取样式处理的loader(提高代码复用率)
function getStyleLoader(pre){//pre为其他的loader,如less-loader
return [
// *************************************************************************
MiniCssExtractPlugin.loader, //将style-loder改成MiniCssExtractPlugin.loader
// ************************************************************************
"css-loader",//将css资源编译成common.js的模块到js当中

// postcss-loader处理css样式的兼容性问题(放在cssloder后面,lessloder前面)*****
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: [
"postcss-preset-env", // 能解决大多数样式兼容性问题
],
},
},
},
// ************************************************************************
pre,
].filter(Boolean)//设置一个布尔值的filter来过滤掉undefined(存css样式不需要pre[即其他的loader])
}

module.exports = {
// 入口
// 相对路径和绝对路径都行
// 单入口
entry: "./src/main.js",//相对路径
// 多入口
// entry: {
// main: "./src/main.js",
// app: "./src/app.js",
// },
// 输出
output:{
// 所有文件的输出目录,必须是绝对路径
// path.resolve()方法返回一个绝对路径
// __dirname为node.js中的变量,代表当前文件的文件夹名(就是这个文件夹的webpack_code)
path: path.resolve(__dirname , "../dist"),//相较于开发模式,生产模式需要输出
// js文件(入口文件)输出的文件名(打包后在输出路径当中生成的文件夹名)
filename:"static/js/main.js",//所以我们这里改成js资源就输出到一个js文件夹当中

// 设置文件的输出名字(动态按需引入的js文件等)*******************************
chunkFilename: 'static/js/[name].js',//设置打包输出的其他文件命名
// ***********************************************************************

clean: true,//需要输出就需要clean(自动清空上次打包内容) 原理:在打包前,将path整个目录内容清空,在进行打包
},
// 加载器
module:{
rules:[
// loder的配置
{
// oneOf配置, 每个文件只能被其中一个loader处理(第一个遇到的)************************
oneOf: [
// 1.处理css资源
{
// 用来匹配 .css 结尾的文件
test: /\.css$/,
// use 数组里面 Loader 执行顺序是从右到左
use: getStyleLoader()
},
// 2.处理less资源
{
test: /\.less$/,
// loader:xxx => 自能使用一个loader
use: getStyleLoader("less-loader")
},
// 3.处理图片资源
{
test: /\.(png|jpe?g|gif|webp)$/,//正则判断图片后缀
type: "asset",
parser:{
dataUrlCondition:{
// 将小于10kb的图片转化为base64
// 优点:减小请求数量 缺点:原图片的体积会变大(故大体积突变不会使用这种方法)
maxSize: 10*1024//10kb(大体积图片不会使用这种方法)
}
},
generator: {
// 将图片文件输出到 static/imgs 目录中
// 将图片文件命名 [hash:8][ext][query]
// [hash:8]: hash值取8位
// [ext]: 使用之前的文件扩展名
// [query]: 添加之前的query参数
filename: 'static/imgs/[hash:8][ext][query]'
}
},
// 4.处理字体资源
{
test: /\.(ttf|woff2?)$/,
type: "asset/resource",//这里的是改成"asset/resource"
generator: {
filename: "static/media/[hash:8][ext][query]",
},
},
// 配置babel***********************************************
{
test: /\.js$/,//检查匹配以js结尾的文件
// Include/Exclude*******************************************
// exclude: /node_modules/, // 排除node_modules代码不编译
include: path.resolve(__dirname, "../src"), // 也可以用包含(两者只能用其一)
// **********************************************************

use: [
{//开启多线程编译打包***************************
loader: "thread-loader", // 开启多进程
options: {
workers: threads, // 数量
},
// *****************************************
},
{
loader: "babel-loader",
options: {
cacheDirectory: true, // 开启babel编译缓存
},
},
],
// ********************************************************
},
]
// ****************************************************************************
}
],
},
// 插件
plugins:[
// plugin的配置

// eslint配置
new ESLintWebpackPlugin({
// 指定检查文件的根目录(src目录下的所有文件的语法)
context: path.resolve(__dirname, "../src"),
// Include/Exclude******************
exclude: "node_modules", // 默认值
// ********************************
cache: true, // 开启缓存
// 缓存目录
cacheLocation: path.resolve(
__dirname,
"../node_modules/.cache/.eslintcache"
),
// 开启多线程打包编译***************
threads, // 开启多进程
// *******************************
}),

// HtmlWebpackPlugin配置*******************************************************
new HtmlWebpackPlugin({
// 以 public/index.html 为模板创建文件
// 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源
template: path.resolve(__dirname, "../public/index.html"),
}),
// ****************************************************************************

// 提取css成单独文件
new MiniCssExtractPlugin({
// 定义输出文件名和输出路径
filename: "static/css/main.css",
}),
// css压缩************************************************************************
// new CssMinimizerPlugin(),
// ******************************************************************************
],

// 开启多线程需要重新手写 Terser 内置模块*********************************************
// webpack5一般指定压缩地方为这里,上面也可以
optimization: {
minimize: true,
minimizer: [
// css压缩
// css压缩也可以写到optimization.minimizer里面,效果一样的
new CssMinimizerPlugin(),//压缩css

//js压缩
// 当生产模式会默认开启TerserPlugin,但是我们需要进行其他配置,就要重新写了
new TerserPlugin({//内置模块压缩js
parallel: threads // 开启多进程
}),

//图片压缩*******************************************
new ImageMinimizerPlugin({
minimizer: {
implementation: ImageMinimizerPlugin.imageminGenerate,
options: {
plugins: [
["gifsicle", { interlaced: true }],
["jpegtran", { progressive: true }],
["optipng", { optimizationLevel: 5 }],
[
"svgo",
{
plugins: [
"preset-default",
"prefixIds",
{
name: "sortAttrs",
params: {
xmlnsOrder: "alphabetical",
},
},
],
},
],
],
},
},
}),
// *************************************************
],

// 代码分割配置
splitChunks: {
// 默认分割即可(spa单页面应用)
chunks: "all", // 对所有模块都进行分割
// 以下是默认值
// minSize: 20000, // 分割代码最小的大小
// minRemainingSize: 0, // 类似于minSize,最后确保提取的文件大小不能为0
// minChunks: 1, // 至少被引用的次数,满足条件才会代码分割
// maxAsyncRequests: 30, // 按需加载时并行加载的文件的最大数量
// maxInitialRequests: 30, // 入口js文件最大并行请求数量
// enforceSizeThreshold: 50000, // 超过50kb一定会单独打包(此时会忽略minRemainingSize、maxAsyncRequests、maxInitialRequests)
// cacheGroups: { // 组,哪些模块要打包到一个组
// defaultVendors: { // 组名
// test: /[\\/]node_modules[\\/]/, // 需要打包到一起的模块
// priority: -10, // 权重(越大越高)
// reuseExistingChunk: true, // 如果当前 chunk 包含已从主 bundle 中拆分出的模块,则它将被重用,而不是生成新的模块
// },
// default: { // 其他没有写的配置会使用上面的默认值
// minChunks: 2, // 这里的minChunks权重更大
// priority: -20,
// reuseExistingChunk: true,
// },
// },
},
},
// **********************************************************************************
/*
生产模式不需要开发服务器(devServer)
*/
// 模式
mode:"production",//生产模式
devtool: "source-map",//sourceMap生产模式
};

结果展示:

image

6. Code Split - 统一命名配置

  • 资源加载的命名多是用的hash,因此我们可以统一的卸载output区域内,使用assetModuleFilename属性来统一命名图片,字体等资源

  • 入口文件main.js和其他动态引入的打包文件命名区分,我们可以在命名路径后面添加.chunk来加以区分,如chunkFilename: "static/js/[name].chunk.js" 对比 filename: "static/js/[name].js",

webpack.config.prod.js源码展示(片段,除去引入的资源和plugins等)

module.exports = {
entry: "./src/main.js",
output: {
path: path.resolve(__dirname, "../dist"), // 生产模式需要输出
//命名**********************************************************************************
filename: "static/js/[name].js", // 入口文件打包输出资源命名方式
chunkFilename: "static/js/[name].chunk.js", // 动态导入输出资源命名方式
assetModuleFilename: "static/media/[name].[hash][ext]", // 图片、字体等资源命名方式(注意用hash)
//*************************************************************************************
clean: true,
},
module: {
rules: [
{
oneOf: [
{
// 用来匹配 .css 结尾的文件
test: /\.css$/,
// use 数组里面 Loader 执行顺序是从右到左
use: getStyleLoaders(),
},
{
test: /\.less$/,
use: getStyleLoaders("less-loader"),
},
{
test: /\.(png|jpe?g|gif|svg)$/,
type: "asset",
parser: {
dataUrlCondition: {
maxSize: 10 * 1024, // 小于10kb的图片会被base64处理
},
},

//省略命名导出,在output中统一导出
// generator: {
// // 将图片文件输出到 static/imgs 目录中
// // 将图片文件命名 [hash:8][ext][query]
// // [hash:8]: hash值取8位
// // [ext]: 使用之前的文件扩展名
// // [query]: 添加之前的query参数
// filename: "static/imgs/[hash:8][ext][query]",
// },
},
{
test: /\.(ttf|woff2?)$/,
type: "asset/resource",

//省略命名导出,在output中统一导出
// generator: {
// filename: "static/media/[hash:8][ext][query]",
// },
},
],
},
],
},
],
},