如果是从一开始就沿用指南的示例,现在应该有一个可以显示“Hello webpack”的小项目。接下来尝试混合一些图像之类的其他资源,看看 webpack 如何处理。
在 webpack 出现之前,前端开发人员会使用 grunt 和 gulp 等工具处理资源,并将它们从 /src
文件夹移动到 /dist
或 /build
目录中。JavaScript 模块也遵循同样的方式。但是,像 webpack 这样的工具将 动态打包 所有依赖并创建所谓的 依赖图。这是极好的创举,因为现在每个模块都可以 明确表述它自身的依赖,以避免打包未使用的模块。
webpack 最出色的功能之一就是除了引入 JavaScript,还可以通过 loader 或内置的 资源模块 引入任何其他类型的文件。换言之,以上列出的那些 JavaScript 的优点(例如显式依赖),同样可以用来构建 web 站点或 web 应用程序中的所有非 JavaScript 内容。让我们从 CSS 开始起步,或许你可能已经熟悉了下面这些设置。
在开始前对项目做一个小的修改:
dist/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
- <title>起步</title>
+ <title>管理资源</title>
</head>
<body>
- <script src="main.js"></script>
+ <script src="bundle.js"></script>
</body>
</html>
webpack.config.js
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
- filename: 'main.js',
+ filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
};
要想在 JavaScript 模块中导入 CSS 文件,需要安装 style-loader 和 css-loader,并在 module
配置 中添加这些 loader:
npm install --save-dev style-loader css-loader
webpack.config.js
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
+ module: {
+ rules: [
+ {
+ test: /\.css$/i,
+ use: ['style-loader', 'css-loader'],
+ },
+ ],
+ },
};
module loader 可以链式调用。链中的每个 loader 都将对资源进行转换,不过链会逆序执行。第一个 loader 将其结果(被转换后的资源)传递给下一个 loader,依此类推。最后,webpack 期望链中的最后的 loader 返回 JavaScript。
请确保 loader 的先后顺序:'style-loader'
在前,而 'css-loader'
在后。如果不遵守此约定,webpack 可能会抛出错误。
这些配置可以帮助在依赖于此样式的 JavaScript 文件中 import './style.css'
。现在,在此模块执行过程中,含有 CSS 字符串的 <style>
标签,将被插入到 HTML 文件的 <head>
中。
让我们来试试!现在在项目中添加一个新的 style.css
文件,并将其导入到 index.js
中:
project
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
.hello {
color: red;
}
src/index.js
import _ from 'lodash';
+import './style.css';
function component() {
const element = document.createElement('div');
// lodash 现在使用 import 引入。
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
+ element.classList.add('hello');
return element;
}
document.body.appendChild(component());
然后运行构建命令:
$ 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.4.0 compiled successfully in 2231 ms
再次在浏览器中打开 dist/index.html
,应该看到 Hello webpack
现在的样式是红色。请检查页面(不要查看页面源代码,它不会显示结果,因为 <style>
标签是由 JavaScript 动态创建的)并查看页面的 head 标签以观察 webpack 做了什么。可以发现,head 标签包含了原本不存在的 style 块元素,也就是在 index.js
中导入的 CSS 文件中的样式。
注意,在多数情况下也可以 压缩 CSS 以便在生产环境中节省加载时间。最重要的是,现有的 loader 可以支持任何可以想到的 CSS 样式 —— postcss、sass 和 less 等。
假如现在正在下载 CSS,但是像背景图片和图标这样的图像应该如何处理呢?webpack5 可以使用内置的 资源模块 轻松地将这些内容混入系统中:
webpack.config.js
const path = require('path');
module.exports = {
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
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
import _ from 'lodash';
import './style.css';
+import Icon from './icon.png';
function component() {
const element = document.createElement('div');
// lodash 现在使用 import 引入。
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
element.classList.add('hello');
+ // 将图像添加到已经存在的 div 中。
+ const myIcon = new Image();
+ myIcon.src = Icon;
+
+ element.appendChild(myIcon);
+
return element;
}
document.body.appendChild(component());
src/style.css
.hello {
color: red;
+ background: url('./icon.png');
}
重新构建并再次打开 index.html
文件:
$ 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.4.0 compiled successfully in 1972 ms
如果一切顺利,现在应该看到图标成为了重复的背景图,并且 Hello webpack
文本旁边出现了 img
元素。检查此元素将看到实际的文件名已更改为像 29822eaa871e8eadeaa4.png
一样的名称。这意味着 webpack 在 src
文件夹中找到了我们的文件,并对其进行了处理!
使用资源模块可以接收并加载任何文件,然后将其输出到构建目录。换言之,我们可以将它们用于任何类型的文件,也包括字体文件。更新 webpack.config.js
以处理字体文件:
webpack.config.js
const path = require('path');
module.exports = {
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
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
+@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 是否处理了字体:
$ 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.4.0 compiled successfully in 2142 ms
重新打开 dist/index.html
观察 Hello webpack
文本是否换上了新的字体。如果一切顺利,应该能看到已经发生了变化。
此外,可以加载的有用资源还有数据,如 JSON 文件、CSV、TSV 和 XML。与 NodeJS 类似,对 JSON 的支持实际上也是内置的,即 import Data from './data.json'
默认将正常运行。可以使用 csv-loader 和 xml-loader 导入 CSV、TSV 与 XML。接下来试试加载这三类文件:
npm install --save-dev csv-loader xml-loader
webpack.config.js
const path = require('path');
module.exports = {
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
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 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
to,from,heading,body
Mary,John,Reminder,Call Cindy on Tuesday
Zoe,Bill,Reminder,Buy orange juice
Autumn,Lindsey,Letter,I miss you
现在可以导入这四种类型的数据(JSON, CSV, TSV, XML)中的任何一种了,所导入的 Data
变量,将包含可直接使用的已解析的 JSON:
src/index.js
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 现在使用 import 引入
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
element.classList.add('hello');
// 将图像添加到已经存在的 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
。在开发者工具中的控制台应该能够看到成功打印了导入的数据!
// 没有警告
import data from './data.json';
// 显示警告,规范不允许这样做。
import { foo } from './data.json';
通过使用 自定义解析器 替代特定的 webpack loader,可以将任何 toml
、yaml
或 json5
文件作为 JSON 模块导入。
假设 src
文件夹下有 data.toml
、data.yaml
以及 data.json5
文件:
src/data.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
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
{
// 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
对应的包:
npm install toml yamljs json5 --save-dev
并在 webpack 中配置它们:
webpack.config.js
const path = require('path');
+const toml = require('toml');
+const yaml = require('yamljs');
+const json5 = require('json5');
module.exports = {
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
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); // 输出 `TOML Example`
+console.log(toml.owner.name); // 输出 `Tom Preston-Werner`
+
+console.log(yaml.title); // 输出 `YAML Example`
+console.log(yaml.owner.name); // 输出 `Tom Preston-Werner`
+
+console.log(json.title); // 输出 `JSON5 Example`
+console.log(json.owner.name); // 输出 `Tom Preston-Werner`
function component() {
const element = document.createElement('div');
// lodash 现在使用 import 引入。
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
element.classList.add('hello');
// 将图像添加到已经存在的 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
目录,而是将资源与代码组合在一起使用。例如,类似这样的结构会非常有用:
- |- /assets
+ |– /components
+ | |– /my-component
+ | | |– index.jsx
+ | | |– index.css
+ | | |– icon.svg
+ | | |– img.png
这种配置方式会使代码更具备可移植性,因为现有的集中放置的方式会让紧密耦合所有资源。假如想在另一个项目中使用 /my-component
,只需将其复制或移动到 /components
目录下。只要你已经安装过全部 外部依赖 ,并且 已经在配置中定义过相同的 loader ,那么项目应该能够良好运行。
但是,假如只能被局限在旧有开发方式,或者有一些在多个组件(视图、模板、模块等)之间共享的资源,仍然可以将这些资源存储在一个基本目录中,甚至配合 alias 可以更方便使用 import
导入。
在下篇指南中无需使用本指南中所有用到的资源,因此需要进行一些清理工作,以便为下篇指南 管理输出 做好准备:
project
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
const path = require('path');
-const toml = require('toml');
-const yaml = require('yamljs');
-const json5 = require('json5');
module.exports = {
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
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); // 输出 `TOML Example`
-console.log(toml.owner.name); // 输出 `Tom Preston-Werner`
-
-console.log(yaml.title); // 输出 `YAML Example`
-console.log(yaml.owner.name); // 输出 `Tom Preston-Werner`
-
-console.log(json.title); // 输出 `JSON5 Example`
-console.log(json.owner.name); // 输出 `Tom Preston-Werner`
function component() {
const element = document.createElement('div');
- // lodash 现在使用 import 引入。
element.innerHTML = _.join(['Hello', 'webpack'], ' ');
- element.classList.add('hello');
-
- // 将图像添加到已经存在的 div 中。
- const myIcon = new Image();
- myIcon.src = Icon;
-
- element.appendChild(myIcon);
-
- console.log(Data);
- console.log(Notes);
return element;
}
document.body.appendChild(component());
并移除之前添加的依赖:
npm uninstall css-loader csv-loader json5 style-loader toml xml-loader yamljs
我们将继续移步到 管理输出。