1.介绍

Webpack 是一个现代 JavaScript 应用程序的静态模块打包工具,它将 JavaScript、CSS、图片、字体等资源文件打包成一个或多个静态文件,以供浏览器使用。当 webpack 处理应用程序时,它会在内部从一个或多个入口点构建一个 依赖图(dependency graph),然后将项目中所需的每一个模块组合成一个或多个 bundles,它们均为静态资源。
基本概念
1.入口(Entry)
入口起点(entry point) 指示 webpack 应该使用哪个模块,来作为构建其内部 依赖图(dependency graph) 的开始。进入入口起点后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的。
默认值是 ./src/index.js,但你可以通过在 webpack configuration 中配置 entry 属性,来指定一个(或多个)不同的入口起点。
入口是 Webpack 处理项目的起点。它指定了 Webpack 应该从哪个文件开始分析和构建模块依赖图。一般来说,入口文件是项目的主要 JavaScript 文件。
2.输出(output)
output 属性告诉 webpack 在哪里输出它所创建的 bundle,以及如何命名这些文件。主要输出文件的默认值是 ./dist/main.js,其他生成文件默认放置在 ./dist 文件夹中。输出配置指定了 Webpack 打包后文件的存放位置和文件名称。通常,输出目录会存放在 dist 目录中,文件名称可以通过配置来定制。
output.filename 和 output.path 属性 告诉 webpack bundle 的名称,以及想要 bundle 生成(emit)到哪里。
3.加载器(loader)
webpack 默认只能理解 JavaScript 和 JSON 文件,loader 让 webpack 能够去处理其他类型的文件,并将它们转换为有效 模块,以供应用程序使用,以及被添加到依赖图中。Loaders 是 Webpack 的一个强大功能,它让你能够转换和处理各种类型的文件(如 CSS、图片、TypeScript、SASS 等),让它们可以作为模块导入到 JavaScript 文件中。
4.插件(Plugins)
Plugins 用来执行各种各样的任务,比如优化输出文件、注入环境变量、生成 HTML 文件等。它们在 Webpack 构建过程中执行各种操作。插件允许你在构建过程中做一些额外的操作(例如:代码压缩、生成 HTML 文件、环境变量注入等)。Webpack 插件通过 webpack 对象的 plugins 属性进行配置。
5.模块(Modules)
Webpack 的核心理念是"一切皆模块"。无论是 JavaScript、CSS、图片,还是字体文件等,都可以被视作模块。在 Webpack 中,模块是应用的基本单位,Webpack 通过模块化来处理所有资源。Webpack 会通过依赖图找到所有模块,进行打包。
6.模式(mode)
通过选择 development, production 或 none 之中的一个,来设置 mode 参数,可以启用 webpack 内置在相应环境下的优化。其默认值为 production。
- development 模式 主要关注开发过程中的效率和可调试性,提供详细的错误信息和调试支持,构建速度较快,但输出的代码未经过压缩,体积较大。
- production 模式 主要关注优化性能和代码体积,启用代码压缩、树摇、分离代码等优化手段,构建速度较慢,但输出的代码小且高效,更适合用于生产环境。
7.开发服务器(Development Server)
Webpack 提供了一个内置的开发服务器 webpack-dev-server,用于在开发过程中提供热更新、自动刷新等功能。可以通过配置 devServer 来启用这个功能。
typescript
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
mode: 'development', // 设置模式
entry: './src/index.js', // 入口文件
output: {
filename: 'bundle.js', // 输出文件名
path: path.resolve(__dirname, 'dist'), // 输出目录
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: 'babel-loader',
},
],
},
plugins: [
new HtmlWebpackPlugin({
template: './src/index.html',
}),
],
devServer: {
contentBase: './dist', // 开发服务器提供的静态文件目录
hot: true, // 启用热更新
},
};
2.入口Entry
它指定了打包过程的入口文件。entry 配置项的主要作用是告诉 Webpack 从哪个文件开始打包。Webpack 会从这个文件开始,递归地分析所有依赖,最终将这些依赖和文件打包成最终的输出。默认情况下,entry 可以是一个字符串,表示单一的入口文件;也可以是一个对象,表示多个入口文件。entry: string | [string]
用于描述入口的对象。可以使用如下属性:
- dependOn: 当前入口所依赖的入口。它们必须在该入口被加载前被加载。
- filename: 指定要输出的文件名称。可以是字符串、模板或函数。
- [name]:入口文件的名称
- [contenthash]:基于文件内容生成的哈希值,用于缓存优化
- [id]:模块的 ID
- [chunkhash]:模块内容生成的哈希值,用于缓存优化
- import: 指定启动时需要加载的模块。
- library: 指定 library 选项,为当前 entry 构建一个 library。用于将打包的代码暴露为一个库,允许其他 JavaScript 环境(比如浏览器、Node.js 或其他框架)使用。libraryTarget:指定库的输出目标,这里使用了 umd(通用模块定义)格式,兼容多种模块化系统。
- runtime: 处理 Webpack 生成的运行时代码,并将其提取到单独的文件中。如果设置了,就会创建一个新的运行时 chunk。在 webpack 5.43.0 之后可将其设为 false 以避免一个新的运行时 chunk。
- publicPath: 当该入口的输出文件在浏览器中被引用时,为它们指定一个公共 URL 地址。指定资源的公共 URL 地址,即所有通过 Webpack 打包的资源(如 JavaScript、CSS、图片等)在浏览器中引用时的基础 URL。它允许你在不同的环境下(开发、生产等)动态地配置资源的加载路径。
1.单入口配置
最简单的形式是通过指定一个文件路径,告诉 Webpack 从哪个文件开始构建。
typescript
module.exports = {
entry: './src/index.js', // 单一入口文件
output: {
filename: 'bundle.js', // 输出的文件
path: path.resolve(__dirname, 'dist'), // 输出目录
},
};
2.多入口配置
如果一个项目有多个入口文件,例如一个多页面应用(MPA),你可以使用一个对象来指定多个入口点。每个入口文件会生成一个独立的输出文件。
typescript
module.exports = {
entry: {
app: './src/app.js',
admin: './src/admin.js',
},
output: {
filename: '[name].js', // 使用入口文件名作为输出文件名
path: path.resolve(__dirname, 'dist'),
},
};
3.动态入口
你也可以使用一个函数来动态返回入口配置。这在根据不同的环境或条件来设置入口文件时非常有用。
typescript
module.exports = (env) => {
return {
entry: env.production ? './src/index.prod.js' : './src/index.dev.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
};
};
4.数组形式
也可以为同一个入口点配置多个模块。通过将多个文件放入一个数组中,你可以告诉 Webpack 从多个文件开始打包。Webpack 会将这些文件合并为一个入口文件。
typescript
module.exports = {
entry: ['./src/file_1.js', './src/file_2.js'],
output: {
filename: 'bundle.js',
},
};
3.输出(output)
在 Webpack 中,output 配置项用于指定打包后的文件如何输出。它控制了 Webpack 构建的输出路径、文件名称、以及其他相关的配置,决定了如何组织和存储打包后的文件。
output 配置项的主要属性
-
path
:指定了输出文件的目标目录。它必须是一个绝对路径(相对路径会引发错误)。 这个路径定义了 Webpack 打包后的文件存放位置。jsconst path = require('path'); module.exports = { output: { path: path.resolve(__dirname, 'dist'), // 输出文件存放目录 }, };
-
filename
:
filename
属性用于设置输出文件的名称。它可以使用模板变量(例如:[name]
、[hash]
、[chunkhash]
等)来根据文件的特征动态命名。 通常,filename
会用于定义 JavaScript 文件的名称。jsmodule.exports = { output: { filename: 'bundle.js', // 输出文件名 }, };
你也可以使用模板字符串来生成文件名,以便支持缓存和版本控制:
jsmodule.exports = { output: { filename: '[name].[contenthash].js', // 输出文件名包含哈希值,便于缓存 }, };
[name]
:表示入口的名称。[hash]
:表示整个构建的哈希值,常用于生成唯一文件名。[chunkhash]
:表示特定 chunk(代码块)的哈希值,适用于按需加载的模块,能更精确地控制缓存。[contenthash]
:表示文件内容的哈希值,适用于文件内容变化时修改文件名,通常用在生产环境中。
-
publicPath
:用于指定加载资源(如 JS、CSS、图片等)时的 URL 路径。它可以是一个相对路径或绝对路径,指示浏览器从哪里加载资源。如果你在服务器上部署文件时需要设置路径,这个配置很有用。可以确保在服务器上资源文件能够正确加载,特别是在有 CDN 或特定路径需求时。jsmodule.exports = { output: { publicPath: '/assets/', // 所有资源都会通过 /assets/ 路径加载 }, };
-
例如,假设你的文件结构是:
/assets/js/bundle.js /assets/css/style.css
那么在 HTML 中引入这些资源时,会自动加上
/assets/
前缀。 -
对于 CDN 资源,可以配置为:
jsoutput: { publicPath: 'https://cdn.example.com/', // CDN 地址 },
-
-
library
和libraryTarget
:Webpack 可以将构建结果暴露为可复用的库,适用于开发 JavaScript 库和工具。library
配置项用于指定输出的代码如何暴露给全局作用域,通常在构建库时使用。你可以选择将代码暴露为一个变量、一个对象,或者其他目标。libraryTarget
用于指定输出文件的模块格式。它的值可以是var
、umd
、commonjs
等。
jsmodule.exports = { output: { library: 'MyLibrary', // 使得打包后的文件可以通过 MyLibrary 变量访问 libraryTarget: 'umd', // 输出的文件兼容多种模块格式 }, };
libraryTarget
可以设置为:var
:将库暴露为全局变量(适用于浏览器)。umd
:通用模块定义,支持浏览器、CommonJS 和 AMD 模块。commonjs
:以 CommonJS 模式输出(适用于 Node.js 环境)。
-
chunkFilename
:指定了异步加载的代码块(chunk)生成的文件名。它通常和代码拆分(Code Splitting)一起使用,将代码分割成多个文件,按需加载。jsmodule.exports = { output: { chunkFilename: '[name].[chunkhash].js', // 非入口文件(按需加载的文件)命名 }, };
chunkFilename
主要用于动态加载的模块文件。它会生成类似于main.chunk.js
或vendor.chunk.js
这样的文件。 -
pathinfo
:是一个布尔值,决定是否在输出的文件中包含模块的信息(如文件的路径信息)。这对于调试很有帮助,但会增加文件体积,因此在生产环境中一般会禁用。jsmodule.exports = { output: { pathinfo: true, // 输出文件中包含模块的路径信息 }, };
在开发模式下,
pathinfo
默认为true
,会在控制台或源代码中显示有关模块的信息。生产环境中,通常会禁用该选项以减小文件体积。
4.加载器(loader)
Webpack 默认只处理 JavaScript 文件,但在现代前端开发中,我们往往需要处理各种不同类型的文件,比如:CSS 文件、图片(如 PNG、JPG、SVG 等)、字体(如 TTF、WOFF 等)、TypeScript 文件、SASS/LESS 文件、JSON 文件以及其他自定义文件格式。加载器通过将这些资源转换为 Webpack 能够处理的模块,使得 Webpack 能够打包、优化这些资源。
Webpack 通过加载器将不同格式的文件转换成模块,整个过程可以理解为:
- 解析文件:当 Webpack 遇到某个文件时,它根据配置的规则判断该文件应该使用哪个加载器进行处理。
- 执行加载器:加载器会对文件内容进行转换。例如,使用 babel-loader 转换 ES6+ 代码为 ES5,或者用 css-loader 将 CSS 文件转换为 JavaScript 模块。
- 返回处理后的内容:加载器会返回处理后的内容,Webpack 会将其继续处理,直到所有依赖的模块都被解析完毕。
- loader 支持链式调用。链中的每个 loader 会将转换应用在已处理过的资源上。一组链式的 loader 将按照相反的顺序执行。链中的第一个 loader 将其结果(也就是应用过转换后的资源)传递给下一个 loader,依此类推。最后,链中的最后一个 loader,返回 webpack 所期望的 JavaScript。
- loader 可以是同步的,也可以是异步的。
- loader 运行在 Node.js 中,并且能够执行任何操作。
- loader 可以通过 options 对象配置(仍然支持使用 query 参数来设置选项,但是这种方式已被废弃)。
js
module.exports = {
module: {
rules: [
{
test: /\.css$/, // 处理 .css 文件
use: ['style-loader', 'css-loader'], // 使用 css-loader 和 style-loader
},
{
test: /\.js$/, // 处理 .js 文件
exclude: /node_modules/, // 排除 node_modules 文件夹中的文件
use: 'babel-loader', // 使用 babel-loader 进行转换
},
],
},
};
常见的加载器
-
babel-loader
(用于 JavaScript 转译)babel-loader
用于将 ES6+ 代码转译为浏览器兼容的 ES5 代码。- 配合 Babel 使用,通常需要配合
.babelrc
配置文件或babel.config.js
文件来指定使用的转译规则。
示例配置:
js{ test: /\.js$/, exclude: /node_modules/, use: { loader: 'babel-loader', options: { presets: ['@babel/preset-env'], // 使用 @babel/preset-env 转译 ES6+ 语法 }, }, }
-
css-loader
(用于处理 CSS)css-loader
用于解析和转换 CSS 文件。它会将 CSS 转换成一个 JavaScript 模块,允许你通过import
语句导入 CSS 文件。
示例配置:
js{ test: /\.css$/, use: ['style-loader', 'css-loader'], // 先用 css-loader 解析,再用 style-loader 将其注入到 HTML 文件中 }
-
style-loader
(用于将 CSS 注入到 HTML 中)style-loader
用于将 JavaScript 中的 CSS 插入到 HTML 文件的<style>
标签中。它通常和css-loader
一起使用。
示例配置:
js{ test: /\.css$/, use: ['style-loader', 'css-loader'], }
-
sass-loader
、less-loader
(用于处理 SASS/LESS)sass-loader
和less-loader
分别用于处理 SASS 和 LESS 文件,并将它们转换为 CSS。使用时通常还需要配合css-loader
和style-loader
。
示例配置(处理 SASS 文件):
js{ test: /\.scss$/, use: ['style-loader', 'css-loader', 'sass-loader'], // 顺序很重要 }
-
file-loader
和url-loader
(用于处理文件)file-loader
用于将文件复制到输出目录,并返回文件的 URL。它常用于图片、字体等资源的处理。url-loader
类似于file-loader
,但它可以将小于指定大小的文件转为 base64 编码,减少网络请求。
示例配置(处理图片):
js{ test: /\.(png|jpg|gif)$/, use: 'file-loader', // 使用 file-loader 处理图片文件 }
示例配置(使用 url-loader,将图片小于 8kb 的转换为 base64):
js{ test: /\.(png|jpg|gif)$/, use: 'url-loader?limit=8192', // 小于 8KB 的图片将转为 base64 }
-
html-loader
(用于处理 HTML 文件中的资源)html-loader
用于将 HTML 文件中的资源(如图片、字体)转换为合适的路径。它会将 HTML 文件中的静态资源引入到打包结果中。
示例配置:
js{ test: /\.html$/, use: 'html-loader', // 处理 HTML 文件中的资源 }
-
json-loader
(用于加载 JSON 文件)json-loader
用于加载 JSON 文件。自 Webpack 2 版本起,JSON 文件已经被 Webpack 内置支持,因此不需要额外的 loader,除非使用特殊的配置或版本。
示例配置:
js{ test: /\.json$/, use: 'json-loader', // 不常用,Webpack 2 以上版本已内建支持 }
-
ts-loader
或awesome-typescript-loader
(用于 TypeScript 文件)ts-loader
和awesome-typescript-loader
用于将 TypeScript 文件(.ts/.tsx)转译为 JavaScript 文件。
示例配置:
js{ test: /\.tsx?$/, use: 'ts-loader', // 使用 ts-loader 处理 TypeScript 文件 }
加载器的执行顺序
当 Webpack 处理一个文件时,它会o从右到左(或从下到上)地取值(evaluate)/执行(execute)配置的加载器。Webpack 会从 use 数组的 右向左 执行加载器(如果有多个加载器时)。这意味着,首先应用数组中的最后一个加载器,然后向前应用加载器。需要注意的是,加载器的顺序非常重要,先执行的加载器处理内容较为原始,后执行的加载器处理的是更高层次的内容。
例如,处理 scss
文件时:
sass-loader
:将 SASS 编译为 CSS。css-loader
:将 CSS 转换为 JavaScript 模块。style-loader
:将最终的 CSS 插入到 HTML 文件中。
自定义 loader
自定义 loader是一个 Node.js 模块,它导出一个函数来处理文件内容。
js
module.exports = function (source) {
// 对文件内容进行处理
return `export default ${JSON.stringify(source)}`;
};
然后在 Webpack 配置中使用自定义 loader:
js
module: {
rules: [
{
test: /\.txt$/,
use: path.resolve(__dirname, 'my-loader.js') // 使用自定义 loader
}
]
}
5.插件(Plugins)
插件目的在于解决 loader 无法实现的其他事。Webpack 插件是一个具有 apply 方法的 JavaScript 对象。apply 方法会被 webpack compiler 调用,并且在 整个 编译生命周期都可以访问 compiler 对象。
插件不同于加载器(Loader),加载器主要负责转换资源文件,而插件则可以在构建过程中执行更复杂的任务,比如优化、打包管理、环境配置、文件生成等。通过插件,开发者可以干预 Webpack 的构建过程,实现从构建流程的各个环节对项目进行深入的定制和优化。
插件通过 Webpack 提供的 生命周期钩子(Hooks) 与构建过程进行交互。在构建过程中,Webpack 会触发一系列的事件,插件通过监听这些事件并执行相应的操作来完成工作。
插件通常是在 Webpack 配置文件中通过 plugins 数组进行配置,插件的调用是同步或异步的,具体取决于插件的设计。
插件通常是通过 new PluginName()
的方式来初始化,并传入必要的配置参数。WebPack 配置文件中的 plugins
是一个数组,所有的插件实例都在这个数组中。
js
const HtmlWebpackPlugin = require('html-webpack-plugin');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
// ...其他配置
plugins: [
new CleanWebpackPlugin(), // 在每次构建之前清理输出目录
new HtmlWebpackPlugin({
template: './src/index.html', // 使用自定义的 HTML 模板
inject: 'body', // 将脚本标签插入到 <body> 中
}),
],
};
常见插件
-
HtmlWebpackPlugin
HtmlWebpackPlugin
用于简化 HTML 文件的创建,特别是在 Webpack 打包时自动插入所有生成的资源文件(如 JS、CSS)。- 它能够自动生成一个
index.html
文件,并将输出的 JS 和 CSS 文件插入到该文件中。
示例配置:
jsconst HtmlWebpackPlugin = require('html-webpack-plugin'); module.exports = { plugins: [ new HtmlWebpackPlugin({ template: './src/index.html', // 自定义模板 filename: 'index.html', // 输出文件名 inject: 'body', // 将所有 JS 脚本标签插入到 <body> 中 }), ], };
-
CleanWebpackPlugin
CleanWebpackPlugin
插件用于在每次构建前清空输出目录。这样可以确保不会留下旧的文件,避免生成无用文件。- 它通常与
output.path
配合使用。
示例配置:
jsconst { CleanWebpackPlugin } = require('clean-webpack-plugin'); module.exports = { plugins: [ new CleanWebpackPlugin(), // 清理 dist 目录 ], };
-
MiniCssExtractPlugin
MiniCssExtractPlugin
用于将 CSS 从 JavaScript 文件中提取出来,生成独立的 CSS 文件。这对于生产环境中的性能优化非常有用,特别是当你有大量的 CSS 时。
示例配置:
jsconst MiniCssExtractPlugin = require('mini-css-extract-plugin'); module.exports = { module: { rules: [ { test: /\.css$/, use: [MiniCssExtractPlugin.loader, 'css-loader'], }, ], }, plugins: [ new MiniCssExtractPlugin({ filename: '[name].[contenthash].css', // 输出文件名包含哈希值 }), ], };
-
TerserWebpackPlugin
TerserWebpackPlugin
是 Webpack 默认用于压缩 JavaScript 代码的插件。它可以删除无用的代码,减小输出文件的体积。- 默认情况下,在
production
模式下启用此插件。
示例配置:
jsconst TerserWebpackPlugin = require('terser-webpack-plugin'); module.exports = { optimization: { minimize: true, // 开启代码压缩 minimizer: [new TerserWebpackPlugin()], }, };
-
CopyWebpackPlugin
CopyWebpackPlugin
用于将静态资源从源目录复制到输出目录(比如复制图片、字体等文件)。
示例配置:
jsconst CopyWebpackPlugin = require('copy-webpack-plugin'); module.exports = { plugins: [ new CopyWebpackPlugin({ patterns: [ { from: 'src/assets', to: 'assets' }, // 将 src/assets 目录复制到 dist/assets ], }), ], };
-
BundleAnalyzerPlugin
BundleAnalyzerPlugin
用于可视化 Webpack 打包生成的文件大小,有助于发现哪些模块占用了较多的空间,从而进行性能优化。
示例配置:
jsconst { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer'); module.exports = { plugins: [ new BundleAnalyzerPlugin(), // 启动 Bundle 分析工具 ], };
-
DefinePlugin
DefinePlugin
用于在代码中创建全局常量,通常用于区分不同的环境(如开发环境和生产环境)。- 比如,我们可以通过这个插件在代码中注入
process.env.NODE_ENV
,以便进行条件渲染。
示例配置:
jsconst webpack = require('webpack'); module.exports = { plugins: [ new webpack.DefinePlugin({ 'process.env.NODE_ENV': JSON.stringify('production'), }), ], };
Webpack 插件通过监听 Webpack 提供的 钩子(hooks) 来与构建流程进行交互。Webpack 的构建过程是由多个阶段组成的,插件可以在这些阶段中执行不同的任务。插件通常使用 Webpack Compiler API 和 Webpack Compilation API 来访问构建过程中的各个阶段。
apply()
:插件的核心方法。所有插件都需要实现apply
方法,这个方法会在插件实例化时被调用。在apply
方法中,插件可以通过 Webpack 提供的钩子和事件与构建过程进行交互。- 钩子 :Webpack 提供了多个钩子供插件使用,如
emit
、afterCompile
、done
等。插件可以在这些钩子中添加自定义逻辑。
6.模块(Modules)
在 Webpack 中,模块是构成应用程序的基本单元。任何在 Webpack 配置中需要被打包的资源,都被视为模块。
- 模块解析: Webpack 会从项目的入口文件开始,递归地分析文件中的依赖关系。它会逐步识别所有 import 或 require 引入的模块,并解析这些模块的内容。
- 使用加载器(Loader): Webpack 处理每个模块时,使用加载器来转换非 JavaScript 模块(如 CSS、图片、SASS 等)。加载器会根据配置的规则,逐步将不同类型的文件转换为 Webpack 可以理解的模块。
- 模块打包: Webpack 会收集所有模块,建立模块之间的依赖图,然后将这些模块和依赖关系打包成最终的输出文件。Webpack 会通过 代码分割(Code Splitting) 来决定将哪些模块打包成单独的文件。
Webpack 会从入口文件开始,递归地解析每个模块的依赖关系。所有模块通过 import 或 require 引入其他模块时,Webpack 会追踪这些依赖,并将它们组织成一个 依赖图(Dependency Graph)。
Webpack 不仅会解析和打包这些模块,还会根据配置的优化策略进行代码分割(Code Splitting)。这样可以将代码分成多个独立的文件,按需加载,优化页面的加载性能。
入口代码分割: Webpack 会根据入口文件,将不同的模块打包成独立的文件。比如,在多个页面之间共享一些代码时,Webpack 会将共享的代码提取成单独的文件。
动态导入(Dynamic Imports): 通过动态导入,Webpack 可以在代码运行时按需加载模块,减少初始加载的包体积。动态导入使用 import() 函数来实现。
在 Webpack 中,每个模块的内容都会被缓存,以便在后续的构建过程中加速构建速度。Webpack 会基于模块的 内容哈希(content hash) 来判断是否需要重新构建模块,这样能够避免不必要的重复构建,提升构建效率。
持久化缓存(Persistent Caching):从 Webpack 5 开始,Webpack 支持持久化缓存(通过硬盘缓存),这样即使是开发环境中的模块,也能在下一次构建时快速加载。
为什么要使用代码分割?
- 减少初次加载时间:如果所有代码都打包成一个文件,用户访问时必须等待整个文件加载完才能使用应用。通过代码分割,只有用户当前需要的部分才会被加载,从而加速页面的首次加载。
- 按需加载:通过动态导入,WebPack 可以根据用户操作或需求,加载页面所需的特定模块。这对于大型应用非常重要,可以避免加载不必要的资源。
- 缓存优化:当代码发生变化时,整个打包文件都会更新并重新下载。通过将不常变化的公共代码(如第三方库)提取为单独的文件,浏览器可以缓存这些文件,避免不必要的重新加载。
- 提高模块复用:通过将公共代码提取成独立的文件,多个页面或入口点可以复用这些模块,避免重复加载相同的内容。
7.模式(mode)
在 Webpack 中,mode 是一个非常重要的配置项,它用于告诉 Webpack 你希望构建过程的目标环境,以便 Webpack 根据该模式优化输出内容。mode 主要有三个可选值:development、production 和 none。每个模式都有不同的优化策略和行为,目的是根据不同的构建需求(开发还是生产环境)来调整 Webpack 的行为,从而提高构建性能和优化输出结果。
-
development(开发模式)
在开发模式下,Webpack 主要关注的是构建速度和开发过程中的调试体验,因此它会做以下优化:
- Source Maps:Webpack 会生成源映射(source maps),这样你可以在浏览器中查看原始的源代码,而不仅仅是打包后的代码。源映射使得调试过程更加简便。
- 不压缩代码:为了便于调试,Webpack 默认不会压缩 JavaScript 文件,以保留代码的可读性。
- 更快的构建速度:Webpack 会做一些优化,使得构建过程更加快速,但牺牲了一些代码体积的优化。
- 更详细的错误和警告信息:开发模式下,Webpack 会提供更详细的错误提示和警告信息,帮助开发者快速定位问题。
-
production(生产模式)
在生产模式下,Webpack 的主要目标是优化代码体积和性能,因此它会做以下优化:
- 代码压缩:Webpack 会使用 TerserPlugin 来压缩 JavaScript 代码,从而减小文件体积。
优化打包体积:Webpack 会启用一些优化策略来删除无用的代码(比如死代码、未使用的模块等),以减少最终打包文件的大小。 - 去除开发相关的代码:在生产环境下,Webpack 会去除一些只对开发有用的功能,比如调试信息、警告和源映射等。
- 自动启用 ModuleConcatenationPlugin:这个插件会将多个模块合并成一个函数调用,减少运行时的开销,提高执行效率。
- 使用更精简的错误和警告输出:在生产环境下,Webpack 会将错误和警告信息压缩和精简,以提高构建效率和减少输出日志的冗余。
- 代码压缩:Webpack 会使用 TerserPlugin 来压缩 JavaScript 代码,从而减小文件体积。
-
none(无模式)
none 模式基本上禁用 Webpack 的所有默认优化配置。在这种模式下,Webpack 不会自动启用任何优化,它会执行最基础的打包工作,适合一些特殊的需求或当你希望完全控制优化策略时使用。
通常,如果你不希望 Webpack 根据模式自动进行优化,或者你想要在自己的配置中完全控制构建过程,那么你可以使用 none 模式。
8.开发服务器(Development Server)
Webpack 的开发服务器(webpack-dev-server)通过搭建一个本地服务器,提供热更新(Hot Module Replacement, HMR)、自动刷新(Auto Refresh)等功能,从而大大提升开发效率。
开发服务器的作用
-
实时预览和自动刷新:开发服务器可以实时查看文件变动,当源代码文件(如 JS、CSS)发生更改时,浏览器会自动刷新或更新页面,避免手动刷新,提升开发体验。
-
热模块替换(Hot Module Replacement, HMR):Webpack Dev Server 可以在不重新加载整个页面的情况下,仅替换更改的模块。这样,开发者在修改代码时,不仅可以看到修改结果,还能保留页面状态,比如表单数据或滚动位置等。
-
简化本地开发:它提供了一个本地开发环境,使得开发者能够像在生产环境中一样运行应用,只是它的构建速度更快,且具备开发过程中所需要的热更新、自动刷新等功能。
-
代理和跨域支持:在开发过程中,很多时候需要与后端 API 进行交互。Webpack Dev Server 支持代理功能,可以将开发服务器的请求转发到后台 API,解决跨域问题。
要在 Webpack 项目中使用开发服务器,需要先安装 webpack-dev-server,然后在 Webpack 配置文件中进行相关配置。在 webpack.config.js 文件中,添加 devServer 配置项,来启用开发服务器功能。
js
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
devServer: {
contentBase: path.join(__dirname, 'dist'), // 指定静态文件的根目录
port: 9000, // 指定开发服务器的端口号
open: true, // 启动后自动打开浏览器
hot: true, // 启用热模块替换
},
};
在这个基本配置中,最重要的部分是 devServer
配置项。以下是常用的配置项和含义:
-
contentBase
:指定开发服务器的静态文件目录,默认是./public
。它告诉服务器从哪里提供静态资源。通常设置为dist
,即构建后的输出目录。 -
port
:设置开发服务器的端口号,默认为8080
,你可以根据需要修改为其他端口。 -
open
:设置为true
时,开发服务器启动后会自动在浏览器中打开应用。默认是false
。 -
hot
:启用热模块替换(HMR)。当代码修改时,Webpack 只会替换修改的模块,而不会重新加载整个页面,从而保持应用的状态。 -
historyApiFallback
:对于使用 HTML5 History API 的单页面应用(SPA),如果在浏览器中直接访问某个路径时返回404
错误,可以通过此配置让开发服务器始终返回index.html
。例如,应用路由是基于 Hash 的时候,刷新页面可能会导致找不到资源。jsdevServer: { historyApiFallback: true, }
-
proxy
:设置代理,解决跨域问题。在开发时,可能需要与后端 API 进行交互。此时,可以通过配置代理将某些请求转发到指定的后端服务器。jsdevServer: { proxy: { '/api': 'http://localhost:3000', // 将 /api 请求代理到后端的 3000 端口 }, }
-
inline
:启用内联模式。在内联模式下,Webpack 会将脚本注入到页面中,通常用于调试。现在大多数情况下不再需要此配置,因为devServer
默认启用内联模式。 -
watchContentBase
:启用该选项后,Webpack 会监控静态资源文件(如 HTML、CSS 等)的变化,自动重新加载页面。 -
compress
:启用 gzip 压缩,压缩服务器响应的文件。在开发环境中,可以减少网络传输的大小,提升性能。jsdevServer: { compress: true, // 启用 gzip 压缩 }
热模块替换(HMR)
热模块替换(HMR)是 Webpack 的一个重要特性,能在应用运行时只替换已更改的模块,而不是重新加载整个页面。它不仅能加速开发过程,还能保持应用的状态(如用户输入的数据、滚动条位置等)。Webpack Dev Server 默认启用了 HMR。
要使用 HMR,除了启用 hot: true
配置外,还需要在应用中引入 HMR 相关的 API。例如,在 JavaScript 中使用 module.hot.accept()
处理模块的更新:
js
if (module.hot) {
module.hot.accept('./module.js', function() {
console.log('Module updated!');
});
}
在 production
模式下,HMR 不会默认启用,因为生产环境下,代码通常经过压缩和优化,不需要实时更新。HMR 主要用于开发环境。
启动开发服务器
在 webpack.config.js
配置好 devServer
后,可以通过命令启动开发服务器:
bash
npx webpack serve
或者,如果你在 package.json
中配置了 scripts
,可以直接使用 npm 或 yarn 启动开发服务器:
json
{
"scripts": {
"start": "webpack serve --config webpack.config.js"
}
}
然后,在浏览器中访问 http://localhost:9000
即可看到运行中的应用。
Webpack Dev Server 并不完全模拟生产环境,它主要是为开发环境提供快速的构建、实时预览和调试功能。它并不会自动进行生产环境的优化(如代码压缩、去除死代码等)。虽然它有一些功能可以模拟生产环境的行为(如代理、热更新等),但其主要目标是提高开发效率。