Ein Proof of Concept zur Beschleunigung von Sass

Avatar of Sebastian Webb
Von Sebastian Webb am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

Zu Beginn eines neuen Projekts erfolgt die Sass-Kompilierung im Handumdrehen. Das fühlt sich großartig an, besonders wenn es mit Browsersync gekoppelt ist, das die Stylesheets für uns im Browser neu lädt. Aber mit zunehmender Menge an Sass steigt die Kompilierungszeit. Das ist alles andere als ideal.

Es kann eine echte Plage sein, wenn die Kompilierungszeit über ein oder zwei Sekunden kriecht. Für mich ist das genug Zeit, um am Ende eines langen Tages den Fokus zu verlieren. Daher möchte ich eine Lösung vorstellen, die in einem WordPress CSS Editor namens Microthemer als Proof of Concept verfügbar ist.

Dies ist ein zweiteiliger Artikel. Der erste Teil richtet sich an Sass-Benutzer. Wir stellen die Grundprinzipien, Leistungsergebnisse und eine interaktive Demo vor. Der zweite Teil behandelt die technischen Details, wie Microthemer Sass schneller macht. Und erörtert, wie dies als npm-Paket implementiert werden kann, um schnelles, skalierbares Sass für eine viel breitere Entwicklergemeinschaft bereitzustellen.

Wie Microthemer Sass im Handumdrehen kompiliert

In gewisser Weise ist diese Leistungsoptimierung einfach. Microthemer kompiliert einfach weniger Sass-Code. Es greift nicht in den internen Kompilierungsprozess von Sass ein.

Um dem Sass-Compiler weniger Code zuzuführen, behält Microthemer ein Verständnis der Sass-Entitäten bei, die im gesamten Code-Basis verwendet werden, wie z. B. Variablen und Mixins. Wenn ein Selektor bearbeitet wird, kompiliert Microthemer nur diesen einzelnen Selektor plus alle zugehörigen Selektoren. Selektoren sind verwandt, wenn sie beispielsweise dieselben Variablen verwenden oder ein Selektor einen anderen erweitert. Mit diesem System bleibt die Sass-Kompilierung mit 3000 Selektoren genauso schnell wie mit einer Handvoll.

Leistungsergebnisse

Bei 3000 Selektoren beträgt die Kompilierungszeit etwa 0,05 Sekunden. Sie variiert natürlich. Manchmal liegt sie näher bei 0,1 Sekunden. Ein anderes Mal erfolgt die Kompilierung so schnell wie 0,01 Sekunden (10 ms).

Um sich selbst davon zu überzeugen, können Sie sich eine Videodemo ansehen. Oder Sie können mit dem Online-Microthemer-Spielplatz herumspielen (siehe Anweisungen unten).

Online Microthemer-Spielplatz

Der Online-Spielplatz macht es einfach, selbst mit Microthemer zu experimentieren.

Anleitung

  1. Gehen Sie zum Online Microthemer-Spielplatz.
  2. Aktivieren Sie die Unterstützung für Sass über Allgemein → Einstellungen → CSS / SCSS → SCSS aktivieren.
  3. Gehen Sie zu Ansicht → Vollständiger Code-Editor → Ein, um globale Variablen, Mixins und Funktionen hinzuzufügen.
  4. Wechseln Sie zurück zur Hauptansicht der Benutzeroberfläche (Ansicht → Vollständiger Code-Editor → Aus).
  5. Erstellen Sie Selektoren über die Schaltfläche "Target".
  6. Fügen Sie Sass-Code über den Editor links von der Schriftartengruppe hinzu.
  7. Nach jeder Änderung können Sie sehen, welcher Code von Microthemer im Kompilierungsprozess enthalten war, indem Sie auf Ansicht → Generiertes CSS → Vorherige SCSS-Kompilierung klicken.
  8. Um zu sehen, wie dies in großem Maßstab funktioniert, können Sie Vanilla-CSS aus einem großen Stylesheet in Microthemer importieren über Pakete → Importieren → CSS-Stylesheet (der Import von Sass wird (noch) nicht unterstützt).

Wünschen Sie dies als npm-Paket?

Die selektive Kompilierungstechnik von Microthemer könnte auch als npm-Paket geliefert werden. Aber die Frage ist, sehen Sie dafür einen Bedarf? Könnte Ihre lokale Sass-Umgebung eine Geschwindigkeitssteigerung gebrauchen? Wenn ja, hinterlassen Sie bitte einen Kommentar unten.

Der Rest dieses Artikels richtet sich an diejenigen, die Werkzeuge für die Community entwickeln. Sowie an diejenigen, die neugierig sind, wie diese Herausforderung angegangen wurde.

Der Microthemer-Weg zur Sass-Kompilierung

Wir werden uns gleich mit einigen Codebeispielen beschäftigen. Betrachten wir jedoch zuerst die Hauptziele der Anwendung.

1. Kompilieren minimalen Codes

Wir möchten den einen bearbeiteten Selektor kompilieren, wenn er keine Beziehung zu anderen Selektoren hat, oder mehrere Selektoren mit verwandten Sass-Entitäten – aber nicht mehr als nötig.

2. Reaktiv auf Codeänderungen

Wir wollen jede gefühlte Wartezeit für die Sass-Kompilierung eliminieren. Wir wollen auch nicht zu viele Daten zwischen Benutzereingaben verarbeiten.

3. Gleiche CSS-Ausgabe

Wir möchten das gleiche CSS zurückgeben, das eine vollständige Kompilierung erzeugen würde, jedoch für einen Teilcode.

Sass-Beispiele

Der folgende Code dient als Referenzpunkt für diesen Artikel. Er deckt alle Szenarien ab, die unser selektiver Compiler handhaben muss. Wie globale Variablen, Mixin-Nebeneffekte und erweiterte Selektoren.

Variablen, Funktionen und Mixins

$primary-color: green;
$secondary-color: red;
$dark-color: black;

@function toRem($px, $rootSize: 16){
  @return #{$px / $rootSize}rem;
}

@mixin rounded(){
  border-radius: 999px;
  $secondary-color: blue !global;
}

Selektoren

.entry-title {
  color: $dark-color;
}

.btn {
  display: inline-block;
  padding: 1em;
  color: white;
  text-decoration: none;
}

.btn-success {
  @extend .btn;
  background-color: $primary-color;
  @include rounded;
}

.btn-error {
  @extend .btn;
  background-color: $secondary-color;
}

// Larger screens
@media (min-width: 960px) {
  .btn-success {
    border:4px solid darken($primary-color, 10%);
    &::before {
      content: "\2713"; // unicode tick
      margin-right: .5em;
    }
  }
}

Die Microthemer-Oberfläche

Microthemer hat zwei Hauptbearbeitungsansichten.

Ansicht 1: Vollständiger Code

Wir bearbeiten den vollständigen Code-Editor auf die gleiche Weise wie eine reguläre Sass-Datei. Dort kommen globale Variablen, Funktionen, Mixins und Imports hinein.

Ansicht 2: Visuell

Die visuelle Ansicht hat eine einfache Selektorarchitektur. Jeder CSS-Selektor ist ein separater UI-Selektor. Diese UI-Selektoren sind in Ordnern organisiert.

Da Microthemer einzelne Selektoren segmentiert, erfolgt die Analyse auf sehr granularer Ebene – ein Selektor nach dem anderen.

Hier ist eine kleine Quizfrage für Sie. Die Variable $secondary-color ist am oberen Rand der Ansicht "Vollständiger Code" auf red gesetzt. Warum ist der Fehlerknopf in den vorherigen Screenshots blau? Tipp: Es hat etwas mit Nebeneffekten von Mixins zu tun. Dazu gleich mehr.

Drittanbieter-Bibliotheken

Ein riesiges Dankeschön an die Autoren der folgenden JavaScript-Bibliotheken, die Microthemer verwendet

  • Gonzales PE – Konvertiert Sass-Code in ein Abstract Syntax Tree (AST) JavaScript-Objekt.
  • Sass.js – Konvertiert Sass in CSS-Code im Browser. Es verwendet Web Worker, um die Kompilierung in einem separaten Thread auszuführen.

Datenobjekte

Nun zu den kniffligen Details. Die Ermittlung einer geeigneten Datenstruktur erforderte einige Versuche und Irrtümer. Aber sobald sie richtig war, ergab sich die Anwendungslogik ganz natürlich. Wir beginnen also mit der Erklärung der wichtigsten Datenspeicher und schließen mit einer kurzen Zusammenfassung der Verarbeitungsschritte ab.

Microthemer verwendet vier Haupt-JavaScript-Objekte zur Speicherung von Anwendungsdaten.

  1. projectCode: Speichert den gesamten Projektcode, unterteilt in einzelne Elemente für einzelne Selektoren.
  2. projectEntities: Speichert alle Variablen, Funktionen, Mixins, extends und Imports, die im Projekt verwendet werden, sowie die Speicherorte, an denen diese Entitäten verwendet werden.
  3. connectedEntities: Speichert die Verbindungen, die ein Codeteil mit Sass-Entitäten des Projekts hat.
  4. compileResources: Speichert die selektiven Kompilierungsdaten nach einer Änderung der Code-Basis.

projectCode

Das Objekt projectCode ermöglicht es uns, Sass-Code-Teile schnell abzurufen. Diese Teile kombinieren wir dann zu einer einzigen Zeichenkette für die Kompilierung.

  • files: Mit Microthemer speichert dies den Code, der zur oben beschriebenen Ansicht "Vollständiger Code" hinzugefügt wird. Bei einer npm-Implementierung würde sich files auf tatsächliche .sass- oder .scss-Systemdateien beziehen.
  • folders: UI-Ordner von Microthemer, die segmentierte UI-Selektoren enthalten.
  • index: Die Reihenfolge eines Ordners oder eines Selektors innerhalb eines Ordners.
  • itemData: Der eigentliche Code für das Element, weiter erläutert im nächsten Code-Snippet.
var projectCode = {

  // Microthemer full code editor
  files: {
    full_code: {
      index: 0,
      itemData: itemData
    }
  },

  // Microthemer UI folders and selectors
  folders: {
    content_header: {
      index:100,
      selectors: {
        '.entry-title': {
          index:0,
            itemData: itemData
        },
      }
    },
    buttons: {
      index:200,
      selectors: {
        '.btn': {
          index:0,
          itemData: itemData
        },
        '.btn-success': {
          index:1,
          itemData: itemData
        },
        '.btn-error': {
          index:2,
          itemData: itemData
        }
      }
    }
  }
};

itemData für .btn-success Selektor

Das folgende Codebeispiel zeigt die itemData für den Selektor .btn-success.

  • sassCode: Wird verwendet, um die Kompilierungszeichenkette zu erstellen.
  • compiledCSS: Speichert kompiliertes CSS zum Schreiben in ein Stylesheet oder einen Style-Knoten im Dokumentkopf.
  • sassEntities: Sass-Entitäten für einen einzelnen Selektor oder eine Datei. Ermöglicht die Analyse vor und nach der Änderung und wird zum Aufbau des projectEntities-Objekts verwendet.
  • mediaQueries: Gleiche Daten wie oben, jedoch für einen Selektor, der innerhalb einer Media Query verwendet wird.
var itemData = {
  sassCode: ".btn-success { @extend .btn; background-color: $primary-color; @include rounded; }",
  compiledCSS: ".btn-success { background-color: green; border-radius: 999px; }",
  sassEntities: {
    extend: {
      '.btn': {
        values: ['.btn']
      }
    },
    variable: {
      primary_color: {
        values: [1]
      }
    },
    mixin: {
      rounded: {
        values: [1]
      }
    }
  },
  mediaQueries: {
    'min-width(960px)': {
      sassCode: ".btn-success { border:4px solid darken($primary-color, 10%); &::before { content: '\\2713'; margin-right: .5em; } }",
      compiledCSS: ".btn-success::before { content: '\\2713'; margin-right: .5em; }",
      sassEntities: {
        variable: {
          primary_color: {
            values: [1]
          }
        },
        function: {
          darken: {
            values: [1]
          }
        }
      }
    }
  }
};

projectEntities

Das Objekt projectEntities ermöglicht es uns zu prüfen, welche Selektoren bestimmte Sass-Entitäten verwenden.

  • variable, function, mixin, extend: Der Typ der Sass-Entität.
  • Z.B. primary_color: Der Name der Sass-Entität. Microthemer normalisiert Bindestrich-Namen, da Sass Bindestriche und Unterstriche austauschbar verwendet.
  • values: Ein Array von Deklarationswerten oder Instanzen. Instanzen werden durch die Zahl 1 repräsentiert. Der Gonzales PE Sass-Parser konvertiert numerische Deklarationswerte in Zeichenketten. Daher habe ich die Ganzzahl 1 zur Kennzeichnung von Instanzen gewählt.
  • itemDeps: Ein Array von Selektoren, die die Sass-Entität verwenden. Dies wird im nächsten Code-Snippet weiter erläutert.
  • relatedEntities: Unser Mixin rounded hat den Nebeneffekt, dass die globale Variable $secondary-color auf blue aktualisiert wird, daher der blaue Fehlerknopf. Dieser Nebeneffekt macht die Entitäten rounded und $secondary-color voneinander abhängig. Wenn also die Variable $secondary-color enthalten ist, sollte auch der Mixin rounded enthalten sein und umgekehrt.
var projectEntities = {
  variable: {
    primary_color: {
      values: ['green', 1],
      itemDeps: itemDeps
    },
    secondary_color: {
      values: ["red", "blue !global", 1],
      itemDeps: itemDeps,
      relatedEntities: {
        mixin: {
          rounded: {}
        }
      }
    },
    dark_color: {
      values: ["black", 1],
      itemDeps: itemDeps
    }
  },
  function: {
    darken: {
      values: [1]
    },
    toRem: {
      values: ["@function toRem($px, $rootSize: 16){↵   @return #{$px / $rootSize}rem;↵}", 1],
      itemDeps: itemDeps
    }
  },
  mixin: {
    rounded: {
      values: ["@mixin rounded(){↵   border-radius:999px;↵   $secondary-color: blue !global;↵}", 1],
      itemDeps: itemDeps,
      relatedEntities: {
        variable: {
          secondary_color: {
            values: ["blue !global"],
          }
        }
      }
    }
  },
  extend: {
    '.btn': {
      values: ['.btn', '.btn'],
        itemDeps: itemDeps
    }
  }
};

itemDeps für die Sass-Entität $primary-color

Das folgende Codebeispiel zeigt die itemDeps für die Variable $primary-color (primary_color). Die Variable $primary-color wird von zwei Formen des Selektors .btn-success verwendet, einschließlich eines Selektors innerhalb der Media Query min-width(960px).

  • path: Wird verwendet, um Selektordaten aus dem projectCode-Objekt abzurufen.
  • mediaQuery: Wird beim Aktualisieren von Style-Knoten oder beim Schreiben in ein CSS-Stylesheet verwendet.
var itemDeps = [
  {
    path: ["folders", 'header', 'selectors', '.btn-success'],
  },
  {
    path: ["folders", 'header', 'selectors', '.btn-success', 'mediaQueries', 'min-width(960px)'],
    mediaQuery: 'min-width(960px)'
  }
];

connectedEntities

Das Objekt connectedEntities ermöglicht es uns, verwandte Codeteile zu finden. Wir füllen es nach einer Änderung der Code-Basis. Wenn wir also die Deklaration font-size aus dem Selektor .btn entfernen würden, würde sich der Code von diesem ändern

.btn {
    display: inline-block;
    padding: 1em;
    color: white;
    text-decoration: none;
    font-size: toRem(21);
}

...zu diesem:

.btn {
    display: inline-block;
    padding: 1em;
    color: white;
    text-decoration: none;
}

Und wir würden die Analyse von Microthemer im folgenden connectedEntities-Objekt speichern.

  • changed: Die Analyse der Änderung, die die Entfernung der Funktion toRem erfasst.

    • actions: Ein Array von Benutzeraktionen.
    • form: Deklaration (z. B. $var: 18px) oder Instanz (z. B. font-size: $var).
    • value: Ein Textwert für eine Deklaration oder die Ganzzahl 1 für eine Instanz.
  • coDependent: Erweiterte Selektoren müssen immer zusammen mit dem erweiternden Selektor kompiliert werden, und umgekehrt. Die Beziehung ist co-dependent. Variablen, Funktionen und Mixins sind nur semi-dependent. Instanzen müssen mit Deklarationen kompiliert werden, aber Deklarationen müssen nicht mit Instanzen kompiliert werden. Microthemer behandelt sie jedoch der Einfachheit halber als co-dependent. In Zukunft wird eine Logik hinzugefügt, um unnötige Instanzen zu filtern, aber dies wurde für die erste Veröffentlichung weggelassen.
  • related: Der Mixin rounded steht in Beziehung zur Variable $secondary-color. Er aktualisiert diese Variable mit dem Flag global. Die beiden Entitäten sind co-dependent; sie sollten immer zusammen kompiliert werden. Aber in unserem Beispiel verwendet der Selektor .btn den Mixin rounded nicht. Daher ist die Eigenschaft related unten nicht mit etwas gefüllt.
var connectedEntities = {
  changed: {
    function: {
      toRem: {
        actions: [{
          action: "removed",
          form: "instance",
          value: 1
        }]
      }
    }
  },
  coDependent: {
    extend: {
      '.btn': {}
    }
  },
  related: {}
};

compileResources

Das Objekt compileResources ermöglicht es uns, einen Teilcode in der richtigen Reihenfolge zu kompilieren. Im vorherigen Abschnitt haben wir die Deklaration der Schriftgröße entfernt. Der folgende Code zeigt, wie das Objekt compileResources nach dieser Änderung aussehen würde.

  • compileParts: Ein Array von Ressourcen, die kompiliert werden sollen.

    • path: Wird verwendet, um die Eigenschaft compiledCSS des relevanten projectCode-Elements zu aktualisieren.
    • sassCode: Wird zum Aufbau der sassString für die Kompilierung verwendet. Wir fügen jedem Teil einen CSS-Kommentar hinzu (/*MTPART*/). Dieser Kommentar wird verwendet, um die kombinierte CSS-Ausgabe in das Array cssParts aufzuteilen.
  • sassString: Eine Zeichenkette von Sass-Code, die zu CSS kompiliert wird.
  • cssParts: CSS-Ausgabe in Form eines Arrays. Die Array-Schlüssel für cssParts stimmen mit dem Array compileParts überein.
var compileResources = {

  compileParts: [
    {
      path: ["files", "full_code"],
      sassCode: "/*MTFILE*/$primary-color: green; $secondary-color: red; $dark-color: black; @function toRem($px, $rootSize: 16){ @return #{$px / $rootSize}rem; } @mixin rounded(){ border-radius:999px; $secondary-color: blue !global;}/*MTPART*/"
    },
    {
      path: ["folders", "buttons", ".btn"],
      sassCode: ".btn { display: inline-block; padding: 1em; color: white; text-decoration: none; }/*MTPART*/"
    },
    {
      path: ["folders", "buttons", ".btn-success"],
      sassCode: ".btn-success { @extend .btn; background-color: $primary-color; @include rounded; }/*MTPART*/"
    },
    {
      path: ["folders", "buttons", ".btn-error"],
      sassCode: ".btn-error { @extend .btn; background-color: $secondary-color; }/*MTPART*/"
    }
  ],

  sassString: 
  "/*MTFILE*/$primary-color: green; $secondary-color: red; $dark-color: black; @function toRem($px, $rootSize: 16){ @return #{$px / $rootSize}rem; } @mixin rounded(){ border-radius:999px; $secondary-color: blue !global;}/*MTPART*/"+
  ".btn { display: inline-block; padding: 1em; color: white; text-decoration: none;}/*MTPART*/"+
  ".btn-success {@extend .btn; background-color: $primary-color; @include rounded;}/*MTPART*/"+
  ".btn-error {@extend .btn; background-color: $secondary-color;}/*MTPART*/",

  cssParts: [
    "/*MTFILE*//*MTPART*/",
    ".btn, .btn-success, .btn-error { display: inline-block; padding: 1em; color: white; text-decoration: none;}/*MTPART*/",
    ".btn-success { background-color: green; border-radius: 999px;}/*MTPART*/",
    ".btn-error { background-color: blue;}/*MTPART*/"
  ]
};

Warum wurden vier Ressourcen aufgenommen?

  1. full_code: Die Sass-Entität toRem wurde geändert und der full_code-Ressource enthält die Deklaration der Funktion toRem.
  2. .btn: Der Selektor wurde bearbeitet.
  3. .btn-success: Verwendet @extend .btn und muss daher immer mit .btn kompiliert werden. Der kombinierte Selektor wird zu .btn, .btn-success.
  4. .btn-error: Dies verwendet ebenfalls @extend .btn und muss daher aus den gleichen Gründen wie .btn-success aufgenommen werden.

Zwei Selektoren werden nicht aufgenommen, da sie nicht mit dem Selektor .btn verwandt sind.

  1. .entry-title
  2. .btn-success (innerhalb der Media Query)

Rekursive Ressourcensammlung

Abgesehen von der Datenstruktur war die zeitaufwendigste Herausforderung herauszufinden, wie der richtige Teil von Sass-Code bezogen werden kann. Wenn ein Codeteil mit einem anderen verbunden ist, müssen wir auch für den zweiten Teil Verbindungen prüfen. Es gibt eine Kettenreaktion. Um dies zu unterstützen, ist die folgende Funktion gatherCompileResources *rekursiv*.

  • Wir durchlaufen das Objekt connectedEntities bis auf die Ebene der Sass-Entitätsnamen.
  • Wir verwenden Rekursion, wenn eine Funktion oder ein Mixin Nebeneffekte hat (z. B. globale Variablen aktualisieren).
  • Die Funktion checkObject gibt den Wert eines Objekts auf einer bestimmten Ebene zurück oder false, wenn kein Wert vorhanden ist.
  • Die Funktion updateObject setzt den Wert eines Objekts auf einer bestimmten Ebene.
  • Wir fügen abhängige Ressourcen zum Array compileParts hinzu und verwenden absoluteIndex als Schlüssel.
  • Microthemer berechnet absoluteIndex, indem es den Ordnerindex zum Selektorindex addiert. Dies funktioniert, da Ordnerindizes in Hundertern inkrementieren und die maximale Anzahl von Selektoren pro Ordner 40 beträgt, was weniger als hundert ist.
  • Wir verwenden Rekursion, wenn Ressourcen, die dem Array compileParts hinzugefügt werden, ebenfalls co-abhängige Beziehungen haben.
function gatherCompileResources(compileResources, connectedEntities, projectEntities, projectCode, config){

  let compileParts = compileResources.compileParts;

  // reasons: changed / coDependent / related
  const reasons = Object.keys(connectedEntities);
  for (const reason of reasons) {

    // types: variable / function / mixin / extend
    const types = Object.keys(connectedEntities[reason]);
    for (const type of types) {

      // names: e.g. toRem / .btn / primary_color
      const names = Object.keys(connectedEntities[reason][type]);
      for (const name of names) {

        // check side-effects for Sass entity (if not checked already)
        if (!checkObject(config.relatedChecked, [type, name])){

          updateObject(config.relatedChecked, [type, name], 1);

          const relatedEntities = checkObject(projectEntities, [type, name, 'relatedEntities']);
          if (relatedEntities){
            compileParts = gatherCompileResources(
              compileResources, { related: relatedEntities }, projectEntities, projectCode, config
            );
          }
        }

        // check if there are dependent pieces of code
        const itemDeps = checkObject(projectEntities, [type, name, 'itemDeps']);
        if (itemDeps && itemDeps.length > 0){

          for (const dep of itemDeps) {

            let path = dep.path,
            resourceID = path.join('.');

            if (!config.resourceAdded[resourceID]){

              // if we have a valid resource
              let resource = checkObject(projectCode, path);
              if (resource){

                config.resourceAdded[resourceID] = 1;

                // get folder index + resource index
                let absoluteIndex = getAbsoluteIndex(path);

                // add compile part
                compileParts[absoluteIndex] = {
                  sassCode: resource.sassCode,
                  mediaQuery: resource.mediaQuery,
                  path: path
                };
                        
                // if resource is co-dependent, pull in others
                let coDependent = getCoDependent(resource);
                if (coDependent){
                  compileParts = gatherCompileResources(
                    compileResources, { coDependent: coDependent }, projectEntities, projectCode, config
                  );
                }
              }
            }
          }
        }
      }
    }
  }
  return compileParts;
}

Der Anwendungsprozess

Wir haben uns nun mit den technischen Aspekten befasst. Um zu sehen, wie alles zusammenhängt, gehen wir die Datenverarbeitungsschritte durch.

Von der Tastatureingabe zum Rendern des Stils

  1. Eine Benutzereingabe löst das Ereignis "Textarea-Änderung" aus.
  2. Wir wandeln den einzelnen bearbeiteten Selektor in ein sassEntities-Objekt um. Dies ermöglicht den Vergleich mit den Sass-Entitäten vor der Bearbeitung: projectCode > dataItem > sassEntities.
  3. Wenn sich Sass-Entitäten geändert haben

    • Wir aktualisieren projectCode > dataItem > sassEntities.
    • Wenn sich eine Regel @extend geändert hat
      • Wir durchsuchen das Objekt projectCode, um passende Selektoren zu finden.
      • Wir speichern den path für passende Selektoren auf dem aktuellen Datenelement: projectCode > dataItem > sassEntities > extend > target > [ path ].
    • Wir bauen das Objekt projectEntities neu auf, indem wir das Objekt projectCode durchlaufen.
    • Wir füllen connectedEntities > changed mit der Analyse der Änderung.
    • Wenn Entitäten extend, variable, function oder mixin vorhanden sind

      • Wir füllen connectedEntities > coDependent mit den relevanten Entitäten.
  4. Die rekursive Funktion gatherCompileResources verwendet das Objekt connectedEntities, um das Objekt compileResources zu füllen.
  5. Wir verketten das Array compileResources > compileParts zu einer einzigen Sass-Zeichenkette.
  6. Wir kompilieren die einzelne Sass-Zeichenkette zu CSS.
  7. Wir verwenden Kommentar-Trennzeichen, um die Ausgabe in ein Array zu teilen: compileResources > cssParts. Dieses Array stimmt mit dem Array compileResources > compileParts über übereinstimmende Array-Schlüssel überein.
  8. Wir verwenden Ressourcenpfade, um das Objekt projectCode mit kompiliertem CSS zu aktualisieren.
  9. Wir schreiben das CSS für einen bestimmten Ordner oder eine Datei in einen Style-Knoten im Dokumentkopf für die sofortige Stilwiedergabe. Auf der Serverseite schreiben wir alles CSS in ein einziges Stylesheet.

Überlegungen für npm

Für ein npm-Paket gibt es einige zusätzliche Überlegungen. In einer typischen NodeJS-Entwicklungsumgebung

  • Benutzer bearbeiten Selektoren als Teil einer größeren Datei, anstatt einzeln.
  • Sass-Imports werden wahrscheinlich eine größere Rolle spielen.

Code segmentierung

Die visuelle Ansicht von Microthemer segmentiert einzelne Selektoren. Dies macht das Parsen von Code in ein sassEntities-Objekt super schnell. Das Parsen ganzer Dateien kann eine andere Geschichte sein, besonders bei großen.

Vielleicht gibt es Techniken zur virtuellen Segmentierung von Systemdateien? Aber nehmen wir an, es gibt keinen Weg am Parsen ganzer Dateien vorbei. Oder es erscheint einfach sinnvoll für eine erste Veröffentlichung. Vielleicht reicht es aus, Endbenutzer anzuweisen, Sass-Dateien klein zu halten, um die besten Ergebnisse zu erzielen.

Sass-Importe

Zum Zeitpunkt der Erstellung analysiert Microthemer keine Importdateien. Stattdessen werden alle Sass-Importe einbezogen, sobald ein Selektor Sass-Entitäten verwendet. Dies ist eine vorläufige Lösung für die erste Veröffentlichung, die im Kontext von Microthemer in Ordnung ist. Aber ich glaube, eine npm-Implementierung sollte die Sass-Nutzung über alle Projektdateien hinweg verfolgen.

Unser projectCode-Objekt hat bereits eine files-Eigenschaft zum Speichern von Dateidaten. Ich schlage vor, Dateiverzeichnisse relativ zur Haupt-Sass-Datei zu berechnen. Zum Beispiel hätte die Datei, die in der ersten @import-Regel verwendet wird, index: 0, die nächste index: 1 und so weiter. Wir müssten @import-Regeln innerhalb von importierten Dateien scannen, um diese Indizes korrekt zu berechnen.

Wir müssten auch absoluteIndex anders berechnen. Im Gegensatz zu Microthemer-Ordnern können Systemdateien beliebig viele Selektoren haben. Das compileParts-Array muss möglicherweise ein Objekt sein, das ein Array von Teilen für jede Datei speichert. Auf diese Weise verfolgen wir nur die Selektorindizes innerhalb einer bestimmten Datei und verketten dann die compileParts-Arrays in der Reihenfolge der Dateien.

Fazit

Dieser Artikel stellt eine neue Methode zur selektiven Kompilierung von Sass-Code vor. Eine nahezu sofortige Sass-Kompilierung war für Microthemer notwendig, da es sich um einen Live-CSS-Editor handelt. Und das Wort "live" bringt bestimmte Geschwindigkeitserwartungen mit sich. Aber es kann auch für andere Umgebungen wünschenswert sein, wie z. B. Node.js. Dies liegt an den Node.js- und Sass-Benutzern zu entscheiden und hoffentlich ihre Gedanken unten in den Kommentaren zu teilen. Wenn die Nachfrage besteht, hoffe ich, dass ein npm-Entwickler die von mir geteilten Punkte nutzen kann.

Zögern Sie bitte nicht, Fragen dazu in meinem Forum zu stellen. Ich helfe Ihnen gerne weiter.