前端构建工具的未来

346 阅读10分钟

希望将来可以不用构建就可以直接使用

前端构建工具对现代前端开发者的工作流程至关重要,原因有很多,包括改善开发者和用户体验。从开发者的角度来看,前端工具为我们提供了:编写模块的能力、用于本地开发的开发服务器、在开发模式下缩短反馈循环的热模块替换(HMR)、使用 polyfills 针对传统浏览器的能力、处理除 JavaScript 以外的一系列文件类型,等等。

因此,用户可以享受能力更强、功能更丰富的应用程序,通过代码分割、缓存、预取和其他资源优化技术保持性能 -- 有些应用程序甚至能够离线工作。

今天,前端工具为我们提供了如此多的东西,以至于很难想象曾经有一段时间甚至根本不需要它。回忆一下,可以帮助我们理解我们是如何走到今天的。

过去

在前端应用程序变得像今天这样复杂之前,所有的 JavaScript 都是用来为其他简单的 HTML 文档添加基本的交互性 -- 类似于 Adobe 的 Flash 的使用方式。

当时没有复杂的 "JavaScript-heavy" 应用,因此不需要任何工具来更好地编写和发布 JavaScript,但这种情况不会一直存在。

随着时间的推移,我们开始在网络上创造更多的用户体验,我们从更多的静态网页转向服务于用户特定数据的高度动态网络应用。这些应用程序比传统的应用程序需要更多的 JavaScript,而使用 JavaScript 的局限性也变得更加明显。

在浏览器中加载 JavaScript 有两种主要方式:一种是用脚本标签引用 JavaScript 文件,另一种是在 HTML 中直接将 JavaScript 写入脚本标签。

 <script src="my-javascript-file.js"></script>

 <script>
     var a = 1;
     var b = 2;
     var result = a + b;
 </script>

当你有大量的 JavaScript 需要加载时,这种对加载 JavaScript 的限制就会成为一个瓶颈。同时加载许多 JavaScript 文件有浏览器的限制,而拥有一个巨大的 JavaScript 文件也有可维护性的问题(比如文件大小、范围问题、命名空间冲突等等)。

我们想出了一些解决方案,如立即调用函数表达式(IIFEs),以帮助解决封装和一些范围问题,之后,我们获得了在许多不同文件中编写 JavaScript 的能力。然后,我们需要一种方法,将这些文件合并成一个文件,在浏览器中提供服务。

现在

由于能够用 IIFEs 将我们的 JavaScript 分割成不同的文件,我们似乎只需要一种方法将这些文件串联起来,并将一个文件传送给浏览器。在这种需求下,Gulp、Grunt、Brocolli 等工具应运而生。然而,我们很快意识到,我们的想法可能有点太简单了。

随着我们的应用程序变得更加复杂,缺乏无用代码消除、针对小更改的完全重建以及其他性能问题等问题让我们意识到我们需要的不仅仅是串联。这催生了更现代的打包工具,如 Webpack、Parcel 等。

随着前端领域的进步步伐没有放缓,我们已经开始观察到现代构建工具的差距和问题。

一些主要的局限性包括:

  • 对其中一些现有的 bundlers 进行复杂的设置和配置;
  • 随着应用程序的迭代,构建时间也随之增加;
  • 开发模式下的性能欠佳。

在 JavaScript 生态系统中,事物变化的速度往往让人感到疲惫,但好处是社区能迅速发现问题并着手解决潜在的解决方案。我们的目标是提高前端工具的性能,新一代的构建工具正在开发中。

未来

由于当今主流构建工具的限制,出现了许多重新构想前端构建工具的尝试,而且目前市面上有相当多的新型构建工具。

仔细观察会发现,这些新工具似乎采取了两种主要方法来解决这个问题(并不一定是相互排斥的):平台的改变和范式的转变,这两者都受到了 Web 开发生态系统中的新进展的推动。

平台的改变

传统上,前端构建工具一直是使用 JavaScript,近年来也使用了 TypeScript 进行构建。这是有道理的,因为 JavaScript 是 Web 的语言,使用相同的语言编写 Web 构建工具可以更容易地让更多人参与其中,并在这些工具周围建立社区。然而,这种方法也存在固有的问题。

作为一种高级语言,JavaScript 无法达到原生的性能水平。这意味着构建在该平台之上的工具对其性能有限制。因此,为了突破这一限制,新的构建工具正在构建在较低级别、本质上性能更高的语言(如 Rust)上。

像 Rust 和 Go 这样的语言已经成为编写下一代构建工具的热门选择,它们非常强调性能。特别是 Rust,不仅因其性能而受欢迎,还因其令人印象深刻的开发者体验而受欢迎在 Stack Overflow 开发者调查中连续六年被评为 "最受喜爱的" 编程语言。

在谈到用 Rust 构建 Rome(一个用于构建现代 JavaScript 应用程序的一体化工具链)的决定时,Jamie Kyle 说:

“许多其他人已经在我们面前传达了 Rust 的性能、内存和安全优势 —— 让我们说,每个曾经说过 Rust 很好的人都是正确的。然而,我们最担心的是我们自己的生产力。[...] 然而,经过一些原型设计后,我们很快意识到我们实际上可能在 Rust 中更有效率”
@Jamie Kyle in Rome Will Be Written In Rust

SWC 项目处于将 Rust 用于前端构建工具这一想法的最前沿。它现在正在为 Next.js 的新编译器、Deno、Parcel 等项目提供支持,其性能比其他现有构建工具高出多个数量级。

像 SWC 这样的项目证明,随着底层平台的改变,构建工具的性能可以得到显着提高。

范式的转变

如今,典型的前端构建流程如下:您可以在许多不同的文件中编写 JavaScript 模块,然后运行一个命令,构建工具会收集这些模块,将它们捆绑成一个单独的模块,将其转换为浏览器可理解的格式,并在浏览器中提供该文件。

为了提高开发模式下的性能,许多需要较长时间才能完成的优化被省略,而是在打包生产应用程序时运行,这样可以确保启动开发服务器、在开发模式下运行应用程序并提高生产效率所需的时间尽可能短。

虽然构建过程仍然需要相当长的时间,但随着项目规模的增长,构建时间(甚至在开发过程中)只会变得越来越长。如果我们能够在仍然可以像往常一样编写模块的同时跳过打包过程,并让浏览器理解如何处理它们,那将是非常棒的,对吧?一些新的构建工具在使用这种方式,并称之为 Unbundled Development

Unbundled Development 很棒。它解决了现有构建工具的一个主要问题:即使是微小的代码更改,它们通常需要重新构建整个应用程序的部分,而随着应用程序的增长,构建时间也越来越长,这失去了快速反馈 —— 这是愉快的开发体验所必需的。

有人可能想知道,如果 Unbundled Development 如此出色,为什么今天不成为常态呢?Unbundled Development 才开始受到关注有两个主要原因:现代浏览器对于最新功能的兼容性和处理节点模块导入的方式。

1. 现代浏览器的兼容性

Unbundled Development 由 ES 模块 (ESM) 提供支持,它为 JavaScript 带来了标准化的模块系统 —— 在包括浏览器在内的多个运行时中得到原生支持。有了这个新功能,我们可以将我们的脚本标记标记为模块,因此可以使用我们都熟悉的 import 和关键字;export

 <script type="module" src="main.js"></script>

 <script type="module">
   /** JavaScript module code goes here */
 </script>

ES 模块已经存在了很长一段时间。尽管如此,我们只能开始将它用于 Unbundled Development 之类的事情,这主要是因为它的标准化在网络生态系统中的所有参与者中花费了很长时间。

在她关于 ES 模块的文章中,关于 Mozilla Hacks,Lin Clark 说:

“ES 模块为 JavaScript 带来了一个官方的、标准化的模块系统。不过,这需要一段时间才能实现 —— 将近 10 年的标准化工作。”
@Lin Clark 在 ES Modules: A Cartoon Deep-Dive

浏览器支持与否的问题长期以来一直困扰着前端开发。这就是为什么我们有供应商为我们的 CSS 加上前缀,有时是 polyfill 的原因,为什么我们花时间确保我们的 Web 应用程序的跨平台支持,以及为什么有时需要相当长的时间才能利用最新和最好的 Web 功能在我们的日常工作中。

尝试使用 Safari 访问 StackBlitz 项目,您将看到以下屏幕,表明非基于 Chromium 的浏览器不支持 WebContainers。

图片

各个浏览器提供商实现新功能的时效并不相同,而且不同供应商实现某些功能的方式通常也有所不同。然而,在 Interop 2022 等举措,未来看起来一片光明。

2. 处理节点模块导入

我们今天编写的大多数(如果不是全部)前端应用程序都依赖于 NPM 的外部库。对于典型的 React 应用程序,我们会在组件文件的顶部导入 React,如下所示:

 import React from 'react'

  /** The rest of your component code */

尝试直接在浏览器中加载它,就像我们在 Unbundled Development 中需要做的那样,会导致两个问题。首先,浏览器不知道如何解析要查找的路径 react,其次,react 库作为 Common JS (CJS) 模块发布 —— 如果不进行一些预处理,它无法在浏览器中本地运行。

后者才是更大的问题,因为将我们的 Node 模块导入替换为指定文件的相对路径是可能的,甚至是微不足道的。然而,由于大多数 NPM 包是以适用于 Node.js 而不是浏览器的模块格式编写的,这就要求我们特殊处理 NPM 依赖项,以促进 Unbundled Development。

特别是 Snowpack,它通过将应用程序的依赖项处理成单独的 Javascript 文件,然后可以直接在浏览器中使用。关于 Snowpack 如何做到这一点的更多信息可以在这里找到。

随着 ES 模块现在成为大多数现代浏览器的主流,以及 NPM 依赖项的巧妙解决方法,像 Vite 和 Snowpack 这样的构建工具可以提供 Unbundled 开发的选项,除了超快的 HMR 之外,还可以显着提高性能、快速构建。

最后的思考

前端开发已经走过了漫长的道路,我们的需求也在不断发展和增加复杂性。构建工具是我们构建前端应用程序的重要组成部分,而现有工具还没有达到标准,这激发了新工具的开发,这些新工具重新构想了构建工具的设计方式。

下一代构建工具非常注重性能、易用性和不太复杂的配置,有望在未来一段时间内为雄心勃勃的前端应用程序提供支持。