面条html5,使用 babel 全家桶模块化古老的面条代码

fc2d6f33ea0d6845b66a08a2f9794bc0.png

在最近的工作中,接手了一个古老的项目,其中的 JS 代码是一整坨的面条代码,约 3000 行的代码全写在一个文件里,维护起来着实让人头疼。

45816d943ab2847c4f221ef62cd1c0b5.png

想不通为啥之前维护项目的同学能够忍受这么难以维护的代码……既然现在这个锅被我拿下了,怎么着也不能容忍如此丑陋的代码继续存在着,必须把它优化一下。

横竖看了半天,由于逻辑都揉在了一个文件里,看都看得眼花缭乱,当务之急便是把它进行模块化拆分,把这一大坨面条状代码拆分成一个个模块并抽离成文件,这样才方便后续的持续优化。

一、结构分析

说干就干,既然要拆分成模块,首先就要分析源码的结构。虽然源码内容很长很复杂,但万幸的是它还是有一个清晰的结构,简化一下,就是下面这种形式:

6973d3ec7562cb00f94b4915e5c913f7.png

很容易看出,这是一种 ES5 时代的经典代码组织方式,在一个 IIFE 里面放一个构造函数,在构造函数的 protorype 上挂载不同的方法,以实现不同的功能。既然代码结构是清晰的,那么我们要做模块化的思路也很清晰,就是想办法把所有绑定在构造函数的 prototype 上的方法抽离出来,以模块文件的形式放置,而源码则使用 ES6 的 import 语句把模块引入进来,完成代码的模块化:

aea64eed8fc42d7d78988479736f87fc.png

为了完成这个效果,我们可以借助 @babel 全家桶来构造我们的转化脚本。

二、借助 AST 分析代码

关于 AST 的相关资料一搜一大堆,在这里就不赘述了。在本文中,我们会借助 AST 去分析源码,挑选源码中需要被抽离、改造的部分,因此 AST 可以说是本文的核心。在 https://astexplorer.net/ 这个网站,我们可以贴入示例代码,在线查看它的 AST 长什么样:

aee6dbed92be644725656c56485f7e85.png

从右侧的 AST 树中可以很清晰地看到,Demo.prototype.func = function () {} 属于 AssignmentExpression 节点,即为“赋值语句”,拥有左右两个不同的节点(left,right)。

由于一段 JS 代码里可能存在多种赋值语句,而我们只想处理形如 Demo.prototype.func = function () {} 的情况,所以我们需要继续对其左右两侧的节点进行深入分析。

首先看左侧的节点,它属于一个“MemberExpression”,其特征如下图箭头所示:

07712b850f10a77c5f9183549072cdca.png

对于左侧的节点,只要它的 object.property.name 的值为 prototype 即可,那么对应的函数名就是该节点的 property.name。

接着看右侧的节点,它属于一个“FunctionExpression”:

b76378899bf27616c704c47a740a64f1.png

我们要做的,就是把它提取出来作为一个独立的文件。

分析完了 AST 以后,我们已经知道需要被处理的代码都有一些什么样的特征,接下来就是针对这些特征进行操作了,这时候就需要我们的 @babel 全家桶出场了!

三、处理代码

首先我们需要安装四个工具,它们分别是:@babel/parser:用于把 JS 源码转化成 AST;

@babel/traverse:用于遍历 AST 树,获取当中的节点内容;

@babel/generator:把 AST 节点转化成对应的 JS 代码;

@babel/types:新建 AST 节点。

接下来新建一个 index.js 文件,引入上面四个工具,并设法加载我们的源码(源码为 demo/es5code.js):const fs = require('fs')

const { resolve } = require('path')

const parser = require('@babel/parser')

const traverse = require('@babel/traverse').default

const generator = require('@babel/generator').default

const t = require('@babel/types')

const INPUT_CODE = resolve(__dirname, '../demo/es5code.js')

const code = fs.readFileSync(`${INPUT_CODE}`, 'utf-8')

接着使用 @babel/parser 获取源码的 AST:const ast = parser.parse(code)

拿到 AST 以后,就可以使用 @babel/traverse 来遍历它的节点。从上一节的 AST 分析可以知道,我们只需要关注“AssignmentExpression”节点即可:traverse(ast, {

AssignmentExpression ({ node }) {

/* ... */

}

})

当前节点即为参数 node,我们需要分析它左右两侧的节点。只有当左侧节点的类型为“MemberExpression”且右侧节点的类型为“FunctionExpression”才需要进入下一步分析(因为形如 a = 1 之类的节点也属于 AssignmentExpression 类型,不在我们的处理范围内)。

由于 JS 中可能存在不同的 MemberExpression 节点,如 a.b.c = function () {},但我们现在只需要处理 a.prototype.func 的情况,意味着要盯着关键字 prototype。通过分析 AST 节点,我们知道这个关键字位于左侧节点的 object.property.name 属性中:

6d4de07d9f9203f08b16cd6d3e7f5fb5.png

同时对应的函数名则藏在左侧节点的 property.name 属性中:

e632c196c0b4129d2fb162e608ed1897.png

因此便可以很方便地提取出方法名:traverse(ast, {

AssignmentExpression ({ node }) {

const { left, right } = node

if (left.type === 'MemberExpression' && right.type === 'FunctionExpression') {

const { object, property } = left

if (object.property.name === 'prototype') {

const funcName = property.name // 提取出方法名

console.log(funcName)

}

}

}

})

可以很方便地把方法名打印出来检查:

47ea10dee812773703666da6a29ee618.png

现在我们已经分析完左侧节点的代码,提取出了方法名。接下来则是处理右侧节点。由于右侧代码直接就是一个 FunctionExpression 节点,因此我们要做的就是通过 @babel/generator 把该节点转化成 JS 代码,并写入文件。

此外,我们也要把原来的代码从 Demo.prototype.func = function () {} 转化成 Demo.prototype.func = func 的形式,因此右侧的节点需要从“FuncitionExpression”类型转化成“Identifier”类型,我们可以借助 @babel/types 来处理。

还有一个事情别忘了,就是我们已经把右侧节点的代码抽离成了 JS 文件,那么我们也应该在最终改造完的源文件里把它们给引入进来,形如 import func1 from './func1'这种形式,因此可以继续使用 @babel/types 的 importDeclaration() 函数来生成对应的代码。这个函数参数比较复杂,可以封装成一个函数:function createImportDeclaration (funcName) {

return t.importDeclaration([t.importDefaultSpecifier(t.identifier(funcName))], t.stringLiteral(`./${funcName}`))

}

只需要传入一个 funcName,就可以生成一段 import funcName from './funcName' 代码。

最终整体代码如下:const fs = require('fs')

const { resolve } = require('path')

const parser = require('@babel/parser')

const traverse = require('@babel/traverse').default

const generator = require('@babel/generator').default

const t = require('@babel/types')

const INPUT_CODE = resolve(__dirname, '../demo/es5code.js')

const OUTPUT_FOLDER = resolve(__dirname, '../output')

const code = fs.readFileSync(`${INPUT_CODE}`, 'utf-8')

const ast = parser.parse(code)

function createFile (filename, code) {

fs.writeFileSync(`${OUTPUT_FOLDER}/${filename}.js`, code, 'utf-8')

}

function createImportDeclaration (funcName) {

return t.importDeclaration([t.importDefaultSpecifier(t.identifier(funcName))], t.stringLiteral(`./${funcName}`))

}

traverse(ast, {

AssignmentExpression ({ node }) {

const { left, right } = node

if (left.type === 'MemberExpression' && right.type === 'FunctionExpression') {

const { object, property } = left

if (object.property.name === 'prototype') {

// 获取左侧节点的方法名

const funcName = property.name

// 获取右侧节点对应的 JS 代码

const { code: funcCode } = generator(right)

// 右侧节点改为 Identifier

const replacedNode = t.identifier(funcName)

node.right = replacedNode

// 借助 `fs.writeFileSync()` 把右侧节点的 JS 代码写入外部文件

createFile(funcName, 'export default ' + funcCode)

// 在文件头部引入抽离的文件

ast.program.body.unshift(createImportDeclaration(funcName))

}

}

}

})

// 输出新的文件

createFile('es6code', generate(ast).code)

四、运行脚本

在我们的项目目录中,其结构如下:.

├── demo

│ └── es5code.js

├── output

├── package.json

└── src

└── index.js

运行脚本,demo/es5code.js 的代码将会被处理,然后输出到 output 目录:.

├── demo

│ └── es5code.js

├── output

│ ├── es6code.js

│ ├── func1.js

│ ├── func2.js

│ └── func3.js

├── package.json

└── src

└── index.js

看看我们的代码:

96371d3046f00057796c73a071c69227.png

fb323ca674631a9ba0f4120098ecb1ba.png

大功告成!把脚本运用到我们的项目中,甚至可以发现原来的约 3000 行代码,已经被整理成了 300 多行:

880969ecfb3151ead1b2f15b0fbb21cb.png

放到真实环境去跑一遍这段代码,原有功能不受影响!

小结

刚刚接手这个项目,我的内心是一万头神兽奔腾而过,内心是非常崩溃的。但是既然接手了,就值得好好对待它。借助 AST 和 @babel 全家桶,我们就有了充分改造源码的手段。花半个小时个脚本,把丑陋的面条代码整理成清晰的模块化代码,内心的阴霾一扫而空,对这个古老的项目更是充满了期待——会不会有更多的地方可以被改造被优化呢?值得拭目以待!

b739ec46bb5c46d9c0aa4ce35ba1ea56.png

关于找一找教程网

本站文章仅代表作者观点,不代表本站立场,所有文章非营利性免费分享。

本站提供了软件编程、网站开发技术、服务器运维、人工智能等等IT技术文章,希望广大程序员努力学习,让我们用科技改变世界。

[使用 babel 全家桶模块化古老的面条代码]http://www.zyiz.net/tech/detail-147399.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/559572.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

cad批量打印快捷键_批量打印CAD图(无删减版)

前面两期小编出的PDF教程想必用了的人都觉得还不错吧?(此处应有掌声)上一期提到的CAD批量打印今天放出来了,擦亮眼睛往下看很多时候大批量的一堆图纸要输出,比如下面这个当然这批图纸并不多,也只是局部的,通常一个项目…

docker 容器之间通信_四、Docker 网络原理、分类及容器互联配置

本文是《Docker必知必会系列》第四篇,原文发布于个人博客:悟尘纪。上一篇:Docker必知必会系列(三):基于 Docker-registry/Nexus3 搭建本地仓库Docker 网络配置Docker 网络基本原理要实现网络通信&#xff0…

键盘与鼠标器是微型计算机上最常用的,2016年职称计算机考试WindowsXP考前预测试题5...

填空题1.3.5英寸磁盘的滑块小孔打开时,该盘只能(读),不能(写),称为(写保护)。2.软盘上的HD标记表示(双面高密度)。3.常用的双面高密度3.5英寸盘的容量为(1.44MB)。4.硬盘与软盘相比,具有(容量大)、(价格低)的特点。5.常见的光盘驱…

2020idea插件怎么同步_没有用过这些插件,别说你在用vscode

vscode 插件Rainbow Brackets编码过程中,尤其在我们使用js进行函数式编程时,代码里会有很多的花括号,想要保证它们对称十分困难,所以就出现了上面小粉同学的尴尬局面,相信很多人都遇到过类似的情况。Rainbow Brackets&…

python 删除特定行数据_怎么用 Python 做数据分析实例

01 生成数据表第一部分是生成数据表,常见的生成方法有两种,第一种是导入外部数据,第二种是直接写入数据。 Excel 中的文件菜单中提供了获取外部数据的功能,支持数据库和文本文件和页面的多种数据源导入。获取外部数据python 支持从…

html 地址 点击召唤高德,高德地图api 点聚合+海量点+点击事件(根据地区或坐标进行定位)...

javascript区划聚合海量点展现html,body,#container {width: 100%;height: 100%;margin: 0px;}#loadingTip {position: absolute;z-index: 9999;top: 0;left: 0;padding: 3px 10px;background: red;color: #fff;font-size: 14px;}#right {position: absolute;z-index: 9999;top…

python中集合运算_入门 | 一文带你了解Python集合与基本的集合运算

原标题:入门 | 一文带你了解Python集合与基本的集合运算 选自DataCamp 作者:Michael Galarnyk 参与:Geek Ai、思源 一般我们熟悉 Python 中列表、元组及字典等数据结构,但集合可能用得稍微少一点。但集合独特的元素唯一性与 O(1) …

python中文本文件r_Python如何读写文本文件

展开全部 1.open使用open打开文件后一定要记2113得调5261用4102文件对象的close()方法。比如可以用try/finally语句来确保最后1653能关闭文件。 file_object open(thefile.txt) try: all_the_text file_object.read( ) finally: file_object.close( ) 注:不能把op…

台式计算机性能清单是强制的吗,教你识别良心商家和奸商电脑配置清单区别以及如何选购台式电脑机箱...

我们想要组装一台电脑,由于隔行如隔山,无疑对硬件品牌型号都不太了解,会将自己的预算和大致的要求和商家说,而商家会根据预算与要求写具体的电脑配置清单,不同商家写出来的配置或多或少存在不同,含糊不清写…

mysql显示表已存在_MySQL数据库与数据表的相关操作

数据库相关操作:显示数据库:show databases;如果是0.00秒并不代表没有花费时间,而是时间非常短,小于0.01秒。创建数据库:Query OK表示创建成功,1行受到影响,处理时间为0、05秒。使用下面的命令查…

怎么用计算机算成250,万能计算器

彩票彩宝贝体彩排列五March 29, 2016彩票计划网站导航Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse ultrices egestas nunc, quis venenatis orci tincidunt id. Fusce commodo blandit eleifend. Nullam viverra tincidunt dolor, at pulvinar dui.…

android怎样判断插入数据是否成功_MySQL一个表的自增id用完了,背井大佬让我用这些姿势再往里插数据...

点击上方"码之初"关注,选择"设为星标"与精品技术文章不期而遇在之前有篇文章中,和大家探讨了在MySOL数据库中,一个表的自增id用完,再插入数据有什么问题?评论处 背井 公众号的大佬建议我另开一篇再…

计算机硬件系统教具,计算机硬件系统 (2)

计算机硬件系统 (2) (3页)本资源提供全文预览,点击全文预览即可全文预览,如果喜欢文档就下载吧,查找使用更方便哦!9.9 积分计算机硬件系统克井一中杨致远教学目标:1、了解计算机的发展概况、特点以及种类2、理解计算机的工作原理3…

经典计算机实现量子逻辑门,量子计算机:对量子逻辑门的探讨

在分析了经典比特和量子比特的异同点之后,阐述了量子逻辑门的特点;然后具体介绍了几种常见的量子逻辑门:基本量子逻辑门,量子异或门,量子与门。最后又给出了更复杂的量子逻辑门的构建方法。维普资讯 http://doc.wendoc.com信息科学}J宋纳红侯丽敏科量子计算机&#…

命名空间中不存在名称_原木定制中不开裂的木材真的存在吗?

广大的读者朋友们大家好,之前壹信缅甸柚木高端全屋定制小编和大家讲解了为什么那么多人喜欢原木实木全屋定制护墙板,本文壹信小编将给大家讲讲原木整装中不开裂的木材真的存在吗。原木整装行业的从业人员都知道,最麻烦最让人担心的是木头的开…

go 字符串替换_Go语言爱好者周刊:第 64 期 — goup 这个工具了解下

这里记录每周值得分享的 Go 语言相关内容,周日发布。本周刊开源(GitHub:polaris1119/golangweekly),欢迎投稿,推荐或自荐文章/软件/资源等,请提交 issue 。鉴于大部分人可能没法坚持把英文文章看…

opencore0.6.3_Ubuntu 18.04 源码编译安装 PHP 7.3

记录在Ubuntu 18.04下源码编译安装 PHP 7.3的过程步骤。0.下载PHP源代码首先需要从PHP官网下载PHP7.3.1的源代码,保存为php-7.3.1.tar.xz。http://cn2.php.net/distributions/php-7.3.1.tar.xz在上述文件保存的目录中打开终端,使用命令将其解压&#xff…

photoshop案例_玩手机不如学PS!200集入门到精通Photoshop自学教程分享3

大家都知道Photoshop(PS)的功能非常的强大,它具有强大的绘图、校正图片及图像创作功能!人们可以利用它创作出具有原创性的作品。应用极为广泛,常应用于平面设计、网页设计、插画设计、界面设计、数码照片与图像的修复、…

西浦与杭电计算机选哪个,四邮四电究竟谁强谁弱?哪三所大学报考更有性价比?...

四邮 北京邮电学院 南京邮电学院 西安邮电大学 重庆邮电学院 四电 西安电子科技大学 电子科技大学 桂林电子科技大学 杭州电子科技大学。总体来说:成电西电>北邮>杭电>南邮重邮≈桂电>西邮。实力基本和各校年经费成正比,成电>西电>北邮…

android 获取当前时间_js如何获取当前时间并显示

js可以通过Date对象获取当前日期和时间,使用Date()获取系统当前时间,在使用getFullYear()、getMonth()、getDate() 、getHours()等方法获取特定格式的时间,在使用innerHTM方法显示。web前端学习:打造全网web前端全栈资料库&#x…