Webpack 实现文件打包的过程是一个复杂而精细的流程,涉及多个步骤和概念。__webpack_require__
是 Webpack 运行时的一部分,确实在模块加载中扮演了重要角色,但它并不是打包过程的全部。以下是 Webpack 打包的核心步骤和组件:
其中__webpack_require__
函数是 Webpack 运行时(runtime)的核心,但它主要负责在运行时加载和执行模块。Webpack 的打包过程是一个涉及文件解析、依赖管理、编译、优化、分割和输出的综合过程,__webpack_require__
只是这个过程的最终产物之一。
Webpack 的打包流程可以简化为以下步骤:
在整个过程中,Webpack 的配置文件(通常是 webpack.config.js
)扮演了至关重要的角色,它定义了如何加载文件、如何应用 loader 和插件、如何优化和输出结果等。
js中加载模块资源,可以通过以下方式
entry::入口模块文件路径
output:输出bundle文件路径
module:模块,webpack构建对象
bundle:输出文件,webpack构建产物
chunk:中间文件,webpack构建的中间产物
loader:文件转换器
Plugin:插件,执行特定任务
入口文件一般为js文件,整个页面根据Js代码的需要去动态地引入对应的所有资源
const os = require("os");
const path = require("path"); // nodejs核心模块,专门用来处理路径问题
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 TerserWebpackPlugin = require("terser-webpack-plugin");
const ImageMinimizerPlugin = require("image-minimizer-webpack-plugin");
const PreloadWebpackPlugin = require("@vue/preload-webpack-plugin");
const WorkboxPlugin = require("workbox-webpack-plugin");
const threads = os.cpus().length; // cpu核数
// 用来获取处理样式的loader
function getStyleLoader(pre) {
return [
MiniCssExtractPlugin.loader, // 提取css成单独文件
"css-loader", // 将css资源编译成commonjs的模块到js中
{
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: [
"postcss-preset-env", // 能解决大多数样式兼容性问题
],
},
},
},
pre,
].filter(Boolean);
}
module.exports = {
// 入口
entry: "./src/main.js", // 相对路径
// 输出
output: {
// 所有文件的输出路径
// __dirname nodejs的变量,代表当前文件的文件夹目录
path: path.resolve(__dirname, "../dist"), // 绝对路径
// 入口文件打包输出文件名
filename: "static/js/[name].[contenthash:10].js",
// 给打包输出的其他文件命名
chunkFilename: "static/js/[name].chunk.[contenthash:10].js",
// 图片、字体等通过type:asset处理资源命名方式
assetModuleFilename: "static/media/[hash:10][ext][query]",
// 自动清空上次打包的内容
// 原理:在打包前,将path整个目录内容清空,再进行打包
clean: true,
},
// 加载器
module: {
rules: [
// loader的配置
{
oneOf: [
{
test: /\.css$/, // 只检测.css文件
use: getStyleLoader(), // 执行顺序:从右到左(从下到上)
},
{
test: /\.less$/,
// loader: 'xxx', // 只能使用1个loader
use: getStyleLoader("less-loader"),
},
{
test: /\.s[ac]ss$/,
use: getStyleLoader("sass-loader"),
},
{
test: /\.styl$/,
use: getStyleLoader("stylus-loader"),
},
{
test: /\.(png|jpe?g|gif|webp|svg)$/,
type: "asset",
parser: {
dataUrlCondition: {
// 小于10kb的图片转base64
// 优点:减少请求数量 缺点:体积会更大
maxSize: 10 * 1024, // 10kb
},
},
// generator: {
// // 输出图片名称
// // [hash:10] hash值取前10位
// filename: "static/images/[hash:10][ext][query]",
// },
},
{
test: /\.(ttf|woff2?|map3|map4|avi)$/,
type: "asset/resource",
// generator: {
// // 输出名称
// filename: "static/media/[hash:10][ext][query]",
// },
},
{
test: /\.js$/,
// exclude: /node_modules/, // 排除node_modules下的文件,其他文件都处理
include: path.resolve(__dirname, "../src"), // 只处理src下的文件,其他文件不处理
use: [
{
loader: "thread-loader", // 开启多进程
options: {
works: threads, // 进程数量
},
},
{
loader: "babel-loader",
options: {
// presets: ["@babel/preset-env"],
cacheDirectory: true, // 开启babel缓存
cacheCompression: false, // 关闭缓存文件压缩
plugins: ["@babel/plugin-transform-runtime"], // 减少代码体积
},
},
],
},
],
},
],
},
// 插件
plugins: [
// plugin的配置
new ESLintPlugin({
// 检测哪些文件
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文件
// 新的html文件特点:1. 结构和原来一致 2. 自动引入打包输出的资源
template: path.resolve(__dirname, "../public/index.html"),
}),
new MiniCssExtractPlugin({
filename: "static/css/[name].[contenthash:10].css",
chunkFilename: "static/css/[name].chunk.[contenthash:10].css",
}),
// new CssMinimizerPlugin(),
// new TerserWebpackPlugin({
// parallel: threads, // 开启多进程和设置进程数量
// }),
new PreloadWebpackPlugin({
// rel: "preload",
// as: "script",
rel: "prefetch",
}),
new WorkboxPlugin.GenerateSW({
// 这些选项帮助快速启用 ServiceWorkers
// 不允许遗留任何“旧的” ServiceWorkers
clientsClaim: true,
skipWaiting: true,
}),
],
optimization: {
// 压缩的操作
minimizer: [
// 压缩css
new CssMinimizerPlugin(),
// 压缩js
new TerserWebpackPlugin({
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",
// 其他都用默认值
},
runtimeChunk: {
name: (entrypoint) => `runtime~${entrypoint.name}.js`,
},
},
// 模式
mode: "production",
devtool: "source-map",
};
const os = require("os");
const path = require("path"); // nodejs核心模块,专门用来处理路径问题
const ESLintPlugin = require("eslint-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const threads = os.cpus().length; // cpu核数
module.exports = {
// 入口
entry: "./src/main.js", // 相对路径
// 输出
output: {
// 所有文件的输出路径
// 开发模式没有输出
path: undefined,
// 入口文件打包输出文件名
filename: "static/js/[name].js",
// 给打包输出的其他文件命名
chunkFilename: "static/js/[name].chunk.js",
// 图片、字体等通过type:asset处理资源命名方式
assetModuleFilename: "static/media/[hash:10][ext][query]",
},
// 加载器
module: {
rules: [
// loader的配置
{
// 每个文件只能被其中一个loader配置处理
oneOf: [
{
test: /\.css$/, // 只检测.css文件
use: [
// 执行顺序:从右到左(从下到上)
"style-loader", // 将js中css通过创建style标签添加html文件中生效
"css-loader", // 将css资源编译成commonjs的模块到js中
],
},
{
test: /\.less$/,
// loader: 'xxx', // 只能使用1个loader
use: [
// 使用多个loader
"style-loader",
"css-loader",
"less-loader", // 将less编译成css文件
],
},
{
test: /\.s[ac]ss$/,
use: [
"style-loader",
"css-loader",
"sass-loader", // 将sass编译成css文件
],
},
{
test: /\.styl$/,
use: [
"style-loader",
"css-loader",
"stylus-loader", // 将stylus编译成css文件
],
},
{
test: /\.(png|jpe?g|gif|webp|svg)$/,
type: "asset",
parser: {
dataUrlCondition: {
// 小于10kb的图片转base64
// 优点:减少请求数量 缺点:体积会更大
maxSize: 10 * 1024, // 10kb
},
},
// generator: {
// // 输出图片名称
// // [hash:10] hash值取前10位
// filename: "static/images/[hash:10][ext][query]",
// },
},
{
test: /\.(ttf|woff2?|map3|map4|avi)$/,
type: "asset/resource",
// generator: {
// // 输出名称
// filename: "static/media/[hash:10][ext][query]",
// },
},
{
test: /\.js$/,
// exclude: /node_modules/, // 排除node_modules下的文件,其他文件都处理
include: path.resolve(__dirname, "../src"), // 只处理src下的文件,其他文件不处理
use: [
{
loader: "thread-loader", // 开启多进程
options: {
works: threads, // 进程数量
},
},
{
loader: "babel-loader",
options: {
// presets: ["@babel/preset-env"],
cacheDirectory: true, // 开启babel缓存
cacheCompression: false, // 关闭缓存文件压缩
plugins: ["@babel/plugin-transform-runtime"], // 减少代码体积
},
},
],
},
],
},
],
},
// 插件
plugins: [
// plugin的配置
new ESLintPlugin({
// 检测哪些文件
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文件
// 新的html文件特点:1. 结构和原来一致 2. 自动引入打包输出的资源
template: path.resolve(__dirname, "../public/index.html"),
}),
],
// 开发服务器: 不会输出资源,在内存中编译打包的
devServer: {
host: "localhost", // 启动服务器域名
port: "3000", // 启动服务器端口号
open: true, // 是否自动打开浏览器
hot: true, // 开启HMR(默认值)
},
optimization: {
// 开发模式下不需要压缩
// 代码分割配置
splitChunks: {
chunks: "all",
// 其他都用默认值
},
},
// 模式
mode: "development",
devtool: "cheap-module-source-map",
};
const path = require("path");
const EslintWebpackPlugin = require("eslint-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerWebpackPlugin = 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");
// 返回处理样式loader函数
const getStyleLoaders = (pre) => {
return [
MiniCssExtractPlugin.loader,
"css-loader",
{
// 处理css兼容性问题
// 配合package.json中browserslist来指定兼容性
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: ["postcss-preset-env"],
},
},
},
pre,
].filter(Boolean);
};
module.exports = {
entry: "./src/main.js",
output: {
path: path.resolve(__dirname, "../dist"),
filename: "static/js/[name].[contenthash:10].js",
chunkFilename: "static/js/[name].[contenthash:10].chunk.js",
assetModuleFilename: "static/media/[hash:10][ext][query]",
clean: true,
},
module: {
rules: [
// 处理css
{
test: /\.css$/,
use: getStyleLoaders(),
},
{
test: /\.less$/,
use: getStyleLoaders("less-loader"),
},
{
test: /\.s[ac]ss$/,
use: getStyleLoaders("sass-loader"),
},
{
test: /\.styl$/,
use: getStyleLoaders("stylus-loader"),
},
// 处理图片
{
test: /\.(jpe?g|png|gif|webp|svg)$/,
type: "asset",
parser: {
dataUrlCondition: {
maxSize: 10 * 1024,
},
},
},
// 处理其他资源
{
test: /\.(woff2?|ttf)$/,
type: "asset/resource",
},
// 处理js
{
test: /\.js$/,
include: path.resolve(__dirname, "../src"),
loader: "babel-loader",
options: {
cacheDirectory: true,
cacheCompression: false,
},
},
{
test: /\.vue$/,
loader: "vue-loader",
},
],
},
// 处理html
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 MiniCssExtractPlugin({
filename: "static/css/[name].[contenthash:10].css",
chunkFilename: "static/css/[name].[contenthash:10].chunk.css",
}),
new CopyPlugin({
patterns: [
{
from: path.resolve(__dirname, "../public"),
to: path.resolve(__dirname, "../dist"),
globOptions: {
// 忽略index.html文件
ignore: ["**/index.html"],
},
},
],
}),
new VueLoaderPlugin(),
// cross-env定义的环境变量给打包工具使用
// DefinePlugin定义环境变量给源代码使用,从而解决vue3页面警告的问题
new DefinePlugin({
__VUE_OPTIONS_API__: true,
__VUE_PROD_DEVTOOLS__: false,
}),
],
mode: "production",
devtool: "source-map",
optimization: {
splitChunks: {
chunks: "all",
},
runtimeChunk: {
name: (entrypoint) => `runtime~${entrypoint.name}.js`,
},
minimizer: [
new CssMinimizerWebpackPlugin(),
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",
},
},
],
},
],
],
},
},
}),
],
},
// webpack解析模块加载选项
resolve: {
// 自动补全文件扩展名
extensions: [".vue", ".js", ".json"],
},
};
const path = require("path");
const EslintWebpackPlugin = require("eslint-webpack-plugin");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const CssMinimizerWebpackPlugin = 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 ReactRefreshWebpackPlugin = require("@pmmmwh/react-refresh-webpack-plugin");
// 获取cross-env定义的环境变量
const isProduction = process.env.NODE_ENV === "production";
// 返回处理样式loader函数
const getStyleLoaders = (pre) => {
return [
isProduction ? MiniCssExtractPlugin.loader : "style-loader",
"css-loader",
{
// 处理css兼容性问题
// 配合package.json中browserslist来指定兼容性
loader: "postcss-loader",
options: {
postcssOptions: {
plugins: ["postcss-preset-env"],
},
},
},
pre && {
loader: pre,
options:
pre === "less-loader"
? {
// antd自定义主题配置
// 主题色文档:https://ant.design/docs/react/customize-theme-cn#Ant-Design-%E7%9A%84%E6%A0%B7%E5%BC%8F%E5%8F%98%E9%87%8F
lessOptions: {
modifyVars: { "@primary-color": "#1DA57A" },
javascriptEnabled: true,
},
}
: {},
},
].filter(Boolean);
};
module.exports = {
entry: "./src/main.js",
output: {
path: isProduction ? path.resolve(__dirname, "../dist") : undefined,
filename: 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",
assetModuleFilename: "static/media/[hash:10][ext][query]",
clean: true,
},
module: {
rules: [
// 处理css
{
test: /\.css$/,
use: getStyleLoaders(),
},
{
test: /\.less$/,
use: getStyleLoaders("less-loader"),
},
{
test: /\.s[ac]ss$/,
use: getStyleLoaders("sass-loader"),
},
{
test: /\.styl$/,
use: getStyleLoaders("stylus-loader"),
},
// 处理图片
{
test: /\.(jpe?g|png|gif|webp|svg)$/,
type: "asset",
parser: {
dataUrlCondition: {
maxSize: 10 * 1024,
},
},
},
// 处理其他资源
{
test: /\.(woff2?|ttf)$/,
type: "asset/resource",
},
// 处理js
{
test: /\.jsx?$/,
include: path.resolve(__dirname, "../src"),
loader: "babel-loader",
options: {
cacheDirectory: true,
cacheCompression: false,
plugins: [
!isProduction && "react-refresh/babel", // 激活js的HMR
].filter(Boolean),
},
},
],
},
// 处理html
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"),
}),
isProduction &&
new MiniCssExtractPlugin({
filename: "static/css/[name].[contenthash:10].css",
chunkFilename: "static/css/[name].[contenthash:10].chunk.css",
}),
isProduction &&
new CopyPlugin({
patterns: [
{
from: path.resolve(__dirname, "../public"),
to: path.resolve(__dirname, "../dist"),
globOptions: {
// 忽略index.html文件
ignore: ["**/index.html"],
},
},
],
}),
!isProduction && new ReactRefreshWebpackPlugin(),
].filter(Boolean),
mode: isProduction ? "production" : "development",
devtool: isProduction ? "source-map" : "cheap-module-source-map",
optimization: {
splitChunks: {
chunks: "all",
cacheGroups: {
// react react-dom react-router-dom 一起打包成一个js文件
react: {
test: /[\\/]node_modules[\\/]react(.*)?[\\/]/,
name: "chunk-react",
priority: 40,
},
// antd 单独打包
antd: {
test: /[\\/]node_modules[\\/]antd[\\/]/,
name: "chunk-antd",
priority: 30,
},
// 剩下node_modules单独打包
libs: {
test: /[\\/]node_modules[\\/]/,
name: "chunk-libs",
priority: 20,
},
},
},
runtimeChunk: {
name: (entrypoint) => `runtime~${entrypoint.name}.js`,
},
// 是否需要进行压缩
minimize: isProduction,
minimizer: [
new CssMinimizerWebpackPlugin(),
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",
},
},
],
},
],
],
},
},
}),
],
},
// webpack解析模块加载选项
resolve: {
// 自动补全文件扩展名
extensions: [".jsx", ".js", ".json"],
},
devServer: {
host: "localhost",
port: 3000,
open: true,
hot: true, // 开启HMR
historyApiFallback: true, // 解决前端路由刷新404问题
},
performance: false, // 关闭性能分析,提升打包速度
};
作用:负责资源文件从输入到输出之间的转换,对于同一个资源文件,可使用多个loader。
执行顺序:由下到上,从右到左
编译转换类
babel-loader
将es6转译为es5
css-loader
把css代码转为css module
style-loader
把css module动态转化为style标签插入页面
操作文件类
file-loader
url-loader
把文件转为file协议的字符串,并export出去(一般用于图片)
html-loader
html-loader
可以将 HTML 文件加载为一个 JavaScript 模块,返回文件内容的字符串。
代码检查类
eslint-loader
eslint规范
在 Webpack 5 中,每个 loader 接收到的参数(通常是一个对象)包含了丰富的信息,这些信息可以用来定制 loader 的行为。以下是一些常见的参数:
this
:
this.async()
用于创建一个异步回调,this.cacheable()
用于标识模块是否应该被缓存。resource
:
resourcePath
:
resource
,表示当前文件的绝对路径。context
:
loaderIndex
:
loaders
:
query
:
data
:
module
部分,允许 loader 访问配置信息。options
:
emitFile
:
fs
和 fileSystem
:
sourceMap
:
target
:
web
、node
等)。webpack
和 compiler
:
mode
:
development
或 production
)。env
:
env.NODE_ENV
。这些参数通过 loader 的调用函数传递,loader 开发者可以根据需要使用这些参数来实现特定的逻辑。例如,可以根据 query
参数来定制 loader 的行为,或者使用 this.async()
来实现异步处理。
以下是一个简单的 loader 示例,展示了如何使用这些参数:
module.exports = function(source) {
const callback = this.async(); // 使用 this.async() 创建异步处理
const query = this.query; // 获取查询参数
const resourcePath = this.resourcePath; // 获取资源路径
// 处理 source 代码...
callback(null, transformedSource); // 使用回调返回处理后的代码
};
在这个示例中,source
是 loader 接收到的文件内容,callback
用于异步返回处理结果,query
和 resourcePath
是从参数中获取的信息。
把md文件的内容加载进来
const marked = require('marked')
module.exports = source => {
// console.log(source)
// return 'console.log("hello ~")'
const html = marked(source)
// return html
// return `module.exports = "${html}"`
// return `export default ${JSON.stringify(html)}`
// 返回 html 字符串交给下一个 loader 处理
return html
}
webpack.config.js配置
记住:执行顺序是从右往左,从后往前
{
module: {
rules: [
{
test: /.md$/,
use: [
'html-loader',
'./markdown-loader'
]
}
]
}
}
plugin 的作用是 Webpack 扩展功能。loader 可以理解为转换器,用于处理模块之间的转换,plugin 则用于执行更广泛的任务,它可以访问 Webpack 的生命周期,在合适的时机执行插件的功能。
一般情况下,plugin更多地用于执行一些自动化的任务。
html-webpack-plugin
copy-webpack-plugin:
clean-webpack-plugin
核心:通过在打包生命周期中的钩子中挂载函数实现拓展。
以下是创建一个基本 Webpack 插件的步骤:
定义插件类:
Webpack 插件是一个 JavaScript 函数,它接收一个 compiler
对象作为参数。可以通过这个对象访问 Webpack 的钩子(hooks)。
访问钩子(Hooks):
使用 compiler.hooks
访问 Webpack 的各种钩子,这些钩子在构建过程中的不同阶段被调用。
实现应用逻辑:
在钩子的回调函数中实现你的插件逻辑。
使用 tapable
钩子:
Webpack 的 tapable
库提供了钩子的注册和监听功能。
编译时和运行时:
根据需要在编译时(compilation
)或运行时(runtime
)应用你的插件逻辑。
处理异步操作:
如果你的插件需要执行异步操作,确保正确处理 Promises 或使用 async/await
。
导出插件:
将插件函数导出,以便在 Webpack 配置中使用。
下面是一个简单的 Webpack 插件示例,该插件在每次构建开始时记录一条消息:
const { Compilation } = require('webpack');
class MyCustomWebpackPlugin {
// 插件构造函数可以接收选项参数
constructor(options) {
this.options = options;
}
// Webpack 会调用这个函数来应用插件
apply(compiler) {
// compiler 是一个 Webpack 编译对象的引用
compiler.hooks.run.tap('MyCustomWebpackPlugin', (compilation) => {
// compilation 对象是当前编译过程的引用
console.log('Webpack build started!');
// 可以访问 compilation.hooks 来进一步扩展编译过程
});
}
}
module.exports = MyCustomWebpackPlugin;
要在 Webpack 配置中使用这个插件,可以将其添加到 plugins
数组中:
const MyCustomWebpackPlugin = require('./path/to/MyCustomWebpackPlugin');
module.exports = {
// ... 其他 Webpack 配置 ...
plugins: [
new MyCustomWebpackPlugin({ /* 插件选项 */ }),
],
};
这个示例展示了一个非常基础的插件结构。Webpack 插件可以执行更复杂的任务,包括但不限于:
entry
点。创建更高级的插件可能需要更深入地了解 Webpack 的工作原理和 API,以及 tapable
库的使用方法。记得在开发插件时,要考虑到插件的性能影响和潜在的错误处理。
在 Webpack 中,compiler.hooks
提供了一系列的钩子(hooks),允许插件在 Webpack 构建流程的特定点执行代码。以下是一些常用的 compiler
钩子:
run
:
当 Webpack 开始编译流程时触发。
watchRun
:
当使用 --watch
选项,每次开始监控文件变化时触发。
beforeCompile
:
在每次编译之前触发,提供了 compilation
参数。
compile
:
当创建一个新的 compilation
对象时触发。
make
:
在 compilation
对象的 make
方法被调用时触发。
buildModule
:
当模块开始构建时触发。
normalModuleFactory
:
当创建正常的模块工厂时触发。
contextModuleFactory
:
当创建上下文模块工厂时触发。
beforeResolve
:
在解析模块请求之前触发。
afterResolve
:
在解析模块请求之后触发。
resolve
:
在模块请求解析时触发。
finishMake
:
当 make
方法完成时触发。
seal
:
当 compilation
对象被密封,准备输出时触发。
optimize
:
在优化阶段触发,例如在调用 optimizeChunks
或 optimizeTree
时。
afterSeal
:
在 compilation
对象密封之后触发。
emit
:
在 compilation
对象准备输出资源到文件系统时触发。
assetEmitted
:
当资源被发出时触发。
afterEmit
:
在 emit
过程结束后触发。
done
:
在编译完成之后触发。
failed
:
当编译失败时触发。
invalid
:
当编译无效时触发,通常用于 --watch
模式下文件更改。
watchClose
:
当 --watch
模式下的监视被关闭时触发。
这些钩子允许插件进行各种操作,如修改配置、访问或修改模块、资源和依赖关系,以及在构建过程中进行自定义逻辑处理。
要使用这些钩子,你需要在插件中注册相应的监听器。例如:
class MyWebpackPlugin {
apply(compiler) {
compiler.hooks.emit.tapAsync('MyWebpackPlugin', (compilation, callback) => {
// 在 emit 阶段执行自定义逻辑
callback();
});
}
}
在这个例子中,tapAsync
方法用于注册一个异步监听器到 emit
钩子。compilation
参数提供了对当前编译过程的访问,callback
是一个必须被调用的回调函数,以确保 Webpack 继续执行后续的钩子。
记得在实际使用中,根据你的插件需求选择合适的钩子,并在其中实现相应的逻辑。
接下来是一个demo
class MyPlugin {
apply(compiler) {
console.log('MyPlugin 启动')
compiler.hooks.emit.tap('MyPlugin', compilation => {
// compilation => 可以理解为此次打包的上下文
for (const name in compilation.assets) {
// console.log(name)
// console.log(compilation.assets[name].source())
if (name.endsWith('.js')) {
const contents = compilation.assets[name].source()
const withoutComments = contents.replace(/\/\*\*+\*\//g, '')
compilation.assets[name] = {
source: () => withoutComments,
size: () => withoutComments.length
}
}
}
})
}
}
作用:将 HTML 文件中通过 html-webpack-plugin
插入的某些 <script>
标签的外部文件资源转换为内联脚本(inline script)。具体来说,该插件会查找特定的 <script>
标签,并将它们引用的外部 JavaScript 文件内容直接嵌入到 HTML 中,而不是通过 src
属性引入外部文件。
const HtmlWebpackPlugin = require("safe-require")("html-webpack-plugin");
class InlineChunkWebpackPlugin {
constructor(tests) {
this.tests = tests;
}
apply(compiler) {
compiler.hooks.compilation.tap("InlineChunkWebpackPlugin", (compilation) => {
// 1. 获取html-webpack-plugin的hooks
const hooks = HtmlWebpackPlugin.getHooks(compilation);
// 2. 注册 html-webpack-plugin的hooks -> alterAssetTagGroups
hooks.alterAssetTagGroups.tap("InlineChunkWebpackPlugin", (assets) => {
// 3. 从里面将script的runtime文件,变成inline script
assets.headTags = this.getInlineChunk(assets.headTags, compilation.assets);
assets.bodyTags = this.getInlineChunk(assets.bodyTags, compilation.assets);
});
// 删除runtime文件
hooks.afterEmit.tap("InlineChunkWebpackPlugin", () => {
// 3. 从里面将script的runtime文件,变成inline script
Object.keys(compilation.assets).forEach((filepath) => {
if (this.tests.some((test) => test.test(filepath))) {
delete compilation.assets[filepath];
}
});
});
});
}
getInlineChunk(tags, assets) {
/*
目前:[
{
tagName: 'script',
voidTag: false,
meta: { plugin: 'html-webpack-plugin' },
attributes: { defer: true, type: undefined, src: 'js/runtime~main.js.js' }
},
]
修改为:
[
{
tagName: 'script',
innerHTML: runtime文件的内容
closeTag: true
},
]
*/
return tags.map((tag) => {
if (tag.tagName !== "script") return tag;
// 获取文件资源路径
const filepath = tag.attributes.src;
if (!filepath) return tag;
if (!this.tests.some((test) => test.test(filepath))) return tag;
return {
tagName: "script",
innerHTML: assets[filepath].source(),
closeTag: true,
};
});
}
}
module.exports = InlineChunkWebpackPlugin;
__webpack_require__
方法在Webpack中,__webpack_require__
是一个在构建过程中自动注入的全局函数,它用于模块的加载和解析。这个函数是Webpack特有的,它在最终的打包文件中提供模块间的依赖关系解析。以下是__webpack_require__
函数的一些主要作用:
模块加载:__webpack_require__
函数用于加载和执行模块。在Webpack打包后的代码中,当需要引入一个模块时,实际上是通过调用__webpack_require__(moduleId)
来实现的,其中moduleId
是模块的唯一标识符。
缓存机制:__webpack_require__
实现了模块的缓存机制。一旦模块被加载,它就会被缓存起来。后续如果再次请求相同的模块,__webpack_require__
会直接从缓存中返回模块,而不是重新加载,这提高了性能。
依赖管理:Webpack使用__webpack_require__
来解析模块之间的依赖关系。当一个模块依赖于其他模块时,Webpack会分析这些依赖并在打包过程中生成相应的__webpack_require__
调用来加载所需的依赖。
异步加载:Webpack支持异步模块加载,__webpack_require__
.e(/* chunkId, moduleId */) 可以用来异步加载指定的模块或代码块。
错误处理:__webpack_require__
还处理模块加载过程中的错误。如果模块加载失败,它会抛出一个错误。
热模块替换(HMR):在开发过程中,__webpack_require__
支持热模块替换,允许在不刷新页面的情况下更新模块。
环境无关性:__webpack_require__
抽象了模块加载的细节,使得Webpack可以在不同的环境中工作,如浏览器和Node.js。
与CommonJS/AMD/ESM的兼容性:Webpack可以处理不同模块格式的代码,__webpack_require__
在内部转换这些模块格式,以确保它们可以在Webpack的打包结果中正常工作。
在Webpack打包的最终输出中,__webpack_require__
函数是核心的运行时部分,它确保了模块能够按照Webpack的逻辑被正确加载和执行。开发者通常不需要直接使用__webpack_require__
,因为它会自动处理模块的导入和导出。
sourceMap主要解决实际运行代码和开发源代码不一致的问题。
详情可参考官方文档:https://webpack.docschina.org/configuration/devtool/#root
常见策略如下:
Source Map 是一种映射文件,它将压缩或编译后的代码映射回原始源代码,使得开发者可以更容易地调试和维护生产环境中的代码。Webpack 在构建过程中可以生成 Source Map,并通过不同的策略来控制它们的生成和使用。以下是一些常见的 Webpack Source Map 策略:
false
:
"eval"
:
eval()
函数来加载模块,每个模块都会通过一个 eval
调用执行,并且生成一个包含 Source Map 的数据 URI。"cheap"
:
"cheap-module-source-map"
:
"cheap"
提供了更详细的调试信息,但仍然不包含完整的源代码。"cheap-module-eval-source-map"
:
"cheap-module-source-map"
,但使用 eval
来加载模块。"source-map"
:
"inline-cheap"
:
"inline-cheap-module"
:
"inline-cheap"
,但包含模块信息。"inline-source-map"
:
"hidden-source-map"
:
"nosources-source-map"
:
const HtmlWebpackPlugin = require('html-webpack-plugin')
const allModes = [
'eval',
'cheap-eval-source-map',
'cheap-module-eval-source-map',
'eval-source-map',
'cheap-source-map',
'cheap-module-source-map',
'inline-cheap-source-map',
'inline-cheap-module-source-map',
'source-map',
'inline-source-map',
'hidden-source-map',
'nosources-source-map'
]
module.exports = allModes.map(item => {
return {
devtool: item,
mode: 'none',
entry: './src/main.js',
output: {
filename: `js/${item}.js`
},
module: {
rules: [
{
test: /\.js$/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
}
]
},
plugins: [
new HtmlWebpackPlugin({
filename: `${item}.html`
})
]
}
})
// module.exports = [
// {
// entry: './src/main.js',
// output: {
// filename: 'a.js'
// }
// },
// {
// entry: './src/main.js',
// output: {
// filename: 'b.js'
// }
// }
// ]
其中几个主要的差异点在于是否eval、是否cheap、是否inline等,个人认为eval这块不太好理解。
在 Webpack 中使用 eval
相关的 Source Map 策略,如 "eval"
、"cheap-module-eval-source-map"
或 "inline-cheap-module-eval-source-map"
,主要是出于以下原因和好处:
无额外文件:
eval
可以在内存中执行模块,而不需要生成额外的文件。这意味着构建过程不需要写入文件到磁盘,可以减少 I/O 操作,从而提高性能。快速更新:
eval
的 HMR(热模块替换)可以实现快速更新。因为模块已经在内存中,所以更新时不需要重新加载文件。Source Map 内联:
eval
相关的 Source Map 策略允许将 Source Map 作为数据 URI 内联到浏览器中,这样浏览器可以直接使用 Source Map,而不需要额外的 HTTP 请求来获取 .map
文件。调试体验:
避免文件碎片:
eval
可以避免生成大量的小文件,这些小文件可能会在某些情况下减慢构建和更新速度。兼容性:
eval
可以提供更好的兼容性。开发体验:
eval
可以提供快速的模块加载和更新,从而提供更流畅的开发体验。然而,使用 eval
也有一些潜在的缺点:
安全性:
eval
可以执行字符串作为代码,这可能带来安全风险,尤其是在处理不受信任的代码时。性能影响:
eval
可能会对性能产生负面影响,尤其是在处理大型模块或复杂应用时。调试限制:
eval
可能会限制浏览器的调试能力,因为 eval
源码不会被计入常规的源码列表中。在 Webpack 配置文件中,可以通过 devtool
选项来设置 Source Map 策略:
module.exports = {
// ...
devtool: 'source-map', // 完整的 Source Map
// 或者
devtool: 'inline-source-map', // 内联 Source Map
// ...
};
"source-map"
)。通过合理选择 Source Map 策略,可以在保证调试便利性的同时,控制构建输出的大小和性能。
以个人实际开发经验来看:
开发环境下:cheap-module-eval-source-map (或者直接source-map)
生产环境:none
避免隐患
如果是内部系统,可以考虑使用nosouces-source-map,用于排查一些很难解决的线上问题
全称:Hot Modules Replacement
热替换只会将有更新的模块进行热插拔替换
使用
const webpack = require('webpack')
// 然后在plugin里添加
new webpack.HotModuleReplacementPlugin()
如果没有loader或自己没有手动处理该模块的热替换逻辑,webpack默认采取的是刷新整个页面
hotOnly: true(或者hot: only) // 只使用 HMR,不会 fallback 到 live reloading,可以保留之前的报错信息
打包时,热替换相关的代码会被自动删除
:实时调整 react 组件。
:此 loader 支持 vue 组件的 HMR,提供开箱即用体验。
import createEditor from './editor'
import background from './better.png'
import './global.css'
const editor = createEditor()
document.body.appendChild(editor)
const img = new Image()
img.src = background
document.body.appendChild(img)
// ============ 以下用于处理 HMR,与业务代码无关 ============
if (module.hot) {
let lastEditor = editor
module.hot.accept('./editor', () => {
// console.log('editor 模块更新了,需要这里手动处理热替换逻辑')
// console.log(createEditor)
const value = lastEditor.innerHTML
document.body.removeChild(lastEditor)
const newEditor = createEditor()
newEditor.innerHTML = value
document.body.appendChild(newEditor)
lastEditor = newEditor
})
module.hot.accept('./better.png', () => {
img.src = background
console.log(background)
})
}
Webpack 的热模块替换(Hot Module Replacement,简称 HMR)是一种在应用程序运行时替换、添加或删除模块,而无需重新加载整个页面的功能。HMR 可以在开发过程中显著提高效率,因为它允许即时反馈修改效果,无需手动刷新浏览器。
运作过程可以总结为:Webpack在开发模式下监测到源文件变更后,通过HMR客户端与服务器通信,仅替换和更新浏览器中受影响的模块,而无需重新加载整个页面。
以下是 Webpack 实现 HMR 的主要机制:
模块热更新 API(Module Hot Accept):
module.hot
对象访问 HMR API。module.hot.accept
方法用于接受依赖模块的更新,当依赖模块更新时,可以执行一些自定义逻辑。热更新事件监听:
模块标识:
更新检查:
模块替换:
状态保留:
依赖图更新:
运行时代码注入:
开发服务器:
浏览器端实现:
错误处理:
配置选项:
hot
选项进行配置,例如 hot: true
启用 HMR。示例代码:
if (module.hot) {
module.hot.accept('./module', () => {
console.log('Module has been hot updated!');
});
}
在这个示例中,module.hot.accept
用于接受特定模块的更新。当该模块更新时,回调函数会被执行。
通过这种方式,Webpack 的 HMR 功能可以在开发过程中提供即时反馈,极大地提高了开发效率。
前提:是使用Es Module实现的模块化
module.exports = {
mode: 'none',
entry: './src/index.js',
output: {
filename: 'bundle.js'
},
optimization: {
// 模块只导出被使用的成员
usedExports: true,
// 尽可能合并每一个模块到一个函数中
concatenateModules: true,
// 压缩输出结果
// minimize: true
}
}
Webpack 的 Tree Shaking 是一个在前端工程化中广泛使用的优化技术,其目的是移除代码库中未被引用的部分,从而减少最终打包文件的大小。以下是 Webpack 实现 Tree Shaking 的主要步骤和原理:
ES6 模块支持:
Tree Shaking 依赖于 ES6 模块的静态结构,因为 ES6 模块的 import
和 export
是静态可分析的。
静态分析:
Webpack 通过静态分析源代码来确定哪些模块或模块的一部分未被引用。
确定引用:
Webpack 检查模块的导出(exports)和导入(imports),确定哪些导出是被引用的。
排除未引用代码:
在打包过程中,Webpack 将排除那些没有被引用的模块或代码块。
副作用评估:
Webpack 会评估模块是否有副作用。如果模块被标记为具有副作用,即使没有被引用,它们也不会被排除。
配置选项:
Webpack 提供了配置选项来启用或禁用 Tree Shaking。例如,可以通过设置 treeShaking: true
来显式启用 Tree Shaking。
副作用标记:
使用 sideEffects: false
在 package.json
中标记整个包没有副作用,这会告诉 Webpack 该包中的所有模块都可以进行 Tree Shaking。
纯注释:
开发者可以在代码中使用 /* @__PURE__ */
注释来告诉 Webpack 某个位置的函数调用是纯函数,即使它们在静态分析中未被引用。
构建优化:
Webpack 在构建过程中应用 Tree Shaking 优化,移除未被引用的代码,减少输出文件的大小。
输出结果:
经过 Tree Shaking 后,Webpack 生成的最终打包文件只包含应用程序实际使用的代码,从而减少文件大小并提高加载性能。
Tree Shaking 是一个在现代前端构建工具中常见的优化特性,它通过静态分析和配置选项,帮助开发者移除未使用的代码,优化最终的打包结果。在 Webpack 中,Tree Shaking 通常在生产环境构建时自动应用,以确保最终的部署包尽可能精简。
默认值为true,即认为每个模块都有可能是包含副作用,具体情况具体分析。
在项目中正确设置 Webpack 的 sideEffects
属性,可以遵循以下步骤:
理解副作用:
确定代码或依赖的库中的哪些部分具有副作用。这包括修改全局状态、进行网络请求、读写文件等。
更新 package.json:
在 package.json
文件中,添加或更新 sideEffects
属性。你可以将其设置为 false
,如果你确定包中所有模块都没有副作用。
{
"name": "your-package-name",
"version": "1.0.0",
"sideEffects": false
}
使用通配符:
如果你只想标记包中的特定文件或文件夹没有副作用,可以使用通配符。例如,如果源代码在 src
文件夹中,可以这样设置:
{
"sideEffects": ["*.css", "!*.js"]
}
这表示所有 CSS 文件都有副作用,但 JavaScript 文件没有。
谨慎使用:
如果包确实包含有副作用的代码,不要将 sideEffects
设置为 false
,因为这可能导致 Webpack 错误地移除必要的代码。
模块级别的副作用:
如果包中有的模块有副作用,有的没有,你需要更细粒度地控制 Tree Shaking。你可以通过在文件顶部添加 /*#__PURE__*/
注释来标记这些模块,告诉 Webpack 这些模块没有副作用。
构建配置:
在 Webpack 配置文件中,可以使用 sideEffects
属性来控制整个项目的 Tree Shaking 行为:
module.exports = {
optimization: {
usedExports: true, // 尝试确定哪些导出是被使用的
providedExports: true, // 尝试确定哪些导出是被提供的
sideEffects: true, // 尝试确定哪些模块有副作用
},
// 其他配置...
};
测试和验证:
更改 sideEffects
设置后,进行彻底的测试以确保没有引入任何问题。验证打包后的代码是否按预期工作,并且没有删除掉重要的代码。
文档和维护:
更新项目文档,记录 sideEffects
的设置和原因。这对于维护和未来的代码审查非常重要。
依赖管理:
检查依赖项,确保它们的 sideEffects
属性设置正确。如果依赖项不正确地声明了副作用,可能会影响项目的 Tree Shaking 效果。
通过正确设置 sideEffects
属性,可以提高 Webpack 构建的性能和输出文件的效率,同时确保代码的正确性。
背景:打包的时候,所有代码都会被打包到一起
目的:优化代码颗粒度
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
mode: 'none',
entry: {
index: './src/index.js',
album: './src/album.js'
},
output: {
filename: '[name].bundle.js'
},
optimization: {
splitChunks: {
// 自动提取所有公共模块到单独 bundle
chunks: 'all'
}
},
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
}
]
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title: 'Multi Entry',
template: './src/index.html',
filename: 'index.html',
chunks: ['index']
}),
new HtmlWebpackPlugin({
title: 'Multi Entry',
template: './src/album.html',
filename: 'album.html',
chunks: ['album']
})
]
}
// import posts from './posts/posts'
// import album from './album/album'
const render = () => {
const hash = window.location.hash || '#posts'
const mainElement = document.querySelector('.main')
mainElement.innerHTML = ''
if (hash === '#posts') {
// mainElement.appendChild(posts())
import(/* webpackChunkName: 'components' */'./posts/posts').then(({ default: posts }) => {
mainElement.appendChild(posts())
})
} else if (hash === '#album') {
// mainElement.appendChild(album())
import(/* webpackChunkName: 'components' */'./album/album').then(({ default: album }) => {
mainElement.appendChild(album())
})
}
}
render()
window.addEventListener('hashchange', render)
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
const TerserWebpackPlugin = require('terser-webpack-plugin')
module.exports = {
mode: 'none',
entry: {
main: './src/index.js'
},
output: {
filename: '[name].bundle.js'
},
optimization: {
minimizer: [
// 内置的js压缩
new TerserWebpackPlugin(),
// css压缩
new OptimizeCssAssetsWebpackPlugin()
]
},
module: {
rules: [
{
test: /\.css$/,
use: [
// 'style-loader', // 将样式通过 style 标签注入
MiniCssExtractPlugin.loader,
'css-loader'
]
}
]
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title: 'Dynamic import',
template: './src/index.html',
filename: 'index.html'
}),
new MiniCssExtractPlugin()
]
}
Webpack 5 通过以下几个步骤实现代码分割(Code Splitting):
入口起点(Entry Points):
Webpack 从配置的入口起点开始分析,确定最初的模块依赖。
依赖图构建:
Webpack 递归地分析所有依赖项,构建一个依赖图,包括动态导入和静态导入。
分割点识别:
Webpack 识别代码中的分割点,如动态 import()
表达式,这些点可以作为分割代码的入口。
分割策略:
Webpack 使用不同的策略来决定如何分割代码。它可以自动分割代码,也可以通过配置来自定义分割逻辑。
生成 Chunk:
基于依赖图和分割策略,Webpack 生成多个 Chunk,每个 Chunk 包含一组模块。
Chunk 命名:
Webpack 为每个 Chunk 生成唯一的名称,以便在最终的输出中引用。
异步加载:
对于异步导入的模块,Webpack 生成异步加载的 Chunk,并在主文件中通过 __webpack_require__.e
来请求加载。
预加载和预获取:
Webpack 支持预加载(prefetch)和预获取(preload),允许开发者提前加载可能需要的 Chunk。
输出文件:
Webpack 将每个 Chunk 输出为单独的文件,并将这些文件引用到主文件或其他 Chunk 中。
运行时更新:
Webpack 的运行时包含逻辑来处理 Chunk 的加载,包括如何处理异步加载和按需加载。
HMR 集成:
Webpack 的热模块替换(HMR)与代码分割集成,允许在开发过程中动态更新 Chunk。
配置选项:
Webpack 提供了多个配置选项,如 optimization.splitChunks
,用于自定义代码分割的行为。
模块联邦(Module Federation):
Webpack 5 引入了模块联邦,允许多个 Webpack 构建之间共享模块,这也是一种代码分割的形式。
浏览器支持:
生成的代码分割逻辑兼容主流浏览器,确保分割的代码可以在浏览器中正确加载和执行。
通过这些步骤,Webpack 5 能够将大型应用程序拆分成更小的、可管理的代码块,这些代码块可以在运行时按需加载,从而提高应用程序的加载性能和用户体验。
生产模式下,文件模式使用hash
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCssAssetsWebpackPlugin = require('optimize-css-assets-webpack-plugin')
const TerserWebpackPlugin = require('terser-webpack-plugin')
module.exports = {
mode: 'none',
entry: {
main: './src/index.js'
},
output: {
filename: '[name]-[contenthash:8].bundle.js'
},
optimization: {
minimizer: [
new TerserWebpackPlugin(),
new OptimizeCssAssetsWebpackPlugin()
]
},
module: {
rules: [
{
test: /\.css$/,
use: [
// 'style-loader', // 将样式通过 style 标签注入
MiniCssExtractPlugin.loader,
'css-loader'
]
}
]
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title: 'Dynamic import',
template: './src/index.html',
filename: 'index.html'
}),
new MiniCssExtractPlugin({
filename: '[name]-[contenthash:8].bundle.css'
})
]
}
在 Webpack 中,runtimeChunk
配置选项用于控制运行时代码(runtime code)的生成和打包方式。运行时代码是指 Webpack 用来处理模块加载、热模块替换(HMR)和其他功能的代码。以下是 runtimeChunk
的主要作用:
分离运行时代码:
runtimeChunk
允许将运行时代码从应用程序的主要bundle中分离出来,单独打包成一个或多个独立的chunk。
优化性能:
将运行时代码分离到单独的chunk可以减少主bundle的大小,从而提高页面加载性能和速度。
便于缓存:
由于运行时代码通常不会频繁更改,将其分离到单独的chunk可以使得浏览器缓存更有效,因为只有更改的部分需要被更新。
配置灵活性:
runtimeChunk
可以配置为一个名称或一个配置对象,以便更细致地控制生成的chunk的名称、位置和行为。
支持多入口点:
当应用程序有多个入口点时,runtimeChunk
可以确保每个入口点使用独立的运行时代码,避免不同入口点之间的运行时代码冲突。
自定义运行时代码:
通过 runtimeChunk
,可以自定义运行时代码的生成逻辑,例如,可以插入自定义的模块加载逻辑或错误处理逻辑。
Webpack 5 中 runtimeChunk
的配置示例:
module.exports = {
// ...
optimization: {
runtimeChunk: {
name: (entrypoint)=> `runtime~${entrypoint.name}-.js`
},
},
// ...
};
还有可以配置文件缓存的策略
module.exports = {
// ...
cache: {
type: 'filesystem', // 使用文件系统缓存
cacheDirectory: path.resolve(__dirname, 'cache'), // 指定缓存目录
buildDependencies: {
config: [__filename], // 依赖的配置文件,当配置文件改变时缓存将失效
},
},
// ...
};
webpack.common.js
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
entry: './src/main.js',
output: {
filename: 'js/bundle.js'
},
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
},
{
test: /\.(png|jpe?g|gif)$/,
use: {
loader: 'file-loader',
options: {
outputPath: 'img',
name: '[name].[ext]'
}
}
}
]
},
plugins: [
new HtmlWebpackPlugin({
title: 'Webpack Tutorial',
template: './src/index.html'
})
]
}
webpack.dev.js
const webpack = require('webpack')
const merge = require('webpack-merge')
const common = require('./webpack.common')
module.exports = merge(common, {
mode: 'development',
devtool: 'cheap-eval-module-source-map',
devServer: {
hot: true,
contentBase: 'public'
},
plugins: [
new webpack.HotModuleReplacementPlugin()
]
})
webpack.prod.js
const merge = require('webpack-merge')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const CopyWebpackPlugin = require('copy-webpack-plugin')
const common = require('./webpack.common')
module.exports = merge(common, {
mode: 'production',
plugins: [
new CleanWebpackPlugin(),
new CopyWebpackPlugin(['public'])
]
})
使用DefinePlugin
const webpack = require('webpack')
module.exports = {
mode: 'none',
entry: './src/main.js',
output: {
filename: 'bundle.js'
},
plugins: [
new webpack.DefinePlugin({
// 值要求的是一个代码片段
API_BASE_URL: JSON.stringify('https://api.example.com')
})
]
}
或者搭配使用cross-env
Webpack 5 引入了模块联邦(Module Federation)功能,这是一种新的代码拆分和动态模块加载的方式,它允许多个独立的 Webpack 构建之间共享模块。模块联邦的实现基于以下关键概念:
远程资源:
共享依赖:
动态导入:
import()
语法来导入远程资源。构建配置:
ModuleFederationPlugin
插件来配置模块联邦。远程配置:
ModuleFederationPlugin
配置中,定义了远程依赖的名称和远程构建的入口点。版本协商:
环境抽象:
热模块替换(HMR):
构建隔离:
远程通信:
依赖图:
代码分割:
服务端渲染(SSR):
开发体验:
配置示例:
// webpack.config.js
const ModuleFederationPlugin = require('webpack/lib/container/ModuleFederationPlugin';
module.exports = {
// ...其他配置...
plugins: [
new ModuleFederationPlugin({
name: 'app', // 暴露的公共名称
filename: 'remoteEntry.js', // 远程入口文件
exposes: {
'./App': './src/App', // 暴露的模块和它们的路径
},
shared: ['react', 'react-dom'], // 共享依赖
}),
],
};
在这个示例中,ModuleFederationPlugin
被配置为暴露一个名为 app
的远程入口点,它将 ./src/App
组件暴露给其他联邦成员使用。同时,react
和 react-dom
被配置为共享依赖,这意味着所有使用这些依赖的联邦成员不需要再次打包它们。
通过这种方式,Webpack 5 的模块联邦功能允许开发者构建一个微前端架构,其中不同的应用程序或应用程序的部分可以独立开发、部署和维护,同时仍然能够共享和重用代码。
当 compiler 开始执行、解析和映射应用程序时,它会保留所有模块的详细要点。这个数据集合称为 “manifest”,当完成打包并发送到浏览器时,runtime 会通过 manifest 来解析和加载模块。无论你选择哪种 ,那些 import
或 require
语句现在都已经转换为 __webpack_require__
方法,此方法指向模块标识符(module identifier)。通过使用 manifest 中的数据,runtime 将能够检索这些标识符,找出每个标识符背后对应的模块。
参考官方网站:https://webpack.docschina.org/
参考上文
参考上文
参考上文
参考上文
在实际开发中,即使没有显式编写有关 module.hot
的逻辑,React组件依旧能够实现热更新,这主要是因为以下几个原因:
React 的内置支持:
React.lazy
进行代码分割或在 index.js
中直接导入组件时,React 能够处理这些模块的热更新。Webpack 的自动 HMR 支持:
module.hot
显式逻辑的情况下,自动更新模块。Babel 插件:
babel-plugin-react-refresh
插件可以为 React 组件提供快速刷新支持。这个插件会自动修改代码,以支持 React Fast Refresh。样式和资源的 HMR:
style-loader
、css-loader
等)来处理 HMR。当这些资源文件发生变化时,Webpack 可以替换掉旧的样式和资源,而不需要重新加载整个页面。框架或库的支持:
React Fast Refresh:
模块替换逻辑:
module.hot
,Webpack 也可能通过其他方式实现模块的替换。例如,使用 import()
动态导入的模块可以被 Webpack 替换而不需要刷新页面。开发服务器:
因此,即使没有显式编写 module.hot
逻辑,React 应用仍然可以实现热更新,这得益于 React、Webpack 和其他工具的内置支持。这些工具在幕后处理了大部分 HMR 相关的工作,使得开发者可以更专注于编写业务逻辑代码。
主要区别在于开发阶段。
webpack会先打包,然后启动开发服务器,请求服务器时直接给予打包结果。 而vite是直接启动开发服务器,请求哪个模块再对该模块进行实时编译。 由于现代浏览器本身就支持ES Module,会自动向依赖的Module发出请求。vite充分利用这一点,将开发环境下的模块文件,就作为浏览器要执行的文件,而不是像webpack那样进行打包合并。 由于vite在启动的时候不需要打包,也就意味着不需要分析模块的依赖、不需要编译,因此启动速度非常快。当浏览器请求某个模块时,再根据需要对模块内容进行编译。这种按需动态编译的方式,极大的缩减了编译时间,项目越复杂、模块越多,vite的优势越明显。 在HMR方面,当改动了一个模块后,仅需让浏览器重新请求该模块即可,不像webpack那样需要把该模块的相关依赖模块全部编译一次,效率更高。 当需要打包到生产环境时,vite使用传统的rollup进行打包,因此,vite的主要优势在开发阶段。另外,由于vite利用的是ES Module,因此在代码中不可以使用CommonJS
更多可参考这篇文章
https://juejin.cn/post/7155408694786654245?searchId=20240721213812DAEC56760836DB9D6D2A
可以参考这篇文章
https://juejin.cn/post/6990869970385109005?searchId=202407212127340BB133A404A4A1A86C45
在 Webpack 中,你可以使用多种方法来单独处理第三方库,以优化构建性能和输出结果。以下是一些常用的方法:
外部化(Externals):
通过配置 externals
,你可以指定某些库在最终打包文件中不被包含,而是在运行时从外部获取。
// webpack.config.js
module.exports = {
// ...
externals: {
'react': 'React',
'react-dom': 'ReactDOM',
},
};
代码分割(Code Splitting):
使用 SplitChunksPlugin
来自动或手动分割代码。你可以配置插件来分离第三方库。
// webpack.config.js
plugins: [
new SplitChunksPlugin({
chunks: 'all',
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendor',
chunks: 'all',
},
},
}),
];
动态导入(Dynamic Imports):
使用动态 import()
语法来按需加载第三方库,实现代码分割。
// 某个组件或模块中
if (condition) {
import('some-library').then((lib) => {
lib.doSomething();
});
}
预加载和预取(Preload and Prefetch):
使用 PreloadPlugin
或 PrefetchPlugin
来预加载或预取第三方库。
全局变量(Global Variables):
如果你不想处理某些全局可用的库(例如,通过 <script>
标签全局加载的库),可以在 Webpack 配置中声明它们。
// webpack.config.js
plugins: [
new webpack.ProvidePlugin({
jQuery: 'jquery',
}),
];
忽略(Ignore Plugin):
使用 IgnorePlugin
来忽略某些特定的模块,不将它们包含在最终的打包文件中。
模块替换(Module Replacement):
使用 NormalModuleReplacementPlugin
或 ModuleReplacementPlugin
来替换某些模块。
DedupePlugin:
使用 DedupePlugin
来避免将重复的库打包多次。
库的自定义别名(Custom Aliases for Libraries):
为第三方库设置别名,通过 resolve.alias
配置,可以控制库的导入方式。
优化配置(Optimization Configuration):
在 Webpack 的 optimization
配置中,可以对库进行特定的优化处理。
这些方法可以帮助你更好地管理和优化第三方库的使用,根据项目需求和构建目标选择合适的方法。在实际应用中,可能需要结合多种方法来达到最佳效果。
DLL(Dynamic Link Library)插件在 Webpack 中用于优化构建性能,特别是对于大型项目。它的主要目的是将一些不经常变化的库或模块预先打包好,这样在后续的构建中就可以避免重复编译这些模块,从而加快构建速度。
以下是 DLL 插件的一些关键点:
预先打包:
使用 DLL 插件可以创建一个包含多个模块的独立包,这些模块通常是第三方库,它们不经常变化。
避免重复编译:
当项目中的其他代码发生变化时,只需要重新编译变化的部分,而预先打包的库不需要重新编译。
加快构建速度:
由于 DLL 插件减少了需要处理的模块数量,因此可以显著加快构建速度。
配置 DLL 插件:
在 Webpack 配置中配置 DLL 插件,指定要预先打包的库或模块。
DLL 引用:
在项目的主应用配置中引用 DLL 包,确保在运行时可以正确加载预先打包的库。
DLL 缓存:
DLL 插件生成的包可以被缓存,这样在开发过程中或在部署后,这些库不需要被重新打包。
插件配合:
DLL 插件通常与 DllReferencePlugin
插件一起使用,后者用于在主应用的 Webpack 配置中引用 DLL 包。
多页面应用(MPA):
在多页面应用中,DLL 插件可以为每个页面或应用的共享库创建单独的包,进一步优化构建过程。
适用于生产环境:
虽然 DLL 插件在开发过程中也可以使用,但它更适用于生产环境,因为生产环境中对构建速度的要求更高。
示例配置:
使用 DLL 插件和 DllReferencePlugin 的基本配置示例:
// webpack.dll.config.js
const path = require('path');
const webpack = require('webpack');
module.exports = {
entry: {
vendor: ['library-a', 'library-b'], // 预先打包的库
},
output: {
path: path.join(__dirname, 'dist'),
filename: '[name].dll.js',
library: '[name]_[hash]',
},
plugins: [
new webpack.DllPlugin({
path: path.join(__dirname, 'dist', '[name]-manifest.json'),
name: '[name]_[hash]',
}),
],
};
// webpack.config.js
module.exports = {
// ...
plugins: [
new webpack.DllReferencePlugin({
context: __dirname,
manifest: require('./dist/vendor-manifest.json'),
}),
],
// ...
};
通过这种方式,DLL 插件可以显著提高大型项目的构建性能,尤其是在开发过程中库的变动不频繁时。
https://juejin.cn/post/6844904094281236487?searchId=202407212131153C3CC278036C80BAD414
https://juejin.cn/post/7138203576098095112?searchId=20240720174243E48F92E3C806E600AD64#heading-27
因篇幅问题不能全部显示,请点此查看更多更全内容
怀疑对方AI换脸可以让对方摁鼻子 真人摁下去鼻子会变形
女子野生动物园下车狼悄悄靠近 后车司机按喇叭提醒
睡前玩8分钟手机身体兴奋1小时 还可能让你“变丑”
惊蛰为啥吃梨?倒春寒来不来就看惊蛰
男子高速犯困开智能驾驶出事故 60万刚买的奔驰严重损毁