JavaScript 新一代构建工具对比

2022-04-18 15:39:05 浏览数 (1)

本文译自:https://css-tricks.com/comparing-the-new-generation-of-build-tools/

在过去的一年里,出现了一批新的开发者工具,它们正在紧跟过去几年主导前端开发的工具,包括 webpack、Babel、Rollup、Parcel、create-react-app

这些新的工具并不是为了完成完全相同的功能而设计的,每个工具都有不同的目标和功能。尽管存在差异,但这些工具有一个共同的目标:改善开发者体验

概览

  • esbuild
  • Snowpack
  • Vite
  • wmr
  • Feature comparison
  • Wrapping up

具体来说,我想对每一个进行评估,概述它们的作用,为什么我们需要它们,以及它们的使用案例。比较并不总是公平的,我们在这篇文章中看到的东西也不是直接的竞争对手。事实上,SnowpackVite 在某些任务中都使用了 esbuild。我们的目标更多的是为了更好地了解运行任务的开发者工具的格局,让我们的工作更轻松。通过这种方式,我们就能看到有哪些选择,以及它们是如何配合的,这样我们就能在需要的时候做出最好的选择。

当然,我分析的所有的这些都会受到我使用 ReactPreact 的经验的影响。我对这些框架库比较熟悉,但我也会关注它们对其他前端框架的支持。

为啥这些工具现在都出现了?

在某种程度上,我认为这些工具的到来是对 JavaScript 工具疲劳的一种反应。

Snowpack、Vitewmr 都用到了浏览器中的原生 JavaScript 模块。早在 2018 年,Firefox 60 发布时默认启用了 ECMAScript 2015Module 。此后,各大浏览器引擎都支持原生 JavaScript 模块。Node.js 也在2019年11月推出了原生 JavaScript 模块。在2021年的今天,我们还在寻找原生 JavaScript 模块能够带来哪些新的可能性。

这些工具和现有的工具有什么不同?

无论我们在开发服务器上使用 webpack、Rollup 还是 Parcel,工具都会从我们的源代码和 node_modules 文件夹中把我们的整个代码库打包在一起,通过构建过程运行这些代码,比如 Babel、TypeScriptPostCSS,然后将打包的代码推送到我们的浏览器上。这一切都需要花费大量的工作,并且会使开发服务器在更大的代码库中慢慢爬行,甚至在所有的工作都用于缓存和优化之后也是如此。

Snowpack、Vitewmr 开发服务器则不采用这种模式。相反,它们会等到浏览器找到一个 import 语句,并为模块发出 HTTP 请求。只有在这个请求发出后,该工具才会对请求的模块和模块导入树中的任何叶节点应用转换,然后将这些转换提供给浏览器。这大大加快了速度,因为在推送到开发服务器的过程中减少了工作。

你会注意到描述中缺少了 esbuild 。它首先是一个 bundler 程序。它并不像其他工具那样绕开 bundler。相反,esbuild 通过避免昂贵的转换、利用并行化和使用Go语言来快速处理代码。

实验

我从 React 文档中选取了一个示例应用,并使用文中所提到的每个工具重新构建了它。我选择的项目是 Yogita VermaSnap Shot。这里有一个原始 repo 的链接,还有一个我的repo链接,里面有四个版本的 Snap Shot,每个版本都使用不同的构建工具。我们稍后会比较每个构建步骤的输出。重新构建这个应用程序,让我可以测试开发人员将一些相当标准的 React 依赖项添加到工具(包括 React Routeraxios)中的体验。

  • 原始 repo:https://github.com/Yog9/SnapShot
  • Demo:https://github.com/Elliotclyde/build-tool-test

可以比较的功能

在我们深入了解每个工具的具体内容之前,它们都支持以下功能(在不同程度上)。

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

所有这些工具都可以将 TypeScript 编译成 JavaScript,但即使有类型错误也会这样做。为了进行正确的类型检查,你需要安装T ypeScript,并在你的 JavaScript 根文件上运行 tsc --noEmit ,或者使用编辑器插件来观察类型错误。

好了,下面我们来具体看看每个工具。

esbuild

esbuild 是由 Evan Wallace(Figma的CTO)创建的。它的主要特点是,它提供的构建步骤比基于 Node 的打包器快 10×-100×(根据他们自己的基准)。它没有提供许多你可能会在 create-react-app 这样的工具中找到的开发者便利。但是有越来越多的 esbuild starter 启动器出现来填补这些空白,包括 create-react-app-esbuild,estrellaSnowpack,它们的构建步骤使用 esbuild

esbuild 是非常新的。它还没有达到 1.0 版本,还没有完全准备好用于生产使用 — 但它已经不远了。它为你提供了直观的 JavaScript 和带有智能默认值的命令行 API

用例

esbuild 完全改变了 bundler 的游戏规则。它将在大型代码库中发挥最大的作用,因为 esbuildnode 打包器之间的速度差异会成倍增加。当 esbuild 达到 1.0 的时候,它在大型生产站点中会非常有用,将为团队节省大量等待构建完成的时间。不幸的是,大型生产站点必须要等到 esbuild 变得稳定。在此期间,它只是很好地增加了一些速度,让你在项目中的 bundling 变得更快。

esbuild 快如闪电的速度对于你正在做的任何工作来说都是一种奖励。减少等待构建运行的时间,对开发者的体验总是有好处的! 考虑到这一点,如果你是在做快速应用的原型,你可能会想要从比 esbuild 更高级的东西开始--否则,在获得我们期望的 JavaScript 中的便利之前,你需要花一些时间引入依赖项并配置你的环境生态系统。另外,如果你想尽可能地减小 bundle 包的大小,你可能会想使用 Rollupterser ,它们会产生略小的 bundle 大小。

设置

我决定以一种幼稚的方式在 esbuild 中启动一个 React 项目:npm安装 esbuild、ReactReactDOM。我创建了一个 src/code秘密花园.jsx 文件和一个 dist/index.html 文件。然后,我使用下面的命令将app编译成一个 dist/bundle.js 文件。

代码语言:javascript复制
./node_modules/.bin/esbuild src/code秘密花园.jsx --bundle --platform=browser --outfile=dist/bundle.js

当我在浏览器中打开 index.html 时,我遇到了 "白屏 "和 "Uncaught ReferenceError: process is not defined " 的控制台错误。文档和CLI都准确地解释了你需要做什么来避免这种情况,但对于初学者来说,这可能有点 "捉襟见肘",因为在 bundling React 时,它需要一个额外的参数。

代码语言:javascript复制
--define:process.env.NODE_ENV="production"

或者,如果你在 npm 脚本中包含了 esbuild ,就像这样写来转义引号。

代码语言:javascript复制
--define:process.env.NODE_ENV=\"production\"

任何绑定到浏览器的需要 node 环境变量的库都需要这个 define 参数。Vue 2.0 也需要这些参数。你在使用 Preact 时不会有同样的问题,因为它不需要任何环境变量,而且默认情况下已经为浏览器准备好了。

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

用法

esbuild 为开发服务器提供了一个 -serve 的选项。它绕过了文件系统,直接从内存中为模块提供服务,确保浏览器不会提取旧版本的模块。然而,它不包括实时/热重载,所以你会发现自己在保存后要刷新浏览器,这不是一个良好的体验。

我决定使用新发布的 watch 功能.这告诉 esbuild 在每次保存源文件时重新编译代码。但是我们仍然需要一个服务器来查看我们保存的变化。我们可以拉入一个开发服务器包,比如 Luke Jacksonservor

代码语言:javascript复制
npm install servor --save-dev

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

代码语言:javascript复制
// 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,所以它肯定不是一台顶级的机器。

如果你需要一个带有实时重载和一些 React 默认值的预配置 esbuild 版本,你可以克隆这个 repo

https://github.com/Elliotclyde/esbuild-react-starter

支持的文件

如果这是你的风格,esbuild 可以在 JavaScript 中导入 CSS 。它将会把CSS编译成一个输出文件,名字和你的主输出 JavaScript 文件一样。它还可以默认打包 CSS @import 语句。目前还没有对CSS模块的支持,但有计划。

用于 esbuild 的插件社区正在不断壮大。例如,Vue单文件组件和 Svelte 组件都有可用的插件。

esbuild 可以使用 JSON 文件,并且可以将它们 bundleJavaScript 模块中,无需任何配置。

它还可以用 JavaScript 导入图片,可以选择将图片转换为数据URL或复制到输出文件夹中。这种行为在默认情况下并没有启用,但你可以在你的 esbuild 配置对象中添加以下内容来启用这两个选项。

代码语言:javascript复制
loader: { '.png': 'dataurl' } // Converts to data url in JS bundle
loader: { '.png': 'file' } // Copies to output folder

代码拆分似乎是一项正在进行中的工作,但大多数情况下是以ESM输出格式进行的,而且看起来确实是项目的优先级。另外值得一提的是,tree-shakingesbuild 默认内置的,无法关闭。

生产构建

在 esbuild 命令中使用 "minify "和 "bundle " 选项不会创建一个像 Rollup/Terser 流水线一样小的 bundle 。这是因为 esbuild 牺牲了一些bundle大小的优化来尽可能少的通过你的代码。然而,根据你的项目,这种差异可能是微不足道的,但对于bundle速度的提高来说是值得的。在我的 Snap Shot 应用程序的克隆中,esbuild 创建了一个177KB的包,这比使用 rollupterserVite 产生的165KB多不了多少。

总结

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

Snowpack

Snowpack 是由 SkypackPika 的创造者开发的一款构建工具。它提供了一个很棒的开发服务器,并且是以 "非打包式开发 "的理念创建的。

引用文档中的一句话 "你应该能够使用一个打包程序,因为你想要,而不是因为你需要。"

默认情况下, Snowpack 的构建步骤并没有将文件打包到一个单一的包中,而是提供了在浏览器中运行的非打包esmodules。实际上 esbuild 是作为一个依赖关系包含在其中的,但我们的想法是使用 JavaScript 模块,只有在需要时才与 esbuild 打包。

Snowpack 有一些非常精巧的文档,包括一个与J avaScript 框架一起使用的指南列表,以及一堆模板。有些指南还在不断完善中,但其他的指南,比如针对 React 的指南,就很不错,很清晰。看起来 Snowpack 也把 Svelte 当做一等公民来对待。实际上,我第一次听说 Snowpack 是在2020年Svelte峰会上 Rich Harris 的 "未来主义Web开发 "演讲中。也就是说,即将推出的 Svelte 元框架 SvelteKit 本来应该由 Snowpack 提供支持,但后来改用了 Vite(我们接下来会对其进行评测)。

使用案例

如果你想在非打包部署上加倍努力,Snowpack 是个不错的选择。你可能会用少量的模块来编写源代码,这就意味着你不会用非捆绑构建来创建一个大的请求瀑布。如果你不需要额外的复杂性和技术债务,那么 Snowpack 是一个很好的选择。一个很好的用例是,如果你正在增量地将前端框架采用到服务器渲染或静态的应用程序中。你可以从node生态系统中获得尽可能少的工具,但你仍然会得到声明式前端框架的好处。

其次,我认为 Snowpackesbuild 的一个很好的封装器。如果你想尝试 esbuild,但同时又想拥有一个开发服务器和预先编写的前端框架模板,那么选择 Snowpack 是不会错的。在 Snowpack 配置的构建步骤中启用 esbuild,你就可以了。

就目前的情况来看,我认为 Snowpack 不会是像 create-react-app 这样的零配置工具的最佳替代品,因为如果你有一个大的应用,需要一个超级花哨的优化生产就绪的构建步骤,你就需要自己导入插件并配置它们。

设置

让我们通过命令行来启动Snowpack的项目。

代码语言:javascript复制
mkdir snowpackproject
cd snowpackproject
npm init #fill with defaults 
npm install snowpack

现在,让我们在package.json中添加以下内容。

代码语言:javascript复制
// package.json
"scripts": {
  "start": "snowpack dev",
  "build": "snowpack build"
},

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

代码语言:javascript复制
// Mac or Linux
touch snowpack.config.js
// Windows
new-item snowpack.config.js

我认为 Snowpack 最神奇的地方在于在配置文件中设置一个看似无害的键值对。例如,把这个粘贴到配置文件中。

代码语言:javascript复制
// snowpack.config.js
module.exports = {
  packageOptions: {
    "source": "remote",
  }
};

source: remote 启用了一种叫做流式导入的东西。通过流式导入使 Snowpack 能够绕过npm安装,将裸导入(例如,从import React from‘ React’)转换为 Skypack 的CDN导入。

继续前进,让我们创建一个 index.html 文件。

代码语言:javascript复制
<!--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>

最后,我们将添加一个 code秘密花园.jsx 文件。

代码语言:javascript复制
// code秘密花园.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

注意,我们在任何阶段都没有安装 ReactReactDOM 的npm。但如果我们像这样启动 Snowpack 开发者服务器。

代码语言:javascript复制
./node_modules/.bin/snowpack dev

我们的应用程序还能用。

Snowpack 不是从 nodemodules 文件夹中提取,而是从 Skypack 中提取npm包,Skypack 是一个托管 npm 注册表的CDN,它是预先优化的,可以在浏览器中工作。然后,Snowpack 将它放在一个 ./snowpack/pkg URL中。

用法

这离基于 Node/npm 的工作流还有很大的差距。我们实际上看到的是一个新的基于 CDN/JavaScript 模块的工作流。

然而,如果我们的应用按原样运行生产构建,Snowpack 会抛出一个错误。这是因为它需要知道在构建时要使用哪个版本的 ReactReactDOM 。你可以通过一个 snowpack.deps.json 来解决这个问题,它可以通过运行下面的程序自动创建。

代码语言:javascript复制
./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、ChaiReact Testing Library,总体的 Node 依赖包重达 80MB

代码语言:javascript复制
npx create-snowpack-app my-react-project --template @snowpack/app-template-react

支持的文件

支持JSX,但同样,默认情况下只支持 .jsx 文件。Snowpack 会自动检测是使用 React 还是 Preact ,并据此决定使用哪种渲染函数来进行JSX转换。但是,如果我们想进一步定制JSX,就需要通过他们的插件引入 Babel 。还有一个 Snowpack 插件可以用于 Vue 单文件组件,当然也可以用于 Svelte 组件。此外,Snowpack 还可以编译 TypeScript ,但对于类型检查,我们需要 TypeScript 插件。

CSS可以导入到 JavaScript 中,并在运行时被扔到文档 <head>中。只要CSS模块的扩展名为 .module.css ,也支持开箱即用的 scoping

导入的JSON文件将被强制转换为一个 JavaScript模块中,并以对象作为默认导出。Snowpack 支持图片,并将其复制到生产文件夹中。为了配合它的非打包理念,Snowpack不将图像作为数据URL纳入捆绑中。

生产构建

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

这很好用,但对于生产来说并不是很好,因为如果源码被分割成很多文件,可能会引起大量的请求。在 Snap Shot 应用中,我最终得到了 184KB 的源文件,然后又从 Skypack 中请求了 105KB 的依赖关系,这就造成了一个非常大的请求。

然而,Snowpackesbuild 作为一个依赖项,我们可以通过在Snowpack配置中添加一个 "optimization "对象,使 esbuild 能够打包、最小化和编译我们的代码。

代码语言:javascript复制
// snowpack.config.js
module.exports = {
  optimize: {
    bundle: true,
    minify: true,
    target: 'es2018',
  },

这样就可以使用 esbuild 提供的优化功能来运行代码,所以只要加入这些选项,我们就可以得到和之前使用 esbuild 一样的构建。

总结

Snowpack 通过功能齐全的开发服务器、详细的文档和易于安装的模板提供轻量级的开发人员体验。你可以决定是否要打包你的应用程序以及如何打包。如果你想要一个既能提供开发服务器又能提供更有意见的构建步骤的工具,你可能会想看看我们列表中的下一个工具 Vite

查看更多文章分类,戳这里

0 人点赞