背景
打包代码时会将所有js文件打包到一个文件中,体积太大了。我们如果只要渲染首页,就应该只加载首页的js文件,其他文件不应该加载
所以我们需要将打包生成的文件进行代码分割,生成多个js文件,渲染哪个页面就只加载某个js文件,
这样加载的资源就少,速度就更快。
是什么
code split做了两件事: 1、分割文件:将打包生成的文件进行分割,生成多个js文件 2、按需加载:需要哪个文件就加载哪个文件
怎么用
代码分割实现方式有不同的方式,为了更加方便体现它们之间的差异,分别创建新的文件来演示
一、多入口
1、文件目录
2、下载包
npm install webpack webpack-cli html-webpack-plugin --save-dev
3、新建文件
内容不用关注,主要看打包后的输出结果
main.js
console.log("hello main")
app.js
console.log("hello app")
4、配置
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: {
path: path.resolve(__dirname, 'dist'),
//[name]webpack命名方式,使用chunk的name那么作为输出的文件名
//什么是chunk,打包的资源是chunk,输出的资源是bundle
//比如说entry中xxx: './sc/yyy.js', yyy就是chunk的文件名
//为什么这么命名?如果写死名称main.js,那么打包生成两个js文件都叫main.js,会发生覆盖(实际会报conflict错误)
filename: '[name].js'
},
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, './public/index.html')
})
],
mode: 'production',
}
5、运行指令
npx webpack
此时在dist目录中能看到输出了两个js文件
总结:配置几个入口,至少输出几个js文件
二、提取重复代码
如果多个文件都引用同一份代码,我们不希望这份代码被打包到两个文件中,导致代码重复,体积更大。
我们需要提取多入口的重复代码,只打包生成一个js文件,其他文件引用它就好
1、修改文件
main.js
import { sum } from './math.js'
console.log("hello main")
console.log(sum(3, 4, 5))
app.js
import { sum } from './math.js'
console.log("hello app")
console.log(sum(1, 2, 3))
添加文件math.js
export function sum(...args) {
return args.reduce((a, b) => {
return a + b;
}, 0)
}
2、修改配置文件
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: {
path: path.resolve(__dirname, 'dist'),
//[name]webpack命名方式,使用chunk的name那么作为输出的文件名
//什么是chunk,打包的资源是chunk,输出的资源是bundle
//比如说entry中xxx: './sc/yyy.js', yyy就是chunk的文件名
//为什么这么命名?如果写死名称main.js,那么打包生成两个js文件都叫main.js,会发生覆盖(实际会报conflict错误)
filename: '[name].js'
},
plugins: [
new HtmlWebpackPlugin({
template: path.resolve(__dirname, './public/index.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,
},
},
},
},
}
3、运行命令
npx webpack
此时会发现生成3个js文件,有一个是提取的公共模块
三、按需加载,动态导入
想要实现按需加载,动态导入模块。还需要额外配置:
1、修改文件
增加count.js
export default function count(x, y) {
return x - y
}
main.js
import count from './count.js'
import { sum } from './math.js'
// import count from './count.js'
console.log("hello main")
console.log(sum(3, 4, 5))
let btn = document.getElementById("btn")
btn.onclick = () => {
// import 动态导入:会将动态导入的文件代码分割(拆分成单独模块),在需要使用的时候自动加载
import('./count.js')
.then((res) => {
console.log('模块加载成功', res.default(7, 4))
})
.catch((err) => {
console.log('模块加载失败', err)
})
}
public/index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Code Split</title>
</head>
<body>
<h1>hello webpack</h1>
<button id="btn">按钮</button>
</body>
</html>
2、运行命令
npx webpack
我们可以发现,一旦通过 import 动态导入语法导入模块,模块就被代码分割,同时也能按需加载了(点击按钮之后才会加载655.js也就打包之前的count.js文件)。
四、单入口
开发时一般是单页面应用(SPA),只有一个入口(单入口)。那么需要这样配置:
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: {
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",
clean: true,
},
plugins: [
new HtmlWebpackPlugin({
template: "./public/index.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,
// },
// },
},
};
五、更新配置
最终我们会使用单入口+代码分割+动态导入方式来进行配置。更新之前的配置文件。
webpack.prod.js
const path = require('path');
const os = require('os');
const ESLintPlugin = require('eslint-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
console.log('hahahahha', __dirname)
const threads = os.cpus().length; //cpu核数
function getStyleLoader(pre) {
return [
MiniCssExtractPlugin.loader,
'css-loader',
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
[
"postcss-preset-env",
],
],
},
},
},
pre
].filter(Boolean);
}
module.exports = {
//入口
entry: './src/main.js',
//输出
output: {
//所有文件的输出目录
//__dirname代表当前文件文件夹目录
path: path.resolve(__dirname, '../dist'),
//入口文件打包输出文件名
filename: 'static/js/main33.js',
//在打包文件前,清除之前的打包文件
clean: true,
//输出图片名称
// assetModuleFilename: 'images/[hash][ext][query]'
},
//加载器
module: {
//loader的配置
rules: [
{
oneOf: [
{
test: /\.css$/i,
use: getStyleLoader()
},
{
test: /\.less$/i,
use: getStyleLoader('less-loader')
},
{
test: /\.s[ac]ss$/i,
use: getStyleLoader('sass-loader')
},
{
test: /\.styl$/i,
use: getStyleLoader('stylus-loader')
},
{
test: /\.(png|jpe?g|gif|webp|svg)/,
type: "asset",
parser: {
dataUrlCondition: {
// 小于10kb的图片转base64
// 优点:减少请求数量 缺点:体积会更大
maxSize: 10 * 1024
}
},
generator: {
filename: 'static/images/[name].[hash:10][ext][query]'
}
},
{
test: /\.(ttf|woff2?|mp3|mp4|avi)/,
type: "asset/resource",
generator: {
filename: 'static/media/[name].[hash:10][ext][query]'
}
},
{
test: /\.js$/,
// exclude: /(node_modules|bower_components)/, //排除node_modules中的js文件,其他文件都处理
include: path.resolve(__dirname, "../src"), //只处理src中的js文件
// loader: 'babel-loader',
// options: {
// cacheDirectory: true, //开启bable缓存
// cacheCompression: false, //关闭缓存文件压缩
// }
use: [
{
loader: 'thread-loader', // 开启多进程
options: {
works: threads // 进程数量
}
},
{
loader: 'babel-loader',
options: {
cacheDirectory: true, //开启bable缓存
cacheCompression: false, //关闭缓存文件压缩
plugins: ["@babel/plugin-transform-runtime"], // 减少代码体积
},
}
],
},
]
}
]
},
//插件
plugins: [
new ESLintPlugin({
// 检测哪些文件
context: path.resolve(__dirname, "../src"),
exclude: "node_modules",
// cache: true, //默认为true
// cacheLocation: path.resolve(__dirname, "../node_modules/.cache/eslintcache")
threads // 开启多进程和设置进程数量
}),
new HtmlWebpackPlugin({
//模板: 以public/index.html文件创建新的html文件
// 新的文件特点:1、结构和原来一致 2、自动引入打包的输出的资源
template: path.resolve(__dirname, "../public/index.html")
}),
new MiniCssExtractPlugin({
filename: 'static/css/main.css'
}),
// new CssMinimizerPlugin(),
// new TerserPlugin({
// parallel: threads // 开启多进程和设置进程数量
// })
],
optimization: {
//压缩的操作
minimizer: [
// 压缩css
new CssMinimizerPlugin(),
// 压缩js
new TerserPlugin({
parallel: threads // 开启多进程和设置进程数量
})
],
//代码分割
splitChunks: {
chunks: 'all',
// 其他的都用默认值即可
// 如果用到node_modules中的文件,就会将node_modules文件单独打包成一个文件
//
}
},
//模式
mode: "production",
devtool: "source-map"
}
六、给动态导入文件取名
1、修改文件
main.js
import count from './js/count.js'
import sum from './js/sum.js'
// import { add } from './js/math.js'
//要想webpack打包资源,必须引入资源
import './css/index.css'
import './less/index.less'
import './sass/index.scss'
import './sass/index.sass'
import './stylus/index.styl'
import './css/iconfont.css'
console.log(count(2, 2))
console.log(sum(1, 2, 3, 4))
// console.log(add(3, 4))
document.getElementById('btn').onclick = () => {
//eslint不是识别动态导入语法,需要额外增加配置
//webpackChunkName: 'xxx'是webpack特殊命名规则,也叫魔法命名
import(/* webpackChunkName: 'math'*/'./js/math.js')
.then(res => {
console.log("模块加载成功", res.mulit(1, 2))
})
}
if (module.hot) {
//判断是否支持热模块替换功能
module.hot.accept('./js/count', () => {
console.log('count文件被更新了')
})
module.hot.accept('./js/sum', () => {
console.log('sum文件被更新了')
})
}
2、修改eslint配置
下载插件依赖包
npm install eslint-plugin-import --save-dev
修改配置文件.eslintrc.js
module.exports = {
extends: [
'eslint:recommended',
],
env: {
node: true, //启用node中全局变量
browser: true //启用浏览器中全局变量
},
parserOptions: {
ecmaVersion: 11, //新版本eslint-plugin-import插件需要使用更高版本ecmaVersion配置:2020 || 11,不然还是会报错
sourceType: 'module'
},
rules: {
"no-var": 2 //不能使用var定义变量
},
plugins: ['import'] //解决动态导入语法报错
}
七、统一命名配置
js代码多入口、动态导入; 图片、字体统一命名
//入口文件打包输出文件名
filename: 'static/js/[name].js',
//给打包输出的其他文件命名
chunkFilename: 'static/js/[name].chunk.js',
//图片、字体等通过type:asset处理的资源
assetModuleFilename: 'static/media/[name].[hash:10][ext][query]',
css多入口,动态导入
new MiniCssExtractPlugin({
filename: 'static/css/[name].css',
chunkFilename: 'static/css/[name].chunk.css'
}),
完整配置:
const path = require('path');
const os = require('os');
const ESLintPlugin = require('eslint-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerPlugin = require('css-minimizer-webpack-plugin');
const TerserPlugin = require('terser-webpack-plugin');
console.log('hahahahha', __dirname)
const threads = os.cpus().length; //cpu核数
function getStyleLoader(pre) {
return [
MiniCssExtractPlugin.loader,
'css-loader',
{
loader: 'postcss-loader',
options: {
postcssOptions: {
plugins: [
[
"postcss-preset-env",
],
],
},
},
},
pre
].filter(Boolean);
}
module.exports = {
//入口
entry: './src/main.js',
//输出
output: {
//所有文件的输出目录
//__dirname代表当前文件文件夹目录
path: path.resolve(__dirname, '../dist'),
//入口文件打包输出文件名
filename: 'static/js/[name].js',
//给打包输出的其他文件命名
chunkFilename: 'static/js/[name].chunk.js',
//图片、字体等通过type:asset处理的资源
assetModuleFilename: 'static/media/[name].[hash:10][ext][query]',
//在打包文件前,清除之前的打包文件
clean: true,
//输出图片名称
// assetModuleFilename: 'images/[hash][ext][query]'
},
//加载器
module: {
//loader的配置
rules: [
{
oneOf: [
{
test: /\.css$/i,
use: getStyleLoader()
},
{
test: /\.less$/i,
use: getStyleLoader('less-loader')
},
{
test: /\.s[ac]ss$/i,
use: getStyleLoader('sass-loader')
},
{
test: /\.styl$/i,
use: getStyleLoader('stylus-loader')
},
{
test: /\.(png|jpe?g|gif|webp|svg)/,
type: "asset",
parser: {
dataUrlCondition: {
// 小于10kb的图片转base64
// 优点:减少请求数量 缺点:体积会更大
maxSize: 10 * 1024
}
},
// generator: {
// //输出的图片名称
// // [hash:10]hash值取前10位
// filename: 'static/images/[name].[hash:10][ext][query]'
// }
},
{
test: /\.(ttf|woff2?|mp3|mp4|avi)/,
type: "asset/resource",
// generator: {
// filename: 'static/media/[name].[hash:10][ext][query]'
// }
},
{
test: /\.js$/,
// exclude: /(node_modules|bower_components)/, //排除node_modules中的js文件,其他文件都处理
include: path.resolve(__dirname, "../src"), //只处理src中的js文件
// loader: 'babel-loader',
// options: {
// cacheDirectory: true, //开启bable缓存
// cacheCompression: false, //关闭缓存文件压缩
// }
use: [
{
loader: 'thread-loader', // 开启多进程
options: {
works: threads // 进程数量
}
},
{
loader: 'babel-loader',
options: {
cacheDirectory: true, //开启bable缓存
cacheCompression: false, //关闭缓存文件压缩
plugins: ["@babel/plugin-transform-runtime"], // 减少代码体积
},
}
],
},
]
}
]
},
//插件
plugins: [
new ESLintPlugin({
// 检测哪些文件
context: path.resolve(__dirname, "../src"),
exclude: "node_modules",
// cache: true, //默认为true
// cacheLocation: path.resolve(__dirname, "../node_modules/.cache/eslintcache")
threads // 开启多进程和设置进程数量
}),
new HtmlWebpackPlugin({
//模板: 以public/index.html文件创建新的html文件
// 新的文件特点:1、结构和原来一致 2、自动引入打包的输出的资源
template: path.resolve(__dirname, "../public/index.html")
}),
new MiniCssExtractPlugin({
filename: 'static/css/[name].css',
chunkFilename: 'static/css/[name].chunk.css'
}),
// new CssMinimizerPlugin(),
// new TerserPlugin({
// parallel: threads // 开启多进程和设置进程数量
// })
],
optimization: {
//压缩的操作
minimizer: [
// 压缩css
new CssMinimizerPlugin(),
// 压缩js
new TerserPlugin({
parallel: threads // 开启多进程和设置进程数量
})
],
//代码分割
splitChunks: {
chunks: 'all',
// 其他的都用默认值即可
// 如果用到node_modules中的文件,就会将node_modules文件单独打包成一个文件
//
}
},
//模式
mode: "production",
devtool: "source-map"
}
执行命令打包效果: