前回の記事「Vue3 のコンパイル プロセス - ソース コード分析」では、 1. AST抽象構文木を生成するまず、 エクスポート関数baseCompile( テンプレート: 文字列 | RootNode, オプション: CompilerOptions = {} ): コード生成結果 { /* 前のロジックを無視*/ const ast = isString(テンプレート) ? baseParse(テンプレート、オプション) : テンプレート 変身( アス、 パラメータを無視する */ ) 生成を返す( アス、 拡張({}, オプション, { プレフィックス識別子 }) ) } 注意を払う必要のないロジックをコメントアウトしたので、関数本体のロジックは非常に明確になります。
ここでは主に ast の生成に焦点を当てます。 ast の生成には三項演算子の判断があることがわかります。渡された エクスポート関数baseParse( 内容: 文字列、 オプション: ParserOptions = {} ): ルートノード { const context = createParserContext(content, options) // 解析コンテキスト オブジェクトを作成します const start = getCursor(context) // 解析プロセスを記録するためのカーソル情報を生成します return createRoot( // ルート ノードを生成して返します parseChildren(context, TextModes.DATA, []), // 子ノードをルート ノードの children 属性として解析します getSelection(context, start) ) } 各関数の役割を理解しやすくするために、 2. ASTのルートノードを作成するエクスポート関数createRoot( 子: TemplateChildNode[], loc = locスタブ ): ルートノード { 戻る { タイプ: NodeTypes.ROOT、 子供たち、 ヘルパー: [], コンポーネント: [], ディレクティブ: [], ホイスト: [], インポート: [], キャッシュ済み: 0, 気温: 0, codegenNode: 未定義、 場所 } }
3. 子ノードの解析関数parseChildren( コンテキスト: ParserContext、 モード: テキストモード、 祖先: ElementNode[] ): テンプレート子ノード[] { const parent = last(ancestors) // 現在のノードの親ノードを取得します。const ns = parent ? parent.ns : Namespaces.HTML const nodes: TemplateChildNode[] = [] // 解析されたノードを保存 // ラベルが閉じられていない場合は、対応するノードを解析します while (!isEnd(context, mode, ancestors)) {/* ロジックを無視*/ // 出力効率を向上させるために空白文字を処理します let removedWhitespace = false if (mode !== TextModes.RAWTEXT && mode !== TextModes.RCDATA){/* ロジックを無視*/ // 空白文字を削除し、解析されたノード配列を返します。 return removedWhitespace ? nodes.filter(Boolean) : nodes } 上記のコードから、 while ステートメントでは、パーサーはテキスト データの種類を判別し、 最初のケースは、Vue テンプレート構文の「 次に、最初の文字が「<」で、2 番目の文字が「!」の場合、コメント タグ 次に、2 番目の文字が "/" の場合、"</" が終了タグの条件を満たしていると判断し、終了タグと一致させようとします。 3 番目の文字が ">" の場合、タグ名が欠落しているためエラーが報告され、パーサーは "</>" をスキップして 3 文字先に進みます。 「</」で始まり、3 番目の文字が小文字の英語の文字である場合、パーサーは終了タグを解析します。 ソース テンプレート文字列の最初の文字が "<" で、2 番目の文字が小文字の英語の文字で始まる場合、 文字列の文字を判定する分岐条件が終了し、解析されたノードがない場合、ノードはテキスト型として扱われ、解析のために parseText が呼び出されます。 最後に、生成されたノードを これは while ループ内のロジックであり、 while (!isEnd(コンテキスト、モード、祖先)) { const s = コンテキスト.ソース ノード: TemplateChildNode | TemplateChildNode[] | undefined = undefined モード === TextModes.DATA || モード === TextModes.RCDATA) { コンテキスト.inVPre が s で始まり、コンテキスト.options.delimiters[0] が 0 の場合 /* タグに v-pre ディレクティブがない場合、ソース テンプレート文字列は二重中括弧 `{{` で始まり、二重中括弧の構文に従って解析されます */ ノード = parseInterpolation(コンテキスト、モード) } それ以外の場合 (mode === TextModes.DATA && s[0] === '<') { // ソーステンプレート文字列の最初の文字位置が `!` の場合 s[1] === '!' の場合 { // '<!--' で始まる場合はコメントとして解析します if (startsWith(s, '<!--')) { ノード = parseComment(コンテキスト) } そうでない場合 (startsWith(s, '<!DOCTYPE')) { // '<!DOCTYPE' で始まる場合は、DOCTYPE を無視して疑似コメントとして解析します。node = parseBogusComment(context) } そうでない場合 (startsWith(s, '<![CDATA[')) { // '<![CDATA['で始まり、HTML環境にある場合は、CDATAを解析します if (ns !== Namespaces.HTML) { ノード = parseCDATA(コンテキスト、祖先) } } // ソーステンプレート文字列の2番目の文字位置が '/' の場合 } そうでない場合 (s[1] === '/') { // ソーステンプレート文字列の3番目の文字位置が '>' の場合、それは自己終了タグであり、スキャン位置は3文字前方に移動します if (s[2] === '>') { 出力エラー(コンテキスト、ErrorCodes.MISSING_END_TAG_NAME、2) advanceBy(コンテキスト, 3) 続く // 3番目の文字位置が英語の文字の場合は、終了タグを解析します} else if (/[az]/i.test(s[2])) { parseTag(コンテキスト、TagType.End、親) 続く } それ以外 { // 上記に当てはまらない場合は、疑似コメントとして解析します。node = parseBogusComment(context) } // タグの2番目の文字が小文字の英語文字の場合、要素タグとして解析されます} else if (/[az]/i.test(s[1])) { ノード = parseElement(コンテキスト、祖先) // 2番目の文字が '?' の場合、疑似コメントとして解釈します} else if (s[1] === '?') { ノード = parseBogusComment(コンテキスト) } それ以外 { // これらの条件がいずれも満たされない場合は、最初の文字が有効なラベル文字ではないことを示すエラー メッセージが表示されます。 出力エラー(コンテキスト、エラーコード。タグ名の最初の文字が無効、1) } } } // 上記の状況を解析した後に対応するノードが作成されない場合は、テキストとして解析します if (!node) { ノード = parseText(コンテキスト、モード) } // ノードが配列の場合は、トラバースしてノード配列に追加し、そうでない場合は直接追加します if (isArray(node)) { (i = 0 とします; i < node.length; i++) { pushNode(ノード、ノード[i]) } } それ以外 { pushNode(ノード、ノード) } } 4. テンプレート要素の解析
まず 関数parseElement( コンテキスト: ParserContext、 祖先: ElementNode[] ): ElementNode | 未定義 { // 開始タグを解析する const parent = last(ancestors) const 要素 = parseTag(コンテキスト、TagType.Start、親) // 自己終了タグまたは空タグの場合は、直接戻ります。 voidTag の例: `<img>`、`<br>`、`<hr>` if (element.isSelfClosing || context.options.isVoidTag(element.tag)) { 戻り要素 } // 子ノードを再帰的に解析する ancestors.push(element) 定数モード = context.options.getTextMode(要素、親) const children = parseChildren(コンテキスト、モード、祖先) 祖先.pop() 要素.children = 子供 // 終了タグを解析します if (startsWithEndTagOpen(context.source, element.tag)) { parseTag(コンテキスト、TagType.End、親) } それ以外 { 出力エラー(コンテキスト、ErrorCodes.X_MISSING_END_TAG、0、要素.loc.start) context.source.length === 0 && element.tag.toLowerCase() === 'script' の場合 { 定数 first = children[0] if (first && startsWith(first.loc.source, '<!--')) { 出力エラー(コンテキスト、ErrorCodes.EOF_IN_SCRIPT_HTML_COMMENT_LIKE_TEXT) } } } // ラベル位置オブジェクトを取得します。element.loc = getSelection(context, element.loc.start) 戻り要素 } まず、現在のノードの親ノードを取得し、次に parseTag 関数は次のプロセスに従って実行されます。
次に、 定数親 = 最後(祖先)
最後に、終了タグを一致させ、要素の loc 位置情報を設定し、解析された 5. 例: テンプレート要素の解析以下に解析するテンプレートを示します。この図は、解析プロセス中に解析した後のノードのスタックのストレージを示しています。 <div> <p>こんにちは世界</p> </div> 図の黄色の四角形はスタックです。解析が開始されると、 このテキスト型 p タグに対応するノードが生成され、対応するノードが p タグからノードを受け取った後、div タグはそれを自身の children 属性に追加し、スタックからポップします。この時点では、祖先スタックは空です。 div タグは閉じた解析ロジックを完了すると、 最後に、 以下もご興味があるかもしれません:
|
<<: Dockerコンテナを更新、パッケージ化、Alibaba Cloudにアップロードする方法
>>: 一般的なSQL削除ステートメントの原則の違いを理解するだけです
多くの友人は、フロントエンドを学習するときに、ボックス モデルがデフォルトで正方形であることに気付き...
「Enter != Submit」問題を実装するには、通常、「ボタンの種類」と「入力ボックスの数」か...
Founder Type Library は、Founder Type Library ビジネス チ...
目次ストアドプロシージャの概要ストアド プロシージャを使用する理由は何ですか?ストアドプロシージャの...
この記事の例は MySQL 5.0 以降で実行されます。ユーザー権限を付与するための MySQL コ...
Linux では、通常、ファイルの名前を変更するために mv コマンドを使用します。これは、単一のフ...
導入HTML は、Web ドキュメントのコンテンツのコンテキスト構造と意味を提供します。HTML 自...
最近、vue について読みました。これまで基本的に見落としていた単一ファイル コンポーネントを見つけ...
バックエンドからフロントエンドまで、なんと悲劇なのでしょう。他の人の CSS を自分の jsp We...
プロジェクトシナリオ: 1. アップロードファイルの制限関数: 1. フロントエンド操作による異常な...
目次ノードのバージョンが一致しない、ノードをアップグレードまたはダウングレードするnvm を使用して...
1. 何ですかCSS アニメーションは、CSS を使用して拡張マークアップ言語 (XML) 要素をア...
zhangxinxu より https://www.zhangxinxu.com/wordpress...
サーバー情報管理サーバー: m01 172.16.1.61サーバー: nfs01 172.16.1....
1. ElasticSearch 6.4.1 インストール パッケージを次の場所からダウンロードしま...