Heim > Web-Frontend > js-Tutorial > Hauptteil

Verwenden von Acorn zum Parsen von JavaScript

高洛峰
Freigeben: 2016-11-15 09:56:44
Original
3369 Leute haben es durchsucht

Die meisten Situationen können mithilfe des Abgleichs regulärer Ausdrücke gehandhabt werden. Sobald dies jedoch vom Inhalt des Codekontexts abhängt, ist die reguläre oder einfache Zeichenanalyse sehr unzureichend. Derzeit ist ein Sprachparser erforderlich, um die gesamte AST (Zusammenfassung) zu erhalten Syntaxbaum).

Dann habe ich mehrere in JavaScript geschriebene JavaScript-Parser gefunden:

Esprima

Acorn

UglifyJS 2

Shift

Den Einreichungsunterlagen zufolge ist die Wartungssituation ziemlich gut, und die verschiedenen Entwicklungsfunktionen von ES haben Schritt gehalten. Ich habe kurz etwas über die jeweiligen Situationen erfahren und über einige ihrer Situationen gesprochen.

Esprima ist ein sehr klassischer Parser, der vor ein paar Jahren geboren wurde. Laut dem Autor von Acorn diente der Bau dieses Rades damals eher dem Spaß. Die Geschwindigkeit ist mit der von Esprima vergleichbar, der Implementierungscode ist jedoch geringer. Der entscheidende Punkt ist, dass die von diesen beiden Parsern erzeugten AST-Ergebnisse (ja, nur die AST, die Token sind unterschiedlich) mit der Estree Spec-Spezifikation übereinstimmen (dies ist das Spezifikationsdokument für die JavaScript-AST-Ausgabe der SpiderMonkey-Engine, gegeben von). Mozilla-Ingenieure) können Sie auch auf SpiderMonkey in MDN verweisen, was bedeutet, dass die erhaltenen Ergebnisse weitgehend kompatibel sind.

Das mittlerweile berühmte Webpack verwendet auch Acorn beim Parsen von Code.

Uglify, ein sehr bekannter JavaScript-Codekompressor, verfügt tatsächlich über einen Codeparser und kann auch AST ausgeben, aber seine Funktion dient eher der Komprimierung von Code. Wenn er zum Parsen von Code verwendet wird, ist dies nicht der Fall fühle mich rein genug.

Ich weiß nicht viel über Shift, ich weiß nur, dass es seine eigenen AST-Spezifikationen definiert.

Es gibt einen Leistungstest auf der offiziellen Website von Esprima. Die Ergebnisse, die ich auf Chrome durchgeführt habe, sind wie folgt:

Es ist ersichtlich, dass die Leistung von Acorn sehr gut ist, und es gibt auch eine Estree-Spezifikation (die Spezifikation ist Sehr wichtig, ich persönlich denke, dass es dem universellen Standard folgt. Die Spezifikation ist eine wichtige Grundlage für die Wiederverwendung von Code. Daher habe ich mich direkt für Acorn für die Codeanalyse entschieden.

Der Leistungsvergleich in der Abbildung ist Googles Traceur, was eher eine ist ES6 bis ES5-Compiler, es stimmt nicht mit der Positionierung des Parsers überein, den wir suchen

Kommen wir zum Punkt, wie man Acorn zum Parsen von JavaScript verwendet

Die APIs des Parsers sind sehr komplex:

Acorn verfügt über eine ganze Reihe von Konfigurationselementen, darunter einige Ereignisse, die mit Callback-Funktionen festgelegt werden können Wichtig:
const ast = acorn.parse(code, options)
Nach dem Login kopieren

ecmaVersion

Die wörtliche Bedeutung ist leicht zu verstehen, nämlich die ECMA-Version des zu analysierenden JavaScripts festzulegen. Der Standardwert ist ES7 >sourceType

Dieses Konfigurationselement hat zwei Werte: module und script , der Standardwert ist script

Der Hauptunterschied zwischen dem strikten Modus und dem Import/Export. Das bedeutet, dass Sie „use strict“ nicht hinzufügen müssen. Das Skript, das wir normalerweise in Browsern verwenden, verfügt nicht über eine Import-/Export-Syntax Wenn Sie das Modul auswählen, benötigen Sie keine strikte Modusdeklaration und können 🎜>locations

verwenden. Der Standardwert ist false, ein zusätzlicher Ort Das Objekt wird im AST-Knoten übertragen, um die aktuellen Start- und Endzeilen- und Spaltennummern darzustellen.

onComment. Übergeben Sie eine Rückruffunktion, die immer dann ausgelöst wird, wenn im Code ein Kommentar erscheint wird analysiert. Sie können den Kommentarinhalt des aktuellen Jahres abrufen: [Block, Text, Start, Ende] gibt an, ob es sich um einen Blockkommentar handelt und Endpositionen des Kommentars.

Der oben erwähnte Espree erfordert das Konfigurationselement von Esprima. Nachdem es auf „true“ gesetzt wurde, werden die Knoten der Code-Analyse mit Kommentaren versehen Informationen (TrailingComments und LeadingComments). Espree verwendet die onComment-Konfiguration von Acorn, um Kompatibilität mit dieser Esprima-Funktion zu erreichen.

Der Parser verfügt in der Regel auch über eine Schnittstelle zum Erhalten lexikalischer Analyseergebnisse:

Mit dem zweiten Parameter der Tokenizer-Methode können auch Standorte konfiguriert werden.

Es gibt bestimmte Unterschiede in der Datenstruktur des lexikalischen Ergebnis-Tokens und des Esprima-Ergebnisses (Espree hat diesen Kompatibilitätsgrad erneut erreicht). Wenn Sie daran interessiert sind, können Sie sich die Parsing-Ergebnisse von Esprima ansehen: http://esprima.org/demo/parse… .

Was den Inhalt von AST und dem von Acorn analysierten Token betrifft, werden wir als Nächstes näher darauf eingehen.

Token

Ich habe lange gesucht und konnte keine detaillierte Einführung in die Token-Datenstruktur finden, also musste ich selbst einen Blick darauf werfen.

Der Code, den ich zum Testen der Analyse verwendet habe, ist:

const tokens = [...acorn.tokenizer(code, options)]
Nach dem Login kopieren

Das analysierte Token-Array ist ein Objekt ähnlich diesem:

Es sieht tatsächlich sehr danach aus Leicht zu verstehen, nicht wahr? In dem Objekt, das dem Typ entspricht, stellt label einen Typ der aktuellen Identifizierung dar, und keyword ist das Schlüsselwort, z. B. import im Beispiel oder function.

value ist der Wert des aktuellen Bezeichners und start/end sind die Start- bzw. Endpositionen.

Normalerweise müssen wir auf Label/Schlüsselwort/Wert achten. Weitere Details finden Sie im Quellcode: tokentype.js.

Die Estree-Spezifikation

这一部分是重头戏,因为实际上我需要的还是解析出来的 AST。最原滋原味的内容来自于:The Estree Spec,我只是阅读了之后的搬运工。

提供了标准文档的好处是,很多东西有迹可循,这里还有一个工具,用于把满足 Estree 标准的 AST 转换为 ESMAScript 代码:escodegen。

好吧,回到正题,我们先来看一下 ES5 的部分,可以在 Esprima: Parser 这个页面测试各种代码的解析结果。

符合这个规范的解析出来的 AST 节点用 Node 对象来标识,Node 对象应该符合这样的接口:

interface Node { 
    type: string; 
    loc: SourceLocation | null; 
}
Nach dem Login kopieren

type 字段表示不同的节点类型,下边会再讲一下各个类型的情况,分别对应了 JavaScript 中的什么语法。

loc 字段表示源码的位置信息,如果没有相关信息的话为 null,否则是一个对象,包含了开始和结束的位置。接口如下:

interface SourceLocation { 
    source: string | null; 
    start: Position; 
    end: Position; 
}
Nach dem Login kopieren

这里的 Position 对象包含了行和列的信息,行从 1 开始,列从 0 开始:

interface Position { 
    line: number; // >= 1 
    column: number; // >= 0 
}
Nach dem Login kopieren

好了,基础部分就是这样,接下来看各种类型的节点,顺带温习一下 JavaScript 语法的一些东西吧。对于这里每一部分的内容,会简单谈一下,但不会展开(内容不少),对 JavaScript 了解的人很容易就明白的。

我觉得看完就像把 JavaScript 的基础语法整理了一遍。

Identifier

标识符,我觉得应该是这么叫的,就是我们写 JS 时自定义的名称,如变量名,函数名,属性名,都归为标识符。相应的接口是这样的:

interface Identifier <: Expression, Pattern { 
    type: "Identifier"; 
    name: string; 
}
Nach dem Login kopieren

一个标识符可能是一个表达式,或者是解构的模式(ES6 中的解构语法)。我们等会会看到 Expression 和 Pattern 相关的内容的。

Literal

字面量,这里不是指 [] 或者 {} 这些,而是本身语义就代表了一个值的字面量,如 1,“hello”, true 这些,还有正则表达式(有一个扩展的 Node 来表示正则表达式),如 /d?/。我们看一下文档的定义:

interface Literal <: Expression { 
    type: "Literal"; 
    value: string | boolean | null | number | RegExp; 
}
Nach dem Login kopieren

value 这里即对应了字面量的值,我们可以看出字面量值的类型,字符串,布尔,数值,null 和正则。

RegExpLiteral

这个针对正则字面量的,为了更好地来解析正则表达式的内容,添加多一个 regex 字段,里边会包括正则本身,以及正则的flags。

interface RegExpLiteral <: Literal { 
  regex: { 
    pattern: string; 
    flags: string; 
  }; 
}
Nach dem Login kopieren

Programs

一般这个是作为跟节点的,即代表了一棵完整的程序代码树。

interface Program <: Node { 
    type: "Program"; 
    body: [ Statement ]; 
}
Nach dem Login kopieren

body 属性是一个数组,包含了多个 Statement(即语句)节点。

Functions

函数声明或者函数表达式节点。

interface Function <: Node { 
    id: Identifier | null; 
    params: [ Pattern ]; 
    body: BlockStatement; 
}
Nach dem Login kopieren

id 是函数名,params 属性是一个数组,表示函数的参数。body 是一个块语句。

有一个值得留意的点是,你在测试过程中,是不会找到 type: "Function" 的节点的,但是你可以找到 type: "FunctionDeclaration" 和 type: "FunctionExpression",因为函数要么以声明语句出现,要么以函数表达式出现,都是节点类型的组合类型,后边会再提及 FunctionDeclaration 和 FunctionExpression 的相关内容。

这让人感觉这个文档规划得蛮细致的,函数名,参数和函数块是属于函数部分的内容,而声明或者表达式则有它自己需要的东西。

Statement

语句节点没什么特别的,它只是一个节点,一种区分,但是语句有很多种,下边会详述。

interface Statement <: Node { }
Nach dem Login kopieren

ExpressionStatement

表达式语句节点,a = a + 1 或者 a++ 里边会有一个 expression 属性指向一个表达式节点对象(后边会提及表达式)。

interface ExpressionStatement <: Statement { 
    type: "ExpressionStatement"; 
    expression: Expression; 
}
Nach dem Login kopieren

BlockStatement

块语句节点,举个例子:if (...) { // 这里是块语句的内容 },块里边可以包含多个其他的语句,所以有一个 body 属性,是一个数组,表示了块里边的多个语句。

interface BlockStatement <: Statement { 
    type: "BlockStatement"; 
    body: [ Statement ]; 
}
Nach dem Login kopieren

EmptyStatement

一个空的语句节点,没有执行任何有用的代码,例如一个单独的分号 ;

interface EmptyStatement  <: Statement { 
    type: "EmptyStatement "; 
}
Nach dem Login kopieren

DebuggerStatement

debugger,就是表示这个,没有其他了。

interface DebuggerStatement <: Statement { 
    type: "DebuggerStatement"; 
}
Nach dem Login kopieren

WithStatement

with 语句节点,里边有两个特别的属性,object 表示 with 要使用的那个对象(可以是一个表达式),body 则是对应 with 后边要执行的语句,一般会是一个块语句。

interface WithStatement <: Statement { 
    type: "WithStatement"; 
    object: Expression; 
    body: Statement; 
}
Nach dem Login kopieren

下边是控制流的语句:

ReturnStatement

返回语句节点,argument 属性是一个表达式,代表返回的内容。

interface ReturnStatement <: Statement { 
    type: "ReturnStatement"; 
    argument: Expression | null; 
}
Nach dem Login kopieren

LabeledStatement

label 语句,平时可能会比较少接触到,举个例子:

loop: for(let i = 0; i < len; i++) { 
    // ... 
    for (let j = 0; j < min; j++) { 
        // ... 
        break loop; 
    } 
}
Nach dem Login kopieren

这里的 loop 就是一个 label 了,我们可以在循环嵌套中使用 break loop 来指定跳出哪个循环。所以这里的 label 语句指的就是loop: ... 这个。

一个 label 语句节点会有两个属性,一个 label 属性表示 label 的名称,另外一个 body 属性指向对应的语句,通常是一个循环语句或者 switch 语句。

interface LabeledStatement <: Statement { 
    type: "LabeledStatement"; 
    label: Identifier; 
    body: Statement; 
}
Nach dem Login kopieren

BreakStatement

break 语句节点,会有一个 label 属性表示需要的 label 名称,当不需要 label 的时候(通常都不需要),便是 null。

interface BreakStatement <: Statement { 
    type: "BreakStatement"; 
    label: Identifier | null; 
}
Nach dem Login kopieren

ContinueStatement

continue 语句节点,和 break 类似。

interface ContinueStatement <: Statement { 
    type: "ContinueStatement"; 
    label: Identifier | null; 
}
Nach dem Login kopieren

下边是条件语句:

IfStatement

if 语句节点,很常见,会带有三个属性,test 属性表示 if (...) 括号中的表达式。

consequent 属性是表示条件为 true 时的执行语句,通常会是一个块语句。

alternate 属性则是用来表示 else 后跟随的语句节点,通常也会是块语句,但也可以又是一个 if 语句节点,即类似这样的结构:

if (a) { //... } else if (b) { // ... }。

alternate 当然也可以为 null。

interface IfStatement <: Statement { 
    type: "IfStatement"; 
    test: Expression; 
    consequent: Statement; 
    alternate: Statement | null; 
}
Nach dem Login kopieren

SwitchStatement

switch 语句节点,有两个属性,discriminant 属性表示 switch 语句后紧随的表达式,通常会是一个变量,cases 属性是一个case 节点的数组,用来表示各个 case 语句。

interface SwitchStatement <: Statement { 
    type: "SwitchStatement"; 
    discriminant: Expression; 
    cases: [ SwitchCase ]; 
}
Nach dem Login kopieren

SwitchCase

switch 的 case 节点。test 属性代表这个 case 的判断表达式,consequent 则是这个 case 的执行语句。

当 test 属性是 null 时,则是表示 default 这个 case 节点。

interface SwitchCase <: Node { 
    type: "SwitchCase"; 
    test: Expression | null; 
    consequent: [ Statement ]; 
}
Nach dem Login kopieren

下边是异常相关的语句:

ThrowStatement

throw 语句节点,argument 属性用以表示 throw 后边紧跟的表达式。

interface ThrowStatement <: Statement { 
    type: "ThrowStatement"; 
    argument: Expression; 
}
Nach dem Login kopieren

TryStatement

try 语句节点,block 属性表示 try 的执行语句,通常是一个块语句。

hanlder 属性是指 catch 节点,finalizer 是指 finally 语句节点,当 hanlder 为 null 时,finalizer 必须是一个块语句节点。

interface TryStatement <: Statement { 
    type: "TryStatement"; 
    block: BlockStatement; 
    handler: CatchClause | null; 
    finalizer: BlockStatement | null; 
}
Nach dem Login kopieren

CatchClause

catch 节点,param 用以表示 catch 后的参数,body 则表示 catch 后的执行语句,通常是一个块语句。

interface CatchClause <: Node { 
    type: "CatchClause"; 
    param: Pattern; 
    body: BlockStatement; 
}
Nach dem Login kopieren

下边是循环语句:

WhileStatement

while 语句节点,test 表示括号中的表达式,body 是表示要循环执行的语句。

interface WhileStatement <: Statement { 
    type: "WhileStatement"; 
    test: Expression; 
    body: Statement; 
}
Nach dem Login kopieren

DoWhileStatement

do/while 语句节点,和 while 语句类似。

interface DoWhileStatement <: Statement { 
    type: "DoWhileStatement"; 
    body: Statement; 
    test: Expression; 
}
Nach dem Login kopieren

ForStatement

for 循环语句节点,属性 init/test/update 分别表示了 for 语句括号中的三个表达式,初始化值,循环判断条件,每次循环执行的变量更新语句(init 可以是变量声明或者表达式)。这三个属性都可以为 null,即 for(;;){}。

body 属性用以表示要循环执行的语句。

interface ForStatement <: Statement { 
    type: "ForStatement"; 
    init: VariableDeclaration | Expression | null; 
    test: Expression | null; 
    update: Expression | null; 
    body: Statement; 
}
Nach dem Login kopieren

ForInStatement

for/in 语句节点,left 和 right 属性分别表示在 in 关键词左右的语句(左侧可以是一个变量声明或者表达式)。body 依旧是表示要循环执行的语句。

interface ForInStatement <: Statement { 
    type: "ForInStatement"; 
    left: VariableDeclaration |  Pattern; 
    right: Expression; 
    body: Statement; 
}
Nach dem Login kopieren

Declarations

声明语句节点,同样也是语句,只是一个类型的细化。下边会介绍各种声明语句类型。

interface Declaration <: Statement { }
Nach dem Login kopieren

FunctionDeclaration

函数声明,和之前提到的 Function 不同的是,id 不能为 null。

interface FunctionDeclaration <: Function, Declaration { 
    type: "FunctionDeclaration"; 
    id: Identifier; 
}
Nach dem Login kopieren

VariableDeclaration

变量声明,kind 属性表示是什么类型的声明,因为 ES6 引入了 const/let。

declarations 表示声明的多个描述,因为我们可以这样:let a = 1, b = 2;。

interface VariableDeclaration <: Declaration { 
    type: "VariableDeclaration"; 
    declarations: [ VariableDeclarator ]; 
    kind: "var"; 
}
Nach dem Login kopieren

VariableDeclarator

变量声明的描述,id 表示变量名称节点,init 表示初始值的表达式,可以为 null。

interface VariableDeclarator <: Node { 
    type: "VariableDeclarator"; 
    id: Pattern; 
    init: Expression | null; 
}
Nach dem Login kopieren

Expressions

表达式节点。

interface Expression <: Node { }
Nach dem Login kopieren

ThisExpression

表示 this。

interface ThisExpression <: Expression { 
    type: "ThisExpression"; 
}
Nach dem Login kopieren

ArrayExpression

数组表达式节点,elements 属性是一个数组,表示数组的多个元素,每一个元素都是一个表达式节点。

interface ArrayExpression <: Expression { 
    type: "ArrayExpression"; 
    elements: [ Expression | null ]; 
}
Nach dem Login kopieren

ObjectExpression

对象表达式节点,property 属性是一个数组,表示对象的每一个键值对,每一个元素都是一个属性节点。

interface ObjectExpression <: Expression { 
    type: "ObjectExpression"; 
    properties: [ Property ]; 
}
Nach dem Login kopieren

Property

对象表达式中的属性节点。key 表示键,value 表示值,由于 ES5 语法中有 get/set 的存在,所以有一个 kind 属性,用来表示是普通的初始化,或者是 get/set。

interface Property <: Node { 
    type: "Property"; 
    key: Literal | Identifier; 
    value: Expression; 
    kind: "init" | "get" | "set"; 
}
Nach dem Login kopieren

FunctionExpression

函数表达式节点。

interface FunctionExpression <: Function, Expression { 
    type: "FunctionExpression"; 
}
Nach dem Login kopieren

下边是一元运算符相关的表达式部分:

UnaryExpression

一元运算表达式节点(++/-- 是 update 运算符,不在这个范畴内),operator 表示运算符,prefix 表示是否为前缀运算符。argument 是要执行运算的表达式。

interface UnaryExpression <: Expression { 
    type: "UnaryExpression"; 
    operator: UnaryOperator; 
    prefix: boolean; 
    argument: Expression; 
}
Nach dem Login kopieren

UnaryOperator

一元运算符,枚举类型,所有值如下:

enum UnaryOperator { 
    "-" | "+" | "!" | "~" | "typeof" | "void" | "delete" 
}
Nach dem Login kopieren

UpdateExpression

update 运算表达式节点,即 ++/--,和一元运算符类似,只是 operator 指向的节点对象类型不同,这里是 update 运算符。

interface UpdateExpression <: Expression { 
    type: "UpdateExpression"; 
    operator: UpdateOperator; 
    argument: Expression; 
    prefix: boolean; 
}
Nach dem Login kopieren

UpdateOperator

update 运算符,值为 ++ 或 --,配合 update 表达式节点的 prefix 属性来表示前后。

enum UpdateOperator { 
  "++" | "--" 
}
Nach dem Login kopieren

下边是二元运算符相关的表达式部分:

BinaryExpression

二元运算表达式节点,left 和 right 表示运算符左右的两个表达式,operator 表示一个二元运算符。

interface BinaryExpression <: Expression { 
    type: "BinaryExpression"; 
    operator: BinaryOperator; 
    left: Expression; 
    right: Expression; 
}
Nach dem Login kopieren

BinaryOperator

二元运算符,所有值如下:

enum BinaryOperator { 
    "==" | "!=" | "===" | "!==" 
         | "<" | "<=" | ">" | ">=" 
         | "<<" | ">>" | ">>>" 
         | "+" | "-" | "*" | "/" | "%" 
         | "|" | "^" | "&" | "in" 
         | "instanceof" 
}
Nach dem Login kopieren

AssignmentExpression

赋值表达式节点,operator 属性表示一个赋值运算符,left 和 right 是赋值运算符左右的表达式。

interface AssignmentExpression <: Expression { 
    type: "AssignmentExpression"; 
    operator: AssignmentOperator; 
    left: Pattern | Expression; 
    right: Expression; 
}
Nach dem Login kopieren

AssignmentOperator

赋值运算符,所有值如下:(常用的并不多)

enum AssignmentOperator { 
    "=" | "+=" | "-=" | "*=" | "/=" | "%=" 
        | "<<=" | ">>=" | ">>>=" 
        | "|=" | "^=" | "&=" 
}
Nach dem Login kopieren

LogicalExpression

逻辑运算表达式节点,和赋值或者二元运算类型,只不过 operator 是逻辑运算符类型。

interface LogicalExpression <: Expression { 
    type: "LogicalExpression"; 
    operator: LogicalOperator; 
    left: Expression; 
    right: Expression; 
}
Nach dem Login kopieren

LogicalOperator

逻辑运算符,两种值,即与或。

enum LogicalOperator { 
    "||" | "&&" 
}
Nach dem Login kopieren

MemberExpression

成员表达式节点,即表示引用对象成员的语句,object 是引用对象的表达式节点,property 是表示属性名称,computed 如果为false,是表示 . 来引用成员,property 应该为一个 Identifier 节点,如果 computed 属性为 true,则是 [] 来进行引用,即property 是一个 Expression 节点,名称是表达式的结果值。

interface MemberExpression <: Expression, Pattern { 
    type: "MemberExpression"; 
    object: Expression; 
    property: Expression; 
    computed: boolean; 
}
Nach dem Login kopieren

下边是其他的一些表达式:

ConditionalExpression

条件表达式,通常我们称之为三元运算表达式,即 boolean ? true : false。属性参考条件语句。

interface ConditionalExpression <: Expression { 
    type: "ConditionalExpression"; 
    test: Expression; 
    alternate: Expression; 
    consequent: Expression; 
}
Nach dem Login kopieren

CallExpression

函数调用表达式,即表示了 func(1, 2) 这一类型的语句。callee 属性是一个表达式节点,表示函数,arguments 是一个数组,元素是表达式节点,表示函数参数列表。

interface CallExpression <: Expression { 
    type: "CallExpression"; 
    callee: Expression; 
    arguments: [ Expression ]; 
}
Nach dem Login kopieren

NewExpression

new 表达式

interface NewExpression <: CallExpression { 
    type: "NewExpression"; 
}
Nach dem Login kopieren

SequenceExpression

这个就是逗号运算符构建的表达式(不知道确切的名称),expressions 属性为一个数组,即表示构成整个表达式,被逗号分割的多个表达式。

interface SequenceExpression <: Expression { 
    type: "SequenceExpression"; 
    expressions: [ Expression ]; 
}
Nach dem Login kopieren

Patterns

模式,主要在 ES6 的解构赋值中有意义,在 ES5 中,可以理解为和 Identifier 差不多的东西。

interface Pattern <: Node { }
Nach dem Login kopieren

这一部分的内容比较多,但都可以举一反三,写这个的时候我就当把 JavaScript 语法再复习一遍。这个文档还有 ES2015,ES2016,ES2017 相关的内容,涉及的东西也蛮多,但是理解了上边的这一些,然后从语法层面去思考这个文档,其他的内容也就很好理解了,这里略去,有需要请参阅:The Estree Spec。

Plugins

回到我们的主角,Acorn,提供了一种扩展的方式来编写相关的插件:Acorn Plugins。

我们可以使用插件来扩展解析器,来解析更多的一些语法,如 .jsx 语法,有兴趣的看看这个插件:acorn-jsx。

官方表示 Acorn 的插件是用于方便扩展解析器,但是需要对 Acorn 内部的运行极致比较了解,扩展的方式会在原本的基础上重新定义一些方法。这里不展开讲了,如果我需要插件的话,会再写文章聊聊这个东西。

Examples

现在我们来看一下如何应用这个解析器,例如我们需要用来解析出一个符合 CommonJS 规范的模块依赖了哪些模块,我们可以用 Acorn 来解析 require 这个函数的调用,然后取出调用时的传入参数,便可以获取依赖的模块。

下边是示例代码:

// 遍历所有节点的函数 
function walkNode(node, callback) { 
  callback(node) 
 
  // 有 type 字段的我们认为是一个节点 
  Object.keys(node).forEach((key) => { 
    const item = node[key] 
    if (Array.isArray(item)) { 
      item.forEach((sub) => { 
        sub.type && walkNode(sub, callback) 
      }) 
    } 
 
    item && item.type && walkNode(item, callback) 
  }) 
} 
 
function parseDependencies(str) { 
  const ast = acorn.parse(str, { ranges: true }) 
  const resource = [] // 依赖列表 
 
  // 从根节点开始 
  walkNode(ast, (node) => { 
    const callee = node.callee 
    const args = node.arguments 
 
    // require 我们认为是一个函数调用,并且函数名为 require,参数只有一个,且必须是字面量 
    if ( 
      node.type === &#39;CallExpression&#39; && 
      callee.type === &#39;Identifier&#39; && 
      callee.name === &#39;require&#39; && 
      args.length === 1 && 
      args[0].type === &#39;Literal&#39; 
    ) { 
      const args = node.arguments 
 
      // 获取依赖的相关信息 
      resource.push({ 
        string: str.substring(node.range[0], node.range[1]), 
        path: args[0].value, 
        start: node.range[0], 
        end: node.range[1] 
      }) 
    } 
  }) 
 
  return resource 
}
Nach dem Login kopieren

这只是简单的一个情况的处理,但是已经给我们呈现了如何使用解析器,Webpack 则在这个的基础上做了更多的东西,包括 var r = require; r('a') 或者 require.async('a') 等的处理。

AST 这个东西对于前端来说,我们无时无刻不在享受着它带来的成果(模块构建,代码压缩,代码混淆),所以了解一下总归有好处。


Verwandte Etiketten:
Quelle:php.cn
Erklärung dieser Website
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn
Beliebte Tutorials
Mehr>
Neueste Downloads
Mehr>
Web-Effekte
Quellcode der Website
Website-Materialien
Frontend-Vorlage