ホームページ ウェブフロントエンド jsチュートリアル Acorn を使用して JavaScript を解析する

Acorn を使用して JavaScript を解析する

Nov 15, 2016 am 09:56 AM
javascript

ほとんどの状況は正規表現のマッチングを使用して処理できますが、コード コンテキストの内容に依存すると、通常の文字解析や単純な文字解析では非常に不十分になります。現時点では、AST (抽象構文ツリー) 全体を取得するには言語パーサーが必要です。 ))。

そして、JavaScript で書かれた複数の JavaScript パーサーを見つけました:

Esprima

Acorn

UglifyJS 2

Shift

提出記録から判断すると、メンテナンス状況はかなり良好で、さまざまな ES 開発機能を維持できます。私は彼ら全員に簡単な紹介をし、彼らの状況のいくつかについて話しました。

Esprima は非常に古典的なパーサーで、Acorn の後に誕生しました。それは数年前のことです。 Acorn の作者によると、このホイールの構築は単なる遊びのためであり、速度は Esprima に匹敵しますが、実装コードは少ないとのことです。重要な点は、これら 2 つのパーサーによって生成された AST 結果 (はい、AST だけです。トークンは異なります) が Estree 仕様仕様 (これは、SpiderMonkey エンジンによって出力される JavaScript AST の仕様文書であり、次のように指定されます) に準拠しているということです。 Mozilla エンジニア)、MDN の SpiderMonkey) も参照できます。つまり、得られた結果はほぼ互換性があります。

今や有名になった Webpack も、コードを解析するときに Acorn を使用します。

非常に有名な JavaScript コード圧縮ツールである Uglify については、実際にはコード パーサーが付属しており、AST も出力できますが、その機能はコードを圧縮するためのものであり、コードを解析するために使用される場合は、十分に純粋ではないように感じます。

私は Shift についてあまり知りません。知っているのは、Shift が独自の AST 仕様セットを定義していることだけです。

Esprima 公式 Web サイトにパフォーマンステストがあります。Chrome で実行した結果は次のとおりです:

Acorn のパフォーマンスが非常に優れていることがわかります。また、Estree 仕様もあります (標準は非常に重要です。個人的には、普遍的な仕様に従うことが重要だと思います)コードの再利用の基礎) なので、コード解析には Acorn を直接選択しました

写真のパフォーマンス比較は Google の Traceur ですが、これは ES6 から ES5 のコンパイラに近く、私たちが探しているパーサーの位置付けと一致しません。

次は、Acorn を使用して JavaScript を解析する方法です。

API

パーサーの API は非常にシンプルです:

const ast = acorn.parse(code, options)
ログイン後にコピー

Acorn には、いくつかのイベントも含まれています。これはコールバック関数で設定できます:

ecmaVersion

文字通りの意味は、解析する JavaScript の ECMA バージョンを設定することです。デフォルトは ES7 です。

sourceType

この設定項目には module と script の 2 つの値があります。デフォルトは script です。

主な違いは、厳密モードとインポート/エクスポートです。つまり、ES6 のモジュールは必要ありません。ブラウザで通常使用するスクリプトにはインポート/エクスポート構文がありません。そのため、スクリプトを選択してインポート/エクスポートエラーが発生した場合は、モジュールを選択すると、厳密モード宣言を使用できます。厳密モード宣言は必要なく、インポート/エクスポート構文を使用できます。

locations

デフォルト値は false で、true の後は、追加の loc オブジェクトが AST ノードに保持され、現在の開始位置と位置を表します。

onComment

は、コード内のコメントが解析されるたびにトリガーされるコールバック関数を渡します。パラメーター リストは次のとおりです。 、end]。

blockはブロックコメントかどうかを示し、textはコメントの内容、startとendは上記のコメント

の開始位置と終了位置を示します。 trueに設定するとEsprimaのattachComment設定項目が必要になります。 , Esprima は、コード解析結果のノードにコメント関連の情報 (trailingComments と leadingComments) を保持します。 Espree は、Acorn の onComment 設定を使用して、この Esprima 機能との互換性を実現します。

パーサーには通常、字句解析結果を取得するためのインターフェイスもあります:

const tokens = [...acorn.tokenizer(code, options)]
ログイン後にコピー

トークナイザー メソッドの 2 番目のパラメーターでは、場所を構成することもできます。

字句結果トークンと Esprima の結果データ構造の間には特定の違いがあります (Espree は再びこのレベルの互換性を実現しました)。さらに詳しく知りたい場合は、Esprima の解析結果を参照してください。 .org/demo /parse... 。

Acornが解析するASTとトークンの内容については、次回詳しく説明します。

トークン

長い間検索しましたが、トークンのデータ構造の詳細な紹介が見つからなかったので、自分で調べなければなりませんでした。

解析のテストに使用したコードは次のとおりです:

import "hello.js" 
 
var a = 2; 
 
// test 
function name() { console.log(arguments); }
ログイン後にコピー

解析されたトークン配列は次のようなオブジェクトです:

Token { 
    type: 
     TokenType { 
       label: 'import', 
       keyword: 'import', 
       beforeExpr: false, 
       startsExpr: false, 
       isLoop: false, 
       isAssign: false, 
       prefix: false, 
       postfix: false, 
       binop: null, 
       updateContext: null }, 
    value: 'import', 
    start: 5, 
    end: 11 },
ログイン後にコピー

type に対応するオブジェクトで、label は現在のものを表します。タイプ、キーワードは、例の import などのキーワード、または関数です。

value は現在の識別子の値、start/end はそれぞれ開始位置と終了位置です。

通常、注意を払う必要があるのはラベル/キーワード/値です。その他の詳細については、ソース コード: tokentype.js を参照してください。

エストリー仕様

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

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

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

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

interface Node { 
    type: string; 
    loc: SourceLocation | null; 
}
ログイン後にコピー

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

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

interface SourceLocation { 
    source: string | null; 
    start: Position; 
    end: Position; 
}
ログイン後にコピー

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

interface Position { 
    line: number; // >= 1 
    column: number; // >= 0 
}
ログイン後にコピー

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

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

Identifier

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

interface Identifier <: Expression, Pattern { 
    type: "Identifier"; 
    name: string; 
}
ログイン後にコピー

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

Literal

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

interface Literal <: Expression { 
    type: "Literal"; 
    value: string | boolean | null | number | RegExp; 
}
ログイン後にコピー

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

RegExpLiteral

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

interface RegExpLiteral <: Literal { 
  regex: { 
    pattern: string; 
    flags: string; 
  }; 
}
ログイン後にコピー

Programs

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

interface Program <: Node { 
    type: "Program"; 
    body: [ Statement ]; 
}
ログイン後にコピー

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

Functions

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

interface Function <: Node { 
    id: Identifier | null; 
    params: [ Pattern ]; 
    body: BlockStatement; 
}
ログイン後にコピー

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

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

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

Statement

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

interface Statement <: Node { }
ログイン後にコピー

ExpressionStatement

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

interface ExpressionStatement <: Statement { 
    type: "ExpressionStatement"; 
    expression: Expression; 
}
ログイン後にコピー

BlockStatement

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

interface BlockStatement <: Statement { 
    type: "BlockStatement"; 
    body: [ Statement ]; 
}
ログイン後にコピー

EmptyStatement

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

interface EmptyStatement  <: Statement { 
    type: "EmptyStatement "; 
}
ログイン後にコピー

DebuggerStatement

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

interface DebuggerStatement <: Statement { 
    type: "DebuggerStatement"; 
}
ログイン後にコピー

WithStatement

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

interface WithStatement <: Statement { 
    type: "WithStatement"; 
    object: Expression; 
    body: Statement; 
}
ログイン後にコピー

下边是控制流的语句:

ReturnStatement

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

interface ReturnStatement <: Statement { 
    type: "ReturnStatement"; 
    argument: Expression | null; 
}
ログイン後にコピー

LabeledStatement

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

loop: for(let i = 0; i < len; i++) { 
    // ... 
    for (let j = 0; j < min; j++) { 
        // ... 
        break loop; 
    } 
}
ログイン後にコピー

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

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

interface LabeledStatement <: Statement { 
    type: "LabeledStatement"; 
    label: Identifier; 
    body: Statement; 
}
ログイン後にコピー

BreakStatement

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

interface BreakStatement <: Statement { 
    type: "BreakStatement"; 
    label: Identifier | null; 
}
ログイン後にコピー

ContinueStatement

continue 语句节点,和 break 类似。

interface ContinueStatement <: Statement { 
    type: "ContinueStatement"; 
    label: Identifier | null; 
}
ログイン後にコピー

下边是条件语句:

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; 
}
ログイン後にコピー

SwitchStatement

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

interface SwitchStatement <: Statement { 
    type: "SwitchStatement"; 
    discriminant: Expression; 
    cases: [ SwitchCase ]; 
}
ログイン後にコピー

SwitchCase

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

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

interface SwitchCase <: Node { 
    type: "SwitchCase"; 
    test: Expression | null; 
    consequent: [ Statement ]; 
}
ログイン後にコピー

下边是异常相关的语句:

ThrowStatement

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

interface ThrowStatement <: Statement { 
    type: "ThrowStatement"; 
    argument: Expression; 
}
ログイン後にコピー

TryStatement

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

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

interface TryStatement <: Statement { 
    type: "TryStatement"; 
    block: BlockStatement; 
    handler: CatchClause | null; 
    finalizer: BlockStatement | null; 
}
ログイン後にコピー

CatchClause

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

interface CatchClause <: Node { 
    type: "CatchClause"; 
    param: Pattern; 
    body: BlockStatement; 
}
ログイン後にコピー

下边是循环语句:

WhileStatement

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

interface WhileStatement <: Statement { 
    type: "WhileStatement"; 
    test: Expression; 
    body: Statement; 
}
ログイン後にコピー

DoWhileStatement

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

interface DoWhileStatement <: Statement { 
    type: "DoWhileStatement"; 
    body: Statement; 
    test: Expression; 
}
ログイン後にコピー

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; 
}
ログイン後にコピー

ForInStatement

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

interface ForInStatement <: Statement { 
    type: "ForInStatement"; 
    left: VariableDeclaration |  Pattern; 
    right: Expression; 
    body: Statement; 
}
ログイン後にコピー

Declarations

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

interface Declaration <: Statement { }
ログイン後にコピー

FunctionDeclaration

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

interface FunctionDeclaration <: Function, Declaration { 
    type: "FunctionDeclaration"; 
    id: Identifier; 
}
ログイン後にコピー

VariableDeclaration

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

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

interface VariableDeclaration <: Declaration { 
    type: "VariableDeclaration"; 
    declarations: [ VariableDeclarator ]; 
    kind: "var"; 
}
ログイン後にコピー

VariableDeclarator

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

interface VariableDeclarator <: Node { 
    type: "VariableDeclarator"; 
    id: Pattern; 
    init: Expression | null; 
}
ログイン後にコピー

Expressions

表达式节点。

interface Expression <: Node { }
ログイン後にコピー

ThisExpression

表示 this。

interface ThisExpression <: Expression { 
    type: "ThisExpression"; 
}
ログイン後にコピー

ArrayExpression

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

interface ArrayExpression <: Expression { 
    type: "ArrayExpression"; 
    elements: [ Expression | null ]; 
}
ログイン後にコピー

ObjectExpression

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

interface ObjectExpression <: Expression { 
    type: "ObjectExpression"; 
    properties: [ Property ]; 
}
ログイン後にコピー

Property

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

interface Property <: Node { 
    type: "Property"; 
    key: Literal | Identifier; 
    value: Expression; 
    kind: "init" | "get" | "set"; 
}
ログイン後にコピー

FunctionExpression

函数表达式节点。

interface FunctionExpression <: Function, Expression { 
    type: "FunctionExpression"; 
}
ログイン後にコピー

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

UnaryExpression

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

interface UnaryExpression <: Expression { 
    type: "UnaryExpression"; 
    operator: UnaryOperator; 
    prefix: boolean; 
    argument: Expression; 
}
ログイン後にコピー

UnaryOperator

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

enum UnaryOperator { 
    "-" | "+" | "!" | "~" | "typeof" | "void" | "delete" 
}
ログイン後にコピー

UpdateExpression

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

interface UpdateExpression <: Expression { 
    type: "UpdateExpression"; 
    operator: UpdateOperator; 
    argument: Expression; 
    prefix: boolean; 
}
ログイン後にコピー

UpdateOperator

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

enum UpdateOperator { 
  "++" | "--" 
}
ログイン後にコピー

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

BinaryExpression

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

interface BinaryExpression <: Expression { 
    type: "BinaryExpression"; 
    operator: BinaryOperator; 
    left: Expression; 
    right: Expression; 
}
ログイン後にコピー

BinaryOperator

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

enum BinaryOperator { 
    "==" | "!=" | "===" | "!==" 
         | "<" | "<=" | ">" | ">=" 
         | "<<" | ">>" | ">>>" 
         | "+" | "-" | "*" | "/" | "%" 
         | "|" | "^" | "&" | "in" 
         | "instanceof" 
}
ログイン後にコピー

AssignmentExpression

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

interface AssignmentExpression <: Expression { 
    type: "AssignmentExpression"; 
    operator: AssignmentOperator; 
    left: Pattern | Expression; 
    right: Expression; 
}
ログイン後にコピー

AssignmentOperator

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

enum AssignmentOperator { 
    "=" | "+=" | "-=" | "*=" | "/=" | "%=" 
        | "<<=" | ">>=" | ">>>=" 
        | "|=" | "^=" | "&=" 
}
ログイン後にコピー

LogicalExpression

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

interface LogicalExpression <: Expression { 
    type: "LogicalExpression"; 
    operator: LogicalOperator; 
    left: Expression; 
    right: Expression; 
}
ログイン後にコピー

LogicalOperator

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

enum LogicalOperator { 
    "||" | "&&" 
}
ログイン後にコピー

MemberExpression

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

interface MemberExpression <: Expression, Pattern { 
    type: "MemberExpression"; 
    object: Expression; 
    property: Expression; 
    computed: boolean; 
}
ログイン後にコピー

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

ConditionalExpression

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

interface ConditionalExpression <: Expression { 
    type: "ConditionalExpression"; 
    test: Expression; 
    alternate: Expression; 
    consequent: Expression; 
}
ログイン後にコピー

CallExpression

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

interface CallExpression <: Expression { 
    type: "CallExpression"; 
    callee: Expression; 
    arguments: [ Expression ]; 
}
ログイン後にコピー

NewExpression

new 表达式

interface NewExpression <: CallExpression { 
    type: "NewExpression"; 
}
ログイン後にコピー

SequenceExpression

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

interface SequenceExpression <: Expression { 
    type: "SequenceExpression"; 
    expressions: [ Expression ]; 
}
ログイン後にコピー

Patterns

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

interface Pattern <: Node { }
ログイン後にコピー

这一部分的内容比较多,但都可以举一反三,写这个的时候我就当把 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 
}
ログイン後にコピー

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

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


このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。

ホットAIツール

Undresser.AI Undress

Undresser.AI Undress

リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover

AI Clothes Remover

写真から衣服を削除するオンライン AI ツール。

Undress AI Tool

Undress AI Tool

脱衣画像を無料で

Clothoff.io

Clothoff.io

AI衣類リムーバー

AI Hentai Generator

AI Hentai Generator

AIヘンタイを無料で生成します。

ホットツール

メモ帳++7.3.1

メモ帳++7.3.1

使いやすく無料のコードエディター

SublimeText3 中国語版

SublimeText3 中国語版

中国語版、とても使いやすい

ゼンドスタジオ 13.0.1

ゼンドスタジオ 13.0.1

強力な PHP 統合開発環境

ドリームウィーバー CS6

ドリームウィーバー CS6

ビジュアル Web 開発ツール

SublimeText3 Mac版

SublimeText3 Mac版

神レベルのコード編集ソフト(SublimeText3)

WebSocket と JavaScript を使用してオンライン音声認識システムを実装する方法 WebSocket と JavaScript を使用してオンライン音声認識システムを実装する方法 Dec 17, 2023 pm 02:54 PM

WebSocket と JavaScript を使用してオンライン音声認識システムを実装する方法 はじめに: 技術の継続的な発展により、音声認識技術は人工知能の分野の重要な部分になりました。 WebSocket と JavaScript をベースとしたオンライン音声認識システムは、低遅延、リアルタイム、クロスプラットフォームという特徴があり、広く使用されるソリューションとなっています。この記事では、WebSocket と JavaScript を使用してオンライン音声認識システムを実装する方法を紹介します。

WebSocket と JavaScript: リアルタイム監視システムを実装するための主要テクノロジー WebSocket と JavaScript: リアルタイム監視システムを実装するための主要テクノロジー Dec 17, 2023 pm 05:30 PM

WebSocketとJavaScript:リアルタイム監視システムを実現するためのキーテクノロジー はじめに: インターネット技術の急速な発展に伴い、リアルタイム監視システムは様々な分野で広く利用されています。リアルタイム監視を実現するための重要なテクノロジーの 1 つは、WebSocket と JavaScript の組み合わせです。この記事では、リアルタイム監視システムにおける WebSocket と JavaScript のアプリケーションを紹介し、コード例を示し、その実装原理を詳しく説明します。 1.WebSocketテクノロジー

JavaScript と WebSocket を使用してリアルタイムのオンライン注文システムを実装する方法 JavaScript と WebSocket を使用してリアルタイムのオンライン注文システムを実装する方法 Dec 17, 2023 pm 12:09 PM

JavaScript と WebSocket を使用してリアルタイム オンライン注文システムを実装する方法の紹介: インターネットの普及とテクノロジーの進歩に伴い、ますます多くのレストランがオンライン注文サービスを提供し始めています。リアルタイムのオンライン注文システムを実装するには、JavaScript と WebSocket テクノロジを使用できます。 WebSocket は、TCP プロトコルをベースとした全二重通信プロトコルで、クライアントとサーバー間のリアルタイム双方向通信を実現します。リアルタイムオンラインオーダーシステムにおいて、ユーザーが料理を選択して注文するとき

WebSocketとJavaScriptを使ったオンライン予約システムの実装方法 WebSocketとJavaScriptを使ったオンライン予約システムの実装方法 Dec 17, 2023 am 09:39 AM

WebSocket と JavaScript を使用してオンライン予約システムを実装する方法 今日のデジタル時代では、ますます多くの企業やサービスがオンライン予約機能を提供する必要があります。効率的かつリアルタイムのオンライン予約システムを実装することが重要です。この記事では、WebSocket と JavaScript を使用してオンライン予約システムを実装する方法と、具体的なコード例を紹介します。 1. WebSocket とは何ですか? WebSocket は、単一の TCP 接続における全二重方式です。

JavaScript と WebSocket: 効率的なリアルタイム天気予報システムの構築 JavaScript と WebSocket: 効率的なリアルタイム天気予報システムの構築 Dec 17, 2023 pm 05:13 PM

JavaScript と WebSocket: 効率的なリアルタイム天気予報システムの構築 はじめに: 今日、天気予報の精度は日常生活と意思決定にとって非常に重要です。テクノロジーの発展に伴い、リアルタイムで気象データを取得することで、より正確で信頼性の高い天気予報を提供できるようになりました。この記事では、JavaScript と WebSocket テクノロジを使用して効率的なリアルタイム天気予報システムを構築する方法を学びます。この記事では、具体的なコード例を通じて実装プロセスを説明します。私たちは

JavaScriptでinsertBeforeを使用する方法 JavaScriptでinsertBeforeを使用する方法 Nov 24, 2023 am 11:56 AM

使用法: JavaScript では、insertBefore() メソッドを使用して、DOM ツリーに新しいノードを挿入します。このメソッドには、挿入される新しいノードと参照ノード (つまり、新しいノードが挿入されるノード) の 2 つのパラメータが必要です。

簡単な JavaScript チュートリアル: HTTP ステータス コードを取得する方法 簡単な JavaScript チュートリアル: HTTP ステータス コードを取得する方法 Jan 05, 2024 pm 06:08 PM

JavaScript チュートリアル: HTTP ステータス コードを取得する方法、特定のコード例が必要です 序文: Web 開発では、サーバーとのデータ対話が頻繁に発生します。サーバーと通信するとき、多くの場合、返された HTTP ステータス コードを取得して操作が成功したかどうかを判断し、さまざまなステータス コードに基づいて対応する処理を実行する必要があります。この記事では、JavaScript を使用して HTTP ステータス コードを取得する方法を説明し、いくつかの実用的なコード例を示します。 XMLHttpRequestの使用

JavaScript と WebSocket: 効率的なリアルタイム画像処理システムの構築 JavaScript と WebSocket: 効率的なリアルタイム画像処理システムの構築 Dec 17, 2023 am 08:41 AM

JavaScript は Web 開発で広く使用されているプログラミング言語であり、WebSocket はリアルタイム通信に使用されるネットワーク プロトコルです。 2 つの強力な機能を組み合わせることで、効率的なリアルタイム画像処理システムを構築できます。この記事では、JavaScript と WebSocket を使用してこのシステムを実装する方法と、具体的なコード例を紹介します。まず、リアルタイム画像処理システムの要件と目標を明確にする必要があります。リアルタイムの画像データを収集できるカメラ デバイスがあるとします。

See all articles