本文最初发布于 Bryan Braun 的个人博客,遵循创作共用许可,由 InfoQ 中文站翻译并分享。
在过去的一年里,我一直在生产环境中运行一个基于 ES 模块的单页 Web 应用程序。
这是一个 JavaScript 应用程序,但它没有使用 Babel、Webpack、Rollup 或任何其他转译或绑定工具。我在开发中编写的文件与在生产环境中提供给最终用户的文件相同。
该应用程序是一个业余项目…一个在线音乐盒歌曲制作工具。它不是一个大型 App,但也不小——目前有大约 60 个 JavaScript 模块,其中包含了组件、工具和第三方库。其代码库看起来很像 ReactJS 项目,但它没有使用 React。它使用普通的 JavaScript,用原生 JavaScript 特性(如模板字符串)替代像 JSX 这样的抽象。这个框架与本文无关,但如果你感兴趣,可以阅读我之前的文章了解更多内容。
当我在 2019 年 8 月发布这款应用时,业界一致认为在生产中使用非绑定 ES6 模块是一个坏主意。可汗学院尝试解绑其主页的 JS,得出的结论是,这样会减慢他们的初始页面加载速度。那是五年前的事了,我还不知道有谁认真考虑过跳过绑定器在生产环境中使用 ES6 模块*。
这太糟糕了,因为现在浏览器对ES模块的支持已经很好了,似乎我们可以通过编写浏览器能够识别的JavaScript来避免很多无意义的东西(至少对于行得通的项目如此)。
我一直在寻找这样的例子,但很难找到,所以我决定尝试一下,看看这到底有多糟糕。以下是我的发现。
符合预期的方面
开发体验。我期望有良好的开发体验,这方面符合预期。不需要安装,没有启动延迟。不需要监视文件、生成源映射或等待重新编译。只需保存文件并刷新即可。
部署。部署非常简单。我所需要做的就是按原样将代码复制到服务器。我托管在 Web 上的 Netlify 可以很轻松地在 git push 时完成这个任务(不过,公平地说,Netlify 甚至可以使最复杂的设置都很容易部署)。
开发/生产对等。每次我发现一个生产缺陷时,我都能够在本地重现它。这不是主要目标,但很方便。
低于预期的方面
依赖项不支持 ES 模块。我经常发现,我想使用的库不支持 ES 模块。它们通常支持 CommonJS,这意味着我不能使用它们。起初,我通过加载使用浏览器全局变量的库版本(通过脚本标签或副作用导入)来解决这个问题。这种方法有效,但感觉并不理想。
最后,我开始使用Snowpack,它可以导入不支持 ES 模块的依赖项,并生成支持 ES 模块的一次性构建。这种方法非常有效,我已经开始在其他项目中使用它了。
环境变量。通常,我会在构建时赋值,但不构建时就不能这样做了。幸运的是,Cory House 有一篇很棒的文章描述了所有的选项。我最后使用了环境嗅探,这感觉有点奇怪,但对我的应用来说终归不是什么大问题。
CSS 的组织。我遵照 BEM 约定使用了传统的 CSS,这很好。不过,我仍然想要分解我的文件,所以我使用了一个main.css
文件和一堆@import
。这样感觉好多了,但后来我遇到了一个阻塞请求,它导致了页面渲染延迟,所以我将@imports
移动到一个内联样式标签中。我不确定我是否喜欢这样,所以我可能会继续改进。
超出预期的方面
缓存失效。我担心我在失效缓存的 CSS 或 JavaScript 时会遇到问题(因为我不能依赖一个绑定器赋予资源的缓存清除文件名)。事实证明,ETags是一个很好的解决方案(特别是我的 Web 主机有一个可靠的实现,既快速又简单)。我听说缓存清除(cache-busting)文件名可以比 Etags 快一点,但在我看来,Netlify 的实现已经相当精炼了。
性能。我所听说的是,ES 模块的性能很糟糕,即使使用 HTTP/2。所以我做好了准备,这很好。我甚至没有做任何优化,除了确保初始 HTML 文件有一些好的默认标记。我觉得它的性能还不错,因为我的应用还没有大到可以碰到瓶颈的程度(这项研究表明,如果你的应用程序没有达到几百个模块,就没问题,这一点似乎已经得到了证实)。这让我意识到,关于什么样的应用才算“太大”,我的直觉错了。在需要一次性加载 300 到 500 个文件之前,你还有很长的路可以走。至少我觉得我的应用不太可能达到这些限制。
我有点担心我的 JavaScript 没有最小化。那不是可以节省大量的字节吗?事实证明,如果你的文件是 gzip 压缩的(或 brotli 压缩的,就像我的情况),那么区别很小。通过重命名变量和删除注释,最小化仍然可以使我的文件更小,但差异比预期的要小。
浏览器支持。由于我没有使用 Babel,我预计会出现很多跨浏览器问题,但这种情况很少**。事实证明,一旦你放弃 IE11 支持,浏览器对现代 JS 特性的支持是非常好的。箭头函数、const/let、模板字符串、ES6 类和fetch
等都有超过 95%的全局支持(包括 IE11)。唯一一个我真正想用而又没有使用的 JS 特性是可选链接操作符,而这个特性在未来一两年可能会得到 95%的支持。Evergreen 浏览器是一个功能强大的东西。
总体评价
这让我感到惊喜。如果这是一个传统的绑定式单页应用程序,那么到现在,我需要处理一两个主要工具的升级。取而代之,我可以把重点放在功能上。原生Web技术万岁!
如果我的模块数量太多以至于性能开始明显下降,我很有信心使用 Snowpack 来解决它。根据其文档:
Snowpack将绑定作为可选的生产优化,这意味着你可以自由跳过绑定的额外复杂性,直到你需要它。你使用绑定器,应该是因为你想这样做,而不是因为你需要这样做。
我喜欢这个延缓复杂性的主意。让人感觉非常灵活。
也许 ES 模块并不适用于每个项目,但它们在我的项目中工作得很好。如果有根本的缺陷,我也还没有发现。
老实说,我现在很难想象用其他方式来构建一个业余项目。
我最近发现了一些其他的 ES 模块实例,包括runpkg.com(源代码)和Phillip Walton的博客(源代码)。如果你知道其他的例子,请通过电子邮件(bbraun7@gmail.com)告诉我。
更准确地说,我曾遇到过一些跨浏览器的问题,但这不是 Babel 能够帮助我解决的问题。当浏览器实现存在Bug或者你的方法存在根本性缺陷时,Babel 的 polyfill 就没有任何帮助了。
原文链接:
ES modules in production: my experience so far
评论