Back to "Moderniser votre pipeline de construction de frontend: De CDNs à Bundling"

This is a viewer only at the moment see the article on how this works.

To update the preview hit Ctrl-Alt-R (or ⌘-Alt-R on Mac) or Enter to refresh. The Save icon lets you save the markdown file to disk

This is a preview from the server running through my markdig pipeline

ASP.NET Frontend Tailwind Webpack

Moderniser votre pipeline de construction de frontend: De CDNs à Bundling

Tuesday, 11 November 2025

Introduction

Au cours de l’année écoulée, Je'ai échouéM SK2expérimenté ,fait des choses défectueuses,, et progressivement modernisé le pipeline de construction du frontend pour ce blogMSC5 Il ne s’agissait pas d’un plan directeur exécuté de manière parfaiteMST7il s’agit d’essais et d’erreurs,MST8 beaucoup d’irrégularités,Mst9 et une persistance soutenue motivée par une vision claire de ce que j’aimerais atteindre.

I'm n'est pas un guru de front-end ou un moniteur Webpack, mais un développeur NET qui a été frustré par les limitations des dépendances basées sur CDN et décidé qu'il fallait trouver une meilleure façon d'y arriver. <script> balises indiquant un unpkg à une pipeline moderne qui fonctionne effectivement.

J’ai fait un grand nombre d’erreurs tout au long de la route (que je documenterai 'll documente), j’ai passé des heures à débuger les erreurs Webpacks cryptiquesM SK3 et probablement réinstallé node_modules Mais la persistance s’est révélée bénéfique, et j’ai appris beaucoup grâce à une détermination sincère de faire ce qui est juste.

Si vous 'êtes un développeur .NET qui regarde les fichiers de configuration du Webpack et se demande ce que vous avez fait dans le monde, cet article est pour vous.

I'je suis un vieux fan de la performance du web ,dans les premiers jours du dial-au premier site que j’ai construit pour des sommes d’argentM SK3qui n’était pas une backend porno en PerlMSC5une histoire pour une autre foisMST6un site Snow Conditions qui sortait d’une ligne téléphonique automatiséeMst7

À l’époque, les préoccupations étaient très différentes. JavaScript était pratiquement inconnu. Je pense que même divs étaient RARE M SK2 à l’ère IE était div et Netscape était layer) et vousM SK1d être LUCKY si vos utilisateurs avaient une connexion 56K. Donc j’ai appris à optimiser

Plus tard à Microsoft, j’ai même travaillé sur un outil automatisé d’image-sprite dans les formulaires Web - c’était fantastique : il a extrait de petites images d’une page

En bref, ce n’est pas une obsession que j’ai vécue toute ma carrière.

Pour être juste, Les NCD ne sont pas intrinsèquement malveillants

Divulgation complète: Ce blog est délibérément super-ingénifié . Il ne l’est pas a besoin Toute cette complexité. Mais en la construisant de cette façon, j’ai pu apprendre les outils modernes d’avant-garde comme il se doit et, crucialement, , , m’a donné quelque chose réel à écrire et à enseigner.

Le problème des NCD

À l’origine, ma démarche était simple.

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

Bien que cela fonctionne, il a plusieurs drawbacks qui sont devenus de plus en plus frustrants.

  • Questions de fiabilité: Les NCD vont de l’avant . IM SK2 j’ai vu un pkg et jsdelivr avoir des pannes
  • Le comportement de chargement imprévisible: Les réponses du CDN peuvent varier de façon sauvage . Parfois rapide, parfois lenteM SK3 parfois elles introduisent des conditions de compétition où les bibliothèques sont chargées dans l'ordre erroné
  • Questions liées au calendrier: Vous n'avez aucun contrôle sur le moment où les scripts se chargent par rapport à l'autre . Cela m'a provoqué des maux de tête sans fin avec l'initialisation de la bibliothèque
  • Demandes HTTP multiples: Chaque bibliothèque a besoin d'une demande distincte, même avec HTTP
  • Aucun arbre-shaking: Vous téléchargez l'ensemble de la bibliothèque , même si vous n'en utilisez qu'une fraction
  • Déplacement de la version: Les CDN peuvent servir des versions différentes à moins que vous ne les attachez explicitement , et même les versions attachées peuvent se comporter différemment entre les fournisseurs de CDN
  • Build- incertitude du temps: Aucun moyen de détecter les incompatibilités avant le temps d'exécution— fréquemment lorsqu'un utilisateur en fait rapport
  • Optimisation limitée: Ne peut ' minifier à travers les frontières ou enlever le code mort
  • Développement hors ligne: Exige une connexion Internet, qui est annonçante lorsqu'on travaille sur les trains ou les avions

C’est pourquoi j’ai voulu contrôler ce que mon site utilise exactement et ce qu’il NE fallait travailler.

Ils sont plus simples à installer, et avec HTTP/2 et HTTP /3 multiplexingM SK4 les frais généraux liés aux demandes multiples sont beaucoup moins importants qu’ils ne l’étaient auparavantMSC5 Pour de nombreux projetsMST6 en particulier pour les petits ou les prototypes MST7 les NCD restent une option parfaitement judicieuseM ST8 Mais pour un site de production où j’avais voulu le contrôleM st9 la fiabilitéMst10 et l’optimisationM St11 le regroupement était plus logiqueM S ST12

L'approche moderne du regroupement

Mon configuration actuelle regroupe toutes les dépendances JavaScript par le biais du Webpack et procède au CSS par l’entremise de PostCSS.

Pourquoi Webpack au lieu de Vite?

Avant de se plonger dans les détails techniques, Je dois m’adresser à l’éléphant dans la salle: pourquoi je suis-je utilisant le Webpack lorsque ViteM SK2 Rollup , esbuildMSC4 et d’autres bundlers modernes existentMNK5

La réponse honeste est simple: Je connais déjà Webpack.

I'd utilise Webpack extensively when teaching my "Beginning Web Development " courseM SK3 and it was the tool I understoodMSC4 When I decided to modernise this blogMska5s build pipelineMske6 I was already facing a steep learning curveM Ska7understanding treeM ska8shakingMSKA9 code splittingMski10 module systemsM Ski11 PostCSS pipelinesM ski12 and how to integrate all of this with ASPMSKI13NET CoreMsky14

Il est généralement une bonne pratique de travailler sur des projets.

Ajouter " apprendre un nouvel outil de construction tout à fait nouveau " en plus, cela semblait inutile. Travailler sur le WebpackM SK3 IlMSC4 est mûrMNK5 Il possède une excellente documentation et un énorme écosystèmeMRK6 Et surtoutMMK7 Je pourrais concentrer ma énergie d’apprentissage sur concepts de la combinaison moderne plutôt que les idiosyncrasies d’un outil particulier.

Est-ce que Vite est plus rapide? Absolutement. Vite' le serveur de développement avec modules ES natifs et esbuild - le regroupement appuyé est significativement plus rapide que Webpack

Pouvez-vous utiliser Vite pour un nouveau projet? Probablement, oui . Si vous' débutez tout à fait neuf et que vous ne possèdez pas de connaissances sur le Webpack existant, ' Vite est probablement la meilleure option.

Do I regret using Webpack? Pas du tout. Il m’a amené là où j’avais besoin d’être . Les leçons que j’ai apprises au sujet de l’assemblage, la séparation des codesM SK3 et l’optimisation sont transférées à n’importe quel outil de constructionMNK4 Et honnêtement, pour ce blog MNK5 l’échelle de MNK6MKK7 la différence de performance entre Webpack et Vite est négligeableMMK8nousMZK9 nous parlons de millisecondes dans les temps de reconstruction de développementMGK10

Il s’agit d’un aspect clé de la façon dont je bâti les choses. Commençons par ce qui est EASY et construisons sur cette base.

La leçon la plus large est que Le progrès bat la perfection. J’aurais pu passer des semaines à étudier les meilleures méthodes de comparaison, à lire des articles de comparation et à s’angoler au sujet de la décision, à comparer les points de repère, à ,, à m’appliquer aux outils que j’avais connus, à l’utiliser, à le faire fonctionner et à me déplacer vers l’avenir, à ce pragmatisme qui m’a permis d’expédier plutôt qu’à réfléchir sans cesse.

Peut-être qu’un jour je migrerai vers Vite. peut-être que j’ai gagné 't. Quoi qu’il en soit, ce blog a un pipeline de construction moderne et optimisé, , qui fonctionne de façon fiableM SK5et c’est ce qui importeMSC6

Structure du paquet

L'article 5 de la Loi sur l'immigration et le statut des réfugiés package.json maintenant maintient deux groupes de dépendance distincts. Cette structure m’a pris plusieurs tentatives pour arriver à la bonne conclusion —initiellementM SK2 Nuget et npm sont similaires à KINDA mais nppm est beaucoup moins amicale lorsqu’on ajoute des pacakges.

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

Dépendances sont des bibliothèques nécessaires au temps de fonctionnement (Alpine .js, HTMXM SK3 EasyMDEMST4 etcMSC5 alors que devDependences sont des outils de construction (Webpack , Babel, Processuseurs PostCSSM SK3 etcMSC4

Créer des scripts

L'article 5 de la Loi sur l'immigration et le statut des réfugiés package.json Les scripts ont beaucoup évolué:

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

Cette configuration sépare les préoccupations:

  • nettoyer: Supprime les sorties de construction antérieures en utilisant rimraf
  • copie:* les tâches: Copies des fichiers CSS statiques qui ne nécessitent pas de traitement 'dont il n'y a pas besoin d'en traiter (ils sont soit 'importationsM SK4 pour un usage spécial comme le chargement de mon style Raleway funky ou ceux adaptés comme l'ajout de thèmes et les améliorations.
  • TÉU, TPAC et TPMD* Tasks: Processes Tailwind CSS via PostCSS
  • Js:* Tasks: Bundles JavaScript via Webpack
  • dev/watch/build: Orchestre l'ensemble du pipeline

L'article 5 de la Loi sur l'immigration et le statut des réfugiés npm-run-all paquet permet l'exécution parallèle pour des constructions plus rapides.

Vous pouvez npm run build pour fonctionner automatiquement au cours de votre construction locale mais il'est un peu douloureux pour la CI (il faut s'assurer qu'elle est désactivée, 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>

Pour le faire, il suffit de l’ajouter à votre csproj...Vous pouvez même dire qu’il ne fonctionne que durant la débugging' etc ... mais je trouve ça messyM SK3 Je préfère simplement le faire fonctionner manuellementMSC4 Donc npm run watch ne fait que ce qui suit :, quand un fichier css / js modifie le fichier, il change automatiquement

NOTE: Dans le monde de JS, ils utilisent presque hot-reload dev runs qui est assez équilibré et vous fait quelque peu odier ASP

Configuration de Webpack Deep Dive

La configuration du paquet Web est là où se produit la magie—et là où j’ai passé la majeure partie de mon temps à corriger les problèmes . Ce n’est pas ce qui s’est produit'il n’a pas été complètement forméM SK3 C’est le résultat d’innombrables répétitionsMSC5 des recherches sur l’excès de stocksMska6 et de lectures dans la documentation du paque Web à Mske7en essayant de comprendre pourquoi mon build était en train de produire des fichiers chunksMSKA8

Voici la configuration actuelle (de webpack.config.js) avec des explications détaillées de ce que chaque partie fait et pourquoi il y a

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

Sections de configuration clé expliquées

Divulgation de code et utilisation d'un clavier

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

Cette configuration répartit automatiquement votre code en petits fragments:

  • fractions: 'total': Analyse les importations simultanées et asynchrones
  • MinSize: M SK1: Crée seulement des chunks pour les modules plus grands que 20KB
  • maxSize: M SK1: Tente de séparer des chunks plus grands que 100KB
  • RuntimeChunk: Extrait le temps de fonctionnement du paquet Web dans un fichier distinct pour améliorer la cache à long terme

Dans la pratique,, cela génère plusieurs fichiers dans wwwroot/js/dist/:

  • main.js - Point d'entrée de votre demande
  • runtime.js Logique de chargement du module Webpack -
  • [vendor].[contenthash].js - Partitionner automatiquement les tronçons du vendeur
  • Particules supplémentaires pour les modules chargés de code-routes partagées ou lazy-module

Babel Transpilation

{
    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 transpile le JavaScript moderne pour soutenir les navigateurs plus anciens:

  • cibles: Appuie les navigateurs dont la part de marché >0.25% est encore maintenue
  • modules: faux: Préserve les modules ES6 pour le Webpack treeM SK2shaking
  • UtiliserBuiltIns: M SK1usage': Inclue automatiquement des polyfills uniquement pour les fonctions que vous utilisez
  • corejs: 3: Utilise le noyau-js version 3 pour les polyfills

Cela signifie que je peux écrire un JavaScript moderne.

Minification de la production

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 minimise agressivement les constructions de production:

  • Drop_console: Relève tous console.log() Déclarations
  • passes: M SK1: Exécute une compression trois fois pour réduire la taille maximale
  • niveau supérieur: vrai: Mangles top- noms de variables de niveau
  • pure_funcs: Supprime les méthodes de console spécifiques même si elles sont attribuées à des variables
  • Commentaires: faux: Traite tous les commentaires de la sortie

Sur ce blog, cela réduit habituellement les tailles des paquets JavaScript par 60-70% comparativement au code non limité.

PostCSS Pipeline pour Tailwind

Tailwind CSS est traité par PostCSS avec plusieurs plugins. Cette configuration (de postcss.config.js) est mercifully simple comparativement à Webpack:

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

Le pipeline fonctionne comme suit:

  1. Postcss-importation: Résolutions @import des énoncés dans les fichiers CSS
  2. tailwindcss: Processue les directives Tailwind et produit des classes d'utilité
  3. Préfixeur automatique: Ajout des préfixes du fournisseur pour la compatibilité avec le navigateur
  4. cssnano: Minimise la sortie finale de CSS

Configuration de Tailwind

L'article 5 de la Loi sur l'immigration et le statut des réfugiés tailwind.config.js le fichier spécifie où Tailwind devrait chercher les noms de classes. Obtenir content paths right was critical—miss a path and Tailwind wonM SK1t generate classes for those files:

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

Principales caractéristiques:

  • Table des matières: Scans .cshtml fichiers pour les noms de classes (y compris les vues Razor et les modèles d'email)
  • Liste de sécurité: Inclure toujours ces classes même si elles ne sont pas trouvées dans les scans
  • darkMode: M SK1classe": Enable l'interruption du mode foncé fondée sur la classe-
  • thème.étendre: Ajout des couleurs personnalisées et des valeurs d'espacement
  • Les plugins: Comprend les plugins Tailwind et la bibliothèque des composantes DaisyUI

Tailwind scanne ces fichiers au moment de la construction, en extrayant seulement les classes d'utilité que vous utilisez réellement. C'est pourquoi le fichier CSS final est beaucoup plus petit que la bibliothèque complète de Tail WindM SK2

Point d'entrée de JavaScript

L'article 5 de la Loi sur l'immigration et le statut des réfugiés main.js ce fichier importe et initialise toutes les dépendances.

// 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();

Cette approche offre plusieurs avantages

  1. Point d'importation unique: Toutes les dépendances chargées par un fichier d'entrée
  2. Versions explicites: Package.json locks versions spécifiques
  3. Bois-shaking: Webpack supprime le code inutilisé des bibliothèques
  4. Exposition mondiale: Les bibliothèques peuvent être consultées par Razor via window
  5. Modules personnalisés: Application-code spécifique organisé en modules importables

Intégration du modèle de layout

À l'intérieur _Layout.cshtml, Je me réfère maintenant uniquement aux biens groupésM SK1

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

Note I specify module afin que le navigateur sache quel type de fichier JS il s'agit de & asp-append-version un petit assistant de balises ASP.NET qui ajoute une version hachée du contenu des fichiers à la chaîne d'interrogatoire.

Les seules dépendances restantes du CDN sont:

  • Google Sign-In: Exige un CDN externe pour la fonctionnalité OAuth
  • Boîtes-symboles: Font de l'icône ( pourrait être assorti d'un paquet, mais un avantage minimalM SK3
  • Umami Analytics: Troisième - script d'analyse de la part des parties - auto-hébergé pour que je puisse voir votre visite non pas Google |

Visualisation du pipeline de construction

Voici' comment l’ensemble du processus de construction s’écoule des fichiers source aux actifs de production.

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

Il semble assez complexe, mais c’est vraiment ce que WebPack fait.

Amélioration du rendement

Encore une fois, pas vraiment le POINTM SK1 Mais il charge comme un bullet absolu maintenant. Vous pouvez toujours avoir des problèmes avec CloudflareMST3s Mst4Rocket LoaderMSt5 MST6qui mangles les événements de chargement de page quelque peuM st7 mais c'estM ST8s SIGHTMstr9

Avantages de l'expérience du développeur

Outre la performance,, le pipeline de construction moderne améliore considérablement le flux de travail du développement:

Type Sécurité et IntelliSense

Le regroupement par Webpack permet une résolution appropriée du module JavaScript, ce qui signifieM SK1

  • IntelliSense dans le code VS: Auto-completion pour les modules importés
  • Passer à la définition: Naviger directement au code source de la bibliothèque
  • Documentation en ligne: Les commentaires de JSDoc provenant des bibliothèques apparaissent dans l'IDE

Lorsque la construction s’interrompt ( avec npm run watch) Je sais INSTANTLY , combiné à l’M SK2 peu mais croissante) Les essais d’unité de JS signifient des cycles de rétroaction plus étroitsMSC4

Remplacement du module chaud

Au cours du développement, npm run watch permet une rétroaction instantanée proche-:

npm run watch

Ce système utilise Webpack en mode de surveillance, reconstitue seulement les modules modifiés:

  • Temps de reconstruction typique: 200-400ms
  • Browser auto-refresh: Utilisation du navigateur-sync ou outils similaires
  • État de l'application préservé: Le HMR maintient l'état de l'application pendant les mises à jour (dans le cas où il est supporté)

Gestion de la dépendance

npm's lock file (package-lock.json) assure des constructions cohérentes dans tous les environnements

npm ci  # Clean install from lock file

Cela garantit les mêmes versions de dépendance dans le développement, CI/CDM SK2 et dans les environnements de production .

Ces sont appelés 'pinning ' dans le monde de la JS, où les dépendances sont définies en pierreM SK3 Vous pouvez faire sans bouclage des paquets mais ensuite vousMSC4dependez sur les auteurs du pacakgeMST5 souvent des milliers d'intervenants différents qui n'enfreignent pas un quelconque relevé mineur

Créer des scripts

Les scripts npm organisés facilitent les tâches courantes:

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

Méthodes et solutions communes

Exposer les bibliothèques groupées à l'échelle mondiale

Certaines bibliothèques ont besoin d’une exposition globale pour être utilisées dans les vues Razor ou les scripts en ligne:

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

Ensuite, dans .cshtml fichiers:

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

Traitement de CSS à partir des modules JavaScript

Certaines bibliothèques incluent CSS qui nécessite l’importation:

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

Webpack's css-loader et style-loader traiter ces importations:

  • CSS est extrait au cours de la construction
  • Injeté dans la page via <style> tags ou fichiers CSS séparés
  • Préfixes et minimisations automatiques

Les bibliothèques lourdes sont chargées lentement

Pour les bibliothèques uniquement nécessaires sur des pages spécifiques, utiliser les importations dynamiques :

// 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();
}

Code Web automatisé-splits les importations dynamiques en morceaux distincts, chargés sur l’ordinateur -demandM SK3

Traitement des modules CommonJS

Certaines bibliothèques plus anciennes utilisent CommonJS plutôt que les modules ES6

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

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

Webpack gère les deux systèmes de modules de façon transparente, convertir CommonJS à ES 6 lorsque nécessaire.

Troubleshooting Common Issues

Source Cartes manquantes dans le développement

Assurer devtool est configuré dans webpack.config.js:

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

Cela génère des cartes de source en développement pour faciliter le débugging.

Questions liées à l'épuration de la classe CSS avec Tailwind

Si Tailwind supprime les classes que vous utilisez, vérifiez la content configuration:

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

Autrement dit, , utiliser le safelist pour les classes dynamiques:

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

Les erreurs du module n'ont pas été trouvées

Si Webpack n’est pas en mesure de résoudre un module', vérifiez ce qui suit:

  1. Correction du chemin d'importation: Les voies relatives débutent avec ./ ou ../
  2. Extensions de fichier: Ajouter .mjs à resolve.extensions le cas échéant
  3. Aliases: Usage resolve.alias pour les chemins complexes
resolve: {
    extensions: ['.js', '.mjs', '.json'],
    alias: {
        '@components': path.resolve(__dirname, 'src/js/components/'),
    }
}

Élaborer des questions de rendement

Si les constructions deviennent lentes:

  1. Enable caching: Webpack's cache accélère considérablement les reconstructions
  2. Réduire la portée de Babel: Exclure d'autres répertoires du traitement Babel
  3. Améliorer les outils: Les versions plus récentes de Webpack et Babel sont plus rapides
  4. Établissements parallèles: Usage thread-loader pour la transpilation multi-threaded
// Enable Webpack caching
cache: {
    type: 'filesystem',
},

Les leçons apprises de la façon difficile

Permettez-moi de vous faire part des erreurs que j’ai commises pendant ce voyage.

Erreur #1: Essayer de regrouper tout en un seul moment

Ma première tentative consistait à détruire toutes les références CDN en un seul go et à essayer de regrouper tout par le biais d’un Webpack.

Erreur #2: Ne comprend pas les types de modules

J’ai gaspillé des heures à essayer de comprendre pourquoi certaines bibliothèques ne seraient pas chargées.require()Modules ) et ESM SK1 (import) sans comprendre comment Webpack les gère, cela conduit à des erreurs cryptiques experiments: { outputModule: true } la configuration n’était't dans ma mise en place initialeM SK1 et je ne pouvais't comprendre pourquoi mes modules n’étaient pas loadésMSC3t

La solution a été trouvée en lisant les numéros de GitHub sur le répertoire Webpack à midi.

Fait erroné #3: Au-dessus -Splitting de code agressif

À un moment donné, ma configuration générait des chunks pour tout ce que j’avais minSize: 10000 (10KBM SK1 ce qui signifiait que Webpack créait des fichiers distincts pour les fonctions d'utilité minces . L'excès- a été corrigé et est passé à MSC4MBMSC5 La charge de la page est devenue une cascade de demandes de petits morceaux ou suspendue en attendant que de gros morceaux soient téléchargésMST7 J'ai appris que le partage du code est bonMTSC8 mais vous avez besoin de seuils raisonnables MTSC9 C'est pourquoi mon configuration actuelle utilise minSize: 20000 (20KBM SK1

Erreur #4: Forgetting About the .mjs Extension

Certains paquets npm distribuent les modules ES6 avec le .mjs extension. Webpack wonM SK1t resolve these by default. J’ai passé un temps embarrassantement long à débuger pourquoi @mostlylucid/mermaid-enhancements Would't load before discovering I needed to add .mjs à resolve.extensions.

Un simple remède une fois que vous le savez, mais trouver ce qu’il fautM SK1 Cela a pris du temps .

Erreur #5: Ne cache pas les constructions de paquets Web

Les premiers travaux de construction ont pris 30-60 secondes parce que je n’avais pas activé le Webpack ' Cache du système de fichiers

Erreur #6: Tailwind Purging Classes I Actually Used

Mon expérience de débugging préférée: Les classes CSS qui fonctionnent bien au cours du développement disparaissent soudainement dans la production . Tailwind les nettoyait parce qu’elles étaient générées dynamiquement en JavaScript. La solution était la suivante : safelist configuration, mais seulement après que j’ai'd perdu des heures en se demandant si je me suis dérangéM SK2 I ALSO (par exemple dans le mostlylucid.pagingtaghelper projet) utiliser M SK1blocs bloqués' où je simplifie mes changements de configuration Tailwind en ayant simplement un bloc caché


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

Lorsque Tailwind construit, il scanne les fichiers cshtml pour les classes; s’ils ne les voient pas' ils les enlèvent . Par conséquent, l’existence de ce bloc caché garantit qu’il voit les classes dont j’ai besoin même si elles sont utilisées seulement dans les vues des bibliothèques embeddeds.


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

}

Comme on l’a mentionné, la méthode 'Tailwind Approved' est d’ajouter les classes à une liste sécuritaire ... mais je suis un ami de ASPMSC5NETM SK6 donc HTML a remporté le championnat.

Ce que j’ai bien compris: Persistence

L’une des choses que j’ai faites correctement était de refuser de renoncer à cette pratique. Chaque message d’erreur a été une occasion d’apprentissage . Chacune des constructions défectueuses m’a appris quelque chose de nouveau sur la façon dont ces outils fonctionnent

Conclusion

La migration des dépendances basées sur le CDN- vers une conduite moderne de regroupement a été transformative pour ce blog , mais elle n’a pas été'sans facilitéM SK3 Les améliorations du rendement sont substantiellesMSC4 l’expérience du développeur est nettement meilleureMST5 et j’ai beaucoup plus de contrôle sur l’optimisation MST6mais il a fallu des mois d’essais et d’erreurs pour arriver iciMst7

Key takeaways:

  1. Le regroupement réduit la charge de travail:arbreM SK1shaking et minification éliminent le code non utilisé
  2. Le partage du code améliore la performance: Les navigateurs ne chargent que ce qui est requis
  3. Créer des outils qui permettent le JavaScript moderne: La compilation Babel supporte les dernières fonctionnalités linguistiques tout en maintenant la compatibilité avec le navigateur
  4. Le pipeline PostCSS optimise le CSS: Tailwind's compiler JIT et cssnano livrent de petites feuilles de style
  5. L'intégration avec .NET est sans fil: Les cibles de MSBuild exploitent automatiquement les builds de frontend
  6. La persévérance bat la perfection: Vous n’avez pas besoin d’être un expert de front-end, mais une vision claire et la volonté de répéter

Pour les développeurs d’ASP.NET Core habitués à un CDN simple, ce qui inclut: , cela sera extrêmement impressionnant à l’origine. C’est le normal de ' . J’ai ressenti la même choseM SK5 Commencer de petite tailleMNK6 migrer une dépendance à chaque foisMRK7 et ne pasMSC8 craindre de détruire des choses au cours du développementMMK9 VousMEK10 apprendrez plus par débugging des édifices défectueux que jamais en lisant la documentationMDK11

La configuration que j’ai partagée ici représente des mois de répétition.

Est-ce que ce blog est trop conçu? Absolutement. J’aurais pu continuer à utiliser les NCD et consacrer mon temps à d’autres choses, mais je ne l’aurais pas fait.

Si vous' restez tributaire des CDN pour vos dépendances de front-end , Je vous encourage à essayer d’établir un paquet. Commençez par une seule bibliothèqueM SK3 Voir comment cela se sentMSC4 Iterativement de làMNK5 L’écosystème s’est considérablement développéMRK6 et alors que la courbe d’apprentissage est réelleMMK7 la rémunération vaut la peine

Autres lectures

logo

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