使用 webpack 输出一个 ES 模块

Output an ES module using webpack

使用 Rollup,我可以通过简单地将 format 选项设置为 'es' 来输出一个 ES 模块。我怎样才能用 webpack 做同样的事情?如果现在不能的话,webpack有没有加入的计划?

我在 documentation for output.libraryTarget 中发现的唯一提到 ES 模块的是:

libraryTarget: "commonjs-module" - Expose it using the module.exports object (output.library is ignored), __esModule is defined (it's threaded as ES2015 Module in interop mode)

但是,我不太清楚。它是否与 libraryTarget: "commonjs2" 相同,唯一的区别是定义了 __esModule?什么是 "interop mode"?

首先我想说明commonJScommonJS2

之间的区别

CommonJS 不支持 node.js 和许多其他 commonJS 实现所使用的 module.exports = function() {}

Webpack2 采用了捆绑库代码的概念,为了广泛使用它并使其在不同环境中工作兼容,我们使用 --libraryTarget选项

现在这里的部分将回答您的两个问题

webpack2 中可能支持的库选项是

  • libraryTarget: "umd", // enum
  • libraryTarget: "umd-module", // ES2015 module wrapped in UMD
  • libraryTarget: "commonjs-module", // ES2015 module wrapped in CommonJS
  • libraryTarget: "commonjs2", // exported with module.exports
  • libraryTarget: "commonjs", // exported as properties to exports
  • libraryTarget: "amd", // defined with AMD defined method
  • libraryTarget: "this", // property set on this
  • libraryTarget: "var", // variable defined in root scope

Interlop有如下含义

In order to encourage the use of CommonJS and ES6 modules, when exporting a default export with no other exports module.exports will be set in addition to exports["default"] as shown in the following example

export default test;
exports["default"] = test;
module.exports = exports["default"];

所以基本上这意味着 commonJS-module 可以通过 使用 interloping 和 [=86] 将其暴露为 module.exports =]ES2015 模块包裹在 commonJS

有关 interloping 的更多信息,请参阅此 blogpost and the Whosebug

基本思想在ES6运行时导出和导入属性无法更改但在commonJS这工作正常 因为 需要在运行时发生变化 因此它有 ES2015interlopedcommonJS.

更新

Webpack 2 提供创建可以捆绑和包含的库的选项.

如果您希望您的模块在不同的环境中使用那么您可以通过添加库选项将其捆绑为一个库并将其输出到您的特定环境docs.

中提到的程序

关于如何使用 commonjs-module 的另一个简单示例

这里要注意的重要一点是 babelexports.__esModule = true 添加到每个 es6 module 并在导入时调用 _interopRequire 检查 属性。

__esModule = true 只需要在 库导出 上设置。需要在入口模块exports上设置。 内部模块不需要__esModule,它只是一个 babel hack。

如文档中所述

__esModule is defined (it's threaded as ES2015 Module in interop mode)

test cases

中提到的用法
export * from "./a";
export default "default-value";
export var b = "b";

import d from "library";
import { a, b } from "library";

Webpack2还没有相关的libraryTarget,不输出ES6 bundle。另一方面,如果您将库捆绑在 CommonJS 中,捆绑器将无法 运行 Tree Shaking,无法消除未使用的模块。这是因为 ES 模块仍在开发中,所以没有人将 ES 包发送到浏览器,而 webpack 主要用于创建支持浏览器的包。

另一方面,如果你要发布库,你可以同时提供 CommonJS (umd) 和 ES 目标,感谢 "module" key in package. json。实际上你不需要 webpack 来发布 ES 目标,你需要做的就是在每个文件上 运行 babel 使其达到 es2015 标准,例如,如果你正在使用 react 你可以 运行 babel with只是 "react" 预设。如果您的源代码已经是 ES 2015,没有额外的功能,您可以将模块直接指向您的 src/index.js:

//package.json
...
  "module": "src/index.js"
  "main": "dist/your/library/bundle.js
...

我发现在我的 index.js 中使用 babel 来处理 export v from 'mod' 指令很方便,所以我有 1 个模块文件导出我所有的模块。这是通过 babel-plugin-transform-export-extensions 实现的(也包含在 stage-1 预设中)。

我从 react-bootstrap 库中发现了这种方法,你可以在他们的 github 中看到脚本(他们是 webpack1)。我在我的 react-sigma repo 中稍微改进了他们的脚本,请随意复制以下文件,它们将满足您的需要:

config/babel.config.js
scripts/buildBabel.js
scripts/es/build.js
scripts/build.js // this is command line controller, if you need just ES you don't need it

另请参阅 lib 目标(scripts/lib/build.js 和 .babelrc),我提供了 lib 转译模块,因此即使没有 ES 显式指定 require("react-sigma/lib/Sigma/"),库用户也可以仅包含他们需要的模块,如果您的库很重且模块化,则特别有用!

如果您不介意向您的包中添加额外的文件,您可以使用此解决方法作为一种解决方案,允许您 distribute/import Webpack 捆绑包作为 ES6 模块:

配置

webpack.config.js

output: {
    path: path.resolve('./dist'),
    filename: 'bundle.js',
    library: '__MODULE_DEFAULT_EXPORT__',
    libraryTarget: 'window',
    libraryExport: 'default'
  },

./dist/index.js(我们需要创建这个文件)

import './bundle.js'
const __MODULE_DEFAULT_EXPORT__ = window.__MODULE_DEFAULT_EXPORT__
delete window.__MODULE_DEFAULT_EXPORT__
export default __MODULE_DEFAULT_EXPORT__

package.json(如果你要分发你的模块很重要)

  "main": "dist/index.js",

工作原理:

  • Webpack 使用 window as libraryTarget 方法将包输出到 /dist/bundle.js。根据我的配置,这使得包 default export 在导入后立即可用 window.__MODULE_DEFAULT_EXPORT__
  • 我们创建一个自定义 "loader" : ./dist/index.js,它导入 /dist/bundle.js ,选择 window.__MODULE_DEFAULT_EXPORT__ ,从 window 对象中删除它(清理), 将其分配给局部变量,并将其导出为 ES6 导出。
  • 我们将 package.json 配置为指向我们的 "loader" :./dist/index.js
  • 我们现在可以执行常规 import MyDefaultExportName from './dist/index.js'

Note : This solution -as it is exposed here- is far from being perfect, and has some downsides and limitations. Although there is space for improvements :)

最好的 non-hacky 解决方案是什么?

显然,this feature is coming in Webpack 5

更新:Webpack 5 已发布,但 still doesn't support this behavior.

更新 2:请参阅 this comment,这让它看起来像是即将推出。

更新 3:The newest version of Webpack should support this use case !(虽然看起来实现仍然有点问题。

如果您需要使用旧版本的 Webpack,您可以使用 this 博客 post 中描述的步骤来使用允许输出 ES 模块的插件。

在使用上面的博客post时(post的示例代码有点偏离)我们首先运行

npm i -D @purtuga/esm-webpack-plugin

然后我们可以将 webpack.config.js 设置为以下内容:

const EsmWebpackPlugin = require("@purtuga/esm-webpack-plugin");
module.exports = {
    mode: "development",
    entry: "index.js",
    output: {
        filename: "consoleUtils.js",
        library: "LIB",
        libraryTarget: "var"
    },
    //...
    plugins: [
        new EsmWebpackPlugin()
    ]
}

然后我们就可以正常使用这个文件了。

import func from "./bundle.js";

func();

另请参阅跟踪此功能的 this issue