大家好,我是Kason。如果你想从JS中找一个API作为整个前端的缩影,那么ESM规范中的import就完美了。在这篇文章中,我们将从这个API开始,来谈谈Web的开发。Web的本质是开放在所有JS运行时中,Web是最开放的(其次是deno)。这可以从导入语法的“模块说明符”中看出。//模块说明符是'./a.js'从'./a.js'导入xxx。ES规范中只明确了“模块说明符是一个字符串字面量”,对于“如何解析模块说明符”没有任何限制,所以“解析模块说明符”的任务就交给了宿主环境。在web的HTML规范中,“模块说明符”可以是以下形式:绝对路径的url,例如:importconfettifrom'https://cdn.skypack.dev/canvas-confetti';以/、./、../开头的相对路径,如:importxxxfrom'./a.js'定义了模块名到url的映射,然后以模块名的形式导入,如:重新引入模块:importmomentfrom"moment";PS:这个方法叫做import-maps[1],目前浏览器兼容性不高:可以发现这三个方法对“模块说明符”的来源很开放。反观Node.js运行时,如果以包名的形式引入模块,如:importmomentfrom"moment";其背后是一组指向node_modules并最终指向npm库的机制。npm是一家私有公司,被github收购,github被微软收购。所以,如果哪天国内不能直接安装npm包也不要惊讶,毕竟背后是一家私人公司。相比之下,网络的开放性让他不会面临如此尴尬的境地。迭代兼容性Web的发展史是一部“三年新、三年旧、三年修修补补”的兼容性史。许多API兼容性问题可以通过polyfill解决。因此,库作者在面临模块规范的兼容性问题时,自然也想为用户做到最好。然而,这种努力也使代码的行为更加混乱。例如:可以在ESM模块中引入CJS模块。对于以下CJS模块://a.jscjsmoduleexports.hello=(){console.log('hello')}在同级ESM模块中导入,通过解构或对象方法使用hello:importutils来自'./a.js';const{hello}=utils;//或consthello=utils.hello;为什么不能以“命名导入”的形式直接使用hello://你不能从'.import{hello}。/a.js'这是因为ESM规范的导入声明是静态的,而CJS规范的导出是动态的,所以ESM模块导入CJS模块时,编译时无法知道有哪些导出。这是非常规范的,但似乎有点违反直觉。比如React只提供了CJS规范的包,那么ESM模块中正确的导入方式是:importReactfrom'react';const{useState}=反应;而大家显然在日常开发中使用较多的是下面这个方法:import{useState}from'react';之所以这个import不会报错,是因为库作者(如vite、babel)在编译的时候默默的进行了转换。为了开发者的方便而违背规范其实是一件非常糟糕的事情(类似的事情还有npm和yarn的shadowdependencies)。但是开发者喜欢看到的API就是好的API,整个Web的发展都是一种修修补补的螺旋式发展。Bundle将存在很长时间。在vite诞生并带来极致的开发速度之后,社区中出现了“bundlevsbundleless”的讨论。既然bundleless可以加快开发环境的速度,那么是不是也可以给生产环境带来同样的优势呢?或者更极端一点,未来前端会不会逐渐放弃打包工具?从ESM规范的角度来看,答案是否定的。现阶段bundleless无法解决的紧迫需求有两个:treeshakingESM模块过多,导致请求量大。因此,打包工具在未来还会长期存在。总结一下,在我的技术组里,经常看到新人在前端感叹:“我不知道该学什么”。原因是目前的前端开发主要使用“各种集成了最佳实践的大型框架”(比如Nuxt、Next.js、UmiJS...)。为了降低入门门槛,这些完全封装的框架隐藏了很多技术细节。如果你也有这种困惑,建议你从ESM规范开始学习。他就像一张地图,可以连接前端的方方面面。参考文献[1]import-maps:https://github.com/WICG/import-maps
