查看原文
其他

JavaScript 新一代构建工具对比

ConardLi 译 大前端技术之路 2022-06-29

在过去的一年里,出现了一批新的开发者工具,它们正在紧跟过去几年主导前端开发的工具,包括 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 文件。

./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 时,它需要一个额外的参数。

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

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

--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

npm install servor --save-dev

然后我们就可以使用 esbuildJavascript API 作为服务器启动,同时运行 esbuildwatch 模式。让我们在项目的根目录创建一个名为 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"' },
  watchtrue,
});

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

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 配置对象中添加以下内容来启用这两个选项。

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的项目。

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 能够绕过npm安装,将裸导入(例如,从import React from‘ React’)转换为 Skypack 的CDN导入。

继续前进,让我们创建一个 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>

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

// 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 开发者服务器。

./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 来解决这个问题,它可以通过运行下面的程序自动创建。

./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

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 能够打包、最小化和编译我们的代码。

// snowpack.config.js
module.exports = {
  optimize: {
    bundletrue,
    minifytrue,
    target'es2018',
  },

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

总结

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

Vite

Vite 是由 Vue 的创始人尤雨溪开发的。esbuild 专注于构建步骤,Snowpack 专注于开发服务器,而 Vite 则同时提供了这两点:一个完整的开发服务器和一个使用 Rollup 的优化构建命令。

用例

如果你想要一个严肃的 create-react-appVue CLI 的竞争对手,Vite 是最接近的一个,因为它带有 batteries-included 的功能。快如闪电的开发服务器和零配置优化的生产构建意味着你可以在没有任何配置的情况下从零到生产。Vite 可用于小型项目或大型生产应用程序,Vite 的一个很好的用例是任何可观的单页应用。

你为什么不使用 ViteVite 是一个有成见的工具,你可能不同意它的意见。你可能不想使用 Rollup 来构建(我们一直在讨论 esbuild 有多快),或者你可能希望你的工具能够给你提供 Babel、eslintwebpack 加载器生态系统的全部功能。

另外,你想要零配置的服务器端渲染框架,你最好还是继续使用基于 webpack 的框架,比如 Nuxt.jsNext.js ,直到 Vite 服务器端渲染更加完善。

设置

ViteesbuildSnowpack 有更多的默认值。它的文档清晰而详细。我们得到了对 Vue 的全面支持,尤雨溪是创建者,所以 ViteVue 开发者来说无疑是一条必经之路。也就是说,Vite 可以和任何前端框架一起使用,甚至还提供了一个模板列表来帮助你入门。

用法

Vite 的开发服务器非常强大。Vite 通过 esbuild 将一个项目的所有依赖关系预先打包到一个单一的本地 JavaScript 模块中,然后用一个大量缓存的 HTTP 头来提供服务。这意味着在第一次页面加载后,不会在编译、服务或请求导入的依赖项上浪费时间。

Vite还提供了清晰的错误信息,打印出准确的代码块和行号,以排除故障。同样使用 Vite ,我在引入使用 node API 或传统格式的依赖项时没有任何问题。它们似乎都被塞进了一个浏览器可接受的 esmodule 中。

ViteReactVue 模板都引入了支持热模块替换的插件。Vue模板为一个用于单文件组件引入了Vue插件,以及一个用于 JSXVue 插件。React模板引入了 react-refresh 插件。无论哪种方式,都会给你提供热模块替换和客户端状态保存。

当然,它们增加了一些依赖性,包括Babel包,但是,在Vite中使用JSX时,Babel其实并不是必须的。默认情况下,JSX 的工作方式和 esbuild 一样--它转换为 React.createElement。它不会自动导入 React,但它的行为可以被配置。

同时,Vite 并不像 Snowpackwmr 那样支持流式导入。这意味着要像往常一样安装npm的依赖关系。

一个很酷的事情是,Vite 包含了对服务器端渲染的实验性支持。挑选你所选择的框架,并生成直接运到客户端的静态HTML。目前,看起来我们需要自己构建这个架构,但这看起来还是一个很好的机会,元框架可以建立在 Vite 之上。尤雨溪已经有一个名为 VitePress 的作品正在进行中,它是 VuePress 的替代品,具有使用Vite的优点。而 Sveltekit 也已经将Vite加入了依赖列表。看来CSS代码拆分收录也是 Sveltekit 改用Vite的原因之一。

支持的文件

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

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

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

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

生产构建

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

该构建包含了我们所期望的 Rollup 特性:打包、最小化和 tree shaking 。但我们也得到了一些额外的功能,比如代码分割动态导入和所谓的 "异步分块加载",这是一种花哨的说法,即如果我们请求导入另一个模块的 JavaScript 模块,构建将被预先优化,以同时加载这两个模块(异步)。

Snap Shot 应用运行Vite的默认构建,最终得到了一个5KB的 JavaScript 文件和一个160KB的JavaScript文件(总共165KB),项目中的所有CSS都被自动最小化为一个2.71KB的小文件。

总结

Vite 的性质使其成为我们当前工具的严重竞争对手。许多工作已经完成,使开发人员的体验真正无缝,并使生产就绪的构建开箱即用。

wmr

和 Vite 一样,wmr 也是另一个成见的构建工具,它同时提供了开发服务器和构建步骤。它是由 Preact 的创建者 Jason Miller 打造的,所以对于 Preact 开发者来说,这绝对是一条幸福的道路。Jason Miller 在做客 JS Party 播客时解释了wmr背后的思路。

Preact 很小,如果你想做一个轻量级的项目,它真的很好。我们的工具在哪里呢?我们有一个基于 webpack 的工具,在生产中被一堆高大上的网站所使用,但那是重量级的工具。原型开发工具在哪里?那是一方面。另一只手是我和一群碰巧在 Preact 团队里的人;我们已经在打包器生态系统的边缘徘徊了一段时间,鞭策人们,试图在一个方向上达成共识,我们可以朝着这个方向前进,以进一步推进这个编写现代代码和发布现代代码的想法。

这告诉我们,wmr 就是要编写和发布现代化的代码,在项目中实现更轻的工具。

你可能想知道 wmr 代表什么?什么也不知道!wmr是什么意思?"Web Modules Runtime "和 "Wet Module Replacement "这两个名字被浮出水面,但这是一个假的缩写,类似于 npm

wmrPreact 一样采用了无情的 bundle size purging,所以它的体积很小--重量只有 2.6 MB--而且完全不包含任何 npm 依赖。不过,它还是设法打包了一大堆非常棒的功能,包括一个热模块替换开发服务器和一个优化的生产构建。

用例

如果我想用 Preact 尽快创建一个原型,我会用 wmr 。没有任何配置,下载只需要几秒钟。感觉就像在使用一个超强的静态文件服务器。通过TypeScript、优化的构建步骤和静态HTML渲染,wmr提供了发布中小型应用程序所需的一切。它的小尺寸也非常适合快速试用一个库或演示一个想法。

如果你不使用 Preact、Reactvanilla JavaScript,wmr可能不是你的工具。Preact 团队还没有为其他框架提供模板。文档也没有我们看过的其他工具那么详细。这意味着你离开快乐的道路越远,你就会越深入地挖掘源头。所以,如果需要大量的定制,我不能推荐它。

设置

如果你使用的是Preact,除了快速安装npm之外,完全不需要任何设置。使用 React with wmr 而不是 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(from code)
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 插件的构建步骤。docs 上有越来越多的 wmr 专用例子,包括一个对HTML进行最小化的插件,还有一个基于文件系统的路由功能。

wmr 支持不同的框架,但没有任何预先构建的模板。而且一开始我发现配置 JSX 变换相当困难。说到这里,Jason 已经确认有计划让JJSX变得更可配置,而且 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>

现在你可以在你的index.js文件中写一个Preact hello world。

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

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

node_modules/.bin/wmr

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

wmr 在转换 JSX 时使用了一个叫 htm 的工具,它提供了一些很棒的好处。比方说,我们在 wmr 中使用 Preact 写一个计数器,却犯了一个错误。

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);

countonClick 处理函数中拼写错误,所以运行这个函数会出现错误。通常情况下,我们必须依靠我们的工具和 source map 来收集关于错误所在的信息,但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 " 面板,我们应该会看到一个脚本,它与源代码在编辑器中的样子几乎相同。

通过这种方式,我们就可以正确地调查错误在浏览器中的位置,而不必使用 source map。当然,这个具体的例子是很造作的,但你可以看到这可能是非常有用的,因为这意味着wmr在你的开发环境中不需要 source map

wmr 默认支持流式导入,所以裸露的导入将从npm注册表中拉下来。这是通过一个复杂的过程来完成的,这个过程会检查 npm 包中的所有源码,删除所有的测试和元数据,并将其转换为一个单一的本地 JavaScript 导入。与 Snowpack 类似,可以在不使用 npm 安装任何东西的情况下构建一个复杂的应用程序。事实上,wmr 是第一个支持这种想法的工具。

支持的文件

至于 wmr 支持的其他类型的文件,CSS 文件可以用 JavaScript 导入,CSS模块也支持。

Vue单文件组件和Svelte组件都没有内置支持。不过,wmr 的构建步骤可以和 Rollup 插件一起使用,开发服务器也可以配置Polka/Express中间件,所以可以用这些来将导入的文件转换成 VueSvelte 组件。事实上,我为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 提供了一个生产构建步骤,包括打包、小型化和 tree-shaking,而不需要任何额外的依赖。看了一下 wmr 的源码,似乎在引擎盖下使用了rollup和terser,wmr包中包含了这些的 minified 版本。Snap Shot 应用的wmr捆绑包是164KB,所以它创建的捆绑包只比Vite创建的两个 JavaScript 文件的总大小小一点点。

还有一种方法可以将wmr配置为这样一种方式,它使用 preact-iso 在浏览器上将一个应用程序渲染为静态 HTML 并加工。这意味着wmr可以作为 Preact 的元框架使用,类似于 Next.js

总结

我喜欢使用 wmr 来为 ReactPreact 应用做原型的体验。使用一个小得离谱但却能提供开发者便利的工具,接近于匹配重量级打包器,感觉很棒。

特征比较

我们刚刚分析了很多地方! 与其在这篇文章中上下滚动比较结果,我已经将所有内容汇编在这里,以查看这些工具并排时的堆叠情况。我甚至为我们没有明确提到的特性添加了额外的比较。

用例

设置

开发服务器

生产构建

其他特性

最后

我很高兴能够用我们刚刚看到的所有工具来构建 JavaScript 应用程序。无论我们是在编写一个小型的辅助项目还是一个大型的网站,所有这些工具都能加快反馈循环,提高生产力。它们已经打开了大门,问我们在 JavaScript 生态系统中需要什么,以及我们是否可以开始失去传统模块和浏览器带来的麻烦。

这些工具将通过提供一个更精简、更快速的开发者环境,在编写的代码和运行在浏览器中的代码之抽象更少,从而降低了新开发人员的进入门槛。

如果你已经厌倦了等待下载依赖和运行构建步骤,我建议你尝试一下这种新一代的工具。

其他的 JavaScript 新工具

  • Rome – 一个完整的工具链,包括 linting、编译、捆绑、测试运行和格式化
  • SWC – 基于 RustJavaScript/TypeScript 编译器
  • DenoJavaScriptTypeScript 的运行时(类似于 Node.js

文中如有错误,欢迎在留言区留言,如果这篇文章帮助到了你,欢迎点赞、在看和关注。

上面介绍的工具,你觉得哪个最好用呢?

本文译自:

https://css-tricks.com/comparing-the-new-generation-of-build-tools/


- EOF -

推荐阅读  点击标题可跳转

1、一招减少 JavaScript 代码量

2、图文并茂讲清楚 JavaScript 内存管理

3、11 个让你惊叹的罕见 JavaScript One-Liner


觉得本文对你有帮助?请分享给更多人

关注「大前端技术之路」加星标,提升前端技能

点赞和在看就是最大的支持❤️

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存