Webpack 管理資源

2023-05-17 15:13 更新

如果你是從開始一直在沿用指南的示例,現(xiàn)在會有一個小項目,顯示 ?"Hello webpack"??,F(xiàn)在我們嘗試混合一些其他資源,比如 images,看看 webpack 如何處理。

在 webpack 出現(xiàn)之前,前端開發(fā)人員會使用 grunt 和 gulp 等工具來處理資源,并將它們從 /src 文件夾移動到 /dist 或 /build 目錄中。JavaScript 模塊也遵循同樣方式,但是,像 webpack 這樣的工具,將動態(tài)打包所有依賴(創(chuàng)建所謂的 依賴圖(dependency graph))。這是極好的創(chuàng)舉,因為現(xiàn)在每個模塊都可以明確表述它自身的依賴,可以避免打包未使用的模塊。

webpack 最出色的功能之一就是,除了引入 JavaScript,還可以通過 loader 或內(nèi)置的 Asset Modules 引入任何其他類型的文件。也就是說,以上列出的那些 JavaScript 的優(yōu)點(例如顯式依賴),同樣可以用來構(gòu)建 web 站點或 web 應(yīng)用程序中的所有非 JavaScript 內(nèi)容。讓我們從 CSS 開始起步,或許你可能已經(jīng)熟悉了下面這些設(shè)置。

設(shè)置

在開始之前,讓我們對項目做一個小的修改:

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

加載 CSS

為了在 JavaScript 模塊中 import 一個 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'],
+      },
+    ],
+  },
 };

模塊 loader 可以鏈式調(diào)用。鏈中的每個 loader 都將對資源進行轉(zhuǎn)換。鏈會逆序執(zhí)行。第一個 loader 將其結(jié)果(被轉(zhuǎn)換后的資源)傳遞給下一個 loader,依此類推。最后,webpack 期望鏈中的最后的 loader 返回 JavaScript。

應(yīng)保證 loader 的先后順序:'style-loader' 在前,而 'css-loader' 在后。如果不遵守此約定,webpack 可能會拋出錯誤。

這使你可以在依賴于此樣式的 js 文件中 import './style.css'。現(xiàn)在,在此模塊執(zhí)行過程中,含有 CSS 字符串的 <style> 標簽,將被插入到 html 文件的 <head> 中。

我們嘗試一下,通過在項目中添加一個新的 style.css 文件,并將其 import 到我們的 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, now imported by this script
   element.innerHTML = _.join(['Hello', 'webpack'], ' ');
+  element.classList.add('hello');

   return element;
 }

 document.body.appendChild(component());

現(xiàn)在運行 build 命令:

$ 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,你應(yīng)該看到 Hello webpack 現(xiàn)在的樣式是紅色。要查看 webpack 做了什么,請檢查頁面(不要查看頁面源代碼,它不會顯示結(jié)果,因為 <style> 標簽是由 JavaScript 動態(tài)創(chuàng)建的),并查看頁面的 head 標簽。它應(yīng)該包含 style 塊元素,也就是我們在 index.js 中 import 的 css 文件中的樣式。

注意,在多數(shù)情況下,你也可以進行 壓縮 CSS,以便在生產(chǎn)環(huán)境中節(jié)省加載時間。最重要的是,現(xiàn)有的 loader 可以支持任何你可以想到的 CSS 風(fēng)格 - postcss, sass 和 less 等。

加載 images 圖像

假如,現(xiàn)在我們正在下載 CSS,但是像 background 和 icon 這樣的圖像,要如何處理呢?在 webpack 5 中,可以使用內(nèi)置的 Asset Modules,我們可以輕松地將這些內(nèi)容混入我們的系統(tǒng)中:

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

現(xiàn)在,在 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, now imported by this script
   element.innerHTML = _.join(['Hello', 'webpack'], ' ');
   element.classList.add('hello');

+  // 將圖像添加到我們已經(jīng)存在的 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');
 }

重新構(gòu)建并再次打開 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

如果一切順利,你現(xiàn)在應(yīng)該看到你的 icon 圖標成為了重復(fù)的背景圖,以及 Hello webpack 文本旁邊的 img 元素。如果檢查此元素,你將看到實際的文件名已更改為 29822eaa871e8eadeaa4.png。這意味著 webpack 在 src 文件夾中找到我們的文件,并對其進行了處理!

加載 fonts 字體

那么,像字體這樣的其他資源如何處理呢?使用 Asset Modules 可以接收并加載任何文件,然后將其輸出到構(gòu)建目錄。這就是說,我們可以將它們用于任何類型的文件,也包括字體。讓我們更新 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');
 }

現(xiàn)在,讓我們重新構(gòu)建,然后看下 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 文本顯示是否換上了新的字體。如果一切順利,你應(yīng)該能看到變化。

加載數(shù)據(jù)

此外,可以加載的有用資源還有數(shù)據(jù),如 JSON 文件,CSV、TSV 和 XML。類似于 NodeJS,JSON 支持實際上是內(nèi)置的,也就是說 import Data from './data.json' 默認將正常運行。要導(dǎo)入 CSV、TSV 和 XML,你可以使用 csv-loader 和 xml-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'],
+      },
     ],
   },
 };

在項目中添加一些數(shù)據(jù)文件:

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

現(xiàn)在,你可以 import 這四種類型的數(shù)據(jù)(JSON, CSV, TSV, XML)中的任何一種,所導(dǎo)入的 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());

重新執(zhí)行 npm run build 命令,然后打開 dist/index.html。查看開發(fā)者工具中的控制臺,你應(yīng)該能夠看到導(dǎo)入的數(shù)據(jù)會被打印出來!

// 沒有警告
import data from './data.json';

// 顯示警告,規(guī)范不允許這樣做。
import { foo } from './data.json';

自定義 JSON 模塊 parser

通過使用 自定義 parser 替代特定的 webpack loader,可以將任何 toml、yaml 或 json5 文件作為 JSON 模塊導(dǎo)入。

假設(shè)你在 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 的 packages:

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());

重新執(zhí)行 npm run build 命令,然后打開 dist/index.html。你應(yīng)該能夠看到導(dǎo)入的數(shù)據(jù)會被打印到控制臺上!

全局資源

上述所有內(nèi)容中最出色之處在于,以這種方式加載資源,你可以以更直觀的方式將模塊和資源組合在一起。無需依賴于含有全部資源的 /assets 目錄,而是將資源與代碼組合在一起使用。例如,類似這樣的結(jié)構(gòu)會非常有用:

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

這種配置方式會使你的代碼更具備可移植性,因為現(xiàn)有的集中放置的方式會讓所有資源緊密耦合起來。假如你想在另一個項目中使用 /my-component,只需將其復(fù)制或移動到 /components 目錄下。只要你已經(jīng)安裝過全部 外部依賴 ,并且 已經(jīng)在配置中定義過相同的 loader ,那么項目應(yīng)該能夠良好運行。

但是,假如你只能被局限在舊有開發(fā)方式,或者你有一些在多個組件(視圖、模板、模塊等)之間共享的資源。你仍然可以將這些資源存儲在一個基本目錄(base directory)中,甚至配合使用 alias 來使它們更方便 import 導(dǎo)入。

回退處理

對于下篇指南,我們無需使用本指南中所有用到的資源,因此我們會進行一些清理工作,以便為下篇指南 管理輸出 做好準備:

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); // 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); //  `JSON5 Example`
-console.log(json.owner.name); // output `Tom Preston-Werner`

 function component() {
   const element = document.createElement('div');

-  // lodash,現(xiàn)在通過 script 標簽導(dǎo)入
   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

下篇指南

我們繼續(xù)移步到 管理輸出

延伸閱讀


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號