You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
通过这种只在编译过程中存在的全局常量,我们可以做很多值的匹配。
因为wepy已经预编译了一层,在框架内的业务代码是读取不了process.env.NODE_ENV的值。我就想着要不做一个类似于webpack的DefinePlugin的babel插件吧。具体的思路是babel编译过程中访问ast时匹配需要替换的标识符或者表达式,然后替换掉相应的值。例如: In
原文地址:
https://segmentfault.com/a/1190000013130489
话不多说先上图,简要说明一下干了些什么事。图可能太模糊,可以点svg看看
![流程图](https://camo.githubusercontent.com/4afd032e7aabc9eb4ac5a625d3a0247fb37fab8905d2fde1fd955a26683c6b6c/68747470733a2f2f7365676d656e746661756c742e636f6d2f696d672f6256336673343f773d37373126683d363331)
背景
最近公司开展了小程序的业务,派我去负责这一块的业务,其中需要处理的一个问题是接入我们web开发的传统架构--模块化开发。
我们来详细说一下模块化开发具体是怎么样的。
我们的git工作流采用的是git flow。一个项目会拆分成几个模块,然后一人负责一个模块(对应git flow的一个feature)独立开发。模块开发并与后端联通后再合并至develop进行集成测试,后续经过一系列测试再发布版本。
目录结构大体如图所示,一个模块包含了他自己的pages / components / assets / model / mixins / apis / routes / scss等等。
这种开发模式的好处不言而喻,每个人都可以并行开发,大大提升开发速度。这次就是要移植这种开发模式到小程序中。
目标
背景说完了,那么来明确一下我们的目标。
我采用的是wepy框架,类vue语法的开发,开发体验非常棒。在vue中,一个组件就是单文件,包含了js、html、css。wepy采用vue的语法,但由与vue稍稍有点区别,wepy的组件分为三种--wepy.app类,wepy.page类,wepy.component类。
对应到我们的目录结构中,每个模块实际上就是一系列的page组件。要组合这一系列的模块,那么很简单,我们要做的就是把这一系列page的路由扫描成一个路由表,然后插入到小程序的入口--app.json中。对应wepy框架那即是app.wpy中的pages字段。
扫描路由表
第一步!先得到所有pages的路由并综合成一个路由表!
我的方案是,在每个模块中新建一份routes文件,相当于注册每个需要插入到入口的page的路由,不需要接入业务的page就不用注册啦。是不是很熟悉呢,对的,就是参考vue-router的注册语法。
而扫描各个模块并合并路由表的脚本非常简单,读写文件就ok了。
路由排序策略
最后得出路由表
替换路由数组
So far so good...问题来了,如何替换入口文件中的路由数组。我如下做了几步尝试。
直接引入
我第一感觉就是,这不很简单吗?在wepy编译之前,先跑脚本得出路由表,再import这份路由表就得了。
然而这样小程序肯定会炸啦,pages字段的值必须是静态的,在小程序运行之前就配置好,动态引入是不行的!不信的话诸君可以试试。那么就是说,划重点---我们必须在wepy编译之前再预编译一次---事先替换掉pages字段的值!
正则匹配替换
既然要事先替换,那就是要精准定位pages字段的值,然后再替换掉。难点在于如果精准定位pages字段的值呢?
最捞然而最快的方法:正则匹配。
事先定好编码规范,在pages字段的值的前后添加
/* __ROUTES__ */
的注释脚本如下:
app.wpy的变化如下:
行吧,也总算跑通了。因为项目很赶,所以先用这个方案开发了一个半星期。开发完之后总觉得这种方案太难受,于是密谋着换另一种各精准的自动的方案。。。
babel插件替换全局常量
####1.思路
想必大家肯定很熟悉这种模式
通过这种只在编译过程中存在的全局常量,我们可以做很多值的匹配。
因为wepy已经预编译了一层,在框架内的业务代码是读取不了process.env.NODE_ENV的值。我就想着要不做一个类似于webpack的DefinePlugin的babel插件吧。具体的思路是babel编译过程中访问ast时匹配需要替换的标识符或者表达式,然后替换掉相应的值。例如:
In
Out
####2.学习如何编写babel插件
在这里先要给大家推荐几份学习资料:
首先是babel官网推荐的这份迷你编译器的代码,读完之后基本能理解编译器做的三件事:解析,转换,生成的过程了。
其次是编写Babel插件入门手册。基本涵盖了编写插件的方方面面,不过由于babel几个工具文档的缺失,在写插件的时候需要去翻查代码中的注释阅读api用法。
然后是大杀器AST转换器--astexplorer.net。我们来看一下,babel的解析器--babylon的文档,涵盖的节点类型这么多,脑绘一张AST树不现实。我在编写脚本的时候会先把代码放在转换器内生成AST树,再一步一步走。
编写babel插件之前先要理解抽象语法树这个概念。编译器做的事可以总结为:解析,转换,生成。具体的概念解释去看入门手册可能会更好。这里讲讲我自己的一些理解。
解析包括词法分析与语法分析。
解析过程吧。其实按我的理解(不知道这样合适不合适= =)抽象语法树跟DOM树其实很类似。词法分析有点像是把html解析成一个一个的dom节点的过程,语法分析则有点像是将dom节点描述成dom树。
转换过程是编译器最复杂逻辑最集中的地方。首先要理解“树形遍历”与“访问者模式”两个概念。
“树形遍历”如手册中所举例子:
假设有这么一段代码:
那么有如下的树形结构:
FunctionDeclaration
Identifier (id)
Identifier (id)
Identifier (params[0])
Identifier (params[0])
BlockStatement (body)
ReturnStatement (body)
BinaryExpression (argument)
Identifier (left)
Identifier (left)
Identifier (right)
Identifier (right)
BinaryExpression (argument)
ReturnStatement (body)
BlockStatement (body)
“访问者模式”则可以理解为,进入一个节点时被调用的方法。例如有如下的访问者:
结合树形遍历来看,就是说每个访问者有进入、退出两次机会来访问一个节点。
而我们这个替换常量的插件的关键之处就是在于,访问节点时,通过识别节点为我们的目标,然后替换他的值!
3.动手写插件
话不多说,直接上代码。这里要用到的一个工具是
babel-types
,用来检查节点。难度其实并不大,主要工作在于熟悉如何匹配目标节点。如匹配memberExpression时使用matchesPattern方法,匹配标识符则直接检查节点的name等等套路。最终成品及用法可以见我的github
4.结果
当然啦,这块插件不可以写在wepy.config.js中配置。因为必须在wepy编译之前执行我们的编译脚本,替换pages字段。所以的方案是在跑
wepy build --watch
之前跑我们的编译脚本,具体操作是引入
babel-core
来转换代码当然最终我们是转换成功啦,这个插件也用上了生产环境。但是后来没有采用这方案替换pages字段。暂时只替换了
__ENV__: process.env.NODE_ENV
与__VERSION__: version
两个常量。为什么呢?
因为每次编译之后标识符
__ROUTES__
都会被转换成我们的路由表,那么下次我想替换的时候难道要手动删掉然后再加上__ROUTES__
吗?我当然不会干跟我们自动化工程化的思想八字不合的事情啦。不过写完这个插件之后收获还是挺大的,基本了解该如何通过编译器寻找并替换我们的目标节点了。
编写babel脚本识别pages字段
1.思路
xmldom
这个库来解析,获取script标签内的代码。wepy.app
的类,再找到config
字段,最后匹配key为pages
的对象的值。最后替换目标节点2.成果
最终脚本:
3.使用方法
只需要在执行
wepy build --watch
之前先执行这份脚本,就可自动替换路由表,自动化操作。监听文件变动,增加模块时自动重新跑脚本,更新路由表,开发体验一流~结语
把代码往更自动化更工程化的方向写,这样的过程收获还是挺大的。但是确实这份脚本仍有不足之处,起码匹配节点这部分的代码是不大严谨的。
The text was updated successfully, but these errors were encountered: