Modernisation de votre pipeline de construction de frontend: des CDNs au bundling (Français (French))

Modernisation de votre pipeline de construction de frontend: des CDNs au bundling

Tuesday, 11 November 2025

//

32 minute read

Présentation

Au cours de la dernière année, j'ai trébuché, expérimenté, cassé des choses, et progressivement modernisé le pipeline de construction de frontend pour ce blog. Ce n'était pas un plan directeur exécuté sans faille – c'était un essai et une erreur, beaucoup d'erreur, et la persistance obstinée animée par une vision claire de ce que je voulais réaliser.

Je ne suis pas un gourou frontal ou un assistant Webpack. Je suis un développeur .NET qui a été frustré par les limites des dépendances basées sur CDN et a décidé qu'il devait y avoir une meilleure façon. Cet article documente le voyage messy, itérative de simple <script> tags pointant vers dépkg vers un pipeline de groupement moderne qui fonctionne réellement.

J'ai fait beaucoup d'erreurs le long du chemin (que je vais documenter), passé des heures à déboger les erreurs cryptographiques de Webpack, et probablement réinstallé node_modules Plus de fois que je ne veux l'admettre, mais la persévérance a payé, et j'ai appris une somme énorme par la volonté pure d'obtenir ce droit.

Si vous êtes un développeur de .NET regardant les fichiers de configuration Webpack se demandant dans quoi sur terre vous vous êtes mis dans – cet article est pour vous.

Je suis un vieux nutter de performance web, dans les premiers jours de la mise en ligne le premier site que j'ai construit pour l'argent (ce n'était pas un backend porno dans Perl... une histoire pour une autre fois) était un site de conditions de neige qui a jailli d'une ligne téléphonique automatisée.

À l'époque, les préoccupations étaient tout à fait différentes. JavaScript était pratiquement inconnu. Je pense que même les divs étaient RARE (à l'époque IE était div et Netscape était layer) et vous seriez LUCKY si vos utilisateurs avaient une connexion 56K. Donc j'ai appris à optimiser (même des choses comme les arrière-plans d'images carrelés pour les éléments de menu) etc...

Plus tard chez Microsoft, j'ai même travaillé sur un outil automatisé de sprite d'image dans Web Forms - c'était génial: il a extrait de petites images d'une page, généré CSS et une image sprite automatiquement. Mais hélas, comme mon temps chez Microsoft, il ne devait pas être.

Bref, c'est une obsession que j'ai porté toute ma carrière. Personne n'aime un site lent !

Pour être justeLes CDN ne sont pas intrinsèquement mauvais.

Divulgation complète: Ce blog est délibérément surexploité. besoin Mais la construction de cette façon me permet d'apprendre l'outillage frontal moderne correctement et, d'une manière cruciale, m'a donné quelque chose de réel à écrire et à enseigner. Parfois, la meilleure façon d'apprendre est de construire quelque chose de légèrement ridicule et documenter le voyage.

Le problème avec les CDN

Au début, mon approche é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 inconvénients qui sont devenus de plus en plus frustrants:

  • Questions de fiabilitéJ'ai vu unpkg et jsdelivr ont des pannes. Quand ils sont en bas, votre site est cassé
  • Comportement de chargement imprévisible: Les réponses CDN peuvent varier sauvagement. Parfois rapides, parfois lents, parfois ils introduisent des conditions de course où les bibliothèques chargent dans le mauvais ordre
  • Questions relatives au calendrier: Vous n'avez aucun contrôle sur le chargement des scripts par rapport à l'autre. Cela m'a causé des maux de tête sans fin avec l'initialisation de la bibliothèque
  • Plusieurs requêtes HTTP: Chaque bibliothèque nécessite une requête séparée, même avec HTTP/2
  • Pas de tremblement d'arbre: Vous téléchargez toute la bibliothèque, même si vous n'en utilisez qu'une fraction
  • Dérive de la version: Les CDN peuvent servir des versions différentes à moins que vous ne les épinglez explicitement, et même les versions épinglées peuvent se comporter différemment entre les fournisseurs de CDN
  • Incertitude relative au temps de construction: Pas moyen d'attraper les incompatibilités jusqu'à l'exécution – souvent quand un utilisateur le rapporte
  • Optimisation limitée: Ne peut pas réduire les limites ou supprimer le code mort
  • Développement hors ligne: Nécessite une connectivité Internet, ce qui est gênant lorsque vous travaillez sur des trains ou des avions

Donc je voulais contrôler... contrôler exactement ce que mon site a utilisé et NEEDED pour travailler. Je voulais regrouper seulement ce dont j'avais besoin, assurer un ordre de chargement fiable, et optimiser pour les performances. Cela signifiait s'éloigner des CDN et vers une approche de groupement.

Ils sont plus simples à configurer, et avec le multiplexage HTTP/2 et HTTP/3, les frais généraux de requêtes multiples sont beaucoup moins critiques qu'auparavant. Pour de nombreux projets, en particulier les petits ou les prototypes, les CDN restent un choix parfaitement raisonnable. Mais pour un site de production où je voulais le contrôle, la fiabilité et l'optimisation, le regroupement était plus logique.

L'approche moderne du regroupement

Ma configuration actuelle regroupe toutes les dépendances JavaScript via Webpack et traite CSS via PostCSS.

Pourquoi Webpack au lieu de Vite?

Avant de plonger dans les détails techniques, je devrais m'adresser à l'éléphant dans la pièce: pourquoi suis-je en utilisant Webpack lorsque Vite, Rollup, esbuild, et d'autres faisceaux modernes existent?

La réponse honnête est simple: J'ai déjà connu Webpack.

J'avais beaucoup utilisé Webpack lors de l'enseignement de mon cours "Début du développement Web", et c'était l'outil que j'ai compris. Quand j'ai décidé de moderniser le pipeline de construction de ce blog, j'étais déjà confronté à une courbe d'apprentissage raide – comprendre le façonnage des arbres, le fractionnement des codes, les systèmes de modules, les pipelines PostCSS, et comment intégrer tout cela avec ASP.NET Core.

C'est généralement une bonne pratique lorsqu'on travaille sur des projets ; limiter le « nouveau » au « maniable ». Il est plus facile de limiter l'incertitude.

Ajouter "apprendre un outil de construction entièrement nouveau" en plus de cela semblait inutile. Webpack fonctionne. Il est mature. Il a une excellente documentation et un écosystème massif. Plus important, je pourrais concentrer mon énergie d'apprentissage sur le concepts d'un groupement moderne plutôt que les idiosyncrasies d'un outil particulier.

Vite est-il plus rapide ? Tout à fait. Le serveur de développement de Vite avec des modules ES natifs et un groupement alimenté par esbuild est significativement plus rapide que Webpack. Pour les grands projets avec des centaines de modules, la différence est dramatique.

Devriez-vous utiliser Vite pour un nouveau projet ? Probablement, oui. Si vous commencez frais et n'avez pas de connaissances Webpack existantes, Vite est probablement le meilleur choix. C'est plus rapide, plus simple à configurer, et représente l'approche moderne de l'outillage frontend.

Est-ce que je regrette d'utiliser Webpack? Les leçons que j'ai apprises sur le regroupement, le fractionnement de code et l'optimisation sont transférables à n'importe quel outil de construction. Et honnêtement, pour l'échelle de ce blog, la différence de performance entre Webpack et Vite est négligeable – nous parlons de millisecondes dans les temps de reconstruction du développement.

C'est un aspect clé de la façon dont je construis des choses; commencer par ce qui est EASY et construire à partir de cette fondation.

La leçon la plus large ici est que le progrès bat la perfection. J'aurais pu passer des semaines à chercher le "meilleur" bundler, à comparer les points de repère, à lire des articles de comparaison, et à m'agoniser sur la décision. Au lieu de cela, j'ai choisi l'outil que je connaissais, je l'ai fait fonctionner, et j'ai avancé. Ce pragmatisme m'a permis d'expédier plutôt que de délibérer sans fin.

Peut-être qu'un jour je pourrais migrer vers Vite. Peut-être que je ne le ferai pas. De toute façon, ce blog a un pipeline de construction moderne et optimisé qui fonctionne de façon fiable – et c'est ce qui compte.

Structure de l'emballage

Les package.json maintenant maintient deux groupes de dépendance distincts. Cette structure m'a pris plusieurs tentatives pour obtenir à droite - initialement, Nuget et npm sont similaires KINDA mais nppm est beaucoup moins sympathique lors de l'ajout de 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 à l'exécution (Alpine.js, HTMX, EasyMDE, etc.), tandis que devDépendances sont des outils de construction (Webpack, Babel, processeurs PostCSS, etc.).

Construire des scripts

Les package.json Les scripts ont considérablement é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 questions suivantes:

  • Nettoyer: Supprime les sorties de build antérieures en utilisant rimraf
  • copie:* tâches: Copie des fichiers CSS statiques qui n'ont pas besoin de traitement (ce sont soit des 'importations') dans un but spécial comme le chargement de ma police funky Raleway ou adaptative comme les améliorations de commutation de thème.
  • C'est-à-dire:* tâches: Processus Tailywind CSS à travers PostCSS
  • JS:* tâches: Bundles JavaScript via Webpack
  • dev/watch/build: Ordonne l'ensemble du pipeline

Les npm-run-all paquet permet l'exécution parallèle pour des constructions plus rapides.

Vous pouvez définir npm run build pour courir automatiquement pendant votre construction locale, mais c'est un peu une douleur pour l'IC (vous devez vous assurer qu'il est désactivé 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 d'ajouter ceci à votre csproj...Vous pouvez même dire que n'exécuter que pendant debug' etc... mais je le trouve désordonné. Je préfère simplement l'exécuter manuellement. npm run watch fait juste cela, quand n'importe quel fichier css / js change il lance automatiquement la build.

REMARQUE: Dans le monde JS ils utilisent presque dev runs hot-reload qui est assez slick et vous fait plutôt détester la tentative faible d'ASP.NET Core.

Configuration du paquet Web Plongée profonde

La configuration du Webpack est l'endroit où la magie se produit, et où j'ai passé la plupart de mon temps à dépanner. Cela n'est pas venu à l'existence complètement formée. C'est le résultat d'innombrables itérations, recherches Stack Overflow, et la lecture à travers la documentation Webpack à 2h du matin essayant de comprendre pourquoi ma construction générait 47 fichiers morceaux.

Voici la configuration actuelle (à partir de webpack.config.js) avec des explications détaillées sur ce que chaque partie fait et pourquoi elle est là:

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 clés de configuration expliquées

Séparer et découper des codes

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

Cette configuration divise automatiquement votre code en petits morceaux :

  • en morceaux: 'tous': Analyse des importations synchrones et asynchrones
  • minTaille: 20000: Ne crée que des morceaux pour des modules de plus de 20Ko
  • Taille max: 100000: Tentatives de fractionner des morceaux de plus de 100Ko
  • runtimeChunk: Extraire le temps d'exécution du Webpack dans un fichier séparé pour améliorer la mise en 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 - Couper automatiquement les morceaux des fournisseurs
  • Des morceaux supplémentaires pour les routes fractionnées de code ou les modules chargés paresseux

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 transpiles moderne JavaScript to support older browsers:

  • cibles: Prend en charge les navigateurs avec >0,25% de part de marché qui sont toujours maintenues
  • modules: faux: Préserve les modules ES6 pour l'arborescence de Webpack
  • UtilisationBuiltIns: 'usage': Inclut automatiquement les polyfills uniquement pour les fonctionnalités que vous utilisez
  • corejs: 3: Utilise la version 3 de core-js pour les polyfills

Cela signifie que je peux écrire le JavaScript moderne (async/attendu, enchaînement optionnel, fusion nullish) tout en maintenant un large support de navigateur.

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 réduit agressivement les constructions de production :

  • drop_console: Enlève tout console.log() Déclarations
  • pass: 3: Exécute la compression trois fois pour une réduction de taille maximale
  • haut niveau: true: Noms de variables de haut niveau Mangles
  • _funcs purs: Supprime les méthodes de console spécifiques même si elles sont assignées à des variables
  • commentaires: faux: Strips tous les commentaires à partir de la sortie

Sur ce blog, cela réduit généralement les tailles de paquets JavaScript de 60-70% par rapport au code non-minifié.

Pipeline PostCSS pour Tailwind

Tailwind CSS est traité par PostCSS avec plusieurs plugins. postcss.config.js) est miséricordieuxment simple par rapport à Webpack:

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

Le pipeline fonctionne comme suit:

  1. postcss-import: Décide @import les déclarations dans les fichiers CSS
  2. Fûts arrière: Processus Directives Tailwind et génère des classes d'utilité
  3. autopréfixeur: Ajoute des préfixes du fournisseur pour la compatibilité du navigateur
  4. cssnano: Minimise la sortie CSS finale

Configuration du vent de queue

Les tailwind.config.js fichier spécifie où Tailwind devrait chercher les noms de classe. content chemins droit était critique — manquer un chemin et Tailwind ne générera pas de classes pour ces fichiers:

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: Numérisations .cshtml fichiers pour les noms de classe (y compris les vues Razor et les modèles de courriel)
  • liste de sécurité: Inclut toujours ces classes même si elles ne sont pas trouvées dans les scans
  • darkMode: "classe": Active la commutation en mode sombre basée sur la classe
  • thème.extend: Ajoute des couleurs personnalisées et des valeurs d'espacement
  • plugins: Comprend les plugins Tailwind et la bibliothèque de composants 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 Tailwind.

Point d'entrée JavaScript

Les main.js Ce fichier importe et initialise toutes les dépendances, et il est cultivé organiquement comme j'ai ajouté des fonctionnalités au 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();

Cette approche présente plusieurs avantages :

  1. Point d'importation unique: Toutes les dépendances chargées dans un seul fichier d'entrée
  2. Versions explicites: Paquet.json verrouille des versions spécifiques
  3. Rasoir d'arbres: Webpack supprime le code inutilisé des bibliothèques
  4. Exposition au niveau mondial: Bibliothèques disponibles pour les vues sur Razor via window
  5. Modules personnalisés: Code spécifique à l'application organisé en modules importables

Intégration du modèle de mise en page

Dans _Layout.cshtml, je ne fais maintenant référence qu'aux actifs groupés:

<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 : Préciser module pour que le navigateur sache quel type de fichier JS c'est & asp-append-version un petit helper de balise ASP.NET qui ajoute une version hashed du contenu du fichier à la requête; efficacement cache-busting lorsque le fichier change.

Les seules dépendances restantes du RNC sont :

  • Connexion Google: Nécessite un CDN externe pour la fonctionnalité OAuth
  • Boxicons: Police d'icônes (pourrait être groupée, mais avec un minimum d'avantages)
  • Umami Analytics: Scénario d'analyse de tiers - auto hébergé pour que je puisse voir votre visite pas Google ??

Construisez la visualisation des pipelines

Voici comment l'ensemble du processus de construction passe des fichiers sources 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 vraiment c'est ce que WebPack fait, il gère la plupart de ceci lui-même (d'une manière compliquée que les semblables de Vite n'ont pas besoin mais.. )

Améliorations de la performance

Encore une fois, pas vraiment le POINT. Mais il ne charge comme une balle absolue maintenant. Vous pouvez toujours obtenir des problèmes avec le 'Rocket Loader' de Cloudflare (qui manipule les événements de chargement de page un peu) mais c'est TIGHT.

Avantages de l'expérience du développeur

Au-delà de la performance, le pipeline de construction moderne améliore considérablement le flux de travail de développement :

Type Sécurité et IntelliSense

Le bundling via Webpack permet une résolution correcte du module JavaScript, ce qui signifie :

  • IntelliSense dans le code VS: Complétion automatique pour les modules importés
  • Aller à la définition: Naviguez directement vers le code source de la bibliothèque
  • Documentation en ligne: Les commentaires des bibliothèques de JSDoc apparaissent dans l'IDE

QUAND la construction se brise (avec npm run watch) Je sais INSTANTLY, combiné avec l'unité (peu mais en croissance) JS teste cela signifie des boucles de rétroaction plus serrées.

Remplacement du module à chaud

En cours de développement, npm run watch permet une rétroaction quasi instantanée:

npm run watch

Ceci exécute Webpack en mode montre, reconstruisant seulement les modules modifiés:

  • Temps de reconstruction typique: 200-400ms
  • Navigateur de rafraîchissement automatique: Utilisation d'un navigateur-sync ou d'un outil similaire
  • État de la demande conservée: HMR maintient l'état de l'application lors des mises à jour (s'il est pris en charge)

Gestion de la dépendance

fichier de verrouillage de npm (package-lock.json) assure des constructions cohérentes entre les environnements:

npm ci  # Clean install from lock file

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

Ils sont appelés «pinning» dans le monde JS, où les dépendances sont mises en pierre. Vous pouvez le faire sans serrure de paquet, mais ensuite vous dépendez des auteurs pacakge; souvent HUNDREDS de personnes différentes qui ne gâchent pas une certaine libération mineure.

Construire des scripts

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

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

Modèles et solutions communs

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 de Razor ou les scripts en ligne:

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

Puis dans .cshtml fichiers & #160;:

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

Manipulation de CSS à partir de modules JavaScript

Certaines bibliothèques comprennent le CSS qui a besoin d'importer :

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

Les Webpacks css-loader et style-loader traiter ces importations:

  • CSS est extrait pendant la construction
  • Injecté dans la page via <style> tags ou fichiers CSS séparés
  • Préfixé et minifié automatiquement

Bibliothèques lourdes de chargement paresseux

Pour les bibliothèques uniquement nécessaires sur des pages spécifiques, utilisez des 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();
}

Webpack envoie automatiquement des code-splits d'importations dynamiques dans des morceaux séparés, chargés à la demande.

Gérer les modules CommonJS

Certaines bibliothèques plus anciennes utilisent CommonJS au lieu des 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 module de manière transparente, convertissant CommonJS en ES6 au besoin.

Dépannage de problèmes communs

Cartes sources manquantes en développement

Veiller à ce que devtool est configuré dans webpack.config.js:

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

Cela génère des cartes sources en développement pour faciliter le débogage.

Problèmes de purge de classe CSS avec Tailwind

Si Tailwind supprime les cours que vous utilisez, vérifiez content configuration & #160;:

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

Alternativement, utiliser le safelist pour les classes dynamiques:

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

Erreurs non trouvées dans le module

Si Webpack ne peut pas résoudre un module, vérifiez :

  1. Voie d'importation correcte: Les chemins relatifs commencent par ./ ou ../
  2. Extensions de fichiers: Ajouter .mjs à l ' Assemblée générale resolve.extensions si nécessaire
  3. Aliases: Utilisation resolve.alias pour les chemins complexes
resolve: {
    extensions: ['.js', '.mjs', '.json'],
    alias: {
        '@components': path.resolve(__dirname, 'src/js/components/'),
    }
}

Créer des problèmes de rendement

Si les constructions deviennent lentes:

  1. Activer la mise en cache: Le cache de Webpack accélère considérablement la reconstruction
  2. Réduire la portée de Babel: Exclure plus de répertoires du traitement Babel
  3. Outils de mise à niveau: Les versions plus récentes de Webpack et Babel sont plus rapides
  4. Constructions parallèles: Utilisation thread-loader pour la transpilation multithreadée
// Enable Webpack caching
cache: {
    type: 'filesystem',
},

Les leçons apprises à la dure voie

Permettez-moi de partager quelques-unes des erreurs que j'ai commises pendant ce voyage, afin que vous puissiez les éviter:

Erreur #1: Essayer de tout regrouper à la fois

Ma première tentative consistait à retirer toutes les références CDN d'un seul coup et à essayer de tout rassembler dans Webpack. La construction s'est effondrée de façon spectaculaire. J'ai appris que la migration progressive est votre ami – déplacer une bibliothèque à la fois, tester soigneusement, puis passer à la suivante.

Erreur #2: Ne pas comprendre les types de modules

J'ai perdu des heures à essayer de comprendre pourquoi certaines bibliothèques ne se chargeraient pas.require()) et les modules ES6 (import) sans comprendre comment Webpack les gère conduit à des erreurs cryptographiques. experiments: { outputModule: true } La configuration n'était pas dans ma configuration initiale, et je n'ai pas réussi à comprendre pourquoi mes modules ne sont pas chargés.

La solution est venue de la lecture à travers les problèmes GitHub sur le dépôt Webpack à minuit.

Erreur no 3 : fractionnement du code trop agressif

À un moment donné, ma configuration générait des morceaux pour tout. minSize: 10000 (10KB) ce qui signifiait que Webpack créait des fichiers séparés pour de petites fonctions d'utilité. Le sur-corrigé et est allé à 2MB... Le chargement de page est devenu une cascade de 50+ petites requêtes de morceaux ou accroché en attendant que d'énormes morceaux à télécharger. J'ai appris que le fractionnement de code est bon, mais vous avez besoin de seuils raisonnables. C'est pourquoi ma config actuelle utilise minSize: 20000 (20Ko).

Erreur #4: Oublier la .mjs Prolongation

Certains paquets npm distribuent des modules ES6 avec .mjs extension. Webpack ne les résoudra pas par défaut. J'ai passé un temps embarrassant à déboger pourquoi @mostlring I needed to add .mjstoRésoudre.extensions'.

Une fois que vous le savez, ça a pris du temps.

Erreur #5: Constructions de Webpack non en cache

Les premières constructions ont pris 30-60 secondes parce que je n'avais pas activé le cache système de fichiers de Webpack. Une fois que j'ai ajouté cache, les temps de reconstruction sont tombés à 2-3 secondes. C'était un changement de jeu pour le flux de travail de développement, mais m'a pris des semaines à découvrir.

Erreur #6: Classes de purge du vent de queue J'ai réellement utilisé

Mon expérience de débogage préférée : les classes CSS qui fonctionnaient bien dans le développement ont soudainement disparu dans la production. Tailwind les purgeait parce qu'elles ont été générées dynamiquement dans JavaScript. La solution était la safelist La configuration, mais seulement après que j'ai gaspillé des heures à me demander si je devenais fou. I ALSO (par exemple dans mostlylucid.pagingtaghelper project) utilisent des 'blocs de dummy' où je simplifie mes changements de configuration Tailwind en ayant juste 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'il ne les voit pas, il les supprime. Donc, avoir ce bloc caché assure qu'il voit les classes dont j'ai besoin même si elles ne sont utilisées que dans les vues de bibliothèques intégrées.


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 mentionné, l'approche « Tailwind Approuvé » est d'ajouter les classes à une liste de sécurité... mais je suis un gars ASP.NET, donc HTML a gagné.

Ce que j'ai bien fait : la persévérance

La seule chose que j'ai bien fait était de refuser d'abandonner. Chaque message d'erreur était une opportunité d'apprentissage. Chaque construction cassée m'a appris quelque chose de nouveau sur le fonctionnement de ces outils.

Le présent règlement entre en vigueur le vingtième jour suivant celui de sa publication au Journal officiel de l'Union européenne.

La migration des dépendances basées sur le CDN vers un pipeline de regroupement moderne a été transformée pour ce blog, mais ce n'était pas facile. Les améliorations de performance sont substantielles, l'expérience du développeur est significativement meilleure, et j'ai beaucoup plus de contrôle sur l'optimisation — mais il a fallu des mois d'essai et d'erreur pour arriver ici.

Prises à emporter clés:

  1. Le bundling réduit la taille de la charge utile: Shaking et minification éliminent le code inutilisé
  2. Le fractionnement du code améliore les performances: Les navigateurs chargent seulement ce qui est nécessaire
  3. Construisez des outils pour activer le JavaScript moderne: Babel transpilation prend en charge les dernières fonctionnalités linguistiques tout en maintenant la compatibilité du navigateur
  4. PostCSS pipeline optimise CSS: Le compilateur JIT de Tailwind et cssnano délivrent de minuscules feuilles de style
  5. L'intégration avec .NET est transparente: Les cibles MSBuild lancent automatiquement les constructions frontend
  6. La persévérance bat la perfection: Vous n'avez pas besoin d'être un expert frontal – vous avez juste besoin d'une vision claire et de la volonté d' itérer

Pour les développeurs ASP.NET Core habitués au simple CDN comprend, cela se sentira accablant au début. C'est normal. Je me suis senti de la même manière. Commencez petit, migrez une dépendance à la fois, et n'ayez pas peur de casser les choses dans le développement. Vous apprendrez plus de débogage des constructions cassées que vous ne le ferez jamais de la documentation de lecture.

La configuration que j'ai partagée ici représente des mois d'itération. Votre voyage sera différent, et c'est bien. Utilisez ceci comme point de départ, pas comme évangile. Adaptez-le à vos besoins, expérimenter, et ne soyez pas découragé par les échecs – ils font partie du processus.

Ce blog est-il surexploité ? Absolument. Aurais-je pu continuer à utiliser les CDN et passer mon temps sur d'autres choses? Bien sûr. Mais je n'aurais pas appris la moitié autant, et je n'aurais pas eu cet article à partager avec vous. Parfois, la suringénierie n'est pas à propos de la destination — c'est à propos de ce que vous apprenez le long du chemin et être en mesure d'enseigner aux autres de cette expérience.

Si vous comptez toujours sur les CDN pour vos dépendances frontales, je vous encourage à donner un coup d'essai. Commencez par une bibliothèque. Voyez comment ça se sent. Itérer à partir de là. L'écosystème a mûri de façon significative, et bien que la courbe d'apprentissage soit réelle, le gain en vaut la peine.

Lecture supplémentaire

Finding related posts...
logo

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