- 原文地址:Misunderstanding ES6 Modules, Upgrading Babel, Tears, and a Solution
- 原文做者:Kent C. Dodds
- 译文出自:掘金翻译计划
- 本文永久连接:github.com/xitu/gold-m…
- 译者:Starrier
- 校对者:SinanJS,caoyi0905
说多了都是泪...javascript
在 2015 年 10 月 29 号,Sebastian McKenzie、James Kyle 以及 Babel 团队的其余成员,发布了一个面向各地前端开发者的大型版本:Babel 6.0.0。太棒了,由于它再也不是一个转译器,而是一个可插拔的 JavaScript 工具平台。做为一个社区,咱们只触及了它能力的表面,我对 JavaScript 工具的将来感到兴奋(谨慎乐观态度)。html
全部这些都说明了,Babel 6.0.0 是一个很是重大的变革版本。一开始可能有点不稳定。所以升级也并不容易,须要学习。这篇文章不必定会讨论如何升级 Babel。我只想讨论我从本身代码中学会的内容 —— 当 Babel 修复了个人严重依赖问题时... 在尝试将 Babel 5 升级到 Babel 6 以前,但愿你能够去阅读如下内容:前端
若是我能够正确理解 ES6 模块规范,对我来讲,升级就不会那么困难了。Babel 5 容许滥用 export 和 import 语句,Babel 6 解决了这个问题。一开始我觉得这多是 Bug。我在 Stack Overflow 和 Logan Smyth 上提问这个问题,反馈的信息告诉我,我从根本上误解了 ES6 模块,并且 Babel 5 滋长了这种误解(编写一个转换器很困难)。android
起初,我不太明白 Logan 的意思,但当我有时间全身心投入个人应用升级时,发生了这些事情:webpack
我疯了么?这是无效的 ES6 么?export default { foo: 'foo', bar: 'bar', }ios
— @kentcdoddsgit
Tyler McGinnis、Josh Manders 和我在这个线程上测试了一下。这可能很难理解,但我意识到问题不是将对象默认导出,而是如何像预期那样能够导入该对象。angularjs
我老是能够导出一个对象做为默认值,而后从该对象中经过解构的方式得到我所须要的部分(字段),以下所示:
// foo.js
const foo = {baz: 42, bar: false}
export default foo
// bar.js
import {baz} from './foo'
复制代码
由于 Babel 5 的转换是导出默认语句,因此它容许咱们这样作。然而,根据规范,这在技术上是不正确的,这也是为何 Babel 6(正确地)删除了该功能,由于它的能力其实是在破坏我在工做中应用程序的 200 多个模块。
当我回顾 Nicolás Bevacqua 的博客时,我终于明白了它的工做原理。
固然,也要感谢 @nzgb 在 ES6 上的 350 个使人惊讶的要点,由于它很是清晰ponyfoo.com/articles/es…@rauschma。
当我读到 Axel Rauschmayer 的博客时,我发现为何我一直在作内容无效。
我想感谢 @rauschma 用 ES6 模块将我从早期中年危机中拯救出来。我可能对这事太专一了。。。
基本思想是:ES6 模块应该是静态可分析的(运行时不能更改该导出/导入),所以不能是动态的。在上述示例中,我能够在运行时更改 foo 的对象属性,而后个人 import 语句就能够导入该动态属性,就像这样:
// foo.js
const foo = {}
export default foo
somethingAsync().then(result => foo[result.key] = result.value)
// bar.js
import {foobar} from './foo'
复制代码
咱们将假设 result.key 是 ‘foobar’。在 CommonJS 中这很好,由于 require 语句发生在运行时(在模块被须要的时候):
// foo.js
const foo = {}
module.exports = foo
somethingAsync().then(result => foo[result.key] = result.value)
// bar.js
const {foobar} = require('./foo')
复制代码
但是,由于 ES6 规范规定导入和导出必须是静态可分析的,因此你不可能在 ES6 中完成这种动态行为。
这也是 Babel 作出改变的缘由。这样作是不太可能的,但这也是件好事。
用文字来描述这个问题确实比较困难,因此我但愿一些代码的示例与对比会有指导意义。
我遇到的问题是,我将 ES6 exports 与 CommonsJS require 组合在一块儿。我会这样作:
// add.js
export default (x, y) => x + y
// bar.js
const three = require('./add')(1, 2)
复制代码
Babel 改变后,我有三个选择:
选择 1:默认 require
// add.js
export default (x, y) => x + y
// bar.js
const three = require('./add').default(1, 2)
复制代码
选择 2:100% 的 ES6 模块
// add.js
export default (x, y) => x + y
// bar.js
import add from './add'
const three = add(1, 2)
复制代码
选择 3:100% 的 CommonJS
// add.js
module.exports = (x, y) => x + y
// bar.js
const three = require('./add')(1, 2)
复制代码
几小时后我开始运行构建并经过了测试。不一样的场景,我有两种不一样的方法:
我将导出更改成 CommonJS(module.exports),而不是 ES6(export default),这样我就能够像一直作的那样继续 require。
我写了一个复杂的正则表达式来查找并替换(应该使用一个 codemod)那些将其余 require 语句从 require(‘./thing’) 转向 require(‘./thing’).default** 的改变。
它工做的很完美,最大的挑战就是理解 ES6 模块规范是如何工做的,Babel 如何将其转换到 CommonJS,从而实现交互操做。一旦我把问题弄清楚了,遵循这一规则来升级个人代码就变成了超简单的工做。
尽可能避免混合 ES6 模块和 CommonsJS。我我的而言,会尽可能使用 ES6。首先,我将它们混合在一块儿的缘由之一是我能够执行单行的 require,并当即使用所需的模块(好比 require(‘./add’)(1, 2))。但这真的不是一个足够大的好处(就我我的看来)。
若是你以为必须将它们组合起来,能够考虑使用如下 Babel 插件/预置之一:
全部这些真正的教训是,咱们应该明白事情是如何运做的。若是我理解 ES6 模块规范其实是如何运做的,我就能够节省大量时间。
你可能会受益于这个 Egghead.io 课程,我演示了如何从 Babel 5 升级到 Babel 6:
另外,记住,没有任何人是完美的,咱们都在这里学习 :-) Twitter 上见
更多示例:
在对 Babel 进行更改以前,有一个像这样的 require 语句:
import add from './add'
const three = add(1, 2)
复制代码
但在 Babel 发生变化以后,Require 语句如今变得就像这样:
import * as add from './add'
const three = add.default(1, 2)
复制代码
我想,致使这个问题的缘由是,add 变量再也不是默认导出,而是一个拥有全部命名导出以及 default export 的对象(在默认键下)。
命名导出:
值得注意的是,你可使用命名导出,个人建议是在工具模块中那么作。这容许你在 import 语句(警告,尽管因为前面的静态分析缘由,他看起来并非真正的析构)中执行相似于析构的语法。所以,你能够那么作:
// math.js
const add = (x, y) => x + y
const subtract = (x, y) => x - y
const multiply = (x, y) => x * y
export {add, subtract, multiply}
// foo.js
import {subtract, multiply} from './math'
复制代码
在 tree shaking 的状况下,这使人兴奋,还很棒。
我的而言,我一般建议对于组件(像 React 组件或 Angular 服务)使用 default export(你知道本身要导入的待定内容,单文件,单组件 😀)。但对于工具模块,一般有各类能够独立使用的纯函数。这是命名导出的一个很好的用例。
若是你以为这颇有趣,那么你应该会喜欢查看我博客的其余内容而且订阅个人最新内容 💌(信息在发送到电子邮件 2 周后,会发布到个人博客)。
TestingJavaScript.com 能够学习更好、更高效的方法来测试任何 JavaScript 程序。
感谢 Tyler McG
若是发现译文存在错误或其余须要改进的地方,欢迎到 掘金翻译计划 对译文进行修改并 PR,也可得到相应奖励积分。文章开头的 本文永久连接 即为本文在 GitHub 上的 MarkDown 连接。
掘金翻译计划 是一个翻译优质互联网技术文章的社区,文章来源为 掘金 上的英文分享文章。内容覆盖 Android、iOS、前端、后端、区块链、产品、设计、人工智能等领域,想要查看更多优质译文请持续关注 掘金翻译计划、官方微博、知乎专栏。