Modernisering van uw Frontend Build Pipeline: Van CDN's naar Bundelen (Nederlands (Dutch))

Modernisering van uw Frontend Build Pipeline: Van CDN's naar Bundelen

Tuesday, 11 November 2025

//

28 minute read

Inleiding

In het afgelopen jaar heb ik gestruikeld, geëxperimenteerd, gebroken dingen, en geleidelijk gemoderniseerd de frontend build pipeline voor deze blog. Dit was niet een masterplan uitgevoerd foutloos was proef en fout, veel fouten, en hardnekkige doorzettingsvermogen gedreven door een duidelijke visie van wat ik wilde bereiken.

Ik ben geen frontend goeroe of een Webpack wizard. Ik ben een .NET ontwikkelaar die gefrustreerd raakte met de beperkingen van CDN-gebaseerde afhankelijkheden en besloot dat er een betere manier moest zijn. Dit artikel documenteert de rommelige, iteratieve reis van eenvoudige <script> tags wijzen naar unpkg naar een moderne bundelen pijplijn die eigenlijk werkt.

Ik maakte tal van fouten langs de weg (die ik zal documenteren), besteed uren debuggen cryptische Webpack fouten, en waarschijnlijk opnieuw geïnstalleerd node_modules Meermaals dan ik wil toegeven... maar doorzettingsvermogen heeft gewerkt... en ik heb een enorme hoeveelheid geleerd... door pure vastberadenheid om dit goed te doen.

Als je een .NET ontwikkelaar staren naar Webpack configuratie bestanden afvragen wat in hemelsnaam je hebt gekregen je in dit artikel is voor u.

Ik ben een oude web performance gek, in de vroege dagen van dial-up de eerste site die ik bouwde voor geld (dat was geen porno backend in Perl...een verhaal voor een andere keer) was een Snow Conditions site die afgedraaid vanaf een automatische telefoonlijn.

In die dagen waren de zorgen heel anders. JavaScript was praktisch onbekend. Ik denk zelfs divs waren RARE (in die dagen IE was div En Netscape was layer) en je zou blij zijn als je gebruikers een 56K verbinding hadden. Dus heb ik geleerd om te optimaliseren (zelfs dingen zoals betegelde afbeelding achtergronden voor menu items) enz...

Later bij Microsoft heb ik zelfs gewerkt aan een automatische afbeelding sprite tool in Web Forms - het was geweldig: het haalde kleine afbeeldingen uit een pagina, gegenereerd CSS en een sprite beeld automatisch. Maar helaas, zoals mijn tijd bij Microsoft, het was niet om te zijn.

Kortom, het is een obsessie dat ik mijn hele carrière heb gedragen. Niemand houdt van een trage website!

**Om eerlijk te zijn.**CDN's zijn niet inherent slecht.

Volledige openbaarmaking: Deze blog is opzettelijk overontwikkeld. behoefte Al deze complexiteit. Maar het bouwen op deze manier laat me leren moderne frontend gereedschap goed en, cruciaal, gaf me iets echts om over te schrijven en te leren. Soms is de beste manier om te leren is om iets enigszins belachelijks te bouwen en documenteren van de reis.

Het probleem met CDN's

In eerste instantie was mijn aanpak eenvoudig:

<!-- 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">

Hoewel dit werkt, heeft het een aantal nadelen die steeds frustrerender werden:

  • Betrouwbaarheidskwesties: CDN's gaan naar beneden. Ik heb gezien unpkg en jsdelivr hebben uitval. Als ze neer, uw site is gebroken
  • Onvoorspelbaar laadgedrag: CDN antwoorden kunnen wild variëren. Soms snel, soms traag, soms introduceren ze race voorwaarden waar bibliotheken laden in de verkeerde volgorde
  • Timingskwesties: Je hebt geen controle over wanneer scripts ten opzichte van elkaar laden. Dit veroorzaakte me eindeloze hoofdpijnen met bibliotheek initialisatie
  • Meerdere HTTP-verzoeken: Elke bibliotheek vereist een aparte aanvraag, zelfs met HTTP/2
  • Geen boomschudden: Je downloadt de hele bibliotheek, zelfs als je er maar een fractie van gebruikt
  • Versiedrift: CDN's kunnen verschillende versies dienen tenzij u ze expliciet pint, en zelfs gepinde versies kunnen zich anders gedragen tussen CDN-providers
  • Onzekerheid in de bouwtijd: Geen manier om oneffenheden te vangen tot de runtime veel wanneer een gebruiker het meldt
  • Beperkte optimalisatie: Kan geen grenzen overschrijden of dode code verwijderen
  • Offline ontwikkeling: Vereist internetconnectiviteit, wat vervelend is bij het werken op treinen of vliegtuigen

Dus wilde ik controle... controle over precies wat mijn site gebruikte en nodig had om te werken. Ik wilde alleen bundelen wat ik nodig had, zorgen voor een betrouwbare lading bestelling, en optimaliseren voor prestaties. Dit betekende weg te gaan van CDN's en naar een bundeling aanpak.

Ze zijn eenvoudiger op te zetten, en met HTTP/2 en HTTP/3 multiplexing, de meervoudige aanvraag overhead is veel minder kritisch dan vroeger. Voor veel projecten, vooral kleine projecten of prototypes, CDN's blijven een perfect verstandige keuze. Maar voor een productie site waar ik wilde controle, betrouwbaarheid, en optimalisatie, bundeling meer zin.

De moderne Bundling-aanpak

Mijn huidige setup bundelt alle JavaScript afhankelijkheden via Webpack en verwerkt CSS via PostCSS.

Waarom Webpack in plaats van Vite?

Voordat ik in de technische details duik, moet ik me richten op de olifant in de kamer: waarom gebruik ik Webpack wanneer Vite, Rollup, esbuild, en andere moderne bundelaars bestaan?

Het eerlijke antwoord is eenvoudig: Ik kende Webpack al..

Ik had Webpack uitgebreid gebruikt bij het onderwijzen van mijn "Beginning Web Development" cursus, en het was de tool die ik begreep. Toen ik besloot om de bouwpijplijn van deze blog te moderniseren, was ik al geconfronteerd met een steile leercurve die boom-schudden, code splitsen, module systemen, PostCSS pijpleidingen, en hoe dit alles te integreren met ASP.NET Core.

Het is over het algemeen een goede praktijk bij het werken aan projecten; beperkt het 'nieuwe' tot het 'beheerbare'. Het is makkelijker om onzekerheid te beperken.

Het toevoegen van "een volledig nieuw bouwgereedschap" op dat leek overbodig. Webpack werkt. Het is volwassen. Het heeft uitstekende documentatie en een enorm ecosysteem. Het belangrijkste, Ik kon mijn leerenergie richten op de concepten van moderne bundeling in plaats van de eigenaardigheden van een bepaald instrument.

Is Vite sneller? Absoluut. Vite's ontwikkelserver met native ES modules en esbuild-powered bundeling is aanzienlijk sneller dan Webpack. Voor grote projecten met honderden modules is het verschil dramatisch.

Moet je Vite gebruiken voor een nieuw project? Waarschijnlijk, ja. Als u begint frisse en niet over bestaande Webpack kennis, Vite is waarschijnlijk de betere keuze. Het is sneller, eenvoudiger te configureren, en vertegenwoordigt de moderne aanpak van frontend tooling.

Heb ik spijt van het gebruik van Webpack? Helemaal niet. Het bracht me naar waar ik moest zijn. De lessen die ik leerde over bundelen, code splitsen en optimalisatie zijn overdraagbaar naar elk bouwgereedschap. En eerlijk gezegd, voor deze blog schaal, is het prestatieverschil tussen Webpack en Vite verwaarloosbaar.We hebben het over milliseconden in ontwikkeling herbouw tijden.

Het is een belangrijk aspect van hoe ik dingen bouw; begin met wat EASY is en bouw vanuit die stichting.

De bredere les hier is dat vooruitgang is beter dan perfectie. Ik had weken kunnen besteden aan het onderzoeken van de "beste" bundelaar, vergelijken van benchmarks, het lezen van vergelijkingsartikelen, en pijnlijk over de beslissing. In plaats daarvan, ik koos het gereedschap dat ik kende, kreeg het werken, en ging vooruit. Dat pragmatisme hield me verzending in plaats van eindeloos delibereren.

Misschien op een dag kan ik migreren naar Vite. Misschien zal ik niet. Hoe dan ook, deze blog heeft een moderne, geoptimaliseerde bouw pijplijn die werkt betrouwbaar ... en dat is wat er toe doet.

Pakketstructuur

De package.json Nu onderhoudt nu twee verschillende afhankelijkheidsgroepen. Deze structuur nam me verschillende pogingen om recht te krijgen in eerste instantie, Nuget en npm zijn KINDA vergelijkbaar, maar nppm is een stuk minder vriendelijk bij het toevoegen van pakkes.

{
  "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"
  }
}

Afhankelijkheden zijn bibliotheken nodig op runtime (Alpine.js, HTMX, EasyMDE, enz.), terwijl devDependencies bouwgereedschappen zijn (Webpack, Babel, PostCSS-processoren, enz.).

Scripts bouwen

De package.json scripts zijn aanzienlijk geëvolueerd:

{
  "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"
  }
}

Deze opzet onderscheidt zich van de volgende punten:

  • schoon: Verwijdert vorige build uitgangen met behulp van rimraf
  • kopie:* taken: Kopieën statische CSS-bestanden die niet hoeven te worden verwerkt (dit zijn ofwel 'importen') voor een speciaal doel zoals het laden van mijn funky Raleway lettertype of adaptieve degenen zoals thema schakelen verbeteringen.
  • tw:* taken: Processen Tailwind CSS via PostCSS
  • js:* taken: Bundels JavaScript via Webpack
  • dev/watch/build: Orkesteert de hele pijpleiding

De npm-run-all pakket maakt parallelle uitvoering mogelijk voor snellere builds.

U kunt instellen npm run build om automatisch te draaien tijdens uw lokale bouw, maar het is een beetje een pijn voor CI (je moet ervoor zorgen dat het is uitgeschakeld etc).

<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>

Om het te doen voeg dit gewoon toe aan je csproj...Je kunt zelfs zeggen alleen draaien tijdens debug' etc...maar ik vind het rommelig. Ik geef er de voorkeur aan om het handmatig uit te voeren. npm run watch doet alleen dat, wanneer een css / js bestand verandert het auto-runs de build.

OPMERKING: In de JS wereld maken ze bijna gebruik van hot-reload dev runs die vrij glad is en maakt je een beetje haat ASP.NET Core's zwakke poging.

Webpack Configuratie Deep Dive

De Webpack configuratie is waar de magie gebeurt en waar ik bracht het grootste deel van mijn tijd probleemoplossing. Dit is niet ontstaan volledig gevormd. Het is het resultaat van talloze iteraties, Stack Overflow zoekopdrachten, en het lezen door Webpack documentatie om 2am proberen te begrijpen waarom mijn bouw was het genereren van 47 chunk bestanden.

Hier is de huidige configuratie (van webpack.config.js) met gedetailleerde uitleg over wat elk deel doet en waarom het er is:

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,
        }
    };
};

Sleutelconfiguratie-secties uitgelegd

Code splitting en Chunking

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

Deze configuratie splitst automatisch uw code in kleinere brokken:

  • brokken: 'all': Analyseert zowel synchrone als asynchrone import
  • minMaat: 20000: Maakt alleen stukjes voor modules groter dan 20KB
  • max. grootte: 100000: Pogingen om stukken groter dan 100KB te splitsen
  • runtimeChunk: Extracten Webpack runtime in een apart bestand om langdurige caching te verbeteren

In de praktijk genereert dit meerdere bestanden in wwwroot/js/dist/:

  • main.js - Uw aanvraag entry point
  • runtime.js - Webpack module laden logica
  • [vendor].[contenthash].js - Automatisch splitsen van brokken van de leverancier
  • Extra brokken voor code-gesplitste routes of luie geladen modules

Babeltranspilatie

{
    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 transpiles modern JavaScript ter ondersteuning van oudere browsers:

  • streefdoelen: Ondersteunt browsers met meer dan 0,25% marktaandeel dat nog steeds wordt gehandhaafd
  • modules: onwaar: Bewaart ES6-modules voor Webpack tree-shakeing
  • useBuiltIns: 'Usage': Automatisch omvat polyfills alleen voor functies die u gebruikt
  • corejs: 3: Gebruikt core-js versie 3 voor polyfills

Dit betekent dat ik kan schrijven moderne JavaScript (async / wacht, optionele ketting, nullish coalescing) met behoud van brede browser ondersteuning.

Minificatie van de productie

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 minimaliseert op agressieve wijze de productiebouw:

  • drop_console: Verwijdert alles console.log() verklaringen
  • pass: 3: Runs compressie drie keer voor maximale grootte reductie
  • toplevel: waar: Mangles top-level variabele namen
  • pure _funcs: Verwijdert specifieke console methoden, zelfs als toegewezen aan variabelen
  • opmerkingen: onjuist: Strips alle opmerkingen van de output

Op deze blog, dit meestal vermindert JavaScript bundel groottes met 60-70% in vergelijking met ongelimineerde code.

PostCSS Pijpleiding voor achterwind

Tailwind CSS wordt verwerkt via PostCSS met verschillende plugins. Deze configuratie (van postcss.config.js) is genadig eenvoudig in vergelijking met Webpack:

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

De pijpleiding werkt als volgt:

  1. postcss-import: Oplossen @import verklaringen in CSS-bestanden
  2. staartwindcsunit synonyms for matching user input: Processes Tailwind richtlijnen en genereert utility classes
  3. autoprefixer: Voegt leveranciersvoorvoegsels voor browsercompatibiliteit
  4. cssnano: Minificeert de uiteindelijke CSS-uitvoer

Configuratie achterwind

De tailwind.config.js bestand geeft aan waar Tailwind naar klassenamen moet zoeken. content paden rechts was kritiekMis een pad en Tailwind zal geen klassen genereren voor die bestanden:

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"),
    ],
};

Belangrijkste kenmerken:

  • inhoud: Scannen .cshtml bestanden voor klassenamen (inclusief Razor-weergaven en e-mailsjablonen)
  • safelist: Altijd bevat deze klassen, zelfs als niet gevonden in scans
  • darkMode: "klasse": Inschakelt op klasse gebaseerde donkere modus schakelen
  • thema.extend: Voegt aangepaste kleuren en afstandswaarden toe
  • plugins: Inclusief Tailwind-plugins en DaisyUI-componentbibliotheek

Tailwind scant deze bestanden op bouwtijd en haalt alleen de nutsklassen uit die u daadwerkelijk gebruikt. Daarom is het uiteindelijke CSS-bestand veel kleiner dan de volledige Tailwind-bibliotheek.

JavaScript Entry Point

De main.js file is where everything comes together. Dit bestand importeert en initialiseert alle afhankelijkheden, en het is biologisch gegroeid als ik functies toegevoegd aan de 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();

Deze aanpak biedt verschillende voordelen:

  1. Eén invoerpunt: Alle afhankelijkheden geladen door één invoerbestand
  2. Expliciete versies: Pakket.json vergrendelt specifieke versies
  3. Boomschudden: Webpack verwijdert ongebruikte code uit bibliotheken
  4. Wereldwijde blootstelling: Bibliotheken beschikbaar voor Razor views via window
  5. Aangepaste modules: Toepassingsspecifieke code georganiseerd in invoerbare modules

Opmaak van sjabloonintegratie

In _Layout.cshtml, Ik verwijs nu alleen naar gebundelde activa:

<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>

Opmerking I specificeren module om de browser te laten weten wat voor soort JS-bestand dit is & asp-append-version een nette kleine ASP.NET tag helper die een gehashed versie van de bestandsinhoud toevoegt aan de querystring; effectief cache-busting wanneer het bestand verandert.

De enige resterende CDN afhankelijkheden zijn:

  • Google Aanmelden: Vereist externe CDN voor OAuth functionaliteit
  • Boxiconen: Pictogram lettertype (kan worden gebundeld, maar minimale voordeel)
  • Umami Analytics: Third-party analytics script - zelf gehost dus ik krijg om uw bezoek te zien niet Google ??

Bouw Pijpleiding Visualisatie

Hier is hoe het gehele bouwproces stroomt van bronbestanden naar productiemiddelen:

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

Het ziet er vrij complex maar echt dat is wat WebPack doet, het behandelt de meeste van dit zelf (op een ingewikkelde manier die de likes van Vite niet nodig, maar ...)

Prestatieverbeteringen

Nogmaals, niet echt het POINT. Maar het laadt nu wel als een absolute kogel. Je kunt nog steeds problemen krijgen met Cloudflare's 'Rocket Loader' (die pagina's een beetje laden) maar het is TIGHT.

Ontwikkelaarservaringsvoordelen

Naast prestaties verbetert de moderne bouwpijplijn de ontwikkelingsworkflow aanzienlijk:

Type veiligheid en IntelliSense

Bundelen via Webpack maakt de juiste JavaScript module resolutie mogelijk, wat betekent:

  • IntelliSense in VS Code: Automatisch aanvullen voor geïmporteerde modules
  • Naar definitie springen: Navigeer direct naar de broncode van de bibliotheek
  • Inline documentatie: JSDoc opmerkingen uit bibliotheken verschijnen in IDE

WANNEER de bouw breekt (met npm run watch) Ik weet INSTANT, gecombineerd met de (paar maar groeiende) JS-eenheid testen betekent het strakkere feedback loops.

Hot Module replacement

Tijdens de ontwikkeling, npm run watch maakt bijna-instant feedback mogelijk:

npm run watch

Dit draait Webpack in horlogemodus, herbouwt alleen gewijzigde modules:

  • Typische herbouwtijd: 200-400m
  • Browser automatisch verversen: Het gebruik van browser-sync of soortgelijke tooling
  • Instandhouding van de toepassingstoestand: HMR behoudt de status van de toepassing tijdens updates (indien ondersteund)

Afhankelijkheidsbeheer

Npm's vergrendelingsbestand (package-lock.json) zorgt voor een consistente opbouw in verschillende omgevingen:

npm ci  # Clean install from lock file

Dit garandeert dezelfde afhankelijke versies in ontwikkeling, CI/CD en productieomgevingen.

Deze worden aangeduid als 'pinning' in de JS wereld, waar de afhankelijkheden zijn ingesteld in steen. Je kunt doen zonder een pakket slot, maar dan ben je afhankelijk van pacakge auteurs; vaak HUNDREDS van verschillende mensen niet het verpesten van een aantal kleine release...s

Scripts bouwen

De georganiseerde npm scripts maken gemeenschappelijke taken gemakkelijk:

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

Gemeenschappelijke patronen en oplossingen

Bundelbibliotheken wereldwijd exposeren

Sommige bibliotheken hebben wereldwijde blootstelling nodig voor gebruik in Razor views of inline scripts:

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

Daarna in .cshtml bestanden:

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

Omgaan met CSS van JavaScript Modules

Sommige bibliotheken bevatten CSS die geïmporteerd moet worden:

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

Webpack's css-loader en style-loader deze invoer te behandelen:

  • CSS wordt verwijderd tijdens de bouw
  • Injecteren in de pagina via <style> tags of afzonderlijke CSS-bestanden
  • Automatisch geprefixeerd en geminifieerd

Lazy Lading Heavy Libraries

Voor bibliotheken alleen nodig op specifieke pagina's, gebruik dynamische import:

// 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 verdeelt automatisch dynamische import in afzonderlijke brokken, geladen op aanvraag.

Omgaan met CommonJS-modules

Sommige oudere bibliotheken gebruiken CommonJS in plaats van ES6-modules:

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

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

Webpack verwerkt beide modulesystemen transparant en zet CommonJS waar nodig om naar ES6.

Problemen met het oplossen van gemeenschappelijke problemen

Bronkaarten ontbreken in ontwikkeling

Zorgen devtool is geconfigureerd in webpack.config.js:

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

Dit genereert bronkaarten in ontwikkeling voor eenvoudiger debuggen.

CSS-klasse Pushingproblemen met staartwind

Als Tailwind verwijdert klassen die u gebruikt, controleer de content configuratie:

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

Als alternatief, gebruik de safelist voor dynamische klassen:

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

Module niet gevonden fouten

Als Webpack een module niet kan oplossen, controleer dan:

  1. Correct import pad: Relatieve paden beginnen met ./ of ../
  2. Bestandsextensies: Toevoegen .mjs tot resolve.extensions indien nodig
  3. BijnamenGebruik resolve.alias voor complexe paden
resolve: {
    extensions: ['.js', '.mjs', '.json'],
    alias: {
        '@components': path.resolve(__dirname, 'src/js/components/'),
    }
}

Build Performance Issues

Als builds langzaam worden:

  1. Caching inschakelen: Webpack's cache versnelt de wederopbouw aanzienlijk
  2. Reduceer het toepassingsgebied van Babel: Exclusief meer mappen van Babel processing
  3. Upgradegereedschappen: Nieuwere versies van Webpack en Babel zijn sneller
  4. Parallelle constructiesGebruik thread-loader voor multithreaded transpilatie
// Enable Webpack caching
cache: {
    type: 'filesystem',
},

Lessons Learned the Hard Way

Laat me enkele fouten delen die ik gemaakt heb tijdens deze reis, zodat je ze kunt vermijden:

Fouten #1: Proberen om alles in één keer te bundelen

Mijn eerste poging betrof het uitrukken van alle CDN referenties in de ene keer en proberen om alles te bundelen door Webpack. De bouw brak spectaculair. Ik leerde dat incrementele migratie is uw vriend verplaatsen een bibliotheek per keer, grondig testen, dan verplaatsen naar de volgende.

Fouten #2: Moduletypes niet begrijpen

Ik heb uren verspild om uit te zoeken waarom bepaalde bibliotheken niet zouden laden.require()) en ES6-modules (import) zonder te begrijpen hoe Webpack ermee omgaat leidt tot cryptische fouten. experiments: { outputModule: true } configuratie was niet in mijn eerste setup, en ik kon niet uitzoeken waarom mijn modules niet geladen waren.

De oplossing kwam van het lezen via GitHub problemen op de Webpack repository om middernacht.

Fouten #3: Over-agressieve code splitsen

Op een gegeven moment, mijn configuratie was het genereren van stukjes voor alles. minSize: 10000 (10KB) wat betekende dat Webpack aparte bestanden creëerde voor kleine utility functies. De over-gecorrigeerde en ging naar 2MB... Paginabelasting werd een waterval van 50+ kleine brok aanvragen of opgehangen wachtend op enorme brokken te downloaden. Ik heb geleerd dat code splitsen is goed, maar je hebt verstandige drempels nodig. Dat is waarom mijn huidige configuratie maakt gebruik van minSize: 20000 (20KB).

Fouten #4: vergeten over de .mjs Uitbreiding

Sommige npm pakketten verdelen ES6-modules met de .mjs extensie. Webpack zal deze standaard niet oplossen. Ik heb een beschamende lange tijd debuggen waarom @mostlring I needed to add .mjsunit synonyms for matching user inputtoOplossen.......... ...................................... ...............................................................................................................................................................................................................................................................................................................................................................................................................................................................................

Simpele oplossing als je het eenmaal weet, maar dat ontdekken duurde even.

Fouten #5: niet Caching Webpack Builds

Early builds duurde 30-60 seconden omdat ik niet had ingeschakeld Webpack's bestandssysteem cache. Zodra ik toegevoegd caching, wederopbouw tijden gedaald tot 2-3 seconden. Dit was een game-changer voor ontwikkeling workflow, maar nam me weken om te ontdekken.

Fouten #6: Achterwinden opruimen klassen die ik eigenlijk gebruikt

Mijn favoriete debugging ervaring: CSS klassen die goed werkten in ontwikkeling plotseling verdwenen in de productie. Tailwind was ze aan het zuiveren omdat ze dynamisch werden gegenereerd in JavaScript. De oplossing was de safelist Configuratie, maar pas nadat ik uren verspild had... vroeg ik me af of ik gek werd. I OOK (bijvoorbeeld in de mostlylucid.pagingtaghelper project) gebruik 'dummy blocks' waar ik mijn Tailwind configuratie wijzigingen vereenvoudig door gewoon een verborgen blok te hebben.


<!--
    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>

Wanneer Tailwind bouwt scant het de cshtml-bestanden voor klassen; als het ze niet ziet verwijdert het ze. Dus met dit verborgen blok zorgt het ziet de klassen die ik nodig heb, zelfs als ze alleen worden gebruikt in embedded library views. JS ontwikkelaars vaak ook scannen JS/TS-bestanden om het gebruik van CSS-stijlen die Tailwind anders zou missen.


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: {...}

}

Zoals gezegd, de 'Tailwind Approval' benadering is om de klassen toe te voegen aan een safelist... maar ik ben een ASP.NET kerel, dus HTML gewonnen.

Wat ik goed heb: Persistentie

Het enige wat ik goed deed was weigeren om op te geven. Elke foutmelding was een leermogelijkheid. Elke gebroken bouw leerde me iets nieuws over hoe deze gereedschappen werken. Ik had een duidelijke visie gebundeld, geoptimaliseerde activa die snel laden en ik bleef itereren totdat ik daar kwam.

Conclusie

Migreren van CDN-gebaseerde afhankelijkheden naar een moderne bundeling pijplijn is transformerend geweest voor deze blog, maar het was niet gemakkelijk. De prestaties verbeteringen zijn aanzienlijk, de ervaring van de ontwikkelaar is aanzienlijk beter, en ik heb veel meer controle over optimalisatie.Maar het duurde maanden van proef en fout om hier te komen.

Belangrijkste afhaalmaaltijden:

  1. Bundelen vermindert de payload grootte: Boomschudden en minificatie elimineren ongebruikte code
  2. Code splitsen verbetert de prestaties: Browsers laden alleen wat nodig is
  3. Bouwgereedschappen inschakelen van moderne JavaScript: Babel transpilation ondersteunt de nieuwste taal functies met behoud van browser compatibiliteit
  4. PostCSS-pijpleiding optimaliseert CSS: Tailwind's JIT compiler en cssnano leveren kleine stylesheets
  5. Integratie met .NET is naadloos: MSBuild targets draaien automatisch frontend builds
  6. Persistentie verslaat perfectie: Je hoeft geen frontend expert te zijn.Je hebt alleen een duidelijke visie nodig en de bereidheid om te itereren.

Voor ASP.NET Core ontwikkelaars die gewend zijn aan eenvoudige CDN omvat, dit zal overweldigend voelen in het begin. Dat is normaal. Ik voelde hetzelfde. Start klein, migreren een afhankelijkheid per keer, en wees niet bang om dingen te breken in ontwikkeling. Je zult meer leren van het debuggen gebroken bouwt dan je ooit zult van het lezen van documentatie.

De configuratie die ik hier heb gedeeld vertegenwoordigt maanden van iteratie. Uw reis zal anders zijn, en dat is prima. Gebruik dit als een startpunt, niet evangelie. Pas het aan uw behoeften, experiment, en worden niet ontmoedigd door mislukkingen zijn deel van het proces.

Is deze blog overontworpen? Absoluut. Zou ik CDN's blijven gebruiken en mijn tijd aan andere dingen doorbrengen? Tuurlijk. Maar ik zou niet de helft van zoveel geleerd hebben, en ik zou dit artikel niet met jou kunnen delen. Soms gaat over-engineering niet over de bestemming.Het gaat over wat je onderweg leert en anderen van die ervaring kan leren.

Als je nog steeds vertrouwt op CDN's voor je frontend afhankelijkheden, moedig ik je aan om bundeling een poging te geven. Begin met één bibliotheek. Zie hoe het voelt. Iterate vanaf daar. Het ecosysteem is aanzienlijk gerijpt, en terwijl de leercurve is echt, de uitbetaling is de moeite waard.

Meer lezen

Finding related posts...
logo

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