网站定做公司,有什么页游传奇平台好,wordpress文章摘要文字,建筑工程机械人才培训网最近笔者的团队迁移了webpack2#xff0c;在迁移过程中#xff0c;笔者发现webpack2中有相当多的兼容代码#xff0c;虽然外界有很多声音一直在质疑作者为什么要破坏性更新#xff0c;其实大家也都知道webpack1那种过于“灵活”的配置方式是有待商榷的#xff0c;所以作者… 最近笔者的团队迁移了webpack2在迁移过程中笔者发现webpack2中有相当多的兼容代码虽然外界有很多声音一直在质疑作者为什么要破坏性更新其实大家也都知道webpack1那种过于“灵活”的配置方式是有待商榷的所以作者才会在webpack2上进行了很多规范但是笔者却隐隐的觉得等到webpack3的时候估计会有更多的破坏性更新不然也不会有这个webpack2了。于是心中有关webpack的话题便也搁置了且等它更稳定一些再谈不迟今天先来讲讲在剧烈的版本变化中不变的部分。 大家都知道webpack是做模块绑定用的那么就不得不牵涉到语法解析的内容而且其极高的扩展性也往往需要依赖于语法解析而在webpack内部使用acorn做语法解析类似的还有babel使用的babylon今天就带来两者的简要分析。 官方给两者的定义都叫做JavaScript parser内部也一致的使用了ASTAbstract syntax tree即抽象语法树的概念。如果对这个概念不明白的同学可以参考WIKIAST的解释 因为babylon引用了floweslint等一些checker所以整个项目结构相当的规范笔者仅已7.0.0为例 文件夹目录如下 index.js //程序入口会调用parser进行初始化
types.js //定义了基本类型和接口
options.js //定义获取配置的方法以及配置的缺省值
parser //所有的parser都在此 index.js //parser入口类继承自 StatementParser 即 ./statement.js statement.js //声明StatementParser 继承自 ExpressionParser 即 ./expression.js expression.js //声明ExpressionParser 继承自 LValParser 即 ./lval.js lval.js //声明 LValParser 继承自 NodeUtils 即 ./node.js node.js //声明 NodeUtils 继承自 UtilParser 即 ./util.js, 同时还实现了上一级目录中types.js 的nodebase接口为Node类 util.js //声明 UtilParser 继承自 Tokenizer 即 ../tokenizer/index.js location.js //声明 LocationParser 主要用于抛出异常 继承自 CommentsParser 即./comments.js comments.js //声明 CommentsParser 继承自 BaseParser 即./base.js base.js //所有parser的基类pluginstokenizer index.js //定义了 Token类 继承自上级目录parser的LocationParser 即 ../parser/location.jsutil 大概流程是这样的 1、首先调用index.js的parse;2、实例化一个parser对象,调用parser对象的parse方法开始转换;3、初始化node开始构造ast; 1) node.js 初始化node 2) tokenizer.js 初始化token 3) statement.js 调用 parseBlockBody,开始解析。这个阶段会构造File根节点和program节点并在parse完成之后闭合 4) 执行parseStatement, 将已经合法的节点插入到body中。这个阶段会产生各种*Statement type的节点 5分解statement, parseExpression。这个阶段除了产生各种expression的节点以外还将将产生type为Identifier的节点 6) 将上步骤中生成的原子表达式调用toAssignable 将其参数归类4、迭代过程完成后封闭节点完成body闭合 不过在笔者看来babylon的parser实现似乎并不能称得上是一个很好的实现而实现中往往还会使用的forward declaration(类似虚函数的概念)如下图 一个“”在方法前面的感觉就像是要以前的IIFE一样。。 有点扯远了总的来说依然是传统语法分析的几个步骤不过笔者在读源码的时候一直觉得蛮奇怪的为何他们内部要使用继承来实现parserparser的场景更像是mixin或者高阶函数的场景不过后者在具体处理中确实没有继承那样清晰的结构。 说了这么多babylon最后会生成什么呢以es2016的幂运算“3 ** 2”为例 {type: File,start: 0,end: 7,loc: {start: {line: 1,column: 0},end: {line: 1,column: 7}},program: {type: Program,start: 0,end: 7,loc: {start: {line: 1,column: 0},end: {line: 1,column: 7}},sourceType: script,body: [{type: ExpressionStatement,start: 0,end: 7,loc: {start: {line: 1,column: 0},end: {line: 1,column: 7}},expression: {type: BinaryExpression,start: 0,end: 6,loc: {start: {line: 1,column: 0},end: {line: 1,column: 6}},left: {type: NumericLiteral,start: 0,end: 1,loc: {start: {line: 1,column: 0},end: {line: 1,column: 1}},extra: {rawValue: 3,raw: 3},value: 3},operator: **,right: {type: NumericLiteral,start: 5,end: 6,loc: {start: {line: 1,column: 5},end: {line: 1,column: 6}},extra: {rawValue: 2,raw: 2},value: 2}}}],directives: []}
} 完整的列表看着未免有些可怕笔者将有关location信息的去除之后构造了以下这个对象 {type: File,program: {type: Program,sourceType: script,body: [{type: ExpressionStatement,expression: {type: BinaryExpression,left: {type: NumericLiteral,value: 3},operator: **,right: {type: NumericLiteral,value: 2}}}]}
} 可以看出这个类AST的的对象是内部大部分内容是其实是有关位置的信息因为很大程度上需要以这些信息去描述这个node的具体作用。 然后让我们再来看看webpack使用的acorn: 也许是acorn的作者和笔者有类似阅读babylon的经历觉得这种实现不太友好。。于是acorn的作者用了更为简单直接的实现 index.js //程序入口 引用了 ./state.js 的Parser类
state.js //构造Parser类
parseutil.js //向Parser类 添加有关 UtilParser 的方法
statement.js //向Parser类 添加有关 StatementParser 的方法
lval.js //向Parser类 添加有关 LValParser 的方法
expression.js //向Parser类 添加有关 ExpressionParser 的方法
location.js //向Parser类 添加有关 LocationParser 的方法
scope.js //向Parser类 添加处理scope的方法identifier.js
locutil.js
node.js
options.js
tokencontext.js
tokenize.js
tokentype.js
util.js
whitespace.js 虽然内部实现基本是类似的有很多连方法名都是一致的注释中使用的类名在acorn中并没有实现只是表示具有某种功能的方法的集合但是在具体实现上acorn不可谓不暴力连多余的目录都没有所有文件全在src目录下其中值得一提的是它并没有使用继承的方式而是使用了对象扩展的方式来实现的Parser类如下图 在具体的文件中直接扩展Paser的prototype 没想到笔者之前戏谈的mixin的方式真的就这样被使用了然而mixin的可读性一定程度上还要差经历过类似ReactComponentWithPureRenderMixin的同学想必印象尤深。 不过话说回来acorn内部实现与babylon并无二致连调用的方法名都是类似的不过acorn多实现了一个scope的概念用于限制作用域。 紧接着我们来看一下acorn生成的结果以“x ** y”为例 {type: Program,body: [{type: ExpressionStatement,expression: {type: BinaryExpression,left: {type: Identifier,name: x,loc: {start: {line: 1,column: 0},end: {line: 1,column: 1}}},operator: **,right: {type: Identifier,name: y,loc: {start: {line: 1,column: 5},end: {line: 1,column: 6}}},loc: {start: {line: 1,column: 0},end: {line: 1,column: 6}}},loc: {start: {line: 1,column: 0},end: {line: 1,column: 6}}}],loc: {start: {line: 1,column: 0},end: {line: 1,column: 6}}
}, {ecmaVersion: 7,locations: true
} 可以看出大部分内容依然是位置信息我们照例去掉它们 {type: Program,body: [{type: ExpressionStatement,expression: {type: BinaryExpression,left: {type: Identifier,name: x,},operator: **,right: {type: Identifier,name: y,}}}]
} 除去一些参数上的不同最大的区别可能就是最外层babylon还有一个File节点而acorn的根节点就是program了毕竟babel和webpack的工作场景还是略有区别的。 也许仅听笔者讲述一切都那么简单然而这只是理想情况现实的复杂远超我们的想象简单的举个印象比较深的例子在两个parser都有有关whitespace的抽象主要是用于提供一些匹配换行符的正则通常都想到的是 /\r\n?|\n/ 但实际上完整的却是 /\r\n?|\n|\u2028|\u2029/ 而且考虑到ASCII码的情况还需要很纠结的枚举出非空格的情况 /[\u1680\u180e\u2000-\u200a\u202f\u205f\u3000\ufeff]/ 因为parse处理的是我们实际开发中自己coding的代码不同的人不同的风格会有怎么样的奇怪的方式其实是非常考验完备性思维的一项工作而且这往往比我们日常的业务工作的场景更为复杂它很多时候甚至是接近一个可能性的全集而并非“大概率可能”的一个集合。虽然我们日常工作这种parser几乎是透明的我们在init的前端项目时基本已经部署好了开发环境但是对于某些情况下的实际问题定位却又有非凡的意义而且这还在一定时间内是一个常态虽然可能在不久的未来就会有更加智能更加强大的前端IDE。 有关ast的实验可以试一下这个网站https://astexplorer.net/转载于:https://www.cnblogs.com/mfoonirlee/p/7054939.html