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
Tuesday, 11 November 2025
Nel corso dell'ultimo anno, ho incappato, sperimentato, rotto le cose, e gradualmente modernizzato il frontend build pipeline per questo blog. Questo non era un qualche masterplan eseguito perfettamente è stato prova ed errore, un sacco di errore, e persistenza ostinata guidata da una chiara visione di ciò che volevo raggiungere.
Non sono un guru frontend o un wizard Webpack. Sono uno sviluppatore .NET che si è frustrato con i limiti delle dipendenze basate su CDN e ha deciso che ci doveva essere un modo migliore. Questo articolo documenta il disordinato, viaggio iterativo da semplice <script> tag che puntano a unpkg ad una moderna pipeline di bundling che funziona davvero.
Ho fatto un sacco di errori lungo la strada (che documenterò), trascorso ore di debug criptico errori Webpack, e probabilmente reinstallato node_modules Ma la perseveranza ha dato i suoi frutti, e ho imparato un'enorme quantita' grazie alla pura determinazione a farlo bene.
Se sei uno sviluppatore .NET che fissa i file di configurazione di Webpack chiedendosi che cosa diavolo avete ottenuto voi stessi in questo articolo è per voi.
I'm a old web performance nutter, in the early days of dial-up the first site I built for money ( that wasn't a porn backend in Perl... a story for another time) was a Snow Conditions site that spun off from a automatic phone line.
In quei giorni le preoccupazioni erano molto diverse. JavaScript era praticamente sconosciuto. Penso che anche i div erano RARE (in quei giorni IE era div e Netscape era layer) e saresti LUCKY se i tuoi utenti avessero una connessione 56K. Così ho imparato ad ottimizzare (anche cose come sfondi di immagini piastrellate per le voci di menu) ecc...
Più tardi a Microsoft ho anche lavorato su uno strumento sprite immagine automatica in moduli Web - è stato fantastico: ha estratto piccole immagini da una pagina, generato CSS e un'immagine sprite automaticamente. Ma ahimè, come il mio tempo a Microsoft, non doveva essere.
In breve, è un'ossessione che ho portato avanti tutta la mia carriera. A nessuno piace un sito lento!
Ad essere onestiI CDN non sono intrinsecamente malvagi.
Divulgazione completa: Questo blog è volutamente esagerato. necessità Tutta questa complessità. Ma costruire in questo modo mi permette di imparare gli strumenti moderni del frontend correttamente e, crucialemente, mi ha dato qualcosa di reale da scrivere e insegnare. A volte il modo migliore per imparare è quello di costruire qualcosa di leggermente ridicolo e documentare il viaggio.
Inizialmente, il mio approccio era semplice:
<!-- 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">
Mentre questo funziona, ha diversi svantaggi che sono diventati sempre più frustranti:
Quindi volevo controllare... controllare esattamente quello che il mio sito ha usato e NEEDED per funzionare. Volevo impacchettare solo quello che mi serviva, garantire un ordine di carico affidabile e ottimizzare le prestazioni. Ciò significava allontanarmi dai CDN e verso un approccio bundling.
Sono più semplici da configurare, e con il multiplexing HTTP/2 e HTTP/3, l'overhead di richiesta multipla è molto meno critico di quanto non lo fosse una volta. Per molti progetti, soprattutto quelli piccoli o prototipi, i CDN rimangono una scelta perfettamente ragionevole. Ma per un sito di produzione dove volevo controllo, affidabilità e ottimizzazione, il bundling ha avuto più senso.
I miei pacchetti di configurazione corrente tutte le dipendenze JavaScript attraverso Webpack e processi CSS attraverso PostCSS.
Prima di tuffarmi nei dettagli tecnici, dovrei rivolgermi all'elefante nella stanza: perché sto usando Webpack quando esistono Vite, Rollup, esbuild e altri bundler moderni?
La risposta onesta è semplice: Conoscevo già Webpack..
Avevo ampiamente utilizzato Webpack quando insegnavo il mio corso "Beginning Web Development," ed era lo strumento che avevo capito. Quando ho deciso di modernizzare la build pipeline di questo blog, ero già di fronte a una ripida curva di apprendimento che comprende tree-shaking, divisione del codice, sistemi di modulo, tubazioni PostCSS, e come integrare tutto questo con ASP.NET Core.
È generalmente una buona pratica quando si lavora su progetti; limitare il 'nuovo' al 'gestibile'. È più facile limitare l'incertezza.
L'aggiunta di "imparare uno strumento di build completamente nuovo" in cima a quello sembrava inutile. Webpack funziona. E 'maturo. Ha una documentazione eccellente e un enorme ecosistema. Soprattutto, ho potuto concentrare la mia energia di apprendimento sul concetti di bundling moderno piuttosto che le idiosincrasie di un particolare strumento.
Vite e' piu' veloce? Assolutamente. Il server di sviluppo di Vite con moduli ES nativi ed esbuild-powered bundling è significativamente più veloce di Webpack. Per i grandi progetti con centinaia di moduli, la differenza è drammatica.
Dovresti usare Vite per un nuovo progetto? Probabilmente, sì. Se stai iniziando da zero e non hai conoscenze esistenti di Webpack, Vite è probabilmente la scelta migliore. È più veloce, più semplice da configurare, e rappresenta l'approccio moderno al frontend tooling.
Mi pento di aver usato Webpack? Niente affatto. Mi ha portato dove dovevo essere. Le lezioni che ho imparato su bundling, divisione del codice, e ottimizzazione sono trasferibili a qualsiasi strumento di build. E onestamente, per la scala di questo blog, la differenza di prestazioni tra Webpack e Vite è trascurabile, stiamo parlando di millisecondi nello sviluppo ricostruire i tempi.
E' un aspetto chiave di come costruisco le cose; inizia con quello che è EASY e costruisci da quella fondazione.
La lezione più ampia qui è che progresso batte la perfezione. Avrei potuto trascorrere settimane a cercare il bundler "migliore," a confrontare i benchmark, a leggere articoli di confronto, e agonizzante sulla decisione. Invece, ho scelto lo strumento che conoscevo, ha funzionato, e andato avanti. Quel pragmatismo mi ha tenuto la spedizione piuttosto che all'infinito deliberante.
Forse un giorno potrei migrare a Vite. Forse non lo farò. In ogni caso, questo blog ha un moderno, ottimizzato build pipeline che funziona in modo affidabile e questo è ciò che conta.
La package.json Ora mantiene due gruppi di dipendenza distinti. Questa struttura mi ha preso diversi tentativi di ottenere a destra inizialmente, Nuget e npm sono simili KINDA, ma nppm è molto meno amichevole quando l'aggiunta di 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"
}
}
Dipendenze sono le librerie necessarie al runtime (Alpine.js, HTMX, EasyMDE, ecc.), mentre devDependenze sono strumenti di compilazione (Webpack, Babel, processori PostCSS, ecc.).
La package.json Gli script si sono evoluti in modo significativo:
{
"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"
}
}
Questa impostazione si distingue per i seguenti aspetti:
rimrafLa npm-run-all pacchetto consente l'esecuzione parallela per build più veloci.
Puoi impostare npm run build per eseguire automaticamente durante la costruzione locale, ma è un po 'di un dolore per CI (è necessario assicurarsi che sia disabilitato ecc).
<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>
Per farlo basta aggiungere questo al tuo csproj... Puoi anche dire che esegui solo durante il debug' etc...ma lo trovo disordinato. Preferisco semplicemente eseguirlo manualmente. npm run watch fa proprio questo, quando qualsiasi file css / js cambia esso auto-eseguire la generazione.
NOTA: Nel mondo JS quasi utilizzano hot-reload dev funziona che è abbastanza slick e ti fa un po 'odio ASP.NET Core debole tentativo.
La configurazione Webpack è dove avviene la magia e dove ho trascorso la maggior parte del mio tempo a risolvere i problemi. Questo non è venuto all'esistenza completamente formata. È il risultato di innumerevoli iterazioni, ricerche Stack Overflow, e la lettura attraverso la documentazione Webpack alle 2 del mattino cercando di capire perché la mia build stava generando 47 file di chunk.
Ecco la configurazione corrente (da webpack.config.js) con spiegazioni dettagliate di ciò che ogni parte fa e perché è 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,
}
};
};
optimization: {
splitChunks: {
chunks: 'all',
minSize: 20000,
maxSize: 100000,
name: false,
},
runtimeChunk: {
name: 'runtime',
},
}
Questa configurazione divide automaticamente il codice in pezzi più piccoli:
In pratica, questo genera più file in wwwroot/js/dist/:
main.js - Il punto d'ingresso della domandaruntime.js - Logica di caricamento del modulo Webpack[vendor].[contenthash].js - Dividi automaticamente i pezzi del venditore{
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 traspiala JavaScript moderno per supportare i browser più vecchi:
Questo significa che posso scrivere JavaScript moderno (async/await, incatenamento opzionale, coalescenza nulla) pur mantenendo ampio supporto del browser.
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 riduce aggressivamente le costruzioni di produzione:
console.log() dichiarazioniSu questo blog, questo riduce tipicamente le dimensioni dei pacchetti JavaScript del 60-70% rispetto al codice non minificato.
CSS Tailwind viene elaborato tramite PostCSS con diversi plugin. Questa configurazione (da postcss.config.js) è misericordiosamente semplice rispetto a Webpack:
// postcss.config.js
module.exports = {
plugins: {
'postcss-import': {},
tailwindcss: {},
autoprefixer: {},
cssnano: { preset: 'default' }
}
}
Il gasdotto funziona come segue:
@import istruzioni nei file CSSLa tailwind.config.js file specifica dove Tailwind dovrebbe cercare i nomi della classe. Ottenere il content i percorsi a destra erano critici perdono un percorso e Tailwind non genera classi per quei file:
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"),
],
};
Caratteristiche principali:
.cshtml file per i nomi di classe (comprese le viste Razor e i modelli di posta elettronica)Tailwind esegue la scansione di questi file al tempo di compilazione, estraendo solo le classi utility che si effettivamente utilizzare. Ecco perché il file CSS finale è molto più piccolo della libreria completa Tailwind.
La main.js file è dove tutto si unisce. Questo file importa e inizializza tutte le dipendenze, ed è cresciuto organicamente come ho aggiunto funzionalità al 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();
Questo approccio offre diversi vantaggi:
windowDentro _Layout.cshtml, mi riferisco ora solo alle attività in bundle:
<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>
Nota da specificare module per far sapere al browser che tipo di file JS è & asp-append-version un piccolo aiutante di tag ASP.NET pulito che aggiunge una versione hashed del contenuto del file alla querystring; efficacemente cache-busting quando il file cambia.
Le sole dipendenze CDN rimanenti sono:
Ecco come l'intero processo di compilazione fluisce dai file sorgenti alle risorse di produzione:
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
Sembra piuttosto complesso, ma in realtà questo è ciò che WebPack fa, gestisce la maggior parte di questo stesso (in un modo complicato che i tipi di Vite non hanno bisogno, ma... )
Ancora una volta, non proprio il PUNTO. Ma si carica come un proiettile assoluto ora. È ancora possibile ottenere problemi con il 'Rocket Loader' di Cloudflare (che strangola in qualche modo gli eventi di carico della pagina) ma è TIGHT.
Oltre alle prestazioni, la moderna pipeline di costruzione migliora significativamente il flusso di lavoro di sviluppo:
Bundling attraverso Webpack consente una corretta risoluzione del modulo JavaScript, il che significa:
QUANDO la costruzione si rompe (con npm run watch) So INSTANTLY, combinato con i (pochi ma in crescita) test unità JS significa loop di feedback più stretti.
Durante lo sviluppo, npm run watch consente un feedback quasi istantaneo:
npm run watch
Questo funziona Webpack in modalità orologio, la ricostruzione ha cambiato solo i moduli:
File di lock npm (package-lock.json) assicura build coerenti in tutti gli ambienti:
npm ci # Clean install from lock file
Ciò garantisce le stesse versioni di dipendenza negli ambienti di sviluppo, CI/CD e produzione.
Questi sono indicati come 'pinning' nel mondo JS, dove le dipendenze sono impostate in pietra. Si può fare a meno di un blocco pacchetto, ma poi si sta a seconda degli autori pacakge; spesso HUNDREDS di diverse tpeople non rovinare qualche rilascio minore...
Gli script npm organizzati semplificano le attività comuni:
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
Alcune librerie hanno bisogno di un'esposizione globale per l'uso nelle viste Razor o negli script in linea:
// Make library available globally
import Alpine from 'alpinejs';
window.Alpine = Alpine;
Alpine.start();
Poi in .cshtml file:
<div x-data="{ open: false }">
<!-- Alpine.js works because it's on window -->
</div>
Alcune librerie includono il CSS che deve essere importato:
import EasyMDE from "easymde";
import 'easymde/dist/easymde.min.css'; // Imported CSS is processed by Webpack
Webpack css-loader e style-loader gestire queste importazioni:
<style> tag o file CSS separatiPer le librerie necessarie solo su pagine specifiche, utilizzare importazioni dinamiche:
// 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 suddivide automaticamente le importazioni dinamiche in pezzi separati, caricati su richiesta.
Alcune librerie più vecchie usano CommonJS invece dei moduli ES6:
// CommonJS require syntax
const hljs = require('highlight.js');
// Or use dynamic import
import('highlight.js').then(hljs => {
// Use hljs
});
Webpack gestisce entrambi i sistemi modulo in modo trasparente, convertendo CommonJS in ES6 ove necessario.
Assicurare devtool è configurato in webpack.config.js:
devtool: isProduction ? false : 'eval-source-map',
Questo genera mappe sorgente in sviluppo per un debug più facile.
Se Tailwind rimuove le classi che stai usando, controlla la content configurazione:
// tailwind.config.js
module.exports = {
content: [
'./Views/**/*.cshtml',
'./Components/**/*.cshtml',
// Add any other paths where classes are used
],
}
In alternativa, utilizzare il safelist per le classi dinamiche:
safelist: [
'bg-blue-500',
'text-red-600',
{
pattern: /bg-(red|green|blue)-(400|500|600)/,
}
]
Se Webpack non riesce a risolvere un modulo, controllare:
./ oppure ../.mjs a resolve.extensions se necessarioresolve.alias per percorsi complessiresolve: {
extensions: ['.js', '.mjs', '.json'],
alias: {
'@components': path.resolve(__dirname, 'src/js/components/'),
}
}
Se i build diventano lenti:
thread-loader per la traspilazione multifilata// Enable Webpack caching
cache: {
type: 'filesystem',
},
Lasciatemi condividere alcuni degli errori che ho commesso durante questo viaggio, in modo da poterli evitare:
Il mio primo tentativo ha coinvolto strappare tutti i riferimenti CDN in una sola volta e cercando di impacchettare tutto attraverso Webpack. La costruzione si è rotta in modo spettacolare. Ho imparato che la migrazione incrementale è il vostro amico Spostare una libreria alla volta, testare accuratamente, poi passare al successivo.
Ho sprecato ore cercando di capire perché alcune librerie non avrebbero caricato. A quanto pare, mescolando CommonJS (require()) e moduli ES6 (import) senza capire come Webpack li gestisce porta ad errori criptici. experiments: { outputModule: true } la configurazione non era nella mia configurazione iniziale, e non riuscivo a capire perché i miei moduli non fossero caricati.
La soluzione è venuta dalla lettura attraverso i problemi di GitHub sul repository Webpack a mezzanotte.
Ad un certo punto, la mia configurazione stava generando pezzi per tutto. minSize: 10000 (10KB) che significava Webpack stava creando file separati per piccole funzioni di utilità. Il sovracorretto ed è andato a 2MB... Il carico di pagina è diventato una cascata di 50+ piccole richieste di pezzi o appeso in attesa di enormi pezzi da scaricare. Ho imparato che la divisione del codice è buona, ma avete bisogno di soglie ragionevoli. Ecco perché la mia configurazione attuale utilizza minSize: 20000 (20KB).
.mjs EstensioneAlcuni pacchetti npm distribuiscono moduli ES6 con il .mjs estensione. Webpack non risolverli per impostazione predefinita. Ho trascorso un tempo imbarazzante debugging perché @mostlring I needed to add .mjsCity name (optional, probably does not need a translation)tosolution.extensions [56].
Semplice soluzione una volta che lo sai, ma scoprirlo?
I build iniziali hanno richiesto 30-60 secondi perché non avevo abilitato la cache del filesystem di Webpack. Una volta aggiunto il caching, i tempi di ricostruzione sono scesi a 2-3 secondi. Questo è stato un cambio di gioco per il flusso di lavoro di sviluppo, ma mi ci sono volute settimane per scoprire.
La mia esperienza di debug preferita: le classi CSS che hanno funzionato bene nello sviluppo improvvisamente sono scomparse nella produzione. Tailwind li stava purgando perché sono stati generati dinamicamente in JavaScript. La soluzione era la safelist configurazione, ma solo dopo aver sprecato ore chiedendomi se stavo impazzendo.
I ANCHE (per esempio nella mostlylucid.pagingtaghelper project) usa i 'blocchi dummy' dove semplifico i cambiamenti di configurazione Tailwind semplicemente avendo un blocco nascosto.
<!--
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>
Quando Tailwind costruisce scansiona i file cshtml per le classi; se non li vede li rimuove. Quindi, avendo questo blocco nascosto assicura che vede le classi di cui ho bisogno anche se sono utilizzati solo nelle viste librerie embedded. Gli sviluppatori JS comunemente anche la scansione di file JS / TS per consentire l'utilizzo di stili CSS che Tailwind altrimenti perderebbe.
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: {...}
}
Come detto, l'approccio 'Approvato al vento' è quello di aggiungere le classi a una safelist... ma io sono un tipo ASP.NET, quindi HTML ha vinto.
L'unica cosa che ho fatto bene era rifiutare di rinunciare. Ogni messaggio di errore era un'opportunità di apprendimento. Ogni build rotto mi ha insegnato qualcosa di nuovo su come funzionano questi strumenti. Avevo una visione chiara in bundle, asset ottimizzati che caricano veloce e ho continuato a iterare fino a quando non ci sono arrivato.
Migrare da dipendenze basate su CDN a una moderna pipeline di bundling è stato trasformativo per questo blog, ma non è stato facile. I miglioramenti delle prestazioni sono sostanziali, l'esperienza degli sviluppatori è significativamente migliore, e ho un controllo molto più grande sull'ottimizzazione... ma ci sono voluti mesi di tentativi ed errori per arrivare qui.
Accompagnamenti chiave:
Per ASP.NET Core sviluppatori abituati a semplice CDN include, questo si sentirà schiacciante in un primo momento. Questo è normale. Ho sentito lo stesso modo. Avviare piccolo, migrare una dipendenza alla volta, e non avere paura di rompere le cose in sviluppo. Imparerai di più dal debug build rotti che mai dalla lettura della documentazione.
La configurazione che ho condiviso qui rappresenta mesi di iterazione. Il vostro viaggio sarà diverso, e va bene. Usate questo come punto di partenza, non come vangelo. Adattatelo alle vostre esigenze, sperimentate, e non scoraggiatevi dai fallimenti che fanno parte del processo.
Questo blog e' troppo ingegnerizzato? Assolutamente. Avrei potuto continuare a usare i CDN e passare il mio tempo su altre cose? Certo. Ma non avrei imparato la metà di tanto, e non avrei questo articolo da condividere con voi. A volte l'ingegnerizzazione eccessiva non è circa la destinazione si tratta di ciò che si impara lungo la strada e di essere in grado di insegnare gli altri da quell'esperienza.
Se fai ancora affidamento sui CDN per le tue dipendenze sul frontend, ti incoraggio a provare a combinare. Inizia con una libreria. Vedi come ci si sente. Iterate da lì. L'ecosistema è maturato in modo significativo, e mentre la curva di apprendimento è reale, la vincita ne vale la pena.
© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.