Webpack
1. 引入
打包工具
随着项目越来越复杂,我们会使用 ES6 模块将代码拆分到多个文件中,并引入许多第三方库。这会导致浏览器需要发起大量的 HTTP 请求来加载这些零散的文件,严重影响页面性能。
打包工具 (Bundler) 正是为了解决这个问题而生。它是一个构建工具,能够将我们项目中所有散落的 JavaScript 模块、CSS 文件、图片等资源,根据依赖关系进行分析、处理和压缩,最终“打包”成少数几个优化过的、适合浏览器加载的静态文件。
Webpack 的核心工作
Webpack 是目前最流行的 JavaScript 打包工具之一。它的核心工作流程如下:
- 从一个或多个入口文件 (Entry) 开始。
- 根据
import
和require
语句,构建一个依赖图 (Dependency Graph),这个图包含了项目所需的所有模块。 - 将图中的所有模块(包括 JavaScript、CSS、图片等)通过 加载器 (Loaders) 和 插件 (Plugins) 进行处理。
- 最终将处理后的内容打包成一个或多个静态文件,输出到指定的出口 (Output) 目录。
可以把 Webpack 想象成一位智能的“项目打包员”。我们告诉他主文件在哪里(Entry),他会顺藤摸瓜,找到所有相关的文件和资源(构建依赖图),然后用各种工具(Loaders 和 Plugins)对这些材料进行加工和分类,最后把它们整齐地打包成几个可以直接发货的包裹(Output)。
2. Webpack 相关概念
核心概念
Webpack 中有如下核心概念:
- 入口 (Entry):Webpack 开始构建依赖图的起点。它告诉 Webpack 应该从哪个文件开始打包。
- 出口 (Output):告诉 Webpack 在哪里输出它所创建的 bundles,以及如何命名这些文件。
- 加载器 (Loaders):让 Webpack 能够去处理那些非 JavaScript 文件(Webpack 自身只能理解 JavaScript 和 JSON)。Loader 可以将所有类型的文件转换为 Webpack 能处理的有效模块。
- 插件 (Plugins):用于执行范围更广的任务,从打包优化和压缩,一直到重新定义环境中的变量。插件是 Webpack 的支柱功能。
目录结构约定
Webpack 约定了如下的目录结构:
src
(source):存放我们编写的源代码(.js, .css, .html 模板等)。我们所有的开发工作都在这里进行。dist
(distribution):存放打包后生成的、用于部署的文件。这个目录是自动生成的,我们不应该手动修改它。
3. 基本配置与使用
初始化项目
- 创建项目目录并初始化
npm
:
mkdir webpack-demo && cd webpack-demo
npm init -y
- 安装 Webpack 相关的包作为开发依赖 (
-D
或--save-dev
):
npm install webpack webpack-cli -D
创建 webpack.config.js
在项目根目录下创建 webpack.config.js
文件。这是 Webpack 的主配置文件,它使用 Node.js 的 CommonJS 模块语法。
// webpack.config.js
const path = require('path'); // 引入 Node.js 的 path 模块
module.exports = {
// 1. 模式 (Mode)
mode: 'development', // 'development' 或 'production'
// 2. 入口 (Entry)
entry: './src/index.js',
// 3. 出口 (Output)
output: {
filename: 'main.js', // 打包后输出的文件名
path: path.resolve(__dirname, 'dist'), // 输出目录的绝对路径
clean: true, // 在生成文件之前清空 output 目录
},
};
执行打包
在终端中运行以下命令,Webpack 会读取配置文件并执行打包:
npx webpack
npx
是一个 npm 工具,它能运行 node_modules/.bin
目录下的可执行文件。命令执行后即可在 dist
目录下看到生成的 main.js
文件。
4. 处理非 JavaScript 资源
处理 HTML
我们需要 html-webpack-plugin
来根据模板自动生成 HTML 文件,并注入打包后的 JS。
- 安装插件:
npm install html-webpack-plugin -D
- 配置
webpack.config.js
:
// webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin'); // 引入插件
module.exports = {
// ...其他配置
plugins: [
new HtmlWebpackPlugin({
template: './src/template.html', // 指定 HTML 模板文件
}),
],
};
加载 CSS
Webpack 需要 css-loader
和 style-loader
来处理 CSS 文件。
css-loader
:解析 CSS 文件中的@import
和url()
。style-loader
:将css-loader
解析后的内容,通过<style>
标签注入到 DOM 中。
- 安装 Loaders:
npm install style-loader css-loader -D
- 配置
webpack.config.js
:
module.exports = {
// ...其他配置
module: {
rules: [
{
test: /\.css$/i, // 匹配所有 .css 文件
use: ['style-loader', 'css-loader'], // 使用这两个 loader
},
],
},
};
注意:
use
数组中 Loader 的执行顺序是从右到左。所以必须先用css-loader
解析,再用style-loader
注入。
加载图片等资源
Webpack 5 内置了资源模块 (Asset Modules),可以轻松处理图片、字体等资源。
配置 webpack.config.js
:
module.exports = {
// ...其他配置
module: {
rules: [
// ...其他 rules
{
test: /\.(png|svg|jpg|jpeg|gif)$/i, // 匹配图片文件
type: 'asset/resource', // 使用资源模块类型
},
],
},
};
配置完成后,你就可以在 JavaScript 中直接 import
图片了:
import OdinImage from './odin.png';
const imgElement = document.createElement('img');
imgElement.src = OdinImage; // Webpack 会返回图片打包后的最终 URL
document.body.appendChild(imgElement);
5. 其他的 Webpack 组件
webpack-dev-server
webpack-dev-server
是一个开发服务器,提供实时重新加载 (Live Reloading) 功能,这样修改代码后无需手动运行 npx webpack
。
- 安装:
npm install webpack-dev-server -D
- 配置
webpack.config.js
:
module.exports = {
// ...其他配置
devServer: {
static: './dist', // 告诉服务器从哪个目录提供内容
watchFiles: ['./src/template.html'], // 监听 HTML 模板文件的变化
},
};
- 启动服务器:
npx webpack serve --open
Source Maps
打包后的代码经过压缩和合并,变得难以阅读。如果出错,浏览器提示的错误位置将是打包后文件的位置。Source Map 是一个映射文件,能将打包后的代码映射回原始源代码,极大地简化了调试过程。
在 webpack.config.js
中添加一行配置即可:
module.exports = {
// ...其他配置
devtool: 'eval-source-map', // 一个适合开发模式的 source map 类型
};
6. 优化工作流与生产环境配置
使用 npm 脚本 (npm scripts)
每次都输入 npx webpack
或 npx webpack serve
这样的命令有些繁琐。我们可以使用 package.json
中的 scripts
字段来创建自定义命令,简化工作流。
在 package.json
中添加 scripts
对象:
{
"name": "webpack-demo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack",
"dev": "webpack serve --open"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"webpack": "^5.70.0",
"webpack-cli": "^4.9.2"
}
}
现在我们就可以使用更简洁、更具语义的命令了:
npm run build
:等同于npx webpack
,用于构建生产版本。npm run dev
:等同于npx webpack serve --open
,用于启动开发服务器。
注意:在
scripts
中,我们可以直接写webpack
而不是npx webpack
。这是因为 npm 脚本在执行时,会自动将node_modules/.bin
目录添加到系统的PATH
中。
Webpack 模式
Webpack 有两种主要的模式:
-
development
(开发模式):- 目标:关注强大的源码调试功能和快速的增量构建。
- 特点:不压缩代码,包含详细的错误信息和提示,构建速度快。
- 我们在
devtool: 'eval-source-map'
中生成的 Source Map 就是在此模式下工作的。
-
production
(生产模式):- 目标:关注打包后文件的体积最小化和运行性能最优化。
- 特点:自动启用代码压缩(Minification)、作用域提升(Scope Hoisting)、移除无用代码(Tree Shaking)等一系列优化。打包后的代码可读性差,但体积小、运行快。
我们不应该在部署上线时使用开发模式,因为它会暴露源码,且文件体积过大。
拆分配置文件
每次在开发和部署之间切换时,手动修改 webpack.config.js
中的 mode
是非常低效且容易出错的。专业的做法是为不同的环境创建不同的配置文件。
我们需要安装一个名为 webpack-merge
的工具来优雅地合并这些配置。
- 安装
webpack-merge
:
npm install webpack-merge -D
- 创建三个配置文件:
webpack.common.js
:存放开发和生产环境共享的配置(如 entry, output, loaders)。webpack.dev.js
:存放仅开发环境使用的配置(如 devServer, devtool)。webpack.prod.js
:存放仅生产环境使用的配置(如性能优化、代码压缩等)。
webpack.common.js
(通用配置)
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
clean: true,
},
plugins: [
new HtmlWebpackPlugin({
template: './src/template.html',
}),
],
module: {
rules: [
{
test: /\.css$/i,
use: ['style-loader', 'css-loader'],
},
{
test: /\.(png|svg|jpg|jpeg|gif)$/i,
type: 'asset/resource',
},
],
},
};
webpack.dev.js
(开发配置)
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common, {
mode: 'development',
devtool: 'eval-source-map',
devServer: {
static: './dist',
watchFiles: ['./src/template.html'],
},
});
webpack.prod.js
(生产配置)
const { merge } = require('webpack-merge');
const common = require('./webpack.common.js');
module.exports = merge(common, {
mode: 'production',
devtool: 'source-map', // 生产环境推荐使用 source-map
});
- 更新
package.json
中的脚本:使用--config
标志来指定要使用的配置文件。
"scripts": {
"build": "webpack --config webpack.prod.js",
"dev": "webpack serve --open --config webpack.dev.js"
}
现在,运行 npm run dev
会使用开发配置启动服务器,而 npm run build
会使用生产配置来打包最终文件。这套设置一劳永逸,极大地提升了开发效率和规范性。
使用模板仓库
每次新建项目都要重复一遍 Webpack 的配置过程是很繁琐的。为了解决这个问题,我们可以利用 GitHub 的模板仓库功能。我们可以创建一个包含所有常用 Webpack 配置、目录结构、linter 设置等内容的样板项目。然后,在该仓库的设置页面,勾选 “Template repository” 选项。这样,当我们新建一个仓库时,就可以从 “Repository template” 下拉菜单中选择模板。这样,新创建的仓库就会直接包含所有预设的配置,从而我们可以跳过繁琐的搭建步骤,直接开始项目开发。