webpack5 新特性
webpack5 已经发布,将主要涉及的新特性及这些特性的使用方法总结了一下。
英文文档地址:webpack
中文文档地址:webpack.docschina
github 地址:github
1、内置静态资源构建能力 —— Asset Modules
在 webpack 5 之前,通常使用:
- raw-loader 将文件导入为字符串
- url-loader 将文件作为 data URI 内联到 bundle 中
- file-loader 将文件发送到输出目录
资源模块类型(asset module type),通过添加 4 种新的模块类型,来替换所有这些 loader:
- asset/resource 发送一个单独的文件并导出 URL。之前通过使用 file-loader 实现。
- asset/inline 导出一个资源的 data URI。之前通过使用 url-loader 实现。
- asset/source 导出资源的源代码。之前通过使用 raw-loader 实现。
- asset 在导出一个 data URI 和发送一个单独的文件之间自动选择。之前通过使用 url-loader,并且配置资源体积限制实现。
1.1 type 分别为asset/resource
、asset/inline
、asset/source
webpack.config.js
module: {
rules: [
{
test: /\.(png|jpg|jpeg|gif)$/,
type: "asset/resource",
},
{
test: /\.svg/,
type: "asset/inline",
},
{
test: /\.txt/,
type: "asset/source", // 原样将txt文件中的文本内容注入到打包文件中
},
];
}
src/index.js
import imgUrl from "./assets/img/pic.jpeg";
import svgUrl from "./assets/img/delete.svg";
import txt from "./assets/example.txt";
//添加图片资源
let img = document.createElement("img");
img.src = imgUrl; // imgUrl: 'file:///Users/yujian2018/work/learning/project/webpack5/dist/assets/img/f972bcf4.pic.jpeg'
img.style.width = "150px";
img.style.height = "150px";
document.body.appendChild(img);
let svg = document.createElement("img");
svg.src = svgUrl; // svgUrl: data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB3aWR0aD0iMTZweCIgaGVpZ2h0PSIxNnB4IiB2aWV3Qm94PSIwIDAgMTYgMTYiIHZlcnNpb249IjEuMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+CiAgICA8IS0tIEdlbmVyYXRvcjogU2tldGNoIDUyLjYgKDY3NDkxKSAtIGh0dHA6Ly93d3cuYm9oZW1pYW5jb2RpbmcuY29tL3NrZXRjaCAtLT4KICAgIDx0aXRsZT7liKDpmaQ8L3RpdGxlPgogICAgPGRlc2M+Q3JlYXRlZCB3aXRoIFNrZXRjaC48L2Rlc2M+CiAgICA8ZGVmcz4KICAgICAgICA8cGF0aCBkPSJNNy4zMzMzMzMzMywxLjMzMzMzMzMzIEwxMC41MzMzMzMzLDEuMzMzMzMzMzMgQzEwLjYwNjk3MTMsMS4zMzMzMzMzMyAxMC42NjY2NjY3LDEuMzkzMDI4NyAxMC42NjY2NjY3LDEuNDY2NjY2NjcgTDEwLjY2NjY2NjcsMi41MzMzMzMzMyBDMTAuNjY2NjY2NywyLjYwNjk3MTMgMTAuNjA2OTcxMywyLjY2NjY2NjY3IDEwLjUzMzMzMzMsMi42NjY2NjY2NyBMMTAsMi42NjY2NjY2NyBMMTAsMTIuOCBDMTAsMTMuMDk0NTUxOSA5Ljc2MTIxODUzLDEzLjMzMzMzMzMgOS40NjY2NjY2NywxMy4zMzMzMzMzIEwxLjIsMTMuMzMzMzMzMyBDMC45MDU0NDgxMzMsMTMuMzMzMzMzMyAwLjY2NjY2NjY2NywxMy4wOTQ1NTE5IDAuNjY2NjY2NjY3LDEyLjggTDAuNjY2NjY2NjY3LDIuNjY2NjY2NjcgTDAuMTMzMzMzMzM1LDIuNjY2NjY2NjcgQzAuMDU5Njk1MzY3NiwyLjY2NjY2NjY3IDQuNzI4OTIyNjVlLTE2LDIuNjA2OTcxMyAzLjMzMDY2OTA3ZS0xNiwyLjUzMzMzMzMzIEwwLDEuNDY2NjY2NjcgQy05LjAxODA1MDAxZS0xOCwxLjM5MzAyODcgMC4wNTk2OTUzNjY3LDEuMzMzMzMzMzMgMC4xMzMzMzMzMzMsMS4zMzMzMzMzMyBMMy4zMzMzMzMzMywxLjMzMzMzMzMzIEwzLjMzMzMzMzMzLDAuMTMzMzMzMzM1IEMzLjMzMzMzMzMzLDAuMDU5Njk1MzY3NiAzLjM5MzAyODcsMS4yNDU0OTM3OGUtMTYgMy40NjY2NjY2NywxLjExMDIyMzAyZS0xNiBMNy4yLDAgQzcuMjczNjM3OTcsMi40ODA4NzU0ZS0xNiA3LjMzMzMzMzMzLDAuMDU5Njk1MzY3NiA3LjMzMzMzMzMzLDAuMTMzMzMzMzM1IEw3LjMzMzMzMzMzLDEuMzMzMzMzMzMgWiBNMy42NjY2NjY2Nyw0LjY2NjY2NjY3IEMzLjU5MzAyODcsNC42NjY2NjY2NyAzLjUzMzMzMzMzLDQuNzI2MzYyMDMgMy41MzMzMzMzMyw0LjggTDMuNTMzMzMzMzMsOS44NjY2NjY2NyBDMy41MzMzMzMzMyw5Ljk0MDMwNDYzIDMuNTkzMDI4NywxMCAzLjY2NjY2NjY3LDEwIEw0LjMzMzMzMzMzLDEwIEM0LjQwNjk3MTMsMTAgNC40NjY2NjY2Nyw5Ljk0MDMwNDYzIDQuNDY2NjY2NjcsOS44NjY2NjY2NyBMNC40NjY2NjY2Nyw0LjggQzQuNDY2NjY2NjcsNC43MjYzNjIwMyA0LjQwNjk3MTMsNC42NjY2NjY2NyA0LjMzMzMzMzMzLDQuNjY2NjY2NjcgTDMuNjY2NjY2NjcsNC42NjY2NjY2NyBaIE02LjMzMzMzMzMzLDQuNjY2NjY2NjcgQzYuMjU5Njk1MzcsNC42NjY2NjY2NyA2LjIsNC43MjYzNjIwMyA2LjIsNC44IEw2LjIsOS44NjY2NjY2NyBDNi4yLDkuOTQwMzA0NjMgNi4yNTk2OTUzNywxMCA2LjMzMzMzMzMzLDEwIEw3LDEwIEM3LjA3MzYzNzk3LDEwIDcuMTMzMzMzMzMsOS45NDAzMDQ2MyA3LjEzMzMzMzMzLDkuODY2NjY2NjcgTDcuMTMzMzMzMzMsNC44IEM3LjEzMzMzMzMzLDQuNzI2MzYyMDMgNy4wNzM2Mzc5Nyw0LjY2NjY2NjY3IDcsNC42NjY2NjY2NyBMNi4zMzMzMzMzMyw0LjY2NjY2NjY3IFoiIGlkPSJwYXRoLTEiPjwvcGF0aD4KICAgIDwvZGVmcz4KICAgIDxnIGlkPSLliKDpmaQiIHN0cm9rZT0ibm9uZSIgc3Ryb2tlLXdpZHRoPSIxIiBmaWxsPSJub25lIiBmaWxsLXJ1bGU9ImV2ZW5vZGQiPgogICAgICAgIDxyZWN0IGZpbGw9IiNGRkZGRkYiIG9wYWNpdHk9IjAiIHg9IjAiIHk9IjAiIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiI+PC9yZWN0PgogICAgICAgIDxnPgogICAgICAgICAgICA8ZyBpZD0iNF9JY29uLzBfYmFzZS9iZy3mm7/mjaIiPjwvZz4KICAgICAgICAgICAgPGcgaWQ9Imljb24vZ3JvdXAiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDIuNjY2NjY3LCAxLjMzMzMzMykiPgogICAgICAgICAgICAgICAgPG1hc2sgaWQ9Im1hc2stMiIgZmlsbD0id2hpdGUiPgogICAgICAgICAgICAgICAgICAgIDx1c2UgeGxpbms6aHJlZj0iI3BhdGgtMSI+PC91c2U+CiAgICAgICAgICAgICAgICA8L21hc2s+CiAgICAgICAgICAgICAgICA8dXNlIGlkPSJNYXNrIiBmaWxsPSIjODM4NjhGIiB4bGluazpocmVmPSIjcGF0aC0xIj48L3VzZT4KICAgICAgICAgICAgPC9nPgogICAgICAgIDwvZz4KICAgIDwvZz4KPC9zdmc+
document.body.appendChild(svg);
let txtEl = document.createElement("div");
txtEl.innerHTML = txt; // txt: 这里是纯文本内容
document.body.appendChild(txtEl);
1.2 type 为asset
对于type: asset
,webpack 将按照默认条件,自动地在 resource 和 inline 之间进行选择:小于 8kb 的文件,将会视为 inline 模块类型,否则会被视为 resource 模块类型。
也可以通过在 webpack 配置的 module rule 层级中,设置 Rule.parser.dataUrlCondition.maxSize 选项来修改此条件:
module:
{ rules: [{ test: /\.(png|jpg|jpeg|gif)$/, type: "asset", ? // 自定义设置
parser
: { dataUrlCondition: { maxSize: 8 * 1024 } } }]
}
1.3 自定义输出文件名
默认情况下,asset/resource 模块以 [hash][ext][query] 文件名发送到输出目录。
可以通过在 webpack.config.js 将 output.assetModuleFilename 和 Rule.generator.filename 结合使用来定制化文件的输出目录:
output: {
filename: 'main.js',
path: path.resolve(__dirname, 'dist'),
assetModuleFilename: 'images/[hash][ext][query]'
},
module: {
rules: [
{
test: /\.(png|jpg|jpeg|gif)$/,
type: 'asset/resource',
parser: {
dataUrlCondition: {
maxSize: 8 * 1024
}
},
generator: {
// [ext]前面自带"."
filename: 'assets/img/[hash:8].[name][ext]', //自定义输出目录
}
}
]
}
注意:Rule.generator.filename 与 output.assetModuleFilename 相同,并且仅适用于 asset 和 asset/resource 模块类型。
2、文件缓存
在 webpack4 中,会使用 cache-loader 缓存一些性能开销较大的 loader ,或者是使用 hard-source-webpack-plugin 为模块提供一些中间缓存。在 Webpack5 之后,默认就为集成了一种自带的缓存能力(对 module 和 chunks 进行缓存)。通过如下配置,即可在二次构建时提速。
cache: {
type: 'filesystem',
// 默认缓存到 node_modules/.cache/webpack 中
// 也可以自定义缓存目录,cache.cacheDirectory 选项仅当 cache.type 被设置成 filesystem 才可用。
// cacheDirectory:path.resolve(__dirname,'node_modules/.cac/webpack'),
buildDependencies : {
// 2. 将配置添加为 buildDependency 以使配置更改时缓存失效
config : [ __filename ]
// 3. 如果有其他构建所依赖的东西可以在这里添加它们
// 请注意,webpack、加载器和从配置中引用的所有模块都会自动添加
}
}
3、更好地 treeshaking
未使用的导出内容不会被打包生成。 将 mode 工作模式改为 production 就会自动开启。
3.1、 嵌套 treeshaking(Nested tree-shaking)
module1.js
import * as module2 from "./module2";
export function fun1() {
console.log("fun1");
}
export function fun2() {
console.log("fun2");
}
export { module2 };
module2.js
export function fun3() {
console.log("fun3");
}
export function fun4() {
console.log("fun4");
}
export const num1 = 111;
export const num2 = 222;
index.js
import * as module1 from "./module1";
console.log(module1.module2.num1);
webpack4 和 webpack5 的打包结果对比:
3.2、 内部模块 treeshaking(Inner-module tree-shaking)
webpack 4 没有分析模块的导出和导入之间的依赖关系。webpack 5 有一个新选项 optimization.innerGraph,它在生产模式下默认启用,它对模块中的符号运行分析以找出从导出到导入的依赖关系。
import { something } from "./something";
function usingSomething() {
return something;
}
export function test() {
return usingSomething();
}
innerGraph 将计算出 something 仅在使用 test 导出时使用。这允许将更多导出标记为未使用并从包中省略更多代码。
当 "sideEffects": false 设置,这允许省略甚至更多的模块。 在此示例中,./something 当 test 导出未使用时将被省略。
3.3 commonjs treeshaking
webpack 5 添加了对某些 CommonJs 结构的支持,允许消除未使用的 CommonJs 导出并跟踪 require()调用中引用的导出名称。
4、模块联邦
模块联邦本身是一个普通的 Webpack 插件 ModuleFederationPlugin,插件有几个重要参数:
- name 当前应用名称,需要全局唯一。
- remotes 可以将其他项目的 name 映射到当前项目中。
- exposes 表示导出的模块,只有在此申明的模块才可以作为远程依赖被使用。
- shared 是非常重要的参数,制定了这个参数,可以让远程加载的模块对应依赖改为使用本地项目的 React 或 ReactDOM。
使用 Module Federation 时,每个应用块都是一个独立的构建,这些构建都将编译为容器。
容器可以被其他应用或者其他容器应用。
一个被引用的容器被称为 remote, 引用者被称为 host,remote 暴露模块给 host, host 则可以使用这些暴露的模块,这些模块被成为 remote 模块。
主要代码:
app_remote 项目中的 weback.config.js
new ModuleFederationPlugin({
name: "app_remote",
filename: "remoteEntry.js",
exposes: {
// 远程应用暴露出的模块名
"./Button": "./src/components/Button.vue",
},
shared: ["vue", "element-ui"],
});
host 项目中的 weback.config.js
new ModuleFederationPlugin({
name: "app_remote",
filename: "remoteEntry.js",
remotes: {
// 声明需要引用的远程应用
remote: "app_remote@http://localhost:3000/remoteEntry.js",
},
shared: ["vue", "element-ui"],
});
host 项目中使用 remote 项目的组件时, src/app.vue
button: () => import("remote/Button");
遇到的问题:
使用 shared 参数时,如果报错:Uncaught Error: Shared module is not available for eager consumption
,则解决方案如下:
新建 bootstrap.js,将 index.js 中的内容粘贴到此文件中。如下:
import Vue from "vue";
import App from "./app.vue";
import ElementUI from "element-ui";
import "element-ui/lib/theme-chalk/index.css";
Vue.use(ElementUI);
new Vue({
render: (h) => h(App),
}).$mount("#app");
将 index.js 中的内容修改为:
import("./bootstrap");
最终效果图如下:
分别是子应用和主应用,其中普通按钮来自子应用,带了 ele 样式的 button 来自主应用。
完整的项目代码如下:
app_remote 项目:
webpack.config.js
const path = require("path");
const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
const VueLoaderPlugin = require("vue-loader/lib/plugin");
module.exports = {
mode: "development", // production none
entry: "./src/index.js",
output: {
filename: "[name].js",
path: path.resolve(__dirname, "dist"),
},
module: {
rules: [
{
test: /\.vue$/,
loader: "vue-loader",
},
{
test: /\.js$/,
loader: "babel-loader",
exclude: /node_modules/,
},
{
test: /\.css$/,
use: ["style-loader", "css-loader"],
},
{
test: /\.(woff|ttf)$/,
loader: "file-loader",
},
],
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title: "app_remote",
template: path.resolve(__dirname, "./public/index.html"),
filename: "index.html",
}),
new ModuleFederationPlugin({
name: "app_remote",
filename: "remoteEntry.js",
exposes: {
// 远程应用暴露出的模块名
"./Button": "./src/components/Button.vue",
},
remotes: {
host: "app_host@http://localhost:9000/remoteEntry.js",
},
shared: ["vue", "element-ui"],
}),
new VueLoaderPlugin(),
],
devServer: {
hot: true,
host: "0.0.0.0",
port: 3000,
},
};
src/app.vue
<template>
<div>
Hello,{{ name }}
<Button />
<List />
</div>
</template>
<script>
export default {
components: {
Button: () => import("./components/Button.vue"),
List: () => import("host/list"),
},
data() {
return {
name: "子应用",
};
},
};
</script>
src/components/Button.vue
<template>
<div>
<button>hahaha</button>
</div>
</template>
app_host 项目中:
webpack.config.js
const path = require("path");
const webpack = require("webpack");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const { CleanWebpackPlugin } = require("clean-webpack-plugin");
const ModuleFederationPlugin = require("webpack/lib/container/ModuleFederationPlugin");
const VueLoaderPlugin = require("vue-loader/lib/plugin");
module.exports = {
mode: "development", // production
entry: "./src/index.js",
output: {
filename: "[name].js",
path: path.resolve(__dirname, "dist"),
// publicPath: "http://localhost:9000/", //部署后的资源地址
},
module: {
rules: [
{
test: /\.vue$/,
loader: "vue-loader",
include: [path.resolve(process.cwd(), "src")],
},
{
test: /\.js$/,
loader: "babel-loader",
exclude: /node_modules/,
},
{
test: /\.css$/,
use: ["style-loader", "css-loader"],
},
{
test: /\.(woff|ttf)$/,
loader: "file-loader",
},
],
},
plugins: [
new VueLoaderPlugin(),
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title: "app_host",
template: path.resolve(__dirname, "./public/index.html"),
filename: "index.html",
}),
new ModuleFederationPlugin({
name: "app_host",
filename: "remoteEntry.js",
exposes: {
"./list": "./src/components/list.vue",
},
remotes: {
// 声明需要引用的远程应用
remote: "app_remote@http://localhost:3000/remoteEntry.js",
},
shared: ["vue", "element-ui"],
}),
],
devServer: {
hot: true,
host: "0.0.0.0",
port: 9000,
},
};
app.vue
<template>
<div>
Hello,{{ name }}
<Button />
<el-button type="primary"></el-button>
</div>
</template>
<script>
export default {
components: {
// Button: (resolve) => require(["remote/Button"], resolve),
Button: () => import("remote/Button"),
},
data() {
return {
name: "主应用",
};
},
};
</script>
src/components/list.vue
<template>
<div>
<el-button type="primary">这里使用了element-ui组件库</el-button>
</div>
</template>
<script>
src/bootstrap.js
import Vue from "vue";
import App from "./app.vue";
import ElementUI from "element-ui";
import "element-ui/lib/theme-chalk/index.css";
Vue.use(ElementUI);
new Vue({
render: (h) => h(App),
}).$mount("#app");
src/index.js
import("./bootstrap");
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南