├── 2024
└── test.md
├── .gitignore
├── Blogs
├── ECMAScript-2021-the-final-feature-set.md
├── how-to-build-a-cli-with-node-js.md
├── images
│ ├── cn01.svg
│ ├── cn02-snowpack.svg
│ ├── cn03-vite.svg
│ ├── cn04-wmr.svg
│ └── cn05.png
├── javascript-what-is-the-meaning-of-this.md
└── simpread-comparing-the-new-generation-of-build-tools.md
├── Docs
└── 0000-vue3-ie11-support.md
├── README.md
├── React Conf 2018
├── README.md
├── React Today and Tomorrow - Part I.md
├── React Today and Tomorrow - Part II.md
├── React Today and Tomorrow and 90% Cleaner React With Hooks - YouTube.srt
├── React Today and Tomorrow part1.srt
├── React Today and Tomorrow part1_zh_CN.srt
├── React Today and Tomorrow part2.srt
├── React Today and Tomorrow part2_zh_CN.srt
└── React的今天和明天——第一部分.md
├── Vite-Open-Source-Friday
├── README.md
├── subtitle-en.srt
└── subtitle-zh.srt
└── crowdin.yml
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | *.log
4 | explorations
5 | TODOs.md
6 | dist/*.gz
7 | dist/*.map
8 | dist/vue.common.min.js
9 | test/e2e/reports
10 | test/e2e/screenshots
11 | coverage
12 | RELEASE_NOTE*.md
13 | dist/*.js
14 | packages/vue-server-renderer/basic.js
15 | packages/vue-server-renderer/build.js
16 | packages/vue-server-renderer/server-plugin.js
17 | packages/vue-server-renderer/client-plugin.js
18 | packages/vue-template-compiler/build.js
19 | .vscode
20 | React Conf 2018/test.js
21 | React Conf 2018/test1.js
22 | Blogs/how-to-build-a-cli-with-node-js.md
23 |
--------------------------------------------------------------------------------
/2024/test.md:
--------------------------------------------------------------------------------
1 | # test
2 | A highly customizable and feature-rich tabs component library for React, inspired by Google Chrome's sleek tab design.
3 | ## IntroDuction
4 |
--------------------------------------------------------------------------------
/Blogs/ECMAScript-2021-the-final-feature-set.md:
--------------------------------------------------------------------------------
1 | # ECMAScript 2021: 最终功能集确定
2 |
3 | > * 原文地址:[ECMAScript 2021: the final feature set](https://2ality.com/2020/09/ecmascript-2021.html)
4 | > * 原文作者:[Axel Rauschmayer](http://dr-axel.de/)
5 | > * 本文永久链接:[https://github.com/Ivocin/Translation/Blogs/ECMAScript-2021-the-final-feature-set.md](https://github.com/Ivocin/Translation/blob/master/Blogs/ECMAScript-2021-the-final-feature-set.md)
6 | > * 翻译、校对:[Ivocin](https://github.com/Ivocin/)
7 |
8 | **更新于 2021-03-09:** 今天,[ES2021 候选提案](https://github.com/tc39/ecma262/releases/tag/es2021-candidate-2021-03) 发布了其最终功能集的版本。如果它能够在今年 6 月的 ECMA 大会上通过,就会成为官方的标准。本文描述了有哪些新的内容。
9 |
10 | ## 1、ECMAScript 2021 的编辑
11 |
12 | 这个版本的编辑是:
13 |
14 | * [Jordan Harband](https://twitter.com/ljharb)
15 | * [Shu-yu Guo](https://twitter.com/_shu)
16 | * [Michael Ficarra](https://twitter.com/smooshMap)
17 | * [Kevin Gibbons](https://twitter.com/bakkoting)
18 |
19 | ## 2、关于 ECMAScript 的版本说明
20 |
21 | 注意,自从 [TC39 进程](https://exploringjs.com/impatient-js/ch_history.html#tc39-process)制定以来,ECMAScript 版本的重要性就降低了很多。现在真正重要的是提案处于哪个阶段:一旦提案到了第 4 阶段,那么它就可以使用了。但是即使这样,你仍然需要检查你的引擎是否支持该功能。
22 |
23 | ## 3、ES2021 功能(第 4 阶段提案)
24 |
25 | * [`String.prototype.replaceAll`](https://exploringjs.com/impatient-js/ch_regexps.html#replace-replaceAll) (Peter Marshall, Jakob Gruber, Mathias Bynens)
26 |
27 | * [`Promise.any()`](https://exploringjs.com/impatient-js/ch_promises.html#Promise.any) (Mathias Bynens, Kevin Gibbons, Sergey Rubanov)
28 |
29 | * WeakRefs (Dean Tribble, Mark Miller, Till Schneidereit, Sathya Gunasekaran, Daniel Ehrenberg) [[proposal](https://github.com/tc39/proposal-weakrefs)]
30 |
31 | * [Logical assignment operators](https://exploringjs.com/impatient-js/ch_operators.html#logical-assignment-operators) (Justin Ridgewell, Hemanth HM)
32 |
33 | * Underscores (`_`) as separators in [number literals](https://exploringjs.com/impatient-js/ch_numbers.html#numeric-separator-number-literals) 以及 [bigint literals](https://exploringjs.com/impatient-js/ch_bigints.html#numeric-separator-bigint-literals) (Sam Goto, Rick Waldron)
34 |
35 |
36 | ## 4、常见问题
37 |
38 | ### 4.1 阶段的含义是什么?
39 |
40 | 阶段是指 “TC39 进程“的成熟阶段。更多信息可以查看“JavaScript for impatient programmers” 中的[“TC39 进程” 部分](https://exploringjs.com/impatient-js/ch_history.html#tc39-process)。
41 |
42 | ### 4.2 [我最喜欢的提案功能] 现在怎么样了?
43 |
44 | 如果你想查看不同的提案功能现在处于什么阶段,请查阅 [ ECMA-262 GitHub 仓库的 README 文件](https://github.com/tc39/ecma262/blob/master/README.md)。
45 |
46 | ### 4.3 有官方的 ECMAScript 功能列表吗?
47 |
48 | 当然,TC39 仓库列出了 [已完成提案](https://github.com/tc39/proposals/blob/master/finished-proposals.md) 以及它们是在哪个 ECMAScript 版本被引入的说明。
49 |
50 | ## 5、ES2021 的免费书籍
51 |
52 | 以下书籍包括了到 ECMAScript 2021 的 JavaScript,并且可以免费在线阅读:
53 |
54 | * [“JavaScript for impatient programmers”](https://exploringjs.com/impatient-js/)
55 | * [“Deep JavaScript”](https://exploringjs.com/deep-js/)
--------------------------------------------------------------------------------
/Blogs/how-to-build-a-cli-with-node-js.md:
--------------------------------------------------------------------------------
1 | > * 原文地址:[How to build a CLI with Node.js](https://www.twilio.com/blog/how-to-build-a-cli-with-node-js)
2 | > * 原文作者:[DOMINIK KUNDEL](https://www.twilio.com/blog/author/dkundel)
3 | > * 本文永久链接:[https://github.com/Ivocin/Translation/Blogs/how-to-build-a-cli-with-node-js.md](https://github.com/Ivocin/Translation/Blogs/how-to-build-a-cli-with-node-js.md)
4 | > * 译者:[Ivocin](https://github.com/Ivocin/)
5 | > * 校对者:
6 |
7 | # 如何使用 Node.js 构建 CLI
8 |
9 | 
10 |
11 | Node.js 中内置的命令行界面(CLI)能够让你充分利用庞大的 Node.js 生态系统自动化执行重复性任务。感谢像 [`npm`](https://www.npmjs.com/) 和 [`yarn`](https://yarnpkg.com/) 这样的包管理器,它们可以在多个平台上轻松分发和使用。在本文中,我们将介绍为什么要编写 CLI、如何使用Node.js 来创建 CLI、一些实用的包以及如何发布你的新 CLI。
12 |
13 | ## 为什么使用 Node.js 创建 CLI
14 |
15 | Node.js 如此受欢迎的原因之一是其丰富的包生态系统,在 [`npm` 注册表](https://npmjs.com)中有超过 900,000 个包。通过使用 Node.js 编写 CLI,你可以利用这个生态系统,包括大量针对 CLI 的软件包。 其中:
16 |
17 | * [`inquirer`](http://npm.im/inquirer)、 [`enquirer`](http://npm.im/enquirer) 或者 `[prompts](https://npm.im/prompts)` :复杂的输入提示
18 | * [`email-prompt`](http://npm.im/email-prompt) :简化电子邮件输入提示
19 | * [`chalk`](http://npm.im/chalk) or `[kleur](https://npm.im/kleur)` :彩色输出
20 | * [`ora`](http://npm.im/ora):漂亮的加载状态动画
21 | * [`boxen`](http://npm.im/boxen):在输出周围绘制边框
22 | * [`stmux`](http://npm.im/stmux):提供 `tmux`(Terminal Multiplexing) 风格的 UI
23 | * [`listr`](http://npm.im/listr):进度列表
24 | * [`ink`](http://npm.im/ink):使用 React 构建 CLI
25 | * [`meow`](http://npm.im/meow) 或者 [`arg`](http://npm.im/arg):基础参数解析
26 | * [ `commander`](http://npm.im/commander) 和 [`yargs`](https://www.npmjs.com/package/yargs):复杂参数解析以及子命令支持
27 | * [`oclif`](https://oclif.io/) 由 Heroku 构建的可扩展 CLI 框架 (也可以选择 `[gluegun](https://infinitered.github.io/gluegun/#/)`)
28 |
29 | Additionally there are many convenient ways to consume CLIs published to `npm` from both `yarn` and `npm`. Take as an example `create-flex-plugin`, a CLI that you can use to bootstrap a plugin for [Twilio Flex](https://twilio.com/flex). You can install it as a global command:
30 | 此外,`yarn` 和 `npm` 都提供了许多方便的方法来使用发布到 `npm` 的 CLI。我们以 `create-flex-plugin` 为例,使用它可以创建 [Twilio Flex](https://twilio.com/flex) 插件。你可以将其安装为全局命令:
31 |
32 | ```
33 | # 使用 npm:
34 | npm install -g create-flex-plugin
35 | # 使用 yarn:
36 | yarn global add create-flex-plugin
37 | # 安装成功后你就可以使用了:
38 | create-flex-plugin
39 | ```
40 |
41 | 也可以将其作为项目的特定依赖:
42 |
43 | ```
44 | # 使用 npm:
45 | npm install create-flex-plugin --save-dev
46 | # 使用 yarn:
47 | yarn add create-flex-plugin --dev
48 | # 安装成功后命令在 ./node_modules/.bin/create-flex-plugin 路径
49 | # 或者使用 npm 的 npx 命令:
50 | npx create-flex-plugin
51 | # 也可以使用 yarn:
52 | yarn create-flex-plugin
53 | ```
54 |
55 | 事实上,即使没有提前安装 CLI,`npx` 命令也可以执行。只需运行 `npx create-flex-plugin` 命令,`npx` 会优先使用本地或全局安装的版本,如果找不到,它会将其下载到缓存中。
56 |
57 | 从 `npm` 6.1 版本开始,使用 `npm init` 和 `yarn` 支持使用名为`create-*` 的 CLI 来启动项目的方法。例如,对于我们的`create-flex-plugin`,我们真正需要调用的是:
58 |
59 | ```
60 | # 使用 Node.js
61 | npm init flex-plugin
62 | # 使用 Yarn:
63 | yarn create flex-plugin
64 | ```
65 |
66 | ## 创建你的第一个 CLI
67 |
68 | 如果你希望按照视频教程进行操作,[请查看我们在 YouTube 上的教程](https://www.youtube.com/watch?v=s2h28p4s-Xs).
69 |
70 | 现在我们介绍了你可能想要使用 Node.js 创建 CLI 的原因,现在让我们来构建一个 CLI。我们将在本教程中使用 `npm`,但是绝大部分都有相同作用的 `yarn` 命令。确保你在系统上安装了 [Node.js](https://nodejs.org/en/download/) 和 [`npm`](https://www.npmjs.com/)。
71 |
72 | 在本教程中,我们将创建一个 CLI,通过运行 `npm init @your-username/project`,创建一个你自己偏好的新项目。
73 |
74 | 通过如下命令创建一个新的 Node.js 项目:
75 |
76 | ```
77 | mkdir create-project && cd create-project
78 | npm init --yes
79 | ```
80 |
81 | 然后在项目的根目录中创建一个名为 `src/` 的目录,并在其中创建 `cli.js` 文件,代码如下:
82 |
83 | ```
84 | export function cli(args) {
85 | console.log(args);
86 | }
87 | ```
88 |
89 | 这将是我们稍后解析逻辑然后触发实际业务逻辑的部分。接下来,我们需要为 CLI 创建入口。在项目的根目录中创建一个新目录 `bin/`,并在其中创建一个名为 `create-project` 的新文件。将以下代码行放入其中:
90 |
91 | ```
92 | #!/usr/bin/env node
93 |
94 | require = require('esm')(module /*, options*/);
95 | require('../src/cli').cli(process.argv);
96 | ```
97 |
98 | 这个小代码片段一共做了两件事情。首先,我们引入了一个名为 `esm` 的模块,它可以让我们在其他文件中使用 `import`。这与构建 CLI 没有直接关系,但我们将在本教程中使用 [ES 模块](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import),`esm` 包允许我们这样做,而无需在没有支持的情况下转换 Node.js 版本。然后我们引入我们的 `cli.js` 文件并调用其暴露的 `cli` 函数,并传入 [`process.argv`](https://nodejs.org/api/process.html#process_process_argv) 参数,该参数是从命令行传递给该脚本的全部参数的数组。
99 |
100 | 在我们测试脚本之前,需要安装 `esm` 依赖,执行如下命令:
101 |
102 | ```
103 | npm install esm
104 | ```
105 |
106 | 我们还必须告知包管理器我们需要暴露的 CLI 脚本。通过在 `package.json` 中添加合适的条目来完成此操作。不要忘记更新 `description`、`name`、`keyword` 和 `main` 属性:
107 |
108 | ```
109 | {
110 | "name": "@your_npm_username/create-project",
111 | "version": "1.0.0",
112 | "description": "A CLI to bootstrap my new projects",
113 | "main": "src/index.js",
114 | "bin": {
115 | "@your_npm_username/create-project": "bin/create-project",
116 | "create-project": "bin/create-project"
117 | },
118 | "publishConfig": {
119 | "access": "public"
120 | },
121 | "scripts": {
122 | "test": "echo \"Error: no test specified\" && exit 1"
123 | },
124 | "keywords": [
125 | "cli",
126 | "create-project"
127 | ],
128 | "author": "YOUR_AUTHOR",
129 | "license": "MIT",
130 | "dependencies": {
131 | "esm": "^3.2.18"
132 | }
133 | }
134 | ```
135 |
136 | 我们来看 `bin` 这个属性,值为一个包含两个键值对的对象。它们定义了你的包管理器将要安装的 CLI 命令。在我们的例子中,我们为相同脚本注册了两条命令。一条命令使用我们自己用户名的 `npm` 作用域,另一条通用的 `create-project` 命令方便使用。
137 |
138 | 现在我们完成了这项工作,我们可以测试我们的脚本。要做到这一点,最简单的方法是使用 [`npm link`](https://docs.npmjs.com/cli/link.html) 命令。在项目内的终端中运行:
139 |
140 | ```
141 | npm link
142 | ```
143 |
144 | 这将当前项目链接到全局执行环境,因此在更新代码时无需重新运行它。运行 `npm link` 后,你就可以使用 CLI 命令了。试试运行:
145 |
146 | ```
147 | create-project
148 | ```
149 |
150 | 应该可以看到类似于如下的输出:
151 |
152 | ```
153 | [ '/usr/local/Cellar/node/11.6.0/bin/node',
154 | '/Users/dkundel/dev/create-project/bin/create-project' ]
155 | ```
156 |
157 | Note that both paths will be different for you depending on where your project lies and where you have Node.js installed. This array will be longer with every argument that you add to this. Try running:
158 |
159 | ```
160 | create-project --yes
161 | ```
162 |
163 | And the output should reflect the new argument:
164 |
165 | ```
166 | [ '/usr/local/Cellar/node/11.6.0/bin/node',
167 | '/Users/dkundel/dev/create-project/bin/create-project',
168 | '--yes' ]
169 | ```
170 |
171 | ## Parsing Arguments and Handling Input
172 |
173 | We are now ready to parse the arguments that are being passed to our script and we can start making sense of them. Our CLI will support one argument and a few options:
174 |
175 | * `[template]`: We'll support different templates out of the box. If this is not passed we'll prompt the user to select a template
176 | * `--git`: This will run `git init` to instantiate a new git project
177 | * `--install`: This will automatically install all the dependencies for the project
178 | * `--yes`: This will skip all prompts and go for default options
179 |
180 | For our project we'll use `inquirer` to prompt for missing values and the `arg` library to parse our CLI arguments. Install the missing dependencies by running:
181 |
182 | ```
183 | npm install inquirer arg
184 | ```
185 |
186 | Let's first write the logic that will parse our arguments into an `options` object that we can work with. Add the following code to your `cli.js`:
187 |
188 | ```
189 | import arg from 'arg';
190 |
191 | function parseArgumentsIntoOptions(rawArgs) {
192 | const args = arg(
193 | {
194 | '--git': Boolean,
195 | '--yes': Boolean,
196 | '--install': Boolean,
197 | '-g': '--git',
198 | '-y': '--yes',
199 | '-i': '--install',
200 | },
201 | {
202 | argv: rawArgs.slice(2),
203 | }
204 | );
205 | return {
206 | skipPrompts: args['--yes'] || false,
207 | git: args['--git'] || false,
208 | template: args._[0],
209 | runInstall: args['--install'] || false,
210 | };
211 | }
212 |
213 | export function cli(args) {
214 | let options = parseArgumentsIntoOptions(args);
215 | console.log(options);
216 | }
217 | ```
218 |
219 | Try running `create-project --yes` and you should see `skipPrompt` to turn to `true` or try passing another argument in like `create-project cli` and the `template` property should be set.
220 |
221 | Now that we are able to parse the CLI arguments, we'll need to add the functionality to prompt for the missing information as well as skip the prompt and resort to default arguments if the `--yes` flag is passed. Add the following code to your `cli.js` file:
222 |
223 | ```
224 | import arg from 'arg';
225 | import inquirer from 'inquirer';
226 |
227 | function parseArgumentsIntoOptions(rawArgs) {
228 | // ...
229 | }
230 |
231 | async function promptForMissingOptions(options) {
232 | const defaultTemplate = 'JavaScript';
233 | if (options.skipPrompts) {
234 | return {
235 | ...options,
236 | template: options.template || defaultTemplate,
237 | };
238 | }
239 |
240 | const questions = [];
241 | if (!options.template) {
242 | questions.push({
243 | type: 'list',
244 | name: 'template',
245 | message: 'Please choose which project template to use',
246 | choices: ['JavaScript', 'TypeScript'],
247 | default: defaultTemplate,
248 | });
249 | }
250 |
251 | if (!options.git) {
252 | questions.push({
253 | type: 'confirm',
254 | name: 'git',
255 | message: 'Initialize a git repository?',
256 | default: false,
257 | });
258 | }
259 |
260 | const answers = await inquirer.prompt(questions);
261 | return {
262 | ...options,
263 | template: options.template || answers.template,
264 | git: options.git || answers.git,
265 | };
266 | }
267 |
268 | export async function cli(args) {
269 | let options = parseArgumentsIntoOptions(args);
270 | options = await promptForMissingOptions(options);
271 | console.log(options);
272 | }
273 | ```
274 |
275 | Save the file and run `create-project` and you should be prompted with a template selection prompt:
276 |
277 | 
278 |
279 | And afterwards you'll be prompted with a question whether you want to initialize `git`. Once you selected both you should see output like this printed:
280 |
281 | ```
282 | { skipPrompts: false,
283 | git: false,
284 | template: 'JavaScript',
285 | runInstall: false }
286 | ```
287 |
288 | Try to run the same command with `-y` and the prompts should be skipped. Instead you'll immediately see the determined options output.
289 |
290 | 
291 |
292 | ## Writing the Logic
293 |
294 | Now that we are able to determine the respective options through prompts and command-line arguments, let's write the actual logic that will create our projects. Our CLI will write into an existing directory similar to `npm init` and it will copy all files from a `templates` directory in our project. We'll allow the target directory to be also modified via the options in case you want to re-use the same logic inside another project.
295 |
296 | Before we write the actual logic, create a `templates` directory in the root of our project and place two directories with the names `typescript` and `javascript` into it. Those are the lower-cased versions of the two values that we prompted the user to pick from. This post will use these names but feel free to use other names you'd like. Inside that directory place any `package.json` that you would like to use as the base of your project and any kind of files you want to have copied into your project. Our code will later simply copy those files into the new project. If you need some inspiration, you can check out my files at github.com/dkundel/create-project.
297 |
298 | In order to do recursive copying of the files we'll use a library called `ncp`. This library supports recursive copying cross-platform and even has a flag to force override existing files. Additionally we'll install `chalk` for colored output. To install the dependencies run:
299 |
300 | ```
301 | npm install ncp chalk
302 | ```
303 |
304 | We'll place all of our core logic into a `main.js` file inside the `src/` directory of our project. Create the new file and add the following code:
305 |
306 | ```
307 | import chalk from 'chalk';
308 | import fs from 'fs';
309 | import ncp from 'ncp';
310 | import path from 'path';
311 | import { promisify } from 'util';
312 |
313 | const access = promisify(fs.access);
314 | const copy = promisify(ncp);
315 |
316 | async function copyTemplateFiles(options) {
317 | return copy(options.templateDirectory, options.targetDirectory, {
318 | clobber: false,
319 | });
320 | }
321 |
322 | export async function createProject(options) {
323 | options = {
324 | ...options,
325 | targetDirectory: options.targetDirectory || process.cwd(),
326 | };
327 |
328 | const currentFileUrl = import.meta.url;
329 | const templateDir = path.resolve(
330 | new URL(currentFileUrl).pathname,
331 | '../../templates',
332 | options.template.toLowerCase()
333 | );
334 | options.templateDirectory = templateDir;
335 |
336 | try {
337 | await access(templateDir, fs.constants.R_OK);
338 | } catch (err) {
339 | console.error('%s Invalid template name', chalk.red.bold('ERROR'));
340 | process.exit(1);
341 | }
342 |
343 | console.log('Copy project files');
344 | await copyTemplateFiles(options);
345 |
346 | console.log('%s Project ready', chalk.green.bold('DONE'));
347 | return true;
348 | }
349 | ```
350 |
351 | This code will export a new function called `createProject` that will first check if the specified template is indeed an available template, by checking the `read` access (`fs.constants.R_OK`) using [`fs.access`](https://nodejs.org/api/fs.html#fs_fs_access_path_mode_callback) and then copy the files into the target directory using `ncp`. Additionally we'll log some colored output saying `DONE Project ready` when we successfully copied the files.
352 |
353 | Afterwards update your `cli.js` to call the new `createProject` function:
354 |
355 | ```
356 | import arg from 'arg';
357 | import inquirer from 'inquirer';
358 | import { createProject } from './main';
359 |
360 | function parseArgumentsIntoOptions(rawArgs) {
361 | // ...
362 | }
363 |
364 | async function promptForMissingOptions(options) {
365 | // ...
366 | }
367 |
368 | export async function cli(args) {
369 | let options = parseArgumentsIntoOptions(args);
370 | options = await promptForMissingOptions(options);
371 | await createProject(options);
372 | }
373 | ```
374 |
375 | To test our progress, create a new directory somewhere like `~/test-dir` on your system and run inside it the command using one of your templates. For example:
376 |
377 | ```
378 | create-project typescript --git
379 | ```
380 |
381 | You should see a confirmation that the project has been created and the files should be copied over to the directory.
382 |
383 | 
384 |
385 | Now there are two more steps we want our CLI to do. We want to optionally initialize `git` and install our dependencies. For this we'll use three more dependencies:
386 |
387 | * [`execa`](http://npm.im/execa) which allows us to easily run external commands like `git`
388 | * [`pkg-install`](http://npm.im/pkg-install) to trigger either `yarn install` or `npm install` depending on what the user uses
389 | * [`listr`](http://npm.im/listr) which let's us specify a list of tasks and gives the user a neat progress overview
390 |
391 | Install the dependencies by running:
392 |
393 | ```
394 | npm install execa pkg-install listr
395 | ```
396 |
397 | Afterwards update your `main.js` to contain the following code:
398 |
399 | ```
400 | import chalk from 'chalk';
401 | import fs from 'fs';
402 | import ncp from 'ncp';
403 | import path from 'path';
404 | import { promisify } from 'util';
405 | import execa from 'execa';
406 | import Listr from 'listr';
407 | import { projectInstall } from 'pkg-install';
408 |
409 | const access = promisify(fs.access);
410 | const copy = promisify(ncp);
411 |
412 | async function copyTemplateFiles(options) {
413 | return copy(options.templateDirectory, options.targetDirectory, {
414 | clobber: false,
415 | });
416 | }
417 |
418 | async function initGit(options) {
419 | const result = await execa('git', ['init'], {
420 | cwd: options.targetDirectory,
421 | });
422 | if (result.failed) {
423 | return Promise.reject(new Error('Failed to initialize git'));
424 | }
425 | return;
426 | }
427 |
428 | export async function createProject(options) {
429 | options = {
430 | ...options,
431 | targetDirectory: options.targetDirectory || process.cwd()
432 | };
433 |
434 | const templateDir = path.resolve(
435 | new URL(import.meta.url).pathname,
436 | '../../templates',
437 | options.template
438 | );
439 | options.templateDirectory = templateDir;
440 |
441 | try {
442 | await access(templateDir, fs.constants.R_OK);
443 | } catch (err) {
444 | console.error('%s Invalid template name', chalk.red.bold('ERROR'));
445 | process.exit(1);
446 | }
447 |
448 | const tasks = new Listr([
449 | {
450 | title: 'Copy project files',
451 | task: () => copyTemplateFiles(options),
452 | },
453 | {
454 | title: 'Initialize git',
455 | task: () => initGit(options),
456 | enabled: () => options.git,
457 | },
458 | {
459 | title: 'Install dependencies',
460 | task: () =>
461 | projectInstall({
462 | cwd: options.targetDirectory,
463 | }),
464 | skip: () =>
465 | !options.runInstall
466 | ? 'Pass --install to automatically install dependencies'
467 | : undefined,
468 | },
469 | ]);
470 |
471 | await tasks.run();
472 | console.log('%s Project ready', chalk.green.bold('DONE'));
473 | return true;
474 | }
475 | ```
476 |
477 | This will run `git init` whenever `--git` is passed or the user chooses `git` in the prompt and it will run `npm install` or `yarn` whenever the user passes `--install`, otherwise it will skip the task with a message informing the user to pass `--install` if they want automatic install.
478 |
479 | Give it a try by deleting your existing test folder first and creating a new one. Then run:
480 |
481 | ```
482 | create-project typescript --git --install
483 | ```
484 |
485 | You should see now both a `.git` folder in your folder indicating that `git` has been initialized and a `node_modules` folder with your dependencies that were specified in the `package.json` installed.
486 |
487 | 
488 |
489 | Congratulations you got your first CLI ready to go!
490 |
491 | 
492 |
493 | If you want to make your code consumable as an actual module so that others can reuse your logic in their code, we'll have to add an `index.js` file to our `src/` directory that exposes the content from `main.js`:
494 |
495 | ```
496 | require = require('esm')(module);
497 | require('../src/cli').cli(process.argv);
498 | ```
499 |
500 | ## What's Next?
501 |
502 | Now that you have your CLI code ready there are a few ways you can go from here. If you just want to use this yourself and don't want to share it with the world you can just keep on going along the path of using `npm link`. In fact try running `npm init project` and it should trigger your code.
503 |
504 | If you want to share your templates with the world either push your code to GitHub and consume it from there or even better push it as a scoped package to the `npm` registry with [`npm publish`](https://docs.npmjs.com/cli/publish). Before you do so, you should make sure to add a `files` key in your `package.json` to specify which files should be published.
505 |
506 | ```
507 | },
508 | "files": [
509 | "bin/",
510 | "src/",
511 | "templates/"
512 | ]
513 | }
514 | ```
515 |
516 | If you want to check which files will be published, run `npm pack --dry-run` and check the output. Afterwards use `npm publish` to publish your CLI. You can find my project under [`@dkundel/create-project`](http://npm.im/@dkundel/create-project) or try run `npm init @dkundel/project`.
517 |
518 | There's also lots of functionality that you can add. In my case I added some additional dependencies that will create a `LICENSE`, `CODE_OF_CONDUCT.md` and `.gitignore` file for me. You can [find the source code for it on GitHub](http://github.com/dkundel/create-project) or check out some of the libraries mentioned above for some additional functionality. If you have a library I didn't list and you believe it should totally be in the list or if you want to show me your own CLI, feel free to send me a message!
519 |
520 | * Email: [dkundel@twilio.com](mailto:dkundel@twilio.com)
521 | * Twitter: [@dkundel](https://twitter.com/dkundel?lang=en)
522 | * GitHub: [dkundel](https://github.com/dkundel)
523 | * [dkundel.com](https://dkundel.com/)
524 |
525 |
526 |
--------------------------------------------------------------------------------
/Blogs/images/cn01.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/Blogs/images/cn02-snowpack.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Blogs/images/cn03-vite.svg:
--------------------------------------------------------------------------------
1 |
16 |
--------------------------------------------------------------------------------
/Blogs/images/cn04-wmr.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/Blogs/images/cn05.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Ivocin/Translation/937be71e288e70f61c9eeab77fb34a9bf78b3e60/Blogs/images/cn05.png
--------------------------------------------------------------------------------
/Blogs/javascript-what-is-the-meaning-of-this.md:
--------------------------------------------------------------------------------
1 | # [译]JavaScript: 带你彻底搞懂 this
2 |
3 | > * 原文地址:[JavaScript: What is the meaning of this?](https://web.dev/javascript-this/)
4 | > * 原文作者:[Jake Archibald](https://web.dev/authors/jakearchibald/)
5 | > * 原文发布时间:2021-03-08
6 | > * 本文永久链接:[https://github.com/Ivocin/Translation/Blogs/javascript-what-is-the-meaning-of-this.md](https://github.com/Ivocin/Translation/blob/master/Blogs/javascript-what-is-the-meaning-of-this.md)
7 | > * 翻译、校对:[Ivocin](https://github.com/Ivocin/)
8 |
9 |
10 | 搞明白 JavaScript 中 `this` 的值有时候会很棘手,本文带你彻底搞懂 `this`。
11 |
12 | JavaScript 的 `this` 往往会成为许多笑话的笑柄,因为它相当复杂。然而,我发现很多开发人员为了避免处理 `this`,用了更加复杂和特定领域的处理。如果你对 `this` 还不熟悉,希望本文能帮助到你。下面进入我的 `this` 指南。
13 |
14 | 我将从最具体的情况开始,以最不具体的情况结束,本文的结构类似与一个大的 `if (…) … else if () … else if (…) …` 语句,所以你可以直接跳转到匹配你代码情况的章节。
15 |
16 |
17 |
18 | 1. [如果是箭头函数](#arrow-functions)
19 | 2. [否则,如果使用 `new` 调用函数/类](#new)
20 | 3. [否则, 函数被 `bind` 了 `this`](#bound)
21 | 4. [否则, 如果 `this` 在调用时设置](#call-apply)
22 | 5. [否则, 如果使用父对象(`parent.func()`) 调用函数](#object-member)
23 | 6. [否则, 如果函数或者其父作用域使用严格模式](#strict)
24 | 7. [否则](#otherwise)
25 |
26 | ## 如果是箭头函数:
27 |
28 | ```js
29 | const arrowFunction = () => {
30 | console.log(this);
31 | };
32 | ```
33 |
34 | 在这种情况下,`this` 的值**永远**与父作用域的 `this` 相同。
35 |
36 | ```js
37 | const outerThis = this;
38 |
39 | const arrowFunction = () => {
40 | // 永远输出 `true`:
41 | console.log(this === outerThis);
42 | };
43 | ```
44 |
45 | 箭头函数非常优秀,因为其内部 `this` 的值无法被改变,它与外部的 `this` **永远** 相同。
46 |
47 | ### 其他例子
48 |
49 | 使用箭头函数, `this` 的值**无法**被 [`bind`](#bound) 改变:
50 |
51 | ```js
52 | // 输出为 `true` - bind `this` 被忽略:
53 | arrowFunction.bind({foo: 'bar'})();
54 | ```
55 |
56 | 使用箭头函数,`this` 的值**无法**被 [`call` 或 `apply`](#call-apply) 改变:
57 |
58 | ```js
59 | // 输出为 `true` - call `this` 被忽略:
60 | arrowFunction.call({foo: 'bar'});
61 | // 输出为 `true` - apply `this` 被忽略:
62 | arrowFunction.apply({foo: 'bar'});
63 | ```
64 |
65 | 使用箭头函数,`this` 的值**无法**通过将函数作为另一个对象的成员变量来调用改变:
66 |
67 | ```js
68 | const obj = {arrowFunction};
69 | // 输出为 `true` - 父对象被忽略:
70 | obj.arrowFunction();
71 | ```
72 |
73 | 使用箭头函数,`this` 的值**无法**通过将函数作为构造函数来调用而改变:
74 |
75 | ```js
76 | // TypeError: arrowFunction is not a constructor
77 | new arrowFunction();
78 | ```
79 |
80 | ### “绑定” 实例方法
81 |
82 | 对于实例方法,如果想要确保 `this` 始终指向类实例,最好的方法是使用箭头函数和 [class fields](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/Public_class_fields):
83 |
84 | ```js
85 | class Whatever {
86 | someMethod = () => {
87 | // 永远是 Whatever 的实例:
88 | console.log(this);
89 | };
90 | }
91 | ```
92 |
93 | 这个模式在将实例方法作为组件内的事件监听器时十分有用(如 React 组件或者 Web Components)。
94 |
95 | 上面的代码貌似打破了“`this` 的值**永远**与父作用域的 `this` 相同”的规则,但是如果你将 class fields 看作将对象设置到构造函数的语法糖,那么就好理解了:
96 |
97 | ```js
98 | class Whatever {
99 | someMethod = (() => {
100 | const outerThis = this;
101 | return () => {
102 | // 永远输出 `true`:
103 | console.log(this === outerThis);
104 | };
105 | })();
106 | }
107 |
108 | // …大致等于:
109 |
110 | class Whatever {
111 | constructor() {
112 | const outerThis = this;
113 | this.someMethod = () => {
114 | // Always logs `true`:
115 | console.log(this === outerThis);
116 | };
117 | }
118 | }
119 | ```
120 |
121 | 其他模式包括在构造函数中绑定现有函数,或在构造函数中对函数赋值。如果你由于某种原因不能使用 class fields,则在构造函数中对函数赋值是一种合理的选择:
122 |
123 | ```js
124 | class Whatever {
125 | constructor() {
126 | this.someMethod = () => {
127 | // …
128 | };
129 | }
130 | }
131 | ```
132 |
133 | ## 否则,如果使用 `new` 调用函数/类:
134 |
135 | ```
136 | new Whatever();
137 | ```
138 |
139 | 上面代码会调用 `Whatever`(或者它的构造函数,如果它是类),并将 `this` 设置为 `Object.create(Whatever.prototype)` 的结果。
140 |
141 | ```js
142 | class MyClass {
143 | constructor() {
144 | console.log(
145 | this.constructor === Object.create(MyClass.prototype).constructor,
146 | );
147 | }
148 | }
149 |
150 | // 输出为 `true`:
151 | new MyClass();
152 | ```
153 |
154 | 使用旧式的构造函数结果也一样:
155 |
156 | ```js
157 | function MyClass() {
158 | console.log(
159 | this.constructor === Object.create(MyClass.prototype).constructor,
160 | );
161 | }
162 |
163 | // 输出 `true`:
164 | new MyClass();
165 | ```
166 |
167 | ### 其他例子
168 |
169 | 使用 `new` 调用,`this` 的值**无法**被 [`bind`](#bound) 改变:
170 |
171 | ```js
172 | const BoundMyClass = MyClass.bind({foo: 'bar'});
173 | // 输出为 `true` - bind `this` 被忽略:
174 | new BoundMyClass();
175 | ```
176 |
177 | 使用 `new` 调用,`this` 的值**无法**通过将函数作为另一个对象的成员变量来调用改变:
178 |
179 | ```js
180 | const obj = {MyClass};
181 | // 输出为 `true` - 父对象被忽略:
182 | new obj.MyClass();
183 | ```
184 |
185 | ## 否则, 函数被 `bind` 了 `this`:
186 |
187 | ```js
188 | function someFunction() {
189 | return this;
190 | }
191 |
192 | const boundObject = {hello: 'world'};
193 | const boundFunction = someFunction.bind(boundObject);
194 | ```
195 |
196 | 每当 `boundFunction` 被调用,它的 `this` 值就是通过 `bind` 传入的值(`boundObject`)。
197 |
198 | ```js
199 | // 输出 `false`:
200 | console.log(someFunction() === boundObject);
201 | // 输出 `true`:
202 | console.log(boundFunction() === boundObject);
203 | ```
204 |
205 | -----
206 |
207 | **Warning**: 避免使用 `bind` 将函数绑定到其外部的 `this`。使用[箭头函数](#arrow-functions)替代,因为这样 `this` 可以在函数声明就能清楚地看出来,而非在后续代码中看到。
208 | 不要使用 `bind` 设置 `this` 为与父对象无关的值;这通常是出乎意料的,这也是 `this` 获得如此糟糕名声的原因。考虑将值作为参数传递;它更加明确,并且可以使用箭头函数。
209 |
210 |
211 |
212 | ### 其他例子
213 |
214 | 使用 `bind` 调用函数,`this` 的值**无法**被 [`call` 或 `apply`](#call-apply) 改变:
215 |
216 | ```js
217 | // 输出为 `true` - call `this` 被忽略:
218 | console.log(boundFunction.call({foo: 'bar'}) === boundObject);
219 | // 输出为 `true` - apply `this` 被忽略:
220 | console.log(boundFunction.apply({foo: 'bar'}) === boundObject);
221 | ```
222 |
223 | 使用 `bind` 调用函数,`this` 的值**无法**通过将函数作为另一个对象的成员变量来调用改变:
224 |
225 | ```js
226 | const obj = {boundFunction};
227 | // Logs `true` - parent object is ignored:
228 | console.log(obj.boundFunction() === boundObject);
229 | ```
230 |
231 | ## 否则, 如果 `this` 在调用时设置:
232 |
233 | ```js
234 | function someFunction() {
235 | return this;
236 | }
237 |
238 | const someObject = {hello: 'world'};
239 |
240 | // 输出 `true`:
241 | console.log(someFunction.call(someObject) === someObject);
242 | // 输出 `true`:
243 | console.log(someFunction.apply(someObject) === someObject);
244 | ```
245 |
246 | `this` 的值就是传递给 `call`/`apply` 的对象。
247 |
248 |
249 | ----
250 |
251 | **警告**: 不要使用 `bind` 设置 `this` 为与父对象无关的值;这通常是出乎意料的,这也是 `this` 获得如此糟糕名声的原因。考虑将值作为参数传递;它更加明确,并且可以使用箭头函数。
252 |
253 | 不幸的是,`this` 可能会被如 DOM 事件监听器之类的函数设置为其他值,使用它会导致代码难以理解:
254 |
255 | 不要这样:
256 |
257 | ```js
258 | element.addEventListener('click', function (event) {
259 | // 输出 `element`, 因为 DOM 将 `this` 设置为
260 | // click 绑定的元素上
261 | console.log(this);
262 | });
263 | ```
264 |
265 | 我会避免在上述场景中使用 `this`,我会这样使用:
266 |
267 | ```js
268 | element.addEventListener('click', (event) => {
269 | // 理想情况, 从父作用域获得它:
270 | console.log(element);
271 | // 但是如果你不想这么做,可以从 event 对象获取它:
272 | console.log(event.currentTarget);
273 | });
274 | ```
275 |
276 | ## 否则, 如果使用父对象(`parent.func()`) 调用函数:
277 |
278 | ```js
279 | const obj = {
280 | someMethod() {
281 | return this;
282 | },
283 | };
284 |
285 | // 输出 `true`:
286 | console.log(obj.someMethod() === obj);
287 | ```
288 |
289 | 在这种情况下,函数作为 `obj` 的成员变量被调用,所以 `this` 指向 `obj`。这是在调用时发生的,因此如果没有使用父对象调用,或者使用一个不同的父对象调用,该连接会断开:
290 |
291 | ```js
292 | const {someMethod} = obj;
293 | // 输出 `false`:
294 | console.log(someMethod() === obj);
295 |
296 | const anotherObj = {someMethod};
297 | // 输出 `false`:
298 | console.log(anotherObj.someMethod() === obj);
299 | // 输出 `true`:
300 | console.log(anotherObj.someMethod() === anotherObj);
301 | ```
302 |
303 | `someMethod() === obj` 为 `false`,因为 `someMethod` **不是** 作为 `obj` 的成员变量被调用的。尝试执行以下操作时,可能会遇到此陷阱:
304 |
305 | ```js
306 | const $ = document.querySelector;
307 | // TypeError: Illegal invocation
308 | const el = $('.some-element');
309 | ```
310 |
311 | 这个报错是因为 `querySelector` 实现会寻找它的 `this` 值,并期望其某种 DOM 节点,上述代码破坏了连接。为了正确实现上述功能,可以这样写:
312 |
313 | ```js
314 | const $ = document.querySelector.bind(document);
315 | // 或者:
316 | const $ = (...args) => document.querySelector(...args);
317 | ```
318 |
319 | 有趣的事实:并不是所有的 API 都在其内部使用了 `this`。Console 方法(如 `console.log`)就改为了不使用 `this` 引用,因此 `log` 方法不需要绑定 `console`。
320 |
321 | ---
322 |
323 | **警告**: 不要使用 `bind` 设置 `this` 为与父对象无关的值;这通常是出乎意料的,这也是 `this` 获得如此糟糕名声的原因。考虑将值作为参数传递;它更加明确,并且可以使用箭头函数。
324 |
325 | ## 否则, 如果函数或者其父作用域使用严格模式:
326 |
327 | ```js
328 | function someFunction() {
329 | 'use strict';
330 | return this;
331 | }
332 |
333 | // 输出 `true`:
334 | console.log(someFunction() === undefined);
335 | ```
336 |
337 | 在这种情况下,`this` 的值是 `undefined`。如果父作用域处于[严格模式](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode)(而且所有模块都处在严格模式),则不需要在函数内部使用 `'use strict'`。
338 |
339 | ---
340 |
341 | **警告**: 不要依赖这个。我的意思是,有更简单的方式来得到一个 `undefined` 值 😀。
342 |
343 | ## 否则:
344 |
345 | ```js
346 | function someFunction() {
347 | return this;
348 | }
349 |
350 | // 输出 `true`:
351 | console.log(someFunction() === globalThis);
352 | ```
353 |
354 | 在这种情况下,`this` 的值与 `globalThis` 相同。
355 |
356 | ---
357 |
358 | 很多人(包括我)把 `globalThis` 称为 `global` 对象,但这不是 100% 技术正确的。在 [Mathias Bynens with the details](https://mathiasbynens.be/notes/globalthis#terminology) 中,有它为什么叫 `globalThis` 而不是 `global` 的原因。
359 |
360 | ---
361 |
362 | **警告**: 避免使用 `this` 指向 `global` 对象(对,我仍然这么叫它)。改为使用[`globalThis`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/globalThis),它更加明确。
363 |
364 |
365 | ## 结语
366 |
367 | 好了,这就是我理解的 `this` 的全部了。如果有任何问题或者我有所遗漏,请[给我发推](https://twitter.com/jaffathecake)。
368 |
369 | 感谢 [Mathias Bynens](https://twitter.com/mathias), [Ingvar Stepanyan](https://twitter.com/RReverser), 和 [Thomas Steiner](https://twitter.com/tomayac) 的审阅。
370 |
371 |
372 |
--------------------------------------------------------------------------------
/Blogs/simpread-comparing-the-new-generation-of-build-tools.md:
--------------------------------------------------------------------------------
1 | # Comparing the New Generation of Build Tools
2 |
3 | A bunch of new developer tools have landed in the past year and they are biting at the heels of the tools that have dominated front-end development over the last few years, including webpack, Babel, Rollup, Parcel, create-react-app.
4 |
5 | These new tools aren’t designed to perform the exact same function, and each has different things they’re trying to achieve and features to get there. Despite their differences, these tools do share a common goal: **improve the developer experience.**
6 |
7 |
8 |
9 | > #### Table of contents
10 | >
11 | > 1. [esbuild](#esbuild)
12 | > 2. [Snowpack](#snowpack)
13 | > 3. [Vite](#vite)
14 | > 4. [wmr](#wmr)
15 | > 5. [Feature comparison](#feature-comparison)
16 | > 6. [Wrapping up](#wrapping-up)
17 |
18 | * * *
19 |
20 | Specifically, I’d like to evaluate each one, outlining what they do, why we need them, and their use cases. I realize that comparisons aren’t always fair. Again, it’s not like any of the things we’re looking at in this article are direct competitors. In fact, Snowpack and Vite actually _use_ esbuild under the hood for certain tasks. Our goal is more to get a better view of the landscape of developer tools that run tasks to make our jobs easier. This way, we see what options are out there and how they stack up, so we can make the best choices when we need them.
21 |
22 | Of course, all of this will be colored by my experience using React and Preact. I’m more familiar with these ~frameworks~ libraries, but we’ll look at their support for other front-end frameworks too.
23 |
24 | There have been a whole lot of great articles, streams and podcasts about these new developer tools. There are a couple of ShopTalk Show episodes I’d recommend for more context: [Episode 454](https://shoptalkshow.com/454/) discusses Vite and [Episode 448](https://shoptalkshow.com/448/) features the creators of wmr and Snowpack. Something that stands out from these episodes is that a huge amount of work has gone into building these tools to modernize our developer environments.
25 |
26 | ### [](#why-are-these-tools-all-arriving-now)Why are these tools all arriving now?
27 |
28 | In part, I think these tools are arriving as a reaction to JavaScript tooling fatigue — something captured nicely in [this article about learning JavaScript back in 2016](https://hackernoon.com/how-it-feels-to-learn-javascript-in-2016-d3a717dd577f). They also fill a missing middle ground between writing a single vanilla JavaScript file, and having to download 200 megabytes of tooling dependencies before you’ve written a line of your own code. They come batteries-included without the dependency list, and are part of a trend of [collapsing layers](https://www.swyx.io/js-third-age/) in the JavaScript ecosystem.
29 |
30 | Snowpack, Vite, and wmr have all been enabled by [native JavaScript modules](https://css-tricks.com/life-with-esm/) in the browser. Back in 2018, [Firefox 60 was released](https://developer.mozilla.org/en-US/docs/Mozilla/Firefox/Releases/60#javascript) with ECMAScript 2015 modules enabled by default. Since then, all major browser engines have supported native JavaScript modules. Node.js also shipped with [native JavaScript modules](https://github.com/nodejs/node/blob/master/doc/changelogs/CHANGELOG_V13.md#13.2.0) in November 2019. We’re still finding out what possibilities native JavaScript modules unlock today in 2021.
31 |
32 | ### [](#how-are-these-different-from-existing-tools)How are these different from existing tools?
33 |
34 | Whether we use webpack, Rollup, or Parcel for a development server, the tool bundles our entire codebase from our source code and a `node_modules` folder, runs these through build processes — like Babel, TypeScript, or PostCSS — then pushes the bundled code to our browser. This all takes work, and can slow development servers to a crawl in larger codebases, even after all the work that’s gone into caching and optimizing.
35 |
36 | Snowpack, Vite, and wmr development servers don’t follow this model. Instead, they wait until the browser finds an import statement and makes an HTTP request for the module. Only after this request is made will the tool apply transforms to the requested module and any leaf nodes in the module’s import tree, then serve these to the browser. This speeds things up a lot as there’s less work in the process of pushing to a dev server.
37 |
38 | You’ll notice [esbuild](https://esbuild.github.io/) missing from this picture. It’s a _bundler_ first and foremost. It doesn’t side-step bundling the way the other tools do. Instead, esbuild processes code [extremely fast](https://esbuild.github.io/faq/#benchmark-details) by [avoiding expensive transformations, leveraging parallelization and using the Go language](https://esbuild.github.io/faq/#why-is-esbuild-fast).
39 |
40 | ### [](#the-experiment)The experiment
41 |
42 | I took one of the the example apps from the React docs and rebuilt it with each tool covered in this article. The project I went with was [Snap Shot](https://github.com/Yog9/SnapShot) by [Yogita Verma](https://yog9.github.io/portfolio/). Here’s a [link to the original repo](https://github.com/Yog9/SnapShot), and a [link to my repo with the four versions of Snap Shot](https://github.com/Elliotclyde/build-tool-test), each using a different build tool. We’ll compare the output of each build step later. Rebuilding this app allowed me to test out the developer experience of pulling some pretty standard React dependencies into the tools, including [React Router](https://reactrouter.com/) and [a](https://github.com/axios/axios)[xios](https://github.com/axios/axios).
43 |
44 | ### [](#comparable-features)Comparable features
45 |
46 | Before we get into the specifics of each individual tool, they _all_ support the following features out of the box (to varying degrees):
47 |
48 | * First-class support for native JavaScript modules
49 | * TypeScript compilation (but not type checking)
50 | * JSX
51 | * Plugin API for extensibility
52 | * A built-in development server
53 | * CSS bundling and support for CSS-in-JS libraries
54 |
55 | All of these tools can compile TypeScript into JavaScript, but will do so _even if there are type errors_. For proper type checking you would need to install TypeScript and run `tsc --noEmit` on your root JavaScript file, or alternatively, use editor plugins to watch for type errors.
56 |
57 | OK, let’s take look at each tool.
58 |
59 | ### [](#esbuild)esbuild
60 |
61 | 
62 |
63 | esbuild was created by [Evan Wallace](https://github.com/evanw) (CTO of [Figma](https://www.figma.com/)). Its main feature is that it provides a build step 10×-100× faster than Node-based bundlers (by their own benchmarks). It doesn’t provide many of the developer conveniences you might find in something like create-react-app. But there are more and more esbuild starters popping up that fills those gaps, including [create-react-app-esbuild](https://github.com/pradel/create-react-app-esbuild), [estrella](https://github.com/rsms/estrella) and [Snowpack](https://www.snowpack.dev/), which uses esbuild for its build step.
64 |
65 | esbuild is very new. It hasn’t yet reached a 1.0 version and isn’t quite ready for production use — but it’s not far off. It gives you intuitive JavaScript and command line APIs with smart defaults.
66 |
67 | #### [](#use-cases)Use cases
68 |
69 | esbuild is a complete game-changer in the bundler world. It’s going to be most useful in large codebases where the speed difference between esbuild and node bundlers gets multiplied. When esbuild hits 1.0 it’s going to be _very_ useful in big production sites, and will save teams a whole lot of time waiting for builds to complete. Unfortunately, big production sites will have to wait until esbuild becomes stable. In the meantime it’ll just be good to add some speed to your bundling in side projects.
70 |
71 | esbuild’s lightening fast speed will be a bonus for any kind of work that you’re doing. Less time spent waiting for builds to run is always going to be good for developer experience! This considered, if you’re prototyping quick applications you might want to start with something more high level than esbuild — otherwise, you’ll need to spend some time pulling in dependencies and configuring your environment before you get conveniences we expect in the JavaScript ecosystem. Also, if you want to minimize the size of your bundle as much as possible you may want to use Rollup and terser, which will produce slightly smaller bundle sizes.
72 |
73 | #### [](#setup)Setup
74 |
75 | I decided to start a React project in esbuild in a naïve way: npm installing esbuild, React and ReactDOM. I created a `src/app.jsx` file and a `dist/index.html` file. Then, I used the following command to compile the app into a `dist/bundle.js` file:
76 |
77 | ```shell
78 | ./node_modules/.bin/esbuild src/app.jsx --bundle --platform=browser --outfile=dist/bundle.js
79 | ```
80 |
81 | When I hosted and opened `index.html` in the browser, I was met with the “white screen of death” and an “Uncaught ReferenceError: process is not defined” console error. [Both the docs and the CLI explain exactly what you need to do to prevent this](https://esbuild.github.io/getting-started/#bundling-for-the-browser) but it might be a bit of a “gotcha” for beginners, because it requires an extra argument when bundling React:
82 |
83 | ```shell
84 | --define:process.env.NODE_ENV=\"production\"
85 | ```
86 |
87 | Or, if you’re including esbuild in npm scripts written like this to escape the quotes:
88 |
89 | ```shell
90 | --define:process.env.NODE_ENV=\\\"production\\\"
91 | ```
92 |
93 | This `define` argument is needed for any library bundled for the browser that expects node environment variables. Vue 2.0 also expects these. You won’t have the same problem with Preact because it doesn’t expect any environment variables and ships ready for the browser by default.
94 |
95 | After I ran the command with the `define` argument, my “Hello world” React app was working perfectly. JSX works out of the box with `.jsx` files. That said, React needs to be manually imported and then JSX is converted to the `React.createElement`. However, there are ways to add [auto imports in JSX and/or configure JSX for](https://esbuild.github.io/content-types/#jsx) [P](https://esbuild.github.io/content-types/#jsx)[react](https://esbuild.github.io/content-types/#jsx).
96 |
97 | #### [](#usage)Usage
98 |
99 | esbuild provides a `--serve` option for a development server. This bypasses the filesystem and serves modules straight from memory, ensuring that the browser doesn’t pull older versions of modules. However, it doesn’t include live/hot reloading, so you will find yourself refreshing the browser after saving which isn’t an ideal experience.
100 |
101 | I decided to use the newly-released [**watch**](https://esbuild.github.io/api/#watch) [feature](https://esbuild.github.io/api/#watch).This tells esbuild to recompile code every time a source file is saved. But we still need a server to see our saved changes. We can pull in a development server package, such as Luke Jackson’s [servor](https://www.npmjs.com/package/servor):
102 |
103 | ```shell
104 | npm install servor --save-dev
105 | ```
106 |
107 | Then we can use the esbuild Javascript API to start as server and run esbuild’s watch mode at the same time. Let’s create a file at the root of our project called `watch.js`:
108 |
109 | ```javascript
110 | // watch.js
111 | const esbuild = require("esbuild");
112 | const servor = require("servor");
113 |
114 | esbuild.build({
115 | // pass any options to esbuild here...
116 | entryPoints: ["src/app.jsx"],
117 | outdir: "dist",
118 | define: { "process.env.NODE_ENV": '"production"' },
119 | watch: true,
120 | });
121 |
122 | async function serve(){
123 | console.log("running server from: http://localhost:8080/");
124 | await servor({
125 | // pass any options to servor here...
126 | browser:true,
127 | root: "dist",
128 | port: 8080,
129 | });
130 | }
131 |
132 | serve();
133 | ```
134 |
135 | Now run `node watch.js` in the command line. This gives us a nice dev server, though again, it doesn’t give us hot module replacement or fast refresh (i.e., your client-side state won’t be preserved). But this was enough for my testing needs.
136 |
137 | Even though we’re rebundling our entire application every time we save a file, we’d need to have a pretty massive application before esbuild slows down. After I set up this tooling, I was getting instant feedback from changes. My computer uses an intel i7 from 2012, so it certainly isn’t a top-of-the-line machine.
138 |
139 | If you need a preconfigured version of esbuild with live reload and some React defaults, you can clone [this repo](https://github.com/Elliotclyde/esbuild-react-starter).
140 |
141 | #### [](#supported-files)Supported files
142 |
143 | esbuild can import CSS in JavaScript if that’s your style. It will compile CSS into an output file with the same name as your main output JavaScript file. It can also bundle CSS `@import` statements by default. There is no support for [CSS Modules](https://css-tricks.com/css-modules-part-1-need/), but there are plans for it.
144 |
145 | There is a [growing community of plugins for esbuild](https://github.com/esbuild/community-plugins). For example, there are plugins available for [Vue single file components](https://github.com/few-far/esbuild-vue-plugin), and [Svelte components](https://github.com/EMH333/esbuild-svelte).
146 |
147 | esbuild works with JSON files and can bundle them into JavaScript modules without any configuration.
148 |
149 | It can also import images in JavaScript with the option to either convert them into data URLs or copying them into an output folder. This behavior isn’t enabled by default, but you can add the following in your esbuild config object to enable either option:
150 |
151 | ```javascript
152 | loader: { '.png': 'dataurl' } // Converts to data url in JS bundle
153 | loader: { '.png': 'file' } // Copies to output folder
154 | ```
155 |
156 | Code splitting appears to be a work in progress, but is mostly there in the ESM output format, and it does look like it is a priority for the project. It’s also worth mentioning that tree-shaking is built into esbuild by default and can’t be turned off.
157 |
158 | #### [](#production-build)Production build
159 |
160 | Using the “[minify](https://esbuild.github.io/api/#minify)” and “[bundle](https://esbuild.github.io/api/#bundle)” options in your esbuild command won’t create a bundle quite as small as a [Rollup](https://rollupjs.org/)/[Terser](https://terser.org/) pipeline. This is because esbuild sacrifices some bundle size optimization to get through your code in as few passes as possible. However, the difference may be pretty negligible, and worth it for the increase in bundling speed, depending on your project. In my clone of the Snap Shot application, esbuild created a bundle of 177 KB which isn’t a lot more than the 165KB produced by Vite, which uses rollup and terser.
161 |
162 | #### [](#overall)Overall
163 |
164 |
esbuild
Templates for multiple front end frameworks
❌
Hot module replacement development server
❌
Streaming imports
❌
Preconfigured production build
❌
Automatic PostCSS and preprocessor conversion
❌
HTM transform
❌
Rollup plugin support
❌
Size on disk (default install)
7.34 MB
165 |
166 | esbuild is an extremely powerful tool. But it might be difficult if you’re used to zero-config setups. If you need more, then you might want to take a look at the next tool, Snowpack, which uses esbuild.
167 |
168 | ### [](#snowpack)Snowpack
169 |
170 | 
171 |
172 | Snowpack is a build tool by the creators of [Skypack](https://www.skypack.dev/) and [Pika](https://www.pika.dev/). It provides an awesome development server and was created with an [“unbundled development”](https://www.snowpack.dev/concepts/how-snowpack-works#unbundled-development) philosophy. To quote the documentation: “You should be able to use a bundler because you want to, and not because you need to.”
173 |
174 | By default, Snowpack’s build step doesn’t bundle files into a single package but provides unbundled esmodules that run in the browser. esbuild is actually included in there as a dependency, but the idea is to use JavaScript modules and only bundle with esbuild when it’s needed.
175 |
176 | Snowpack has some [pretty slick documentation](https://www.snowpack.dev/), including a [list of guides](https://www.snowpack.dev/guides) for using it with JavaScript frameworks, and a [bunch of templates for them](https://github.com/snowpackjs/snowpack/tree/main/create-snowpack-app). Some of the guides are still a work in progress, but others like [the one for React](https://www.snowpack.dev/tutorials/react) are nice and clear. It also looks like Snowpack treats [Svelte as a first-class citizen](https://www.snowpack.dev/tutorials/svelte/). I actually first heard about Snowpack from Rich Harris’s [“Futuristic Web Development”](https://www.youtube.com/watch?v=qSfdtmcZ4d0&t=636s) talk at Svelte Summit 2020. That said, the upcoming Svelte meta-framework [SvelteKit](https://svelte.dev/blog/whats-the-deal-with-sveltekit) was supposed to be powered by Snowpack but has since switched to Vite (which we’ll review next).
177 |
178 | #### [](#use-cases)Use cases
179 |
180 | Snowpack is a good choice if you want to double down on unbundled deployment. You may be writing source code with a small number of modules. This would mean you’re not creating a big request waterfall with an unbundled build. If you don’t need the added complexity and technical debt of bundling, then Snowpack is a great choice. A good use case would be if you’re incrementally adopting a front-end framework into a server-rendered or static application. You’d be pulling in as little tooling as possible from the node ecosystem but you’d still be getting the benefits of declarative frontend frameworks.
181 |
182 | Secondly, I’d argue that Snowpack is a great wrapper around esbuild. If you want to try out esbuild but also want a development server and pre-written templates for front-end frameworks, then you can’t go wrong with Snowpack. Enable esbuild in the build step of your Snowpack config and you’re good to go.
183 |
184 | As things currently stand, I’d argue that Snowpack wouldn’t be the best replacement for a zero configuration tool like create-react-app because you’ll need to pull in plugins and configure them yourself if you have a big application and need a super-fancy optimized production-ready build step.
185 |
186 | #### [](#setup)Setup
187 |
188 | Let’s start a project with Snowpack by jumping into the command line:
189 |
190 | ```shell
191 | mkdir snowpackproject
192 | cd snowpackproject
193 | npm init #fill with defaults
194 | npm install snowpack
195 | ```
196 |
197 | Now, let’s add the following to `package.json`:
198 |
199 | ```javascript
200 | // package.json
201 | "scripts": {
202 | "start": "snowpack dev",
203 | "build": "snowpack build"
204 | },
205 | ```
206 |
207 | Next, we’ll create a configuration file:
208 |
209 | ```shell
210 | // Mac or Linux
211 | touch snowpack.config.js
212 | // Windows
213 | new-item snowpack.config.js
214 | ```
215 |
216 | I think the most magical part of Snowpack comes when setting one innocent-looking key value pair in the configuration file. Paste this into the configuration file, for example:
217 |
218 | ```javascript
219 | // snowpack.config.js
220 | module.exports = {
221 | packageOptions: {
222 | "source": "remote",
223 | }
224 | };
225 | ```
226 |
227 | `source: remote` enables something called [**streaming imports**](https://www.snowpack.dev/guides/streaming-imports#how-streaming-imports-work). Streaming imports enable Snowpack to bypass npm installation by converting bare imports (e.g., `import React from 'react';`) into CDN imports from Skypack.
228 |
229 | Moving ahead, let’s make an `index.html` file:
230 |
231 | ```index.html
232 |
233 |
234 |
235 |
236 | >
237 | Snowpack streaming imports
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 | ```
246 |
247 | And, finally, we’ll add an `app.jsx` file:
248 |
249 | ```jsx
250 | // app.jsx
251 | import React from 'react'
252 | import ReactDOM from 'react-dom'
253 | const App = ()=>{
254 | return
Welcome to Snowpack streaming imports!
255 | }
256 | ReactDOM.render(,document.getElementById('root')); 0
257 | ```
258 |
259 | Note that we didn’t npm install React or ReactDOM at any stage. But if we start up the Snowpack developer server like this:
260 |
261 | ```shell
262 | ./node_modules/.bin/snowpack dev
263 | ```
264 |
265 | …our app still works!
266 |
267 | Instead of pulling from a `node_modules` folder, Snowpack pulls the npm package down from Skypack, a CDN that hosts the npm registry, and it’s is pre-optimized to work in the browser. Snowpack then serves it in a `./_snowpack/pkg` URL.
268 |
269 | #### [](#usage)Usage
270 |
271 | This is a big step away from Node/npm-based workflow. What we’re actually looking at is a new **CDN/JavaScript module-based workflow.**
272 |
273 | If, however, we our app as it is and run a production build, Snowpack throws an error. This is because it needs to know which versions of React and ReactDOM to use when building. You can fix this by writing to a `snowpack.deps.json` which can automatically be created by running the following:
274 |
275 | ```shell
276 | ./node_modules/.bin/snowpack add react
277 | ./node_modules/.bin/snowpack add react-dom
278 | ```
279 |
280 | That won’t download the package from npm, but it will record the version of the packages used for Snowpack builds.
281 |
282 | One caveat is that we miss out on developer error messages, as Skypack will ship the production version of packages.
283 |
284 | Even if we aren’t using streaming imports, the Snowpack development server bundles each dependency from `node_modules` into one JavaScript file per dependency, converts those files to a native JavaScript module, then serves it to the browser. This means the browser can cache these scripts and only re-request them if they’ve changed. The development server automatically refreshes on save, but doesn’t preserve the client-side state. All dependencies from node seemed to work out of the box regardless of whether they were using legacy module formats or node APIs (such as the infamous `process.env` we had trouble with in esbuild).
285 |
286 | Preserving client-side state in React requires [react-refresh](https://www.skypack.dev/view/@snowpack/plugin-react-refresh), which requires a few Babel packages of its own as dependencies. These aren’t included by default, but are available using the more maximal React template. The template pulls in react-refresh, Prettier, Chai, and React Testing Library, for a total Node dependency package weighing in at 80 MB:
287 |
288 | ```shell
289 | npx create-snowpack-app my-react-project --template @snowpack/app-template-react
290 | ```
291 |
292 | #### [](#supported-files)Supported files
293 |
294 | JSX is supported, but again, only with `.jsx` files by default. Snowpack automatically detects if whether React or Preact is being used, and decides accordingly which render function to use for the JSX transform. However, if we want to customize the JSX further than this, we’d need to pull in Babel via [their plugin](https://www.npmjs.com/package/@snowpack/plugin-babel). There is also a [Snowpack plugin available for Vue single file components](https://www.npmjs.com/package/@snowpack/plugin-vue) and, of course, for [Svelte components](https://github.com/snowpackjs/snowpack/tree/main/plugins/plugin-svelte). Further, Snowpack compiles TypeScript, but for type checking we need the [TypeScript plugin](https://www.npmjs.com/package/@snowpack/plugin-typescript).
295 |
296 | CSS can be imported into JavaScript and are tossed into the document `` at runtime. CSS modules are also supported out of the box for scoping as long as they have the `.module.css` extension.
297 |
298 | Imported JSON files will be cast into a JavaScript module with an object as a default export. Snowpack supports images and copies them into the production folder. Going along with its unbundled philosophy, Snowpack does not include images as data URLs in the bundle.
299 |
300 | #### [](#production-build)Production build
301 |
302 | The default `snowpack build` command basically copies the exact source file structure into an output folder. For files that compile to JavaScript (e.g. TypeScript, JSX, JSON, `.vue`, `.svelte`), it transforms each individual file into a separate browser-friendly JavaScript module.
303 |
304 | This works fine, but isn’t great for production, as it could cause a big waterfall of requests if the source code is split into a lot of files. In the Snap Shot application I ended up with 184KB of source files which would then request another 105 KB of dependencies from Skypack which made for a pretty huge waterfall.
305 |
306 | However, Snowpack pulls esbuild as a dependency and we can enable esbuild to bundle, minify and compile our code by adding an “optimize” object to the Snowpack config:
307 |
308 | ```javascript
309 | // snowpack.config.js
310 | module.exports = {
311 | optimize: {
312 | bundle: true,
313 | minify: true,
314 | target: 'es2018',
315 | },
316 | };
317 | ```
318 |
319 | This runs the code using the optimization features provided by esbuild, so by just adding these options we could get the same build we had earlier on with esbuild.
320 |
321 | Since esbuild hasn’t reached 1.0 yet, Snowpack recommends using either the [webpack](https://www.npmjs.com/package/@snowpack/plugin-webpack) or [Rollup](https://github.com/ParamagicDev/snowpack-plugin-rollup-bundle) plugin for production builds, both of which need to be configured.
322 |
323 | #### [](#overall)Overall
324 |
325 | Snowpack provides a lightweight developer experience with a full-featured development server, detailed documentation, and easy-to-install templates. You are left to decide whether you want to bundle your application and how you want to do so. If you want a tool that provides both a dev server and a more opinionated build step, you might want to take a look at Vite, the next tool on our list.
326 |
327 |
328 |
329 | ### [](#vite)Vite
330 |
331 | 
332 |
333 | Vite is developed by Vue creator (and [Hades speedrunner](https://mobile.twitter.com/youyuxi/status/1331084461652516864)) Evan You. Where esbuild concentrates on the build step and Snowpack concentrates on the development server, Vite provides both: a full development server _and_ an optimized build command using Rollup.
334 |
335 | #### [](#use-cases)Use cases
336 |
337 | If you want a serious create-react-app or Vue CLI competitor, Vite is the closest one in the bunch because it comes with batteries-included features. The lightening-fast development server and zero-config optimized production build mean you can get from zero to production without any configuration. Vite is a tool that could be used in both a tiny side-project or a big production application. A good use case for Vite would be any sizeable single page app.
338 |
339 | Why _wouldn’t_ you use Vite? Vite is an opinionated tool and you might disagree with its opinions. You might not want to use Rollup for your build (we’ve been talking about how fast esbuild is), or you might want your tooling to give you the full power of Babel, eslint and the ecosystem of webpack loaders out of the box.
340 |
341 | Also, you want zero-config server-side rendering meta-frameworks, you’d be better off staying with webpack-based frameworks, like Nuxt.js and Next.js until the story for Vite server-side rendering is more complete.
342 |
343 | #### [](#setup)Setup
344 |
345 | Vite has more opinionated defaults than esbuild and Snowpack. Its [documentation](https://vitejs.dev/) is clear and detailed. We get full support for Vue with Evan being the creator and all, so Vite is a definite happy path for Vue developers. That said, Vite can be used with any front-end framework and even provides a [list of templates](https://github.com/vitejs/vite/tree/main/packages/create-app) to get you started.
346 |
347 | #### [](#usage)Usage
348 |
349 | Vite’s development server is pretty powerful. Vite pre-bundles all of a project’s dependencies together into a single native JavaScript module with esbuild, then serves it up with a heavily cached HTTP header. This means no time is wasted on compiling, serving or requesting imported dependencies after the first page load. Vite also provides clear error messaging, printing the exact block of code and the line numbers to troubleshoot. Again with Vite, I didn’t have any issues pulling in dependencies that used node APIs or legacy formats. They all seemed to be shimmed into a browser-acceptable esmodule.
350 |
351 | Vite’s React and Vue templates both pull in plugins that enable hot module replacement. The Vue template pulls in a [Vue plugin for single file components](https://github.com/vitejs/vite/tree/main/packages/plugin-vue), and a [Vue plugin for JSX](https://github.com/vitejs/vite/blob/main/packages/plugin-vue-jsx/package.json). The React template pulls in the [react-refresh plugin](https://github.com/vitejs/vite/tree/main/packages/plugin-react-refresh). Either way, both will give you hot module replacement and client-side state preservation. Sure, they add a few more dependencies, including Babel packages, but, Babel isn’t actually necessary when using JSX in Vite. By default, JSX works the same way as esbuild — it converts to `React.createElement`. It won’t automatically import React, but its behavior can be configured.
352 |
353 | And while we’re at it, Vite doesn’t support streaming imports like Snowpack and wmr do. That means npm-installing dependencies as usual.
354 |
355 | One cool thing is that Vite includes [experimental support](https://vitejs.dev/guide/ssr.html) for server-side rendering. Pick your framework of choice and generate static HTML that ships directly to the client. At the moment, it looks like we need to construct this architecture on our own, but still, this looks like a good opportunity for meta-frameworks to be built on top of Vite. Evan You already has a work in progress called [VitePress](https://vitepress.vuejs.org/), a replacement for [VuePress](https://vuepress.vuejs.org/) with the benefits of using Vite. And Sveltekit has also [added Vite to its dependency list](https://svelte.dev/blog/sveltekit-beta). It looks like [CSS code splitting](https://vitejs.dev/guide/features.html#css-code-splitting) inclusion was part of the reason Sveltekit made the switch to Vite.
356 |
357 | #### [](#supported-files)Supported files
358 |
359 | For CSS, Vite provides the most features out of all of the tools that we are looking at. It supports bundling CSS imports as well as CSS modules. But we can also `npm install` PostCSS plugins and create a `postcss.config.js` file, and Vite will automatically start applying these transforms to CSS.
360 |
361 | We can install and use CSS preprocessors — simply `npm install` the preprocessor and rename the file to the right extension (e.g. `.filename.scss`) and Vite will start applying the corresponding preprocessor. And, as we said in the overview, Vite support CSS code-splitting.
362 |
363 | Image imports default to a public URL, but we’re also able load them into the bundle as strings by using a `?raw` parameter at the end of the URL string.
364 |
365 | JSON files can be imported in the source and converted into an esmodule exporting a single object. We can also provide a named import and Vite will look in the root field of the JSON file to find the import and treeshake the rest.
366 |
367 | #### [](#production-build)Production build
368 |
369 | Vite uses Rollup for a preconfigured production build with a bunch of optimizations. It intentionally provides a zero-config build which should be enough for most use cases.
370 |
371 | The build comes with the Rollup features we expect: bundling, minification and tree shaking. But we also get extras, like code-splitting dynamic imports and something called “asynchronous chunk loading” which is a fancy way to say that if we request a JavaScript module that imports another module, the build will be pre-optimized to load both at the same time (asynchronously).
372 |
373 | Running Vite’s default build with the Snap Shot app I ended up with one 5KB JavaScript file and one 160KB JavaScript file (for a grand total of 165KB) and all CSS in the project was automatically minified to a tiny 2.71KB file.
374 |
375 | #### [](#overall)Overall
376 |
377 | The opinionated nature of Vite makes it a serious competitor with our current tooling. A lot of work has been done to make the developer experience really seamless and make production-ready builds out of the box.
378 |
379 |
Vite
Templates for multiple front end frameworks
✅
Hot module replacement development server
✅ (when using templates)
Streaming imports
❌
Preconfigured production build
✅
Automatic PostCSS and preprocessor conversion
✅
HTM transform
❌
Rollup plugin support
✅
Size on disk (default install)
17.1 MB
380 |
381 | ### [](#wmr)wmr
382 |
383 | 
384 |
385 | Like Vite, wmr is another opinionated build tool that provides both a development server and a build step. It was built by the creator of [Preact](https://preactjs.com/), [Jason Miller](https://twitter.com/_developit), so it’s definitely a happy path for Preact developers. Jason Miller explained the thinking behind wmr when [he appeared as a guest on the JS Party podcast](https://changelog.com/jsparty/158):
386 |
387 | > Preact is tiny and it’s really good if you want to do a lightweight project. Where is our tooling for that? We have a webpack-based tool that’s used in used in production by a bunch of high profile sites, but that’s the heavyweight tool. Where’s the prototyping tool? That was the one hand. The other hand is myself and a bunch of others who happened to be on the Preact team; We had been kind of on the sidelines of the bundler ecosystem for a little while, prodding people, trying to get consensus on a direction that we can move in to further this idea of writing modern code and shipping modern code.
388 |
389 | This tells us that wmr is all about writing and shipping modern code, enabling lighter tooling in a project.
390 |
391 | You might be wondering what wmr stands for? Nothing! The names “Web Modules Runtime” and “Wet Module Replacement” were floated, but it’s a fake abbreviation, similar to npm.
392 |
393 | wmr is built with the same ruthless bundle size purging as Preact, so it’s tiny — weighing a mere 2.6 MB — and contains exactly zero npm dependencies. Still, it manages to pack in a whole lot of really awesome features including a hot-module-replacing development server and an optimized production build.
394 |
395 | #### [](#use-cases)Use cases
396 |
397 | I would use wmr if I was looking to create a prototype using Preact as fast as possible. There’s no configuration and it only takes seconds to download. It feels like using a supercharged static file server. With TypeScript, an optimized-build step, and static HTML rendering, wmr offers everything needed to ship small-to-medium sized applications. Its small size is also great for quickly trying out a library or demoing an idea.
398 |
399 | wmr may not be the tool for you if you’re not using Preact, React or vanilla JavaScript. The Preact team has yet to provide templates for other frameworks. The documentation also isn’t as detailed as the other tools we’ve looked at. This means the further you stray from the happy path, the more you’ll dig into the source. So, I can’t recommend it if a lot of customization is needed.
400 |
401 | #### [](#setup)Setup
402 |
403 | If you’re using preact there is absolutely zero setup needed except for a quick npm install. To use React with wmr instead of Preact, there are currently two steps to take. First, alias `htm/preact` to `htm/react`, and `react` to `es-react` in your `package.json`:
404 |
405 | ```javascript
406 | "alias": {
407 | "htm/preact": "htm/react",
408 | "react": "es-react"
409 | },
410 | ```
411 |
412 | Then add imports from `es-react` into your components:
413 |
414 | ```javascript
415 | // ReactDOM only needed on root render
416 | import { React, ReactDOM,} from 'es-react';
417 | ```
418 |
419 | This means we aren’t actually using the normal React package you might be used to, but instead pulling in React from [es-react](https://github.com/lukejacksonn/es-react). This is because wmr relies on packages being compatible with native JavaScript modules. React does not use native modules by default, instead using an older style of module called [UMD modules](https://github.com/umdjs/umd). es-react is a package that pulls in React but provides exports that are compatible with the web platform.
420 |
421 | This illustrates wmr’s philosophy of using native primitives of the web platform as opposed to using tooling to sidestep and abstract it away.
422 |
423 | Another option could be to use Skypack imports in our application, which is also pre-optimized to work in the browser:
424 |
425 | ```javascript
426 | import React from 'https://cdn.skypack.dev/react';
427 | import ReactDOM from 'https://cdn.skypack.dev/react-dom';
428 | ```
429 |
430 | wmr expects that you’re writing modern code which runs in the browser, which may mean that you’ll need to do some configuration if you pull in dependencies that use node APIs or legacy module systems. To get the Snap Shot app working I needed to dive into node modules and convert a library or two to use native JavaScript module syntax. This might slow you down if you are using older libraries. The Preact ecosystem is all optimized to run in the browser and shouldn’t require any massaging. This is another reason to stick with the Preact happy path in wmr.
431 |
432 | There are plugins for wmr. It exposes a plugin API that supports Rollup plugins for a build step. There are more and more wmr-specific examples on the docs, including a plugin that [minifies](https://github.com/preactjs/wmr/wiki/Configuration-Recipes#minifying-html) [HTML](https://github.com/preactjs/wmr/wiki/Configuration-Recipes#minifying-html), and one that features [filesystem](https://github.com/preactjs/wmr/wiki/Configuration-Recipes#filesystem-based-routing--page-component-loading)[–](https://github.com/preactjs/wmr/wiki/Configuration-Recipes#filesystem-based-routing--page-component-loading)[based routing](https://github.com/preactjs/wmr/wiki/Configuration-Recipes#filesystem-based-routing--page-component-loading).
433 |
434 | wmr supports different frameworks, but there aren’t any pre-built templates for them. And at first I found it rather difficult to configure the JSX transform. All that said, Jason has confirmed there are plans to make JSX more configurable and that wmr is intended to be framework-agnostic. JSX is planned to work out of the box in regular JavaScript files.
435 |
436 | #### [](#usage)Usage
437 |
438 | To get started you can either run this command in the command line:
439 |
440 | ```shell
441 | npm init wmr your-project-name
442 | ```
443 |
444 | Or alternatively, you can run these commands to manually build up your application:
445 |
446 | ```shell
447 | npm init -y
448 | npm install wmr
449 | mkdir public
450 | touch public/index.html
451 | touch public/index.js
452 | ```
453 |
454 | Then add an script import in the body of your `index.html` (again make sure to use `type="module"`):
455 |
456 | ```html
457 |
458 | ```
459 |
460 | Now you can write a Preact hello world into your `index.js` file:
461 |
462 | ```javascript
463 | import { render } from 'preact';
464 | render(
Hello World!
, document.body);
465 | ```
466 |
467 | And finally start your development server:
468 |
469 | ```shell
470 | node_modules/.bin/wmr
471 | ```
472 |
473 | Now we have a full hot module replacement development server which will immediately respond to any changes to our source-code.
474 |
475 | wmr uses a tool called [htm](https://github.com/developit/htm) when transforming JSX, which provides some awesome benefits. Let’s say we’re writing a counter using Preact in wmr and make a mistake:
476 |
477 | ```jsx
478 | import { render } from 'preact';
479 | import { useState } from 'preact/hooks';
480 | function App() {
481 | const [count,setCount] = useState(0)
482 | return <>
483 | // HIGHLIGHT
484 |
count: {count}
485 | >
486 | }
487 | render(, document.body);
488 | ```
489 |
490 | `count` is misspelled in the `onClick` handler function, so running this results in an error. Usually, we would have to rely on our tooling and a source map to gather information about where the bug is located, but wmr takes a different solution. With htm, this gets as close as you can get to native JSX in the browser by using tagged template literals. So, where writing React or Preact code usually looks like this:
491 |
492 | ```jsx
493 | I am JSX. I am not actually valid Javascript
494 | ```
495 |
496 | …htm looks more like this:
497 |
498 | ```html
499 | html`<${MyComponent}>I am about as close as it gets to JSX as you can get while being able to run in the browser`
500 | ```
501 |
502 | Now, if we’re debugging our code and open the “Sources” panel in DevTools, we should see a script that’s almost identical to what the source code looks like in the editor.
503 |
504 | 
505 |
506 | This way, we can properly investigate where bugs are located in the browser without having to use sourcemaps. Sure, this specific example is pretty contrived, but you can see how this could be really useful because it means wmr doesn’t need source maps in your developer environment.
507 |
508 | wmr supports streaming imports by default, so bare imports will be pulled down from the npm registry. This is done through a complex process which examines all of the source in the npm package, removes all the tests and metadata, and converts it to a single native JavaScript import. Similar to Snowpack, it’s possible to build a complex app without using npm to install anything. In fact, wmr is the first tool to support this idea.
509 |
510 | #### [](#supported-files)Supported files
511 |
512 | As far as the other types of files wmr supports, CSS files can be imported in JavaScript, and CSS modules are supported as well.
513 |
514 | There isn’t any built-in support for either Vue single file components or Svelte components. However, wmr’s build step works with Rollup plugins and the development server can be configured with [Polka](https://github.com/lukeed/polka)/[Express](https://expressjs.com/) middleware, so it’s possible to use these to convert imports into Vue and Svelte components. In fact, I wrote a [small plugin for Vue Single file Components](https://github.com/Elliotclyde/wmr-vue-plugin) to show how this might be done.
515 |
516 | We can’t import images into JavaScript as data URLs in wmr without a plugin. Instead, we need to import them using a syntactically correct JavaScript method. So, if we have, say, a picture of a dog in our public folder we might include it in a Preact component like this:
517 |
518 | ```javascript
519 | function Dog() {
520 | return
521 | }
522 | ```
523 |
524 | And once the build step runs, the image is copied and accessible from the distribution folder. There is hot module replacement for images in the development server, so changes with images are immediately reflected in the browser.
525 |
526 | One more note on file support: JSON can be imported, and is converted into a JavaScript object for use. But when actually building an application, we’d need the [Rollup JSON plugin](https://github.com/rollup/plugins/tree/master/packages/json).
527 |
528 | #### [](#production-build)Production build
529 |
530 | wmr provides a production build step that includes bundling, minification and tree-shaking without any additional dependencies. Having a look at the source of wmr, it looks like rollup and terser are used under the hood, and minified versions of these are included in the wmr package. The wmr bundle for the Snap Shot app was 164KB so it created a bundle just a tiny bit smaller than total size of the two JavaScript files created by Vite.
531 |
532 | There is also a way to configure wmr is such a way that it renders an application to static HTML and hydrates on the browser using [preact-iso](https://www.npmjs.com/package/preact-iso). This means wmr can be used as a meta-framework for Preact, similar to Next.js.
533 |
534 | #### [](#overall)Overall
535 |
536 | I love the experience of using wmr to prototype both React and Preact applications. It feels great to get started with a tool that is ridiculously small but provides developer conveniences that get close to matching heavyweight bundlers.
537 |
538 |
wmr
Templates for multiple front end frameworks
✅
Hot module replacement development server
✅
Streaming imports
✅
Preconfigured production build
✅
Automatic PostCSS and preprocessor conversion
❌
HTM transform
✅
Rollup plugin support
✅
Size on disk (default install)
2.57 MB
539 |
540 | ### [](#feature-comparison)Feature comparison
541 |
542 | We just covered a lot ground! Rather than scroll up and down this article to compare results, I’ve compiled everything here to see how the tools stack up wen they’re side-by-side. I’ve even included additional comparisons for features that we didn’t explicitly covers.
543 |
544 | #### [](#use-cases)Use cases
545 |
546 |
Tool
Use case
esbuild
Large codebases. Not ready for production yet.
Snowpack
Small applications that don’t need bundling or applications where you want to choose which bundler you use. Also good for incrementally adopting JavaScript frameworks in server-rendered apps.
Vite
Replacement for Vue CLI/Create-React-App for producing single page applications. This is the happy path for Vue.
wmr
Prototypes. Good for small- to medium-size apps and can be used for either single page or server-rendered apps. This is the happy path for Preact.