服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - 编程技术 - 混乱是进步的阶梯 — ESM规范的崛起【上】

混乱是进步的阶梯 — ESM规范的崛起【上】

2021-04-23 23:19魔术师卡颂卡颂 编程技术

在无数开源大神的努力下,很多前端开发的痛点(比如「静态类型检查」、「浏览器兼容性」)早已有了事实上的标准解决方案(比如TS、babel)。

混乱是进步的阶梯 — ESM规范的崛起【上】

 前言

 

前端发展到如今,社区生态已经非常丰富。

在无数开源大神的努力下,很多前端开发的痛点(比如「静态类型检查」、「浏览器兼容性」)早已有了事实上的标准解决方案(比如TS、babel)。

然而,在这繁荣之下,有一个日常开发不易感知的问题:

  • 模块化规范的混乱

你可曾遇到过莫名其妙的bug,在多方搜资源,反复验证,耗费数个小时终于发现:

原来是某个包导出的是CJS,而项目使用ESM导致。

比如这个例子:记一次打包压缩报错[1]

如果你觉得这是个很容易发现的问题,再考虑结合上node_modules的层层依赖呢?

这个问题,揭开了模块化规范间斗争与博弈的冰山一角。

作为现代前端工程化的基石,模块化规范有太多值得深究的内容。

我会花几篇文章来讲解模块化规范。本文是第一篇,会围绕模块化规范的演进展开。

正文

 

如果问十年前的前端最头疼的是什么?一定是浏览器兼容性。

随着babel等编译工具出现,兼容性逐渐被工程化方案解决(ES6+编译为ES5)。

不仅是「兼容性」问题,DSL(如JSX、VUE的模版语法)、代码压缩、代码静态检查(TS)等日常开发的刚需都能在工程化方案中找到解决办法。

如果将当今繁荣的前端工程化生态比喻为一座大厦,那大厦的地基一定是「模块化规范」。

现代JS代码都是基于「模块化规范」组织起来,让我们从下往上来看看这座大厦:

混乱是进步的阶梯 — ESM规范的崛起【上】

规范的实现依赖于宿主环境,比如浏览器环境实现了EcmaScript Module(后文简称ESM)规范。

Node v12之前支持CommonJS(后文简称CJS)规范,12之后同时支持CJS与ESM。

在「宿主环境」之上,是基于模块化规范实现的「工具集」,比如webpack、vite、VScode生态。

再往上,基于「工具集」提供的API,可以实现各种工程化工具。比如:

  • webpack loader
  • VScode plugin
  • babel plugin

再往上,就是开发者自己编写的业务代码。

开发者只需要在工具集中配置好工具,就能为业务代码提供服务。比如:

在VScode(工具集)中配置eslint(工具),就能在开发时获得相应提示

在webpack(工具集)中配置babel loader(工具),就能在开发时使用ES6+语法

可见,理想状态下,在开发者视角是不需要关注底层的「模块化规范」实现的。

规范之争

 

然而,事物是动态发展的,模块化规范也不是一蹴而就的,让我们回到09年。

美国程序员「Ryan Dahl」创造了node.js项目,将JS用于服务端开发。

混乱是进步的阶梯 — ESM规范的崛起【上】

node.js使用CJS[2]标准作为模块化规范。

混乱是进步的阶梯 — ESM规范的崛起【上】

有了服务端模块规范(CJS),很自然的,JS开发者们想为客户端(主要是浏览器)提供一种模块化规范。

然而CJS是为服务端设计的。

在服务端,IO操作通常能迅速完成,所以CJS规范定义的:

  • 模块加载 --> 模块解析 --> 模块执行

这个流程是作为一个整体同步执行的。

然而在浏览器环境,「模块加载」(即数据请求)通常很耗时。有人曾作出一个形象的比喻:

如果一个CPU周期花费1秒完成,那么文件的网络请求需要花费4年。

混乱是进步的阶梯 — ESM规范的崛起【上】

显然浏览器端需要一种「支持异步」的模块化规范。

AMD(Asynchronous Module Definition 异步模块定义)规范,就是这样需求背景下的产物。

然而这些社区提出的规范终究只是为了解决一时的需求,随着历史的发展,新的模块化规范不断涌入、消亡。

直到ESM规范被提出。

ESM规范是ES标准的模块化规范,他的早期讨论可以追溯到2009年。

你可以在这里看到ESM规范的历史es-module-history[3]

ESM将模块规范分为三个阶段:

  • 模块加载 --> 模块实例化 --> 模块执行

其中「模块加载」由宿主环境提供的loader完成(比如在浏览器环境,loader的行为由HTML规范[4]定义)。

「模块实例化」与「模块执行」由ESM规范定义执行流程。

区别于CJS规范的同步执行,ESM规范将流程拆解为3个独立阶段。

「模块加载」同步、异步与否由宿主环境决定。

支持不同宿主环境,抹平多端差异、能力比其他规范都强大(后文会介绍)、再加上血统纯正(ES官方提出),

使得ESM规范一统前端「看似」指日可待。

然而,此时社区已经有大量基于CJS规范产出的开源包、组件,他们无法立刻切换到ESM规范。

所以,JS生态的现状是:会处于、并将长期处于CJS规范的库与ESM规范的库共存的状态。

但是最终,ESM规范一定会成为主导,毕竟他的优点太多(同样,后文会介绍)。

规范割裂带来的机会

 

混乱是进步的阶梯 — ESM规范的崛起【上】

当前模块化规范的混乱,对开源大佬们来说,就是机会。

为了让开发者将更多精力放在业务,而不是模块规范的适配上。

很多开源「工具集」都试图抹平模块化差异,比如:

  • 在babel中使用babel-plugin-transform-commonjs可以将CJS规范的代码转换为ESM规范
  • 为了一刀切解决当前ESM、CJS、浏览器script标签导入这3种规范互相不兼容的情况,提出了兼容三者格式的UMD(Universal Module Definition)规范

一些「工具集」利用模块化规范的不同与其他竞品形成差异化竞争,比如:

  • browserify这款打包工具的卖点是:使用CJS规范打包,使一份代码同时在Node环境与浏览器环境(打包后)执行。
混乱是进步的阶梯 — ESM规范的崛起【上】

其中,在浏览器环境中,Node的一些核心库(如events、stream、path...)会被打包成浏览器支持的版本。

  • Vite在DEV环境使用ESM规范构建模块间的依赖关系。

依赖于大部分现代浏览器原生支持ESM规范,省去了打包的过程,使其编译速度大大提升。

  • rollup原生对ESM提供更多支持。

严格支持ESM规范,并提供更好的静态分析,使rollup一度提供性能更优异的treeShaking能力。

成为更多库打包工具的首选。

与webpack这样的的大而全方案形成差异竞争。

规范割裂带来的痛

 

可以看到,由于底层宿主环境对模块化规范支持的割裂,需要上层工具集来抹平模块规范的差异。

设想一个同时使用了webpack、babel、TS的项目。

这3个工具集都对多种模块规范有兼容处理。比如:

单独使用babel时,对于如下代码:

  1. import a from 'lib'
  2. console.log(a); 

会被babel编译为:

  1. "use strict"
  2.  
  3. var _lib = _interopRequireDefault(require("lib")); 
  4.  
  5. function _interopRequireDefault(obj) { 
  6.   return obj && obj.__esModule ? obj : { default: obj }; 
  7.  
  8. console.log(_lib.default); 

ESM的「默认导出」会被编译为包含default属性的对象。

你可以打开babel playground[5]试试

当多个「工具集」在同一个项目中,为了各自目的做着同一件事(抹平模块化规范差异),

一旦工具链中某个插件配置有一丝丝不符合预期,或者引入了一个不符合预期的包,那么艰难的debug就此开始了......

混乱是进步的阶梯 — ESM规范的崛起【上】

曙光即使当前有诸多不便,历史的进程是无法阻止的,那些被历史巨轮甩下并碾碎的模块化规范,会逐渐消失在开发者的视野中。

混乱是进步的阶梯 — ESM规范的崛起【上】

而赢家注定会通吃。

为什么ESM注定会成为最大赢家?他有什么无法比拟的优势?我们会在下篇文章揭晓。

参考资料

 

[1]记一次打包压缩报错:

https://cloud.tencent.com/developer/article/1650627

[2]CJS:

http://wiki.commonjs.org/wiki/Modules/1.1

[3]es-module-history:

https://gist.github.com/jkrems/769a8cd8806f7f57903b641c74b5f08a

[4]HTML规范:

https://html.spec.whatwg.org/#fetch-a-module-script-tree

[5]babel playground:

https://babeljs.io/repl

原文地址:https://mp.weixin.qq.com/s/eR_wx_WF18v-OKV2EkUiGA

延伸 · 阅读

精彩推荐
  • 编程技术2021年值得关注的React PDF 库

    2021年值得关注的React PDF 库

    今天,许多网络应用程序为其用户提供内置的PDF浏览选项。然而,选择一个并不容易,因为它们的功能远远超过显示PDF。在这篇文章中,我将评估5个React的...

    TianTianUp5232021-06-21
  • 编程技术让开发效率倍增的 VS Code 插件

    让开发效率倍增的 VS Code 插件

    今天来分享一些提升开发效率的实用 VS Code 插件!Better Comments 扩展可以帮助我们在代码中创建更人性化的注释,有不同形式和颜色的注释供我们选择。 ...

    前端充电宝7132022-04-21
  • 编程技术真正聪明的程序员,总有办法不加班

    真正聪明的程序员,总有办法不加班

    工作效率提升了,就可以少加班了,聪明的程序员,总会有一堆可以提升编码效率的工具?当一种工具满足不了工作需求,就去探索新的,今天纬小创就给...

    今日头条12482021-03-04
  • 编程技术Delphi - Indy idMessage和idSMTP实现邮件的发送

    Delphi - Indy idMessage和idSMTP实现邮件的发送

    这篇文章主要介绍了Delphi - Indy idMessage和idSMTP实现邮件的发送,本文通过实例代码给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下...

    JJ_JeremyWu6592020-09-22
  • 编程技术简单、好懂的Svelte实现原理

    简单、好懂的Svelte实现原理

    本文会围绕一张流程图和两个Demo讲解,正确的食用方式是用电脑打开本文,跟着流程图、Demo一边看、一边敲、一边学...

    魔术师卡颂4822021-11-10
  • 编程技术用户态 Tcpdump 如何实现抓到内核网络包的?

    用户态 Tcpdump 如何实现抓到内核网络包的?

    在网络包的发送和接收过程中,绝大部分的工作都是在内核态完成的。那么问题来了,我们常用的运行在用户态的程序 tcpdump 是那如何实现抓到内核态的包...

    开发内功修炼11612021-09-08
  • 编程技术AIOps,SRE工程师手中的利器

    AIOps,SRE工程师手中的利器

    AIOps开始成为一种极为重要的站点可靠性工程工具。它能够高效吸纳观察数据、参与数据以及来自第三方工具的数据,判断系统运行状态并保证其处于最佳...

    至顶网5972021-03-08
  • 编程技术从Context源码实现谈React性能优化

    从Context源码实现谈React性能优化

    这篇文章主要介绍Context的实现原理,源码层面掌握React组件的render时机,从而写出高性能的React组件,源码层面了解shouldComponentUpdate、React.memo、PureComponen...

    魔术师卡颂5312020-12-20