截图自https://sokra.github.io/interop-test/
今天因为 esbuild 的一个 bug ,需要升级 esbuild 的版本,升级完后惊讶的发现 Babel 居然挂了,我只是升级了个小版本(0.14.1 -> 0.14.5),理应不该出现如此大的变动,后来追踪了下 esbuild 的 changelog ,发现了 esbuild 在 0.14.4 引入了一个巨大的 breaking change (严谨如 esbuild 也没严格遵循语义化版本,可见业务如果强依赖语义化版本是个多不靠谱的事情)。
esbuild 0.14.4 引入的 breaking ,正是 js 社区臭名昭著的一个问题,即 ESM 和 CJS 的 Interop(互操作性)问题,esbuild 的 changelog 写了相当长的篇幅总结了这个问题( esbuild 的 changelog 是业界良心,总能学到新东西)。下面内容均来自 esbuild changelog 的翻译。
在开发 ECMAScript 模块导入/导出语法时,CommonJS 模块格式(用于 Node.js )已经被广泛使用。正因为如此,为了解决 ESM 和 CJS 的交互性问题,名为 default 的导出名称被赋予了特殊的语法。你可以不写 import { default as foo } from 'bar'
,而只写 import foo from 'bar'
。
这个想法的初衷是,当 ECMAScript 模块(又称 ES 模块)被引入时,你可以使用新的导入语法来导入现有的 CommonJS 模块来实现兼容性。由于 CommonJS 模块的导出是动态的,而 ES 模块的导出是静态的,一般来说,在模块实例化的时候不可能确定一个 CommonJS 模块的导出名称,因为此时代码还没有被执行。所以 module.exports 的值只能作为默认的导出(因为无法确定其他 name ,只能约定一个 default 作为整体的导出 name ),特殊的默认导入语法让你很容易访问 module.exports(即import foo from 'bar'
等价于 const foo = require('bar')
。
到这里一切设计都很合乎情理,似乎这个设计也无懈可击,然而这里同时埋下了祸根,即这个交互性问题其实只需要支持个
import foo from 'bar'
这个 syntax sugar (语法糖)即可满足,然而却同时错误的支持了export default 'xxx'
这个语法,为后续的交互性问题埋下了祸根。
然而(一切不幸的开始),ES 模块语法需要一段时间才能被 JavaScript 运行系统原生支持,而人们仍然希望在这期间开始使用 ES 模块语法。Babel 通过将 ES 编译到 CJS 让你现在就可以使用 ES 模块进行编码。你可以将每个 ES 模块文件转化为一个行为相同的 CommonJS 模块文件。
然而,这种转换有一个问题:如何准确的将import语法降级到 commonjs,上述设计意味着export default 0
和import foo from 'bar'
在转换为 CommonJS 时行为将不再一致。代码export default 0
变成了module.exports.default = 0
,代码import foo from 'bar'
变成了const foo = require('bar')
(这里是为了对齐上述的交互性行为)。这导致代码在降级到 cjs 前和降级到 cjs 后的行为是不一致的了。
降级前:
export default 0
export default 0
降级后:
module.exports.default = 0
const foo = require('bar') // foo结果为{default:0}
console.log('foo',foo);
降级前后运行结果不一致,这是非常显然的bug。
为了解决这个问题,Babel 在将 ES 模块转换为 CommonJS 模块时,通过将属性 __esModule 设置为true 标记这个模块是一个编译后的 ES 模块。然后,在导入 default 导出时,它可以知道使用 module.exports.default 的值,而不是 module.exports 的值,以确保 CommonJS 模块的行为与原始 ES 模块的行为正确匹配。这一修正在整个生态系统中被广泛采用,并进入了其他工具,如 TypeScript ,甚至 esbuild 。babel 修复后的结果如下:
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.default = 0;
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
var bar_1 = __importDefault(require("bar"));
consol.log('foo', bar_1.default); // 结果为0
// 计算过程如下
* require("bar")=> {default: 0,__esModule: true}
* bar_1 => __importDefault => ({default:0,_esModule}).__esModule ? ({default: __esModule}) : {default: {default:0,__esModule:true}} => {default: 0,__esModule:true}
* bar_1.default => ({default:0,__esModule:true}).default => 0
至此,前端社区的代码实际上可以认为跑在了一个虚拟的 Babel |Webpack 的 runtime上,这个 babel runtime 通过将ES编译为CJS帮我们解决了ESM和CJS的交互性问题了,如果没有后续Node的背刺,实际上已经是趋于稳定了。
然而(另一个不幸的事情,让事情雪上加霜),当 Node.js 最终发布他们的 ES 模块实现时,他们采用了原来的实现,即 default 导出总是等于 module.exports ,这打破了与现有的 ES 模块生态系统的兼容性(即和 Babel runtime 的兼容性),这些模块已经被 Babel 交叉编译成 CommonJS 模块。现在你必须根据你的代码是需要在 Node 环境中还是在 Babel 环境中运行,来添加或删除一个额外的 .default
属性,这就导致了更严重性的互操作性问题。此外,像 esbuild 这样的 JavaScript 工具现在需要猜测你是想要 Node 风格还是 Babel 风格的默认导入。工具没有办法肯定地知道某个文件所期望的是哪一种,如果你的工具猜错了,你的代码就会被破坏。
至此我们总结下,目前 ESM 和 CJS 的交互性问题,由三件不幸的事情组成,
import xxx from 'bar'
本来应该是个处理交互性的语法糖,但是并没有和其他的模块导入 && 导出进行区分(就不应该支持export default
), Babel 错误的实现了 ESM 到 CJS 的降级方案,虽然后来修复了但是还是造成了一定问题,node 选择了与 Babel runtime (前端社区)不兼容的方案,导致市面上存在两套 interop 的逻辑,并且彼此不兼容,我们可以明显的感知到node社区和前端社区存在很大的割裂性。
这个版本改变了 esbuild 围绕默认导出和 __esModule 标记的启发式方法,以试图改善与 Webpack 和 Node 的兼容性(大部分的生态都是基于他俩),其行为变化如下:
如果导入语句被用来加载一个CommonJS文件,并且
module.exports 中存在 default 属性,那么 esbuild 将把默认导出设置为 module.exports.default(像 Babel)。否则默认出口被设置为 module.exports(像Node)。
module.exports 是一个对象,
module.exports.__esModule 是 truthy ,并且
如果一个 require 调用被用来加载一个 ES 模块文件,返回的模块命名空间对象的 __esModule 属性被设置为 true 。这就像 ES 模块通过 Babel 兼容的转换被转换为 CommonJS 一样。
当编写纯 ESM 代码时,esModule 标记可能会不一致地出现在模块命名空间对象上(即import * as
)。具体来说,如果一个模块命名空间对象被物化(materialized)了,那么 esModule 标记就会出现,但如果它被优化掉了,那么 __esModule 标记就会消失。
不允许创建一个名为 esModule 的 ES 模块导出。这避免了生成的代码与上述行为冲突导致代码 break ,同时也避免了 esModule 的重复定义问题。
请注意,这意味着默认出口在以前没有被定义的情况下现在可能是未定义的。这与 Webpack 的行为相匹配,所以希望它能更加兼容。
还要注意,这意味着导入行为现在取决于文件的扩展名和 package.json 的内容。这也符合 Webpack 的行为,希望能提高兼容性。
本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/uIkeydRZnqS-tjoDD1T0Tg
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。