编写库 | Authoring Libraries

创建一个 library

除了打包应用程序代码,webpack 还可以用于打包 JavaScript library。以下指南适用于希望流水线化(streamline)打包策略的 library 作者。

创作一个库

假设你正在编写一个名为 webpack-numbers 的小的 library,可以将数字 1 到 5 转换为文本表示,反之亦然,例如将 2 转换为 'two'。

基本的项目结构可能如下所示:

项目

+ |- webpack.config.js + |- package.json + |- /src + |- index.js + |- ref.json

初始化npm,安装webpack和lodash:

npm init -y npm install --save-dev webpack lodash

src/ref.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

import _ from 'lodash'; import numRef from './ref.json'; export function numToWord(num) { return _.reduce(numRef, (accum, ref) => { return ref.num === num ? ref.word : accum; }, '' }; export function wordToNum(word) { return _.reduce(numRef, (accum, ref) => { return ref.word === word && word.toLowerCase() ? ref.num : accum; }, -1 };

库使用的使用规范如下:

// ES2015 module import import * as webpackNumbers from 'webpack-numbers'; // CommonJS module require var webpackNumbers = require('webpack-numbers' // ... // ES2015 and CommonJS module use webpackNumbers.wordToNum('Two' // ... // AMD module require require(['webpackNumbers'], function ( webpackNumbers) { // ... // AMD module use webpackNumbers.wordToNum('Two' // ... }

用户还可以通过 script 标签来加载和使用此 library:

<html> ... <script src="https://unpkg.com/webpack-numbers"></script> <script> // ... // Global variable webpackNumbers.wordToNum('Five') // Property in the window object window.webpackNumbers.wordToNum('Five') // ... </script> </html>

注意,我们还可以通过以下配置方式,将 library 暴露:

  • global 对象中的属性,用于 Node.js。

  • this对象中的属性。

有关完整的库配置和代码,请参阅webpack-library-example

基本配置

现在,让我们以某种方式打包这个 library,能够实现以下几个目标:

  • 不打包 lodash,而是使用 externals 来 require 用户加载好的 lodash

  • 将库名称设置为webpack-numbers

  • 将 library 暴露为一个名为 webpackNumbers的变量。

  • 能够访问Node.js中的库

此外,用户应该能够通过以下方式访问 library:

  • ES2015模块。即import webpackNumbers from 'webpack-numbers'

  • CommonJS模块。即require('webpack-numbers')

  • 通过script标签包含全局变量。

我们可以从这个基本的webpack配置开始:

webpack.config.js

var path = require('path' module.exports = { entry: './src/index.js', output: { path: path.resolve(__dirname, 'dist'), filename: 'webpack-numbers.js' } };

外部化 Lodash

现在,如果执行 webpack,你会发现创建了一个非常巨大的文件。如果你查看这个文件,会看到 lodash 也被打包到代码中。在这种场景中,我们更倾向于把 lodash 当作 peerDependency。也就是说,用户应该已经将 lodash 安装好。因此,你可以放弃对外部 library 的控制,而是将控制权让给使用 library 的用户。

这可以使用externals配置完成:

webpack.config.js

var path = require('path' module.exports = { entry: './src/index.js', output: { path: path.resolve(__dirname, 'dist'), filename: 'webpack-numbers.js' - } + }, + externals: { + lodash: { + commonjs: 'lodash', + commonjs2: 'lodash', + amd: 'lodash', + root: '_' + } + } };

这意味着你的 library 需要一个名为 lodash 的依赖,这个依赖在用户的环境中必须存在且可用。

请注意,如果您只打算将库用作另一个webpack包中的依赖项,则可以指定externals为数组。

对于从一个依赖目录中,调用多个文件的 library:

import A from 'library/one'; import B from 'library/two'; // ...

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

externals: [ 'library/one', 'library/two', // Everything that starts with "library/" /^library\/.+$/ ]

公开库

对于用途广泛的 library,我们希望它能够兼容不同的环境,例如 CommonJS,AMD,Node.js 或者作为一个全局变量。为了让你的 library 能够在各种用户环境(consumption)中可用,需要在 output 中添加 library 属性:

webpack.config.js

var path = require('path' module.exports = { entry: './src/index.js', output: { path: path.resolve(__dirname, 'dist'), - filename: 'webpack-numbers.js' + filename: 'webpack-numbers.js', + library: 'webpackNumbers' }, externals: { lodash: { commonjs: 'lodash', commonjs2: 'lodash', amd: 'lodash', root: '_' } } };

当你在 import 引入模块时,这可以将你的 library bundle 暴露为名为 webpackNumbers 的全局变量。为了让 library 和其他环境兼容,还需要在配置文件中添加 libraryTarget 属性。这是可以控制 library 如何以不同方式暴露的选项。

webpack.config.js

var path = require('path' module.exports = { entry: './src/index.js', output: { path: path.resolve(__dirname, 'dist'), filename: 'webpack-numbers.js', - library: 'webpackNumbers' + library: 'webpackNumbers', + libraryTarget: 'umd' }, externals: { lodash: { commonjs: 'lodash', commonjs2: 'lodash', amd: 'lodash', root: '_' } } };

可以通过以下方式暴露 library:

  • 变量:作为一个全局变量,通过 script 标签来访问(libraryTarget:'var')。

  • this:通过 this 对象访问(libraryTarget:'this')。

  • window:通过 window 对象访问,在浏览器中(libraryTarget:'window')。

  • UMD:在 AMD 或 CommonJS 的 require 之后可访问(libraryTarget:'umd')。

如果设置了 library 但没设置 libraryTarget,则 libraryTarget 默认为 var,详细说明请查看 output 配置文档。查看output.libraryTarget,以获取所有可用选项的详细列表。

在 webpack 3.5.5 中,使用 libraryTarget: { root:'_' } 将无法正常工作(参考 issue 4824) 所述)。然而,可以设置 libraryTarget: { var: '_' } 来将 library 作为全局变量。

最后的步骤

遵循生产环境指南中的步骤,来优化生产环境下的输出。那么,我们还需要通过设置 package.json 中的 main 字段,添加生成 bundle 的文件路径。

package.json

{ ... "main": "dist/webpack-numbers.js", ... }

或者,按照本指南添加为标准模块:

{ ... "module": "src/index.js", ... }

键(key) main 是指 package.json 标准,以及 module 是 一个提案,此提案允许 JavaScript 生态系统升级使用 ES2015 模块,而不会破坏向后兼容性。

module 属性应指向一个使用 ES2015 模块语法的脚本,但不包括浏览器或 Node.js 尚不支持的其他语法特性。这使得 webpack 本身就可以解析模块语法,如果用户只用到 library 的某些部分,则允许通过 tree shaking 打包更轻量的包。

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