资源管理
如果你从一开始就遵循这些指南,你现在应该有一个显示“Hello webpack”的小项目。现在让我们尝试整合一些其他资源,如图像,看看如何处理它们。
在 webpack 出现之前,前端开发人员会使用 grunt 和 gulp 等工具来处理这些资源,并将它们从 /src 文件夹移动到 /dist 或 /build 目录。JavaScript 模块也采用了同样的思路,但像 webpack 这样的工具会**动态地打包(bundle)**所有依赖(创建所谓的 dependency graph(依赖图))。这非常棒,因为每个模块现在都_显式地声明了其依赖关系_,并且我们可以避免打包未使用的模块。
webpack 最酷的特性之一是,除了 JavaScript 之外,你还可以_包含任何其他类型的文件_,只要有对应的 loader 或内置的 Asset Modules 支持即可。这意味着上面为 JavaScript 列出的那些优点(例如显式依赖)同样可以应用于构建网站或 Web 应用时使用的所有资源。让我们从 CSS 开始,你可能已经熟悉这种配置了。
基本配置
在开始之前,让我们对项目做一些小改动:
dist/index.html
diff
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
- <title>Getting Started</title>
+ <title>Asset Management</title>
</head>
<body>
- <script src="main.js"></script>
+ <script src="bundle.js"></script>
</body>
</html>
webpack.config.js
diff
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export default {
entry: './src/index.js',
output: {
- filename: 'main.js',
+ filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
};
加载 CSS
为了在 JavaScript 模块中 import 一个 CSS 文件,你需要安装并将 style-loader 和 css-loader 添加到你的 module 配置中:
bash
npm install --save-dev style-loader css-loader
webpack.config.js
diff
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export default {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
+ module: {
+ rules: [
+ {
+ test: /\.css$/i,
+ use: ['style-loader', 'css-loader'],
+ },
+ ],
+ },
};
模块 loader 可以被链式调用。链中的每个 loader 都会对处理的资源进行转换。链是逆序执行的(从右到左)。
例如,给定以下规则:
js
export default {
module: {
rules: [
{
test: /\.scss$/i,
use: ["postcss-loader", "sass-loader"],
},
],
},
};
尽管 postcss-loader 在 use 数组中出现在 sass-loader 之前,但 webpack 会先执行 sass-loader(将 Sass 编译为 CSS),然后对结果执行 postcss-loader。
如果此顺序未保持,webpack 可能会抛出错误。
project
diff
webpack-demo
├── package.json
├── package-lock.json
├── webpack.config.js
├── /dist
│ ├── bundle.js
│ └── index.html
├── /src
+ │ ├── style.css
│ └── index.js
└── /node_modules
src/style.css
css
.hello {
color: red;
}
src/index.js
diff
import _ from 'lodash';
+import './style.css';
function component() {
const element = document.createElement('div');
// Lodash, now imported by this script
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
+ element.classList.add('hello');
return element;
}
document.body.appendChild(component());
现在运行你的构建命令:
bash
$ npm run build
...
[webpack-cli] Compilation finished
asset bundle.js 72.6 KiB [emitted] [minimized] (name: main) 1 related asset
runtime modules 1000 bytes 5 modules
orphan modules 326 bytes [orphan] 1 module
cacheable modules 539 KiB
modules by path ./node_modules/ 538 KiB
./node_modules/lodash/lodash.js 530 KiB [built] [code generated]
./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js 6.67 KiB [built] [code generated]
./node_modules/css-loader/dist/runtime/api.js 1.57 KiB [built] [code generated]
modules by path ./src/ 965 bytes
./src/index.js + 1 modules 639 bytes [built] [code generated]
./node_modules/css-loader/dist/cjs.js!./src/style.css 326 bytes [built] [code generated]
webpack 5.x.x compiled successfully in 2231 ms
再次在浏览器中打开 dist/index.html,你应该会看到 Hello webpack 现在是红色样式。要查看 webpack 做了什么,请检查页面(不要查看页面源代码,因为它不会显示结果,因为 <style> 标签是由 JavaScript 动态创建的)并查看页面的 head 标签。它应该包含我们在 index.js 中导入的 style 块。
请注意,你可以且大多数情况下应该压缩 css 以获得更好的生产环境加载时间。除此之外,几乎任何你想到的 CSS 风格都有对应的 loader——例如 postcss、sass 和 less。
加载图像
现在我们引入了 CSS,但像背景图和图标这样的图像呢?从 webpack 5 开始,使用内置的 Asset Modules,我们可以轻松地将它们也整合到我们的系统中:
webpack.config.js
diff
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export default {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: /\.css$/i,
use: ['style-loader', 'css-loader'],
},
+ {
+ test: /\.(png|svg|jpg|jpeg|gif)$/i,
+ type: 'asset/resource',
+ },
],
},
};
现在,当你 import MyImage from './my-image.png' 时,该图像将被处理并添加到你的 output 目录中_并且_ MyImage 变量将包含处理后的最终 url。如上所示,当使用 css-loader 时,你的 CSS 中的 url('./my-image.png') 也会发生类似的过程。loader 会识别出这是一个本地文件,并将 './my-image.png' 路径替换为 output 目录中图像的最终路径。html-loader 以相同的方式处理 <img src="./my-image.png" />。
让我们向项目添加一个图像,看看它是如何工作的,你可以使用任何你喜欢的图像:
project
diff
webpack-demo
├── package.json
├── package-lock.json
├── webpack.config.js
├── /dist
│ ├── bundle.js
│ └── index.html
├── /src
+ │ ├── icon.png
│ ├── style.css
│ └── index.js
└── /node_modules
src/index.js
diff
import _ from 'lodash';
import './style.css';
+import Icon from './icon.png';
function component() {
const element = document.createElement('div');
// Lodash, now imported by this script
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
element.classList.add('hello');
+ // Add the image to our existing div.
+ const myIcon = new Image();
+ myIcon.src = Icon;
+
+ element.appendChild(myIcon);
+
return element;
}
document.body.appendChild(component());
src/style.css
diff
.hello {
color: red;
+ background: url('./icon.png');
}
让我们创建一个新的构建并再次打开 index.html 文件:
bash
$ npm run build
...
[webpack-cli] Compilation finished
assets by status 9.88 KiB [cached] 1 asset
asset bundle.js 73.4 KiB [emitted] [minimized] (name: main) 1 related asset
runtime modules 1.82 KiB 6 modules
orphan modules 326 bytes [orphan] 1 module
cacheable modules 540 KiB (javascript) 9.88 KiB (asset)
modules by path ./node_modules/ 539 KiB
modules by path ./node_modules/css-loader/dist/runtime/*.js 2.38 KiB
./node_modules/css-loader/dist/runtime/api.js 1.57 KiB [built] [code generated]
./node_modules/css-loader/dist/runtime/getUrl.js 830 bytes [built] [code generated]
./node_modules/lodash/lodash.js 530 KiB [built] [code generated]
./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js 6.67 KiB [built] [code generated]
modules by path ./src/ 1.45 KiB (javascript) 9.88 KiB (asset)
./src/index.js + 1 modules 794 bytes [built] [code generated]
./src/icon.png 42 bytes (javascript) 9.88 KiB (asset) [built] [code generated]
./node_modules/css-loader/dist/cjs.js!./src/style.css 648 bytes [built] [code generated]
webpack 5.x.x compiled successfully in 1972 ms
如果一切顺利,你现在应该看到你的图标作为重复背景,以及一个 img 元素位于我们的 Hello webpack 文本旁边。如果你检查这个元素,你会看到实际的文件名已更改为类似于 29822eaa871e8eadeaa4.png 的名称。这意味着 webpack 在 src 文件夹中找到了我们的文件并处理了它!
加载字体
那么像字体这样的其他资源呢?Asset Modules 会获取你通过它们加载的任何文件,并将其输出到你的构建目录。这意味着我们可以将它们用于任何类型的文件,包括字体。让我们更新 webpack.config.js 来处理字体文件:
webpack.config.js
diff
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export default {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: /\.css$/i,
use: ['style-loader', 'css-loader'],
},
{
test: /\.(png|svg|jpg|jpeg|gif)$/i,
type: 'asset/resource',
},
+ {
+ test: /\.(woff|woff2|eot|ttf|otf)$/i,
+ type: 'asset/resource',
+ },
],
},
};
向你的项目添加一些字体文件:
project
diff
webpack-demo
├── package.json
├── package-lock.json
├── webpack.config.js
├── /dist
│ ├── bundle.js
│ └── index.html
├── /src
+ │ ├── my-font.woff
+ │ ├── my-font.woff2
│ ├── icon.png
│ ├── style.css
│ └── index.js
└── /node_modules
配置好 loader 并放置好字体后,你可以通过 @font-face 声明来使用它们。本地的 url(...) 指令会被 webpack 识别,就像图像一样:
src/style.css
diff
+@font-face {
+ font-family: 'MyFont';
+ src: url('./my-font.woff2') format('woff2'),
+ url('./my-font.woff') format('woff');
+ font-weight: 600;
+ font-style: normal;
+}
+
.hello {
color: red;
+ font-family: 'MyFont';
background: url('./icon.png');
}
现在运行一个新的构建,让我们看看 webpack 是否处理了我们的字体:
bash
$ npm run build
...
[webpack-cli] Compilation finished
assets by status 9.88 KiB [cached] 1 asset
assets by info 33.2 KiB [immutable]
asset 55055dbfc7c6a83f60ba.woff 18.8 KiB [emitted] [immutable] [from: src/my-font.woff] (auxiliary name: main)
asset 8f717b802eaab4d7fb94.woff2 14.5 KiB [emitted] [immutable] [from: src/my-font.woff2] (auxiliary name: main)
asset bundle.js 73.7 KiB [emitted] [minimized] (name: main) 1 related asset
runtime modules 1.82 KiB 6 modules
orphan modules 326 bytes [orphan] 1 module
cacheable modules 541 KiB (javascript) 43.1 KiB (asset)
javascript modules 541 KiB
modules by path ./node_modules/ 539 KiB
modules by path ./node_modules/css-loader/dist/runtime/*.js 2.38 KiB 2 modules
./node_modules/lodash/lodash.js 530 KiB [built] [code generated]
./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js 6.67 KiB [built] [code generated]
modules by path ./src/ 1.98 KiB
./src/index.js + 1 modules 794 bytes [built] [code generated]
./node_modules/css-loader/dist/cjs.js!./src/style.css 1.21 KiB [built] [code generated]
asset modules 126 bytes (javascript) 43.1 KiB (asset)
./src/icon.png 42 bytes (javascript) 9.88 KiB (asset) [built] [code generated]
./src/my-font.woff2 42 bytes (javascript) 14.5 KiB (asset) [built] [code generated]
./src/my-font.woff 42 bytes (javascript) 18.8 KiB (asset) [built] [code generated]
webpack 5.x.x compiled successfully in 2142 ms
再次打开 dist/index.html,看看我们的 Hello webpack 文本是否已更改为新字体。如果一切正常,你应该会看到这些变化。
加载数据
另一个可以加载的有用资源是数据,例如 JSON 文件、CSV、TSV 和 XML。JSON 的支持实际上是内置的,类似于 NodeJS,这意味着 import Data from './data.json' 默认情况下就可以工作。要导入 CSV、TSV 和 XML,你可以使用 csv-loader 和 xml-loader。让我们来处理加载这三类数据:
bash
npm install --save-dev csv-loader xml-loader
webpack.config.js
diff
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export default {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: /\.css$/i,
use: ['style-loader', 'css-loader'],
},
{
test: /\.(png|svg|jpg|jpeg|gif)$/i,
type: 'asset/resource',
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource',
},
+ {
+ test: /\.(csv|tsv)$/i,
+ use: ['csv-loader'],
+ },
+ {
+ test: /\.xml$/i,
+ use: ['xml-loader'],
+ },
],
},
};
向你的项目添加一些数据文件:
project
diff
webpack-demo
├── package.json
├── package-lock.json
├── webpack.config.js
├── /dist
│ ├── bundle.js
│ └── index.html
├── /src
+ │ ├── data.xml
+ │ ├── data.csv
│ ├── my-font.woff
│ ├── my-font.woff2
│ ├── icon.png
│ ├── style.css
│ └── index.js
└── /node_modules
src/data.xml
xml
<?xml version="1.0" encoding="UTF-8"?>
<note>
<to>Mary</to>
<from>John</from>
<heading>Reminder</heading>
<body>Call Cindy on Tuesday</body>
</note>
src/data.csv
csv
to,from,heading,body
Mary,John,Reminder,Call Cindy on Tuesday
Zoe,Bill,Reminder,Buy orange juice
Autumn,Lindsey,Letter,I miss you
现在你可以 import 这四类数据(JSON、CSV、TSV、XML)中的任何一种,而你导入的 Data 变量将包含解析后的 JSON,可供使用:
src/index.js
diff
import _ from 'lodash';
import './style.css';
import Icon from './icon.png';
+import Data from './data.xml';
+import Notes from './data.csv';
function component() {
const element = document.createElement('div');
// Lodash, now imported by this script
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
element.classList.add('hello');
// Add the image to our existing div.
const myIcon = new Image();
myIcon.src = Icon;
element.appendChild(myIcon);
+ console.log(Data);
+ console.log(Notes);
+
return element;
}
document.body.appendChild(component());
重新运行 npm run build 命令并打开 dist/index.html。如果你查看开发者工具中的控制台,你应该能够看到你导入的数据被打印到控制台!
T> 当你使用像 d3 这样的工具实现某种数据可视化时,这会特别有用。你可以在构建过程中将数据加载到你的模块中,而不是在运行时发出 ajax 请求并解析数据,这样一旦模块到达浏览器,解析后的数据就随时可以使用。
W> JSON 模块只有默认导出可以在没有警告的情况下使用。
js
// No warning
import data from "./data.json";
js
// Warning shown, this is not allowed by the spec.
import { foo } from "./data.json";
自定义 JSON 模块的解析器
可以使用 自定义 parser(解析器) 将任何 toml、yaml 或 json5 文件作为 JSON 模块导入,而无需使用特定的 webpack loader。
假设你在 src 文件夹下有 data.toml、data.yaml 和 data.json5 文件:
src/data.toml
toml
title = "TOML Example"
[owner]
name = "Tom Preston-Werner"
organization = "GitHub"
bio = "GitHub Cofounder & CEO\nLikes tater tots and beer."
dob = 1979-05-27T07:32:00Z
src/data.yaml
yaml
title: YAML Example
owner:
name: Tom Preston-Werner
organization: GitHub
bio: |-
GitHub Cofounder & CEO
Likes tater tots and beer.
dob: 1979-05-27T07:32:00.000Z
src/data.json5
json5
{
// comment
title: "JSON5 Example",
owner: {
name: "Tom Preston-Werner",
organization: "GitHub",
bio: "GitHub Cofounder & CEO\n\
Likes tater tots and beer.",
dob: "1979-05-27T07:32:00.000Z",
},
}
首先安装 toml、yamljs 和 json5 包:
bash
npm install toml yamljs json5 --save-dev
并在你的 webpack 配置中配置它们:
webpack.config.js
diff
import path from 'node:path';
import { fileURLToPath } from 'node:url';
+import toml from 'toml';
+import yaml from 'yamljs';
+import json5 from 'json5';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export default {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: /\.css$/i,
use: ['style-loader', 'css-loader'],
},
{
test: /\.(png|svg|jpg|jpeg|gif)$/i,
type: 'asset/resource',
},
{
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource',
},
{
test: /\.(csv|tsv)$/i,
use: ['csv-loader'],
},
{
test: /\.xml$/i,
use: ['xml-loader'],
},
+ {
+ test: /\.toml$/i,
+ type: 'json',
+ parser: {
+ parse: toml.parse,
+ },
+ },
+ {
+ test: /\.yaml$/i,
+ type: 'json',
+ parser: {
+ parse: yaml.parse,
+ },
+ },
+ {
+ test: /\.json5$/i,
+ type: 'json',
+ parser: {
+ parse: json5.parse,
+ },
+ },
],
},
};
src/index.js
diff
import _ from 'lodash';
import './style.css';
import Icon from './icon.png';
import Data from './data.xml';
import Notes from './data.csv';
+import toml from './data.toml';
+import yaml from './data.yaml';
+import json from './data.json5';
+
+console.log(toml.title); // output `TOML Example`
+console.log(toml.owner.name); // output `Tom Preston-Werner`
+
+console.log(yaml.title); // output `YAML Example`
+console.log(yaml.owner.name); // output `Tom Preston-Werner`
+
+console.log(json.title); // output `JSON5 Example`
+console.log(json.owner.name); // output `Tom Preston-Werner`
function component() {
const element = document.createElement('div');
// Lodash, now imported by this script
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
element.classList.add('hello');
// Add the image to our existing div.
const myIcon = new Image();
myIcon.src = Icon;
element.appendChild(myIcon);
console.log(Data);
console.log(Notes);
return element;
}
document.body.appendChild(component());
重新运行 npm run build 命令并打开 dist/index.html。你应该能够看到你导入的数据被打印到控制台!
全局资源
上面提到的所有内容中最酷的部分是,以这种方式加载资源允许你以更直观的方式对模块和资源进行分组。与其依赖一个包含所有内容的全局 /assets 目录,你可以将资源与使用它们的代码分组在一起。例如,这样的结构可能很有用:
diff
- ├── /assets
+ └── /components
+ └── /my-component
+ ├── index.jsx
+ ├── index.css
+ ├── icon.svg
+ └── img.png
这种设置使你的代码更具可移植性,因为所有紧密耦合的东西都放在一起。假设你想在另一个项目中使用 /my-component,只需将其复制或移动到那边的 /components 目录。只要你安装了_外部依赖_并且你的_配置定义了相同的 loader_,就应该没问题。
但是,假设你被旧的习惯束缚,或者你有一些在多个组件(视图、模板、模块等)之间共享的资源。你仍然可以将这些资源存储在基本目录中,甚至可以使用 alias(别名) 使它们更容易被 import。
总结
对于后续指南,我们不会使用本指南中使用的所有不同资源,所以让我们做一些清理工作,为下一部分指南 Output Management(输出管理)做好准备:
project
diff
webpack-demo
├── package.json
├── package-lock.json
├── webpack.config.js
├── /dist
│ ├── bundle.js
│ └── index.html
├── /src
- │ ├── data.csv
- │ ├── data.json5
- │ ├── data.toml
- │ ├── data.xml
- │ ├── data.yaml
- │ ├── icon.png
- │ ├── my-font.woff
- │ ├── my-font.woff2
- │ ├── style.css
│ └── index.js
└── /node_modules
webpack.config.js
diff
import path from 'node:path';
import { fileURLToPath } from 'node:url';
-import toml from 'toml';
-import yaml from 'yamljs';
-import json5 from 'json5';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
export default {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
- module: {
- rules: [
- {
- test: /\.css$/i,
- use: ['style-loader', 'css-loader'],
- },
- {
- test: /\.(png|svg|jpg|jpeg|gif)$/i,
- type: 'asset/resource',
- },
- {
- test: /\.(woff|woff2|eot|ttf|otf)$/i,
- type: 'asset/resource',
- },
- {
- test: /\.(csv|tsv)$/i,
- use: ['csv-loader'],
- },
- {
- test: /\.xml$/i,
- use: ['xml-loader'],
- },
- {
- test: /\.toml$/i,
- type: 'json',
- parser: {
- parse: toml.parse,
- },
- },
- {
- test: /\.yaml$/i,
- type: 'json',
- parser: {
- parse: yaml.parse,
- },
- },
- {
- test: /\.json5$/i,
- type: 'json',
- parser: {
- parse: json5.parse,
- },
- },
- ],
- },
};
src/index.js
diff
import _ from 'lodash';
-import './style.css';
-import Icon from './icon.png';
-import Data from './data.xml';
-import Notes from './data.csv';
-import toml from './data.toml';
-import yaml from './data.yaml';
-import json from './data.json5';
-
-console.log(toml.title); // output `TOML Example`
-console.log(toml.owner.name); // output `Tom Preston-Werner`
-
-console.log(yaml.title); // output `YAML Example`
-console.log(yaml.owner.name); // output `Tom Preston-Werner`
-
-console.log(json.title); // output `JSON5 Example`
-console.log(json.owner.name); // output `Tom Preston-Werner`
function component() {
const element = document.createElement('div');
- // Lodash, now imported by this script
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
- element.classList.add('hello');
-
- // Add the image to our existing div.
- const myIcon = new Image();
- myIcon.src = Icon;
-
- element.appendChild(myIcon);
-
- console.log(Data);
- console.log(Notes);
return element;
}
document.body.appendChild(component());
并移除我们之前添加的那些依赖:
bash
npm uninstall css-loader csv-loader json5 style-loader toml xml-loader yamljs
下一步
让我们进入 Output Management(输出管理)
延伸阅读
- Loading Fonts(加载字体) (SurviveJS)
帮助我们改进文档
发现翻译问题或内容错误?请告诉我们。
