123. 精读《用 Babel 创造自定义 JS 语法》


1 引言

在写这次精读之前,我想谈谈前端精读可以为读者带来哪些价值,以及如何评判这些价值。

前端精读已经写到第 123 篇了,大家已经不必担心它突然停止更新,因为我已养成每周写一篇文章的习惯,而读者也养成了每周看一篇的习惯。所以我想说的其实是一种更有生命力的自媒体运作方式,定期更新。一个定期更新的专栏比一个不不定期更新的专栏更有活力,也更受读者喜爱,因为读者能看到文章之间的联系,跟随作者一起成长。个人学习也是如此,养成定期学习的习惯,比在培训班突击几个月更有用,学会在生活中规律的学习,甚至好过读几年名牌大学。

前端精读想带给读者的不仅是一篇篇具体的内容和知识,知识是无穷无尽的,几万篇文章也说不完,但前端精读一直沿用了“引言-概述-精读-总结”这套学习模式,无论是前端任何领域的问题,还是对人生和世界的思考都可以套用,希望能为读者提供一套学习思维框架,让你能学习到如何找到好的文章,以及如何解读它。

至今已经选择了许多源码解读的题材,与培训思维的源码解读不同,我希望你不要带着面试的目的学习源码,因为这样会让你只局限在 react、vue 这种热门的框架上。前端精读选取的框架类型之所以广泛,是希望你能静下心来,吸取不同框架风格与作者的优势,培养一种优雅编码的气质。

进入正题,这次选择的文章 《用 Babel 创造自定义 JS 语法》 也是培养编码气质的一类文章,虽然对你实际工作用处不大,但这篇文章可以培养几个程序员梦寐以求的能力:深入理解 Babel、深入理解框架拓展机制。理解一个复杂系统或培养框架思维不是一朝一夕的,但持续阅读这种文章可以让你越来越接近掌握它。

之所以选择 Babel,是因为 Babel 处理的一直是语法树相关的底层逻辑,编译原理是程序世界的基座之一,拥有很大的学习价值。所以我们的目的并不是像文章标题说的 - 创造一个自定义 JS 语法,因为你创造的语法只会让 JS 复杂体系更加混乱,但可以让你理解 Babel 解析标准 JS 语法的原理,以及看待新语法提案时,拥有从实现层面思考的能力。

最后,不必多说,能重温 Babel 经典的插件机制,你可以发现 Babel 的插件拓展机制和 Antrl4 很像,在设计业务模块拓展方案时也可以作为参考。

2 概述

我们要利用 Babel 实现 function @@ 的新语法,用 @@ 装饰的函数会自动柯里化:

// '@@' makes the function `foo` curried
function @@ foo(a, b, c) {
return a + b + c;
}
console.log(foo(1, 2)(3)); // 6

可以看到,function @@ foo 描述的函数 foo 支持 foo(1, 2)(3) 这种柯里化调用。

实现方式分为两步:

  1. Fork babel 源码。

  2. 创建一个 babel 转换器插件。

不要畏惧这些步骤,“如果你读完了这篇文章,你将成为同事眼中的 Babel 大神” - 原文。

首先 Fork babel 源码到本地,执行下面的命令可以初始化并编译 babel:

$ make bootstrap
$ make build

babel 使用 Makefile 执行编译命令,并且采用 monorepo 管理,我们这次要关心的是 package/babel-parser 这个模块。

词法

首先要了解词法知识,更详细的可以阅读原文或精读之前的一篇系列文章:精读《词法分析》。

要解析语法,首先要进行词法分析。任何语法输入都是一个字符串,比如 function @@ foo(a, b, c),词法分析就是要将这个长度为 24 的字符拆分为一个个有语义的单词片段:function @@ foo ( a ..

由于 @@ 是我们创造的语法,所以我们第一个任务就是让 babel 词法分析可以识别它。

下面是 package/babel-parser 的文件结构:

- src/
- tokenizer/
- parser/
- plugins/
- jsx/
- typescript/
- flow/
- ...
- test/

可以看到,分为词法分析 tokenizer,语法分析 parser,以及支持一些特殊语法的插件,以及测试用例 test

推荐使用 Test-driven development (TDD) - 测试驱动开发的方式,就是先写测试用例,再根据测试用例开发。这种开发方式在后端或者 babel 这种底层框架很常见,因为 TDD 方式开发的逻辑能保证测试用例 100% 覆盖,同时先看测试用例也是个很好的切面编程思维。

// packages/babel-parser/test/curry-function.js

import { parse } from '../lib';

function getParser(code) {
return () => parse(code, { sourceType: 'module' });
}

describe('curry function syntax', function() {
it('should parse', function() {
expect(getParser(`function @@ foo() {}`)()).toMatchSnapshot();
});
});

可以利用 jest 直接测试这段代码:

BABEL_ENV=test node_modules/.bin/jest -u packages/babel-parser/test/c

结果会出现如下报错:

SyntaxError: Unexpected token (1:9)

at Parser.raise (packages/babel-parser/src/parser/location.js:39:63)
at Parser.raise [as unexpected] (packages/babel-parser/src/parser/util.js:133:16)
at Parser.unexpected [as parseIdentifierName] (packages/babel-parser/src/parser/expression.js:2090:18)
at Parser.parseIdentifierName [as parseIdentifier] (packages/babel-parser/src/parser/expression.js:2052:23)
at Parser.parseIdentifier (packages/babel-pars

第 9 个字符就是 @,说明程序现在还不支持函数前面的 @ 解析。我们还可以在错误堆栈中找到报错位置,并把当前 Token 与下一个 Token 打印出来:

// packages/babel-parser/src/parser/expression.js

parseIde
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值