webpack配置全面讲解【完整篇】
文章目录
- 前言
- webpack 核心包:
- 配置文件导出三种方式:
- 在线配置 webpack
- 配置文件解析:
- 入口(Entry):
- 输出(Output):
- 加载器(Loaders):
- 插件(Plugins):
- 模式(Mode):
- 目标(Target):
- webpack 的 runtime 和 manifest
- 服务器
- 模块解析配置
- 优化配置
- 热模块替换(Hot Module Replacement, HMR):
- 代码分割(Code Splitting):
- 在 Node 中使用 webpack
前言
Webpack 是一个现代 JavaScript 应用程序的静态
模块打包工具
,它可以将项目中的所有模块
(JavaScript、图片、CSS 等)打包成一个或多个 bundle
。
Webpack 4 是一个重要的版本,它提供了零配置、合理的默认设置、性能改进和开箱即用的优化工具。本文将对webpack 4
进行介绍,后面会对其他版本特性进行补充
webpack 核心包:
-
webpack: 这是 Webpack 的
核心包
,提供了模块打包的能力。它能够处理项目中 JavaScript 模块的依赖关系,并生成最终的静态资源文件。 -
webpack-cli: 这是 Webpack 的
命令行工具
,允许你通过命令行界面来运行 Webpack。它提供了各种命令来控制 Webpack 的构建过程。 -
webpack-dev-server: 这是一个小型的 Express
开发服务器
,用于开发环境。它提供了热模块替换(HMR)功能,允许在开发过程中实时重新加载模块,而无需刷新整个页面。
配置文件导出三种方式:
默认使用的是 webpack.config.js
文件,也可以是其他名字,webpack -c xxx
,只需要通过-c
置顶文件就行,配置文件默认有是三种导出格式:
- 对象或数组:单个配置或多个配置
- 函数: 可以根据环境变量和条件导出不同的配置
- Promise:用于远程加载其他配置参数,最终返回结果
代码实例:
// 对象或数组
module.exports = [
{
output: {
filename: "./dist.js",
},
entry: "./app.js",
},
{
output: {
filename: "./dist-test.js",
},
entry: "./app.js",
},
];
// 函数
module.exports = function (env, argv) {
return {
mode: env.production ? "production" : "development",
devtool: env.production ? "source-map" : "eval",
plugins: [
new TerserPlugin({
terserOptions: {
compress: argv.mode === "production", // only if `--mode production` was passed
},
}),
],
};
};
// promise
module.exports = () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
entry: "./app.js",
/* ... */
});
}, 5000);
});
};
在线配置 webpack
- createapp.dev
配置文件解析:
入口(Entry):
- 入口点是应用程序的起点,Webpack 从这些点开始构建其依赖图。入口可以是一个文件或者一个文件数组。
// 单入口
entry: {
main:'./index.js',
}
// 多入口
entry: {
main: './src/app.js',
foo: './src/foo.js',
},
输出(Output):
- 输出属性指定了打包后的文件将如何被写入到磁盘上。它定义了输出文件的名称、路径以及它们在浏览器中被引用的方式。
output: {
filename: '[name].js', //hash选择:[hash],[contenthash] 或者 [chunkhash] 的长度可以使用 [hash:16]
path: __dirname + '/dist', //输出目录
publicPath: 'https://cdn.example.com/assets/[fullhash]/' // 设置cdn 和 hash
},
加载器(Loaders):
- Webpack 只能理解 JavaScript,
Loaders 允许 Webpack 处理其他类型的文件
,并将它们转换为 Webpack 能够理解的模块【模块转化】
。 - loader 可以使你在 import 模块时预处理文件。因此,loader 类似于其他构建工具中“任务(task)”,并提供了处理前端构建步骤的得力方式
- loader 支持配置和内联的方式书写
- loader 执行顺序:
从右到左(或从下到上)
的执行(execute)
module: {
rules: [
{
test: /\.css$/,
exclude: /node_modules/,
use: [
{ loader: 'style-loader' },
{
loader: 'css-loader',
options: {
modules: true, // css模块化
},
},
{ loader: 'sass-loader' },
],
},
],
}
插件(Plugins):
- 插件在 Webpack 构建过程中提供了更多的功能,如优化、压缩、分割代码等。插件可以在整个构建过程中的特定时机注入上下文。
- Webpack 内部广泛使用
Tapable
(一个 Node.js 库),它提供了一个发布-订阅(pub-sub)
机制,允许用户在特定的钩子(hook)上注册监听器(listener)。
const HtmlWebpackPlugin = require("html-webpack-plugin");
const webpack = require("webpack"); // 访问内置的插件
plugins: [
new webpack.ProgressPlugin(),
new HtmlWebpackPlugin({ template: "./src/index.html" }),
];
模式(Mode):
- 模式决定了 Webpack 是为开发环境还是生产环境构建应用程序。它可以是
development
、production
或none
。模式影响 Webpack 的优化和警告。
// 模式
mode: 'production', //development
目标(Target):
用于告知 Webpack 为哪个环境或目标(target)进行构建编译 , 默认情况下,
target 的值是 "web"
// 模式
target: 'web', //node、webworker 。。。。
webpack 的 runtime 和 manifest
- manifest 包含文件一般包含 ,如模块及模块依赖、每个模块的源码映射、异步加载的模块,等。
- runtime 会通过 manifest 来解析和加载模块。
服务器
配置开发服务器,包括代理、热模块替换等
devServer: {
// 设置开发服务器监听的主机地址
// '0.0.0.0' 表示服务器将接受所有可用网络接口的请求
host: '0.0.0.0',
// 设置开发服务器监听的端口号
port: 3000,
// 设置为 true 时,当 webpack 编译完成后,会自动在浏览器中打开应用
open: true,
// 启用模块热替换(HMR),允许在不刷新整个页面的情况下,实时更新模块
hot: true,
// 为单页应用(SPA)提供支持,当访问不存在的路由时,会返回 index.html
historyApiFallback: true,
// 开启 gzip 压缩,提高传输效率
compress: true,
// 指定了 public 目录下的文件作为静态资源
// 当你访问如 /js/index.js 这样的路径时,devServer 会从 public 目录提供文件
static: {
directory: path.join(__dirname, 'public')
},
// 配置代理,用于开发环境的后端接口代理
proxy: {
// 当请求前缀为 /api 时,触发代理
'/api': {
// 代理目标服务器的地址
target: 'http://backend.server.com',
// 配置是否改变请求头中的origin字段
changeOrigin: true,
// URL 重写,这里将请求中的 /api 替换为空字符串 例如,将 /api/user 重写为 /user
pathRewrite: { '^/api': '' },
},
},
},
模块解析配置
resolve: {
alias: { //创建 import 或 require 的别名
@: path.resolve(__dirname, 'src/'),
_: path.resolve(__dirname, 'src/utils/'),
},
extensions: ['.js', '.jsx', '.ts','.tsx'], //尝试按顺序解析这些后缀名
modules: [path.resolve(__dirname, 'src'), 'node_modules'], //告诉 webpack 解析模块时应该搜索的目录。
},
优化配置
配置代码压缩、优化分割代码块。
// 优化
optimization: {
minimize:true, // 开启优化
// minimizer 属性用于配置压缩工具,用于压缩 JavaScript 和 CSS 文件
minimizer: [
// TerserPlugin 是一个插件,用于压缩 JavaScript 文件
new TerserPlugin({
// terserOptions 用于传递给 Terser 的选项
terserOptions: {
// compress 属性用于配置压缩选项
compress: {
// drop_console 选项设置为 true,将删除所有的 console 语句
drop_console: true
}
},
// extractComments 选项设置为 false,以防止插件提取注释到单独文件
extractComments: false
}),
// OptimizeCSSAssetsPlugin 用于压缩 CSS 文件
new OptimizeCSSAssetsPlugin({})
],
// splitChunks 属性用于配置代码分割选项
splitChunks: {
// chunks 属性用于指定应该分割哪些 chunks,'all' 表示所有 chunks
chunks: 'all',
// minSize 属性定义了分割出的 chunk 的最小大小,这里是 20000 字节
minSize: 20000,
// minRemainingSize 属性定义了分割后剩余模块的最小大小,这里设置为 0
minRemainingSize: 0,
// minChunks 属性定义了模块应该被分割的最小 chunk 数量,这里是 1
minChunks: 1,
// maxAsyncRequests 属性定义了按需加载时的最大并行请求数,这里是 5
maxAsyncRequests: 5,
// maxInitialRequests 属性定义了初始加载时的最大并行请求数,这里是 3
maxInitialRequests: 3,
// automaticNameDelimiter 属性定义了自动生成的 chunk 名称之间的分隔符
automaticNameDelimiter: '~',
// automaticNameMaxLength 属性定义了自动生成的 chunk 名称的最大长度,这里是 30
automaticNameMaxLength: 30,
// cacheGroups 属性用于配置缓存组,用于更细致地控制代码分割
cacheGroups: {
// defaultVendors 缓存组用于处理来自 node_modules 的第三方库
defaultVendors: {
// test 属性用于定义哪些模块应该被包含在这个缓存组
test: /[\\/]node_modules[\\/]/,
// priority 属性定义了这个缓存组的优先级,-10 表示较低优先级
priority: -10,
// reuseExistingChunk 选项设置为 true,表示尽可能重用现有的 chunk
reuseExistingChunk: true
},
// default 缓存组用于处理应用程序代码
default: {
// minChunks 属性定义了模块应该被分割的最小 chunk 数量,这里是 2
minChunks: 2,
// priority 属性定义了这个缓存组的优先级,-20 表示较低优先级
priority: -20,
// reuseExistingChunk 选项设置为 true,表示尽可能重用现有的 chunk
reuseExistingChunk: true
}
}
}
},
热模块替换(Hot Module Replacement, HMR):
- HMR 是一种功能,允许在应用程序运行时替换、添加或删除模块,而无需重新加载整个页面。
- 原理:
- 浏览器和服务器建立socket 通道
- 修改代码后,webpack compiler 发出
update
事件, - hmr 请求 manifest和chunk,根据页面内容配置,更新模块
// webpack.config.js
devServer:{
hot:true
}
plugins: [
new webpack.HotModuleReplacementPlugin()
]
// component.js
function component() {
const element = document.createElement('input');
element.value = 123;
return element;
}
let element = component();
document.body.appendChild(element);
if (module.hot) {
module.hot.accept('./print.js', function() {
console.log('热更新进入!');
const value = document.getElementsByTagName("input")[0].value.value;
element = component(); // 重新渲染 component 以更新点击事件处理程序
document.body.appendChild(element);
// 回显数据
document.getElementsByTagName("input")[0].value.value = value
})
}
代码分割(Code Splitting):
- 代码分割是 Webpack 的一个特性,可以将代码分割成多个块,这些块可以按需加载或者并行加载,从而优化加载时间和性能。
- 代码拆分手段:import 动态导入、ensure方法、SplitChunksPlugin插件、多入口
- 动态导入(Dynamic Imports)
动态导入是实现代码分割最简单的方式之一。你可以使用 import()
函数来动态地加载模块。
// 1. 直接使用
if (someCondition) {
import("./otherModule.js").then((otherModule) => {
otherModule.doSomething();
});
}
// 2. promise 返回
function loadModule(modulePath) {
return new Promise((resolve, reject) => {
import(modulePath).then(
(module) => {
resolve(module);
},
(err) => {
reject(err);
}
);
});
}
// 动态加载模块
loadModule("./otherModule.js")
.then((module) => {
module.doSomething();
})
.catch((err) => {
console.error("模块加载失败", err);
});
在 Webpack 配置中,你不需要做任何特别的配置来处理动态导入,因为 Webpack 已经自动支持了。
- 使用
require.ensure
require.ensure
是一个更老的代码分割方法,require.ensure()
是 webpack 特有的,已被 import()
取代。。
// 参数1: dependencies 依赖数组
// 参数2: callback 回调函数
// 参数3: errorCallback: 失败回调
// 参数4: chunkName
// Webpack会创建一个名为 `feature` 的新chunk。
require.ensure(
[],
() => {
const module = require("./module");
module.someFunction();
},
"feature"
);
- SplitChunksPlugin
SplitChunksPlugin
是 Webpack 4+ 引入的一个插件,用于控制代码块的分割逻辑。以下是一个基本的配置示例:
const webpack = require("webpack");
module.exports = {
// ...
optimization: {
splitChunks: {
chunks: "all", // 代码分割的chunk类型,可选值有 'initial'、'async' 或 'all'
minSize: 20000, // 最小chunk大小,默认单位是byte
minChunks: 1, // 最小chunk数量,默认是1
maxAsyncRequests: 5, // 按需加载时的最大并行请求数
maxInitialRequests: 3, // 入口点的最大并行请求数
automaticNameDelimiter: "~", // 分割出的chunk文件名的连接符
cacheGroups: {
defaultVendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
reuseExistingChunk: true,
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
},
},
},
// ...
};
- 多入口拆分
如果你想要手动拆分第三方库,可以在 entry
属性中指定:
entry: {
bundle: './src/index.js',
vendor: ['react', 'react-dom'] // 将React和ReactDOM拆分出去
},
output: {
filename: '[name].js' // 根据entry的key来命名
},
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendor',
chunks: 'all'
},
},
},
},
-
摇树优化(Tree Shaking):
- 摇树优化是一种去除应用程序中未引用代码的技术,通常与 ES6 模块一起使用。
-
懒加载(Lazy Loading):
- 懒加载是一种按需加载模块的技术,可以减少初始加载时间。
-
配置(Configuration):
- Webpack 的配置是通过一个名为
webpack.config.js
的文件来进行的,它定义了入口、输出、加载器、插件等。
- Webpack 的配置是通过一个名为
-
解析(Resolving):
- 解析是 Webpack 确定如何处理文件的过程,包括如何找到文件、如何解析文件中的模块请求等。
-
缓存(Caching):
- Webpack 通过缓存来提高构建性能,它会跳过未发生变化的模块,避免不必要的重新构建。
-
记录(Profiling):
- Webpack 可以生成构建的记录,帮助开发者分析和优化构建过程。
-
持久化(Persistence):
- Webpack 4 引入了持久化缓存,即使在构建之间也可以保持缓存状态。
在 Node 中使用 webpack
const webpack = require("webpack"); // 访问 webpack 运行时(runtime)
const configuration = require("./webpack.config.js");
let compiler = webpack(configuration); //传入配置
new webpack.ProgressPlugin().apply(compiler);
compiler.run(function (err, stats) {
if (err) {
console.log("编译出错");
}
});