esbuild, Teil 2: JavaScript-Bundling praktisch umsetzen

Seite 2: Die passende esbuild-Konfiguration

Inhaltsverzeichnis

Damit esbuild solchen JavaScript-Code verarbeiten kann, ist eine entsprechende Konfiguration nötig. Dazu und zum Aufruf von esbuild selbst dient ein JavaScript-Wrapper. Da der Wrapper universell einsetzbar sein soll, ist er parametrisierbar zu entwickeln.

Als Mindestanforderung ist die Option --debug zu definieren. Ist sie gesetzt, erzeugt sie die development-Variable des JavaScript-Bundles. Ist sie nicht gesetzt, soll esbuild die production-Variable erzeugen. In der development-Variable sind Sourcemap und Kommentare enthalten, wogegen in der production-Variablen minimierter JavaScript-Code entstehen soll, der zu einer möglichst kleinen Ausgabedatei führt.

Weiterhin soll der esbuild-Wrapper den Namen der JavaScript-Quelldatei sowie der JavaScript-Zieldatei als Parameter akzeptieren.

Er hat zunächst nur wenige Abhängigkeiten:

import { basename, resolve } from "path";
import { argv } from "process";

import esbuild from "esbuild";
import sass from "sass";
...

Die ersten beiden import-Statements sind für das Handling von Pfaden und den Zugriff auf die Kommandozeilenargumente notwendig und bedienen sich der Node.js-Core-Funktionen. Lediglich der Import von esbuild und des Sass-CSS-Transpilers ist noch notwendig.

Los geht es mit den einfachen Dingen: Alles, was esbuild an integrierten Konfigurationsmöglichkeiten mitbringt, ist nach eigenen Vorstellungen zu konfigurieren.

{
  entryPoints: [ARGS[INPUT_ARG]],
  bundle: true,
  loader: {
    ".mjs": "jsx",
  },
  platform: "browser",
  target: "esnext",
  define: {
    "process.env.NODE_ENV": `"${ARGS.debug ? "development" : "production"}"`,
  },
  jsxFragment: "window.wp.element.Fragment",
  jsxFactory: "window.wp.element.createElement",
  outfile: ARGS[OUTPUT_ARG],
}

entryPoints ist der JavaScript-Quelldatei als Parameter zu übergeben. bundle: true teilt esbuild mit, dass es nicht nur transpilieren, sondern auch bundlen soll. Daher gelangen im JavaScript-Code importierte Libraries mit in die Ausgabedatei. Im transpilierten JavaScript-Code sollen nur diejenigen enthalten sein, die nicht schon durch die WordPress-Gutenberg-Umgebung in der globalen Browservariablen window.wp. bereitgestellt sind.

Standardmäßig akzeptiert esbuild die JSX-Syntax von React nur in .jsx-Dateien. Da die hier verwendeten Quelltexte einheitlich in .mjs-Dateien (Module JavaScript, ebenfalls aus der Node.js-Welt) vorliegen, erhält esbuild mittels loader: { ".mjs": "jsx", } die Information, dass .mjs-Dateien ebenfalls JSX-Syntax enthalten können.

Die Option platform teilt esbuild mit, für welche JavaScript-Zielumgebung der Quellcode zu transpilieren ist, in diesem Fall für den Browser. target beschreibt die JavaScript-Fähigkeiten der Zielplattform. Dieser Wert kann steuern, in welche JavaScript-Syntax (ES5, ES6, ES2020 usw.) der Quelltext zu transpilieren ist. Das ist im praktischen Einsatz von Bedeutung, da nicht alle Nutzer des Projektes den neuesten Browser verwenden dürften. So lässt sich ein Array an Werten übergeben, das die Menge der geeigneten Browser definiert. Der Wert esnext in der Konfiguration teilt esbuild mit, dass das Transpilat die neuesten Sprachfeatures verwenden darf.

Mittels define lassen sich dem Transpiler globale Variablen mitteilen, die dieser in den Quelltexten ersetzen darf. Das funktioniert ähnlich wie ein Präprozessor. Das konfigurierte define bewirkt in den JavaScript-Quelldateien abhängig von der Option --debug das Setzen von process.env.NODE_ENV entweder auf production oder development.

jsxFragment: "window.wp.element.Fragment" und jsxFactory: "window.wp.element.createElement" weisen esbuild an, die JSX-Statements im vorliegenden JavaScript nicht in React.createElement- und React.createFragment-Calls zu transpilieren, sondern in die Gutenberg-spezifischen Factory Method Calls window.wp.element.Fragment und window.wp.element.createElement.

Nun ist nur noch der SCSS-Import zu behandeln und esbuild mitzuteilen, dass die mit @wordpress/ geprefixten Pakete teilweise nicht mit in die JavaScript-Ausgabedatei gelangen sollen, sondern über globale Variablen zu beziehen sind. Dazu ist jeweils eine kleine esbuild-Erweiterung zu schreiben.

Auf GitHub findet sich die recht übersichtliche, aber nicht zu knappe esbuild-Plug-in-API samt Beispielen. Es ist zudem immer lohnend, sich den Quellcode bereits existierender esbuild-Plug-ins anzuschauen, um daraus zu lernen.

Ein esbuild-Plug-in wird als JavaScript-Objekt überreicht, das den Namen des Plug-ins und eine setup-Funktion beinhaltet. Üblicherweise wird für das Erzeugen und Parametrisieren eines esbuild-Plug-ins eine Factory-Methode bereitgestellt.

Ein Plug-in für esbuild hat die folgende Syntax:

...
export default function MyPlugin(options) {
  return {
    name : 'MyPlugin',
    setup(build) {
      ...
    }
  };
}

Der entscheidende Punkt ist die setup-Funktion. Darin lassen sich mit dem build-Parameter Callbacks registrieren, die esbuild auf mannigfaltige Weise unter die Arme greifen und seine Funktionalität erweitern.