知海

编写库(Authoring Libraries)

webpackjsorg-main指南-教程

编写库(Authoring Libraries)

除了应用程序,webpack 还可用于打包 JavaScript 库。以下指南面向希望简化其打包策略的库作者。

编写一个库

假设我们正在编写一个小型库 webpack-numbers,它允许用户将数字 1 到 5 从数字表示转换为文本表示,反之亦然,例如,将 2 转换为 'two'。

基本项目结构如下:

项目

diff 复制代码
+ ├── webpack.config.js
+ ├── package.json
+ └── /src
+     ├── index.js
+     └── ref.json

使用 npm 初始化项目,然后将 webpackwebpack-clilodash 安装为开发依赖:

bash 复制代码
npm init -y
npm install --save-dev webpack webpack-cli lodash

我们将 lodash 安装为 devDependency,因为最初我们会将它打包进我们的库中。由于它包含在最终输出中,我们库的使用者将无需自行安装它。

src/ref.json

json 复制代码
[
  {
    "num": 1,
    "word": "One"
  },
  {
    "num": 2,
    "word": "Two"
  },
  {
    "num": 3,
    "word": "Three"
  },
  {
    "num": 4,
    "word": "Four"
  },
  {
    "num": 5,
    "word": "Five"
  },
  {
    "num": 0,
    "word": "Zero"
  }
]

src/index.js

js 复制代码
import _ from "lodash";
import numRef from "./ref.json";

export function numToWord(num) {
  return _.reduce(
    numRef,
    (accum, ref) => (ref.num === num ? ref.word : accum),
    "",
  );
}

export function wordToNum(word) {
  return _.reduce(
    numRef,
    (accum, ref) => (ref.word === word && word.toLowerCase() ? ref.num : accum),
    -1,
  );
}

Webpack 配置

让我们从以下基本的 webpack 配置开始:

webpack.config.js

js 复制代码
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: {
    path: path.resolve(__dirname, "dist"),
    filename: "webpack-numbers.js",
  },
};

在上面的示例中,我们让 webpack 将 src/index.js 打包到 dist/webpack-numbers.js

添加 Source Map

在打包库时,建议生成 source map。Source map 允许您库的使用者调试您的原始源代码,而不是压缩后的 bundle。这可以通过 devtool 选项实现:

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',
+   devtool: 'source-map',
    output: {
      path: path.resolve(__dirname, 'dist'),
      filename: 'webpack-numbers.js',
    },
  };

T> 将 devtool 的值设置为 'source-map' 会生成一个与 bundle 同目录的独立 .map 文件。请确保也发布这个 .map 文件,以便使用者可以用它进行调试。

暴露库

到目前为止,一切都应该与打包应用程序相同,接下来的部分则有所不同——我们需要通过 output.library 选项暴露入口点的导出。

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: {
      path: path.resolve(__dirname, 'dist'),
      filename: 'webpack-numbers.js',
+     library: 'webpackNumbers',
    },
  };

我们将入口点暴露为 webpackNumbers,以便用户可以通过 script 标签使用它:

html 复制代码
<script src="https://example.org/webpack-numbers.js"></script>
<script>
  window.webpackNumbers.wordToNum("Five");
</script>

然而,这仅在通过 script 标签引用时有效,它不能用于其他环境,如 CommonJS、AMD、Node.js 等。

作为库作者,我们希望它能兼容不同的环境,即用户应该能够通过以下几种方式使用打包后的库:

  • CommonJS 模块 require

    js 复制代码
    const webpackNumbers = require("webpack-numbers");
    
    // ...
    webpackNumbers.wordToNum("Two");
  • AMD 模块 require

    js 复制代码
    require(["webpackNumbers"], (webpackNumbers) => {
      // ...
      webpackNumbers.wordToNum("Two");
    });
  • script 标签

    html 复制代码
    <!DOCTYPE html>
    <html>
      ...
      <script src="https://example.org/webpack-numbers.js"></script>
      <script>
        // ...
        // 全局变量
        webpackNumbers.wordToNum("Five");
        // window 对象上的属性
        window.webpackNumbers.wordToNum("Five");
        // ...
      </script>
    </html>

让我们更新 output.library 选项,并将其 type 设置为 'umd'

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: {
     path: path.resolve(__dirname, 'dist'),
     filename: 'webpack-numbers.js',
-    library: 'webpackNumbers',
+    globalObject: 'this',
+    library: {
+      name: 'webpackNumbers',
+      type: 'umd',
+    },
   },
 };

现在 webpack 将打包一个可以同时适用于 CommonJS、AMD 和 script 标签的库。

T> 请注意,library 配置与 entry 配置相关。对于大多数库,指定一个单一的入口点就足够了。虽然可以实现多部分库,但更简单的做法是通过一个 index script 作为单一入口点来暴露部分导出。对于库来说,不建议使用 array 作为 entry 入口点。

外部化 Lodash

现在,如果你运行 npx webpack,你会发现创建了一个相当大的 bundle。如果你检查该文件,会看到 lodash 已经与你的代码一起被打包。为了避免打包 lodash 并使我们的库变得臃肿,我们可以配置 webpack 将其视为外部模块。由于我们不再打包它,使用者将需要自行提供它。因此,你应该将 lodashdevDependencies 移动到 dependencies(或 peerDependencies),以便包管理器为你库的使用者自动安装它。

这可以通过 externals 配置来实现:

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: {
      path: path.resolve(__dirname, 'dist'),
      filename: 'webpack-numbers.js',
      library: {
        name: 'webpackNumbers',
        type: 'umd',
      },
    },
+   externals: {
+     lodash: {
+       commonjs: 'lodash',
+       commonjs2: 'lodash',
+       amd: 'lodash',
+       root: '_',
+     },
+   },
  };

这意味着你的库期望在使用者的环境中存在一个名为 lodash 的依赖。

外部化的限制

对于使用某个依赖中多个文件的库:

js 复制代码
import A from "library/one";
import B from "library/two";

// ...

你无法通过在 externals 中指定 library 来将它们排除在 bundle 之外。你需要逐个排除它们,或者使用正则表达式。

js 复制代码
export default {
  // ...
  externals: [
    "library/one",
    "library/two",
    // 所有以 "library/" 开头的内容
    /^library\/.+$/,
  ],
};

最终步骤

按照生产环境指南中提到的步骤优化你的生产环境输出。同时,让我们在 package.json 中添加生成 bundle 的路径作为包的 main 字段。

package.json

json 复制代码
{
  ...
  "main": "dist/webpack-numbers.js",
  ...
}

或者,根据此指南将其作为标准模块添加:

json 复制代码
{
  ...
  "module": "src/index.js",
  ...
}

main 键指的是 package.json 的标准,而 module 键指的是一个提案,旨在让 JavaScript 生态系统在不破坏向后兼容性的情况下升级使用 ES2015 模块。

W> module 属性应指向使用 ES2015 模块语法但不使用浏览器或 node 尚不支持的其他语法特性的脚本。这使得 webpack 可以自行解析模块语法,如果用户只使用库的某些部分,则可以通过摇树优化实现更轻量的 bundle。

现在,你可以将其发布为 npm 包,并在 unpkg.com 上找到它,以分发给你的用户。

T> 要暴露与你的库关联的样式表,应使用 MiniCssExtractPlugin。用户可以像加载任何其他样式表一样使用和加载它们。

帮助我们改进文档

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