比较新一代构建工具

Avatar of Hugh Haworth
Hugh Haworth

DigitalOcean 为您旅程的每个阶段提供云产品。立即开始使用 $200 的免费积分!

过去一年中,许多新的开发者工具问世,它们正在挑战那些在过去几年中主导前端开发的工具,包括 webpack、Babel、Rollup、Parcel、create-react-app。

这些新工具并非旨在执行完全相同的功能,每个工具都有其不同的目标和实现这些目标的功能。尽管它们存在差异,但这些工具确实有一个共同的目标:**改善开发者体验。**

目录

  1. 为什么这些工具现在都出现了?
  2. 它们与现有工具有何不同?
  3. 实验
  4. 可比较的功能
  5. esbuild
  6. Snowpack
  7. Vite
  8. wmr
  9. 功能比较
  10. 总结

具体来说,我想评估每个工具,概述它们的功能,为什么我们需要它们以及它们的用例。我意识到比较并不总是公平的。再次强调,我们本文中讨论的工具并非直接竞争对手。事实上,Snowpack 和 Vite 实际上在某些任务中使用了 esbuild。我们的目标更多的是更好地了解用于简化我们工作的开发工具的格局。这样,我们就可以看到有哪些选项,以及它们如何相互比较,以便在需要时做出最佳选择。

当然,所有这些都会受到我使用 React 和 Preact 的经验的影响。我对这些框架库比较熟悉,但我们也会看一下它们对其他前端框架的支持情况。

关于这些新的开发者工具,已经出现了很多优秀的文章、直播和播客。我推荐观看几个 ShopTalk Show 的节目以了解更多信息:第 454 期 讨论了 Vite,第 448 期 则介绍了 wmr 和 Snowpack 的创造者。从这些节目中可以明显看出,在构建这些工具以使我们的开发环境现代化方面,付出了大量的努力。

为什么这些工具现在都出现了?

我认为,这些工具的出现部分原因是是对 JavaScript 工具疲劳的反应——这在这篇文章中关于 2016 年学习 JavaScript 的感受中得到了很好的体现。它们还填补了编写单个原生 JavaScript 文件和在编写第一行代码之前需要下载 200 兆字节的工具依赖项之间的空白。它们包含了内置功能,而无需依赖项列表,并且是 JavaScript 生态系统中层级崩溃趋势的一部分。

Snowpack、Vite 和 wmr 都得益于浏览器中的原生 JavaScript 模块。早在 2018 年,Firefox 60 发布,默认情况下启用了 ECMAScript 2015 模块。从那时起,所有主要的浏览器引擎都支持原生 JavaScript 模块。Node.js 也在 2019 年 11 月发布了原生 JavaScript 模块。我们仍在探索原生 JavaScript 模块在 2021 年今天所能带来的可能性。

它们与现有工具有何不同?

无论我们使用 webpack、Rollup 还是 Parcel 来构建开发服务器,该工具都会从我们的源代码和一个 node_modules 文件夹中捆绑我们的整个代码库,并运行这些代码库以进行构建过程(例如 Babel、TypeScript 或 PostCSS),然后将捆绑的代码推送到我们的浏览器。所有这些都需要工作,并且可能会减缓大型代码库中开发服务器的运行速度,即使已经对缓存和优化进行了大量工作。

Snowpack、Vite 和 wmr 开发服务器不遵循这种模式。相反,它们会等待浏览器找到 import 语句并对该模块发出 HTTP 请求。只有在发出此请求后,该工具才会将转换应用于所请求的模块及其模块导入树中的所有叶节点,然后将这些转换后的模块提供给浏览器。这大大提高了速度,因为在推送到开发服务器的过程中,所需的工作量减少了。

您会注意到esbuild 在这张图中缺失了。它首先是一个捆绑器。它没有像其他工具那样绕过捆绑。相反,esbuild 通过避免昂贵的转换,利用并行化以及使用 Go 语言,以极快的速度处理代码。

实验

我从 React 文档中的示例应用程序中选择了一个,并使用本文中介绍的每个工具对其进行了重新构建。我选择的项目是Snap Shot,由Yogita Verma 创建。这里有一个指向原始仓库的链接,以及一个指向我的仓库的链接,其中包含四个版本的 Snap Shot,每个版本都使用了不同的构建工具。我们稍后将比较每个构建步骤的输出。重新构建这个应用程序使我能够测试将一些非常标准的 React 依赖项(包括React Routeraxios)引入这些工具的开发者体验。

可比较的功能

在我们深入了解每个工具的具体细节之前,它们默认支持以下功能(程度不同)

  • 对原生 JavaScript 模块的一流支持
  • TypeScript 编译(但不进行类型检查)
  • JSX
  • 用于扩展的插件 API
  • 内置开发服务器
  • CSS 捆绑以及对 CSS-in-JS 库的支持

所有这些工具都可以将 TypeScript 编译为 JavaScript,但即使存在类型错误也会进行编译。为了进行正确的类型检查,您需要安装 TypeScript 并对根 JavaScript 文件运行 tsc --noEmit,或者使用编辑器插件来监视类型错误。

好了,让我们看一下每个工具。

esbuild

esbuild 是由Evan WallaceFigma 的 CTO)创建的。它的主要功能是提供比基于 Node 的捆绑器快 10-100 倍的构建步骤(根据其自身基准测试)。它没有提供像 create-react-app 那样提供的许多开发者便利。但是,越来越多的 esbuild 启动器正在出现,填补了这些空白,包括create-react-app-esbuildestrellaSnowpack,后者使用 esbuild 来进行构建步骤。

esbuild 非常新。它尚未达到 1.0 版本,并且尚未完全准备好用于生产环境——但它已经不远了。它为开发者提供了直观的 JavaScript 和命令行 API,并具有智能默认设置。

用例

esbuild 是捆绑器世界的一款彻底改变游戏规则的工具。它在大型代码库中将最有用,因为 esbuild 与 Node 捆绑器之间的速度差异会成倍放大。当 esbuild 达到 1.0 版本时,它将对大型生产网站非常有用,并且可以帮助团队节省大量等待构建完成的时间。不幸的是,大型生产网站必须等到 esbuild 变得稳定之后才能使用它。在此期间,它将只对您的个人项目中的捆绑过程提供一些速度提升。

esbuild 的闪电般的速度对于您进行的任何工作都是一种额外优势。减少等待构建运行的时间总是会改善开发者体验!考虑到这一点,如果您要快速构建原型应用程序,您可能想要从比 esbuild 更高级的工具开始——否则,您需要花费一些时间来引入依赖项并配置环境,才能获得我们在 JavaScript 生态系统中所期望的便利性。此外,如果您想要尽可能地减小捆绑包的大小,您可能想要使用 Rollup 和 terser,它们会生成稍微更小的捆绑包大小。

设置

我决定以一种天真的方式在 esbuild 中启动一个 React 项目:使用 npm 安装 esbuild、React 和 ReactDOM。我创建了一个 src/app.jsx 文件和一个 dist/index.html 文件。然后,我使用以下命令将应用程序编译到 dist/bundle.js 文件中

./node_modules/.bin/esbuild src/app.jsx --bundle --platform=browser --outfile=dist/bundle.js

当我将 index.html 托管并在浏览器中打开它时,我遇到了“死亡白屏”和一个“Uncaught ReferenceError: process is not defined”控制台错误。 文档和 CLI 都准确地解释了如何防止这种情况,但这对于初学者来说可能是一个“陷阱”,因为它需要在捆绑 React 时使用额外的参数

--define:process.env.NODE_ENV=\"production\"

或者,如果你将 esbuild 包含在像这样编写的 npm 脚本中以转义引号

--define:process.env.NODE_ENV=\\\"production\\\"

define 参数对于任何为浏览器捆绑的、期望节点环境变量的库都是必需的。Vue 2.0 也期望这些。你不会在 Preact 中遇到同样的问题,因为它不期望任何环境变量,并且默认情况下为浏览器准备好了。

在我使用 define 参数运行命令后,我的“Hello world”React 应用程序完美地运行了。JSX 在 .jsx 文件中开箱即用。也就是说,React 需要手动导入,然后 JSX 被转换为 React.createElement。但是,有一些方法可以添加 JSX 中的自动导入,或者为 Preact 配置 JSX。

用法

esbuild 提供了一个 --serve 选项来用于开发服务器。这绕过了文件系统,并直接从内存中提供模块,确保浏览器不会拉取旧版本的模块。但是,它不包括实时/热重载,因此你将在保存后发现自己刷新浏览器,这不是理想的体验。

我决定使用新发布的 watch 功能。这告诉 esbuild 每次保存源文件时重新编译代码。但我们仍然需要一个服务器来查看我们的保存更改。我们可以引入一个开发服务器包,例如 Luke Jackson 的 servor

npm install servor --save-dev

然后,我们可以使用 esbuild Javascript API 同时启动服务器并运行 esbuild 的 watch 模式。让我们在项目根目录创建一个名为 watch.js 的文件

// watch.js
const esbuild = require("esbuild");
const servor = require("servor");

esbuild.build({
  // pass any options to esbuild here...
  entryPoints: ["src/app.jsx"],
  outdir: "dist",
  define: { "process.env.NODE_ENV": '"production"' },
  watch: true,
});

async function serve(){
  console.log("running server from: http://localhost:8080/");
  await servor({
    // pass any options to servor here...
    browser:true,
    root: "dist",
    port: 8080,
  });
}

serve();

现在在命令行中运行 node watch.js。这给了我们一个不错的开发服务器,不过同样,它没有提供热模块替换或快速刷新(即你的客户端状态不会保留)。但对于我的测试需求来说已经足够了。

即使我们每次保存文件时都会重新捆绑整个应用程序,在我们 esbuild 慢下来之前,我们也需要有一个相当庞大的应用程序。在我设置好这个工具后,我从更改中得到了即时的反馈。我的电脑使用的是 2012 年的英特尔 i7 处理器,所以它绝对不是一台顶级的机器。

如果你需要一个预先配置好的 esbuild 版本,其中包含实时重载和一些 React 默认设置,你可以克隆 这个仓库

支持的文件

如果你的风格是这样,esbuild 可以导入 JavaScript 中的 CSS。它将 CSS 编译到一个输出文件中,该文件与你的主输出 JavaScript 文件具有相同的名称。它也可以默认捆绑 CSS @import 语句。不支持 CSS 模块,但有计划将其纳入。

有一个 不断增长的 esbuild 插件社区。例如,有一些插件可以用于 Vue 单文件组件Svelte 组件

esbuild 与 JSON 文件一起工作,可以将它们捆绑到 JavaScript 模块中,无需任何配置。

它还可以使用以下选项在 JavaScript 中导入图像:将它们转换为数据 URL 或将它们复制到输出文件夹中。此行为默认情况下未启用,但你可以在 esbuild 配置对象中添加以下内容以启用任一选项

loader: { '.png': 'dataurl' } // Converts to data url in JS bundle
loader: { '.png': 'file' } // Copies to output folder

代码拆分似乎正在进行中,但在 ESM 输出格式中基本已经完成,并且它确实看起来是该项目的优先事项。还值得一提的是,tree-shaking 默认情况下内置在 esbuild 中,无法关闭。

生产构建

在你的 esbuild 命令中使用“minify”和“bundle”选项不会创建与 Rollup/Terser 管道一样小的捆绑包。这是因为 esbuild 牺牲了一些捆绑包大小优化,以便尽可能少地通过你的代码。但是,根据你的项目,这个差异可能是微不足道的,并且为了提高捆绑速度而值得。在我的 Snap Shot 应用程序克隆中,esbuild 创建了一个 177 KB 的捆绑包,这比 Vite(使用 rollup 和 terser)产生的 165KB 多不了多少。

总体

esbuild
多个前端框架的模板
热模块替换开发服务器
流式导入
预配置的生产构建
自动 PostCSS 和预处理器转换
HTM 转换
Rollup 插件支持
磁盘上的大小(默认安装)7.34 MB

esbuild 是一个非常强大的工具。但如果你习惯了零配置设置,它可能很困难。如果你需要更多,那么你可能想要看看下一个工具 Snowpack,它使用 esbuild。

Snowpack

Snowpack 是 SkypackPika 的创建者开发的构建工具。它提供了一个很棒的开发服务器,并且是使用 “非捆绑开发” 哲学创建的。引用文档:“你应该能够使用捆绑器,因为你想使用,而不是因为你需要使用。”

默认情况下,Snowpack 的构建步骤不会将文件捆绑到一个单独的包中,而是提供在浏览器中运行的非捆绑 esmodules。esbuild 实际上作为依赖项包含在其中,但想法是使用 JavaScript 模块,只有在需要时才使用 esbuild 进行捆绑。

Snowpack 有些 非常棒的文档,包括一个 使用它的指南列表,以及一个 用于它们的模板集合。一些指南仍在开发中,但其他一些指南,比如 用于 React 的指南 很好,也很清晰。它看起来也像是 Snowpack 将 Svelte 作为一等公民。事实上,我第一次从 Rich Harris 的 “未来主义 Web 开发” 演讲(在 2020 年 Svelte 峰会上)中听说 Snowpack。也就是说,即将发布的 Svelte 元框架 SvelteKit 应该由 Snowpack 提供支持,但后来切换到了 Vite(我们将在下一节中回顾)。

用例

如果你想在非捆绑部署上加倍下注,Snowpack 是一个不错的选择。你可能使用少量模块来编写源代码。这意味着你不会使用非捆绑构建创建大量请求级联。如果你不需要捆绑带来的额外复杂性和技术债务,那么 Snowpack 是一个不错的选择。一个很好的用例是,如果你正在将一个前端框架逐步引入到一个服务器端渲染或静态应用程序中。你将从节点生态系统中获取尽可能少的工具,但你仍然可以从声明式前端框架中获益。

其次,我认为 Snowpack 是围绕 esbuild 的一个很好的包装器。如果你想尝试 esbuild,但也想要一个开发服务器和为前端框架预先编写的模板,那么你使用 Snowpack 不会出错。在你的 Snowpack 配置的构建步骤中启用 esbuild,你就万事大吉了。

就目前而言,我认为 Snowpack 不会成为像 create-react-app 这样的零配置工具的最佳替代品,因为如果你有一个大型应用程序并且需要一个超级炫酷的优化后的生产就绪构建步骤,那么你需要自己引入插件并配置它们。

设置

让我们通过进入命令行来使用 Snowpack 启动一个项目

mkdir snowpackproject
cd snowpackproject
npm init #fill with defaults 
npm install snowpack

现在,让我们将以下内容添加到 package.json

// package.json
"scripts": {
  "start": "snowpack dev",
  "build": "snowpack build"
},

接下来,我们将创建一个配置文件

// Mac or Linux
touch snowpack.config.js
// Windows
new-item snowpack.config.js

我认为 Snowpack 最神奇的部分是,在配置文件中设置一个看起来无辜的键值对。例如,将此粘贴到配置文件中

// snowpack.config.js
module.exports = {
  packageOptions: {
    "source": "remote",
  }
};

source: remote 启用了一种名为 流式导入 的功能。流式导入使 Snowpack 能够通过将裸导入(例如,import React from 'react';)转换为来自 Skypack 的 CDN 导入来绕过 npm 安装。

接下来,让我们创建一个 index.html 文件

<!--index.html-->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">>
  <title>Snowpack streaming imports</title>
</head>
<body>
  <div id="root"></div>
  <!-- Note the type="module". This is important for JavaScript module imports. -->
  <script type="module" src="app.js"></script>
</body>
</html>

最后,我们将添加一个 app.jsx 文件

// app.jsx
import React from 'react'
import ReactDOM from 'react-dom'
const App = ()=>{
  return <h1>Welcome to Snowpack streaming imports!</h1>
}
ReactDOM.render(<App />,document.getElementById('root')); 0

请注意,我们没有在任何阶段 npm 安装 React 或 ReactDOM。但是,如果我们像这样启动 Snowpack 开发服务器

./node_modules/.bin/snowpack dev

…我们的应用程序仍然可以工作!

Snowpack 不是从 node_modules 文件夹中拉取,而是从 Skypack(一个托管 npm 注册表的 CDN)拉取 npm 包,并且它经过预优化以在浏览器中运行。然后,Snowpack 将其提供在一个 ./_snowpack/pkg URL 中。

用法

这是与基于 Node/npm 的工作流程的重大区别。我们实际上正在看的是一种新的 CDN/JavaScript 模块化工作流程。

但是,如果我们按原样运行应用程序并执行生产构建,Snowpack 会抛出错误。这是因为它需要知道在构建时使用 React 和 ReactDOM 的哪些版本。您可以通过写入 snowpack.deps.json 来解决此问题,该文件可以通过运行以下命令自动创建

./node_modules/.bin/snowpack add react
./node_modules/.bin/snowpack add react-dom

这不会从 npm 下载包,但会记录用于 Snowpack 构建的包版本。

需要注意的是,我们会错过开发人员的错误消息,因为 Skypack 会发布包的生产版本。

即使我们没有使用流式导入,Snowpack 开发服务器也会将 node_modules 中的每个依赖项捆绑到每个依赖项一个 JavaScript 文件中,将这些文件转换为原生 JavaScript 模块,然后将其提供给浏览器。这意味着浏览器可以缓存这些脚本,并且只有在它们发生更改时才会重新请求它们。开发服务器会在保存时自动刷新,但不保留客户端状态。来自 node 的所有依赖项似乎都开箱即用,无论它们是使用传统的模块格式还是 node API(例如我们之前在 esbuild 中遇到问题的臭名昭著的 process.env)。

在 React 中保留客户端状态需要 react-refresh,它需要几个 Babel 包作为依赖项。这些默认情况下不包括在内,但可以使用更完整 React 模板获得。该模板包含 react-refresh、Prettier、Chai 和 React Testing Library,总 Node 依赖项包大小为 80 MB

npx create-snowpack-app my-react-project --template @snowpack/app-template-react

支持的文件

支持 JSX,但默认情况下仅支持 .jsx 文件。Snowpack 会自动检测是否正在使用 React 或 Preact,并据此决定使用哪个渲染函数来执行 JSX 转换。但是,如果我们希望进一步自定义 JSX,则需要通过 他们的插件 拉入 Babel。还有一个 可用于 Vue 单文件组件的 Snowpack 插件,当然还有用于 Svelte 组件 的插件。此外,Snowpack 会编译 TypeScript,但对于类型检查,我们需要 TypeScript 插件

可以将 CSS 导入到 JavaScript 中,并在运行时将其放入文档的 <head> 中。只要它们具有 .module.css 扩展名,CSS 模块也开箱即用以进行作用域。

导入的 JSON 文件将被转换为一个 JavaScript 模块,该模块将一个对象作为默认导出。Snowpack 支持图像,并将它们复制到生产文件夹中。与它的非捆绑理念一致,Snowpack 不会将图像作为数据 URL 包含在捆绑包中。

生产构建

默认的 snowpack build 命令基本上将确切的源文件结构复制到输出文件夹中。对于编译成 JavaScript 的文件(例如 TypeScript、JSX、JSON、.vue.svelte),它会将每个单独的文件转换为一个单独的浏览器友好的 JavaScript 模块。

这可以正常工作,但对于生产来说并不理想,因为它会导致大量瀑布请求,如果源代码被分割成很多文件。在 Snap Shot 应用程序中,我最终得到了 184KB 的源文件,然后会从 Skypack 请求另外 105KB 的依赖项,这造成了相当大的瀑布请求。

但是,Snowpack 会将 esbuild 作为依赖项拉取,我们可以通过向 Snowpack 配置添加一个“optimize”对象来启用 esbuild 来捆绑、压缩和编译我们的代码

// snowpack.config.js
module.exports = {
  optimize: {
    bundle: true,
    minify: true,
    target: 'es2018',
  },
};

这会使用 esbuild 提供的优化功能运行代码,因此只需添加这些选项,我们就可以获得与之前使用 esbuild 获得的相同的构建。

由于 esbuild 尚未达到 1.0 版本,Snowpack 建议使用 webpackRollup 插件进行生产构建,这两个插件都需要配置。

总体

Snowpack 提供了一种轻量级的开发体验,具有功能齐全的开发服务器、详细的文档和易于安装的模板。您可以自行决定是否要捆绑应用程序,以及如何捆绑。如果您想要一个同时提供开发服务器和更有见地的构建步骤的工具,您可能想看看 Vite,这是我们列表中的下一个工具。

Snowpack
多个前端框架的模板
热模块替换开发服务器✅ (使用模板时)
流式导入
预配置的生产构建
自动 PostCSS 和预处理器转换
HTM 转换
Rollup 插件支持✅ (使用 snowpack-plugin-rollup-bundle 进行构建步骤时)
磁盘上的大小(默认安装)16 MB

Vite

Vite 由 Vue 创建者(以及 Hades 极速玩家)Evan You 开发。esbuild 专注于构建步骤,而 Snowpack 专注于开发服务器,Vite 提供了这两者:一个完整的开发服务器 *和* 使用 Rollup 的优化构建命令。

用例

如果您想要一个严肃的 create-react-app 或 Vue CLI 竞争对手,Vite 是最接近的一个,因为它自带电池的功能。闪电般的快速开发服务器和零配置的优化生产构建意味着您可以从零到生产无需任何配置。Vite 是一种既可以用于微小的副项目,也可以用于大型生产应用程序的工具。Vite 的一个很好的用例是任何相当大的单页应用程序。

为什么 *不* 使用 Vite?Vite 是一个有见地的工具,您可能不同意它的见解。您可能不想使用 Rollup 进行构建(我们一直在谈论 esbuild 的速度有多快),或者您可能希望您的工具开箱即用地为您提供 Babel、eslint 和 webpack 加载器生态系统的全部功能。

此外,如果您想要零配置的服务器端渲染元框架,您最好坚持使用基于 webpack 的框架,例如 Nuxt.js 和 Next.js,直到 Vite 服务器端渲染的故事更加完整。

设置

Vite 比 esbuild 和 Snowpack 具有更多有见地的默认值。它的 文档 清晰且详细。我们获得了对 Vue 的完全支持,Evan 是创建者,因此,Vite 对于 Vue 开发人员来说绝对是幸福之路。也就是说,Vite 可以与任何前端框架一起使用,甚至提供一个 模板列表 来帮助您入门。

用法

Vite 的开发服务器非常强大。Vite 使用 esbuild 将项目的所有依赖项预捆绑到一个原生 JavaScript 模块中,然后使用一个高度缓存的 HTTP 标头将其提供。这意味着在第一个页面加载后,不会浪费时间编译、提供或请求导入的依赖项。Vite 还提供清晰的错误消息,打印出确切的代码块和行号以进行故障排除。同样,在 Vite 中,我没有遇到任何问题,可以将使用 node API 或传统格式的依赖项拉取进来。它们似乎都已屏蔽到浏览器可接受的 esmodule 中。

Vite 的 React 和 Vue 模板都包含启用热模块替换的插件。Vue 模板包含一个 用于单文件组件的 Vue 插件 和一个 用于 JSX 的 Vue 插件。React 模板包含 react-refresh 插件。无论哪种方式,两者都将为您提供热模块替换和客户端状态保留。当然,它们添加了一些其他依赖项,包括 Babel 包,但是,在 Vite 中使用 JSX 时,实际上并不需要 Babel。默认情况下,JSX 的工作方式与 esbuild 相同,它会转换为 React.createElement。它不会自动导入 React,但其行为可以配置。

说到这里,Vite 不支持像 Snowpack 和 wmr 那样的流式导入。这意味着像往常一样 npm 安装依赖项。

一件很酷的事情是 Vite 包含了对 服务器端渲染的实验性支持。选择您喜欢的框架,并生成直接发送到客户端的静态 HTML。目前,看起来我们需要自己构建这种架构,但是,这看起来是构建在 Vite 之上的元框架的良好机会。Evan You 已经有一个正在进行的工作,名为 VitePress,这是一个使用 Vite 的优势来替代 VuePress 的工具。Sveltekit 也已 将其依赖项列表中添加了 Vite。看起来 CSS 代码拆分 的加入是 Sveltekit 转向 Vite 的原因之一。

支持的文件

对于 CSS,Vite 在我们正在查看的所有工具中提供了最多的功能。它支持将 CSS 导入捆绑为 CSS 模块。但是我们也可以 npm install PostCSS 插件并创建一个 postcss.config.js 文件,Vite 会自动开始将这些转换应用于 CSS。

我们可以安装和使用 CSS 预处理器,只需 npm install 预处理器并将文件重命名为正确的扩展名(例如 .filename.scss),Vite 将开始应用相应的预处理器。并且,正如我们在概述中所说,Vite 支持 CSS 代码拆分。

图像导入默认使用公共 URL,但我们也可以通过在 URL 字符串末尾使用 ?raw 参数将它们作为字符串加载到捆绑包中。

可以在源代码中导入 JSON 文件,并将其转换为一个导出单个对象的 esmodule。我们也可以提供一个命名导入,Vite 会在 JSON 文件的根字段中查找导入,并对其余部分进行摇树优化。

生产构建

Vite 使用 Rollup 进行预配置的生产构建,并进行了一系列优化。它有意提供一个零配置构建,这对于大多数用例来说应该足够了。

构建附带了我们期望的 Rollup 功能:捆绑、压缩和摇树优化。但是我们也获得了额外的功能,例如代码拆分动态导入,以及“异步块加载”等功能,这是一种花哨的说法,表示如果我们请求一个导入另一个模块的 JavaScript 模块,构建将经过预优化以同时(异步地)加载这两个模块。

使用 Snap Shot 应用程序运行 Vite 的默认构建,我最终得到了一个 5KB 的 JavaScript 文件和一个 160KB 的 JavaScript 文件(总计 165KB),并且项目中的所有 CSS 都被自动压缩成一个只有 2.71KB 的小文件。

总体

Vite 的有见地的特性使其成为我们当前工具的强大竞争对手。为了使开发体验真正无缝,并开箱即用地创建生产就绪的构建,已经做了很多工作。

Vite
多个前端框架的模板
热模块替换开发服务器✅ (使用模板时)
流式导入
预配置的生产构建
自动 PostCSS 和预处理器转换
HTM 转换
Rollup 插件支持✅ 
磁盘上的大小(默认安装)17.1 MB

wmr

与Vite类似,wmr是另一个带有主观意见的构建工具,它同时提供开发服务器和构建步骤。它由Preact的创建者Jason Miller创建,因此对于Preact开发者来说无疑是最佳选择。Jason Miller在JS Party播客的访谈中解释了wmr背后的理念。

Preact非常小巧,如果你想做一个轻量级的项目,它非常适合。那么我们应该使用什么工具呢?我们有一个基于Webpack的工具,它被许多知名网站用于生产环境,但它是一个重量级的工具。那么原型工具在哪里呢?这是其中一个方面。另一方面,我和Preact团队的其他人,我们一直在捆绑器生态系统的边缘观望,不断地推动,试图达成共识,以找到一个方向,让我们能够继续推进这种编写和交付现代代码的想法。

这告诉我们,wmr的核心在于编写和交付现代代码,在项目中使用更轻量的工具。

你可能想知道wmr代表什么?什么也不代表!曾有人提议使用“Web Modules Runtime”和“Wet Module Replacement”这两个名字,但它只是一个虚假的缩写,类似于npm。

wmr与Preact一样,拥有无情的包大小精简功能,因此它非常小巧——仅重2.6 MB——并且没有任何npm依赖项。尽管如此,它仍然包含了大量非常棒的功能,包括热模块替换开发服务器和经过优化的生产构建。

用例

如果我想要使用Preact尽快创建一个原型,我会选择wmr。它无需任何配置,下载速度只需几秒钟。使用它就像使用一个超级增强的静态文件服务器一样。wmr拥有TypeScript、优化构建步骤和静态HTML渲染功能,能够满足小型到中型应用程序的全部需求。它的小巧尺寸也使其非常适合快速尝试库或演示想法。

如果你没有使用Preact、React或原生JavaScript,那么wmr可能不适合你。Preact团队尚未为其他框架提供模板。文档也没有我们之前讨论过的其他工具那么详细。这意味着你偏离最佳路径越远,就越需要深入研究源代码。因此,如果需要进行大量自定义,我不建议使用它。

设置

如果你使用Preact,除了快速执行npm install之外,无需任何配置。为了在wmr中使用React而不是Preact,目前需要执行两个步骤。首先,在你的`package.json`中将`htm/preact`别名为`htm/react`,将`react`别名为`es-react`。

"alias": {
  "htm/preact": "htm/react",
  "react": "es-react"
},

然后在你的组件中添加来自`es-react`的导入。

// ReactDOM only needed on root render
import { React, ReactDOM,} from 'es-react';

这意味着我们实际上并没有使用你可能习惯的普通React包,而是从es-react中提取了React。这是因为wmr依赖于与原生JavaScript模块兼容的包。React默认情况下不使用原生模块,而是使用一种称为UMD模块的旧式模块。es-react是一个包,它导入React,但提供与Web平台兼容的导出。

这说明了wmr的理念,即使用Web平台的原生原语,而不是使用工具绕过并将其抽象化。

另一个选择是在我们的应用程序中使用Skypack导入,它也经过预优化,可以在浏览器中运行。

import React from 'https://cdn.skypack.dev/react';
import ReactDOM from 'https://cdn.skypack.dev/react-dom';

wmr期望你编写在浏览器中运行的现代代码,这意味着如果你导入使用Node API或传统模块系统的依赖项,可能需要进行一些配置。为了使Snap Shot应用程序正常运行,我需要深入研究Node模块,并将一个或两个库转换为使用原生JavaScript模块语法。如果你使用的是旧的库,这可能会减慢你的速度。Preact生态系统经过全面优化,可以在浏览器中运行,无需任何修改。这也是在wmr中坚持使用Preact最佳路径的另一个原因。

wmr有插件。它提供了一个插件API,支持Rollup插件进行构建步骤。文档中越来越多的wmr专用示例,包括一个压缩HTML的插件,以及一个提供文件系统基于路由的插件。

wmr支持不同的框架,但没有为它们提供任何预先构建的模板。起初,我发现配置JSX转换相当困难。尽管如此,Jason已经确认,他们计划使JSX更易于配置,并且wmr旨在与框架无关。JSX计划在普通JavaScript文件中开箱即用。

用法

要开始使用,你可以在命令行中运行以下命令

npm init wmr your-project-name

或者,你可以运行以下命令手动构建应用程序

npm init -y
npm install wmr
mkdir public
touch public/index.html
touch public/index.js 

然后在你的`index.html`主体中添加一个脚本导入(再次确保使用`type="module"`)

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

现在,你可以将Preact的hello world代码写入你的`index.js`文件

import { render } from 'preact';
render(<h1>Hello World!</h1>, document.body);

最后,启动你的开发服务器

node_modules/.bin/wmr

现在,我们拥有一个完整的热模块替换开发服务器,它将立即响应我们源代码的任何更改。

wmr在转换JSX时使用了一个名为htm的工具,它带来了一些很棒的优势。假设我们正在使用Preact在wmr中编写一个计数器,并且犯了一个错误

import { render } from 'preact';
import { useState } from 'preact/hooks';
function App() {
  const [count,setCount] = useState(0)
  return <>
  <button onClick={()=>{setCount(cout+5)}}>Click to add 5 to count</button> // HIGHLIGHT
  <p>count: {count}</p>
  </>
}
render(<App />, document.body);

`count`在`onClick`处理程序函数中拼写错误,因此运行此代码会导致错误。通常,我们需要依靠我们的工具和源映射来收集有关错误位置的信息,但wmr采用了不同的解决方案。使用htm,通过使用标记模板字面量,这尽可能地接近了浏览器中的原生JSX。因此,通常编写React或Preact代码的样子如下

<MyComponent>I am JSX. I am not actually valid Javascript</MyComponent>

…htm 看起来更像这样

html`<${MyComponent}>I am about as close as it gets to JSX as you can get while being able to run in the browser</MyComponent>`

现在,如果我们正在调试代码并在DevTools中打开“Sources”面板,我们应该看到一个与编辑器中源代码几乎相同的脚本。

Image of the source code.

通过这种方式,我们可以正确地调查错误在浏览器中的位置,而无需使用源映射。当然,这个具体的例子是相当牵强的,但你可以看到这将非常有用,因为它意味着wmr不需要你开发环境中的源映射。

wmr默认情况下支持流式导入,因此裸导入将从npm注册表下载。这是通过一个复杂的过程完成的,该过程检查npm包中的所有源代码,删除所有测试和元数据,并将其转换为单个原生JavaScript导入。与Snowpack类似,可以构建复杂的应用程序,而无需使用npm安装任何内容。实际上,wmr是第一个支持此理念的工具。

支持的文件

关于wmr支持的其他文件类型,CSS文件可以在JavaScript中导入,并且也支持CSS模块。

wmr没有内置支持Vue单文件组件或Svelte组件。但是,wmr的构建步骤可以使用Rollup插件,开发服务器可以使用Polka/Express中间件进行配置,因此可以使用它们将导入转换为Vue和Svelte组件。实际上,我写了一个Vue单文件组件的小插件来展示如何实现这一点。

在wmr中,我们无法将图像作为数据URL导入JavaScript,除非使用插件。相反,我们需要使用语法正确的JavaScript方法导入它们。因此,如果我们的公共文件夹中有一张狗的图片,我们可能会在Preact组件中包含它,如下所示

function Dog() {
  return <img src={new URL('./dog.jpg', import.meta.url)} alt="dog hanging out"></img>
}

构建步骤运行后,图像将被复制,并可以从分发文件夹中访问。开发服务器中对图像进行了热模块替换,因此图像的更改会立即反映在浏览器中。

关于文件支持的另一个说明:JSON可以被导入,并被转换为一个JavaScript对象供使用。但在实际构建应用程序时,我们需要Rollup JSON插件

生产构建

wmr提供了一个生产构建步骤,包括捆绑、压缩和摇树优化,而无需任何额外的依赖项。查看wmr的源代码,看起来它使用了rollup和terser,并且这两个工具的压缩版本包含在wmr包中。Snap Shot应用程序的wmr包大小为164KB,因此它创建的包大小比Vite创建的两个JavaScript文件总大小略小。

还可以通过配置wmr,使其使用preact-iso将应用程序渲染为静态HTML并在浏览器中进行水合。这意味着wmr可以作为Preact的元框架使用,类似于Next.js。

总体

我喜欢使用wmr来为React和Preact应用程序创建原型。使用一个极小的工具,但却提供了接近重量级捆绑器的开发者便利性,这感觉很棒。

wmr
多个前端框架的模板
热模块替换开发服务器
流式导入
预配置的生产构建
自动 PostCSS 和预处理器转换
HTM 转换
Rollup 插件支持✅ 
磁盘上的大小(默认安装)2.57 MB

功能比较

我们刚刚介绍了许多内容!为了避免上下滚动本文来比较结果,我将所有内容都整理在这里,以便在并排比较时查看这些工具的优劣。我还包括了我们没有明确介绍的功能的额外比较。

用例

工具用例
esbuild大型代码库。尚未准备好投入生产。
Snowpack不需要捆绑的小型应用程序或你希望选择使用哪种捆绑器的应用程序。也适用于在服务器渲染应用程序中逐步采用JavaScript框架。
Vite用于生成单页应用程序的Vue CLI/Create-React-App的替代方案。这是Vue的最佳路径。
wmr原型。适用于小型到中型应用程序,可用于单页应用程序或服务器渲染应用程序。这是Preact的最佳路径。

设置

esbuildSnowpackVitewmr
多个前端框架的模板
磁盘上的大小(默认安装)7.34 MB16 MB17.1 MB2.57 MB
零配置生产构建
带零配置的HMR开发服务器
处理Node包的process.env

开发服务器

esbuildSnowpackVitewmr
热模块替换
CSS热替换
npm依赖项预捆绑
浏览器错误消息
HTM转换

生产构建

esbuildSnowpackVitewmr
Snap Shop应用程序的输出包大小177 KB184 KB(多个JavaScript文件),加上 105 KB 的Skypack CDN依赖项165 KB(一个5 KB文件和一个160 KB文件)164 KB
基于Go的捆绑✅ 当在构建步骤中使用 esbuild 时
预配置的生产构建
异步块加载
Rollup 插件支持

其他功能

esbuildSnowpackVitewmr
流式输入
服务器端渲染✅ (实验性)
CSS 模块
自动 PostCSS 和预处理器转换

总结

我对使用我们刚刚看到的任何工具构建 JavaScript 应用程序感到非常兴奋。无论我们是在编写小型副项目还是大型生产网站,所有这些工具都能够加快反馈循环并提高生产力。它们打开了通往 JavaScript 生态系统的大门,让我们可以思考 JavaScript 生态系统所需的必要功能,以及我们是否可以开始摆脱旧版模块和浏览器带来的累赘。这些工具将通过提供更精简、更快的开发环境来降低新开发人员的入门门槛,并在编写的代码和在浏览器中运行的代码之间减少抽象。

如果您厌倦了等待依赖项下载和构建步骤运行,我建议您尝试使用这一代新工具。

进一步阅读

其他值得关注的新 JavaScript 工具

  • Rome – 包含代码风格检查、编译、打包、测试运行和格式化的完整工具链
  • SWC – 基于 Rust 的 JavaScript/TypeScript 编译器
  • Deno – JavaScript 和 TypeScript 的运行时(类似于 Node.js)