知海

资源管理

webpackjsorg-main指南-教程

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

在 webpack 出现之前,前端开发人员会使用 gruntgulp 等工具来处理这些资源,并将它们从 /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-loadercss-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-loaderuse 数组中出现在 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——例如 postcsssassless

加载图像

现在我们引入了 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-loaderxml-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(解析器) 将任何 tomlyamljson5 文件作为 JSON 模块导入,而无需使用特定的 webpack loader。

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

首先安装 tomlyamljsjson5 包:

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(输出管理)

延伸阅读

帮助我们改进文档

发现翻译问题或内容错误?请告诉我们。