Output

output 位于对象最顶级键(key),包括了一组选项,指示 webpack 如何去输出、以及在哪里输出你的「bundle、asset 和其他你所打包或使用 webpack 载入的任何内容」。

output.assetModuleFilename

string = '[hash][ext][query]'

output.filename 相同,不过应用于 Asset Modules

对从数据 URI 替换构建的静态资源,[name], [file], [query], [fragment], [base][path] 为空字符串。

output.asyncChunks

boolean = true

创建按需加载的异步 chunk。

webpack.config.js

module.exports = {
  //...
  output: {
    //...
    asyncChunks: true,
  },
};

output.auxiliaryComment

string object

在和 output.libraryoutput.libraryTarget 一起使用时,此选项允许用户向导出容器(export wrapper)中插入注释。要为 libraryTarget 每种类型都插入相同的注释,将 auxiliaryComment 设置为一个字符串:

webpack.config.js

module.exports = {
  //...
  output: {
    library: 'someLibName',
    libraryTarget: 'umd',
    filename: 'someLibName.js',
    auxiliaryComment: 'Test Comment',
  },
};

将会生成如下:

someLibName.js

(function webpackUniversalModuleDefinition(root, factory) {
  // Test Comment
  if (typeof exports === 'object' && typeof module === 'object')
    module.exports = factory(require('lodash'));
  // Test Comment
  else if (typeof define === 'function' && define.amd)
    define(['lodash'], factory);
  // Test Comment
  else if (typeof exports === 'object')
    exports['someLibName'] = factory(require('lodash'));
  // Test Comment
  else root['someLibName'] = factory(root['_']);
})(this, function (__WEBPACK_EXTERNAL_MODULE_1__) {
  // ...
});

对于 libraryTarget 每种类型的注释进行更细粒度地控制,请传入一个对象:

webpack.config.js

module.exports = {
  //...
  output: {
    //...
    auxiliaryComment: {
      root: 'Root Comment',
      commonjs: 'CommonJS Comment',
      commonjs2: 'CommonJS2 Comment',
      amd: 'AMD Comment',
    },
  },
};

output.charset

boolean = true

告诉 webpack 为 HTML 的 <script> 标签添加 charset="utf-8" 标识。

output.chunkFilename

string = '[id].js' function (pathData, assetInfo) => string

此选项决定了非初始(non-initial)chunk 文件的名称。有关可取的值的详细信息,请查看 output.filename 选项。

注意,这些文件名需要在运行时根据 chunk 发送的请求去生成。因此,需要在 webpack runtime 输出 bundle 值时,将 chunk id 的值对应映射到占位符(如 [name][chunkhash])。这会增加文件大小,并且在任何 chunk 的占位符值修改后,都会使 bundle 失效。

默认使用 [id].js 或从 output.filename 中推断出的值([name] 会被预先替换为 [id][id].)。

webpack.config.js

module.exports = {
  //...
  output: {
    //...
    chunkFilename: '[id].js',
  },
};

Usage as a function:

webpack.config.js

module.exports = {
  //...
  output: {
    chunkFilename: (pathData) => {
      return pathData.chunk.name === 'main' ? '[name].js' : '[name]/[name].js';
    },
  },
};

output.chunkFormat

false string: 'array-push' | 'commonjs' | 'module' | <any string>

chunk 的格式(formats 默认包含 'array-push' (web/WebWorker)、'commonjs' (node.js)、'module' (ESM),还有其他情况可由插件添加)。

webpack.config.js

module.exports = {
  //...
  output: {
    //...
    chunkFormat: 'commonjs',
  },
};

output.chunkLoadTimeout $#outputchunkLoadtimeout$

number = 120000

chunk 请求到期之前的毫秒数,默认为 120000。从 webpack 2.6.0 开始支持此选项。

webpack.config.js

module.exports = {
  //...
  output: {
    //...
    chunkLoadTimeout: 30000,
  },
};

output.chunkLoadingGlobal

string = 'webpackChunkwebpack'

webpack 用于加载 chunk 的全局变量。

webpack.config.js

module.exports = {
  //...
  output: {
    //...
    chunkLoadingGlobal: 'myCustomFunc',
  },
};

output.chunkLoading

false string: 'jsonp' | 'import-scripts' | 'require' | 'async-node' | 'import' | <any string>

加载 chunk 的方法(默认值有 'jsonp' (web)、'import' (ESM)、'importScripts' (WebWorker)、'require' (sync node.js)、'async-node' (async node.js),还有其他值可由插件添加)。

webpack.config.js

module.exports = {
  //...
  output: {
    //...
    chunkLoading: 'async-node',
  },
};

output.clean

5.20.0+

boolean { dry?: boolean, keep?: RegExp | string | ((filename: string) => boolean) }

module.exports = {
  //...
  output: {
    clean: true, // 在生成文件之前清空 output 目录
  },
};
module.exports = {
  //...
  output: {
    clean: {
      dry: true, // 打印而不是删除应该移除的静态资源
    },
  },
};
module.exports = {
  //...
  output: {
    clean: {
      keep: /ignored\/dir\//, // 保留 'ignored/dir' 下的静态资源
    },
  },
};

// 或者

module.exports = {
  //...
  output: {
    clean: {
      keep(asset) {
        return asset.includes('ignored/dir');
      },
    },
  },
};

你也可以使用钩子函数:

webpack.CleanPlugin.getCompilationHooks(compilation).keep.tap(
  'Test',
  (asset) => {
    if (/ignored\/dir\//.test(asset)) return true;
  }
);

output.compareBeforeEmit

boolean = true

告知 webpack 在写入到输出文件系统时检查输出的文件是否已经存在并且拥有相同内容。

module.exports = {
  //...
  output: {
    compareBeforeEmit: false,
  },
};

output.crossOriginLoading

boolean = false string: 'anonymous' | 'use-credentials'

告诉 webpack 启用 cross-origin 属性 加载 chunk。仅在 target 设置为 'web' 时生效,通过使用 JSONP 来添加脚本标签,实现按需加载模块。

  • 'anonymous' - 不带凭据(credential) 启用跨域加载
  • 'use-credentials' - 携带凭据(credential) 启用跨域加载

output.devtoolFallbackModuleFilenameTemplate

string function (info)

当上面的模板字符串或函数产生重复时使用的备用内容。

查看 output.devtoolModuleFilenameTemplate

output.devtoolModuleFilenameTemplate

string = 'webpack://[namespace]/[resource-path]?[loaders]' function (info) => string

此选项仅在 「devtool 使用了需要模块名称的选项」时使用。

自定义每个 source map 的 sources 数组中使用的名称。可以通过传递模板字符串(template string)或者函数来完成。例如,当使用 devtool: 'eval',默认值是:

webpack.config.js

module.exports = {
  //...
  output: {
    devtoolModuleFilenameTemplate:
      'webpack://[namespace]/[resource-path]?[loaders]',
  },
};

模板字符串(template string)中做以下替换(通过 webpack 内部的 ModuleFilenameHelpers):

TemplateDescription
[absolute-resource-path]绝对路径文件名
[all-loaders]自动和显式的 loader,并且参数取决于第一个 loader 名称
[hash]模块标识符的 hash
[id]模块标识符
[loaders]显式的 loader,并且参数取决于第一个 loader 名称
[resource]用于解析文件的路径和用于第一个 loader 的任意查询参数
[resource-path]不带任何查询参数,用于解析文件的路径
[namespace]模块命名空间。在构建成为一个 library 之后,通常也是 library 名称,否则为空

当使用一个函数,同样的选项要通过 info 参数并使用驼峰式(camel-cased):

module.exports = {
  //...
  output: {
    devtoolModuleFilenameTemplate: (info) => {
      return `webpack:///${info.resourcePath}?${info.loaders}`;
    },
  },
};

如果多个模块产生相同的名称,使用 output.devtoolFallbackModuleFilenameTemplate 来代替这些模块。

output.devtoolNamespace

string

此选项确定 output.devtoolModuleFilenameTemplate 使用的模块名称空间。未指定时的默认值为:output.uniqueName。在加载多个通过 webpack 构建的 library 时,用于防止 source map 中源文件路径冲突。

例如,如果你有两个 library,分别使用命名空间 library1library2,并且都有一个文件 ./src/index.js(可能具有不同内容),它们会将这些文件暴露为 webpack://library1/./src/index.jswebpack://library2/./src/index.js

output.enabledChunkLoadingTypes

[string: 'jsonp' | 'import-scripts' | 'require' | 'async-node' | <any string>]

允许入口点使用的 chunk 加载类型列表。将被 webpack 自动填充。只有当使用一个函数作为入口配置项并从那里返回 chunkLoading 配置项时才需要。

webpack.config.js

module.exports = {
  //...
  output: {
    //...
    enabledChunkLoadingTypes: ['jsonp', 'require'],
  },
};

output.enabledLibraryTypes

[string]

入口点可用的 library 类型列表.

module.exports = {
  //...
  output: {
    enabledLibraryTypes: ['module'],
  },
};

output.enabledWasmLoadingTypes

[string]

用于设置入口支持的 wasm 加载类型的列表。

module.exports = {
  //...
  output: {
    enabledWasmLoadingTypes: ['fetch'],
  },
};

## `output.environment` $#outputenvironment$

告诉 webpack 在生成的运行时代码中可以使用哪个版本的 ES 特性。

```javascript
module.exports = {
  output: {
    environment: {
      // The environment supports arrow functions ('() => { ... }').
      arrowFunction: true,
      // The environment supports BigInt as literal (123n).
      bigIntLiteral: false,
      // The environment supports const and let for variable declarations.
      const: true,
      // The environment supports destructuring ('{ a, b } = obj').
      destructuring: true,
      // The environment supports an async import() function to import EcmaScript modules.
      dynamicImport: false,
      // The environment supports 'for of' iteration ('for (const x of array) { ... }').
      forOf: true,
      // The environment supports ECMAScript Module syntax to import ECMAScript modules (import ... from '...').
      module: false,
      // The environment supports optional chaining ('obj?.a' or 'obj?.()').
      optionalChaining: true,
      // The environment supports template literals.
      templateLiteral: true,
    },
  },
};

output.filename

string function (pathData, assetInfo) => string

此选项决定了每个输出 bundle 的名称。这些 bundle 将写入到 output.path 选项指定的目录下。

对于单个入口起点,filename 会是一个静态名称。

webpack.config.js

module.exports = {
  //...
  output: {
    filename: 'bundle.js',
  },
};

然而,当通过多个入口起点(entry point)、代码拆分(code splitting)或各种插件(plugin)创建多个 bundle,应该使用以下一种替换方式,来赋予每个 bundle 一个唯一的名称……

使用入口名称:

webpack.config.js

module.exports = {
  //...
  output: {
    filename: '[name].bundle.js',
  },
};

使用内部 chunk id

webpack.config.js

module.exports = {
  //...
  output: {
    filename: '[id].bundle.js',
  },
};

使用由生成的内容产生的 hash:

webpack.config.js

module.exports = {
  //...
  output: {
    filename: '[contenthash].bundle.js',
  },
};

结合多个替换组合使用:

webpack.config.js

module.exports = {
  //...
  output: {
    filename: '[name].[contenthash].bundle.js',
  },
};

使用函数返回 filename:

webpack.config.js

module.exports = {
  //...
  output: {
    filename: (pathData) => {
      return pathData.chunk.name === 'main' ? '[name].js' : '[name]/[name].js';
    },
  },
};

请确保已阅读过 指南 - 缓存 的详细信息。这里涉及更多步骤,不仅仅是设置此选项。

注意此选项被称为文件名,但是你还是可以使用像 'js/[name]/bundle.js' 这样的文件夹结构。

注意,此选项不会影响那些「按需加载 chunk」的输出文件。它只影响最初加载的输出文件。对于按需加载的 chunk 文件,请使用 output.chunkFilename 选项来控制输出。通过 loader 创建的文件也不受影响。在这种情况下,你必须尝试 loader 特定的可用选项。

Template strings

可以使用以下替换模板字符串(通过 webpack 内部的TemplatedPathPlugin):

可在编译层面进行替换的内容:

模板描述
[fullhash]compilation 完整的 hash 值
[hash]同上,但已弃用

可在 chunk 层面进行替换的内容:

模板描述
[id]此 chunk 的 ID
[name]如果设置,则为此 chunk 的名称,否则使用 chunk 的 ID
[chunkhash]此 chunk 的 hash 值,包含该 chunk 的所有元素
[contenthash]此 chunk 的 hash 值,只包括该内容类型的元素(受 optimization.realContentHash 影响)

可在模块层面替换的内容:

模板描述
[id]模块的 ID
[moduleid]同上,但已弃用
[hash]模块的 Hash 值
[modulehash]同上,但已弃用
[contenthash]模块内容的 Hash 值

可在文件层面替换的内容:

模板描述
[file]filename 和路径,不含 query 或 fragment
[query]带前缀 ? 的 query
[fragment]带前缀 # 的 fragment
[base]只有 filename(包含扩展名),不含 path
[filebase]同上,但已弃用
[path]只有 path,不含 filename
[name]只有 filename,不含扩展名或 path
[ext]带前缀 . 的扩展名(对 output.filename 不可用)

可在 URL 层面替换的内容:

模块描述
[url]URL

[hash][contenthash] 或者 [chunkhash] 的长度可以使用 [hash:16](默认为 20)来指定。或者,通过指定output.hashDigestLength 在全局配置长度。

当你要在实际文件名中使用占位符时,webpack 会过滤出需要替换的占位符。例如,输出一个文件 [name].js, 你必须通过在括号之间添加反斜杠来转义[name]占位符。 因此,[\name\] 生成 [name] 而不是 name

例如:[\id\] 生成 [id] 而不是 id

如果将这个选项设为一个函数,函数将返回一个包含上面表格中含有替换信息数据的对象。 替换也会被应用到返回的字符串中。 传递的对象将具有如下类型(取决于上下文的属性):

type PathData = {
  hash: string;
  hashWithLength: (number) => string;
  chunk: Chunk | ChunkPathData;
  module: Module | ModulePathData;
  contentHashType: string;
  contentHash: string;
  contentHashWithLength: (number) => string;
  filename: string;
  url: string;
  runtime: string | SortableSet<string>;
  chunkGraph: ChunkGraph;
};
type ChunkPathData = {
  id: string | number;
  name: string;
  hash: string;
  hashWithLength: (number) => string;
  contentHash: Record<string, string>;
  contentHashWithLength: Record<string, (number) => string>;
};
type ModulePathData = {
  id: string | number;
  hash: string;
  hashWithLength: (number) => string;
};

output.globalObject

string = 'self'

当输出为 library 时,尤其是当 libraryTarget'umd'时,此选项将决定使用哪个全局对象来挂载 library。为了使 UMD 构建在浏览器和 Node.js 上均可用,应将 output.globalObject 选项设置为 'this'。对于类似 web 的目标,默认为 self

入口点的返回值将会使用 output.library.name 赋值给全局对象。依赖于 target 配置项,全局对象将会发生对应的改变,例如:self, global 或者 globalThis

示例:

webpack.config.js

module.exports = {
  // ...
  output: {
    library: 'myLib',
    libraryTarget: 'umd',
    filename: 'myLib.js',
    globalObject: 'this',
  },
};

output.hashDigest

string = 'hex'

在生成 hash 时使用的编码方式。支持 Node.js hash.digest 的所有编码。对文件名使用 'base64',可能会出现问题,因为 base64 字母表中具有 / 这个字符(character)。同样的,'latin1' 规定可以含有任何字符(character)。

output.hashDigestLength

number = 20

散列摘要的前缀长度。

output.hashFunction

string = 'md4' function

散列算法。支持 Node.JS crypto.createHash 的所有功能。从 4.0.0-alpha2 开始,hashFunction 现在可以是一个返回自定义 hash 的构造函数。出于性能原因,你可以提供一个不加密的哈希函数(non-crypto hash function)。

module.exports = {
  //...
  output: {
    hashFunction: require('metrohash').MetroHash64,
  },
};

确保 hash 函数有可访问的 updatedigest 方法。

output.hashSalt

一个可选的加盐值,通过 Node.JS hash.update 来更新哈希。

output.hotUpdateChunkFilename

string = '[id].[fullhash].hot-update.js'

自定义热更新 chunk 的文件名。可选的值的详细信息,请查看 output.filename 选项。

其中值唯一的占位符是 [id][fullhash],其默认为:

webpack.config.js

module.exports = {
  //...
  output: {
    hotUpdateChunkFilename: '[id].[fullhash].hot-update.js',
  },
};

output.hotUpdateGlobal

string

只在 target 设置为 'web' 时使用,用于加载热更新(hot update)的 JSONP 函数。

JSONP 函数用于异步加载(async load)热更新(hot-update) chunk。

欲了解详情,请查阅 output.chunkLoadingGlobal

output.hotUpdateMainFilename

string = '[runtime].[fullhash].hot-update.json' function

自定义热更新的主文件名(main filename)。[fullhash][runtime] 均可作为占位符。

output.iife

boolean = true

告诉 webpack 添加 IIFE 外层包裹生成的代码.

module.exports = {
  //...
  output: {
    iife: true,
  },
};

output.importFunctionName

string = 'import'

内部 import() 函数的名称. 可用于 polyfilling, 例如 通过 dynamic-import-polyfill.

webpack.config.js

module.exports = {
  //...
  output: {
    importFunctionName: '__import__',
  },
};

output.library

输出一个库,为你的入口做导出。

  • 类型:string | string[] | object

一起来看一个简单的示例。

webpack.config.js

module.exports = {
  // …
  entry: './src/index.js',
  output: {
    library: 'MyLibrary',
  },
};

假设你在 src/index.js 的入口中导出了如下函数:

export function hello(name) {
  console.log(`hello ${name}`);
}

此时,变量 MyLibrary 将与你的入口文件所导出的文件进行绑定,下面是如何使用 webpack 构建的库的实现:

<script src="https://example.org/path/to/my-library.js"></script>
<script>
  MyLibrary.hello('webpack');
</script>

在上面的例子中,我们为 entry 设置了一个入口文件,然而 webpack 可以接受 多个入口,例如一个 array 或者一个 object

  1. 如果你将 entry 设置为一个 array,那么只有数组中的最后一个会被暴露。

    module.exports = {
      // …
      entry: ['./src/a.js', './src/b.js'], // 只有在 b.js 中导出的内容才会被暴露
      output: {
        library: 'MyLibrary',
      },
    };
  2. 如果你将 entry 设置为一个 object,所以入口都可以通过 libraryarray 语法暴露:

    module.exports = {
      // …
      entry: {
        a: './src/a.js',
        b: './src/b.js',
      },
      output: {
        filename: '[name].js',
        library: ['MyLibrary', '[name]'], // name is a placeholder here
      },
    };

    假设 a.jsb.js 导出名为 hello 的函数,这就是如何使用这些库的方法:

    <script src="https://example.org/path/to/a.js"></script>
    <script src="https://example.org/path/to/b.js"></script>
    <script>
      MyLibrary.a.hello('webpack');
      MyLibrary.b.hello('webpack');
    </script>

    查看 示例 获取更多内容。

    请注意,如果你打算在每个入口点配置 library 配置项的话,以上配置将不能按照预期执行。这里是如何 在每个入口点下 做的方法:

    module.exports = {
      // …
      entry: {
        main: {
          import: './src/index.js',
          library: {
            // `output.library` 下的所有配置项可以在这里使用
            name: 'MyLibrary',
            type: 'umd',
            umdNamedDefine: true,
          },
        },
        another: {
          import: './src/another.js',
          library: {
            name: 'AnotherLibrary',
            type: 'commonjs2',
          },
        },
      },
    };

output.library.name

module.exports = {
  // …
  output: {
    library: {
      name: 'MyLibrary',
    },
  },
};

指定库的名称。

  • 类型:

    string | string[] | {amd?: string, commonjs?: string, root?: string | string[]}

output.library.type

配置将库暴露的方式。

  • 类型:string

    类型默认包括 'var''module''assign''assign-properties''this''window''self''global''commonjs''commonjs2''commonjs-module''commonjs-static''amd''amd-require''umd''umd2''jsonp' 以及 'system',除此之外也可以通过插件添加。

对于接下来的示例,我们将会使用 _entry_return_ 表示被入口点返回的值。

Expose a Variable

These options assign the return value of the entry point (e.g. whatever the entry point exported) to the name provided by output.library.name at whatever scope the bundle was included at.

type: 'var'
module.exports = {
  // …
  output: {
    library: {
      name: 'MyLibrary',
      type: 'var',
    },
  },
};

让你的库加载之后,入口起点的返回值 将会被赋值给一个变量:

var MyLibrary = _entry_return_;

// 在加载了 `MyLibrary` 的单独脚本中
MyLibrary.doSomething();
type: 'assign'
module.exports = {
  // …
  output: {
    library: {
      name: 'MyLibrary',
      type: 'assign',
    },
  },
};

这将生成一个隐含的全局变量,它有可能重新分配一个现有的值(请谨慎使用):

MyLibrary = _entry_return_;

请注意,如果 MyLibrary 没有在你的库之前定义,那么它将会被设置在全局作用域。

type: 'assign-properties'
5.16.0+
module.exports = {
  // …
  output: {
    library: {
      name: 'MyLibrary',
      type: 'assign-properties',
    },
  },
};

type: 'assign' 相似但是更安全,因为如果 MyLibrary 已经存在的话,它将被重用:

// 仅在当其不存在是创建 MyLibrary
MyLibrary = typeof MyLibrary === 'undefined' ? {} : MyLibrary;
// 然后复制返回值到 MyLibrary
// 与 Object.assign 行为类似

// 例如,你像下面这样在你的入口导出一个 `hello` 函数
export function hello(name) {
  console.log(`Hello ${name}`);
}

// 在另外一个已经加载 MyLibrary 的脚本中
// 你可以像这样运行 `hello` 函数
MyLibrary.hello('World');

Expose Via Object Assignment

这些配置项分配入口点的返回值(例如:无论入口点导出的什么内容)到一个名为 output.library.name 的对象中。

type: 'this'
module.exports = {
  // …
  output: {
    library: {
      name: 'MyLibrary',
      type: 'this',
    },
  },
};

入口起点的返回值 将会被赋值给 this 对象下的 output.library.name 属性。this 的含义取决于你:

this['MyLibrary'] = _entry_return_;

// 在一个单独的脚本中
this.MyLibrary.doSomething();
MyLibrary.doSomething(); // 如果 `this` 为 window 对象
type: 'window'
module.exports = {
  // …
  output: {
    library: {
      name: 'MyLibrary',
      type: 'window',
    },
  },
};

入口起点的返回值 将会被赋值给 window 对象下的 output.library.name

window['MyLibrary'] = _entry_return_;

window.MyLibrary.doSomething();
type: 'global'
module.exports = {
  // …
  output: {
    library: {
      name: 'MyLibrary',
      type: 'global',
    },
  },
};

入口起点的返回值 将会被复制给全局对象下的 output.library.name。取决于 target 值,全局对象可以分别改变,例如,selfglobal 或者 globalThis

global['MyLibrary'] = _entry_return_;

global.MyLibrary.doSomething();
type: 'commonjs'
module.exports = {
  // …
  output: {
    library: {
      name: 'MyLibrary',
      type: 'commonjs',
    },
  },
};

入口起点的返回值 将使用 output.library.name 赋值给 exports 对象。顾名思义,这是在 CommonJS 环境中使用。

exports['MyLibrary'] = _entry_return_;

require('MyLibrary').doSomething();

Module Definition Systems

这些配置项将生成一个带有完整 header 的 bundle,以确保与各种模块系统兼容。output.library.name 配置项在不同的 output.library.type 中有不同的含义。

type: 'module'
module.exports = {
  // …
  experiments: {
    outputModule: true,
  },
  output: {
    library: {
      // do not specify a `name` here
      type: 'module',
    },
  },
};

输出 ES 模块。

然而该特性仍然是实验性的,并且没有完全支持,所以请确保事先启用 experiments.outputModule。除此之外,你可以在 这里 追踪开发进度。

type: 'commonjs2'
module.exports = {
  // …
  output: {
    library: {
      // note there's no `name` here
      type: 'commonjs2',
    },
  },
};

入口起点的返回值 将会被赋值给 module.exports。顾名思义,这是在 Node.js(CommonJS)环境中使用的:

module.exports = _entry_return_;

require('MyLibrary').doSomething();

如果我们指定 output.library.nametype: commmonjs2,你的入口起点的返回值将会被赋值给 module.exports.[output.library.name]

type: 'commonjs-static'
5.66.0+
module.exports = {
  // …
  output: {
    library: {
      // note there's no `name` here
      type: 'commonjs-static',
    },
  },
};

单个导出将被设置为 module.exports 中的属性。名称中的 "static" 是指输出是静态可分析的,因此具名导出可以通过 Node.js 导入到 ESM 中:

输入:

export function doSomething() {}

输出:

function doSomething() {}

// …

exports.doSomething = __webpack_exports__.doSomething;

Consumption (CommonJS):

const { doSomething } = require('./output.cjs'); // doSomething => [Function: doSomething]

Consumption (ESM):

import { doSomething } from './output.cjs'; // doSomething => [Function: doSomething]
type: 'amd'

可以将你的库暴露为 AMD 模块。

AMD module 要求入口 chunk(例如,第一个通过 <script> 标签加载的脚本)使用特定的属性来定义, 例如 definerequire,这通常由 RequireJS 或任何兼容的 loader(如 almond)提供。否则,直接加载产生的 AMD bundle 将导致一个错误,如 define is not defined

按照下面的配置

module.exports = {
  //...
  output: {
    library: {
      name: 'MyLibrary',
      type: 'amd',
    },
  },
};

生成的输出将被定义为 "MyLibrary",例如:

define('MyLibrary', [], function () {
  return _entry_return_;
});

该 bundle 可以使用 script 标签引入,并且可以被这样引入:

require(['MyLibrary'], function (MyLibrary) {
  // Do something with the library...
});

如果没有定义 output.library.name 的话,会生成以下内容。

define(function () {
  return _entry_return_;
});

如果使用一个 <script> 标签直接加载。它只能通过 RequireJS 兼容的异步模块 loader 通过文件的实际路径工作,所以在这种情况下,如果 output.pathoutput.filename 直接在服务端暴露,那么对于这种特殊设置可能会变得很重要。

type: 'amd-require'
module.exports = {
  //...
  output: {
    library: {
      name: 'MyLibrary',
      type: 'amd-require',
    },
  },
};

它会用一个立即执行的 AMD require(dependencies, factory) 包装器来打包输出。

'amd-require' 类型允许使用 AMD 的依赖,而不需要单独的后续调用。与 'amd' 类型一样,这取决于在加载 webpack 输出的环境中适当的 require 函数 是否可用。

使用该类型的话,不能使用库的名称。

type: 'umd'

这将在所有模块定义下暴露你的库, 允许它与 CommonJS、AMD 和作为全局变量工作。可以查看 UMD Repository 获取更多内容。

在这种情况下,你需要使用 library.name 属性命名你的模块:

module.exports = {
  //...
  output: {
    library: {
      name: 'MyLibrary',
      type: 'umd',
    },
  },
};

最终的输出为:

(function webpackUniversalModuleDefinition(root, factory) {
  if (typeof exports === 'object' && typeof module === 'object')
    module.exports = factory();
  else if (typeof define === 'function' && define.amd) define([], factory);
  else if (typeof exports === 'object') exports['MyLibrary'] = factory();
  else root['MyLibrary'] = factory();
})(global, function () {
  return _entry_return_;
});

请注意,根据 对象赋值部分,省略 library.name 将导致入口起点返回的所有属性直接赋值给根对象。示例:

module.exports = {
  //...
  output: {
    libraryTarget: 'umd',
  },
};

输出将会是:

(function webpackUniversalModuleDefinition(root, factory) {
  if (typeof exports === 'object' && typeof module === 'object')
    module.exports = factory();
  else if (typeof define === 'function' && define.amd) define([], factory);
  else {
    var a = factory();
    for (var i in a) (typeof exports === 'object' ? exports : root)[i] = a[i];
  }
})(global, function () {
  return _entry_return_;
});

你可以为 library.name 指定一个对象,每个目标的名称不同:

module.exports = {
  //...
  output: {
    library: {
      name: {
        root: 'MyLibrary',
        amd: 'my-library',
        commonjs: 'my-common-library',
      },
      type: 'umd',
    },
  },
};
type: 'system'

这将会把你的库暴露为一个 System.register 模块。这个特性最初是在 webpack 4.30.0 中发布。

System 模块要求当 webpack bundle 执行时,全局变量 System 出现在浏览器中。编译的 System.register 格式允许你在没有额外配置的情况下使用 System.import('/bundle.js'),并将你的 webpack bundle 加载到系统模块注册表中。

module.exports = {
  //...
  output: {
    library: {
      type: 'system',
    },
  },
};

输出:

System.register([], function (__WEBPACK_DYNAMIC_EXPORT__, __system_context__) {
  return {
    execute: function () {
      // ...
    },
  };
});

除了设置 output.library.typesystem,还要将 output.library.name 添加到配置中,输出的 bundle 将以库名作为 System.register 的参数:

System.register(
  'MyLibrary',
  [],
  function (__WEBPACK_DYNAMIC_EXPORT__, __system_context__) {
    return {
      execute: function () {
        // ...
      },
    };
  }
);

Other Types

type: 'jsonp'
module.exports = {
  // …
  output: {
    library: {
      name: 'MyLibrary',
      type: 'jsonp',
    },
  },
};

这将把入口起点的返回值包装到 jsonp 包装器中。

MyLibrary(_entry_return_);

你的库的依赖将由 externals 配置定义。

output.library.export

指定哪一个导出应该被暴露为一个库。

  • 类型:string | string[]

默认为 undefined,将会导出整个(命名空间)对象。下面的例子演示了使用 output.library.type: 'var' 配置项产生的作用。

module.exports = {
  output: {
    library: {
      name: 'MyLibrary',
      type: 'var',
      export: 'default',
    },
  },
};

入口起点的默认导出将会被赋值为库名称:

// 如果入口有一个默认导出
var MyLibrary = _entry_return_.default;

你也可以向 output.library.export 传递一个数组,它将被解析为一个要分配给库名的模块的路径:

module.exports = {
  output: {
    library: {
      name: 'MyLibrary',
      type: 'var',
      export: ['default', 'subModule'],
    },
  },
};

这里就是库代码:

var MyLibrary = _entry_return_.default.subModule;

output.library.auxiliaryComment

在 UMD 包装器中添加注释。

  • 类型:string | { amd?: string, commonjs?: string, commonjs2?: string, root?: string }

为每个 umd 类型插入相同的注释,将 auxiliaryComment 设置为 string。

module.exports = {
  // …
  mode: 'development',
  output: {
    library: {
      name: 'MyLibrary',
      type: 'umd',
      auxiliaryComment: 'Test Comment',
    },
  },
};

这将产生以下结果:

(function webpackUniversalModuleDefinition(root, factory) {
  //Test Comment
  if (typeof exports === 'object' && typeof module === 'object')
    module.exports = factory();
  //Test Comment
  else if (typeof define === 'function' && define.amd) define([], factory);
  //Test Comment
  else if (typeof exports === 'object') exports['MyLibrary'] = factory();
  //Test Comment
  else root['MyLibrary'] = factory();
})(self, function () {
  return _entry_return_;
});

对于细粒度控制,可以传递一个对象:

module.exports = {
  // …
  mode: 'development',
  output: {
    library: {
      name: 'MyLibrary',
      type: 'umd',
      auxiliaryComment: {
        root: 'Root Comment',
        commonjs: 'CommonJS Comment',
        commonjs2: 'CommonJS2 Comment',
        amd: 'AMD Comment',
      },
    },
  },
};

output.library.umdNamedDefine

boolean

当使用 output.library.type: "umd" 时,将 output.library.umdNamedDefine 设置为 true 将会把 AMD 模块命名为 UMD 构建。否则使用匿名 define

module.exports = {
  //...
  output: {
    library: {
      name: 'MyLibrary',
      type: 'umd',
      umdNamedDefine: true,
    },
  },
};

AMD module 将会是这样:

define('MyLibrary', [], factory);

output.libraryExport

string [string]

通过配置 libraryTarget 决定暴露哪些模块。默认情况下为 undefined,如果你将 libraryTarget 设置为空字符串,则与默认情况具有相同的行为。例如,如果设置为 '',将导出整个(命名空间)对象。下述 demo 演示了当设置 libraryTarget: 'var' 时的效果。

支持以下配置:

libraryExport: 'default' - 入口的默认导出将分配给 library target:

// if your entry has a default export of `MyDefaultModule`
var MyDefaultModule = _entry_return_.default;

libraryExport: 'MyModule' - 这个 确定的模块 将被分配给 library target:

var MyModule = _entry_return_.MyModule;

libraryExport: ['MyModule', 'MySubModule'] - 数组将被解析为要分配给 library target 的 模块路径

var MySubModule = _entry_return_.MyModule.MySubModule;

使用上述指定的 libraryExport 配置时,library 的结果可以这样使用:

MyDefaultModule.doSomething();
MyModule.doSomething();
MySubModule.doSomething();

output.libraryTarget

string = 'var'

配置如何暴露 library。可以使用下面的选项中的任意一个。注意,此选项与分配给 output.library 的值一同使用。对于下面的所有示例,都假定将 output.library 的值配置为 MyLibrary

暴露为一个变量

这些选项将入口起点的返回值(例如,入口起点的任何导出值),在 bundle 包所引入的位置,赋值给 output.library 提供的变量名。

libraryTarget: 'var' $#libraryTarget-var$

当 library 加载完成,入口起点的返回值将分配给一个变量:

var MyLibrary = _entry_return_;

// 在一个单独的 script...
MyLibrary.doSomething();

libraryTarget: 'assign' $#libraryTarget-assign$

这将产生一个隐含的全局变量,可能会潜在地重新分配到全局中已存在的值(谨慎使用):

MyLibrary = _entry_return_;

注意,如果 MyLibrary 在作用域中未在前面代码进行定义,则你的 library 将被设置在全局作用域内。

libraryTarget: 'assign-properties' $#libraryTarget-assign-properties$

5.16.0+

如果目标对象存在,则将返回值 copy 到目标对象,否则先创建目标对象:

// 如果不存在的话就创建目标对象
MyLibrary = typeof MyLibrary === 'undefined' ? {} : MyLibrary;
// 然后复制返回值到 MyLibrary
// 与 Object.assign 行为类似

// 例如,你在入口导出了一个 `hello` 函数
export function hello(name) {
  console.log(`Hello ${name}`);
}

// 在另一个脚本中运行 MyLibrary
// 你可以像这样运行 `hello` 函数
MyLibrary.hello('World');

通过在对象上赋值暴露

这些选项将入口起点的返回值(例如,入口起点的任何导出值)赋值给一个特定对象的属性(此名称由 output.library 定义)下。

如果 output.library 未赋值为一个非空字符串,则默认行为是,将入口起点返回的所有属性都赋值给一个对象(此对象由 output.libraryTarget 特定),通过如下代码片段:

(function (e, a) {
  for (var i in a) {
    e[i] = a[i];
  }
})(output.libraryTarget, _entry_return_);

libraryTarget: 'this'

入口起点的返回值将分配给 this 的一个属性(此名称由 output.library 定义)下,this 的含义取决于你:

this['MyLibrary'] = _entry_return_;

// 在一个单独的 script...
this.MyLibrary.doSomething();
MyLibrary.doSomething(); // 如果 this 是 window

libraryTarget: 'window'

入口起点的返回值将使用 output.library 中定义的值,分配给 window 对象的这个属性下。

window['MyLibrary'] = _entry_return_;

window.MyLibrary.doSomething();

libraryTarget: 'global'

入口起点的返回值将使用 output.library 中定义的值,分配给 global 对象的这个属性下。

global['MyLibrary'] = _entry_return_;

global.MyLibrary.doSomething();

libraryTarget: 'commonjs'

入口起点的返回值将使用 output.library 中定义的值,分配给 exports 对象。这个名称也意味着,模块用于 CommonJS 环境:

exports['MyLibrary'] = _entry_return_;

require('MyLibrary').doSomething();

模块定义系统

这些选项将使得 bundle 带有更完整的模块头,以确保与各种模块系统的兼容性。根据 output.libraryTarget 选项不同,output.library 选项将具有不同的含义。

libraryTarget: 'module'

输出 ES 模块。请确保事先启用 experiments.outputModule

需要注意的是,该功能还未完全支持,请在此处跟进进度。

libraryTarget: 'commonjs2'

入口起点的返回值将分配给 module.exports 对象。这个名称也意味着模块用于 CommonJS 环境:

module.exports = _entry_return_;

require('MyLibrary').doSomething();

注意,output.library 不能与 output.libraryTarget 一起使用,具体原因请参照此 issue

libraryTarget: 'amd'

将你的 library 暴露为 AMD 模块。

AMD 模块要求入口 chunk(例如使用 <script> 标签加载的第一个脚本)通过特定的属性定义,例如 definerequire,它们通常由 RequireJS 或任何兼容的模块加载器提供(例如 almond)。否则,直接加载生成的 AMD bundle 将导致报错,如 define is not defined

配置如下:

module.exports = {
  //...
  output: {
    library: 'MyLibrary',
    libraryTarget: 'amd',
  },
};

生成的 output 名称将被定义为 "MyLibrary":

define('MyLibrary', [], function () {
  return _entry_return_;
});

可以在 script 标签中,将 bundle 作为一个模块整体引入,并且可以像这样调用 bundle:

require(['MyLibrary'], function (MyLibrary) {
  // Do something with the library...
});

如果 output.library 未定义,将会生成以下内容。

define([], function () {
  return _entry_return_;
});

如果直接加载 <script> 标签,此 bundle 无法按预期运行,或者根本无法正常运行(在 almond loader 中)。只能通过文件的实际路径,在 RequireJS 兼容的异步模块加载器中运行,因此在这种情况下,如果这些设置直接暴露在服务器上,那么 output.pathoutput.filename 对于这个特定的设置可能变得很重要。

libraryTarget: 'amd-require'

这将使用立即执行的 AMD require(dependencies, factory) 包装器包装您的输出。

'amd-require' 目标(target)允许使用 AMD 依赖项,而无需单独的后续调用。与 'amd' 目标(target)一样, 这取决于在加载 webpack 输出的环境中适当可用的 require function

对于此 target,库名称将被忽略。

libraryTarget: 'umd'

将你的 library 暴露为所有的模块定义下都可运行的方式。它将在 CommonJS, AMD 环境下运行,或将模块导出到 global 下的变量。了解更多请查看 UMD 仓库

在这个例子中,你需要 library 属性来命名你的模块:

module.exports = {
  //...
  output: {
    library: 'MyLibrary',
    libraryTarget: 'umd',
  },
};

最终的输出结果为:

(function webpackUniversalModuleDefinition(root, factory) {
  if (typeof exports === 'object' && typeof module === 'object')
    module.exports = factory();
  else if (typeof define === 'function' && define.amd) define([], factory);
  else if (typeof exports === 'object') exports['MyLibrary'] = factory();
  else root['MyLibrary'] = factory();
})(typeof self !== 'undefined' ? self : this, function () {
  return _entry_return_;
});

注意,省略 library 会导致将入口起点返回的所有属性,直接赋值给 root 对象,就像对象分配章节。例如:

module.exports = {
  //...
  output: {
    libraryTarget: 'umd',
  },
};

输出结果如下:

(function webpackUniversalModuleDefinition(root, factory) {
  if (typeof exports === 'object' && typeof module === 'object')
    module.exports = factory();
  else if (typeof define === 'function' && define.amd) define([], factory);
  else {
    var a = factory();
    for (var i in a) (typeof exports === 'object' ? exports : root)[i] = a[i];
  }
})(typeof self !== 'undefined' ? self : this, function () {
  return _entry_return_;
});

从 webpack 3.1.0 开始,你可以将 library 指定为一个对象,用于给每个 target 起不同的名称:

module.exports = {
  //...
  output: {
    library: {
      root: 'MyLibrary',
      amd: 'my-library',
      commonjs: 'my-common-library',
    },
    libraryTarget: 'umd',
  },
};

libraryTarget: 'system'

这将暴露你的 library 作为一个由 System.register 的模块。此特性首次发布于 webpack 4.30.0

当 webpack bundle 被执行时,系统模块依赖全局的变量 System。编译为 System.register 形式后,你可以使用 System.import('/bundle.js') 而无需额外配置,并会将你的 webpack bundle 包加载到系统模块注册表中。

module.exports = {
  //...
  output: {
    libraryTarget: 'system',
  },
};

输出:

System.register([], function (_export) {
  return {
    setters: [],
    execute: function () {
      // ...
    },
  };
});

除了将 output.libraryTarget 设置为 system 之外,还可将 output.library 添加到配置中,输出 bundle 的 library 名将作为 System.register 的参数:

System.register('my-library', [], function (_export) {
  return {
    setters: [],
    execute: function () {
      // ...
    },
  };
});

你可以通过 __system_context__ 访问 SystemJS context

// 记录当前系统模块的 URL
console.log(__system_context__.meta.url);

// 导入一个系统模块,通过将当前的系统模块的 url 作为 parentUrl
__system_context__.import('./other-file.js').then((m) => {
  console.log(m);
});

其他 Targets

libraryTarget: 'jsonp'

这将把入口起点的返回值,包裹到一个 jsonp 包装容器中

MyLibrary(_entry_return_);

你的 library 的依赖将由 externals 配置定义。

output.module

boolean = false

以模块类型输出 JavaScript 文件。由于此功能还处于实验阶段,默认禁用。

当启用时,webpack 会在内部将 output.iife 设置为 false,将 output.scriptType'module',并将 terserOptions.module 设置为 true

如果你需要使用 webpack 构建一个库以供别人使用,当 output.moduletrue 时,一定要将 output.libraryTarget 设置为 'module'

module.exports = {
  //...
  experiments: {
    outputModule: true,
  },
  output: {
    module: true,
  },
};

output.path

string = path.join(process.cwd(), 'dist')

output 目录对应一个绝对路径

webpack.config.js

const path = require('path');

module.exports = {
  //...
  output: {
    path: path.resolve(__dirname, 'dist/assets'),
  },
};

注意,[fullhash] 在参数中被替换为编译过程(compilation)的 hash。详细信息请查看指南 - 缓存

output.pathinfo

boolean=true string: 'verbose'

告知 webpack 在 bundle 中引入「所包含模块信息」的相关注释。此选项在 development 模式时的默认值为 true,而在 production 模式时的默认值为 false。当值为 'verbose' 时,会显示更多信息,如 export,运行时依赖以及 bailouts。

webpack.config.js

module.exports = {
  //...
  output: {
    pathinfo: true,
  },
};

output.publicPath

  • Type:

    • function

    • string

      targets 设置为 webweb-workeroutput.publicPath 默认为 'auto',查看该指南获取其用例

对于按需加载(on-demand-load)或加载外部资源(external resources)(如图片、文件等)来说,output.publicPath 是很重要的选项。如果指定了一个错误的值,则在加载这些资源时会收到 404 错误。

此选项指定在浏览器中所引用的「此输出目录对应的公开 URL」。相对 URL(relative URL) 会被相对于 HTML 页面(或 <base> 标签)解析。相对于服务的 URL(Server-relative URL),相对于协议的 URL(protocol-relative URL) 或绝对 URL(absolute URL) 也可是可能用到的,或者有时必须用到,例如:当将资源托管到 CDN 时。

该选项的值是以 runtime(运行时) 或 loader(载入时) 所创建的每个 URL 的前缀。因此,在多数情况下,此选项的值都会以 / 结束

规则如下:output.path 中的 URL 以 HTML 页面为基准。

webpack.config.js

const path = require('path');

module.exports = {
  //...
  output: {
    path: path.resolve(__dirname, 'public/assets'),
    publicPath: 'https://cdn.example.com/assets/',
  },
};

对于这个配置:

webpack.config.js

module.exports = {
  //...
  output: {
    publicPath: '/assets/',
    chunkFilename: '[id].chunk.js',
  },
};

对于一个 chunk 请求,看起来像这样 /assets/4.chunk.js

对于一个输出 HTML 的 loader 可能会像这样输出:

<link href="/assets/spinner.gif" />

或者在加载 CSS 的一个图片时:

background-image: url(/assets/spinner.gif);

webpack-dev-server 也会默认从 publicPath 为基准,使用它来决定在哪个目录下启用服务,来访问 webpack 输出的文件。

注意,参数中的 [fullhash] 将会被替换为编译过程(compilation) 的 hash。详细信息请查看指南 - 缓存

示例:

module.exports = {
  //...
  output: {
    // One of the below
    publicPath: 'auto', // It automatically determines the public path from either `import.meta.url`, `document.currentScript`, `<script />` or `self.location`.
    publicPath: 'https://cdn.example.com/assets/', // CDN(总是 HTTPS 协议)
    publicPath: '//cdn.example.com/assets/', // CDN(协议相同)
    publicPath: '/assets/', // 相对于服务(server-relative)
    publicPath: 'assets/', // 相对于 HTML 页面
    publicPath: '../assets/', // 相对于 HTML 页面
    publicPath: '', // 相对于 HTML 页面(目录相同)
  },
};

在编译时(compile time)无法知道输出文件的 publicPath 的情况下,可以留空,然后在入口文件(entry file)处使用自由变量(free variable) __webpack_public_path__,以便在运行时(runtime)进行动态设置。

__webpack_public_path__ = myRuntimePublicPath;

// 应用程序入口的其他部分

有关 __webpack_public_path__ 的更多信息,请查看此讨论

output.scriptType

string: 'module' | 'text/javascript' boolean = false

这个配置项允许使用自定义 script 类型加载异步 chunk,例如 <script type="module" ...>

module.exports = {
  //...
  output: {
    scriptType: 'module',
  },
};

output.sourceMapFilename

string = '[file].map[query]'

仅在 devtool 设置为 'source-map' 时有效,此选项会向硬盘写入一个输出文件。

可以使用 #output-filename 中的 [name], [id], [hash][chunkhash] 替换符号。除此之外,还可以使用 Template strings 在 Filename-level 下替换。

output.sourcePrefix

string = ''

修改输出 bundle 中每行的前缀。

webpack.config.js

module.exports = {
  //...
  output: {
    sourcePrefix: '\t',
  },
};

output.strictModuleErrorHandling

按照 ES Module 规范处理 module 加载时的错误,会有性能损失。

  • 类型:boolean
  • 可用版本:5.25.0+
module.exports = {
  //...
  output: {
    strictModuleErrorHandling: true,
  },
};

output.strictModuleExceptionHandling

boolean = false

如果一个模块是在 require 时抛出异常,告诉 webpack 从模块实例缓存(require.cache)中删除这个模块。

出于性能原因,默认为 false

当设置为 false 时,该模块不会从缓存中删除,这将造成仅在第一次 require 调用时抛出异常(会导致与 node.js 不兼容)。

例如,设想一下 module.js

throw new Error('error');

strictModuleExceptionHandling 设置为 false,只有第一个 require 抛出异常:

// with strictModuleExceptionHandling = false
require('module'); // <- 抛出
require('module'); // <- 不抛出

相反,将 strictModuleExceptionHandling 设置为 true,这个模块所有的 require 都抛出异常:

// with strictModuleExceptionHandling = true
require('module'); // <- 抛出
require('module'); // <- 仍然抛出

output.trustedTypes

boolean = false string object

5.37.0+

控制 Trusted Types 兼容性。启用时,webpack 将检测 Trusted Types 支持,如果支持,则使用 Trusted Types 策略创建它动态加载的脚本 url。当应用程序在 require-trusted-types-for 内容安全策略指令下运行时使用。

默认为 false(不兼容,脚本 URL 为字符串)。

  • 设置为 true 时,webpack 将会使用 output.uniqueName 作为 Trusted Types 策略名称。
  • 设置为非空字符串时,它的值将被用作策略名称。
  • 设置为一个对象时,策略名称取自对象 policyName 属性。

webpack.config.js

module.exports = {
  //...
  output: {
    //...
    trustedTypes: {
      policyName: 'my-application#webpack',
    },
  },
};

output.umdNamedDefine

boolean

当使用 libraryTarget: "umd" 时,设置 output.umdNamedDefinetrue 将命名由 UMD 构建的 AMD 模块。否则将使用一个匿名的 define

module.exports = {
  //...
  output: {
    umdNamedDefine: true,
  },
};

output.uniqueName

string

在全局环境下为防止多个 webpack 运行时 冲突所使用的唯一名称。默认使用 output.library 名称或者上下文中的 package.json 的 包名称(package name), 如果两者都不存在,值为 ''

output.uniqueName 将用于生成唯一全局变量:

webpack.config.js

module.exports = {
  // ...
  output: {
    uniqueName: 'my-package-xyz',
  },
};

output.wasmLoading

boolean = false string

此选项用于设置加载 WebAssembly 模块的方式。默认可使用的方式有 'fetch'(web/WebWorker),'async-node'(Node.js),其他额外方式可由插件提供。

其默认值会根据 target 的变化而变化:

  • 如果 target 设置为 'web''webworker''electron-renderer''node-webkit' 其中之一,其默认值为 'fetch'
  • 如果 target 设置为 'node''async-node''electron-main''electron-preload',其默认值为 'async-node'
module.exports = {
  //...
  output: {
    wasmLoading: 'fetch',
  },
};

output.workerChunkLoading

string: 'require' | 'import-scripts' | 'async-node' | 'import' | 'universal' boolean: false

新选项 workerChunkLoading 用于控制 workder 的 chunk 加载。

webpack.config.js

module.exports = {
  //...
  output: {
    workerChunkLoading: false,
  },
};

26 位贡献者

sokraskipjacktomasAlabesmattceirthfvgsdhurlburtusaMagicDuckfadysamirsadekbyzykmadhavarshneyharshwardhansingheemeliEugeneHlushkog-planesmelukovNeob91anikethsahajamesgeorge007hiroppychenxsansnitin315QC-LanshumanvmrzalyaulJakobJingleheimer