Webpack5入门到原理

Webpack5学习

尚硅谷Webpack5新版视频教程

B站直达:https://www.bilibili.com/video/BV14T4y1z7sw

百度网盘:https://pan.baidu.com/s/114lJRGua2uHBdLq_iVLOOQ 提取码:yyds

阿里云盘:https://www.aliyundrive.com/s/UMkmCzdWsGh(教程配套资料请从百度网盘下载)

围观尚硅谷前端课程:http://www.atguigu.com/web

更多尚硅谷视频教程请访问:http://www.atguigu.com/download.shtml

在线课程地址:https://yk2012.github.io/sgg_webpack5/

Webpack5中文文档:Loaders | webpack 中文文档 (docschina.org)
代码仓库:https://gitee.com/szxio/webpack5

1. 简单使用

1.1 搭建项目

image-20230821224325473

1.2 编写JS代码

count.js

export default function count(a,b){return a + b
}

sum.js

export default function sum(...args){return args.reduce((a,b)=>a+b,0)
}

main.js

import count from "./js/count";
import sum from "./js/sum";console.log(count(1,2))
console.log(sum(1,2,3,4))

index.html

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body><h1>Hello World</h1>
</body>
<script src="./src/main.js"></script>
</html>

1.3 运行代码

image-20230821224516038

可以发现浏览器报错。默认不支持ES module语法

1.4 初始化依赖

npm init -y

会自动生成 package.json

image-20230821224645037

继续安装依赖

npm i webpack webpack-cli -D

1.5 启动webpack

开发环境构建

npx webpack ./src/main.js --mode=development

生产环境构建

npx webpack ./src/main.js --mode=production

修改 main.js 文件的引用地址。浏览器正常打印

2. 基本配置

2.1 5大核心概念

  1. entry(入口)

指示 Webpack 从哪个文件开始打包

  1. output(输出)

指示 Webpack 打包完的文件输出到哪里去,如何命名等

  1. loader(加载器)

webpack 本身只能处理 js、json 等资源,其他资源需要借助 loader,Webpack 才能解析

  1. plugins(插件)

扩展 Webpack 的功能

  1. mode(模式)

主要由两种模式:

  • 开发模式:development
  • 生产模式:production

2.2 基础配置文件

在项目根目录新建文件 webpack.config.js

const path = require("path");
module.exports = {// 入口文件,相对路径entry: "./src/main.js",// 输出output: {// 要输出到的位置,绝对路径path: path.resolve(__dirname,"dist"),// 文件名filename: "main.js"},// 加载器module: {rules: [// loader 的配置]},// 插件plugins: [// plugins的配置],// 模式mode: "development"
}

有了这个配置文件,我们再进行打包时只需输入如下命令即可

npx webpack

这个命令会自动找根目录中的 webpa.config.js 文件中的配置进行打包

3. 处理样式资源

3.1 处理CSS资源

webpack默认只支持JS文件,我们编写CSS后打包会出错,我们可以通过添加loader来处理CSS资源

更多loader可以通过webpack官方文档来查看

当我们在 main.js 中引入 css 文件后,执行打包会出现下面的错误

image-20230823223056375

安装 loader

npm i css-loader style-loader -D

然后配置规则

module.exports = {module: {rules: [{test: /\.css$/,use: ['style-loader','css-loader',],},],},
};

此时再次执行打包即可

3.2 处理Less资源

安装

npm install less less-loader --save-dev

配置规则

module.exports = {module: {rules: [{test: /\.less$/i,use: [// compiles Less to CSS'style-loader','css-loader','less-loader',],},],},
};

3.3 处理Scss资源

安装

npm install sass-loader sass --save-dev

配置规则

module.exports = {module: {rules: [{test: /\.s[ac]ss$/i,use: [// 将 JS 字符串生成为 style 节点'style-loader',// 将 CSS 转化成 CommonJS 模块'css-loader',// 将 Sass 编译成 CSS'sass-loader',],},],},
};

4. 处理图片资源

4.1 图片转Base64

我们可以通过配置,将小于一定大小的文件转成base64处理,从而减少请求

添加以下配置

module.exports = {module: {rules: [{test: /\.(png|jpe?g|gif|webp)$/,type: "asset",parser: {dataUrlCondition: {maxSize: 10 * 1024 // 小于10kb的图片会被base64处理}}},],},
};

转成base64后请求不会耗时间

image-20230824214034674

5. 修改文件输出地址

目前我们打包生成的文件都在一个文件下

image-20230824214711536

我想让他变成 JS 文件在 JS 文件下,图片文件在图片文件下

修改配置如下

const path = require("path");
module.exports = {// 入口文件,相对路径entry: "./src/main.js",// 输出output: {// 要输出到的位置,绝对路径path: path.resolve(__dirname,"dist"),// 文件名
+       filename: "js/main.js",},// 加载器module: {rules: [{test: /\.(png|jpe?g|gif|webp)$/,type: "asset",parser: {dataUrlCondition: {maxSize: 18 * 1024 // 小于10kb的图片会被base64处理}},
+               generator: {
+                   // 修改图片文件的输出地址
+                   // hash:10 只取10位哈希值
+                   // ext图片后缀名
+                   // query 图片文件后面携带的参数
+                   filename: 'images/[hash:10][ext][query]'
+               }},]},// 插件plugins: [// plugins的配置],// 模式mode: "development"
}

再次打包看效果

image-20230824220006600

6. 自动清空上次打包文件

在 output 下新增 clean 属性即可

const path = require("path");module.exports = {// 入口文件,相对路径entry: "./src/main.js",// 输出output: {// 要输出到的位置,绝对路径path: path.resolve(__dirname,"dist"),// 文件名filename: "js/main.js",// 每次打包前自动清空path对应的目录文件clean: true},
}

7. 处理字体图标资源

我们可以在 阿里巴巴矢量图标库 中找一些图标资源,然后加入到项目中,点击下载到本地

image-20230824222354716

使用第二种方式引入

image-20230824222420993

在项目中导入下载下来的 css 和图标文件

image-20230824222512888

在 main.js 中引入 iconfont.css

然后配置规则,将字体图标打包输出到 font 文件夹中

module.exports = {module: {rules: [{test: /\.(ttf|woff2?)$/,type: "asset/resource",generator: {filename: 'font/[hash:10][ext][query]'}},]},
}

然后再页面中可以通过下面的方式使用

<span class="iconfont icon-aixin" style="font-size: 30px;color: orangered"></span>
<span class="iconfont icon-bianji"></span>
<span class="iconfont icon-dianzan"></span>
<span class="iconfont icon-dingwei"></span>

image-20230824222755309

8. 处理其他资源

其他资源我们直接在下面的规则中添加对应的文件后缀即可,webpack 会原封不动的将文件输出到指定目录

module.exports = {module: {rules: [{test: /\.(ttf|woff2?|mp3|mp4|word)$/,type: "asset/resource",generator: {filename: 'font/[hash:10][ext][query]'}},]},
}

9. 处理JS文件

9.1 Eslint 插件

可组装的 JavaScript 和 JSX 检查工具。

这句话意思就是:它是用来检测 js 和 jsx 语法的工具,可以配置各项功能

我们使用 Eslint,关键是写 Eslint 配置文件,里面写上各种 rules 规则,将来运行 Eslint 时就会以写的规则对代码进行检查

配置文件的写法

配置文件有很多种写法:

  • .eslintrc.*

    :新建文件,位于项目根目录

    • .eslintrc
    • .eslintrc.js
    • .eslintrc.json
    • 区别在于配置格式不一样
  • package.jsoneslintConfig:不需要创建文件,在原有文件基础上写

ESLint 会查找和自动读取它们,所以以上配置文件只需要存在一个即可

具体配置

.eslintrc.js 为例

module.exports = {// 解析选项parserOptions: {},// 具体检查规则rules: {},// 继承其他规则extends: [],// ...// 其他规则详见:https://eslint.bootcss.com/docs/user-guide/configuring
};

parserOptions 解析选项

parserOptions: {ecmaVersion: 6, // ES 语法版本sourceType: "module", // ES 模块化ecmaFeatures: { // ES 其他特性jsx: true // 如果是 React 项目,就需要开启 jsx 语法}
}

rules 具体规则

  • "off"0 - 关闭规则
  • "warn"1 - 开启规则,使用警告级别的错误:warn (不会导致程序退出)
  • "error"2 - 开启规则,使用错误级别的错误:error (当被触发的时候,程序会退出)
rules: {semi: "error", // 禁止使用分号'array-callback-return': 'warn', // 强制数组方法的回调函数中有 return 语句,否则警告'default-case': ['warn', // 要求 switch 语句中有 default 分支,否则警告{ commentPattern: '^no default$' } // 允许在最后注释 no default, 就不会有警告了],eqeqeq: ['warn', // 强制使用 === 和 !==,否则警告'smart' // https://eslint.bootcss.com/docs/rules/eqeqeq#smart 除了少数情况下不会有警告],
}

更多规则详见:规则文档

extends 继承

开发中一点点写 rules 规则太费劲了,所以有更好的办法,继承现有的规则。

现有以下较为有名的规则:

  • Eslint 官方的规则open in new window:eslint:recommended
  • Vue Cli 官方的规则open in new window:plugin:vue/essential
  • React Cli 官方的规则open in new window:react-app
module.exports = {extends: ["eslint:recommended"],rules: {// 我们的规则会覆盖掉react-app的规则// 所以想要修改规则直接改就是了eqeqeq: ["warn", "smart"],},
};

Esline在Webpack中的使用

安装

npm install eslint-webpack-plugin eslint --save-dev

然后把插件添加到你的 webpack 配置。例如:

const ESLintPlugin = require('eslint-webpack-plugin');module.exports = {// ...plugins: [new ESLintPlugin({// 指定文件的根目录context:path.resolve(__dirname, "src"),})],// ...
};

在根目录新建 .eslintrc.js

module.exports = {// 继承 Eslint 规则extends: ["eslint:recommended"],env: {node: true, // 启用node中全局变量browser: true, // 启用浏览器中全局变量},parserOptions: {ecmaVersion: 6,sourceType: "module",},rules: {"no-var": 2, // 不能使用 var 定义变量},
};

我们编写一个错误的代码

image-20230824231332115

在打包时会提示错误,并终止打包

image-20230824231322801

在 webstorm 中可以开启 eslint 检测

image-20230824231406983

这样在编写代码时就可以发现错误

添加eslint忽略文件

新建 .eslintignore 文件

# 忽略dist目录下所有文件
dist

9.2 Babel 插件

JavaScript 编译器。

主要用于将 ES6 语法编写的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中

配置文件

配置文件由很多种写法:

  • babel.config.*
    

    :新建文件,位于项目根目录

    • babel.config.js
    • babel.config.json
  • .babelrc.*
    

    :新建文件,位于项目根目录

    • .babelrc
    • .babelrc.js
    • .babelrc.json
  • package.jsonbabel:不需要创建文件,在原有文件基础上写

Babel 会查找和自动读取它们,所以以上配置文件只需要存在一个即可

配置示例

我们以 babel.config.js 配置文件为例:

module.exports = {// 预设presets: ["@babel/preset-env"],
};

presets 预设

简单理解:就是一组 Babel 插件, 扩展 Babel 功能

  • @babel/preset-env: 一个智能预设,允许您使用最新的 JavaScript。
  • @babel/preset-react:一个用来编译 React jsx 语法的预设
  • @babel/preset-typescript:一个用来编译 TypeScript 语法的预设

在webpack中使用

安装

npm install -D babel-loader @babel/core @babel/preset-env

用法一:

在 webpack 配置对象中,需要将 babel-loader 添加到 module 列表中,就像下面这样

module: {rules: [{test: /\.m?js$/,exclude: /(node_modules|bower_components)/, // 排除文件use: {loader: 'babel-loader',options: {presets: ['@babel/preset-env']}}}]
}

用法二(推荐用法):

在项目根目录新建 babel.config.js

module.exports = {presets: ['@babel/preset-env']
}

然后在 webpack 配置对象中添加

module: {rules: [{test: /\.m?js$/,exclude: /(node_modules|bower_components)/, // 排除文件use: {loader: 'babel-loader',       }}]
}

打包前生成的文件代码

image-20230826093200339

打包后的文件代码

image-20230826093532713

修改输出的结果不以箭头输出

在 output 中 添加 environment 配置

// 输出
output: {// 要输出到的位置,绝对路径path: path.resolve(__dirname,"dist"),// 文件名filename: "js/main.js",// 每次打包前自动清空path对应的目录文件clean: true,environment: {// 关闭箭头函数输出arrowFunction: false}
},

10. 处理HTML文件

我们希望每次打包后可以为我们自动的引入JS,通过如下方式设置

安装

npm install --save-dev html-webpack-plugin

引入和使用

const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');module.exports = {plugins: [// 处理HTML文件new HtmlWebpackPlugin({template: path.resolve(__dirname, "index.html"), // 使用的模板地址title: "学习Webpack", // 网站titlehash:true, // 添加哈希值})],
};

设置了 title 后需要在模板中修改 title 的设置方式,需要动态获取

<title><%= htmlWebpackPlugin.options.title %></title>

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body><span class="iconfont icon-aixin" style="font-size: 30px;color: orangered"></span><span class="iconfont icon-bianji"></span><span class="iconfont icon-dianzan"></span><span class="iconfont icon-dingwei"></span><div class="box"></div><div class="box1"></div><div class="box2"><span class="desc">哈哈</span></div><h1>Hello World</h1>
</body>
</html>

更多 options 配置查看文档:https://github.com/jantimon/html-webpack-plugin#options

打包后生成的文件

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>学习Webpack</title>
<script defer src="js/main.js?15bb642196d30b484393"></script></head>
<body><span class="iconfont icon-aixin" style="font-size: 30px;color: orangered"></span><span class="iconfont icon-bianji"></span><span class="iconfont icon-dianzan"></span><span class="iconfont icon-dingwei"></span><div class="box"></div><div class="box1"></div><div class="box2"><span class="desc">哈哈</span></div><h1>Hello World</h1>
</body>
</html>

11. 搭建开发服务器

我们次每次修改完代码后都需要重新打包,然后运行html文件才能看到效果,我们可以通过搭建开发服务器,可以实时的查看改动

安装

npm i webpack-dev-server -D

添加配置

// 开发服务器
devServer: {host: "localhost", // 启动服务器域名port: "3000", // 启动服务器端口号open: true, // 是否自动打开浏览器
},

然后通过如下命令启动

npx webpack serve

启动成功后光标会停留在下面,同时自动打开浏览器,我们修改代码后刷新浏览器会自动生效

image-20230826110943453

12. 生产环境准备工作

根目录新建 config 文件,将原来的 webpck.config.js 放在 config 文件中,并复制一份配置

修改两个配置文件名分别为

  • webpack.dev.js
  • webpack.prod.js

然后再所有的相对路径前添加 ../ 往外跳一层

删除 dev 配置文件中的 output 配置,因为dev环境不需要打包,删除 prod 配置中的 devServer 配置

设置好的配置文件分别如下

  • webpack.dev.js
const path = require("path");
const ESLintPlugin = require('eslint-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin');module.exports = {// 入口文件,相对路径entry: "./src/main.js",// 加载器module: {rules: [{// 用来匹配 .css 结尾的文件test: /\.css$/,// use 数组里面 Loader 执行顺序是从右到左use: ["style-loader", "css-loader"],},{test: /\.less$/i,use: [// compiles Less to CSS'style-loader','css-loader','less-loader',],},{test: /\.s[ac]ss$/i,use: [// 将 JS 字符串生成为 style 节点'style-loader',// 将 CSS 转化成 CommonJS 模块'css-loader',// 将 Sass 编译成 CSS'sass-loader',],},{test: /\.(png|jpe?g|gif|webp)$/,type: "asset",parser: {dataUrlCondition: {maxSize: 18 * 1024 // 小于10kb的图片会被base64处理}},generator: {// 修改图片文件的输出地址// hash:10 只取10位哈希值// ext图片后缀名// query 图片文件后面携带的参数filename: 'images/[hash:10][ext][query]'}},{test: /\.(ttf|woff2?|mp3|mp4|word)$/,type: "asset/resource",generator: {filename: 'font/[hash:10][ext][query]'}},{test: /\.m?js$/,exclude: /(node_modules|bower_components)/, // 排除文件use: {loader: 'babel-loader',}}]},// 插件plugins: [// Eslint插件new ESLintPlugin({// 指定文件的根目录context:path.resolve(__dirname, "../src"),}),// 处理HTML文件new HtmlWebpackPlugin({template: path.resolve(__dirname, "../index.html"), // 使用的模板地址title: "学习Webpack", // 网站titlehash:true, // 添加哈希值})],// 模式mode: "development",// 开发服务器devServer: {host: "localhost", // 启动服务器域名port: "3000", // 启动服务器端口号open: true, // 是否自动打开浏览器liveReload:true,},
}
  • webpack.build.js
const path = require("path");
const ESLintPlugin = require('eslint-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin');module.exports = {// 入口文件,相对路径entry: "./src/main.js",// 输出output: {// 要输出到的位置,绝对路径path: path.resolve(__dirname,"../dist"),// 文件名filename: "js/main.js",// 每次打包前自动清空path对应的目录文件clean: true,// 关闭箭头函数输出environment: {arrowFunction: false}},// 加载器module: {rules: [{// 用来匹配 .css 结尾的文件test: /\.css$/,// use 数组里面 Loader 执行顺序是从右到左use: ["style-loader", "css-loader"],},{test: /\.less$/i,use: [// compiles Less to CSS'style-loader','css-loader','less-loader',],},{test: /\.s[ac]ss$/i,use: [// 将 JS 字符串生成为 style 节点'style-loader',// 将 CSS 转化成 CommonJS 模块'css-loader',// 将 Sass 编译成 CSS'sass-loader',],},{test: /\.(png|jpe?g|gif|webp)$/,type: "asset",parser: {dataUrlCondition: {maxSize: 18 * 1024 // 小于10kb的图片会被base64处理}},generator: {// 修改图片文件的输出地址// hash:10 只取10位哈希值// ext图片后缀名// query 图片文件后面携带的参数filename: 'images/[hash:10][ext][query]'}},{test: /\.(ttf|woff2?|mp3|mp4|word)$/,type: "asset/resource",generator: {filename: 'font/[hash:10][ext][query]'}},{test: /\.m?js$/,exclude: /(node_modules|bower_components)/, // 排除文件use: {loader: 'babel-loader',}}]},// 插件plugins: [// Eslint插件new ESLintPlugin({// 指定文件的根目录context:path.resolve(__dirname, "../src"),}),// 处理HTML文件new HtmlWebpackPlugin({template: path.resolve(__dirname, "../index.html"), // 使用的模板地址title: "学习Webpack", // 网站titlehash:true, // 添加哈希值})],// 模式mode: "production",
}

然后修改 package.json 文件,设置启动命令和打包命令

"scripts": {"dev": "webpack serve --config ./config/webpack.dev.js","build": "webpack --config ./config/webpack.prod.js"
}

13. CSS处理

13.1 提取CSS成单独文件

安装

npm install --save-dev mini-css-extract-plugin

需要将原来的 style-loader 换成 MiniCssExtractPlugin.loader,并在 plugins 使用插件

module.exports = {plugins: [new MiniCssExtractPlugin({filename:"state/css/main.css" // 指定css文件输出位置})],module: {rules: [{test: /\.css$/i,use: [MiniCssExtractPlugin.loader, "css-loader"],},],},
};

然后配合上面的 html-webpack-plugin 插件,可以自动完成资源引入

打包后生成的目录如下

image-20230826160424031

13.2 处理CSS兼容性问题

有些CSS样式在部分浏览器中可能会存在兼容性问题,我们通过配置可以来处理这部分的兼容问题

安装

npm i postcss-loader postcss postcss-preset-env -D

添加配置。这个配置必须放在 css-loader 后面

rules:[{// 用来匹配 .css 结尾的文件test: /\.css$/,// use 数组里面 Loader 执行顺序是从右到左use: [MiniCssExtractPlugin.loader, "css-loader", {loader: "postcss-loader",options: {postcssOptions: {plugins: ["postcss-preset-env", // 能解决大多数样式兼容性问题],},},}],},
]

在 package.json 文件中添加配置

"browserslist": ["ie >= 6"
]

这个意思表示兼容到 ie6

我们尝试在代码中添加如下样式

.box{width: 200px;height: 200px;background-color: pink;background-image: url("../images/1.png");transition: 0.5s;
}
.box:hover{transform: scale(1.2);transition: 0.5s;
}

然后执行打包命令,查看打包后的代码

.box{width: 200px;height: 200px;background-color: pink;background-image: url(../../state/images/626c3888ec.png);transition: 0.5s;
}
.box:hover{-ms-transform: scale(1.2);transform: scale(1.2);transition: 0.5s;
}

可以看到加了一些兼容性的代码

但是通常情况下我们一般不考虑旧版本浏览器了,所以我们可以这样设置:

"browserslist": ["last 2 version","> 1%","not dead"
]
  • last 2 version 只考虑浏览器最近的两个版本
  • > 1% 覆盖99%的浏览器
  • not dead 不考虑已经死掉的版本

上面三个配置取交集做兼容处理

13.3 提取重复配置

const path = require("path");
const ESLintPlugin = require('eslint-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");function myCssLoader(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: {// 要输出到的位置,绝对路径path: path.resolve(__dirname,"../dist"),// 文件名filename: "state/js/main.js",// 每次打包前自动清空path对应的目录文件clean: true,// 关闭箭头函数输出environment: {arrowFunction: false}},// 加载器module: {rules: [{// 用来匹配 .css 结尾的文件test: /\.css$/,// use 数组里面 Loader 执行顺序是从右到左use: myCssLoader(),},{test: /\.less$/i,use: myCssLoader('less-loader')},{test: /\.s[ac]ss$/i,use: myCssLoader('sass-loader')},{test: /\.(png|jpe?g|gif|webp)$/,type: "asset",parser: {dataUrlCondition: {maxSize: 18 * 1024 // 小于10kb的图片会被base64处理}},generator: {// 修改图片文件的输出地址// hash:10 只取10位哈希值// ext图片后缀名// query 图片文件后面携带的参数filename: 'state/images/[hash:10][ext][query]'}},{test: /\.(ttf|woff2?|mp3|mp4|word)$/,type: "asset/resource",generator: {filename: 'state/resource/[hash:10][ext][query]'}},{test: /\.m?js$/,exclude: /(node_modules|bower_components)/, // 排除文件use: {loader: 'babel-loader',}}]},// 插件plugins: [// Eslint插件new ESLintPlugin({// 指定文件的根目录context:path.resolve(__dirname, "../src"),}),// 处理HTML文件new HtmlWebpackPlugin({template: path.resolve(__dirname, "../index.html"), // 使用的模板地址title: "学习Webpack", // 网站titlehash:true, // 添加哈希值}),// 将CSS处理成单独文件new MiniCssExtractPlugin({filename:"state/css/main.css"})],// 模式mode: "production",
}

13.4 压缩CSS

下载依赖

const CssMinimizerPlugin = require("css-minimizer-webpack-plugin");
plugins:[// 压缩CSSnew CssMinimizerPlugin()
]

打包看效果

image-20230826163915973

14. 高级优化

14.1 提升开发体验SourceMap

开发时我们运行的代码是经过 webpack 编译后的,例如下面这个样子:

image-20230826165819723

如果代码在某一行出现问题,我们调试起来很不方便,但是通过 SourceMap 会创建一种映射,我们可以直接定位到源代码,从而快速方便的查看是哪一行代码出问题

通过查看Webpack DevTool 文档open in new window可知,SourceMap 的值有很多种情况.

但实际开发时我们只需要关注两种情况即可:

  • 开发模式:cheap-module-source-map
    • 优点:打包编译速度快,只包含行映射
    • 缺点:没有列映射
module.exports = {// 其他省略mode: "development",devtool: "cheap-module-source-map",
};
  • 生产模式:

    source-map
    
    • 优点:包含行/列映射
    • 缺点:打包编译速度更慢
module.exports = {// 其他省略mode: "production",devtool: "source-map",
};

14.2 Hot热替换

为什么

开发时我们修改了其中一个模块代码,Webpack 默认会将所有模块全部重新打包编译,速度很慢。

所以我们需要做到修改某个模块代码,就只有这个模块代码需要重新打包编译,其他模块不变,这样打包速度就能很快。

是什么

HotModuleReplacement(HMR/热模块替换):在程序运行中,替换、添加或删除模块,而无需重新加载整个页面。

怎么用

  1. 基本配置
module.exports = {// 其他省略devServer: {host: "localhost", // 启动服务器域名port: "3000", // 启动服务器端口号open: true, // 是否自动打开浏览器hot: true, // 开启HMR功能(只能用于开发环境,生产环境不需要了)},
};

此时 css 样式经过 style-loader 处理,已经具备 HMR 功能了。 但是 js 还不行。

  1. JS 配置
// main.js
import count from "./js/count";
import sum from "./js/sum";
// 引入资源,Webpack才会对其打包
import "./css/iconfont.css";
import "./css/index.css";
import "./less/index.less";
import "./sass/index.sass";
import "./sass/index.scss";
import "./styl/index.styl";const result1 = count(2, 1);
console.log(result1);
const result2 = sum(1, 2, 3, 4);
console.log(result2);// 判断是否支持HMR功能
if (module.hot) {module.hot.accept("./js/count.js", function (count) {const result1 = count(2, 1);console.log(result1);});module.hot.accept("./js/sum.js", function (sum) {const result2 = sum(1, 2, 3, 4);console.log(result2);});
}

上面这样写会很麻烦,所以实际开发我们会使用其他 loader 来解决。

比如:vue-loaderopen in new window, react-hot-loaderopen in new window

14.3 oneOf

为什么

打包时每个文件都会经过所有 loader 处理,虽然因为 test 正则原因实际没有处理上,但是都要过一遍。比较慢。

是什么

顾名思义就是只能匹配上一个 loader, 剩下的就不匹配了

怎么用

const path = require("path");
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");function myCssLoader(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: {// 要输出到的位置,绝对路径path: path.resolve(__dirname,"../dist"),// 文件名filename: "state/js/main.js",// 每次打包前自动清空path对应的目录文件clean: true,// 关闭箭头函数输出environment: {arrowFunction: false}},// 加载器module: {rules: [{oneOf: [{// 用来匹配 .css 结尾的文件test: /\.css$/,// use 数组里面 Loader 执行顺序是从右到左use: myCssLoader(),},{test: /\.less$/i,use: myCssLoader('less-loader')},{test: /\.s[ac]ss$/i,use: myCssLoader('sass-loader')},{test: /\.(png|jpe?g|gif|webp)$/,type: "asset",parser: {dataUrlCondition: {maxSize: 18 * 1024 // 小于10kb的图片会被base64处理}},generator: {// 修改图片文件的输出地址// hash:10 只取10位哈希值// ext图片后缀名// query 图片文件后面携带的参数filename: 'state/images/[hash:10][ext][query]'}},{test: /\.(ttf|woff2?|mp3|mp4|word)$/,type: "asset/resource",generator: {filename: 'state/resource/[hash:10][ext][query]'}},{test: /\.m?js$/,exclude: /(node_modules|bower_components)/, // 排除文件use: {loader: 'babel-loader',}}]}]},// 插件plugins: [// Eslint插件new ESLintPlugin({// 指定文件的根目录context:path.resolve(__dirname, "../src"),}),// 处理HTML文件new HtmlWebpackPlugin({template: path.resolve(__dirname, "../index.html"), // 使用的模板地址title: "学习Webpack", // 网站titlehash:true, // 添加哈希值}),// 将CSS处理成单独文件new MiniCssExtractPlugin({filename:"state/css/main.css"}),// 压缩CSSnew CssMinimizerPlugin()],// 模式mode: "production",devtool: "source-map"
}

14.4 Include/Exclude

为什么

开发时我们需要使用第三方的库或插件,所有文件都下载到 node_modules 中了。而这些文件是不需要编译可以直接使用的。

所以我们在对 js 文件处理时,要排除 node_modules 下面的文件。

是什么

  • include

包含,只处理 xxx 文件

  • exclude

排除,除了 xxx 文件以外其他文件都处理

怎么用

const path = require("path");
const ESLintPlugin = require('eslint-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin');module.exports = {// 入口文件,相对路径entry: "./src/main.js",// 加载器module: {rules: [// ....省略其他{test: /\.m?js$/,// exclude: /(node_modules|bower_components)/, // 排除文件include: path.resolve(__dirname,"../src"), // 也可以设置只包含某个文件夹下的JSuse: {loader: 'babel-loader',}}]},// 插件plugins: [// Eslint插件new ESLintPlugin({// 指定文件的根目录context:path.resolve(__dirname, "../src"),exclude:["node_modules"] // 排除 node_modules 文件夹下的文件,默认是node_module}),// 处理HTML文件new HtmlWebpackPlugin({template: path.resolve(__dirname, "../index.html"), // 使用的模板地址title: "学习Webpack", // 网站titlehash:true, // 添加哈希值})],// 模式mode: "development",devtool: "cheap-module-source-map",// 开发服务器devServer: {host: "localhost", // 启动服务器域名port: "3000", // 启动服务器端口号open: true, // 是否自动打开浏览器hot:true, // 热替换,改变JS或者CSS是可以不刷新页面就看到最新效果},
}

14.5 Catch

为什么

每次打包时 js 文件都要经过 Eslint 检查 和 Babel 编译,速度比较慢。

我们可以缓存之前的 Eslint 检查 和 Babel 编译结果,这样第二次打包时速度就会更快了

怎么用

rules:[{test: /\.m?js$/,exclude: /(node_modules|bower_components)/, // 排除文件use: {loader: 'babel-loader',options: {cacheDirectory: true, // 开启babel编译缓存cacheCompression: false, // 缓存文件不要压缩}}}
]

eslint-webpack-plugin 默认已经开启了缓存

打包后查看 node_modules/.catch

image-20230826212721210

14.6 开启多线程打包

为什么

当我们项目文件足够大,数量足够多时,执行打包操作会耗时很长,这时我们就可以通过设置开启多线程打包提高打包速度

安装

cnpm install terser-webpack-plugin thread-loader --save-dev

terser-webpack-plugin 在webpack5中默认自带,但是我们要修改默认配置开启多线程打包,所以也要重新安装一下

TerserWebpackPlugin | webpack 中文文档 (docschina.org)

thread-loader 是用来开启多线程,放在需要开启多线程的loader前面

thread-loader | webpack 中文文档 (docschina.org)

开启的线程数默认是:cpu核心数 -1

怎么用

const path = require("path");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");function myCssLoader(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: {// 要输出到的位置,绝对路径path: path.resolve(__dirname,"../dist"),// 文件名filename: "state/js/main.js",// 每次打包前自动清空path对应的目录文件clean: true,// 关闭箭头函数输出environment: {arrowFunction: false}},// 加载器module: {rules: [{oneOf: [{// 用来匹配 .css 结尾的文件test: /\.css$/,// use 数组里面 Loader 执行顺序是从右到左use: myCssLoader(),},{test: /\.less$/i,use: myCssLoader('less-loader')},{test: /\.s[ac]ss$/i,use: myCssLoader('sass-loader')},{test: /\.(png|jpe?g|gif|webp)$/,type: "asset",parser: {dataUrlCondition: {maxSize: 18 * 1024 // 小于10kb的图片会被base64处理}},generator: {// 修改图片文件的输出地址// hash:10 只取10位哈希值// ext图片后缀名// query 图片文件后面携带的参数filename: 'state/images/[hash:10][ext][query]'}},{test: /\.(ttf|woff2?|mp3|mp4|word)$/,type: "asset/resource",generator: {filename: 'state/resource/[hash:10][ext][query]'}},{test: /\.m?js$/,exclude: /(node_modules|bower_components)/, // 排除文件use: [// 在需要开启多线程运行的loader前加上 thread-loader"thread-loader",{loader: 'babel-loader',options: {cacheDirectory: true, // 开启babel编译缓存cacheCompression: false, // 缓存文件不要压缩}}]}]}]},// 插件plugins: [// Eslint插件new ESLintPlugin({// 指定文件的根目录context:path.resolve(__dirname, "../src"),threads:true // 开启多线程,默认线程数是cpu核心数-1}),// 处理HTML文件new HtmlWebpackPlugin({template: path.resolve(__dirname, "../index.html"), // 使用的模板地址title: "学习Webpack", // 网站titlehash:true, // 添加哈希值}),// 将CSS处理成单独文件new MiniCssExtractPlugin({filename:"state/css/main.css",}),],optimization: {minimize: true,minimizer: [// 压缩CSSnew CssMinimizerPlugin({parallel:true,// 开启多线程}),// 当生产模式会默认开启TerserPlugin,但是我们需要进行其他配置,就要重新写了new TerserPlugin({parallel:true // 开启多线程})]},// 模式mode: "production",devtool: "source-map"
}

14.7 减少代码体积

为什么

Babel 为编译的每个文件都插入了辅助代码,使代码体积过大!

Babel 对一些公共方法使用了非常小的辅助代码,比如 _extend。默认情况下会被添加到每一个需要它的文件中。

你可以将这些辅助代码作为一个独立模块,来避免重复引入。

是什么

@babel/plugin-transform-runtime: 禁用了 Babel 自动对每个文件的 runtime 注入,而是引入 @babel/plugin-transform-runtime 并且使所有辅助代码从这里引用。

怎么用

npm i @babel/plugin-transform-runtime -D
rules:[{test: /\.m?js$/,// exclude: /(node_modules|bower_components)/, // 排除文件include: path.resolve(__dirname,"../src"), // 也可以设置只包含某个文件夹下的JSuse: {loader: 'babel-loader',options: {cacheDirectory: true, // 开启babel编译缓存cacheCompression: false, // 缓存文件不要压缩plugins: ["@babel/plugin-transform-runtime"], // 减少代码体积}}}
]

14.8 压缩图片

下载

npm i image-minimizer-webpack-plugin imagemin -D

根据情况继续下载依赖

无损压缩

npm install imagemin-gifsicle imagemin-jpegtran imagemin-optipng imagemin-svgo -D

有损压缩

npm install imagemin-gifsicle imagemin-mozjpeg imagemin-pngquant imagemin-svgo -D

我们使用无损压缩使用

// 引入
const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");module.exports = {optimization: {minimize: true,minimizer: [// 压缩图片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",},},],},],],},},}),]},// 模式mode: "production",devtool: "source-map"
}

打包看效果

image-20230826224900002

15. 提高代码运行效率

15.1 Code Split

打包代码时会将所有 js 文件打包到一个文件中,体积太大了。我们如果只要渲染首页,就应该只加载首页的 js 文件,其他文件不应该加载。所以我们需要将打包生成的文件进行代码分割,生成多个 js 文件,渲染哪个页面就只加载某个 js 文件,这样加载的资源就少,速度就更快。

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

  1. 分割文件:将打包生成的文件进行分割,生成多个 js 文件。
  2. 按需加载:需要哪个文件就加载哪个文件。

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

多入口配置

需要将 entry 设置成一个对象,key 是生成的文件名,value 对应文件相对路径

在 output 中使用 [name].js 设置输出的文件

const path = require("path")
const HtmlWebpackPlugin = require("html-webpack-plugin");module.exports = {entry: {app:"./src/app.js",main:"./src/main.js"},output:{path:path.resolve(__dirname,"./dist"),filename: "js/[name].js",clean: true},plugins: [new HtmlWebpackPlugin({template: path.resolve(__dirname,"index.html")})],mode: "production"
}

代码目录结构

demo01             
├── src            
│   ├── app.js     
│   └── main.js
├── index.html
├── package.json
└── webpack.config.js

打包后的目录

dist          
├── js        
│   ├── app.js
│   └── main.js
└── index.html

提取重复文件

在项目中如果我们有两个文件都引用了另外一个文件的内容,但是打包时会吧共同的引用的文件打包两份,这样会造成打包后的体积变大,从而降低加载速度,我们可以通过配置 splitChunks 来解决这种问题

首先创建一个 sum.js

export function sum(...args){return args.reduce((a,b)=>a+b,0)
}

然后 app.jsmain.js 分别使用 sum 函数

// app.js
import {sum} from "./sum";console.log("app")
console.log(sum(1,2,3,4))
// main.js
import {sum} from "./sum";console.log("main")
console.log(sum(5,6,7,8))

我们现在执行一次打包操作,看看打包后的输出是什么样子

image-20230827110838347

image-20230827110851500

可以看到还是输出了两个文件,并且每个文件里面都有sum函数

添加以下配置信息

const path = require("path")
const HtmlWebpackPlugin = require("html-webpack-plugin");module.exports = {entry: {app:"./src/app.js",main:"./src/main.js"},output:{path:path.resolve(__dirname,"./dist"),filename: "js/[name].js",clean: true},plugins: [new HtmlWebpackPlugin({template: path.resolve(__dirname,"index.html")})],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,},},},},mode: "production"
}

然后再次打包可以看到 sum 函数被单独打包成了一个文件

image-20230827111109476

按需加载

例如页面中一个按钮,点击按钮后会调用一个函数进行加法运算,不点击按钮就不会进行加法运算。这时我们可以使用按需加载的方式来加载这个JS,如果不点击,则不会请求这个文件,从而提高页面首次加载速度

新建 count.js

export default function (a,b){return a + b
}

main.js 中添加代码

document.getElementById("btn").onclick = function (){import("./count").then(res=>{console.log(res.default(1,2))})
}

然后 index.html 中添加按钮

<body><button id="btn">按钮</button>
</body>

打包文件,打开 index.html

image-20230827112722995

image-20230827112754300

Esllint 默认不支持 import 动态导入语法,在 .eslintrc.js 文件中添加配置即可

plugins: ["import"]

给动态生成的文件命名

细心的同学已经发现,我们上面点击按钮是,动态加载了一个叫 293.js 的文件,但是我们在代码中并不是这个名,将来在我们调试的时候可能会分不清楚那个文件是我们要找的文件。

所以我们可以通过编写特定对的注释代码,来对动态加载的文件命名

document.getElementById("btn").onclick = function () {import(/* webpackChunkName: "count" */ "./js/count").then(res=>{console.log(res.default(1, 2))})
}

固定写法,在动态加载的文件前添加 /* webpackChunkName: "count" */ 这段注释。count 就是我们想要生成的文件名称

然后再配置文件中添加下面的代码即可

chunkFilename: "state/js/chunk/[name].js",

具体配置

// 入口文件,相对路径
entry: "./src/main.js",
// 输出
output: {// 要输出到的位置,绝对路径path: path.resolve(__dirname,"../dist"),// 文件名filename: "state/js/main.js",// 给动态生成的文件命名chunkFilename: "state/js/chunk/[name].js",// 每次打包前自动清空path对应的目录文件clean: true,// 关闭箭头函数输出environment: {arrowFunction: false}
},

然后打包后的文件会自动放在 chunk 文件夹下

image-20230827125051405

16. 文件统一命名

将凡是涉及到文件输出的地方,统一用 [name].文件后缀 的方式来命名,这样webpack会自动将源文件名设置为打包后的文件名

修改后的配置

const path = require("path");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");
const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");function myCssLoader(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: {// 要输出到的位置,绝对路径path: path.resolve(__dirname,"../dist"),// 文件名filename: "state/js/[name].js",// 给动态生成的文件命名chunkFilename: "state/js/chunk/[name].js",// 只要是使用 type:asset 的loader统一设置输出名// hash:10 只取10位哈希值// ext文件后缀名// query 图片文件后面携带的参数assetModuleFilename: "state/asset/[hash:10][ext][query]",// 每次打包前自动清空path对应的目录文件clean: true,// 关闭箭头函数输出environment: {arrowFunction: false}},// 加载器module: {rules: [{oneOf: [{// 用来匹配 .css 结尾的文件test: /\.css$/,// use 数组里面 Loader 执行顺序是从右到左use: myCssLoader(),},{test: /\.less$/i,use: myCssLoader('less-loader')},{test: /\.s[ac]ss$/i,use: myCssLoader('sass-loader')},{test: /\.(png|jpe?g|gif|webp)$/,type: "asset",parser: {dataUrlCondition: {maxSize: 18 * 1024 // 小于10kb的图片会被base64处理}},// generator: {//     // 修改图片文件的输出地址//     // hash:10 只取10位哈希值//     // ext图片后缀名//     // query 图片文件后面携带的参数//     // filename: 'state/images/[hash:10][ext][query]'// }},{test: /\.(ttf|woff2?|mp3|mp4|word)$/,type: "asset/resource",// generator: {//     filename: 'state/resource/[hash:10][ext][query]'// }},{test: /\.m?js$/,exclude: /(node_modules|bower_components)/, // 排除文件use: [// 在需要开启多线程运行的loader前加上 thread-loader"thread-loader",{loader: 'babel-loader',options: {cacheDirectory: true, // 开启babel编译缓存cacheCompression: false, // 缓存文件不要压缩plugins: ["@babel/plugin-transform-runtime"], // 减少代码体积},}]}]}]},// 插件plugins: [// Eslint插件new ESLintPlugin({// 指定文件的根目录context:path.resolve(__dirname, "../src"),threads:true // 开启多线程,默认线程数是cpu核心数-1}),// 处理HTML文件new HtmlWebpackPlugin({template: path.resolve(__dirname, "../index.html"), // 使用的模板地址title: "学习Webpack", // 网站titlehash:true, // 添加哈希值}),// 将CSS处理成单独文件new MiniCssExtractPlugin({filename:"state/css/[name].css",}),],optimization: {minimize: true,minimizer: [// 压缩CSSnew CssMinimizerPlugin({parallel:true,// 开启多线程}),// 当生产模式会默认开启TerserPlugin,但是我们需要进行其他配置,就要重新写了new TerserPlugin({parallel:true}),// 压缩图片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",},},],},],],},},}),]},// 模式mode: "production",devtool: "source-map"
}

17. Preload / Prefetch

为什么

我们前面已经做了代码分割,同时会使用 import 动态导入语法来进行代码按需加载(我们也叫懒加载,比如路由懒加载就是这样实现的)。

但是加载速度还不够好,比如:是用户点击按钮时才加载这个资源的,如果资源体积很大,那么用户会感觉到明显卡顿效果。

我们想在浏览器空闲时间,加载后续需要使用的资源。我们就需要用上 PreloadPrefetch 技术。

是什么

  • Preload:告诉浏览器立即加载资源。
  • Prefetch:告诉浏览器在空闲时才开始加载资源。

它们共同点:

  • 都只会加载资源,并不执行。
  • 都有缓存。

它们区别:

  • Preload加载优先级高,Prefetch加载优先级低。
  • Preload只能加载当前页面需要使用的资源,Prefetch可以加载当前页面资源,也可以加载下一个页面需要使用的资源。

总结:

  • 当前页面优先级高的资源用 Preload 加载。
  • 下一个页面需要使用的资源用 Prefetch 加载。

它们的问题:兼容性较差。

  • 我们可以去 Can I Useopen in new window 网站查询 API 的兼容性问题。
  • Preload 相对于 Prefetch 兼容性好一点。

怎么用

安装

npm i @vue/preload-webpack-plugin -D

配置

const PreloadWebpackPlugin = require("@vue/preload-webpack-plugin");// 插件
plugins: [// 资源懒加载new PreloadWebpackPlugin({//rel: "preload", // preload 兼容性更好as: "script",rel: 'prefetch' // prefetch 兼容性更差})
],

配置完成后,点击按钮加载JS就会从缓存中获取,速度比较快

image-20230827161019364

18. contenthash

为什么

我们通过打包发现,每次打包后的文件都叫 main.js,这样浏览器在加载文件时可能会存在缓存问题,我们可以通过设置让每次打包后都加一个哈希值,这样在加载资源时就不会存在缓存问题

是什么

webpack 设置哈希值有几种方式,它们都会生成一个唯一的 hash 值。

  • fullhash(webpack4 是 hash)

每次修改任何一个文件,所有文件名的 hash 至都将改变。所以一旦修改了任何一个文件,整个项目的文件缓存都将失效。

  • chunkhash

根据不同的入口文件(Entry)进行依赖文件解析、构建对应的 chunk,生成对应的哈希值。我们 js 和 css 是同一个引入,会共享一个 hash 值。

  • contenthash

根据文件内容生成 hash 值,只有文件内容变化了,hash 值才会变化。所有文件 hash 值是独享且不同的。

通过对比,contenthash 最适合我们使用

怎么用

module.exports = {// 入口文件,相对路径entry: "./src/main.js",// 输出output: {// 要输出到的位置,绝对路径path: path.resolve(__dirname,"../dist"),// 文件名filename: "state/js/[name].[contenthash:8].js",// 给动态生成的文件命名chunkFilename: "state/js/chunk/[name].[contenthash:8].js",// 只要是使用 type:asset 的loader统一设置输出名// hash:10 只取10位哈希值// ext文件后缀名// query 图片文件后面携带的参数assetModuleFilename: "state/asset/[hash:10][ext][query]",// 每次打包前自动清空path对应的目录文件clean: true,// 关闭箭头函数输出environment: {arrowFunction: false}},// 插件plugins: [// 将CSS处理成单独文件new MiniCssExtractPlugin({filename:"state/css/[name].[contenthash:8].css",}),],
}

静态资源还是使用 hash:10

19. runtimeChunk

为什么

我们通过上面的配置设置打包哈希值,但是在某些场景下,例如:a 文件依赖了 b文件,当b文件发生变化时只改变b文件的哈希值,不要改变a文件的哈希值。这样做的目的是为了在控制缓存的前提下提高加载效率,而不是只改了一个文件,其他的文件都要发生变化。

是什么

将 hash 值单独保管在一个 runtime 文件中。

我们最终输出三个文件:main、math、runtime。当 math 文件发送变化,变化的是 math 和 runtime 文件,main 不变。

runtime 文件只保存文件的 hash 值和它们与文件关系,整个文件体积就比较小,所以变化重新请求的代价也小。

怎么用

添加如下配置即可

module.exports = {optimization: {// 提取runtime文件runtimeChunk: {name: (entrypoint) => `runtime~${entrypoint.name}`, // runtime文件命名规则},},
}

修改一下 count.js

image-20230827163828575

然后再次打包,通过对比发现,只有count.js和runtime文件的哈希值发生改变,其他均未改变

image-20230827163638656

20. CoreJS

为什么

过去我们使用 babel 对 js 代码进行了兼容性处理,其中使用@babel/preset-env 智能预设来处理兼容性问题。

它能将 ES6 的一些语法进行编译转换,比如箭头函数、点点点运算符等。但是如果是 async 函数、promise 对象、数组的一些方法(includes)等,它没办法处理。

所以此时我们 js 代码仍然存在兼容性问题,一旦遇到低版本浏览器会直接报错。所以我们想要将 js 兼容性问题彻底解决

是什么

core-js 是专门用来做 ES6 以及以上 API 的 polyfill

polyfill翻译过来叫做垫片/补丁。就是用社区上提供的一段代码,让我们在不兼容某些新特性的浏览器上,使用该新特性

怎么用

安装

npm i core-js

使用,修改 babel.config.js

module.exports = {// 智能预设:能够编译ES6语法presets: [["@babel/preset-env",// 按需加载core-js的polyfill{ useBuiltIns: "usage", corejs: { version: "3", proposals: true } },],],
};

21. 优化总结

我们从 4 个角度对 webpack 和代码进行了优化:

  1. 提升开发体验
  • 使用 Source Map 让开发或上线时代码报错能有更加准确的错误提示。
  1. 提升 webpack 提升打包构建速度
  • 使用 HotModuleReplacement 让开发时只重新编译打包更新变化了的代码,不变的代码使用缓存,从而使更新速度更快。
  • 使用 OneOf 让资源文件一旦被某个 loader 处理了,就不会继续遍历了,打包速度更快。
  • 使用 Include/Exclude 排除或只检测某些文件,处理的文件更少,速度更快。
  • 使用 Cache 对 eslint 和 babel 处理的结果进行缓存,让第二次打包速度更快。
  • 使用 Thead 多进程处理 eslint 和 babel 任务,速度更快。(需要注意的是,进程启动通信都有开销的,要在比较多代码处理时使用才有效果)
  1. 减少代码体积
  • 使用 Tree Shaking 剔除了没有使用的多余代码,让代码体积更小。
  • 使用 @babel/plugin-transform-runtime 插件对 babel 进行处理,让辅助代码从中引入,而不是每个文件都生成辅助代码,从而体积更小。
  • 使用 Image Minimizer 对项目中图片进行压缩,体积更小,请求速度更快。(需要注意的是,如果项目中图片都是在线链接,那么就不需要了。本地项目静态图片才需要进行压缩。)
  1. 优化代码运行性能
  • 使用 Code Split 对代码进行分割成多个 js 文件,从而使单个文件体积更小,并行加载 js 速度更快。并通过 import 动态导入语法进行按需加载,从而达到需要使用时才加载该资源,不用时不加载资源。
  • 使用 Preload / Prefetch 对代码进行提前加载,等未来需要使用时就能直接使用,从而用户体验更好。
  • 使用 Network Cache 能对输出资源文件进行更好的命名,将来好做缓存,从而用户体验更好。
  • 使用 Core-js 对 js 进行兼容性处理,让我们代码能运行在低版本浏览器。
  • 使用 PWA 能让代码离线也能访问,从而提升用户体验。

22. 手动搭建Vue脚手架

22.1 项目结构

MyVueCli              
├── config            
│   └── webpack.dev.js
│   └── webpack.prod.js
├── public
│   └── index.html
├── src
│   ├── router
│   │   └── index.js
│   ├── view
│   │   ├── About
│   │   │   └── index.vue
│   │   └── Home
│   │       └── index.vue
│   ├── App.vue
│   └── main.js
├── .eslintrc.js
├── babel.config.js
└── package.json

22.2 开发模式配置

webpack.dev.js

// webpack.dev.js
const path = require("path");
const ESLintWebpackPlugin = require("eslint-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { VueLoaderPlugin } = require("vue-loader");
const { DefinePlugin } = require("webpack");const getStyleLoaders = (preProcessor) => {return ["vue-style-loader","css-loader",{loader: "postcss-loader",options: {postcssOptions: {plugins: ["postcss-preset-env", // 能解决大多数样式兼容性问题],},},},preProcessor,].filter(Boolean);
};module.exports = {entry: "./src/main.js",output: {path: undefined,filename: "static/js/[name].js",chunkFilename: "static/js/[name].chunk.js",assetModuleFilename: "static/js/[hash:10][ext][query]",},module: {rules: [{// 用来匹配 .css 结尾的文件test: /\.css$/,// use 数组里面 Loader 执行顺序是从右到左use: getStyleLoaders(),},{test: /\.less$/,use: getStyleLoaders("less-loader"),},{test: /\.s[ac]ss$/,use: getStyleLoaders("sass-loader"),},{test: /\.(png|jpe?g|gif|svg)$/,type: "asset",parser: {dataUrlCondition: {maxSize: 10 * 1024, // 小于10kb的图片会被base64处理},},},{test: /\.(ttf|woff2?)$/,type: "asset/resource",},// vue-loader不支持oneOf{test: /\.vue$/,loader: "vue-loader", // 内部会给vue文件注入HMR功能代码options: {// 开启缓存cacheDirectory: path.resolve(__dirname,"node_modules/.cache/vue-loader"),},},],},plugins: [new ESLintWebpackPlugin({context: path.resolve(__dirname, "../src"),exclude: "node_modules",cache: true,cacheLocation: path.resolve(__dirname,"../node_modules/.cache/.eslintcache"),}),new HtmlWebpackPlugin({template: path.resolve(__dirname, "../public/index.html"),}),new VueLoaderPlugin(),// 解决页面警告new DefinePlugin({__VUE_OPTIONS_API__: "true",__VUE_PROD_DEVTOOLS__: "false",}),],optimization: {splitChunks: {chunks: "all",},runtimeChunk: {name: (entrypoint) => `runtime~${entrypoint.name}`,},},resolve: {extensions: [".vue", ".js", ".json"], // 自动补全文件扩展名,让vue可以使用},devServer: {open: true,host: "localhost",port: 3000,hot: true,compress: true,historyApiFallback: true, // 解决vue-router刷新404问题},mode: "development",devtool: "cheap-module-source-map",
};

22.3 生产模式配置

webpack.prod.js

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 TerserWebpackPlugin = require("terser-webpack-plugin");
const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
const CopyPlugin = require("copy-webpack-plugin")
const { VueLoaderPlugin } = require("vue-loader");
const { DefinePlugin } = require("webpack");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: {// 打包结果输出的位置path: path.resolve(__dirname,"dist"),// JS文件打包的命名:文件名.哈希值.jsfilename: "static/js/[name].[contenthash:10].js",// 映射文件名chunkFilename: "static/js/[name].[contenthash:10].chunk.js",// type:asset loader的文件生成名assetModuleFilename: "static/asset/[hash:10][ext][query]",// 自动清理上次打包文件clean: true,},module: {rules: [{// 用来匹配 .css 结尾的文件test: /\.css$/,// use 数组里面 Loader 执行顺序是从右到左use: getStyleLoaders(),},{test: /\.less$/,use: getStyleLoaders("less-loader"),},{test: /\.s[ac]ss$/,use: getStyleLoaders("sass-loader"),},{test: /\.(png|jpe?g|gif|svg)$/,type: "asset",parser: {dataUrlCondition: {maxSize: 10 * 1024, // 小于10kb的图片会被base64处理},},},{test: /\.(ttf|woff2?)$/,type: "asset/resource",},{test: /\.(jsx|js)$/,include: path.resolve(__dirname, "../src"),loader: "babel-loader",options: {cacheDirectory: true,cacheCompression: false,plugins: [// "@babel/plugin-transform-runtime" // presets中包含了],},},// vue-loader不支持oneOf{test: /\.vue$/,loader: "vue-loader", // 内部会给vue文件注入HMR功能代码options: {// 开启缓存cacheDirectory: path.resolve(__dirname,"node_modules/.cache/vue-loader"),},},],},plugins: [// Eslintnew ESLintWebpackPlugin({context: path.resolve(__dirname, "../src"),exclude: "node_modules",cache: true,cacheLocation: path.resolve(__dirname,"../node_modules/.cache/.eslintcache"),}),// 自动引入jsnew HtmlWebpackPlugin({template: path.resolve(__dirname, "../public/index.html"),}),// 复制public文件到打包目录中new CopyPlugin({patterns: [{from: path.resolve(__dirname, "../public"),to: path.resolve(__dirname, "../dist"),toType: "dir",noErrorOnMissing: true,globOptions: {ignore: ["**/index.html"],},info: {minimized: true,},},],}),// 将CSS单独放在一个文件中new MiniCssExtractPlugin({filename: "static/css/[name].[contenthash:10].css",chunkFilename: "static/css/[name].[contenthash:10].chunk.css",}),// 处理vuenew VueLoaderPlugin(),// 处理Vue的警告new DefinePlugin({__VUE_OPTIONS_API__: "true",__VUE_PROD_DEVTOOLS__: "false",}),],optimization: {// 压缩的操作minimizer: [// 压缩CSSnew CssMinimizerPlugin(),// 提取重复文件new TerserWebpackPlugin(),// 无损压缩图片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",},// 缓存runtimeChunk: {name: (entrypoint) => `runtime~${entrypoint.name}`,},},// 导入文件自动加上后缀resolve: {extensions: [".vue", ".js", ".json"],},// 模式为开发模式mode: "production",// 报错提示的信息具体到行和列devtool: "source-map",
};

22.4 其他文件代码

.eslintrc.js

module.exports = {root: true,env: {node: true,},extends: ["plugin:vue/vue3-essential", "eslint:recommended"],parserOptions: {parser: "@babel/eslint-parser",},
};

babel.config.js

module.exports = {presets: ["@vue/cli-plugin-babel/preset"],
};

package.json

{"name": "myvuecli","version": "1.0.0","description": "","main": "index.js","scripts": {"dev": "cross-env NODE_ENV=development webpack serve --config ./config/webpack.dev.js","build": "cross-env NODE_ENV=production webpack --config ./config/webpack.prod.js"},"keywords": [],"author": "","license": "ISC","dependencies": {"@babel/eslint-parser": "^7.22.11","vue": "^3.3.4"},"devDependencies": {"@vue/cli-plugin-babel": "^5.0.8","babel-loader": "^9.1.3","cross-env": "^7.0.3","css-loader": "^6.8.1","eslint": "^8.48.0","eslint-plugin-vue": "^9.17.0","eslint-webpack-plugin": "^4.0.1","html-webpack-plugin": "^5.5.3","less": "^3.13.1","less-loader": "^10.2.0","postcss-loader": "^7.3.3","postcss-preset-env": "^9.1.1","sass-loader": "^13.3.2","vue-loader": "^17.2.2","vue-router": "^4.2.4","vue-style-loader": "^4.1.3","vue-template-compiler": "^2.7.14","webpack": "^5.88.2","webpack-cli": "^5.1.4","webpack-dev-server": "^4.15.1"}
}

main.js

import {createApp} from "vue";
import App from "./App";
import router from "./router"const app = createApp(App);
app.use(router)
app.mount(document.getElementById("app"))

router/index.js

import {createRouter,createWebHashHistory} from "vue-router";export default createRouter({history:createWebHashHistory(),routes:[{path:"/",redirect:"/home"},{path:"/home",component:()=> import("../view/Home")},{path:"/about",component:()=> import("../view/About")}]
})

App.vue

<template><ul><li><router-link to="/home">Home</router-link></li><li><router-link to="/about">About</router-link></li></ul><router-view/>
</template><script setup></script><style scoped lang="less">
ul{display: flex;flex-direction: column;gap: 15px;margin: 0;padding: 0;li{margin-left: 0;list-style: none;line-height: 35px;height: 35px;background-color: pink;}
}
</style>

22.5 测试运行

运行测试

npm run dev

image-20230827211045139

运行效果

image-20230827211057675

打包测试

npm run build

image-20230827222309795

打包后的文件

image-20230827222407506

运行没问题

image-20230827222437558

22.6 合并配置

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 TerserWebpackPlugin = require("terser-webpack-plugin");
const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
const CopyPlugin = require("copy-webpack-plugin")
const { VueLoaderPlugin } = require("vue-loader");
const { DefinePlugin } = require("webpack");const isProduction = process.env.NODE_ENV === "production";const getStyleLoaders = (preProcessor) => {return [isProduction ? MiniCssExtractPlugin.loader : "vue-style-loader","css-loader",{loader: "postcss-loader",options: {postcssOptions: {plugins: ["postcss-preset-env", // 能解决大多数样式兼容性问题],},},},preProcessor,].filter(Boolean);
};module.exports = {// 入口文件entry: "./src/main.js",output: {// 打包结果输出的位置path: isProduction ? path.resolve(__dirname,"../dist") : undefined,// JS文件打包的命名:文件名.哈希值.jsfilename: isProduction ? "static/js/[name].[contenthash:10].js" : "static/js/[name].js",// 映射文件名chunkFilename:isProduction ? "static/js/[name].[contenthash:10].chunk.js" : "static/js/[name].chunk.js",// type:asset loader的文件生成名assetModuleFilename: "static/asset/[hash:10][ext][query]",// 自动清理上次打包文件clean: true,},module: {rules: [{// 用来匹配 .css 结尾的文件test: /\.css$/,// use 数组里面 Loader 执行顺序是从右到左use: getStyleLoaders(),},{test: /\.less$/,use: getStyleLoaders("less-loader"),},{test: /\.s[ac]ss$/,use: getStyleLoaders("sass-loader"),},{test: /\.(png|jpe?g|gif|svg)$/,type: "asset",parser: {dataUrlCondition: {maxSize: 10 * 1024, // 小于10kb的图片会被base64处理},},},{test: /\.(ttf|woff2?)$/,type: "asset/resource",},{test: /\.(jsx|js)$/,include: path.resolve(__dirname, "../src"),loader: "babel-loader",options: {cacheDirectory: true,cacheCompression: false,plugins: [// "@babel/plugin-transform-runtime" // presets中包含了],},},// vue-loader不支持oneOf{test: /\.vue$/,loader: "vue-loader", // 内部会给vue文件注入HMR功能代码options: {// 开启缓存cacheDirectory: path.resolve(__dirname,"node_modules/.cache/vue-loader"),},},],},plugins: [// Eslintnew ESLintWebpackPlugin({context: path.resolve(__dirname, "../src"),exclude: "node_modules",cache: true,cacheLocation: path.resolve(__dirname,"../node_modules/.cache/.eslintcache"),}),// 自动引入jsnew HtmlWebpackPlugin({template: path.resolve(__dirname, "../public/index.html"),}),// 复制public文件到打包目录中isProduction && new CopyPlugin({patterns: [{from: path.resolve(__dirname, "../public"),to: path.resolve(__dirname, "../dist"),toType: "dir",noErrorOnMissing: true,globOptions: {ignore: ["**/index.html"],},info: {minimized: true,},},],}),// 将CSS单独放在一个文件中isProduction && new MiniCssExtractPlugin({filename: "static/css/[name].[contenthash:10].css",chunkFilename: "static/css/[name].[contenthash:10].chunk.css",}),// 处理vuenew VueLoaderPlugin(),// 处理Vue的警告new DefinePlugin({__VUE_OPTIONS_API__: "true",__VUE_PROD_DEVTOOLS__: "false",}),].filter(Boolean),optimization: {minimize: isProduction,// 压缩的操作minimizer: [// 压缩CSSnew CssMinimizerPlugin(),// 提取重复文件new TerserWebpackPlugin(),// 无损压缩图片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",},// 缓存runtimeChunk: {name: (entrypoint) => `runtime~${entrypoint.name}`,},},// 导入文件自动加上后缀resolve: {extensions: [".vue", ".js", ".json"],},// 模式为开发模式mode: isProduction ? "production" : "development",// 报错提示的信息具体到行和列devtool: isProduction ? "source-map" : "cheap-module-source-map",// 运行devServer: {open: true,host: "localhost",port: 3000,hot: true,compress: true,historyApiFallback: true, // 解决vue-router刷新404问题},
};

23. ElementPlus按需导入

安装 element-plus

npm install element-plus --save

安装按需导入的依赖

npm install -D unplugin-vue-components unplugin-auto-import

配置

// webpack.config.js
const AutoImport = require('unplugin-auto-import/webpack')
const Components = require('unplugin-vue-components/webpack')
const { ElementPlusResolver } = require('unplugin-vue-components/resolvers')module.exports = {// ...plugins: [AutoImport({resolvers: [ElementPlusResolver()],}),Components({resolvers: [ElementPlusResolver()],}),],
}

然后页面中直接使用即可

<template><div>Home</div><el-button type="primary">按钮</el-button>
</template>

image-20230828220543058

24. loader原理

24.1 loader概念

帮助 webpack 将不同类型的文件转换为 webpack 可识别的模块。

24.2 loader的执行顺序

  1. 分类
  • pre: 前置 loader
  • normal: 普通 loader
  • inline: 内联 loader
  • post: 后置 loader
  1. 执行顺序
  • 4 类 loader 的执行优级为:pre > normal > inline > post
  • 相同优先级的 loader 执行顺序为:从右到左,从下到上

例如:

// 此时loader执行顺序:loader3 - loader2 - loader1
module: {rules: [{test: /\.js$/,loader: "loader1",},{test: /\.js$/,loader: "loader2",},{test: /\.js$/,loader: "loader3",},],
},
// 此时loader执行顺序:loader1 - loader2 - loader3
module: {rules: [{enforce: "pre",test: /\.js$/,loader: "loader1",},{// 没有enforce就是normaltest: /\.js$/,loader: "loader2",},{enforce: "post",test: /\.js$/,loader: "loader3",},],
},

24.3 自定义loader

初始化项目结构

MyLoader
├── js
│   └── main.js
├── loader
│   └── test-loader.js
├── src
│   └── index.html
├── package.json
└── webpack.config.js

main.js

console.log("hello word")

编写一个简单的loader,test-loader.js 代码如下

module.exports = function (content){console.log("loader打印内容",content)return content
}

使用

// webpack.config.jsconst path = require("path")
const HtmlWebpackPlugins = require("html-webpack-plugin")
module.exports = {entry: "./js/main.js",output: {path: path.resolve(__dirname,"dist"),filename: "js/[name].[hash:10].js",clean: true},module: {rules: [{test: /\.js$/,loader: "./loader/test-loader.js"}]},plugins: [new HtmlWebpackPlugins({template:path.resolve(__dirname,"./src/index.html")})],mode: "development"
}

然后安装依赖

npm install webpack webpack-cli html-webpack-plugin -D

然后执行打包命令

npx webpack

观察输出

image-20230831224039986

总结:

loader 接收要处理的文件的源码,在loader内部处理完毕后再返回出去

24.4 同步loader和异步loader

新建同步loader

// loader/async/loader1.jsmodule.exports = function (content,map,meta){console.log("同步loader打印")console.log(meta)/*** 第一个参数:错误信息,没有就是null,有的话传错误信息* content:要处理的源文件信息* map:继续传递source-map* meta:给下一个loader传递参数*/this.callback(null,content,map,meta)
}

新建异步loader

// loader/async/loader2.jsmodule.exports = function (content,map,meta){console.log("异步loader打印")let callback = this.async()/*** 第一个参数:错误信息,没有就是null,有的话传错误信息* content:要处理的源文件信息* map:继续传递source-map* meta:给下一个loader传递参数*/setTimeout(()=>{callback(null,content,map, {name:"我是来自异步loader的参数"})},2000)
}

使用

const path = require("path")
const HtmlWebpackPlugins = require("html-webpack-plugin")
module.exports = {entry: "./js/main.js",output: {path: path.resolve(__dirname,"dist"),filename: "js/[name].[hash:10].js",clean: true},module: {rules: [// {//     test: /\.js$/,//     loader: "./loader/test-loader.js"// }{test:/\.js$/,use: ["./loader/async/loader1.js","./loader/async/loader2.js",]}]},plugins: [new HtmlWebpackPlugins({template:path.resolve(__dirname,"./src/index.html")})],mode: "development"
}

打包输出结果

image-20230831230039876

24.5 raw loader

raw loader接收到的文件时Buffer数据,一般用来处理图片,字体等文件

定义 raw-loader

// loader/raw/raw-loader.jsmodule.exports = function (content){console.log(content)return content
}// 暴露raw为true,表示这个是raw loader
module.exports.raw = true

使用

const path = require("path")
const HtmlWebpackPlugins = require("html-webpack-plugin")
module.exports = {entry: "./js/main.js",output: {path: path.resolve(__dirname,"dist"),filename: "js/[name].[hash:10].js",clean: true},module: {rules: [// {//     test: /\.js$/,//     loader: "./loader/test-loader.js"// }// {//     test:/\.js$/,//     use: [//         "./loader/async/loader1.js",//         "./loader/async/loader2.js",//     ]// }{test: /\.js$/,loader: "./loader/raw/raw-loader.js"}]},plugins: [new HtmlWebpackPlugins({template:path.resolve(__dirname,"./src/index.html")})],mode: "development"
}

查看输出

image-20230831230842737

24.6 pitch loader

pitch loader会在 normal 之前执行,并且当存在多个 pitch loader 时执行的循序和normal loader循序相反,从左往右,从上到下

定义 pitch loader1

// loader/pitch/pitch1.jsmodule.exports = function (content){console.log("pitch-normal-1",content)return content
}module.exports.pitch = function (){console.log("pitch1")
}

定义 pitch loader2

// loader/pitch/pitch1.jsmodule.exports = function (content){console.log("pitch-normal-2",content)return content
}module.exports.pitch = function (){console.log("pitch2")
}

使用

const path = require("path")
const HtmlWebpackPlugins = require("html-webpack-plugin")
module.exports = {entry: "./js/main.js",output: {path: path.resolve(__dirname,"dist"),filename: "js/[name].[hash:10].js",clean: true},module: {rules: [// {//     test: /\.js$/,//     loader: "./loader/test-loader.js"// }// {//     test:/\.js$/,//     use: [//         "./loader/async/loader1.js",//         "./loader/async/loader2.js",//     ]// }// {//     test: /\.js$/,//     loader: "./loader/raw/raw-loader.js"// }{test:/\.js$/,use:["./loader/pitch/pitch1.js","./loader/pitch/pitch2.js",]}]},plugins: [new HtmlWebpackPlugins({template:path.resolve(__dirname,"./src/index.html")})],mode: "development"
}

执行打包输出结果

image-20230902095630023

通过结果可以看到先执行 pitch1 和 pitch2 方法,再接着执行 normal loader 方法

当在一个 pitch loader 中添加的 return 时,会终止后面的 pitch 方法和 normal loader 方法,然后把之前的 normal loader 方法执行完

例如,再新建一个 pitch3

module.exports = function (content){console.log("pitch-normal-3",content)return content
}module.exports.pitch = function (){console.log("pitch3")
}

然后修改 pitch2

module.exports = function (content){console.log("pitch-normal-2",content)return content
}module.exports.pitch = function (){console.log("pitch2")return "pitch2"
}

修改配置


module.exports = {module: {rules: [{test:/\.js$/,use:["./loader/pitch/pitch1.js","./loader/pitch/pitch2.js","./loader/pitch/pitch3.js",]}]},
}

执行打包效果

image-20230902100354552

image-20230902100748081

执行流程图如上

24.7. loader Api

方法名含义用法
this.async异步回调 loader。返回 this.callbackconst callback = this.async()
this.callback可以同步或者异步调用的并返回多个结果的函数this.callback(err, content, sourceMap?, meta?)
this.getOptions(schema)获取 loader 的 optionsthis.getOptions(schema)
this.emitFile产生一个文件this.emitFile(name, content, sourceMap)
this.utils.contextify返回一个相对路径this.utils.contextify(context, request)
this.utils.absolutify返回一个绝对路径this.utils.absolutify(context, request)

更多文档,请查阅 webpack 官方 loader api 文档

25. 自实现loader

25.1 clear-log-loader

在项目开发阶段我们会使用 console.log 打印信息,但是到生产环境后我们不需要这个 log 打印。所以可以通过 loader 在打包时将 console.log 去掉

新建 clear-log-loader.js

module.exports = function (content){return content.replace(/console\.log\(.*\);?/g,"")
}

使用


module.exports = {module: {rules: [{test:/\.js$/,loader: "./loader/clear-log-loader.js"}]},
}

25.2 给loader添加options参数

上面的 loader 中我们通过 option 添加一个 enable 属性,俩控制是否清除 log

首先在 loader 中添加 enable 配置


module.exports = {module: {rules: [{test:/\.js$/,loader: "./loader/clear-log-loader.js",options: {enable:false}}]},
}

修改 clear-log-loader.js

// 定义loader options属性的规则约束
const schema = {// options的类型是一个objecttype:"object",// 定义options中的属性有哪些properties:{// 有enable属性enable:{// enable属性是一个布尔值type:"boolean"}},// 不允许使用未定义的属性additionalProperties:false
}module.exports = function (content){// this.getOptions 获取配置项let {enable} = this.getOptions(schema)return enable ? content.replace(/console\.log\(.*\);?/g,"") : content
}

这个时候我们配置的enable值是false,所以打包后文件是有log的

image-20230902111210143

我们改成 true,再次打包,log就没有了

image-20230902111245817

25.3 自实现babel-loader

babel 官网:Babel 中文文档 | Babel中文网 · Babel 中文文档 | Babel中文网 (babeljs.cn)

将代码转成ES5语法

安装

npm i @babel/core @babel/preset-env -D

新建 babel-loader.js

const schema = {"type": "object","properties": {"presets": {"type": "array"}},"additionalProperties": true
}
const babel = require("@babel/core");module.exports = function (content) {const options = this.getOptions(schema);// 使用异步loaderconst callback = this.async();// 使用babel对js代码进行编译babel.transform(content, options, function (err, result) {callback(err, result.code);});
};

wabpack配置

rules:[{test:/\.js$/,loader: "./loader/babel-loader.js",options: {presets:["@babel/preset-env"],}}
]

25.4 file-loader

将文件原封不动的输出

安装

npm i loader-utils -D

file-loader

const loaderUtils = require("loader-utils")module.exports = function (content){// 根据文件内容自动生成文件名const interpolatedName = loaderUtils.interpolateName(this,"asset/[name].[hash].[ext]",{// 文件内容content:content})// 将文件输出出去this.emitFile(interpolatedName,content)// 返回 module.export = "文件路径/文件名"return `module.exports = "${interpolatedName}"`
}module.exports.raw = true

使用

rules:[{test:/\.(png|jpg)$/,loader: "./loader/file-loader.js",type:"javascript/auto" // 阻止默认的asset打包配置}
]

26. Plugin原理

Plugin 的作用

通过插件我们可以扩展 webpack,加入自定义的构建行为,使 webpack 可以执行更广泛的任务,拥有更强的构建能力。

Plugin 工作原理

webpack 就像一条生产线,要经过一系列处理流程后才能将源文件转换成输出结果。 这条生产线上的每个处理流程的职责都是单一的,多个流程之间有存在依赖关系,只有完成当前处理后才能交给下一个流程去处理。 插件就像是一个插入到生产线中的一个功能,在特定的时机对生产线上的资源做处理。webpack 通过 Tapable 来组织这条复杂的生产线。 webpack 在运行过程中会广播事件,插件只需要监听它所关心的事件,就能加入到这条生产线中,去改变生产线的运作。 webpack 的事件流机制保证了插件的有序性,使得整个系统扩展性很好。 ——「深入浅出 Webpack」

站在代码逻辑的角度就是:webpack 在编译代码过程中,会触发一系列 Tapable 钩子事件,插件所做的,就是找到相应的钩子,往上面挂上自己的任务,也就是注册事件,这样,当 webpack 构建的时候,插件注册的事件就会随着钩子的触发而执行了。

Webpack 内部的钩子

什么是钩子

钩子的本质就是:事件。为了方便我们直接介入和控制编译过程,webpack 把编译过程中触发的各类关键事件封装成事件接口暴露了出来。这些接口被很形象地称做:hooks(钩子)。开发插件,离不开这些钩子。

Tapable

Tapable 为 webpack 提供了统一的插件接口(钩子)类型定义,它是 webpack 的核心功能库。webpack 中目前有十种 hooks,在 Tapable 源码中可以看到,他们是:

// https://github.com/webpack/tapable/blob/master/lib/index.js
exports.SyncHook = require("./SyncHook");
exports.SyncBailHook = require("./SyncBailHook");
exports.SyncWaterfallHook = require("./SyncWaterfallHook");
exports.SyncLoopHook = require("./SyncLoopHook");
exports.AsyncParallelHook = require("./AsyncParallelHook");
exports.AsyncParallelBailHook = require("./AsyncParallelBailHook");
exports.AsyncSeriesHook = require("./AsyncSeriesHook");
exports.AsyncSeriesBailHook = require("./AsyncSeriesBailHook");
exports.AsyncSeriesLoopHook = require("./AsyncSeriesLoopHook");
exports.AsyncSeriesWaterfallHook = require("./AsyncSeriesWaterfallHook");
exports.HookMap = require("./HookMap");
exports.MultiHook = require("./MultiHook");

Tapable 还统一暴露了三个方法给插件,用于注入不同类型的自定义构建行为:

  • tap:可以注册同步钩子和异步钩子。
  • tapAsync:回调方式注册异步钩子。
  • tapPromise:Promise 方式注册异步钩子。

Plugin 构建对象

Compiler

compiler 对象中保存着完整的 Webpack 环境配置,每次启动 webpack 构建时它都是一个独一无二,仅仅会创建一次的对象。

这个对象会在首次启动 Webpack 时创建,我们可以通过 compiler 对象上访问到 Webapck 的主环境配置,比如 loader 、 plugin 等等配置信息。

它有以下主要属性:

  • compiler.options 可以访问本次启动 webpack 时候所有的配置文件,包括但不限于 loaders 、 entry 、 output 、 plugin 等等完整配置信息。
  • compiler.inputFileSystemcompiler.outputFileSystem 可以进行文件操作,相当于 Nodejs 中 fs。
  • compiler.hooks 可以注册 tapable 的不同种类 Hook,从而可以在 compiler 生命周期中植入不同的逻辑。

compiler hooks 文档open in new window

Compilation

compilation 对象代表一次资源的构建,compilation 实例能够访问所有的模块和它们的依赖。

一个 compilation 对象会对构建依赖图中所有模块,进行编译。 在编译阶段,模块会被加载(load)、封存(seal)、优化(optimize)、 分块(chunk)、哈希(hash)和重新创建(restore)。

它有以下主要属性:

  • compilation.modules 可以访问所有模块,打包的每一个文件都是一个模块。
  • compilation.chunks chunk 即是多个 modules 组成而来的一个代码块。入口文件引入的资源组成一个 chunk,通过代码分割的模块又是另外的 chunk。
  • compilation.assets 可以访问本次打包生成所有文件的结果。
  • compilation.hooks 可以注册 tapable 的不同种类 Hook,用于在 compilation 编译模块阶段进行逻辑添加以及修改。

compilation hooks 文档open in new window

生命周期简图

27. 自实现Plugin

27.1 TestPlugin

plugin都是一个构造函数,所以我们采用es6的class语法来自定义Plugin

webpack 在打包过程中触发 plugin 中的 apply 方法

class TestPlugin{constructor() {console.log("TestPlugin constructor")}apply(){console.log("TestPlugin apply")}
}module.exports = TestPlugin

使用

const path = require("path")
const HtmlWebpackPlugins = require("html-webpack-plugin")
const TestPlugin = require("./plugins/test-plugin")module.exports = {plugins: [new HtmlWebpackPlugins({template:path.resolve(__dirname,"./src/index.html")}),new TestPlugin()],mode: "development"
}

打包的时候会先输出plugin中的内容

image-20230902173132814

27.2 注册hook

class TestPlugin{constructor() {console.log("TestPlugin constructor")}apply(compiler){console.log("TestPlugin apply")// 从文档可知, compile hook 是 SyncHook, 也就是同步钩子, 只能用tap注册compiler.hooks.compile.tap("TestPlugin", (compilationParams) => {console.log("compiler.compile()");});// 从文档可知, make 是 AsyncParallelHook, 也就是异步并行钩子, 特点就是异步任务同时执行// 可以使用 tap、tapAsync、tapPromise 注册。// 如果使用tap注册的话,进行异步操作是不会等待异步操作执行完成的。compiler.hooks.make.tap("TestPlugin", (compilation) => {setTimeout(() => {console.log("compiler.make() 111");}, 2000);});// 使用tapAsync、tapPromise注册,进行异步操作会等异步操作做完再继续往下执行compiler.hooks.make.tapAsync("TestPlugin", (compilation, callback) => {setTimeout(() => {console.log("compiler.make() 222");// 必须调用callback();}, 1000);});compiler.hooks.make.tapPromise("TestPlugin", (compilation) => {console.log("compiler.make() 333");// 必须返回promisereturn new Promise((resolve) => {resolve();});});// 从文档可知, emit 是 AsyncSeriesHook, 也就是异步串行钩子,特点就是异步任务顺序执行compiler.hooks.emit.tapAsync("TestPlugin", (compilation, callback) => {setTimeout(() => {console.log("compiler.emit() 111");callback();}, 3000);});compiler.hooks.emit.tapAsync("TestPlugin", (compilation, callback) => {setTimeout(() => {console.log("compiler.emit() 222");callback();}, 2000);});compiler.hooks.emit.tapAsync("TestPlugin", (compilation, callback) => {setTimeout(() => {console.log("compiler.emit() 333");callback();}, 1000);});}
}module.exports = TestPlugin

image-20230902210225146

27.3 添加node调试命令

在plugin中添加 debugger

然后添加 package.json 添加调试启动命令

"scripts": {"debug": "node --inspect-brk ./node_modules/webpack-cli/bin/cli.js"
},

运行

npm run debug

然后随便一个页面打开浏览器的控制台,点击 node 图标

image-20230902212955581

点击后会跳转一个新页面,默认是在程序第一行打断点,我们点击下一步跳转到下一个断点即可跳转到我们打断点的位置

image-20230902213024409

27.4 BannerWebpackPlugin

作用,给打包生成的文件添加作者信息

class BannerWebpackPlugin{constructor(options) {this.options = options;}apply(compile){compile.hooks.emit.tapAsync("BannerWebpackPlugin",(compilation,callback)=>{// 得到将要输出的文件,只获取JS和CSSconst assetPaths = Object.keys(compilation.assets).filter(assetsFile=>{let extensions  = ["js","css"]let splits = assetsFile.split(".")let splitEd = splits[splits.length - 1]return extensions.includes(splitEd)})debuggerassetPaths.forEach(path=>{// 得到文件本身const asset = compilation.assets[path]
// 要添加的注释信息
const newSource = `/*** author:${this.options.auther}*/${asset.source()}
`// 覆盖资源compilation.assets[path] = {// 返回新的资源source(){return newSource},// 返回新的大小size(){return newSource.length}}})callback()})}
}module.exports = BannerWebpackPlugin

使用

const path = require("path")
const HtmlWebpackPlugins = require("html-webpack-plugin")
const BannerWebpackPlugin = require("./plugins/banner-webpack-plugin")module.exports = {entry: "./js/main.js",plugins: [new HtmlWebpackPlugins({template:path.resolve(__dirname,"./src/index.html")}),new BannerWebpackPlugin({auther:"SongZX"})],mode: "production"
}

打包后的效果

image-20230902230032368

27.5 CleanWebpackPlugin

打包前删除旧的打包文件

添加 clean-webpack-plugin.js

class CleanWebpackPlugin{apply(compiler) {// 获取打包输出的目的地址let outputPath = compiler.options.output.path;// 获取操作文件的对象let fs = compiler.outputFileSystem;// 注册钩子,在文件输出前执行删除旧文件操作compiler.hooks.emit.tap("CleanWebpackPlugin",(compilation)=>{// 通过fs删除打包目录下的所有文件this.removeFiles(fs,outputPath)})}removeFiles(fs,filePath){// 读取指定文件夹下的文件内容,得到的是一个数组let files = fs.readdirSync(filePath);// 遍历文件一个个删除files.forEach(fileName=>{// 得到文件全路径let path = `${filePath}/${fileName}`// 判断是否是文件夹let stats = fs.statSync(path);// isDirectory方法用来判断是否是文件夹if(stats.isDirectory()){this.removeFiles(fs,path)}else{fs.unlinkSync(path)}})}
}module.exports = CleanWebpackPlugin

使用

const path = require("path")
const HtmlWebpackPlugins = require("html-webpack-plugin")
const TestPlugin = require("./plugins/test-plugin")
const BannerWebpackPlugin = require("./plugins/banner-webpack-plugin")
const CleanWebpackPlugin = require("./plugins/clean-webpack-plugin")module.exports = {entry: "./js/main.js",output: {path: path.resolve(__dirname,"dist"),filename: "js/[name].[hash:10].js",clean: false, // 关闭默认的删除文件assetModuleFilename: "asset/[name].[hash:10][ext]"},plugins: [new HtmlWebpackPlugins({template:path.resolve(__dirname,"./src/index.html")}),// new TestPlugin()new BannerWebpackPlugin({auther:"SongZX"}),// 使用自定义的删除文件插件new CleanWebpackPlugin()],mode: "production"
}

27.6 AnalyzeWebpackPlugin

在打包时自动生成一个分析文件大小的 md 文件

新建 analyze-webpack-plugin.js

class AnalyzeWebpackPlugin{apply(compiler){compiler.hooks.emit.tap("AnalyzeWebpackPlugin",(compilation)=>{// 获取即将输出的文件let assets = Object.entries(compilation.assets)let content = `| 文件名称 | 文件大小 |
| ------ | ------ |`assets.forEach(([fileName,file])=>{content += `\n|${fileName}|${Math.ceil(file.size()/1024)}kb|`})// 覆盖资源compilation.assets['analyze.md'] = {// 返回新的资源source(){return content},// 返回新的大小size(){return content.length}}})}
}
module.exports = AnalyzeWebpackPlugin

使用

const path = require("path")
const HtmlWebpackPlugins = require("html-webpack-plugin")
const TestPlugin = require("./plugins/test-plugin")
const BannerWebpackPlugin = require("./plugins/banner-webpack-plugin")
const CleanWebpackPlugin = require("./plugins/clean-webpack-plugin")
const AnalyzeWebpackPlugin = require("./plugins/analyze-webpack-plugin")module.exports = {entry: "./js/main.js",output: {path: path.resolve(__dirname,"dist"),filename: "js/[name].[hash:10].js",clean: false,assetModuleFilename: "asset/[name].[hash:10][ext]"},plugins: [new HtmlWebpackPlugins({template:path.resolve(__dirname,"./src/index.html")}),// new TestPlugin()new BannerWebpackPlugin({auther:"SongZX"}),new CleanWebpackPlugin(),new AnalyzeWebpackPlugin()],mode: "production"
}

效果

image-20230903144121290

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/69500.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

[数据集][目标检测]裸土识别裸土未覆盖目标检测数据集VOC格式857张2类别

数据集格式&#xff1a;Pascal VOC格式(不包含分割路径的txt文件和yolo格式的txt文件&#xff0c;仅仅包含jpg图片和对应的xml) 图片数量(jpg文件个数)&#xff1a;857 标注数量(xml文件个数)&#xff1a;857 标注类别数&#xff1a;2 标注类别名称:["luotu","n…

【小沐学Unity3d】3ds Max 骨骼动画制作(CAT、Character Studio、Biped、骨骼对象)

文章目录 1、简介2、 CAT2.1 加载 CATRig 预设库2.2 从头开始创建 CATRig 3、character studio3.1 基本描述3.2 Biped3.3 Physique 4、骨骼系统4.1 创建方法4.2 简单示例 结语 1、简介 官网地址&#xff1a; https://help.autodesk.com/view/3DSMAX/2018/CHS https://help.aut…

2021年09月 C/C++(六级)真题解析#中国电子学会#全国青少年软件编程等级考试

C/C++编程(1~8级)全部真题・点这里 第1题:双端队列 定义一个双端队列,进队操作与普通队列一样,从队尾进入。出队操作既可以从队头,也可以从队尾。编程实现这个数据结构。 时间限制:1000 内存限制:65535 输入 第一行输入一个整数t,代表测试数据的组数。 每组数据的第一…

【广州华锐互动】煤矿设备AR远程巡检系统实现对井下作业的远程监控和管理

煤矿井下作业环境复杂&#xff0c;安全隐患较多。传统的巡检方式存在诸多弊端&#xff0c;如巡检人员难以全面了解井下情况&#xff0c;巡检效率低下&#xff0c;安全隐患难以及时发现和整改等。为了解决这些问题&#xff0c;提高煤矿安全生产水平&#xff0c;越来越多的企业开…

DAY01_瑞吉外卖——软件开发整体介绍瑞吉外卖项目介绍开发环境搭建后台系统登录功能后台系统退出功能

目录 1. 软件开发整体介绍1.1 软件开发流程1.2 角色分工1.3 软件环境 2. 瑞吉外卖项目介绍2.1 项目介绍2.2 产品原型2.3 技术选型2.4 功能架构2.5 角色 3. 开发环境搭建3.1 数据库环境搭建3.1.1 创建数据库3.1.2 数据库表导入3.1.3 数据库表介绍 3.2 Maven项目搭建3.2.1 创建ma…

Elsaticsearch倒排索引

搜索引擎应该具有什么要求&#xff1f; 查询快 高效的压缩算法 快速的编码和解码速度 结果准确 BM25 TF-IDF 检索结果丰富 召回率 面向海量数据&#xff0c;如何达到搜索引擎级别的查询效率&#xff1f; 索引 帮助快速检索以数据结构为载体以文件形式落地 倒排…

Ubuntu18.04安装docker-io

1. 安装docker 1.1 网上一搜&#xff0c;全是更新仓库、下载依赖、添加docker的gpg密钥、添加docker仓库、安装docker-ce的步骤&#xff0c;但是在安装docker-ce时却提示“package "docker-ce" has no installation candidate”&#xff0c;就很迷。 1.2 安装docke…

webpack打包常用配置项

webpack打包配置项 参考链接 文件结构&#xff1a;最基础版 先安装 npm i webpack webpack-cli --dev 运行命令&#xff1a;npx webpack 进行打包 1. 配置webpack.config.js文件&#xff1a; const path require(path); module.exports {mode: development, // 开发环境 …

Python 实现单例模式的五种写法!

单例模式&#xff08;Singleton Pattern&#xff09; 是一种常用的软件设计模式&#xff0c;该模式的主要目的是确保某一个类只有一个实例存在。当你希望在整个系统中&#xff0c;某个类只能出现一个实例时&#xff0c;单例对象就能派上用场。 比如&#xff0c;某个服务器程序的…

【ccf-csp题解】第1次csp认证-第三题-命令行选项-题解

题目描述 思路讲解 本题是一个简单的字符串模拟题&#xff0c;这种题目是csp认证第三题的常客 大致思路是用两个bool数组记录某一个选项&#xff08;0--25下标对应小写字母a--z&#xff09;&#xff0c;第一个数组中无参选项为true&#xff0c;第二个数组中有参选项为true&a…

K8S的CKA考试环境和题目

CKA考试这几年来虽然版本在升级&#xff0c;但题目一直没有大的变化&#xff0c;通过K8S考试的方法就是在模拟环境上反复练习&#xff0c;通过练习熟悉考试环境和考试过程中可能遇到的坑。这里姚远老师详细向大家介绍一下考试的环境和题目&#xff0c;需要详细资料的同学请在文…

Tomcat多实例和负载均衡动静分离

一、Tomcat多实例部署 安装jdk 设置jdk环境变量 安装tomcat 配置Tomcat环境变量 修改端口号 修改tomcat中startup.sh和shutdown.sh文件添加tomcat环境变量 启动Tomcat中的startup.sh 浏览器测试 http://192.168.30.100:8080 http://192.168.30.100:8081 二、负载均衡动静分离…

linux并发服务器 —— IO多路复用(八)

半关闭、端口复用 半关闭只能实现数据单方向的传输&#xff1b;当TCP 接中A向 B 发送 FIN 请求关闭&#xff0c;另一端 B 回应ACK 之后 (A 端进入 FIN_WAIT_2 状态)&#xff0c;并没有立即发送 FIN 给 A&#xff0c;A 方处于半连接状态 (半开关)&#xff0c;此时 A 可以接收 B…

你知道用Woof创建的Linux吗?

Quirky 8.2 已发布&#xff0c;它是 Puppy Linux 的姊妹项目&#xff0c;是用一份叫 Woof 的定制工具创建的 Linux 发行。 新版本 Quirky 8.2 运行在 64 位的 x86 计算机上&#xff0c;主要提供了针对以前的 8.x 版本的增量改进。 Quirky Linux 8.2 x86_64 的代号是Xerus&…

Python爬虫:下载小红书无水印图片、视频

该代码只提供学习使用&#xff0c;该项目是基于https://github.com/JoeanAmier/XHS_Downloader的小改动 1.下载项目 git clone https://github.com/zhouayi/XHS_Downloader.git2.找到需要下载的文章的ID 写入main.py中 3.下载 python main.py最近很火的莲花楼为例<嘿嘿…

LeetCode 周赛上分之旅 #44 同余前缀和问题与经典倍增 LCA 算法

⭐️ 本文已收录到 AndroidFamily&#xff0c;技术和职场问题&#xff0c;请关注公众号 [彭旭锐] 和 BaguTree Pro 知识星球提问。 学习数据结构与算法的关键在于掌握问题背后的算法思维框架&#xff0c;你的思考越抽象&#xff0c;它能覆盖的问题域就越广&#xff0c;理解难度…

成都瀚网科技有限公司:抖店怎么开通直播?

随着互联网和移动支付的快速发展&#xff0c;越来越多的人选择开设自己的抖音商店。抖音作为国内最受欢迎的短视频平台之一&#xff0c;拥有庞大的用户基础&#xff0c;成为众多创业者青睐的平台。那么&#xff0c;如何经营自己的抖音店铺呢&#xff1f;下面将从几个方面为您介…

Si24R2F+畜牧 耳标测体温开发资料

Si24R2F是针对IOT应用领域推出的新款超低功耗2.4G内置NVM单发射芯片。广泛应用于2.4G有源活体动物耳标&#xff0c;带实时测温计步功能。相较于Si24R2E&#xff0c;Si24R2F增加了温度监控、自动唤醒间隔功能&#xff1b;发射功率由7dBm增加到12dBm&#xff0c;距离更远&#xf…

k8s 搭建基于session模式的flink集群

1.flink集群搭建 不废话直接上代码&#xff0c;都是基于官网的&#xff0c;在此记录一下 Kubernetes | Apache Flink flink-configuration-configmap.yaml apiVersion: v1 kind: ConfigMap metadata:name: flink-configlabels:app: flink data:flink-conf.yaml: |jobmanager…

postgresql-条件表达式

postgresql-条件表达式 简单Case表达式搜索Case表达式缩写函数总结 简单Case表达式 select e.first_name , e.last_name , e.department_id , case e.department_id when 90 then 管理when 60 then 开发else 其他end as "部门" from cps.public.employees e ;-- 统…