Modernisierung Ihrer Frontend Build Pipeline: Von CDNs bis zur Bündelung (Deutsch (German))

Modernisierung Ihrer Frontend Build Pipeline: Von CDNs bis zur Bündelung

Tuesday, 11 November 2025

//

28 minute read

Einleitung

Im vergangenen Jahr bin ich gestolpert, experimentiert, kaputt gegangen und habe die Frontend-Build-Pipeline für diesen Blog schrittweise modernisiert. Dies war nicht irgendein Masterplan, der einwandfrei ausgeführt wurde – es war Versuch und Fehler, viel Fehler und hartnäckige Beharrlichkeit, getrieben von einer klaren Vision dessen, was ich erreichen wollte.

Ich bin kein Frontend-Guru oder ein Webpack-Assistent. Ich bin ein .NET-Entwickler, der mit den Einschränkungen der CDN-basierten Abhängigkeiten frustriert wurde und entschied, dass es einen besseren Weg geben musste. Dieser Artikel dokumentiert die chaotische, iterative Reise von einfachen <script> Tags, die auf unpkg auf eine moderne bündelnde Pipeline verweisen, die tatsächlich funktioniert.

Ich machte viele Fehler auf dem Weg (die ich dokumentieren), verbrachte Stunden Debugging kryptische Webpack-Fehler, und wahrscheinlich neu installiert node_modules Aber Beharrlichkeit zahlte sich aus, und ich lernte eine enorme Menge durch schiere Entschlossenheit, das richtig zu machen.

Wenn Sie ein .NET-Entwickler sind, der Webpack-Konfigurationsdateien anstarrt und sich fragt, in was auf der Erde Sie sich gebracht haben – dieser Artikel ist für Sie.

Ich bin ein alter Web-Performance-Nutter, in den frühen Tagen der Einwahl der ersten Website, die ich für Geld gebaut (das war nicht ein Porno-Backend in Perl ... eine Geschichte für ein anderes Mal) war eine Snow Condition Website, die aus einer automatisierten Telefonleitung gesponnen.

In jenen Tagen waren die Bedenken ganz anders. JavaScript war praktisch unbekannt. Ich denke, sogar divs waren RARE (in jenen Tagen IE war div und Netscape war layer) und Sie wären LUCKY, wenn Ihre Benutzer eine 56K-Verbindung hätten. So habe ich gelernt, zu optimieren (auch Dinge wie geflieste Bild-Hintergründe für Menüpunkte) etc...

Später bei Microsoft arbeitete ich sogar an einem automatisierten Image Sprite Tool in Web Forms - es war fantastisch: es extrahierte kleine Bilder von einer Seite, generierte CSS und ein Sprite Bild automatisch. Aber leider, wie meine Zeit bei Microsoft, es war nicht zu sein.

Kurz gesagt, es ist eine Besessenheit, die ich meine gesamte Karriere getragen habe. Niemand mag eine langsame Website!

Um fair zu seinCDNs sind nicht von Natur aus böse.

Vollständige Unterrichtung: Dieser Blog ist bewusst überengineered. Es tut nicht Notwendigkeit All diese Komplexität. Aber es so bauen lassen mich lernen moderne Frontend-Werkzeuge richtig und, entscheidend, gab mir etwas Reales zu schreiben und zu lehren. Manchmal der beste Weg zu lernen ist, etwas leicht lächerlich zu bauen und dokumentieren die Reise.

Das Problem mit CDNs

Zunächst war mein Ansatz einfach:

<!-- Old CDN-based approach -->
<script src="https://unpkg.com/[email protected]/dist/cdn.min.js"></script>
<script src="https://unpkg.com/[email protected]"></script>
<script src="https://cdn.jsdelivr.net/npm/easymde/dist/easymde.min.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/easymde/dist/easymde.min.css">

Während dies funktioniert, hat es mehrere Nachteile, die zunehmend frustrierend geworden:

  • Zuverlässigkeitsaspekte: CDNs gehen nach unten. Ich habe gesehen, unpkg und jsdelivr haben Ausfälle. Wenn sie unten sind, ist Ihre Website gebrochen
  • Unvorhersehbares Ladeverhalten: CDN Antworten können wild variieren. Manchmal schnell, manchmal langsam, manchmal führen sie Rennen Bedingungen, wo Bibliotheken in der falschen Reihenfolge laden
  • Zeitliche Aspekte: Sie haben keine Kontrolle darüber, wenn Skripte relativ zueinander laden. Dies verursachte mir endlose Kopfschmerzen mit Bibliothek Initialisierung
  • Mehrere HTTP-Anfragen: Jede Bibliothek benötigt eine separate Anfrage, auch mit HTTP/2
  • Kein Baumschütteln: Sie laden die gesamte Bibliothek herunter, auch wenn Sie nur einen Bruchteil davon verwenden
  • Versionsdrift: CDNs können verschiedene Versionen bedienen, es sei denn, Sie pin sie explizit, und sogar gepinnte Versionen können unterschiedliche Verhalten gegenüber CDN-Anbietern
  • Build-Time-Unsicherheit: Keine Möglichkeit, Unvereinbarkeiten bis zur Laufzeit zu erfassen – oft, wenn ein Benutzer es meldet
  • Begrenzte Optimierung: Kann nicht über Grenzen verkleinern oder toten Code entfernen
  • Offline-Entwicklung: Benötigt Internet-Konnektivität, was bei der Arbeit an Zügen oder Flugzeugen ärgerlich ist

Also wollte ich die Kontrolle über genau das, was meine Website verwendet und NOTWENDIG zu arbeiten. Ich wollte nur bündeln, was ich brauchte, sicherzustellen, zuverlässige Ladeauftrag, und Optimierung für die Leistung. Dies bedeutete, weg von CDNs und hin zu einem Bündelung Ansatz.

Sie sind einfacher einzurichten, und mit HTTP/2 und HTTP/3 Multiplexing ist der mehrfache Request Overhead viel weniger kritisch als früher. Für viele Projekte, besonders kleine oder Prototypen, bleiben CDNs eine vollkommen vernünftige Wahl. Aber für eine Produktionsstätte, an der ich Kontrolle, Zuverlässigkeit und Optimierung wollte, ergab die Bündelung mehr Sinn.

Der moderne Ansatz zur Bündelung

Mein aktuelles Setup bündelt alle JavaScript-Abhängigkeiten über Webpack und Prozesse CSS über PostCSS.

Warum Webpack statt Vite?

Bevor ich in die technischen Details eintauche, sollte ich mich an den Elefanten im Raum wenden: Warum benutze ich Webpack, wenn Vite, Rollup, Esbuild und andere moderne Bundles existieren?

Die ehrliche Antwort ist einfach: Ich kannte Webpack schon..

Ich hatte Webpack ausgiebig verwendet, wenn ich meinen "Beginning Web Development" Kurs unterrichtete, und es war das Werkzeug, das ich verstand. Als ich beschloss, die Baupipeline dieses Blogs zu modernisieren, stand ich bereits vor einer steilen Lernkurve – Verständnis von Baumschütteln, Code-Splitting, Modulsystemen, PostCSS Pipelines und wie man all dies mit ASP.NET Core integriert.

Es ist in der Regel eine gute Praxis bei der Arbeit an Projekten; begrenzen Sie das "neue" auf das "manageable".WAY ist leichter, Unsicherheit zu begrenzen.

Hinzu kommt, dass das "Lernen eines völlig neuen Build-Tools" unnötig erschien. Webpack funktioniert. Es ist ausgereift. Es verfügt über eine ausgezeichnete Dokumentation und ein massives Ökosystem. Am wichtigsten war, dass ich meine Lernenergie auf die Begriffsbestimmungen der modernen Bündelung statt der Eigenheiten eines bestimmten Werkzeugs.

Ist Vite schneller? Absolut. Vite's Entwicklungsserver mit nativen ES-Modulen und esbuild-powered Bündelung ist deutlich schneller als Webpack. Für große Projekte mit Hunderten von Modulen ist der Unterschied dramatisch.

Sollten Sie Vite für ein neues Projekt verwenden? Wahrscheinlich, ja. Wenn Sie beginnen, frisch und haben keine bestehenden Webpack-Wissen, Vite ist wahrscheinlich die bessere Wahl. Es ist schneller, einfacher zu konfigurieren, und stellt die moderne Herangehensweise an Frontend-Tooling.

Bedauere ich die Verwendung von Webpack? Es brachte mich dahin, wo ich sein musste. Die Lektionen, die ich über Bündelung, Code-Splitting und Optimierung gelernt habe, sind auf jedes Build-Tool übertragbar. Und ehrlich gesagt, für die Skala dieses Blogs ist der Leistungsunterschied zwischen Webpack und Vite vernachlässigbar – wir reden über Millisekunden in Entwicklungs-Wiederaufbauzeiten.

Es ist ein Schlüsselaspekt, wie ich Sachen baue; beginne mit dem, was EASY ist und baue von diesem Fundament aus.

Die umfassendere Lektion hier ist, dass Fortschritt schlägt PerfektionIch hätte Wochen damit verbringen können, den "besten" Bundleer zu erforschen, Benchmarks zu vergleichen, Vergleichsartikel zu lesen und die Entscheidung zu quälen. Stattdessen wählte ich das Werkzeug, das ich kannte, brachte es zum Laufen und bewegte mich vorwärts.

Vielleicht könnte ich eines Tages nach Vite migrieren. Vielleicht werde ich das nicht. So oder so, dieser Blog hat eine moderne, optimierte Baupipeline, die zuverlässig funktioniert – und das ist es, was zählt.

Paketstruktur

Das package.json Nun gibt es zwei unterschiedliche Abhängigkeitsgruppen. Diese Struktur hat mich mehrere Versuche gekostet, Recht zu bekommen – ursprünglich sind Nuget und npm KINDA ähnlich, aber nppm ist viel weniger freundlich, wenn man Pacakges hinzufügt.

{
  "dependencies": {
    //NOTE - This is a pre-release of these enhancements, the 1.0.0 release is OUT NOW!
    "@mostlylucid/mermaid-enhancements": "^1.0.0-alpha0",
    "alpinejs": "^3.14.1",
    "codemirror": "5.65.13",
    "core-js": "^3.39.0",
    "easymde": "2.20.0",
    "flatpickr": "^4.6.13",
    "highlight.js": "^11.10.0",
    "highlightjs-cshtml-razor": "^2.1.1",
    "html-to-image": "^1.11.13",
    "htmx.org": "^2.0.1",
    "mermaid": "^11.0.2",
    "regenerator-runtime": "^0.14.1",
    "svg-pan-zoom": "^3.6.2"
  },
  //These are just used for build; not needed to RUN the app so we have a separate place for 'em
  "devDependencies": {
    "@babel/core": "7.26.9",
    "@babel/preset-env": "7.26.9",
    "@tailwindcss/aspect-ratio": "^0.4.2",
    "@tailwindcss/forms": "^0.5.7",
    "@tailwindcss/typography": "^0.5.12",
    "autoprefixer": "10.4.21",
    "babel-loader": "10.0.0",
    "cpx": "1.5.0",
    "css-loader": "7.1.2",
    "cssnano": "7.0.6",
    "daisyui": "^4.12.10",
    "mini-css-extract-plugin": "^2.9.4",
    "npm-run-all": "4.1.5",
    "postcss": "8.5.3",
    "postcss-cli": "11.0.1",
    "postcss-import": "^16.1.0",
    "rimraf": "6.0.1",
    "style-loader": "4.0.0",
    "tailwindcss": "3.4.17",
    "terser-webpack-plugin": "^5.3.10",
    "webpack": "^5.91.0",
    "webpack-cli": "^5.1.4"
  }
}

Abhängigkeiten sind Bibliotheken, die zur Laufzeit benötigt werden (Alpine.js, HTMX, EasyMDE usw.), während devDependencies sind Build-Tools (Webpack, Babel, PostCSS-Prozessoren, etc.).

Skripte erstellen

Das package.json Skripte haben sich deutlich entwickelt:

{
  "scripts": {
    "clean": "rimraf ./.tmp ./wwwroot/css/dist ./wwwroot/js/dist",
    "copy:static": "cpx \"src/css/{raleway,easymde-overrides}.css\" \"wwwroot/css/dist\"",
    "copy:highlight": "cpx \"src/css/highlight/*.min.css\" \"wwwroot/css/highlight\"",
    "copy:all": "npm-run-all --parallel copy:static copy:highlight",
    "copy:watch": "cpx \"src/css/{raleway,easymde-overrides}.css\" \"wwwroot/css/dist\" --watch & cpx \"src/css/highlight/*.min.css\" \"wwwroot/css/highlight\" --watch",
    "tw:dev": "postcss ./src/css/main.css -o ./wwwroot/css/dist/main.css",
    "tw:prod": "postcss ./src/css/main.css -o ./wwwroot/css/dist/main.css --no-map --verbose",
    "tw:watch": "postcss ./src/css/main.css -o ./wwwroot/css/dist/main.css --watch",
    "js:dev": "webpack --env development",
    "js:prod": "webpack --mode production",
    "js:watch": "webpack watch --mode development",
    "dev": "npm-run-all clean --parallel copy:all tw:dev js:dev",
    "watch": "npm-run-all clean copy:all --parallel copy:watch tw:watch js:watch",
    "build": "npm-run-all clean copy:all --parallel tw:prod js:prod"
  }
}

Diese Einrichtung unterscheidet sich von folgenden Aspekten:

  • sauber: Entfernt frühere Build-Ausgaben mit rimraf
  • Kopie:* Aufgaben: Kopiert statische CSS-Dateien, die keine Verarbeitung benötigen (dies sind entweder 'Importe') für einen speziellen Zweck wie das Laden meiner funky Raleway-Schrift oder adaptive, wie Themen-Schaltungs-Erweiterungen.
  • tw:* Aufgaben: Prozesse Tailwind CSS über PostCSS
  • (Js:) Nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein, nein.* Aufgaben: Bundles JavaScript über Webpack
  • dev/watch/build: Orchestriert die gesamte Pipeline

Das npm-run-all Paket ermöglicht parallele Ausführung für schnellere Builds.

Sie können einstellen npm run build Auto laufen während Ihres lokalen Aufbaus, aber es ist ein bisschen ein Schmerz für CI (Sie müssen sicherstellen, dass es deaktiviert ist usw.).

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net8.0</TargetFramework>
    <SpaRoot>ClientApp\</SpaRoot>
  </PropertyGroup>

  <!-- Run npm install only when package.json changes -->
  <Target Name="NpmInstall" Inputs="$(SpaRoot)package.json" Outputs="$(SpaRoot)node_modules" BeforeTargets="Build">
    <Message Importance="high" Text="Running npm install in $(SpaRoot)" />
    <Exec WorkingDirectory="$(SpaRoot)" Command="npm ci" />
  </Target>

  <!-- Run npm build before the .NET build -->
  <Target Name="NpmBuild" DependsOnTargets="NpmInstall" BeforeTargets="Build">
    <Message Importance="high" Text="Running npm run build in $(SpaRoot)" />
    <Exec WorkingDirectory="$(SpaRoot)" Command="npm run build" />
  </Target>

</Project>

Um es einfach zu tun, fügen Sie dies zu Ihrem csproj...Sie können sogar sagen, nur während Debug ' usw. laufen... aber ich finde es chaotisch. Ich bevorzuge es einfach manuell laufen. So npm run watch tut genau das, wenn jede css / js-Datei ändert es automatisch läuft das build.

HINWEIS: In der JS-Welt benutzen sie fast Hot-Reload-Dev-Runs, was ziemlich glatt ist und Sie irgendwie ASP.NET Cores schwachen Versuch hassen lässt.

Webpack-Konfiguration Deep Dive

Die Webpack-Konfiguration ist, wo die Magie geschieht – und wo ich verbrachte die meiste Zeit der Fehlersuche. Dies ist nicht in die Existenz vollständig gebildet. Es ist das Ergebnis von unzähligen Iterationen, Stack Overflow Suchen und Lesen durch Webpack-Dokumentation um 2 Uhr morgens versucht, zu verstehen, warum mein Build erzeugt 47 Stück Dateien.

Hier ist die aktuelle Konfiguration (aus webpack.config.js) mit ausführlichen Erklärungen zu dem, was jeder Teil tut und warum es dort ist:

const TerserPlugin = require('terser-webpack-plugin');
const path = require('path');

module.exports = (env, argv) => {
    const isProduction = argv.mode === 'production';

    return {
        mode: isProduction ? 'production' : 'development',
        entry: {
            main: './src/js/main.js',
        },
        output: {
            filename: '[name].js',
            chunkFilename: '[name].[contenthash].js',
            path: path.resolve(__dirname, 'wwwroot/js/dist'),
            publicPath: '/js/dist/',
            module: true,
            clean: true,
        },
        experiments: {
            outputModule: true,
        },
        module: {
            rules: [
                {
                    test: /\.css$/i,
                    use: ['style-loader', 'css-loader'],
                },
                {
                    test: /\.js$/,
                    exclude: /node_modules/,
                    use: {
                        loader: 'babel-loader',
                        options: {
                            presets: [
                                ['@babel/preset-env', {
                                    targets: '> 0.25%, not dead',
                                    modules: false,
                                    useBuiltIns: 'usage',
                                    corejs: 3,
                                }],
                            ],
                        },
                    },
                },
            ],
        },
        resolve: {
            extensions: ['.js', '.mjs'],
            alias: {
                '@mostlylucid/mermaid-enhancements$': path.resolve(__dirname, 'node_modules/@mostlylucid/mermaid-enhancements/dist/index.min.js')
            }
        },
        optimization: {
            splitChunks: {
                chunks: 'all',
                minSize: 20000,
                maxSize: 100000,
                name: false,
            },
            runtimeChunk: {
                name: 'runtime',
            },
            minimize: isProduction,
            minimizer: isProduction ? [
                new TerserPlugin({
                    terserOptions: {
                        ecma: 2020,
                        compress: {
                            drop_console: true,
                            passes: 3,
                            toplevel: true,
                            pure_funcs: ['console.info', 'console.debug'],
                        },
                        mangle: {
                            toplevel: true,
                        },
                        format: {
                            comments: false,
                        },
                    },
                    extractComments: true,
                }),
            ] : [],
        },
        devtool: isProduction ? false : 'eval-source-map',
        performance: {
            hints: isProduction ? 'warning' : false,
        }
    };
};

Schlüsselkonfigurationsabschnitte erklärt

Code-Splitting und Chunking

optimization: {
    splitChunks: {
        chunks: 'all',
        minSize: 20000,
        maxSize: 100000,
        name: false,
    },
    runtimeChunk: {
        name: 'runtime',
    },
}

Diese Konfiguration teilt Ihren Code automatisch in kleinere Stücke auf:

  • Stücke: 'Alle': analysiert sowohl synchrone als auch asynchrone Importe
  • minGröße: 20000: Erzeugt nur Stücke für Module größer als 20KB
  • maxGröße: 100000: Versuche, Stücke größer als 100KB zu teilen
  • LaufzeitChunk: Extrahiert Webpack Laufzeit in eine separate Datei, um langfristiges Caching zu verbessern

In der Praxis erzeugt dies mehrere Dateien in wwwroot/js/dist/:

  • main.js - Ihre Bewerbungs-Eingangsstelle
  • runtime.js - Webpack Modul Ladelogik
  • [vendor].[contenthash].js - Automatisch geteilte Verkaufsstücke
  • Zusätzliche Stücke für Code-Split-Routen oder faul geladene Module

Babel-Übertragung

{
    test: /\.js$/,
    exclude: /node_modules/,
    use: {
        loader: 'babel-loader',
        options: {
            presets: [
                ['@babel/preset-env', {
                    targets: '> 0.25%, not dead',
                    modules: false,
                    useBuiltIns: 'usage',
                    corejs: 3,
                }],
            ],
        },
    },
}

Babel transpiliert modernes JavaScript zur Unterstützung älterer Browser:

  • Ziele: Unterstützt Browser mit > 0,25% Marktanteil, die noch beibehalten werden
  • Module: false: Bewahrt ES6-Module für Webpack-Baum-Shaking
  • useBuildIns: 'Nutzung': Enthält Polyfills automatisch nur für Funktionen, die Sie verwenden
  • corejs: 3: Verwendet core-js Version 3 für Polyfills

Das bedeutet, ich kann modernes JavaScript schreiben (async/await, optionale Verkettung, nullish coalescing) und dabei einen breiten Browser unterstützen.

Produktionsminimierung

minimizer: isProduction ? [
    new TerserPlugin({
        terserOptions: {
            ecma: 2020,
            compress: {
                drop_console: true,
                passes: 3,
                toplevel: true,
                pure_funcs: ['console.info', 'console.debug'],
            },
            mangle: {
                toplevel: true,
            },
            format: {
                comments: false,
            },
        },
        extractComments: true,
    }),
] : []

Terser vermint die Produktion aggressiv:

  • drop_console: Entfernt alle console.log() Erklärungen
  • Durchläufe: 3: Funktioniert die Komprimierung dreimal für maximale Größenreduktion
  • Top-Level: wahr: Mangles Top-Level-Variablennamen
  • pure_funcs: Entfernt bestimmte Konsolen-Methoden, auch wenn Variablen zugewiesen werden
  • Anmerkungen: falsch: Strips alle Kommentare von der Ausgabe

Auf diesem Blog reduziert dies typischerweise die Größe von JavaScript-Bundles um 60-70% im Vergleich zu unminifiziertem Code.

PostCSS Pipeline für Rückenwind

Tailwind CSS wird über PostCSS mit mehreren Plugins verarbeitet. postcss.config.js) ist erbarmungsvoll einfach im Vergleich zu Webpack:

// postcss.config.js
module.exports = {
    plugins: {
        'postcss-import': {},
        tailwindcss: {},
        autoprefixer: {},
        cssnano: { preset: 'default' }
    }
}

Die Pipeline funktioniert wie folgt:

  1. Postcss-Einfuhr: Lösen @import Aussagen in CSS-Dateien
  2. Rückenwinde: Prozesse Tailwind Direktiven und erzeugt Utility-Klassen
  3. Autoprefixer: Fügt Herstellerpräfixe für Browser-Kompatibilität hinzu
  4. Cssnano: Minimiert die endgültige CSS-Ausgabe

Einrichtung des Rückenwindes

Das tailwind.config.js Datei gibt an, wo Tailwind nach Klassennamen suchen soll. content Pfade rechts war kritisch – Fehlt ein Pfad und Tailwind generiert keine Klassen für diese Dateien:

module.exports = {
    content: ["./Views/**/*.cshtml", "./EmailSubscription/**/*.cshtml"],
    safelist: ["dark", "light"],
    darkMode: "class",
    theme: {
        fontFamily: {
            body: ["Raleway", "sans-serif"],
        },
        extend: {
            colors: {
                "custom-light-bg": "#ffffff",
                "custom-dark-bg": "#1d232a",
                primary: "#072344",
                secondary: "#00aaa1",
                // ... more custom colours
            },
        },
    },
    plugins: [
        require("@tailwindcss/aspect-ratio"),
        require("@tailwindcss/typography"),
        require("daisyui"),
    ],
};

Hauptmerkmale:

  • Inhalt: Scans .cshtml Dateien für Klassennamen (einschließlich Razor-Ansichten und E-Mail-Vorlagen)
  • Sicherheitsliste: Enthält immer diese Klassen, auch wenn sie nicht in Scans gefunden werden
  • darkMode: "Klasse": Ermöglicht klassenbasiertes Dunkelmodus-Schalten
  • theme.extend: Fügt benutzerdefinierte Farben und Abstandswerte hinzu
  • Komponenten: Enthält Tailwind-Plugins und DaisyUI-Komponentenbibliothek

Tailwind scannt diese Dateien zur Build-Zeit, extrahiert nur die Utility-Klassen, die Sie tatsächlich verwenden. Deshalb ist die endgültige CSS-Datei viel kleiner als die vollständige Tailwind-Bibliothek.

JavaScript-Eingangspunkt

Das main.js Datei ist, wo alles zusammenkommt. Diese Datei importiert und initialisiert alle Abhängigkeiten, und es ist organisch gewachsen, wie ich Funktionen hinzugefügt habe, um den Blog:

// src/js/main.js
import hljsRazor from "highlightjs-cshtml-razor";
import mermaid from "mermaid";
import Alpine from 'alpinejs';
import htmx from "htmx.org";
import hljs from "highlight.js";
import EasyMDE from "easymde";
import 'easymde/dist/easymde.min.css';
import flatpickr from "flatpickr";
import 'flatpickr/dist/flatpickr.min.css';

// Expose libraries globally for Razor views
window.Alpine = Alpine;
window.hljs = hljs;
window.htmx = htmx;
window.mermaid = mermaid;
window.EasyMDE = EasyMDE;
window.flatpickr = flatpickr;

// Import custom modules
import { typeahead } from "./typeahead";
import { submitTranslation, viewTranslation } from "./translations";
import { codeeditor } from "./simplemde_editor";
import { globalSetup } from "./global";
import { comments } from "./comments";

// Attach to namespace
window.mostlylucid = window.mostlylucid || {};
window.mostlylucid.typeahead = typeahead;
window.mostlylucid.comments = comments();
window.mostlylucid.translations = {
    submitTranslation: submitTranslation,
    viewTranslation: viewTranslation
};

// Initialise Alpine
Alpine.start();

Dieser Ansatz bietet mehrere Vorteile:

  1. Einmalige Einfuhrstelle: Alle Abhängigkeiten, die durch eine Eingabedatei geladen werden
  2. Explizite Versionen: Package.json sperrt bestimmte Versionen
  3. Baumschütteln: Webpack entfernt ungenutzten Code aus Bibliotheken
  4. Gesamtexposition: Bibliotheken für Razor-Ansichten über window
  5. Benutzerdefinierte Module: Anwendungsspezifischer Code, organisiert in importierbare Module

Integration von Layout-Vorlagen

Im _Layout.cshtml, ich referenziere jetzt nur gebündelte Vermögenswerte:

<head>
    <link href="/css/dist/main.css" asp-append-version="true" rel="stylesheet" />
</head>
<body>
    <!-- Content -->
    <script src="~/js/dist/main.js" type="module" asp-append-version="true"></script>
</body>

Anmerkung I module den Browser wissen zu lassen, welche Art von JS-Datei dies ist & asp-append-version ein ordentliches kleines ASP.NET Tag Helper, das eine Hash-Version des Dateiinhalts an den Querystring anhängt; effektiv Cache-Busting, wenn sich die Datei ändert.

Die einzigen verbleibenden CDN-Abhängigkeiten sind:

  • Google-Anmeldung: Erfordert externe CDN für OAuth-Funktionalität
  • Boxicons: Icon-Schrift (könnte gebündelt werden, aber minimaler Nutzen)
  • Umami Analytics: Third-Party-Analytics-Skript - selbst gehostet, so dass ich Ihren Besuch nicht Google sehen ??

Pipeline-Visualisierung erstellen

Hier ist, wie der gesamte Build-Prozess von Quelldateien zu Produktionsanlagen fließt:

graph TD
    A[Source Files] --> B[npm run build]
    B --> C[Clean Task]
    C --> D[rimraf wwwroot/css/dist wwwroot/js/dist]

    B --> E[Copy Task]
    E --> F[cpx: Copy static CSS files]
    F --> G[wwwroot/css/dist/raleway.css]
    F --> H[wwwroot/css/dist/easymde-overrides.css]

    B --> I[Tailwind Task]
    I --> J[PostCSS Pipeline]
    J --> K[postcss-import]
    K --> L[Tailwind CSS]
    L --> M[Autoprefixer]
    M --> N[cssnano]
    N --> O[wwwroot/css/dist/main.css]

    B --> P[Webpack Task]
    P --> Q[Entry: src/js/main.js]
    Q --> R[Babel Transpilation]
    R --> S[Module Resolution]
    S --> T[Tree Shaking]
    T --> U[Code Splitting]
    U --> V[Terser Minification]
    V --> W[wwwroot/js/dist/main.js]
    V --> X[wwwroot/js/dist/runtime.js]
    V --> Y[wwwroot/js/dist/vendor chunks]

    O --> Z[ASP.NET Core Static Files]
    W --> Z
    X --> Z
    Y --> Z
    G --> Z
    H --> Z

    Z --> AA[Browser]

    style A stroke:#0ea5e9,stroke-width:3px
    style Z stroke:#f59e0b,stroke-width:3px
    style AA stroke:#10b981,stroke-width:3px

Es sieht ziemlich komplex, aber wirklich das ist, was WebPack tut, es behandelt das meiste von diesem selbst (in einer komplizierten Weise, die die Likes von Vite nicht brauchen, aber.. )

Leistungsverbesserungen

Wieder, nicht wirklich der POINT. Aber es lädt jetzt wie eine absolute Kugel. Sie können immer noch Probleme mit Cloudflares 'Rocket Loader' (die Seite lädt Ereignisse etwas) aber es ist TIGHT.

Vorteile für Entwickler

Über die Leistung hinaus verbessert die moderne Build-Pipeline den Entwicklungsprozess erheblich:

Typ Sicherheit und IntelliSense

Die Bündelung durch Webpack ermöglicht eine korrekte JavaScript-Modulauflösung, d.h.:

  • IntelliSense im VS-Code: Automatische Vervollständigung für importierte Module
  • Zur Definition springen: Direkt zum Quellcode der Bibliothek navigieren
  • Inline-Dokumentation: JSDoc-Kommentare aus Bibliotheken erscheinen in IDE

ALS der Bau bricht (mit npm run watch) Ich kenne INSTANTLY, kombiniert mit den (wenigen, aber wachsenden) JS-Tests bedeutet es engere Rückkopplungsschleifen.

Hot-Modul-Ersatz

Während der Entwicklung, npm run watch ermöglicht nahezu sofortiges Feedback:

npm run watch

Dies läuft Webpack im Uhrenmodus, nur geänderte Module wiederhergestellt:

  • Typische Umbauzeit: 200-400ms
  • Browser automatisch aktualisieren: Verwendung von Browser-sync oder ähnlichen Werkzeugen
  • Vorbehaltener Anwendungszustand: HMR hält den Anwendungszustand bei Aktualisierungen aufrecht (sofern unterstützt)

Abhängigkeitsmanagement

Die Sperrdatei von npm (package-lock.json) gewährleistet konsistente Builds über Umgebungen hinweg:

npm ci  # Clean install from lock file

Dies garantiert die gleichen Abhängigkeitsversionen in Entwicklungs-, CI/CD- und Produktionsumgebungen.

Diese werden als 'Spinning' in der JS Welt bezeichnet, wo die Abhängigkeiten in Stein gesetzt sind. Sie können auf ein Paketschloss verzichten, aber dann sind Sie abhängig von pacakge Autoren; oft HUNDREDS von unterschiedlichen tpeoples nicht versauen einige kleinere Freigabe..s

Skripte erstellen

Die organisierten npm-Skripte erleichtern gemeinsame Aufgaben:

npm run dev      # One-time development build
npm run watch    # Continuous development builds
npm run build    # Production build with optimisations
npm run clean    # Remove build artefacts

Gemeinsame Muster und Lösungen

Aufdecken von gebündelten Bibliotheken weltweit

Einige Bibliotheken benötigen eine globale Belichtung für die Verwendung in Razor-Ansichten oder Inline-Skripten:

// Make library available globally
import Alpine from 'alpinejs';
window.Alpine = Alpine;
Alpine.start();

Dann in .cshtml Dateien:

<div x-data="{ open: false }">
    <!-- Alpine.js works because it's on window -->
</div>

Handhabung von CSS aus JavaScript-Modulen

Einige Bibliotheken enthalten CSS, die importiert werden müssen:

import EasyMDE from "easymde";
import 'easymde/dist/easymde.min.css';  // Imported CSS is processed by Webpack

Webpacks css-loader und style-loader Diese Einfuhren werden wie folgt behandelt:

  • CSS wird während des Erstellens extrahiert
  • Injiziert in die Seite über <style> Tags oder separate CSS-Dateien
  • Automatisch vorfixiert und miniert

Faules Laden schwerer Bibliotheken

Für Bibliotheken, die nur auf bestimmten Seiten benötigt werden, verwenden Sie dynamische Importe:

// Only load Mermaid when needed
async function initMermaid() {
    const mermaid = await import('mermaid');
    mermaid.default.initialize({ startOnLoad: true });
}

// Call when needed
if (document.querySelector('.mermaid')) {
    initMermaid();
}

Webpack automatisch Code-Splits dynamische Importe in separate Stücke, geladen on-demand.

Umgang mit CommonJS-Modulen

Einige ältere Bibliotheken verwenden CommonJS anstelle von ES6-Modulen:

// CommonJS require syntax
const hljs = require('highlight.js');

// Or use dynamic import
import('highlight.js').then(hljs => {
    // Use hljs
});

Webpack verarbeitet beide Modulsysteme transparent und konvertiert CommonJS in ES6, wo nötig.

Fehlerbehebung bei gemeinsamen Problemen

Quellenkarten fehlen in der Entwicklung

Sicherstellen devtool ist konfiguriert in webpack.config.js:

devtool: isProduction ? false : 'eval-source-map',

Dies erzeugt Quellkarten in der Entwicklung für ein einfacheres Debuggen.

CSS-Klasse Purging Probleme mit Endwind

Wenn Tailwind Klassen entfernt, die Sie verwenden, überprüfen Sie die content Konfiguration:

// tailwind.config.js
module.exports = {
    content: [
        './Views/**/*.cshtml',
        './Components/**/*.cshtml',
        // Add any other paths where classes are used
    ],
}

Alternativ können Sie die safelist für dynamische Klassen:

safelist: [
    'bg-blue-500',
    'text-red-600',
    {
        pattern: /bg-(red|green|blue)-(400|500|600)/,
    }
]

Nicht gefundene Fehler des Moduls

Wenn Webpack ein Modul nicht auflösen kann, überprüfen Sie:

  1. korrekter Importpfad: Relative Pfade beginnen mit ./ oder ../
  2. Dateierweiterungen: Hinzufügen .mjs zu resolve.extensions bei Bedarf
  3. Alias-Erdäpfel: Verwendung resolve.alias für komplexe Wege
resolve: {
    extensions: ['.js', '.mjs', '.json'],
    alias: {
        '@components': path.resolve(__dirname, 'src/js/components/'),
    }
}

Leistungsfragen erstellen

Wenn Builds langsam werden:

  1. Caching aktivieren: Webpack-Cache beschleunigt Umbauten deutlich
  2. Geltungsbereich von Babel reduzieren: Weitere Verzeichnisse von Babel-Verarbeitung ausschließen
  3. Upgrade-Tools: Neuere Versionen von Webpack und Babel sind schneller
  4. Parallele Builds: Verwendung thread-loader für Multi-Threaded Transpilation
// Enable Webpack caching
cache: {
    type: 'filesystem',
},

Lektionen lernten den harten Weg

Lassen Sie mich einige der Fehler teilen, die ich auf dieser Reise gemacht habe, damit Sie sie vermeiden können:

Fehler Nr. 1: Versuchen, alles auf einmal zu bündeln

Mein erster Versuch bestand darin, alle CDN-Referenzen auf einmal herauszureißen und alles durch Webpack zu bündeln. Der Bau brach spektakulär. Ich erfuhr, dass inkrementelle Migration dein Freund ist – eine Bibliothek nach der anderen zu verschieben, gründlich zu testen und dann zur nächsten zu wechseln.

Fehler #2: Modultypen nicht verstehen

Ich verschwendete Stunden, um herauszufinden, warum bestimmte Bibliotheken nicht laden würden.require()) und ES6 Module (import) ohne zu verstehen, wie Webpack mit ihnen umgeht, führt zu kryptischen Fehlern. experiments: { outputModule: true } Konfiguration war nicht in meinem ursprünglichen Setup, und ich konnte nicht herausfinden, warum meine Module nicht geladen wurden.

Die Lösung kam aus dem Lesen von GitHub-Problemen im Webpack-Repository um Mitternacht.

Fehler #3: Überaggressive Code-Splitting

An einem Punkt, meine Konfiguration war die Erzeugung von Stücken für alles. minSize: 10000 (10KB) was bedeutete, Webpack erstellte separate Dateien für winzige Utility-Funktionen. Die überkorrigierte und ging zu 2MB... Page load wurde zu einem Wasserfall von 50+ winzigen Brocken Anfragen oder hing warten auf riesige Brocken zum Download. Ich habe gelernt, dass Code-Splitting gut ist, aber Sie brauchen vernünftige Schwellen. Deshalb meine aktuelle config verwendet minSize: 20000 ) Einzelnachweise

Fehler #4: Vergessen über die .mjs Erweiterung

Einige npm-Pakete verteilen ES6-Module mit dem .mjs Erweiterung. Webpack wird diese nicht standardmäßig lösen. Ich verbrachte eine peinlich lange Zeit Debugging warum @mostlring I needed to add .mjs .mjs .mjs .mjs .mjs .mjs .mjs .mjs .mjs .mjs .mjs .mjs .mjs .mjs .mjs .mjs .mjs .mjs .mjs .mjs .mjs .mjs .mjs .mjs .mjs .mjs .mjs .mjs .mjs .mjs .mjs .mjs .mjs .mjstosolve.extensions`.

Einfach zu reparieren, wenn du es weißt, aber das rauszufinden?

Fehler #5: Webpack baut sich nicht auf

Frühe Builds dauerten 30-60 Sekunden, weil ich den Dateisystem-Cache von Webpack nicht aktiviert hatte. Sobald ich Caching hinzugefügt hatte, fielen die Umbauzeiten auf 2-3 Sekunden. Dies war ein Game-changer für den Entwicklungsprozess, aber dauerte Wochen, um zu entdecken.

Fehler #6: Tailwind Purging Classes I I actually Used

Meine Lieblings-Debugging-Erfahrung: CSS-Klassen, die in der Entwicklung gut funktionierten, verschwanden plötzlich in der Produktion. Tailwind reinigte sie, weil sie dynamisch in JavaScript generiert wurden. Die Lösung war die safelist Konfiguration, aber erst nachdem ich Stunden verschwendet hatte und mich gefragt hatte, ob ich verrückt würde. I AUCH (z.B. in der mostlylucid.pagingtaghelper project) verwenden Sie 'Dummy-Blöcke', wo ich meine Tailwind-Konfigurationsänderungen einfach mit einem versteckten Block vereinfache.


<!--
    Preserve Tailwind & DaisyUI classes used in embedded pager views.
    Without this, TailwindCSS tree-shaking removes classes from the embedded library views.
-->
<span class="hidden
    btn btn-sm btn-active btn-disabled join join-item badge badge-sm select select-bordered select-sm label label-text
    px-3 py-2 py-1 text-sm font-medium border rounded whitespace-nowrap cursor-not-allowed
    text-gray-700 text-gray-600 text-gray-400 text-white bg-white bg-gray-100 bg-blue-600 border-gray-300 border-blue-600
    hover:bg-gray-50 hover:bg-blue-700
    dark:bg-gray-800 dark:bg-gray-700 dark:bg-blue-500 dark:text-gray-300 dark:text-gray-500 dark:text-gray-200
    dark:border-gray-600 dark:border-blue-500 dark:hover:bg-gray-700 dark:hover:bg-blue-600">
</span>

Wenn Tailwind baut, scannt es die cshtml-Dateien nach Klassen; wenn es sie nicht sieht, entfernt es sie. Wenn also dieser versteckte Block sorgt dafür, dass er die Klassen sieht, die ich brauche, auch wenn sie nur in eingebetteten Bibliotheksansichten verwendet werden. JS-Entwickler scannen häufig auch JS/TS-Dateien, um die Verwendung von CSS-Stilen zu ermöglichen, die Tailwind sonst verpassen würde.


module.exports = {
content: ["./Views/**/*.cshtml", "./EmailSubscription/**/*.cshtml"],
-//^^^^ This is what Tailwing knows where to look for classes duritng the tree Shake. 
+//^^^^ This is what Tailwind knows where to look for classes during the tree-shake.
  
  ...

future: {...}

}

Wie bereits erwähnt, ist der 'Tailwind Approved' Ansatz, die Klassen zu einer sicheren Liste hinzuzufügen... aber ich bin ein ASP.NET Kerl, also HTML gewann.

Was ich richtig habe: Beharrlichkeit

Eine Sache, die ich richtig gemacht habe, war die Weigerung aufzugeben. Jede Fehlermeldung war eine Lernmöglichkeit. Jeder kaputte Bau brachte mir etwas Neues darüber bei, wie diese Werkzeuge funktionieren. Ich hatte eine klare Vision – gebündelte, optimierte Assets, die schnell geladen wurden – und ich iterierte, bis ich dort ankam.

Schlußfolgerung

Die Migration von CDN-basierten Abhängigkeiten zu einer modernen Bündelung Pipeline war transformativ für diesen Blog, aber es war nicht einfach. Die Performance-Verbesserungen sind erheblich, die Entwicklererfahrung ist deutlich besser, und ich habe viel mehr Kontrolle über die Optimierung – aber es dauerte Monate der Probe und Fehler, um hier zu kommen.

Schlüssel zum Mitnehmen:

  1. Bündelung reduziert Nutzlastgröße: Baum-Shaking und Verkleinerung eliminieren ungenutzten Code
  2. Code-Splitting verbessert die Leistung: Browser laden nur, was benötigt wird
  3. Build-Tools aktivieren modernes JavaScript: Babel Transpilation unterstützt die neuesten Sprachfunktionen unter Beibehaltung der Browser-Kompatibilität
  4. PostCSS Pipeline optimiert CSS: Der JIT-Compiler und cssnano von Tailwind liefern winzige Stylesheets
  5. Die Integration mit .NET ist nahtlos: MSBuild Targets starten automatisch Frontend-Builds
  6. Persistenz schlägt Perfektion: Du brauchst kein Frontend-Experte zu sein – du brauchst nur eine klare Vision und die Bereitschaft zu iterieren

Für ASP.NET Core-Entwickler gewohnt an einfache CDN beinhaltet, wird dies zunächst überwältigend fühlen. Das ist normal. Ich fühlte die gleiche Weise. Starten Sie klein, migrieren Sie eine Abhängigkeit auf einmal, und haben Sie keine Angst, Dinge in der Entwicklung zu brechen. Sie werden mehr von Debugging gebrochenen Builds lernen, als Sie jemals durch das Lesen von Dokumentation.

Die Konfiguration, die ich hier geteilt habe, stellt Monate der Iteration dar. Deine Reise wird anders sein, und das ist in Ordnung. Nutze dies als Ausgangspunkt, nicht als Evangelium. Passe es an deine Bedürfnisse an, experimentiere und entmutige dich nicht durch Misserfolge – sie sind Teil des Prozesses.

Ist dieser Blog überengineered? Aber ich hätte nicht halb so viel gelernt, und ich hätte diesen Artikel nicht mit Ihnen zu teilen. Manchmal ist Überengineering nicht über das Ziel - es geht darum, was Sie lernen auf dem Weg und in der Lage, andere von dieser Erfahrung zu lehren.

Wenn Sie immer noch auf CDNs für Ihre Frontend-Abhängigkeiten vertrauen, ermutige ich Sie, eine Bündelung zu geben. Beginnen Sie mit einer Bibliothek. Sehen Sie, wie es sich anfühlt. Iterate von dort. Das Ökosystem ist deutlich gereift, und während die Lernkurve real ist, lohnt sich die Auszahlung.

Literaturhinweise

Finding related posts...
logo

© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.