資源管理

如果你從一開始就遵循指南,現在你將會有一個顯示「Hello webpack」的小專案。現在讓我們嘗試加入一些其他資產,例如圖片,看看如何處理它們。

在 webpack 之前,前端開發人員會使用 gruntgulp 等工具來處理這些資產,並將它們從 /src 資料夾移到 /dist/build 目錄。JavaScript 模組也使用相同的想法,但 webpack 等工具會動態組合所有相依性(建立所謂的 相依性圖)。這很好,因為現在每個模組都會明確指出其相依性,而且我們會避免組合未使用中的模組。

Webpack 最酷的功能之一是,除了 JavaScript 之外,你還可以包含任何其他類型的檔案,這些檔案有載入器或內建的 資源模組 支援。這表示上面列出的 JavaScript 相同好處(例如明確的相依性)可以套用在建置網站或網路應用程式的任何事物上。讓我們從 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' 匯入依賴於該樣式的檔案中。現在,當執行該模組時,將會在 html 檔案的 <head> 中插入一個包含字串化 css 的 <style> 標籤。

讓我們透過在專案中新增一個 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 動態建立的)並查看頁面的 head 標籤。它應該包含我們在 index.js 中匯入的樣式區塊。

請注意,您可以在大多數情況下,最小化 css 以在生產環境中獲得更好的載入時間。除此之外,幾乎所有您能想到的 CSS 類型都有對應的載入器,例如 postcsssassless 等。

載入圖片

現在我們正在載入我們的 CSS,但我們的圖片(例如背景和圖示)呢?從 webpack 5 開始,使用內建的 Asset Modules,我們也可以輕鬆地將它們納入我們的系統中

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變數中,在處理後會包含該影像的最終網址。當使用css-loader時,如上所示,你的 CSS 中的url('./my-image.png')會發生類似的處理程序。載入器會辨識這是個本機檔案,並將'./my-image.png'路徑替換為output目錄中影像的最終路徑。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資料夾中找到了我們的檔案並處理了它!

載入字型

那麼像字型這樣的其他資源呢?Asset 模組會取得你透過它們載入的任何檔案,並將其輸出到你的建置目錄。這表示我們可以用於任何類型的檔案,包括字型。讓我們更新我們的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 loader,將任何 tomlyamljson5 檔案匯入為 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