前回の記事「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削除ステートメントの原則の違いを理解するだけです
序文springboot設定ファイルでは、設定ファイルの名前には独自の意味と用途があります。 dev...
はじめに:ナビゲーション バーなどのコンポーネント ベースのアイデアを使用して機能モジュールを完全に...
コンポーネントに背景画像コントロールを追加するには、次の 2 つの手順だけが必要です。 <表示...
<br />まず最初に、私はこのグループの中では完全な新人だということを述べなければなり...
nginx 設定ファイルのパスを表示する nginx -t 経由nginx -t コマンドの本来の機...
序文vsftp は使いやすく安全な FTP サーバー ソフトウェアです。システムユーザーまたは仮想ユ...
Nginx のインストールCentOS 6.x yum にはデフォルトで nginx ソフトウェア ...
1. アクセス頻度、同時接続、ダウンロード速度を制限するために使用されるモジュールと命令の概要ngx...
MySQL解凍版とNavicatデータベース操作ツールのインストールは、以下のとおりです。 1. M...
最近、 Three.jsでReactを使用して、720 度のパノラマ写真を閲覧できるプロジェクトを構...
デザインの分野では、毎年さまざまなデザインのトレンドや流行があります。たとえば、近年のレスポンシブデ...
目次それを覆う栗パラメータの受け渡し値渡し共同配送要約する拡張機能 - 遅延評価私は最近、JavaS...
目次安定スロットリング要約する安定自動ドアは人を感知してドアを開け、5 秒間のカウントダウンを開始し...
/****************** * Linux カーネルの時間管理 ***********...
この記事では、例を使用して、MySQL マスター/スレーブ レプリケーションと読み取り/書き込み分離...