首页 > 其他分享 >前言

前言

时间:2022-09-18 10:37:04浏览次数:89  
标签:前言 通天塔 常量 代码 节点 模板 构造函数

前言

相信现在大部分前端同学都听说过并用过 通天塔 ,其官网自我介绍如下: Babel 是一个 JavaScript 编译器 .在开发中,我们最常使用它来做 ES6+ 语法方向 ES5 还会配合打包工具做压缩、混淆等工作,其工作过程可以简单概括为三个步骤:

  • 解析:将代码转换为抽象语法树( ast )
  • 兑换: 通天塔 或相关插件 ast 执行操作转换
  • 生成:使用新的抽象语法树重新生成代码

从以上三个步骤可以看出,它使用抽象语法树来贯穿整个过程。基于抽象语法树,我们很容易做一些静态分析。本文引用了我在实际工作中遇到的三个例子,并使用 通天塔 解决这三个实际问题。在我们开始之前,有必要介绍一下 通天塔 相关概念和 API .

相关概念

我们先介绍几个会用到的包:

  • @babel/cli 通天塔 用于从命令行编译文件的命令行客户端。
  • @babel/解析器 : 将代码解析为 ast
  • @babel/遍历 : 遍历 ast
  • @babel/模板 : 转换中 ast 在此过程中,需要更换节点。这个库可以用静态声明代替,下面会提到。
  • @babel/发电机 :将要 ast 生成为代码
  • @babel/类型 : 用于生成节点或检查节点类型的工具包

介绍完会用到的包,再介绍一些 通天塔 插件概念。插件返回一个函数,在这个函数中, 通天塔 帮助我们穿越 ast ,我们使用访问者模式为每个节点类型定义处理函数。如下:

 导出默认函数(){  
 返回 {  
 访客:{  
 标识符:{  
 输入:(路径){  
  
 },  
 退出:(路径){  
  
 }  
 },  
 },  
 };  
 }  
 复制代码

标识符 是一种节点,它可以是一个对象 进入 属性和 出口 属性。 ast 是一个树形结构, 进入 是从根节点到子节点的方向遍历时触发的回调函数。 出口 是子节点回溯到根节点时触发的回调函数。插件开发完成后,可以在项目根目录下下载 .babelrc 该插件在文件中被引用。

 {  
 “插件”:[“./src/plugins/autoBindThis”]  
 }  
 复制代码

以上是对本文将用到的一些包和一些概念的简单介绍。更详细的文档内容可以移到以下两个链接:

babel中文网站 通天塔 各种相关概念和教程

babel-plugin-handbook : 插件开发的相关教程和思路

那么废话不多说,让我们进入实战吧。

自动绑定这个

做过 反应 开发的同学应该知道,我们有时会这样写代码:

 类比较{  
 构造函数(){  
 这个。函数 = 这个。功能绑定(这个)  
 }  
 函数(){  
 这个。设置状态({  
 //xxx  
 })  
 }  
 使成为() {  
 return < 子函数 = {this.func}/ >  
 }  
 }  
  
 复制代码

因为 功能 该函数在子组件中调用,如果 功能 在函数中有 这个 相关操作,然后 这个 最后一点将是不准确的。之前常用的写法是在构造函数中使用。 绑定 显式绑定一次。然后了解之后 通天塔 之后,您可以使用 通天塔 来解决这个问题。

思路也很清晰,找到所有的函数定义,去掉一些肯定不需要绑定的函数——比如 构造函数 ,组件的生命周期钩子等;然后在 构造函数 每个函数的函数 这个 绑定。但需要注意的一点是,如果原始代码不存在 构造函数 函数,那么这个插件必须先 构造函数 功能来补充。开发中 通天塔 相关的事情, 探索者 这个网站是必不可少的。

s4iqhn2pqz.png

在这里我们可以看到代码被解析成抽象语法树的数据结构,我们也可以很方便的看到对应的节点及其属性。那么我们先实现第一个需求:判断原始代码是否存在 构造函数 函数,如果不存在则需要添加。

e79e928dvc.png

在这里你可以看到 类体 这个节点,它的 身体 属性对应这个类的所有方法,所以我们只需要遍历数组来判断它是否存在。 构造函数 函数,存在则忽略,不存在则添加。如何弥补?如果是刚入门的话,可能上手有点困难,建议在上面的网站上写一篇。 构造函数 函数来查看它的数据结构是什么样的。

qcx2h0hybd.png

从上图可以看出,它是一个 类方法 输入,然后有 种类 , 钥匙 以此类推,然后配合我们小编的提示:

zaq4n1wypm.png

只知道它的参数应该怎么传,这两个东西互相配合,然后构造出我们需要的东西 节点 .具体代码实现如下:

 模块。出口=函数({类型:t}){  
 返回 {  
 访客:{  
 类体:{  
 输入(路径){  
 const allMethods = 路径。节点。身体  
 //判断构造函数是否存在  
 const 构造函数Exist = allMethods。查找(方法 => 方法。种类 === '构造函数')  
 if (!constructorExist) {  
 // 构造一个构造函数并将其填充到数组中  
 小路。节点。身体。取消移位(  
 吨。类方法(  
 '构造函数',  
 吨。标识符('构造函数'),  
 [],  
 吨。块语句([])  
 )  
 )  
 }  
 }  
 }  
 }  
 };  
 };  
 复制代码

然后删除一些不需要的绑定 这个 函数,遍历所有剩余函数,转到 构造函数 给函数添加绑定代码,也就是添加类似 this.fun = this.fun.bind(this) 这样的声明。

m2yp3c1f21.png

每个句子都属于 表达式语句 类型,存储在 身体 在属性中。所以我们需要构造 节点 塞满 身体 在属性中。

40xjvun2jo.png

以上是绑定语句的抽象语法树的节点信息。看起来这只是一行代码,但是构造起来还是挺麻烦的:

 常量 subAst = t。表达式语句(  
 吨。赋值表达式(  
 '=',  
 吨。成员表达式(  
 吨。 thisExpression(), t。标识符(路径。节点。键。名称)  
 ),  
 吨。调用表达式(  
 吨。成员表达式(  
 吨。 memberExpression(t.thisExpression(), t.identifier(path.node.key.name)),  
 吨。标识符('绑定')  
 ),  
 [吨。这个表达式()]  
 )  
 )  
 )  
 复制代码

这时候就可以使用上面提到的 通天塔模板 这个包,只需要下面几行简单的代码就可以实现上述功能:

 常量模板 = 要求('@babel/模板');  
 常量模板字符串 = 模板。默认(`this.METHOD = this.METHOD.bind(this)`)  
 常量 subAst = 模板字符串({  
 方法:t。标识符(路径。节点。键。名称)  
 })  
 复制代码

最后,我们来看看这个插件实现的完整代码:

 const disabled = [ 'constructor', 'render' /*其他生命周期函数...*/]  
 常量模板 = 要求('@babel/模板');  
 模块。出口=函数({类型:t}){  
 返回 {  
 访客:{  
 类体:{  
 输入(路径){  
 const allMethods = 路径。节点。身体  
 const constructorExist = [...allMethods]。查找(方法 => 方法。种类 === '构造函数')  
 if (!constructorExist) {  
 小路。节点。身体。取消移位(  
 吨。类方法('构造函数',  
 吨。标识符('构造函数'),  
 [],  
 吨。块语句([])  
 )  
 )  
 }  
 },  
 },  
 类方法:{  
 输入(路径){  
 if (!disabled.includes(path.node.kind) && !disabled.includes(path.node.key.name)) {  
 const 构造函数 = 路径。容器。 find(method => method.kind === 'constructor');  
 如果(构造函数){  
 常量模板字符串 = 模板。默认(`this.METHOD = this.METHOD.bind(this)`)  
 常量 subAst = 模板字符串({  
 方法:t。标识符(路径。节点。键。名称)  
 })  
 构造函数。身体。身体。推(  
 亚斯特  
 )  
 }  
 }  
 }  
 },  
 }  
 };  
 };  
 复制代码

然后你可以通过 babel-cli 命令行工具调用 通天塔 编译相应的文件 - 像这样 npx babel test.js --out-file ./out/test.js ,别忘了 .babelrc 该插件在文件中配置。结果如下:

9ksudl9le1.png

在实践这个的过程中,我个人觉得一开始如何构建节点是比较无能的。希望通过上面的展示,能给大家一些思路。

生成文档

接下来我们来看一个生成文档的案例,这也是实际场景中遇到的需求。不管是开发前端组件,还是写后端 API ,如果开发完成后没有对应的文档,别人很难一下子看出来怎么用。但是很多时候,写完相应的MD文档,人们甚至都懒得去维护它。所以这里我想做一个工具,根据一些注解信息来生成文档,方便大家在写注解的过程中写文档。如果其他人甚至不想写这些笔记怎么办? 埃斯林特+哈士奇 可以帮助你,我会写一篇关于 埃斯林特 , 样式表 Plugin 的文章,但是是时候在那里展开了。实际上 沙哑 这并不是要阻止所有人,总有人不假装置身于自己的环境中。所以要彻底解决这个问题,个人的想法应该是禁止直接提交 掌握 分支(发布分支),仅通过 特征 分支合并到 掌握 , 强制卡 先生 .有点扯远了,下面正式看一下这个工具的具体实现。

假设我现在有这样一个服务器文件:

 常量表达 = 要求('表达')  
 常量应用程序 = 快递()  
 常量端口 = 3000  
  
 /** * @path /login * @param username 用户名字符串 必填 * @param password 密码字符串 必填 */  
 应用程序。 get('/login', (req, res) => {  
 水库发送('你好世界!')  
 })  
  
 /** * @path /user * @param userId userid string 必填 * @param fields 要查询的字段数组|undefined */  
 应用程序。 get('/user', (req, res) => {  
 水库发送('用户信息')  
 })  
  
 应用程序。听(端口,()=> {  
 安慰。 log( `监听端口 ${port} 的示例应用程序`)  
 })  
 复制代码

我希望把 登录 , 用户 提取这两个接口前面的注释,生成接口文档。我应该怎么办?老规矩,第一 探索者 看看他们长什么样。

b4d07sg6li.png

注意节点有 领先的评论 尾随评论 这两个属性分别代表节点前后的评论。我们规定相关的注释写在节点前面,所以需要处理。 领先的评论 足够的。总体思路如下:

  • 读取所有要处理的文件,生成 ast
  • 正确的 表达式语句 要处理的节点类型,这取决于情况。
  • 处理节点 领先的评论 ,按照约定的前缀和字段顺序拼接字符串,输出到 医学博士 在文件中

我只是贴出整体代码,行数不多。感觉代码里全是“业务”处理,所以只贴一些注释,不再展开。

 const { parse } = require('@babel/parser')  
 const traverse = require('@babel/traverse')  
 常量路径 = 要求('路径')  
 常量目录 = 路径。解决(__dirname,'./server')  
 常量 fs = 要求('fs')  
 // 读取所有文件  
 常量文件 = fs。读取目录同步(目录)  
 常量文档 = {}  
 文件。 forEach(文件名 => {  
 常量文件路径 = ` ${dir} / ${filename} `  
 if (!doc[文件名]) doc[文件名] = ``  
 // 读取文件内容  
 常量内容 = fs。 readFileSync(文件路径,{编码:'utf8'})  
 // 生成 ast  
 常量 ast = 解析(内容)  
 遍历。默认(AST,{  
 表达式语句:{  
 输入(路径){  
 if (path.node.leadingComments && path.node.leadingComments.length > 0) {  
 让评论=路径。节点。领先的评论[0]。价值  
 让 tableHead = false  
 评论=评论。拆分('\n')。地图(项目=>项目。replaceAll('*','')。修剪())。过滤器(v => !!v)  
 评论。 forEach(线=> {  
 常量结果 = 行。分裂( ' ')。过滤器(v => !!v)  
 常量评论名 = 结果[0]  
 // 添加接口地址  
 if (commentName == '@path') {  
 常量 apiPath = 结果[1]  
 doc[文件名] += ` ## ${apiPath} `  
 }  
 if (commentName == '@param') {  
 // 添加标题  
 如果(!tableHead){  
 doc[filename] += `|参数名称|参数说明|类型|必填| |---|---|---|---| `  
 表头 = 真  
 }  
 让 [_, fieldName, desc = null, type = null, required = 'NoRequired'] = 结果  
 文档[文件名] += `| ${字段名} | ${描述} | ${type.replaceAll( '|' , '\\|' )} | ${必需 === '必需' ?真:假} | `  
 }  
 })  
 }  
 }  
 }  
 })  
 目的。键(文档)。 forEach(文件名 => {  
 // 输出文档  
 fs。写入文件同步(  
 小路。解决(__dirname, `./doc/ ${filename.replace( '.js' , '.md' )} `),  
 doc[文件名],  
 { 编码:'utf8' }  
 )  
 })  
 })  
  
 复制代码

最终生成的界面文档如下所示:

scdm7zp4fj.png

其实代码的实现并不难,只是这个生成文档的代码实现很难抽象出一个公共方法。现在有一些现成的库,比如 jsdoc .但是如果这些库不能满足你的专业化需求,你可以考虑使用上面的方法来实现自己的一套工具。上面是一个接口文档的例子,当然你也可以把它当成组件使用文档等等。

全球化

国际化也是我们在业务中经常遇到的要求。实现方式可以是使用一些库,也可以是自己实现一个。 翻译 函数,然后在代码中写各种文字的时候需要用到这个 翻译 功能来设置它。其实也可以用 通天塔 的静态分析功能,自动执行此操作,编码时照常编写文本,之后 通天塔 转换后,生成的代码会自动为你戴上 翻译 功能。

我们这里主要处理两种节点:

  • 纯文本:对应 字符串字面量 类型
  • 模板字符串:对应 模板文字 类型

纯文本

纯文本的处理比较简单,假设我们的翻译函数是 ,或者老办法,在上面的网站上看看 文本 t('文本') 有什么区别,那么你可以快速编写如下代码:

 遍历。默认(AST,{  
 字符串字面量: {  
 输入(路径){  
 常量 { 值 } = 路径。节点  
 如果 (!(  
 小路。父母。类型 === 'CallExpression' && ['t', 'require']。包括(路径。父。被调用者。名称)  
 )  
 ) {  
 常量 astNode = t。调用表达式(  
 吨。标识符('t'),  
 [吨。字符串字面量(值)]  
 )  
 小路。替换(astNode)  
 }  
 }  
 }  
 }  
 复制代码

这里需要注意的是,有些字符串不需要再次套用 功能,例如,它已被自己应用 函数,或对关联路径的引用。然后我们可以使用 用。。。来代替 这种方法来替换节点。

模板字符串

我们再来看看模板字符串的处理,如果是转换前的情况:

 const text3 = ` ${num} :parameter1: ${num1} ${num1} ,参数二:${num2} `  
 复制代码

然后我想要这样的转换:

 const text3 = t(`#num#:参数1:#num1##num1#,参数2:#num2#`,{  
 无论  
 数字1:数字1,  
 数字2:数字2  
 });  
 复制代码

所以我们先观察一下模板字符串 节点

gky8qhvmq2.png

有两个属性需要注意:

  • 仿佛 : 普通文本
  • 表达式 :表达

这两个属性之间有一个规则:表达式的两边必须是普通文本,如果不是,那么这个 里面 价值 该属性是空字符串。使用这个规则,我们就可以开始拼接和替换了。

 模板文字:{  
 输入(路径){  
 const quasis = [...路径。节点。准]  
 常量表达式 = [...路径。节点。表达]  
 让 str = ''  
 常量变量 = 新的 Set()  
 而(准长度){  
 const准=准。转移()  
 str += 准。价值。生的  
 如果(表达式。长度){  
 常量表达式 = 表达式。转移()  
 str += `# ${expression.name} #`  
 // 收集变量  
 变量。添加(表达式。名称)  
 }  
 }  
 常量属性 = []  
 如果(变量。大小> 0){  
 for(让变量的变量){  
 常量属性 = t。对象属性(  
 吨。标识符(变量),  
 吨。标识符(变量)  
 )  
 特性。推(属性)  
 }  
 }  
 常量参数 = [  
 吨。模板文字(  
 [  
 吨。模板元素({  
 原始:str,  
 熟:str  
 })  
 ],  
 []  
 ),  
 ]  
 如果(属性。长度){  
 参数。推(  
 吨。对象表达式(  
 特性  
 )  
 )  
 }  
 // 构建 t 函数抽象节点  
 常量 astNode = t。调用表达式(  
 吨。标识符('t'),  
 参数  
 )  
 //替换节点  
 小路。替换(astNode)  
 }  
 }  
 复制代码

因为上面花了一些篇幅讲如何构建节点,这里就不详细解释了。替换前后的结果是这样的:

m550ps9q2k.png

翻译功能

最后一步是实现全局翻译功能。实际上,为什么要用那个替换模板字符串中的变量呢?让我们来看看中英文的一些表达方式。在中文里,我们说 我手头有5个项目 ,对应的英文实际上可能是 我有五个项目 .然后我们在代码中写 我手头有 ${num} 件物品 ,对应的英文术语是 我有 ${num} 个项目 ,上述模板字符串的替换方式可以看成是一种中间状态,用一些占位符标记,方便中英文转换时插入变量。

 模块。出口=函数(字符串,参数= {}){  
 常量和 = {  
 '文本':'文本',  
 '#num#:参数1:#num1##num1#,参数2:#num2#':'#num#:param1:#num1##num1#,param2:#num2#',  
 “字符串”:“字符串”  
 }  
 让结果 = en[字符串]  
 if (!result) 返回字符串  
 const reg = /(#([^#][0-9a-zA-Z]+)#)/  
 if ( Object.keys(params).length === 0) {  
 返回结果  
 } 别的 {  
 让 regResult  
 while (regResult = reg.exec(result)) {  
 常量原点 = 结果[1]  
 常量变量 = 结果[2]  
 结果=结果。替换(原点,参数 [变量])  
 }  
 返回结果  
 }  
 }  
 复制代码

所以上述转换后的代码输出如下,整体符合预期:

o006c6urqh.png

最后

本文引用实际应用中遇到的三个例子,并使用 通天塔 解决之道,属于引玉。如果平时在实际场景中使用 通天塔 如果你来做其他事情,请在评论区分享如果你觉得这篇文章有用或有趣,请关注并喜欢它

版权声明:本文为博主原创文章,遵循CC 4.0 BY-SA版权协议。转载请附上原文出处链接和本声明。

这篇文章的链接: https://homecpp.art/3018/8821/0729

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明

本文链接:https://www.qanswer.top/37546/20021810

标签:前言,通天塔,常量,代码,节点,模板,构造函数
From: https://www.cnblogs.com/amboke/p/16704311.html

相关文章

  • .net core微服务系列之前言
    微服务概念其实已经流行了不短的年头了,只是大部分实战都是在以java为主的大型互联网公司使用,.net在国内的市场,作为.net程序猿们都懂得,就拿北京来说,前2年别说微服务了,就......
  • [Django]-前言
    一、PythonWEB框架Python作为当前最流行的解释型编程语言之一,除了丰富的内置模块和第三方模块支持快速开发外,也支持Web开发,与模块类似,Python的WEB开发有很多Web框架。如......
  • 1. SQL--前言
    1.前言SQL是StructuredQueryLanguage的缩写,译为“结构化查询语言”。SQL是一种数据库操作语言,用来检索和管理关系型数据库中的数据,比如插入数据、删除数据、查询数......
  • 由浇花工具开始物联网平台之开始前言篇【1】
    在2020年时,突然有个想法,就是做个浇花工具,因为平时喜欢养花,有时忘记浇花,有时感觉手动浇花太麻烦,所以做个这个小玩意,是用.NET开发的WinForm小程序,来控制单片机,带动水泵浇花,......
  • WPF开发快速入门【0】前言与目录
    前言WPF是一个生不逢时的技术,刚推出的时候由于是XP时代,WPF技术有两个不方便的地方:1、由于操作系统没有自带Framework,需要另外安装,比较麻烦;2、程序第一次启动时,由于要加......
  • 前言 - JavaScript指南
    前  言 读书是一件快乐的事情。读书能够增长知识,了解社会,了解人类的思想,继而转换成智慧。无论是什么人,都需要读书,多读书,读好书,同时也要把书中的精髓记录下......