使用 webpack 输出一个 ES 模块

Posted

技术标签:

【中文标题】使用 webpack 输出一个 ES 模块【英文标题】:Output an ES module using webpack 【发布时间】:2017-05-08 10:28:48 【问题描述】:

使用 Rollup,我只需将 format 选项设置为 'es' 即可输出 ES 模块。我怎样才能对 webpack 做同样的事情?如果现在不可以,webpack 有计划添加吗?

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

libraryTarget: "commonjs-module" - 使用module.exports 对象公开它(output.library 被忽略),__esModule 被定义(它在互操作模式下作为 ES2015 模块线程化)

但是,我不太清楚。它是否与libraryTarget: "commonjs2" 相同,唯一的区别是定义了__esModule?什么是“互操作模式”?

【问题讨论】:

仅供参考,webpack 上的相关票证:github.com/webpack/webpack/issues/2933 再过 3 个月,就是 2022 年了,门票仍然开放,从 2016 年开始也是如此!可能必须转到 汇总 【参考方案1】:

首先我想说明commonJScommonJS2之间的区别

CommonJS 不支持 module.exports = function() 的使用,node.js 和许多其他 commonJS 实现都使用它。

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有以下含义

为了鼓励使用CommonJSES6模块,当导出一个default export而没有其他exports时,将设置module.exports除了exports["default"]如下例所示

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

所以基本上这意味着 commonJS-module 可以通过 暴露 使用 module.exports 来使用 interlopingES2015 封装在 commonJS 中的模块

有关interloping 的更多信息可以在此blogpost 和*** link 中找到。

基本思想是在ES6 运行时导出和导入属性不能更改,但在commonJS 这很好因为需要更改发生在运行时 因此它具有 ES2015commonJS插入

更新

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

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

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

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

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

如文档中所述

__esModule 被定义(它被线程化为 ES2015 Module in interop 模式)

test cases中提到的用法

export * from "./a";
export default "default-value";
export var b = "b";

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

【讨论】:

你能回答我的主要问题吗:是否可以使用 webpack 输出 ES 模块?另外,commonjs-module 如何处理命名导出? 如果你不明白我在更新中写了什么,那么答案是肯定的——通过添加库选项,并如上所述命名导出测试用例。 commonjs-modulecommonjs2 是 apparently equivalent at this point,前者可能已被弃用。【参考方案2】:

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

另一方面,如果您正在发布库,您可以同时提供 CommonJS (umd) 和 ES 目标,这要感谢"module" key in package. json。实际上你不需要 webpack 来发布 ES 目标,你需要做的就是在每个文件上运行 babel 以使其达到 es2015 标准,例如,如果你使用 react 你可以只使用“react”预设运行 babel。如果您的源代码已经是 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 /"),如果你的库很重且模块化,特别有用!

【讨论】:

我发现直接导入 src/index.js 的问题是,如果原始源正在导入图像、sass 等,它就不起作用,因为 webpack.config 中的加载程序通常会排除 node_modules对于这些文件类型。 node_modules 从 babel-loader 中排除,因为库已经转译,我们不需要 babel。如果源需要导入图像或其他资产配置,则应使用适当的加载器进行扩展,例如文件加载器或 sass-loader 并在需要时包含 node_modules 的设置。参考图像或 css 的最佳做法是将所有静态内容复制到您的应用程序静态文件夹中。 这样做的缺点是,如果你正在构建一个组件库或类似类型的模块来导入其他资产,那么每个使用你的模块的应用程序都必须配置所有适当的加载器。它给应用程序的 Webpack 配置增加了很多麻烦,而且它很脆弱,因为该库可能只与特定版本的插件/加载器兼容。如果在构建库时仍将非 JS 模块转换为可导入的等价物,那么使用 webpack 而不捆绑/转换为 CJS 会很好【参考方案3】:

如果你不介意在你的包中添加一个额外的文件,你可以使用这个解决方法作为一个解决方案,允许你将 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 作为 libraryTarget 方法将包输出到 /dist/bundle.js。根据我的配置,这会使包默认导出在导入后立即以window.__MODULE_DEFAULT_EXPORT__ 的形式提供。 我们创建一个自定义“加载器”:./dist/index.js,它导入 /dist/bundle.js,选择 window.__MODULE_DEFAULT_EXPORT__,从 window 对象中删除它(清理),将其分配给局部变量,然后导出它作为 ES6 导出。 我们将package.json 配置为指向我们的“加载程序”:./dist/index.js 我们现在可以执行常规的import MyDefaultExportName from './dist/index.js'

注意:这个解决方案——因为它在这里公开——远非完美,并且有一些缺点和限制。虽然还有改进的空间:)

【讨论】:

这非常适合。令人难以置信的是,webpack 仍然没有打包为模块的选项。 如果在node中运行,估计会报错window is not defined @xianshenglu for Node 你可以用global替换window【参考方案4】:

什么是最好的非 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 博客文章中描述的步骤来使用可以允许输出 ES 模块的插件。

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

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,它正在跟踪此功能。

【讨论】:

考虑到 Webpack 目前不支持这个功能,所有的解决方案都将是 hacky。与您的提案唯一不同的是,hacky 方法是由插件采用的,而不是由您直接采用的。 @colxi 当然,插件的真正解决方案是hacky,但是我们不必处理任何hacky。这对初学者来说特别容易,因为它是一个非常小的修改。

以上是关于使用 webpack 输出一个 ES 模块的主要内容,如果未能解决你的问题,请参考以下文章

ES6模块和commonjs模块的区别

ES6模块和commonjs模块的区别

webpack打包原理

webpack4搭建详解——SME-Router

前端模块化工具--webpack学习心得

将 ES6 模块与 WebPack 一起使用,为啥仍然需要 require