外部扩展(externals)

externals 配置选项提供了「从输出的 bundle 中排除依赖」的方法。相反,所创建的 bundle 依赖于那些存在于用户环境(consumer's environment)中的依赖。此功能通常对 library 开发人员来说是最有用的,然而也会有各种各样的应用程序用到它。

externals

string [string] object function RegExp

防止将某些 import 的包(package)打包到 bundle 中,而是在运行时(runtime)再去从外部获取这些扩展依赖(external dependencies)

例如,从 CDN 引入 jQuery,而不是把它打包:

index.html

<script
  src="https://code.jquery.com/jquery-3.1.0.js"
  integrity="sha256-slogkvB1K3VOkzAI8QITxV3VzpOnkeNVsKvtkYLMjfk="
  crossorigin="anonymous"
></script>

webpack.config.js

module.exports = {
  //...
  externals: {
    jquery: 'jQuery',
  },
};

这样就剥离了那些不需要改动的依赖模块,换句话,下面展示的代码还可以正常运行:

import $ from 'jquery';

$('.my-element').animate(/* ... */);

具有外部依赖(external dependency)的 bundle 可以在各种模块上下文(module context)中使用,例如 CommonJS, AMD, 全局变量和 ES2015 模块。外部 library 可能是以下任何一种形式:

  • root:可以通过一个全局变量访问 library(例如,通过 script 标签)。
  • commonjs:可以将 library 作为一个 CommonJS 模块访问。
  • commonjs2:和上面的类似,但导出的是 module.exports.default.
  • amd:类似于 commonjs,但使用 AMD 模块系统。

可以接受以下语法……

字符串

请查看上面的例子。属性名称是 jquery,表示应该排除 import $ from 'jquery' 中的 jquery 模块。为了替换这个模块,jQuery 的值将被用来检索一个全局的 jQuery 变量。换句话说,当设置为一个字符串时,它将被视为全局的(定义在上面和下面)。

另一方面,如果你想将一个符合 CommonJS 模块化规则的类库外部化,你可以提供外联类库的类型以及类库的名称。

如果你想将 fs-extra 从输出的 bundle 中剔除并在运行时中引入它,你可以如下定义:

module.exports = {
  // ...
  externals: {
    'fs-extra': 'commonjs2 fs-extra',
  },
};

这样的做法会让任何依赖的模块都不变,正如以下所示的代码:

import fs from 'fs-extra';

会将代码编译成:

const fs = require('fs-extra');

[string]

module.exports = {
  //...
  externals: {
    subtract: ['./math', 'subtract'],
  },
};

subtract: ['./math', 'subtract'] 转换为父子结构,其中 ./math 是父模块,而 bundle 只引用 subtract 变量下的子集。该例子会编译成 require('./math').subtract;

对象

module.exports = {
  //...
  externals: {
    react: 'react',
  },

  // 或者

  externals: {
    lodash: {
      commonjs: 'lodash',
      amd: 'lodash',
      root: '_' // 指向全局变量
    },
  },

  // 或者

  externals: {
    subtract: {
      root: ['math', 'subtract'],
    },
  },
};

此语法用于描述外部 library 所有可用的访问方式。这里 lodash 这个外部 library 可以在 AMD 和 CommonJS 模块系统中通过 lodash 访问,但在全局变量形式下用 _ 访问。subtract 可以通过全局 math 对象下的属性 subtract 访问(例如 window['math']['subtract'])。

函数

  • function ({ context, request, contextInfo, getResolve }, callback)
  • function ({ context, request, contextInfo, getResolve }) => promise 5.15.0+

对于 webpack 外部化,通过定义函数来控制行为,可能会很有帮助。例如,webpack-node-externals 能够排除 node_modules 目录中所有模块,还提供一些选项,比如白名单 package(whitelist package)。

函数接收两个入参:

  • ctx (object):包含文件详情的对象。
    • ctx.context (string): 包含引用的文件目录。
    • ctc.request (string): 被请求引入的路径。
    • ctx.contextInfo (string): 包含 issuer 的信息(如,layer)
    • ctx.getResolve 5.15.0+: 获取当前解析器选项的解析函数。
  • callback (function (err, result, type)): 用于指明模块如何被外部化的回调函数

回调函数接收三个入参:

  • err (Error): 被用于表明在外部外引用的时候是否会产生错误。如果有错误,这将会是唯一被用到的参数。
  • result (string [string] object): 描述外部化的模块。可以接受形如 ${type} ${path} 格式的字符串,或者其它标准化外部化模块格式,(string, [string],或 object)。
  • type (string): 可选的参数,用于指明模块的类型(如果它没在 result 参数中被指明)。

作为例子,要外部化所有匹配一个正则表达式的引入,你可以像下面那样处理:

webpack.config.js

module.exports = {
  //...
  externals: [
    function ({ context, request }, callback) {
      if (/^yourregex$/.test(request)){
        // 使用 request 路径,将一个 commonjs 模块外部化
        return callback(null, 'commonjs ' + request);
      }

      // 继续下一步且不外部化引用
      callback();
    },
  ],
};

其它例子使用不同的模块格式:

webpack.config.js

module.exports = {
  externals: [
    function (ctx, callback) {
      // 该外部化的模块,是一个 `commonjs2` 的模块,且放在 `@scope/library` 目录中
      callback(null, '@scope/library', 'commonjs2');
    },
  ],
};

webpack.config.js

module.exports = {
  externals: [
    function (ctx, callback) {
      // 该外部化模块是一个全局变量叫作 `nameOfGlobal`.
      callback(null, 'nameOfGlobal');
    },
  ],
};

webpack.config.js

module.exports = {
  externals: [
    function (ctx, callback) {
      // 该外部化模块是一个在`@scope/library`模块里的命名导出(named export)。
      callback(null, ['@scope/library', 'namedexport'], 'commonjs');
    },
  ],
};

webpack.config.js

module.exports = {
  externals: [
    function (ctx, callback) {
      // 外部化模块是一个 UMD 模块
      callback(null, {
        root: 'componentsGlobal',
        commonjs: '@scope/components',
        commonjs2: '@scope/components',
        amd: 'components',
      });
    },
  ],
};

RegExp

匹配给定正则表达式的每个依赖,都将从输出 bundle 中排除。

webpack.config.js

module.exports = {
  //...
  externals: /^(jquery|\$)$/i,
};

这个示例中,所有名为 jQuery 的依赖(忽略大小写),或者 $,都会被外部化。

混用语法

有时你需要混用上面介绍的语法。这可以像以下这样操作:

webpack.config.js

module.exports = {
  //...
  externals: [
    {
      // 字符串
      react: 'react',
      // 对象
      lodash: {
        commonjs: 'lodash',
        amd: 'lodash',
        root: '_', // indicates global variable
      },
      // 字符串数组
      subtract: ['./math', 'subtract'],
    },
    // 函数
    function ({ context, request }, callback) {
      if (/^yourregex$/.test(request)) {
        return callback(null, 'commonjs ' + request);
      }
      callback();
    },
    // 正则表达式
    /^(jquery|\$)$/i,
  ],
};

关于如何使用此 externals 配置的更多信息,请参考 如何编写 library

byLayer

function object

按层指定 externals:

webpack.config.js

module.exports = {
  externals: {
    byLayer: {
      layer: {
        external1: 'var 43',
      },
    },
  },
};

externalsType

string = 'var'

指定 externals 的默认类型。当 external 被设置为 amdumdsystem 以及 jsonp 时,output.libraryTarget 的值也应相同。例如,你只能在 amd 库中使用 amd 的 externals。

支持的类型如下:

  • 'var'
  • 'module'
  • 'assign'
  • 'this'
  • 'window'
  • 'self'
  • 'global'
  • 'commonjs'
  • 'commonjs-module'
  • 'amd'
  • 'amd-require'
  • 'umd'
  • 'umd2'
  • 'jsonp'
  • 'system'
  • 'promise' - 与 'var' 相同,但会 awaits 结果(适用于 async 模块)
  • 'import' - 使用 import() 加载原生的 EcmaScript 模块(适用于 async 模块)
  • 'script' - 使用 HTML 的 <script> 元素加载 script,用于暴露预定义的全局变量

webpack.config.js

module.exports = {
  //...
  externalsType: 'promise',
};

script

External script can be loaded from any URL when externalsType is set to 'script'. The <script> tag would be removed after the script has been loaded.

Syntax

module.exports = {
  externals: {
    packageName: [
      'http://example.com/script.js',
      'global',
      'property',
      'property',
    ], // properties are optional
  },
};

如果你不打算定义任何熟悉,你可以使用简写形式:

module.exports = {
  externals: {
    packageName: 'global@http://example.com/script.js', // no properties here
  },
};

示例

从 CDN 加载 lodash

webpack.config.js

module.exports = {
  // ...
  externalsType: 'script',
  externals: {
    lodash: ['https://cdn.jsdelivr.net/npm/lodash@4.17.19/lodash.min.js', '_'],
  },
};

然后,代码中使用方式如下:

import _ from 'lodash';
console.log(_.head([1, 2, 3]));

下面示例是针对上面示例新增了属性配置:

module.exports = {
  // ...
  externalsType: 'script',
  externals: {
    lodash: [
      'https://cdn.jsdelivr.net/npm/lodash@4.17.19/lodash.min.js',
      '_',
      'head',
    ],
  },
};

当你 import 'loadsh' 时,局部变量 head 和全局变量 window._ 都会被暴露:

import head from 'lodash';
console.log(head([1, 2, 3])); // logs 1 here
console.log(window._.head(['a', 'b'])); // logs a here

externalsPresets

object

为特定的 target 启用 externals 的 preset。

选项描述输入类型
electron将 main 和预加载上下文中常见的 electron 内置模块视为 external 模块(如 electronipcshell),使用时通过 require() 加载。boolean
electronMain将 main 上下文中的 electron 内置模块视为 external 模块(如 appipc-mainshell),使用时通过 require() 加载。boolean
electronPreload将预加载上下文的 electron 内置模块视为 external 模块(如 web-frameipc-renderershell),使用时通过 require() 加载。boolean
electronRenderer将 renderer 上下文的 electron 内置模块视为 external 模块(如 web-frameipc-renderershell),使用时通过 require() 加载。boolean
node将 node.js 的内置模块视为 external 模块(如 fspathvm),使用时通过 require() 加载。boolean
nwjsNW.js 遗留的 nw.gui 模块视为 external 模块,使用时通过 require() 加载。boolean
webhttp(s)://... 以及 std:... 视为 external 模块,使用时通过 import 加载。(注意,这将改变执行顺序,因为 external 代码会在该块中的其他代码执行前被执行)boolean
webAsync将 'http(s)://...' 以及 'std:...' 的引用视为 external 模块,使用时通过 async import() 加载。(注意,此 external 类型为 async 模块,它对执行会产生各种副作用)boolean

Example

使用 node 的 preset 不会构建内置模块,而会将其视为 external 模块,使用时通过 require() 加载。

webpack.config.js

module.exports = {
  // ...
  externalsPresets: {
    node: true,
  },
};