前回の記事「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削除ステートメントの原則の違いを理解するだけです
序文最近、仕事で問題が発生しました。データベースを作成した後、データベースに接続するときにエラーが発...
1. インストールパッケージをダウンロードするダウンロードアドレス: https://sourcef...
1. インライン参照:ラベルに直接使用されるが、メンテナンスコストが高い スタイル='フォ...
目次序文クイックレビュー: JavaScript 関数関数とは何ですか?関数を宣言する関数の呼び出し...
では、早速リソースについて見ていきましょう。 123WORDPRESS.COM ダウンロードSQLy...
この記事では、MySQL のデータベース テーブルの容量を確認するためのコマンド ステートメントを紹...
CentOS 8にDockerをインストールする公式ドキュメント: https://docs.doc...
この記事では、グラフィック認証コードログインを実装するためのVueの具体的なコードを参考までに紹介し...
Tomcat は、Java Community Process を通じて Sun が開発した、広く使...
この記事では、スクロールウィンドウを実装するためのJavaScriptの具体的なコードを参考までに紹...
この記事の例では、参考までに簡単な虫眼鏡効果を実現するためのjsの具体的なコードを共有しています。具...
Mixin は、再利用可能な機能を Vue コンポーネント間で分散する非常に柔軟な方法を提供します。...
2次元配列の次元削減配列インスタンスメソッド concat と ES6 スプレッド演算子を使用した次...
目次質問再生暗黙的な変換要約する参照する質問仕事中、1 つの SQL クエリ ステートメントのみを実...
目次1. はじめに2. レンダリング3. コード4. 学生情報管理システムのメインインターフェース1...