打包代码时会将所有 js 文件打包到一个文件中,体积太大了。我们如果只要渲染首页,就应该只加载首页的 js 文件,其他文件不应该加载。
所以我们需要将打包生成的文件进行代码分割,生成多个 js 文件,渲染哪个页面就只加载某个 js 文件,这样加载的资源就少,速度就更快。
代码分割(Code Split)主要做了两件事:
1. 分割文件:将打包生成的文件进行分割,生成多个 js 文件。
2. 按需加载:需要哪个文件就加载哪个文件。
新建文件
├── public
├── src
| ├── app.js
| └── main.js
├── package.json
└── 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: {
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: "[name].js",
// clear: true,
},
plugins: [
new HtmlWebpackPlugin({
template:path.resolve(__dirname,"public/index.html"),
}),
],
mode: "production",
};
下载依賴
npm init -y 生成 package.json
npm i webpack webpack-cli html-webpack-plugin -D 生成node包 html-webpack-plugin 插件
打包 npx webpack
多入口使用同一段代码 优化 // 代码分割配置 splitChunks
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: "[name].js",
// clear: true,
},
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,
},
},
},
},
};
按需加载动态导入
如果有一些初始加载用不上的资源 就不需要一上来就加载
1. 修改文件
- main.js
```js
import { sum } from './math.js';
// import count from "./count.js";
console.log("hello main");
console.log(sum(1,2,3));
// document.getElementById('btn').onclick=function(){
// console.log(count(2,1));
// };
document.getElementById('btn').onclick=function(){
// 动态导入 --> 实现按需加载
// 即使只被引用了一次,也会代码分割
import('./count').then(
(res)=>{
console.log('模块动态加载成功',res);
})
.catch((err)=>{
console.log('模块加载失败',err);
})
};
```
- app.js
```js
console.log("hello app");
```
- public/index.html
```html
Code Split
hello webpack
```
```count.js
export default function count(x,y){
return x-y;
}
单入口
开发时我们可能是单页面应用(SPA),只有一个入口(单入口)。那么我们需要这样配置:
js/math.js
export function mul(...args){
return args.reduce((p,c)=>p+c,0);
}
import count from "./js/count.js";
import sum from "./js/sum.js";
//要想webpack打包资源必须引入该资源
import "./css/iconfont.css";
import "./css/index.css";
import "./less/index.less";
import "./sass/index.sass";
import "./sass/test1.scss";
import "./styl/index.styl";
const result=count(2,1);
console.log(result);
console.log(sum(1,2,3,4));
document.getElementById("btn").onclick=function(){
// eslint会对动态导入语法报错,需要修改eslint配置文件
// webpackChunkName: "math":这是webpack动态导入模块命名的方式
import(/*webpackChunkName: "math"*/"./js/math").then(({mul})=>{
console.log(mul(3,3));
});
}
if(module.hot){
//判断是否支持热模块 替换功能
module.hot.accept("./js/count.js");
module.hot.accept("./js/sum.js");
}
eslint会对动态导入语法报错,需要修改eslint配置文件
module.exports = {
// 继承 Eslint 规则
extends: ["eslint:recommended"],
env: {
node: true, // 启用node中全局变量
browser: true, // 启用浏览器中全局变量
},
parserOptions: {
ecmaVersion: 6, //es6
sourceType: "module", //es module
},
rules: {
"no-var": 2, // 不能使用 var 定义变量
},
plugins:["import"],//解決动态导入语法报错
};
打包生成带数字文件名 修改文件名 精准定位 文件 原单入口 webpack.prod.js 配置
module.exports = {
...
output: {
path: path.resolve(__dirname, "../dist"), // 生产模式需要输出
filename: "static/js/main.js", // 将 js 文件输出到 static/js 目录中
chunkFilename:'static/js/[name].js', //打包输出的其他文件命名
clean: true,
},
....
}
CodeSplit 统一命名 图片 JS 样式 命名
const os = require("os");
const path = require("path");
const ESLintWebpackPlugin = 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");
const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
// cpu核数
const threads = os.cpus().length;
// 获取处理样式的Loaders
const getStyleLoaders = (preProcessor) => {
return [
MiniCssExtractPlugin.loader,
"css-loader",
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: [
"postcss-preset-env", // 能解决大多数样式兼容性问题
],
},
},
},
preProcessor,
].filter(Boolean);
};
module.exports = {
entry: "./src/main.js", //相对路径
output: {
//所有文件输出路径
//__dirname node.js 的变量,代表当前文件夹的目录
path: path.resolve(__dirname, "../dist"), // 绝对路径
// 入口文件打包输出文件名
filename: "static/js/[name].js",
//打包输出的其他文件命名
chunkFilename:'static/js/[name].chunk.js',
//图片 字体等通过type:asset处理资源命名
assetModuleFilename:"static/media/[hash:8][ext][query]",
//自动清空上次打包内容
//原理:在打包前,将path整个目录清空,再进行打包
clean: true,
},
module: {
rules: [
{
oneOf: [
{
// 用来匹配 .css 结尾的文件
test: /\.css$/,
// use 数组里面 Loader 执行顺序是从右到左
use: getStyleLoaders(),
},
{
test: /\.less$/,
use: getStyleLoaders("less-loader"),
},
{
test: /\.s[ac]ss$/,
use: getStyleLoaders("sass-loader"),
},
{
test: /\.styl$/,
use: getStyleLoaders("stylus-loader"),
},
{
test: /\.(png|jpe?g|gif|svg)$/,
type: "asset",
parser: {
dataUrlCondition: {
maxSize: 10 * 1024, // 小于10kb的图片会被base64处理
},
},
// generator: {
// // 将图片文件输出到 static/imgs 目录中
// // 将图片文件命名 [hash:8][ext][query]
// // [hash:8]: hash值取8位
// // [ext]: 使用之前的文件扩展名
// // [query]: 添加之前的query参数
// filename: "static/imgs/[hash:8][ext][query]",
// },
},
{
test: /\.(ttf|woff2?|mp3|mp4|avi)$/,
type: "asset/resource",
//复用
// generator: {
// filename: "static/media/[hash:8][ext][query]",
// },
},
{
test: /\.js$/,
// exclude: /node_modules/, // 排除node_modules代码不编译
include: path.resolve(__dirname, "../src"), // 也可以用包含
use: [
{
loader: "thread-loader", // 开启多进程
options: {
workers: threads, // 数量
},
},
{
loader: "babel-loader",
options: {
cacheDirectory: true, // 开启babel编译缓存
cacheCompression: false, // 缓存文件不要压缩
plugins: ["@babel/plugin-transform-runtime"], // 减少代码体积
},
},
],
},
],
},
],
},
plugins: [
new ESLintWebpackPlugin({
// 指定检查文件的根目录
context: path.resolve(__dirname, "../src"),
exclude: "node_modules", // 默认值
cache: true, // 开启缓存
// 缓存目录
cacheLocation: path.resolve(
__dirname,
"../node_modules/.cache/.eslintcache"
),
threads, // 开启多进程
}),
new HtmlWebpackPlugin({
// 以 public/index.html 为模板创建文件
// 新的html文件有两个特点:1. 内容和源文件一致 2. 自动引入打包生成的js等资源
template: path.resolve(__dirname, "../public/index.html"),
}),
// 提取css成单独文件
new MiniCssExtractPlugin({
// 定义输出文件名和目录
filename: "static/css/[name].css",
chunkFilename:"static/css/[name].chunk.css", //动态导入的CSS命名
}),
// css压缩
// new CssMinimizerPlugin(),
],
optimization: {
minimizer: [
// css压缩也可以写到optimization.minimizer里面,效果一样的
new CssMinimizerPlugin(),
// 当生产模式会默认开启TerserPlugin,但是我们需要进行其他配置,就要重新写了
new TerserPlugin({
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: {
chunks: "all", // 对所有模块都进行分割
// 其他内容用默认配置即可
},
},
// devServer: {
// host: "localhost", // 启动服务器域名
// port: "3000", // 启动服务器端口号
// open: true, // 是否自动打开浏览器
// },
mode: "production",
devtool: "source-map",
};