资产管理

如果你从一开始就一直在遵循指南,你现在应该有一个小型项目,显示“Hello webpack”。现在让我们尝试合并一些其他资源,例如图像,看看它们是如何处理的。

在 Webpack 出现之前,前端开发者会使用像 gruntgulp 这样的工具来处理这些资源,并将它们从 /src 文件夹移动到 /dist/build 目录。同样的思路也应用于 JavaScript 模块,但像 Webpack 这样的工具会 **动态地捆绑** 所有依赖项(创建所谓的 依赖关系图)。这很棒,因为现在每个模块都 *明确地声明了它的依赖项*,并且我们将避免捆绑未使用的模块。

Webpack 最酷的功能之一是,除了 JavaScript 之外,你还可以 *包含任何其他类型的文件*,只要有相应的加载器或内置的 资源模块 支持。这意味着上面列出的 JavaScript 的所有优点(例如显式依赖项)都可以应用于构建网站或 Web 应用程序中使用的所有内容。让我们从 CSS 开始,因为你可能已经熟悉这种设置。

设置

在我们开始之前,让我们对项目进行一些小的更改

dist/index.html

 <!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

 const path = require('path');

 module.exports = {
   entry: './src/index.js',
   output: {
-    filename: 'main.js',
+    filename: 'bundle.js',
     path: path.resolve(__dirname, 'dist'),
   },
 };

加载 CSS

为了从 JavaScript 模块中 import CSS 文件,你需要安装并添加 style-loadercss-loader 到你的 module 配置 中。

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'],
+      },
+    ],
+  },
 };

模块加载器可以串联起来。链中的每个加载器都会对处理过的资源进行转换。链的执行顺序是反向的。第一个加载器将它的结果(应用了转换的资源)传递给下一个加载器,依此类推。最后,Webpack 期望链中最后一个加载器返回 JavaScript。

以上加载器的顺序应该保持不变:'style-loader' 放在前面,后面跟着 'css-loader'。如果这个约定没有被遵循,Webpack 很可能会抛出错误。

这使你能够将 import './style.css' 导入到依赖该样式的文件中。现在,当该模块运行时,一个包含字符串化 CSS 的 <style> 标签将被插入到你的 html 文件的 <head> 中。

让我们尝试一下,在项目中添加一个新的 style.css 文件,并在 index.js 中导入它。

项目

  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, now imported by this script
   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现在以红色显示。要查看webpack做了什么,请检查页面(不要查看页面源代码,因为它不会显示结果,因为<style>标签是由JavaScript动态创建的),并查看页面的头部标签。它应该包含我们在index.js中导入的样式块。

请注意,您可以(并且在大多数情况下应该)压缩css,以便在生产环境中获得更快的加载时间。除此之外,几乎所有你能想到的CSS风格都有对应的加载器——postcsssassless,仅举几例。

加载图片

现在我们已经引入了CSS,但是我们如何处理背景和图标之类的图片呢?从webpack 5开始,使用内置的资产模块,我们可以轻松地将它们整合到我们的系统中。

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'),也会发生类似的过程。加载器会识别出这是一个本地文件,并用您output目录中图片的最终路径替换'./my-image.png'路径。html-loader以相同的方式处理<img src="./my-image.png" />

让我们向项目中添加一个图片,看看它是如何工作的,您可以使用任何您喜欢的图片。

项目

  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, 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

 .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',
+      },
     ],
   },
 };

将一些字体文件添加到您的项目中

项目

  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

配置好加载器并准备好字体后,您可以通过 `@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。对 JSON 的支持实际上是内置的,类似于 NodeJS,这意味着 `import Data from './data.json'` 默认情况下将起作用。要导入 CSV、TSV 和 XML,您可以使用 csv-loaderxml-loader。让我们处理加载所有三种类型的数据。

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'],
+      },
     ],
   },
 };

将一些数据文件添加到您的项目中

项目

  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

现在您可以 `import` 任何这四种类型的数据(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, 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`。如果您查看开发者工具中的控制台,您应该能够看到您导入的数据被记录到控制台中!

// No warning
import data from './data.json';

// Warning shown, this is not allowed by the spec.
import { foo } from './data.json';

自定义 JSON 模块的解析器

可以通过使用 自定义解析器 而不是特定的 webpack 加载器,将任何 `toml`、`yaml` 或 `json5` 文件作为 JSON 模块导入。

假设您在 src 文件夹下有 data.tomldata.yamldata.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',
  },
}

首先安装 tomlyamljsjson5

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); // 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 目录,您可以将资产与使用它们的代码分组。例如,以下结构可能很有用

- |- /assets
+ |– /components
+ |  |– /my-component
+ |  |  |– index.jsx
+ |  |  |– index.css
+ |  |  |– icon.svg
+ |  |  |– img.png

这种设置使您的代码更具可移植性,因为现在所有紧密耦合的内容都放在一起。假设您想在另一个项目中使用 /my-component,将其复制或移动到那里的 /components 目录中。只要您已安装任何外部依赖项,并且您的配置具有相同的加载器定义,您就可以正常运行。

但是,假设您坚持使用旧方法,或者您有一些在多个组件(视图、模板、模块等)之间共享的资产。仍然可以将这些资产存储在基本目录中,甚至使用 别名 使它们更容易 import

总结

对于接下来的指南,我们不会使用本指南中使用过的所有不同资产,因此让我们进行一些清理,以便为指南的下一部分 输出管理 做好准备

项目

  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); // 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 uninstall css-loader csv-loader json5 style-loader toml xml-loader yamljs

下一指南

让我们继续 输出管理

进一步阅读

10 位贡献者

skipjackmichael-ciniawskyTheDutchCodersudarsangpchenxsanEugeneHlushkoAnayaDesignwizardofhogwartsastonizersnitin315