UMD 被淘汰了吗?不考虑的 UMD 的库如何在纯 UMD 前端项目中运行?

我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第7篇文章,点击查看活动详情

如果你对模块化不太了解,建议阅读我这篇文章:彻底搞清楚 ECMAScript 的模块化

本文不会过多介绍 JS 的模块化规范。

背景:Hackathon

最近团队在参加一场国际 Hackathon 活动,虽然名义上我也是项目的成员,但我不是主要参加者,主要负责人是一位架构师大佬。

经过讨论后,我需要帮助大佬完成一些他不太擅长的前端工作。

这个项目模板是从一个开源 VR 项目中拉下来的。

我打开后,发现目录结构如下:

没有使用 node.js 和 npm,所以我称它是纯 umd 项目。

可能研究 VR 的大佬不怎么喜欢研究前端吧,或者对前端工程化完全不感兴趣。

在 index.html 中一堆包直接导入就好了。

这种用法一时间仿佛让我回到了十年前,但我不打算深究这个问题。

团队经常参加 Hackathon 活动来为我们的开源项目增加知名度,但是 Hackathon 项目一般都不会维护,开发完拿过去演示一下,基本上就再也不会碰了。所以我也没必要再去折腾它的前端工程化。

我现在要做的事,就是在 2 天之内为这个项目增加几个功能。

它的作用就是用 three 画个地板空间,空间的一端有两个屏幕。一个是老师的摄像头,一个是老师的屏幕。

学生会蹲在地板上听老师讲课。

和普通的直播项目不一样的是,你可以戴着 VR 参加直播。

虽然项目的前端看上去有些落后,可是技术含量一点儿都不低。

一切似乎很顺利。

问题出现了!

可是当我需要为项目引入两个包时,却碰到了问题。

  • nanoid:id 生成器。
  • presencejs:p2p 通信。

由于我必须生成唯一的 nanoid,所以要使用 nanoid 这个库。

可是 nanoid 的官网并没有介绍通过 umd 的方式使用,于是我查了下 issues。

果然有人碰到了和我一样的问题,但是 nanoid 的作者回复,说 umd 已老,我们不考虑支持 umd 了!

至于 presencejs,本来就是我维护的,v1 版本确实也不支持 umd。

umd 和 esm 混用!

那该怎么办呢?

两种选择:

  1. 折腾前端工程化,把项目用 npm 管理起来,通过打包工具构建项目。
  2. 通过 esm 的方式导入第三方库。

很明显后者成本更低,但它也有个问题,就是不支持老浏览器。

我立马询问了架构师这个项目的运行环境,是在最新版本的 Chrome 中运行。

那这个问题也不用考虑了。

具体用法就是在 esm 中把 umd 需要的 API 挂载到 window 对象上,伪代码:

<script type="module">
  import { nanoid } from "https://unpkg.com/[email protected]/nanoid.js";
  window.nanoid = nanoid;
</script>
复制代码

不过在 esm 和 umd 混合使用的过程中又碰到了些问题。

执行顺序问题

esm 是异步加载、异步执行。umd 是立即加载、立即执行。

如果 umd 依赖 esm 怎么办?

比如我加载一个 umd 文件,立马要通过 nanoid 生成一个 id。但是此时 nanoid 还没有加载出来。

umd 有一个 async 和 defer 属性,可以让 js 文件以异步或者延迟的方式加载。

但是仍然没有用,因为 esm 和 umd 都是异步执行,没办法保证哪个先执行哪个后执行。

这个问题也有两个办法可以解决:

  1. 把 umd 改造成 esm。
  2. 在 esm 中等第三方库加载结束再调用 umd。

因为项目中有大量的 umd 文件,第一种方案风险明显更高。

那么选择第二种方案,伪代码如下:

定义 import UMD 函数。

function importUMD(url) {
  return fetch(url)
    .then(response => response.text())
    .then(txt => eval(txt))
}
复制代码
<script type="module">
  import { nanoid } from "https://unpkg.com/[email protected]/nanoid.js";
  window.nanoid = nanoid;

	importUMD('./my.js')
</script>
复制代码

依赖问题

解决了 nanoid 的问题之后,我又发现了另一个问题。

presencejs 又依赖了一些 peer 级别的库。

在 esm 文件的开头会有一堆导入:

import { interval, Subject } from 'rxjs';
import { distinctUntilChanged, filter, map, takeWhile } from 'rxjs/operators';
import { WebSocketSubject } from 'rxjs/webSocket';
复制代码

解决方案就是导入 rxjs 的 umd CDN。

<script src="https://unpkg.com/rxjs@^7/dist/bundles/rxjs.umd.min.js"></script>
复制代码

然后把 presencejs 的依赖项改为如下这种写法:

const { interval, Subject, operators, webSocket } = window.rxjs
const { distinctUntilChanged, filter, map, takeWhile } = operators
const { WebSocketSubject } = webSocket
复制代码

所幸 presencejs 依赖的第三方库只有 rxjs 一个,如果依赖了很多 peer 级别的第三方库的话,那需要导入的内容就多了去了。

JavaScript 模块规范的发展

我发现最近这几年,越来越多的库都在渐渐不支持 UMD 规范。原因大致相同,大家都认为这种用法已经过时了,应该被淘汰,大家应该拥抱官方的 ESM 规范。

这种想法是对的,但是没有向后兼容。

一段 JavaScript 代码的运行环境有非常多种,比如浏览器脚本、浏览器模块、Nodejs、deno、bun 等。理想状态是我们编写一份代码,就可以在全平台运行。当然依赖某些平台特性的 API,比如浏览器的 DOM API、Nodejs 的 process 等除外。

但现实情况根本不现实,必须通过编译工具对代码进行编译,输出多份文件,分别对应不同的环境。

最初 UMD 就是为了解决上述问题而产生的规范,一份 UMD 的编译结果,可以在所有平台上运行,这也是被大家认可和接受的。但如今它似乎已经是一个即将被时代淘汰的过度品,社区越来越拥抱 ESM 了。

猜你喜欢

转载自juejin.im/post/7143114340386734116