写点什么

Babel 还是 Node 开发的“必需品”吗?

  • 2019-09-23
  • 本文字数:4479 字

    阅读完需:约 15 分钟

Babel还是Node开发的“必需品”吗?

现在做 Node 开发还需要“麻烦”的 Babel 吗?毋庸置疑,Babel 曾经对构建和开发 Node.js 应用程序有过很大的影响,但随着 Node.js 的原生功能不断强大,Babel 或许也不再是 Node 开发的“必需品”。本文将主要介绍关于如何在 Node 开发中摆脱 Babel 的方法。



如果你是 Node.js 资深开发人员,乃至涉足了 React 或 Vue.js 等前端库,那么不用说你很有可能跟 Babel 打过交道。Babel 最初曾是 Reddit 上的一个不起眼的项目,但现在已经发展得如此壮大,甚至从根本上改变了我们构建和开发 Node.js 应用程序的方式。


很难准确地形容 Babel 的影响力到底有多大,因为现在它被拆分成了许多小包的形式,但只要看看 npm 的 @Babel/core 包就足见一斑了(提示:它一周的下载量差不多有 800 万次,而 React 才不过 500 万而已!)。


Babel 的确取得了惊人的成就,但它也在某些方面很让人胃疼。首先,现在你得在你的应用程序或库中引入一套构建系统。虽然这本身没那么可怕,但这样做确实带来了许多额外的复杂性和问题:你有没有同时打包兼容 ES 和 ES20XX 版本的库呢?你想要输出到 ECMAScript 规范的哪一个“阶段”?此外我个人觉得最典型的例子就是,你当前的工具集怎样与它配合呢(调试之类的事情)?


当然,我们不能忘了我们的源映射(source maps)老朋友,我们可以用它智能地从已转换的代码逆向到源代码上。如果你正在同时为浏览器和 Node.js 构建,那么事情就更复杂了,因为你还必须为浏览器打包一个版本——啊,好麻烦!


但我要说的是,也许你根本就用不着 Babel。以前只有 Babel 才有的许多酷炫的玩意儿现在都成了 Node.js 的原生功能,也就是说你可以省掉许多依赖项和构建步骤,甚至用不着第三方系统帮你做自动编译了。


认真读完这篇文章后,我希望大家能和我达成共识,看到 Node 开发的“复兴”时代就要来临了——我们不再需要什么构建系统,自然也用不着 Babel!

摆脱 Babel 的第一步:处理模块

JavaScript 开发中比较让人头疼的一部分就是它的模块系统。有些人可能不太熟悉这块,比如说你可能会在 Web 上看到很多这样的语法:


export const double = (number) => number * 2;export const square = (number) => number * number;
复制代码


但要在 Node 中运行上面的代码却不加任何类型的 Babel-ifying(或标志),就会出现以下错误:


export const double = (number) => number * 2;^^^^^^
SyntaxError: Unexpected token export
复制代码


有很多年经历的开发人员可能会回想起 requirejs 和 commonjs 语法流行的时代,彼时和我们现在处理 commonjs 和 ECMAScript 模块语法的方式非常相似。


但是如果你在用 Node 比较新的版本——哪怕是 8.0 也行——那么无需任何转换或 Babel 也能用 ECMAScript 模块。你只需使用 --experimental-modules 开关启动你的应用程序即可:


node --experimental-modules my-app.mjs
复制代码


当然,最关键的是要注意——至少在版本 8 和 10 中——你的文件必须用 mjs 这个扩展名,表明它们是 ECMAScript 模块而不是 CommonJS。在 Node 12 中情况要好得多,只需将一个新属性附加到你的应用程序(或库)的 pacakge.json 即可:


// package.json{    "name": "my-application",    "type": "module" // Required for ECMASCript modules}
复制代码


在 Node.js 12 及更高版本上使用 type 方法时,它还有一个额外的好处,就是加载的所有依赖项都支持 ECMAScript 模块。因此随着越来越多的库迁移到“原生”JavaScript,你用不着再担心当不同的库打包不同的模块系统时如何处理 import 或 require 了。


可以在 Node 的文档站点上阅读更多信息

摆脱 Babel 的第二步:使用现代化的异步控制流程

如果你一直在愉快地使用 Node.js 中更现代化的异步控制流方法(名为 Promise 和搭配它们的 async/await),一个好消息是它们自 Node 8 以来就获得了原生支持!


良好的控制流(特别是针对并行发出请求等操作)是编写快速且可维护的 Node 应用程序的关键所在。要在 Node 8 中使用像 Promise 或 await 这样的东西,其实你什么都用不着准备:


// log.jsasync function delayedLogger(...messages) {    return new Promise((resolve) => {        setImmediate(() => {            console.debug(...messages);            resolve(true);        });    });}
async function doLogs() { delayedLogger('2. Then I run next!'); console.log('1. I run first!'); await delayedLogger('3. Now I run third because I "await"'); console.log('4. And I run last!');}
doLogs();
复制代码


下面这个例子现在很容易就能实现:


node log.js
复制代码


用不着特殊的开关,也不用更新你的 package.json——直接就能搞定!不仅如此,你甚至可以使用这些原生 Promise 尝试捕获未捕获的异常,万一你的应用程序出现问题也能即时发现:


process.on('unhandledRejection', (reason, promise) => {  console.log('Unhandled Rejection at:', promise, '\nMessage:', reason);});
async function willThrowErrors() { return new Promise(function shouldBeCaught(resolve, reject) { reject('I should be caught and handled with!'); });}
willThrowErrors();
复制代码


虽说这很好用,但如果我们需要深入了解异步调用堆栈并查看抛出的内容和背后的机制,有时就会很让人头疼。要启用异步堆栈跟踪,你需要升级到 Node 12 并对特定版本使用 --async-stack-traces 开关。


成功启用后,你就可以更容易地推断出错误的来源,并找出问题的根源所在。举个例子,像下面这样的程序很难看出它到底是怎么出错的:


// app.jsasync function sleep(num) {    return new Promise((resolve) => {        setTimeout(resolve, num);    });}
async function execute() { await sleep(10); await stepOne();}
async function stepOne() { await sleep(10); await stepTwo();}
async function stepTwo() { await sleep(10); await stepThree();}
async function stepThree() { await sleep(10); throw new Error('Oops');}
execute() .then(() => console.log('success')) .catch((error) => console.error(error.stack));
复制代码


在 Node 10 中运行它将返回以下跟踪:


$ node temp.js --async-stack-tracesError: Oops    at stepThree (/Users/joelgriffith/Desktop/app.js:24:11)
复制代码


如果我们切换到 Node 12 上就能获得更好的输出,可以清楚地看到调用的结构:


$ node temp.js --async-stack-tracesError: Oops    at stepThree (/Users/joelgriffith/Desktop/temp.js:24:11)    at async stepTwo (/Users/joelgriffith/Desktop/temp.js:19:5)    at async stepOne (/Users/joelgriffith/Desktop/temp.js:14:5)    at async execute (/Users/joelgriffith/Desktop/temp.js:9:5)
复制代码

摆脱 Babel 第三步:留下语法糖!

Babel 的一大好处就是它从 ES6 开始这么多年积累的一大堆出色的语法糖。有了这些便利,我们就能用更易读和更简洁的方式执行常用操作。但更让我高兴的是,自 Node 的第 6 版以来,大多数语法糖都能直接用了。


我最喜欢的例子之一是解构赋值。这个小捷径做出的下面这种效果更容易理解,并且不需要任何构建系统就能在 Node 中正常运作:


const letters = ['a', 'b', 'c'];
const [a, b, c] = letters;
console.log(a, b, c);
复制代码


如果你只关心第三个元素,那么下面这种代码也能用,就是看起来有点难看。


const stuff = ['boring', 'boring', 'interesting'];
const [,, interesting] = stuff;
console.log(interesting);
复制代码


提到语法糖,对象解构也是开箱即用的:


const person = {    name: 'Joel',    occupation: 'Engineer',};
const personWithHobbies = { ...person, hobbies: ['music', 'hacking'],};
console.log(personWithHobbies);
复制代码


不过对象解构需要 Node 8 以上版本,而数组解构最早获得支持的版本是 Node 6。


最后,现在 Node 6 及以上版本完整支持默认参数了(这是这个语言以前缺少的重要功能)。它会省去程序中(以及 Babel 的转换输出)的大量 typeof 检查,因此你可以执行以下操作:


function messageLogger(message, level = 'debug >') {    console.log(level, message);}
messageLogger('Cool it works!');messageLogger('And this also works', 'error >');
复制代码


其实 Node 中能用的东西太多了,上面举的这些例子也不过是皮毛而已:此外模板字面量、反引号(多行字符串),胖箭头,甚至是 class 关键字都准备好了。

别急,还有呢!

摆脱不必要的依赖项是提高应用程序安全性和可维护性的一个好办法。你不再需要依赖外部维护的软件,不需要等待生态系统进化,于是就能更快地前进。除此之外,移除 Babel 后你实际上也在部署更易读的代码。


例如,Babel 有时会在程序文件的开头注入大量的 polyfill。虽然这些帮助程序在大多数情况下完全无害,但它可能会把新手或不熟悉这种代码的人们绕糊涂了。这条规律一般来说没错:如果新手会被某件事物弄糊涂,那么它可能就不应该加到你的项目里。


如果有人正在使用你的软件包,想要确定问题是来自你的代码还是来自转换器注入的帮助程序就更复杂了。如果你的最终输出中注入的代码没那么多,你也能更好地理解正在构建的程序底层是怎样的结构。


不管是 Babel 也好,其他依赖项也罢,要采用或者抛弃它们都要考虑一点,那就是责任。不管什么时候,只要你引入了没有亲自阅读或了解过的代码,都可能会出一些意料之外的问题。比如说依赖关系太复杂导致 npm install 速度缓慢,比如说模块需要在线打猴子补丁而导致程序启动缓慢,又比如说出现问题却没能正确报告……为了避免这些麻烦,不用 Babel 这样的包可能是最好的。


引入新的模块或构建过程不单单是我们个人的事情,更是我们团队和项目要解决的问题,所以我希望你们更多地把它当作是一种责任(维护它、升级它,并意识到使用它的后果) ,而不仅仅是当成随手拿来用的工具。

最后,为什么你可能还是要用 Babel 呢

虽然 Node 已经进步了这么多,但有时你可能还是要用 Babel 才行。如果你想体验规范中“最新和最好的”那部分,那么 Babel 是你唯一的选择。如果你想无需改动整个构建管道就使用 TypeScript,那么用 Babel 也能做到。


有时 Babel 的代码实际上比 Node 原生方法更快。通常来说这是源于 Node 维护者必须处理的一些边缘情况,这些情况 Babel 不一定要考虑。再过几年,我相信 Node 的性能优势会覆盖所有层面,但是新功能往往会比用户手里的实现慢很多。


最后,如果你需要向 Web 浏览器交付代码,那么在可预见的未来你可能还得继续使用 Babel。像 React 这样的库以及其他用来实现或增强语言的库总归需要一种方法来转换为浏览器可理解的代码。


但如果你的用户群主要使用的是现代化的浏览器,那么放弃构建系统就是利大于弊的,能显著缩小程序体积。这不仅能加快页面加载,而且还能显著提升性能表现,因为哪怕额外的 1KB 内容也可能花费大量时间来处理,毕竟每个字节在执行之前都需要解析和验证!


我希望本文能帮助你编写更好、更快、更安全的 Node.js 应用程序——而且不用 Babel 也能写出很多功能来!


原文链接:


You don’t need Babel with Node


2019-09-23 19:202963

评论

发布
暂无评论
发现更多内容

软件测试/测试开发丨Python 常用数据结构-字典

测试人

Python 软件测试 自动化测试 测试开发

记一次redis主从切换导致的数据丢失与陷入只读状态故障

程序员小毕

数据库 redis 程序员 程序人生 后端

BSN-DDC基础网络详解(十二):算力中心开发者门户部署说明(1)

BSN研习社

自动驾驶成为汽车产业未来趋势,连接器行业迎来进一步发展

华秋电子

Seata-go TCC 设计与实现

阿里巴巴云原生

阿里云 云原生 TCC Seata-go

涨姿势了!分享一个简单好用的源码调试方法

Java永远的神

程序员 程序人生 后端 jdk源码 架构师

为什么 GPU 能够极大地提高仿真速度?

思茂信息

gpu 仿真软件 计算机硬件 仿真技术

含“AI”量超高!那些正在改变潮水方向的人丨创业邦2023年新青年创投榜单重磅发布

创业邦

中移链合约常用开发介绍(五)合约项目编译

BSN研习社

AIGC持续火爆大模型争相推出,庞大市场造就算力供应模式演变

王吉伟频道

nvidia 算力 AI大模型 AIGC 生成式AI

inBuilder今日分享丨智能化开发的现状与未来

inBuilder低代码平台

虚拟显示器软件:BetterDisplay Pro 激活Mac版

真大的脸盆

Mac Mac 软件 显示器校准软件

java异常体系

echoes

从“0”到“1”!低代码开发和云计算的碰撞,引领数字化转型浪潮!

加入高科技仿生人

云计算 低代码 数字化 数智化

全网独一份微服务架构深度解析,连京东师哥都熬夜也要看完

小小怪下士

Java 程序员 微服务架构

如何通过Java代码将 PDF 转为 HTML 格式

在下毛毛雨

Java html PDF 文档转换

软件测试/测试开发丨Python学习笔记-继承与多态

测试人

Python 软件测试 自动化测试 测试开发

Amazon CodeWhisperer代码提示——Golang测评

衝鋒壹号

云原生产品免费试用领取攻略,看看有哪些新玩法?

阿里巴巴云原生

阿里云 云原生

北京站丨云原生技术实践营邀请您参加!

阿里巴巴云原生

阿里云 云原生 实践营

构建系列之webpack窥探下

江湖修行

前端 Web webpack cli

智能的支柱:算法

TiAmo

算法 动态规划 分治 回溯算法 分支限界

加速信创生态建设 焱融科技与优炫软件完成兼容性互认证

焱融科技

文件存储 高性能存储 #分布式存储

理论+实操,带你了解多沙箱容器运行时Kuasar

华为云开发者联盟

云原生 后端 华为云 华为云开发者联盟 企业号 5 月 PK 榜

数据驱动运营增长

MobTech袤博科技

@广州 智在粤港澳,Serverless 为创新提速

阿里巴巴云原生

阿里云 Serverless 云原生

企业敏捷开发的三种落地实践途径

力软低代码开发平台

火山引擎DataLeap数据调度实例的 DAG 优化方案(三):技术实现

字节跳动数据平台

大数据平台 数据检索 DataLeap

关于大型语言模型的争论和局限

OneFlow

自然语言处理 AI大语言模型

云从科技进入百模大战,行业大模型成为胜负手

ToB行业头条

救命稻草!阿里P8耗时5月打造的架构师速成手册,千金难求

程序知音

Java 分布式 java架构 Java进阶 后端技术

Babel还是Node开发的“必需品”吗?_大前端_Joel Griffith Follow_InfoQ精选文章