JavaScript 新一代构建工具对比
在过去的一年里,出现了一批新的开发者工具,它们正在紧跟过去几年主导前端开发的工具,包括 webpack、Babel、Rollup、Parcel、create-react-app
。
这些新的工具并不是为了完成完全相同的功能而设计的,每个工具都有不同的目标和功能。尽管存在差异,但这些工具有一个共同的目标:改善开发者体验
。
概览
esbuild
Snowpack
Vite
wmr
Feature comparison
Wrapping up
具体来说,我想对每一个进行评估,概述它们的作用,为什么我们需要它们,以及它们的使用案例。比较并不总是公平的,我们在这篇文章中看到的东西也不是直接的竞争对手。事实上,Snowpack
和 Vite
在某些任务中都使用了 esbuild
。
我们的目标更多的是为了更好地了解运行任务的开发者工具的格局,让我们的工作更轻松。通过这种方式,我们就能看到有哪些选择,以及它们是如何配合的,这样我们就能在需要的时候做出最好的选择。
当然,我分析的所有的这些都会受到我使用 React
和 Preact
的经验的影响。我对这些框架库比较熟悉,但我也会关注它们对其他前端框架的支持。
为啥这些工具现在都出现了?
在某种程度上,我认为这些工具的到来是对 JavaScript
工具疲劳的一种反应。
Snowpack、Vite
和 wmr
都用到了浏览器中的原生 JavaScript
模块。早在 2018
年,Firefox 60
发布时默认启用了 ECMAScript 2015
的 Module
。此后,各大浏览器引擎都支持原生 JavaScript
模块。
Node.js
也在2019年11月推出了原生 JavaScript
模块。在2021年的今天,我们还在寻找原生 JavaScript
模块能够带来哪些新的可能性。
这些工具和现有的工具有什么不同?
无论我们在开发服务器上使用 webpack、Rollup
还是 Parcel
,工具都会从我们的源代码和 node_modules
文件夹中把我们的整个代码库打包在一起,通过构建过程运行这些代码,比如 Babel、TypeScript
或 PostCSS
,然后将打包的代码推送到我们的浏览器上。这一切都需要花费大量的工作,并且会使开发服务器在更大的代码库中慢慢爬行,甚至在所有的工作都用于缓存和优化之后也是如此。
Snowpack、Vite
和 wmr
开发服务器则不采用这种模式。相反,它们会等到浏览器找到一个 import
语句,并为模块发出 HTTP
请求。只有在这个请求发出后,该工具才会对请求的模块和模块导入树中的任何叶节点应用转换,然后将这些转换提供给浏览器。这大大加快了速度,因为在推送到开发服务器的过程中减少了工作。
你会注意到描述中缺少了 esbuild
。它首先是一个 bundler
程序。它并不像其他工具那样绕开 bundler
。相反,esbuild
通过避免昂贵的转换、利用并行化和使用Go语言来快速处理代码。
实验
我从 React
文档中选取了一个示例应用,并使用文中所提到的每个工具重新构建了它。我选择的项目是 Yogita Verma
的 Snap Shot
。这里有一个原始 repo
的链接,还有一个我的repo
链接,里面有四个版本的 Snap Shot
,每个版本都使用不同的构建工具。
我们稍后会比较每个构建步骤的输出。重新构建这个应用程序,让我可以测试开发人员将一些相当标准的 React
依赖项添加到工具(包括 React Router
和 axios
)中的体验。
原始 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,estrella
和 Snowpack
,它们的构建步骤使用 esbuild
。
esbuild
是非常新的。它还没有达到 1.0
版本,还没有完全准备好用于生产使用 — 但它已经不远了。它为你提供了直观的 JavaScript
和带有智能默认值的命令行 API
。
用例
esbuild
完全改变了 bundler
的游戏规则。它将在大型代码库中发挥最大的作用,因为 esbuild
和 node
打包器之间的速度差异会成倍增加。当 esbuild
达到 1.0
的时候,它在大型生产站点中会非常有用,将为团队节省大量等待构建完成的时间。
不幸的是,大型生产站点必须要等到 esbuild
变得稳定。在此期间,它只是很好地增加了一些速度,让你在项目中的 bundling
变得更快。
esbuild
快如闪电的速度对于你正在做的任何工作来说都是一种奖励。减少等待构建运行的时间,对开发者的体验总是有好处的! 考虑到这一点,如果你是在做快速应用的原型,你可能会想要从比 esbuild
更高级的东西开始--否则,在获得我们期望的 JavaScript
中的便利之前,你需要花一些时间引入依赖项并配置你的环境生态系统。
另外,如果你想尽可能地减小 bundle
包的大小,你可能会想使用 Rollup
和 terser
,它们会产生略小的 bundle
大小。
设置
我决定以一种幼稚的方式在 esbuild
中启动一个 React
项目:npm安装 esbuild、React
和 ReactDOM
。我创建了一个 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 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,所以它肯定不是一台顶级的机器。
如果你需要一个带有实时重载和一些 React
默认值的预配置 esbuild
版本,你可以克隆这个 repo
。
https://github.com/Elliotclyde/esbuild-react-starter
支持的文件
如果这是你的风格,esbuild
可以在 JavaScript
中导入 CSS
。它将会把CSS编译成一个输出文件,名字和你的主输出 JavaScript
文件一样。它还可以默认打包 CSS @import
语句。目前还没有对CSS模块的支持,但有计划。
用于 esbuild
的插件社区正在不断壮大。例如,Vue单文件组件和
Svelte
组件都有可用的插件。
esbuild
可以使用 JSON
文件,并且可以将它们 bundle
到 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
流水线一样小的 bundle
。这是因为 esbuild
牺牲了一些bundle
大小的优化来尽可能少的通过你的代码。然而,根据你的项目,这种差异可能是微不足道的,但对于bundle
速度的提高来说是值得的。
在我的 Snap Shot
应用程序的克隆中,esbuild
创建了一个177KB的包,这比使用 rollup
和 terser
的 Vite
产生的165KB多不了多少。
总结
esbuild是一个非常强大的工具。但如果你习惯于零配置的设置,那可能会很困难。如果你需要更多,那么你可能想看看下一个工具,基于esbuild的Snowpack。
Snowpack
Snowpack
是由 Skypack
和 Pika
的创造者开发的一款构建工具。它提供了一个很棒的开发服务器,并且是以 "非打包式开发 "的理念创建的。
引用文档中的一句话 "你应该能够使用一个打包程序,因为你想要,而不是因为你需要。"
默认情况下, Snowpack
的构建步骤并没有将文件打包到一个单一的包中,而是提供了在浏览器中运行的非打包esmodules
。实际上 esbuild
是作为一个依赖关系包含在其中的,但我们的想法是使用 JavaScript
模块,只有在需要时才与 esbuild
打包。
Snowpack
有一些非常精巧的文档,包括一个与J avaScript
框架一起使用的指南列表,以及一堆模板。有些指南还在不断完善中,但其他的指南,比如针对 React
的指南,就很不错,很清晰。看起来 Snowpack
也把 Svelte
当做一等公民来对待。
实际上,我第一次听说 Snowpack
是在2020年Svelte峰会上 Rich Harris
的 "未来主义Web开发 "演讲中。也就是说,即将推出的 Svelte
元框架 SvelteKit
本来应该由 Snowpack
提供支持,但后来改用了 Vite
(我们接下来会对其进行评测)。
使用案例
如果你想在非打包部署上加倍努力,Snowpack
是个不错的选择。你可能会用少量的模块来编写源代码,这就意味着你不会用非捆绑构建来创建一个大的请求瀑布。
如果你不需要额外的复杂性和技术债务,那么 Snowpack
是一个很好的选择。一个很好的用例是,如果你正在增量地将前端框架采用到服务器渲染或静态的应用程序中。你可以从node生态系统中获得尽可能少的工具,但你仍然会得到声明式前端框架的好处。
其次,我认为 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
能够绕过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
注意,我们在任何阶段都没有安装 React
或 ReactDOM
的npm。但如果我们像这样启动 Snowpack
开发者服务器。
./node_modules/.bin/snowpack dev
我们的应用程序还能用。
Snowpack
不是从 nodemodules
文件夹中提取,而是从 Skypack
中提取npm包,Skypack
是一个托管 npm
注册表的CDN,它是预先优化的,可以在浏览器中工作。然后,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
依赖包重达 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
的依赖关系,这就造成了一个非常大的请求。
然而,Snowpack
将 esbuild
作为一个依赖项,我们可以通过在Snowpack配置中添加一个 "optimization "对象,使 esbuild
能够打包、最小化和编译我们的代码。
// snowpack.config.js
module.exports = {
optimize: {
bundle: true,
minify: true,
target: 'es2018',
},
这样就可以使用 esbuild
提供的优化功能来运行代码,所以只要加入这些选项,我们就可以得到和之前使用 esbuild
一样的构建。
总结
Snowpack
通过功能齐全的开发服务器、详细的文档和易于安装的模板提供轻量级的开发人员体验。你可以决定是否要打包你的应用程序以及如何打包。如果你想要一个既能提供开发服务器又能提供更有意见的构建步骤的工具,你可能会想看看我们列表中的下一个工具 Vite
。
Vite
Vite
是由 Vue
的创始人尤雨溪开发的。esbuild
专注于构建步骤,Snowpack
专注于开发服务器,而 Vite
则同时提供了这两点:一个完整的开发服务器和一个使用 Rollup
的优化构建命令。
用例
如果你想要一个严肃的 create-react-app
或 Vue CLI
的竞争对手,Vite
是最接近的一个,因为它带有 batteries-included
的功能。快如闪电的开发服务器和零配置优化的生产构建意味着你可以在没有任何配置的情况下从零到生产。Vite
可用于小型项目或大型生产应用程序,Vite
的一个很好的用例是任何可观的单页应用。
你为什么不使用 Vite
?Vite
是一个有成见的工具,你可能不同意它的意见。你可能不想使用 Rollup
来构建(我们一直在讨论 esbuild
有多快),或者你可能希望你的工具能够给你提供 Babel、eslint
和 webpack
加载器生态系统的全部功能。
另外,你想要零配置的服务器端渲染框架,你最好还是继续使用基于 webpack
的框架,比如 Nuxt.js
和 Next.js
,直到 Vite
服务器端渲染更加完善。
设置
Vite
比 esbuild
和 Snowpack
有更多的默认值。它的文档清晰而详细。我们得到了对 Vue
的全面支持,尤雨溪是创建者,所以 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
之上。尤雨溪已经有一个名为 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
。
wmr
和 Preact
一样采用了无情的 bundle size purging
,所以它的体积很小--重量只有 2.6 MB
--而且完全不包含任何 npm
依赖。不过,它还是设法打包了一大堆非常棒的功能,包括一个热模块替换开发服务器和一个优化的生产构建。
用例
如果我想用 Preact
尽快创建一个原型,我会用 wmr
。没有任何配置,下载只需要几秒钟。感觉就像在使用一个超强的静态文件服务器。通过TypeScript、优化的构建步骤和静态HTML渲染,wmr提供了发布中小型应用程序所需的一切。它的小尺寸也非常适合快速试用一个库或演示一个想法。
如果你不使用 Preact、React
或 vanilla 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);
count
在 onClick
处理函数中拼写错误,所以运行这个函数会出现错误。通常情况下,我们必须依靠我们的工具和 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中间件,所以可以用这些来将导入的文件转换成 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
提供了一个生产构建步骤,包括打包、小型化和 tree-shaking
,而不需要任何额外的依赖。看了一下 wmr
的源码,似乎在引擎盖下使用了rollup和terser,wmr包中包含了这些的 minified
版本。Snap Shot
应用的wmr捆绑包是164KB,所以它创建的捆绑包只比Vite创建的两个 JavaScript
文件的总大小小一点点。
还有一种方法可以将wmr配置为这样一种方式,它使用 preact-iso
在浏览器上将一个应用程序渲染为静态 HTML 并加工。这意味着wmr可以作为 Preact
的元框架使用,类似于 Next.js
。
总结
我喜欢使用 wmr
来为 React
和 Preact
应用做原型的体验。使用一个小得离谱但却能提供开发者便利的工具,接近于匹配重量级打包器,感觉很棒。
特征比较
我们刚刚分析了很多地方! 与其在这篇文章中上下滚动比较结果,我已经将所有内容汇编在这里,以查看这些工具并排时的堆叠情况。我甚至为我们没有明确提到的特性添加了额外的比较。
用例
设置
开发服务器
生产构建
其他特性
最后
我很高兴能够用我们刚刚看到的所有工具来构建 JavaScript
应用程序。无论我们是在编写一个小型的辅助项目还是一个大型的网站,所有这些工具都能加快反馈循环,提高生产力。它们已经打开了大门,问我们在 JavaScript
生态系统中需要什么,以及我们是否可以开始失去传统模块和浏览器带来的麻烦。
这些工具将通过提供一个更精简、更快速的开发者环境,在编写的代码和运行在浏览器中的代码之抽象更少,从而降低了新开发人员的进入门槛。
如果你已经厌倦了等待下载依赖和运行构建步骤,我建议你尝试一下这种新一代的工具。
其他的 JavaScript 新工具
Rome
– 一个完整的工具链,包括linting
、编译、捆绑、测试运行和格式化SWC
– 基于Rust
的JavaScript/TypeScript
编译器Deno
–JavaScript
和TypeScript
的运行时(类似于Node.js
)
文中如有错误,欢迎在留言区留言,如果这篇文章帮助到了你,欢迎点赞、在看和关注。
上面介绍的工具,你觉得哪个最好用呢?
本文译自:
https://css-tricks.com/comparing-the-new-generation-of-build-tools/
- EOF -
觉得本文对你有帮助?请分享给更多人
关注「大前端技术之路」加星标,提升前端技能
点赞和在看就是最大的支持❤️