Vue3 AST Parser-Quellcode-Analyse

Vue3 AST Parser-Quellcode-Analyse

Im vorherigen Artikel Vue3-Kompilierungsprozess – Quellcodeanalyse haben wir mit dem Eintrag packges/vue/src/index.ts begonnen und den Kompilierungsprozess eines Vue-Objekts kennengelernt. In dem Artikel haben wir erwähnt, dass baseCompile während der Ausführung einen abstrakten AST-Syntaxbaum generiert. Dies ist zweifellos ein kritischer Schritt, da wir nur durch Abrufen des generierten AST die Knoten des AST durchlaufen können, um Transformationsvorgänge auszuführen, z. B. verschiedene Anweisungen wie v-if und v-for zu analysieren oder die Knoten zu analysieren, um die Knoten, die die Bedingungen erfüllen, statisch zu fördern. All dies basiert auf dem zuvor generierten abstrakten AST-Syntaxbaum. Heute werden wir einen Blick auf die AST-Analyse werfen und uns ansehen, wie Vue Vorlagen analysiert.

1. Generieren Sie einen abstrakten AST-Syntaxbaum

Lassen Sie uns zunächst die Logik von ast in baseCompile und ihre anschließende Verwendung überprüfen:

Exportfunktion baseCompile(
  Vorlage: Zeichenfolge | RootNode,
  Optionen: CompilerOptions = {}
): CodegenResult {

  /* Vorherige Logik ignorieren*/

  const ast = isString(Vorlage) ? baseParse(Vorlage, Optionen) : Vorlage

  verwandeln(
    ast,
    {/* Parameter ignorieren */}
  )

  Rückgabe generieren(
    ast,
    erweitern({}, Optionen, {
      Präfixkennungen
    })
  )
}

Da ich die Logik, auf die wir nicht achten müssen, auskommentiert habe, ist die Logik im Funktionskörper jetzt sehr klar:

  • AST-Objekt generieren
  • Übergeben Sie ast Objekt als Parameter an die transform , um ast Knoten zu transformieren.
  • Übergeben Sie das AST-Objekt als Parameter an die generate und geben Sie das kompilierte Ergebnis zurück.

Hier konzentrieren wir uns hauptsächlich auf die Generierung von AST. Es ist ersichtlich, dass die Generierung von ast eine ternäre Operatorbeurteilung hat. Wenn der übergebene template eine Zeichenfolge ist, wird baseParse aufgerufen, um die Vorlagenzeichenfolge zu analysieren, andernfalls wird template direkt als ast Objekt verwendet. Was wird in baseParse getan, um ast zu generieren? Werfen wir einen Blick auf den Quellcode.

Exportfunktion baseParse(
  Inhalt: Zeichenfolge,
  Optionen: ParserOptions = {}
): Stammknoten {
  const context = createParserContext(content, options) // Ein Parsing-Kontext-Objekt erstellen const start = getCursor(context) // Cursor-Informationen generieren um den Parsing-Prozess aufzuzeichnen return createRoot( // Stammknoten generieren und zurückgeben parseChildren(context, TextModes.DATA, []), // Kindknoten als untergeordnetes Attribut des Stammknotens analysieren getSelection(context, start)
  )
}

Ich habe der Funktion baseParse Kommentare hinzugefügt, damit Sie die Rolle jeder Funktion besser verstehen. Zuerst wird der Analysekontext erstellt und dann werden die Cursorinformationen basierend auf dem Kontext abgerufen. Da die Analyse noch nicht durchgeführt wurde, entsprechen column , line und offset im Cursor alle der Startposition template . Der nächste Schritt besteht darin, einen Stammknoten zu erstellen und den Stammknoten zurückzugeben. An diesem Punkt wird der AST-Baum generiert und die Analyse abgeschlossen.

2. Erstellen Sie den Stammknoten von AST

Exportfunktion createRoot(
  untergeordnete Elemente: TemplateChildNode[],
  loc = locStub
): Stammknoten {
  zurückkehren {
    Typ: NodeTypes.ROOT,
    Kinder,
    Helfer: [],
    Komponenten: [],
    Anweisungen: [],
    Hebezeuge: [],
    Importe: [],
    zwischengespeichert: 0,
    Temperaturen: 0,
    CodegenNode: undefiniert,
    loc
  }
}

Wenn wir uns den Code der Funktion createRoot ansehen, können wir feststellen, dass die Funktion ein Stammknotenobjekt vom Typ RootNode zurückgibt, in dem der von uns übergebene Kinderparameter als children des Stammknotens verwendet wird. Das ist sehr leicht zu verstehen. Stellen Sie es sich einfach als eine Baumdatenstruktur vor. Daher liegt der Schwerpunkt bei der Generierung von AST auf der Funktion parseChildren . Wenn Sie sich den Quellcode der Funktion parseChildren nicht ansehen, können Sie aus dem Text ungefähr erkennen, dass dies eine Funktion zum Parsen von untergeordneten Knoten ist. Als nächstes werfen wir einen Blick auf die wichtigste parseChildren -Funktion beim AST-Parsing. Wie üblich werde ich die Logik in der Funktion vereinfachen, damit Sie sie leichter verstehen.

3. Untergeordnete Knoten analysieren

Funktion parseChildren(
  Kontext: ParserContext,
  Modus: Textmodi,
  Vorfahren: ElementNode[]
): VorlageChildNode[] {
  const parent = last(ancestors) // Den übergeordneten Knoten des aktuellen Knotens abrufen const ns = parent ? parent.ns : Namespaces.HTML
  const nodes: TemplateChildNode[] = [] // Analysiere die analysierten Knoten // Wenn das Label nicht geschlossen ist, analysiere den entsprechenden Knoten while (!isEnd(context, mode, ancestors)) {/* Logik ignorieren*/}

  // Verarbeiten Sie Leerzeichen, um die Ausgabeeffizienz zu verbessern. let removedWhitespace = false
  if (mode !== TextModes.RAWTEXT && mode !== TextModes.RCDATA) {/* Logik ignorieren*/}

  // Leerzeichen entfernen und das analysierte Knoten-Array zurückgeben return removedWhitespace ? nodes.filter(Boolean) : nodes
}

Aus dem obigen Code können wir erkennen, dass parseChildren drei Parameter empfängt: context : Parserkontext, mode : Textdatentyp, ancestors “: Vorfahrenknoten-Array. Beim Ausführen der Funktion wird zuerst der übergeordnete Knoten des aktuellen Knotens vom Vorgängerknoten abgerufen, der Namespace bestimmt und ein leeres Array zum Speichern der analysierten Knoten erstellt. Danach gibt es eine while-Schleife, um zu bestimmen, ob die Schließposition des Tags erreicht wurde. Wenn es sich nicht um ein Tag handelt, das geschlossen werden muss, wird die Quellvorlagenzeichenfolge im Schleifenkörper klassifiziert und analysiert. Danach folgt eine Logik zur Verarbeitung von Leerzeichen. Nach der Verarbeitung wird das analysierte Knoten-Array zurückgegeben. Nachdem Sie nun ein erstes Verständnis des Ausführungsflusses von parseChildren haben, werfen wir einen Blick auf den Kern der Funktion, die Logik innerhalb der while-Schleife.

In der While-Anweisung bestimmt der Parser den Typ der Textdaten und setzt die Analyse nur fort, wenn TextModes DATA oder RCDATA ist.

Der erste Fall besteht darin, zu bestimmen, ob die „ Mustache “-Syntax (doppelte Klammern) in der Vue-Vorlagensyntax analysiert werden muss. Wenn im aktuellen Kontext keine v-pre-Anweisung zum Überspringen des Ausdrucks vorhanden ist und die Quellvorlagenzeichenfolge mit dem von uns angegebenen Trennzeichen beginnt (in diesem Fall enthält context.options.delimiters doppelte Klammern), werden die doppelten Klammern analysiert. Hier sehen Sie, dass Sie, wenn Sie spezielle Anforderungen haben und keine doppelten Klammern als Ausdrucksinterpolation verwenden möchten, vor dem Kompilieren nur delimiters Trennzeicheneigenschaft in den Optionen ändern müssen.

Wenn das erste Zeichen "<" und das zweite Zeichen '!' ist, wird als nächstes versucht, das Kommentar-Tag ,<!DOCTYPE und <!CDATA zu analysieren. In allen drei Fällen wird DOCTYPE ignoriert und als Kommentar analysiert.

Anschließend wird ermittelt, dass „</“, wenn das zweite Zeichen „/“ ist, die Bedingungen eines schließenden Tags erfüllt und es wird versucht, eine Übereinstimmung mit dem schließenden Tag zu finden. Wenn das dritte Zeichen ">" ist, fehlt der Tag-Name, es wird ein Fehler gemeldet und der Parser geht drei Zeichen weiter und überspringt "</>".

Wenn es mit „</“ beginnt und das dritte Zeichen ein englischer Kleinbuchstabe ist, analysiert der Parser das End-Tag.

Wenn das erste Zeichen der Quellvorlagenzeichenfolge „<“ ist und das zweite Zeichen mit einem englischen Kleinbuchstaben beginnt, wird parseElement aufgerufen, um das entsprechende Tag zu analysieren.

Wenn die Verzweigungsbedingung zur Beurteilung der Zeichenfolge endet und kein Knoten analysiert wird, wird der Knoten als Texttyp behandelt und zum Analysieren wird „parseText“ aufgerufen.

Fügen Sie abschließend den generierten Knoten zum nodes Array hinzu und geben Sie ihn am Ende der Funktion zurück.

Dies ist die Logik innerhalb der while-Schleife und der wichtigste Teil von parseChildren . In diesem Beurteilungsprozess haben wir die Analyse der Syntax der doppelten geschweiften Klammern, die Analyse des Kommentarknotens, die Analyse des Start-Tags und des schließenden Tag sowie die Analyse des Textinhalts gesehen. Der vereinfachte Code befindet sich in der Box unten. Sie können die obige Erklärung zu Rate ziehen, um den Quellcode zu verstehen. Natürlich sind auch die Kommentare im Quellcode sehr ausführlich.

während (!isEnd(Kontext, Modus, Vorfahren)) {
  const s = Kontext.Quelle
  let-Knoten: TemplateChildNode | TemplateChildNode[] | undefined = undefiniert

  wenn (Modus === TextModes.DATA || Modus === TextModes.RCDATA) {
    wenn (!context.inVPre && startsWith(s, context.options.delimiters[0])) {
      /* Wenn das Tag keine v-pre-Direktive hat, beginnt die Quellvorlagenzeichenfolge mit doppelten geschweiften Klammern `{{` und wird gemäß der Syntax der doppelten geschweiften Klammern analysiert*/
      Knoten = parseInterpolation(Kontext, Modus)
    } sonst wenn (Modus === TextModes.DATA && s[0] === '<') {
      // Wenn die erste Zeichenposition der Quellvorlagenzeichenfolge „!“ ist.
      wenn (s[1] === '!') {
    // Wenn es mit '<!--' beginnt, analysiere es als Kommentar if (startsWith(s, '<!--')) {
          Knoten = parseComment(Kontext)
        } sonst wenn (startetMit(s, '<!DOCTYPE')) {
     // Wenn es mit „<!DOCTYPE“ beginnt, ignoriere DOCTYPE und analysiere es als Pseudokommentar node = parseBogusComment(context)
        } sonst wenn (startetMit(s, '<![CDATA[')) {
          // Wenn es mit '<![CDATA[' beginnt und sich in einer HTML-Umgebung befindet, analysieren Sie CDATA
          wenn (ns !== Namespaces.HTML) {
            Knoten = parseCDATA(Kontext, Vorfahren)
          }
        }
      // Wenn die zweite Zeichenposition der Quellvorlagenzeichenfolge '/' ist
      } sonst wenn (s[1] === '/') {
        // Wenn die dritte Zeichenposition der Quellvorlagenzeichenfolge '>' ist, handelt es sich um ein selbstschließendes Tag, und die Scanposition wird um drei Zeichen nach vorne verschoben, wenn (s[2] === '>') {
          emitError(Kontext, Fehlercodes.FEHLENDE_END_TAG_NAME, 2)
          AdvanceBy(Kontext, 3)
          weitermachen
        // Wenn die dritte Zeichenposition ein englisches Zeichen ist, analysiere das End-Tag} else if (/[az]/i.test(s[2])) {
          parseTag(Kontext, TagType.Ende, übergeordnetes Element)
          weitermachen
        } anders {
          // Wenn dies oben nicht der Fall ist, analysieren Sie es als Pseudokommentar node = parseBogusComment(context)
        }
      // Wenn das zweite Zeichen des Tags ein englisches Kleinbuchstabe ist, wird es als Element-Tag analysiert} else if (/[az]/i.test(s[1])) {
        Knoten = ParseElement(Kontext, Vorfahren)
        
      // Wenn das zweite Zeichen '?' ist, interpretiere es als Pseudokommentar} else if (s[1] === '?') {
        Knoten = parseBogusComment(Kontext)
      } anders {
        // Wenn keine dieser Bedingungen erfüllt ist, wird eine Fehlermeldung ausgegeben, die darauf hinweist, dass das erste Zeichen kein gültiges Beschriftungszeichen ist.
        emitError(Kontext, Fehlercodes.UNGÜLTIGES ERSTES ZEICHEN DES TAGNAMENS, 1)
      }
    }
  }
  
  // Wenn nach der Analyse der obigen Situation kein entsprechender Knoten erstellt wird, analysieren Sie ihn als Text, wenn (! Knoten) {
    Knoten = parseText(Kontext, Modus)
  }
  
  // Wenn der Knoten ein Array ist, durchlaufe ihn und füge ihn dem Knoten-Array hinzu, andernfalls füge ihn direkt hinzu if (isArray(node)) {
    für (lass i = 0; i < Knotenlänge; i++) {
      pushNode(Knoten, Knoten[i])
    }
  } anders {
    pushNode(Knoten, Knoten)
  }
}

4. Vorlagenelemente analysieren

In der while Schleife können wir in jedem Verzweigungsbeurteilungszweig sehen, node den Rückgabewert der Analysefunktion verschiedener Knotentypen erhält. Hier werde ich ausführlich auf die Funktion parseElement eingehen, da dies das am häufigsten verwendete Szenario in Vorlagen ist.

Ich werde zuerst den Quellcode von parseElement vereinfachen und hier einfügen und dann über die darin enthaltene Logik sprechen.

Funktion parseElement(
  Kontext: ParserContext,
  Vorfahren: ElementNode[]
): ElementNode | undefiniert {
  //Start-Tag analysieren const parent = last(ancestors)
  const-Element = parseTag(Kontext, TagType.Start, übergeordnetes Element)
  
  // Wenn es ein selbstschließendes Tag oder ein leeres Tag ist, direkt zurückkehren. voidTagBeispiel: `<img>`, `<br>`, `<hr>`
  wenn (element.isSelfClosing || context.options.isVoidTag(element.tag)) {
    Rücklaufelement
  }

  // Untergeordnete Knoten rekursiv analysieren ancestors.push(element)
  const mode = context.options.getTextMode(Element, übergeordnetes Element)
  const children = parseChildren(Kontext, Modus, Vorfahren)
  Vorfahren.pop()

  element.children = Kinder

  // Analysiere das End-Tag if (startsWithEndTagOpen(context.source, element.tag)) {
    parseTag(Kontext, TagType.Ende, übergeordnetes Element)
  } anders {
    emitError(Kontext, ErrorCodes.X_MISSING_END_TAG, 0, Element.loc.start)
    wenn (context.source.length === 0 und element.tag.toLowerCase() === 'script') {
      const erste = Kinder[0]
      wenn (first && beginnt mit (first.loc.source, '<!--')) {
        emitError(Kontext, Fehlercodes.EOF_IN_SCRIPT_HTML_COMMENT_LIKE_TEXT)
      }
    }
  }
  // Holen Sie sich das Beschriftungspositionsobjekt element.loc = getSelection(context, element.loc.start)

  Rücklaufelement
}

Zuerst holen wir uns den übergeordneten Knoten des aktuellen Knotens und rufen dann parseTag auf, um ihn zu analysieren.

Die Funktion parseTag wird gemäß dem folgenden Prozess ausgeführt:

  • Passen Sie zuerst den Tag-Namen an.
  • Analysieren Sie die Attributattribute im Element und speichern Sie sie im Attribut „props“
  • Überprüfen Sie, ob eine v-pre-Anweisung vorhanden ist. Wenn ja, ändern Sie das inVPre-Attribut im Kontext auf „true“.
  • Erkennen Sie selbstschließende Tags. Wenn sie selbstschließend sind, setzen Sie die Eigenschaft isSelfClosing auf true.
  • Bestimmen Sie den Tag-Typ, ob es sich um ein ELEMENT-Element, eine COMPONENT-Komponente oder einen SLOT-Slot handelt
  • Gibt das generierte Elementobjekt zurück

Nach dem Abrufen element wird ermittelt, ob es sich element um ein selbstschließendes Tag oder ein leeres Tag wie <img>, <br>, <hr> handelt. Wenn dies der Fall ist, wird element direkt zurückgegeben.

Dann versuchen wir, die untergeordneten Knoten des element zu analysieren, element in den Stapel zu schieben und dann rekursiv parseChildren aufzurufen, um die untergeordneten Knoten zu analysieren.

const parent = letzte(Vorfahren)

Wenn wir auf die Codezeilen in parseChildren und parseElement zurückblicken, können wir feststellen, dass der übergeordnete Knoten, den wir erhalten, der aktuelle Knoten ist, nachdem wir element in den Stapel geschoben haben. Nachdem die Analyse abgeschlossen ist, rufen Sie ancestors.pop() auf, um element zu entfernen, dessen untergeordneter Knoten gerade analysiert wird, weisen Sie das analysierte children Objekt children -Attribut des element zu und schließen Sie die Analyse des untergeordneten Knotens des element ab. Dies ist ein sehr cleveres Design.

Passen Sie abschließend das End-Tag an, legen Sie die Positionsinformationen des Elements fest und geben Sie das analysierte element zurück.

5. Beispiel: Analyse von Vorlagenelementen

Bitte sehen Sie sich die Vorlage an, die wir unten analysieren werden. Das Bild zeigt die Speicherung des Knotenstapels nach der Analyse während des Analysevorgangs.

<div>
  <p>Hallo Welt</p>
</div>

Das gelbe Rechteck in der Abbildung ist ein Stapel. Wenn die Analyse beginnt, stößt parseChildren zuerst auf das div-Tag und beginnt mit dem Aufruf der Funktion parseElement . Das Div-Element wird mit der Funktion „parseTag“ analysiert, in den Stapel geschoben und die untergeordneten Knoten werden rekursiv analysiert. Beim zweiten Aufruf der Funktion parseChildren wird das p-Element gefunden und die Funktion parseElement wird aufgerufen, um das p-Tag in den Stapel zu schieben. Zu diesem Zeitpunkt befinden sich zwei Tags, div und p, im Stapel. Analysieren Sie die untergeordneten Knoten in p erneut und rufen Sie das Tag parseChildren zum dritten Mal auf. Dieses Mal wird kein Tag gefunden und kein entsprechender Knoten generiert. Daher wird die Funktion parseText verwendet, um Text zu generieren, den Knoten als HelloWorld zu analysieren und den Knoten zurückzugeben.

Nachdem dieser node zum untergeordneten Attribut des p-Tags hinzugefügt wurde, werden die untergeordneten Knoten des p-Tags analysiert, der Vorgängerstapel wird entfernt und nach der Analyse des End-Tags wird element dem p-Tag entsprechende Elementobjekt zurückgegeben.

Der dem p-Tag entsprechende Knoten wird generiert und der entsprechende Knoten wird in parseChildren zurückgegeben.

Nachdem der Knoten vom p-Tag empfangen wurde, fügt das div-Tag ihn seinem eigenen untergeordneten Attribut hinzu und entfernt ihn vom Stapel. Zu diesem Zeitpunkt ist der Ahnenstapel leer. Nachdem das Div-Tag die geschlossene Analyselogik abgeschlossen hat, gibt es das element zurück.

Schließlich gibt der erste Aufruf von parseChildren das Ergebnis zurück, generiert das dem div entsprechende Knotenobjekt und gibt auch das Ergebnis zurück. Dieses Ergebnis wird als children-Parameter createRoot -Funktion übergeben, um das Stammknotenobjekt zu generieren und die AST-Analyse abzuschließen.

Dies ist das Ende dieses Artikels über die Quellcodeanalyse Vue3 AST Parsers. Weitere verwandte Inhalte zum Vue3 AST-Parser finden Sie in den vorherigen Artikeln von 123WORDPRESS.COM oder in den folgenden verwandten Artikeln. Ich hoffe, dass jeder 123WORDPRESS.COM in Zukunft unterstützen wird!

Das könnte Sie auch interessieren:
  • Vue3-Kompilierungsprozess - Quellcodeanalyse
  • Details zu 7 Arten der Komponentenkommunikation in Vue3
  • Detaillierte Erläuterung der Vue3-Kapselungsfunktion für Nachrichtenaufforderungsinstanzen
  • Der Unterschied und die Verwendung des Kommunikationsbusses der Bruderkomponenten von Vue2 und Vue3
  • Verwenden von vue3 zum Implementieren eines Beispiels für die Kapselung von Zählfunktionskomponenten
  • Vue3.0 implementiert die Kapselung des Dropdown-Menüs
  • Vue3.0 implementiert die Kapselung von Kontrollkästchenkomponenten
  • Vergleich der Vorteile von vue3 und vue2
  • Praktischer Bericht über die Entwicklung von Vue3- und TypeScript-Projekten
  • Zusammenfassung der Projektentwicklungspraxis in Kombination mit Vue3 von TypeScript

<<:  So aktualisieren, verpacken und laden Sie Docker-Container in die Alibaba Cloud hoch

>>:  Verstehen Sie einfach die Unterschiede in den Prinzipien gängiger SQL-Delete-Anweisungen

Artikel empfehlen

Protokoll des Kompilierungs- und Installationsprozesses des Nginx-Quellcodes

Die Installation des RPM-Pakets ist relativ einfa...

...

CSS3-Implementierungscode für einfaches Karussellbildschneiden

Umsetzungsideen Erstellen Sie zunächst einen über...

Installieren Sie MySQL 5.7 unter Ubuntu 18.04

Dieser Artikel wurde unter Bezugnahme auf die off...

Beispielcode des Spread-Operators und seiner Anwendung in JavaScript

Der Spread-Operator ermöglicht die Erweiterung ei...

jQuery verwendet das Canvas-Tag, um den Bestätigungscode zu zeichnen

Das <canvas>-Element ist für clientseitige ...

Detaillierte Erläuterung der Nginx-Anti-Hotlink- und Anti-Crawler-Konfiguration

Erstellen Sie eine neue Konfigurationsdatei (gehe...

So implementieren Sie den Dienststatus zur Nginx-Konfigurationserkennung

1. Überprüfen Sie, ob das Modul „Status prüfen“ i...

Design-Sharing der Download-Seite des Pengyou.com-Mobilclients (Bild und Text)

Schauen wir uns zunächst einige einfache Daten an:...