# Frontend Building -putken modernisointi: CDN:stä niputukseen

<datetime class="hidden">2025-11-11T02:15</datetime>

<!-- category -- ASP.NET, Tailwind, Webpack, Frontend -->
## Johdanto

Viimeisen vuoden aikana olen kompuroinut, kokeillut, rikkonut asioita ja vähitellen modernisoinut tämän blogin etuosan rakennusputkea. Tämä ei ollut mikään mestarisuunnitelma, joka toteutettiin virheettömästi – se oli yritys ja virhe, paljon virheitä ja itsepäistä sinnikkyyttä, jonka taustalla oli selkeä visio siitä, mitä halusin saavuttaa.

En ole etunojaguru enkä Webpack-velho. Olen .NET-kehittäjä, joka turhautui CDN-pohjaisten riippuvuussuhteiden rajoitteisiin ja päätti, että on oltava parempi tapa. Tässä artikkelissa dokumentoidaan sotkuista, iteratiivista matkaa yksinkertaisesta `<script>` Tagit, jotka osoittavat, että unpkg on nykyaikainen niputusputki, joka todella toimii.

Tein matkan varrella paljon virheitä (jotka dokumentoin), käytin tunteja salamyhkäisten Webpack-virheiden vianetsintään ja luultavasti asensin ne uudelleen `node_modules` Mutta sinnikkyys kannatti, ja opin valtavasti pelkän päättäväisyyden kautta.

Jos olet .NET-kehittäjä, joka tuijottaa Webpack-asetustiedostoja, ihmettelet, mihin olet joutunut - tämä artikkeli on sinua varten.

Olen vanha nettiperformanssin kahjo, ja alkuaikoina, kun valitsin ensimmäisen sivuston, jonka rakensin rahasta (se ei ollut pornon taustaosa Perlissä...juttu toiseen kertaan), se oli Lumiolosuhteet-sivusto, joka kehrättiin pois automaattisesta puhelinlinjasta.

Siihen aikaan huoli oli aivan toinen. JavaScript oli käytännössä tuntematon. Uskon, että jopa divs oli RARE (niinä päivinä IE oli `div` Ja Netscape oli `layer`) ja olisit onnekas, jos käyttäjilläsi olisi 56K-yhteys. Niinpä opin optimoimaan (jopa kaapeloidut kuvataustat valikkoesineille) jne....

Myöhemmin Microsoftilla työskentelin jopa Web Forms -sivuston automaattisen kuvan sprite-työkalun parissa - se oli mahtava: se keräsi pieniä kuvia sivulta, loi CSS:n ja sprite-kuvan automaattisesti. Mutta valitettavasti, kuten Microsoftin aikani, ei ollut tarkoituskaan.

Lyhyesti sanottuna se on pakkomielle, että olen kantanut koko urani. Kukaan ei pidä hitaasta nettisivustosta!

**Ollakseni reilu**CDN:t eivät ole luonnostaan pahoja.

**Täydet tiedot**Tämä blogi on tarkoituksella ylikehitetty. *tarve* Kaikki tämä monimutkaisuus. Mutta kun rakennan sen tällä tavalla, opettelen modernin frontend-työkalun kunnolla ja, ratkaisevasti, sain jotain todellista kirjoitettavaa ja opetettavaa. Joskus paras tapa oppia on rakentaa jotain hieman naurettavaa ja dokumentoida matka.

[TOC]

## CDN-levyjen ongelma

Aluksi lähestymistapani oli yksinkertainen:

```html
<!-- Old CDN-based approach -->
<script src="https://unpkg.com/alpinejs@3.x.x/dist/cdn.min.js"></script>
<script src="https://unpkg.com/htmx.org@2.x.x"></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">
```

Vaikka tämä toimii, sillä on useita haittoja, joista tuli yhä turhauttavampia:

- **Luotettavuuskysymykset**CDN:t laskevat. Olen nähnyt unpkg ja jsdelivr on katkos. Kun ne ovat alhaalla, sivustosi on rikki
- **Ennakoimaton latauskäyttäytyminen**: CDN-vastaukset voivat vaihdella hurjasti. Joskus nopeat, joskus hitaat, joskus he ottavat käyttöön kilpailuolosuhteet, joissa kirjastot lataavat väärässä järjestyksessä
- **Aikatauluun liittyvät kysymykset**: Sinulla ei ole mitään valtaa, kun käsikirjoitukset lataavat keskenään. Tämä aiheutti minulle loputonta päänsärkyä kirjaston alustamisessa
- **Useita HTTP-pyyntöjä**: Jokainen kirjasto vaatii erillisen pyynnön, vaikka HTTP/2
- **Ei puiden ravistelua**: Lataat koko kirjaston, vaikka käyttäisit siitä vain murto-osan
- **Version muuttuminen**: CDN:t saattavat palvella eri versioita, ellet kiinnitä niitä selkeästi, ja jopa kiinnitetyt versiot voivat käyttäytyä eri tavalla CDN-toimittajien välillä
- **Rakennusajan epävarmuus**: Yhteensopivuutta ei saada kiinni ennen kuin usein käyttäjä ilmoittaa siitä
- **Rajoitettu optimointi**: Ei voi väheksyä rajoja tai poistaa kuollutta koodia
- **Offline-kehitys**: Vaatii internetyhteyttä, mikä on ärsyttävää junissa tai lentokoneissa työskennellessä

Joten halusin kontrolloida... kontrolloida juuri sitä, mitä sivustoni käytti ja tarvitsi toimiakseni. Halusin niputtaa vain sen, mitä tarvitsin, varmistaa luotettavan latausjärjestyksen ja optimoida suorituskyvyn. Tämä tarkoitti siirtymistä pois CDN-levyiltä ja kohti niputtamista.

Ne on yksinkertaisempi perustaa, ja HTTP/2:n ja HTTP/3:n multipleksoinnin myötä moninkertainen pyyntö on paljon vähemmän kriittinen kuin aiemmin. Monissa projekteissa, erityisesti pienissä tai prototyypeissä, CDN:t ovat edelleen täysin järkevä valinta, mutta tuotantopaikalla, jossa halusin valvontaa, luotettavuutta ja optimointia, niputtaminen oli järkevämpää.

## Nykyaikainen yhteenkytkeminen

Nykyinen settini yhdistää kaikki Javascript-riippuvuudet Webpackin kautta ja käsittelee CSS:ää PostCSS:n kautta.

### Miksi Webpack eikä Vite?

Ennen kuin sukeltan yksityiskohtiin, minun pitäisi puhua huoneessa olevalle norsulle: miksi käytän Webpackia, kun Vite, Rollup, esbuild ja muut modernit nippukauppiaat ovat olemassa?

Rehellinen vastaus on yksinkertainen: **Tunsin jo Webpackin**.

Olin käyttänyt Webpackia laajasti opettaessani verkkokehityksen aloituskurssiani, ja se oli työkalu, jonka ymmärsin. Kun päätin modernisoida tämän blogin rakennusputken, edessäni oli jo jyrkkä oppimiskäyrä, jossa ymmärsin puiden ravistelua, koodinjakoa, moduulijärjestelmiä, PostCSS-putkistoja ja miten tämä kaikki yhdistettiin ASP.NET Coreen.

**Se on yleensä hyvä käytäntö, kun työstetään projekteja, rajataan "uusi" "hallittavissa olevaan". Se on tapa, jolla epävarmuutta on helpompi rajoittaa.**

"Opettele täysin uusi rakennustyökalu" sen päälle vaikutti tarpeettomalta. Webpack toimii. Se on kypsä. Siinä on erinomainen dokumentointi ja valtava ekosysteemi. *käsitteet* Nykyaikaisesta niputtamisesta eikä tietyn työkalun omalaatuisuudesta.

**Onko Vite nopeampi?** Viten kehityspalvelin alkuperäisillä ES-moduuleilla ja eskonstruktiolla on huomattavasti nopeampi kuin Webpack. Suurissa projekteissa, joissa on satoja moduuleja, ero on dramaattinen.

**Pitäisikö sinun käyttää Viteä uuteen projektiin?** Vite on todennäköisesti parempi valinta, jos aloittaa puhtaalta pöydältä ilman olemassa olevaa Webpack-tietoa. Se on nopeampi, yksinkertaisempi konfiguroida ja edustaa modernia lähestymistapaa etupään työkaluihin.

**Kadunko Webpackin käyttöä?** Ei lainkaan. Se vei minut sinne, missä minun pitikin olla. Opetukseni niputtamisesta, koodien jakamisesta ja optimoinnista ovat siirrettävissä mihin tahansa rakennustyökaluun. Tämän blogin mittakaavan vuoksi Webpackin ja Viten suoritusero on mitätön – puhutaan millisekunneista kehityksen korjausajoissa.

Se on keskeinen osa sitä, miten rakennan tavaraa; aloita siitä, mikä on helppoa ja rakenna siltä pohjalta.

Laajempi opetus tässä on se, että **edistys voittaa täydellisyyden**Olisin voinut viikkokausia tutkia "parasta" nippua, vertailla vertailutietoja, lukea vertailusanoja ja tuskailla päätöstä. Sen sijaan valitsin työkalun, jonka tiesin, sain sen toimimaan ja jatkoin eteenpäin. Tuo pragmatismi piti minut laivassa eikä loputtomasti keskustelemassa.

Ehkä jonain päivänä voisin muuttaa Vitelle. Ehkä en. Joka tapauksessa tässä blogissa on moderni, optimoitu rakennusputki, joka toimii luotettavasti, ja juuri sillä on merkitystä.

### Pakkausrakenne

Erytropoietiini [`package.json`](https://github.com/scottgal/mostlylucidweb/blob/main/Mostlylucid/package.json) Nyt minulla on kaksi erillistä riippuvuusryhmää. Tämä rakenne vaati useita yrityksiä saada asiat kuntoon – aluksi Nuget ja npm ovat samanlaisia, mutta nppm on paljon vähemmän ystävällinen, kun siihen lisätään pacakgeja.

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

**Riippuvuussuhteet** ovatko kirjastot tarpeen ajon aikana (Alpine.js, HTMX, EasyMDE jne.), kun taas **devDependiteetit** ovat rakennustyökaluja (Webpack, Babel, PostCSS-prosessorit jne.).

### Rakenna skriptejä

Erytropoietiini `package.json` käsikirjoitukset ovat kehittyneet merkittävästi:

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

Tämä asetus erottaa toisistaan seuraavat seikat:

- **puhtaana**: Poistaa aiemmat rakentamislähdöt käyttämällä `rimraf`
- **kappale:*** Tehtävät: Kopioi staattisia CSS-tiedostoja, joita ei tarvitse käsitellä (nämä ovat joko "tuontia") erityistarkoitukseen, kuten funky Raleway -kirjasinten lataamiseen tai adaptiivisiin, kuten teemanvaihtopäivityksiin.
- **tw:*** Tehtävät: Processes Tailwind CSS PostCSS:n kautta
- **js:*** tehtävät: Bundles JavaScript through Webpack
- **dev/watch/building**: Järjestää koko putken

Erytropoietiini `npm-run-all` Paketti mahdollistaa rinnakkaistoteutuksen nopeammille rakennuksille.

Voit asettaa `npm run build` Automatisoiminen paikallisen rakentamisen aikana, mutta se on hieman hankalaa CI:lle (se täytyy varmistaa, että se on toimintakyvytön jne.).

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

```

Jos haluat vain lisätä tämän csproj... Voit jopa sanoa vain ajaa aikana debug' jne....mutta pidän sitä sotkuisena. Haluan vain manuaalisesti ajaa sen. Joten `npm run watch` tekee juuri niin, kun mikä tahansa css / js-tiedosto muuttuu, se automaattisesti pyörittää rakennelmaa.

HUOMAUTUS: JS-maailmassa he melkein käyttävät kuumaladattuja dev-juoksuja, mikä on aika ovelaa ja saa tavallaan vihaamaan ASP.NET Coren heikkoa yritystä.

## Webpack Deep Sukellus Deep Sukellus

Webpack-kokoonpano on paikka, jossa taika tapahtuu – ja jossa käytin suurimman osan ajastani vianetsintään. Tämä ei syntynyt täysin. Se on tulosta lukemattomista iteroinneista, Stack Overflow -hauista ja lukemasta Webpack-dokumentaatiosta kello 2.00 yrittäen ymmärtää, miksi rakennukseni tuotti 47 kappaletta tiedostoja.

Tässä on nykyinen asetus (alkaen [`webpack.config.js`](https://github.com/scottgal/mostlylucidweb/blob/main/Mostlylucid/webpack.config.js)) yksityiskohtaisilla selvityksillä siitä, mitä kukin osa tekee ja miksi se on siellä:

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

### Avainasetukset selitetty

#### Koodinjakoa ja nuuskaamista

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

Tämä asetus jakaa koodisi automaattisesti pienempiin osiin:

- **kolikot: "kaikki"**: Analysoi sekä synkronista että epäsynkronista tuontia
- **Minikoko: 20000**: Luo vain yli 20 KB-moduuleille
- **Maksimikoko: 100000**: Yritti pilkkoa yli 100KB:n kokoisia kappaleita
- **RuntimeChunk**: Ottaa Webpack-ajon erilliseen tiedostoon pitkäaikaisen välimuistin parantamiseksi

Käytännössä tämä luo useita tiedostoja `wwwroot/js/dist/`:

- `main.js` - Hakemuksen hakupiste
- `runtime.js` - Webpack-moduulin latauslogiikka
- `[vendor].[contenthash].js` - Automaattisesti jaettuja myyjän palasia
- Lisäosia koodinjakamisreiteille tai laiskasti ladattaville moduuleille

#### Baabelin käännytys

```javascript
{
    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,
                }],
            ],
        },
    },
}
```

Baabelin transpiles modern JavaScript tukee vanhempia selaimia:

- **tavoitteet**: Tukee selaimia >0,25 prosentin markkinaosuudella, jota pidetään edelleen yllä
- **moduulit: vääriä**: Säilöö ES6-moduuleita Webpack-puiden ravisteluun
- **useBuiltIns: "käyttö"**: Sisältää automaattisesti monitäyttöjä vain ominaisuuksille, joita käytät
- **Corejs: 3**: Käyttää core-js-versiota 3 monitäytteisiin

Tämä tarkoittaa, että voin kirjoittaa modernin JavaScriptin (async/await, optional chaining, nullish collessaging) säilyttäen samalla laajan selaintuen.

#### Tuotannon pienennys

```javascript
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 minimoi tuotantorakennuksia aggressiivisesti:

- **pudota _konsolin**: Poistaa kaikki `console.log()` lausunnot
- **ohittaa: 3**: Runs pakkaus kolme kertaa maksimaalisen koon pienentäminen
- **ylin taso: totta**: Manglesin huipputason muuttujanimet
- **puhtaat _sienet**: Poistaa tiettyjä konsolin menetelmiä, vaikka muuttujat määriteltäisiin
- **kommentit: vääriä**: Poistaa kaikki kommentit tuotoksesta

Tässä blogissa tämä tyypillisesti pienentää JavaScript-nippukokoja 60-70 prosentilla verrattuna minimoimattomaan koodiin.

## PostCSS-putki myötätuuleen

Perätuuli CSS:ää käsitellään PostCSS:n kautta useilla liitännäisillä. Tämä asetus (alkaen [`postcss.config.js`](https://github.com/scottgal/mostlylucidweb/blob/main/Mostlylucid/postcss.config.js)) on armollisen yksinkertainen verrattuna Webpackiin:

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

Putkisto toimii seuraavasti:

1. **postcss-tuonti**: Selvitetään `@import` lausuntoja CSS-tiedostoissa
2. **myötätuulivoimalat**: Prosessit myötätuulidirektiivejä ja tuottaa hyödyllisyysluokkia
3. **Autoprefinderi**: Lisää myyjän etuliitteet selainten yhteensopivuuteen
4. **cssnano**: Vähentää lopullisen CSS-tuloksen

### Perätuuliasetukset

Erytropoietiini [`tailwind.config.js`](https://github.com/scottgal/mostlylucidweb/blob/main/Mostlylucid/tailwind.config.js) Tiedosto määrittelee, mistä Tailwindin tulisi etsiä luokan nimiä. `content` Oikeat polut olivat kriittisiä – missaa polku ja Tailwind ei luo kursseja noille tiedostoille:

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

Tärkeimmät ominaisuudet:

- **sisältöä**: Skannaukset `.cshtml` luokannimien tiedostot (mukaan lukien Razorin näkymät ja sähköpostimallit)
- **safelist**: Sisällytä nämä luokat aina, vaikka niitä ei löytyisikään skannauksista
- **DarkMode: "luokkaa"**: Mahdollistaa luokkapohjaisen pimeän tilan vaihtamisen
- **teema.laajennus**: Lisää omia värejä ja väliarvoja
- **Liitännäiset**: Sisältää Tailwind-liitännäiset ja DaisyUI-komponenttikirjaston

Sen vuoksi lopullinen CSS-tiedosto on paljon pienempi kuin koko Tailwind-kirjasto.

## Javascript Entry Point

Erytropoietiini [`main.js`](https://github.com/scottgal/mostlylucidweb/blob/main/Mostlylucid/src/js/main.js) Tämä tiedosto tuo ja alustaa kaikki riippuvuudet, ja se on kasvanut orgaanisesti, kun olen lisännyt ominaisuuksia blogiin:

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

Tästä lähestymistavasta on useita etuja:

1. **Kertatuontipiste**: Kaikki riippuvuudet ladattu yhden tietuetiedoston kautta
2. **Eksplisiittiset versiot**: Package.json lukitsee tiettyjä versioita
3. **Puiden ravistelu**: Webpack poistaa kirjastoista käyttämättömän koodin
4. **Maailmanlaajuinen altistuminen**: Razorin näköpiirissä olevat kirjastot `window`
5. **Mukautetut moduulit**: Sovelluskohtainen koodi, joka on järjestetty maahantuotaviksi moduuleiksi

### Asettelumallin integrointi

Sisään `_Layout.cshtml`Nyt viittaan vain niputettuihin omaisuuseriin:

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

Huomautusta täsmennän `module` Kertoaksesi selaimelle, minkä tyyppinen JS-tiedosto tämä on & `asp-append-version` Näppärä pieni ASP.NET-tunnisteavustin, joka liittää hashed-version tiedostojen sisällöstä kyselyyn; tehokkaasti välimuistin syötön, kun tiedosto muuttuu.

Ainoat jäljellä olevat CDN-riippuvuudet ovat:

- **Googlen sisäänkirjautuminen**: Vaatii ulkoisen CDN:n OAuth-toiminnoille
- **Boksikonit**: Kuvakekirjasin (voidaan niputtaa, mutta siitä on mahdollisimman vähän hyötyä)
- **Umami Analytics**: Kolmannen osapuolen analytiikkakäsikirjoitus - itseohjautunut, jotta näen vierailusi, en Googlea?

## Rakenna putkiston visualisointi

Näin koko rakennusprosessi siirtyy lähdetiedostoista tuotanto-omaisuuteen:

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

Se näyttää aika monimutkaiselta, mutta sitä WebPack tekee, se käsittelee suurimman osan tästä itse (monimutkaisella tavalla, jota Viten kaltaiset eivät tarvitse, mutta.)

## Suorituskyvyn paraneminen

Ei oikeastaan piste, mutta nyt se on kuin ehdoton luoti. Cloudflaren "Rocket Loader" (joka kolauttaa sivuja jonkin verran) voi silti aiheuttaa ongelmia, mutta se on TIGHT.

## Kehittäjäkokemuksen edut

Suorituskyvyn lisäksi nykyaikainen rakennusputki parantaa merkittävästi kehitystyön sujuvuutta:

### Tyyppi Turvallisuus ja IntelliSense

Webpackin avulla yhteenliittäminen mahdollistaa oikean Javascript-moduulin resoluution, mikä tarkoittaa:

- **IntelliSense in VS Code**: Tuotujen moduulien automaattinen täydentäminen
- **Siirry määrittelyyn**: Siirry suoraan kirjaston lähdekoodiin
- **Inline-dokumentaatio**: Kirjastojen JSDoc-kommentit näkyvät IDE:ssä

KUN rakennus katkeaa `npm run watch`) Tiedän, että yhdessä (harvan mutta kasvavan) JS-yksikön testien kanssa se tarkoittaa tiukempia palautesilmukoita.

### Kuuman moduulin korvaaminen

Kehityksen aikana `npm run watch` mahdollistaa lähes välittömän palautteen:

```bash
npm run watch
```

Tämä pyörittää Webpackia kellotilassa, uudelleenrakentamalla vain vaihdettuja moduuleja:

- **Tyypillinen rekonstruktioaika**: 200–400 ms
- **Selaimen automaattipäivitys**: Selainsynkronoinnin tai vastaavan työkalun käyttö
- **Säilötty hakemustila**: HMR ylläpitää sovellustilaa päivitysten aikana (jos tuettu)

### Riippuvuusjohtaminen

npm:n lukitustiedosto (`package-lock.json`) varmistaa johdonmukaisen rakentamisen eri ympäristöissä:

```bash
npm ci  # Clean install from lock file
```

Tämä takaa samat riippuvuusversiot kehitys-, CI/CD- ja tuotantoympäristöissä.

Niitä kutsutaan JS-maailmassa "pinnaksi", jossa riippuvuudet ovat kiveen hakattuja. Voit pärjätä ilman pakettilukkoa, mutta sitten olet riippuvainen packge-kirjoittajista; usein HUNDREDS on erilainen ihminen, joka ei pilaa pientä julkaisua..s

### Rakenna skriptejä

Järjestetyt npm-kirjoitukset helpottavat yhteisiä tehtäviä:

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

## Yhteiset kuviot ja ratkaisut

### Myytyjen kirjastojen esittely maailmanlaajuisesti

Jotkin kirjastot tarvitsevat maailmanlaajuista altistusta käyttääkseen Razor-näkymiä tai inline-skriptejä:

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

Sitten sisään `.cshtml` tiedostot:

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

### CSS:n käsittely JavaScript-moduuleista

Joihinkin kirjastoihin kuuluu tuontia kaipaava CSS:

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

Webpack's `css-loader` sekä `style-loader` käsitellä näitä tuontituotteita:

- CSS uutetaan rakentamisen aikana
- Ruiskutetaan sivulle `<style>` tunnisteet tai erilliset CSS-tiedostot
- Automaattisesti esikiinnitetty ja hienosäädetty

### Laiska lataus raskaita kirjastoja

Jos kirjastoja tarvitaan vain tietyillä sivuilla, käytä dynaamista tuontia:

```javascript
// 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 jakaa automaattisesti koodin jakaen dynaamisen tuonnin erillisiksi kappaleiksi, jotka ladataan tilauksesta.

### CommonJS-moduulien käsittely

Osa vanhoista kirjastoista käyttää CommonJS:ää ES6-moduulien sijaan:

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

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

Webpack käsittelee molemmat moduulijärjestelmät läpinäkyvästi ja muuntaa tarvittaessa CommonJS:n ES6:ksi.

## Yhteisiä ongelmia

### Lähdekartat puuttuvat kehityksestä

Varmista `devtool` on konfiguroitu `webpack.config.js`:

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

Tämä tuottaa lähdekarttoja helpommaksi vianetsintää varten.

### CSS-luokan ongelmien selvittäminen myötätuulella

Jos Tailwind poistaa käyttämäsi kurssit, tarkista `content` kokoonpano:

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

Vaihtoehtoisesti on käytettävä `safelist` Dynaamiset luokat:

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

### Moduulia ei löytynyt

Jos Webpack ei pysty ratkaisemaan moduulia, tarkista:

1. **Oikea tuontipolku**: Suhteelliset polut alkavat `./` tai `../`
2. **Tiedoston päätteet**: Lisää `.mjs` Kohtiin `resolve.extensions` tarvittaessa
3. **Aitaukset**: Käyttö `resolve.alias` monimutkaisille poluille

```javascript
resolve: {
    extensions: ['.js', '.mjs', '.json'],
    alias: {
        '@components': path.resolve(__dirname, 'src/js/components/'),
    }
}
```

### Kehitä suorituskykykysymyksiä

Jos rakennuksista tulee hitaita:

1. **Ota välimuisti käyttöön**: Webpackin välimuisti nopeuttaa merkittävästi rekonstruointia
2. **Vähennä Baabelin soveltamisalaa**: Jätä lisää hakemistoja pois Baabelin käsittelystä
3. **Päivitystyökalut**: Webpackin ja Baabelin uudemmat versiot ovat nopeampia
4. **Rinnakkaiset rakenteet**: Käyttö `thread-loader` monisäikeisen transpilaation osalta

```javascript
// Enable Webpack caching
cache: {
    type: 'filesystem',
},
```

## Opetuksia vaikeasti opittu

Anna minun jakaa joitakin virheitä, joita tein tämän matkan aikana, jotta voit välttää ne:

### Erehdys numero 1: Yrittää punnertaa kaikkea kerralla

Ensimmäinen yritykseni oli repiä pois kaikki CDN-viitteet yhdellä kertaa ja yrittää niputtaa kaiken Webpackin läpi. Rakennus hajosi näyttävästi. Opin, että asteittainen muuttoliike on ystäväsi – siirrä kirjasto kerrallaan, testaa perusteellisesti ja siirry seuraavaan.

### Virhe #2: Moduulin tyyppien ymmärtämättä jättäminen

Tuhlasin tunteja yrittäessäni selvittää, miksi tietyt kirjastot eivät lataisi.`require()`) ja ES6-moduuleita (`import`) ymmärtämättä, miten Webpack käsittelee niitä, johtaa salamyhkäisiin virheisiin. `experiments: { outputModule: true }` Konfiguraatio ei ollut alkuperäisessä kokoonpanossani, enkä ymmärtänyt, miksi moduulejani ei ladattu.

Ratkaisu syntyi lukemalla GitHub-numeroita Webpackin arkistosta keskiyöllä.

### Virhe #3: Yliaggressiivisen koodin jakaminen

Yhdessä vaiheessa konfiguraationi tuotti paloja kaikkeen. `minSize: 10000` (10KB), joka tarkoitti, että Webpack loi erilliset tiedostot pieniä aputoimintoja varten. Ylikorjattu ja meni 2MB:lle... Sivukuormasta tuli 50+ pienen palan vesiputous tai se roikkui odottamassa valtavia kappaleita ladattavaksi. Opin, että koodin jakaminen on hyvä, mutta tarvitset järkeviä raja-arvoja. Siksi nykyinen konfiguraationi käyttää sitä. `minSize: 20000` (20 KB).

### Virhe #4: Unohtaminen `.mjs` Laajennus

Jotkin npm-paketit jakavat ES6-moduuleja `.mjs` Laajennus. Webpack ei ratkaise näitä oletuksena. Vietin häkellyttävän pitkään vianetsintää, miksi `@mostlring I needed to add `.mjs`to`Päättäkää.laajennukset.

Helppo ratkaisu, kun sen tietää, mutta sen selvittäminen vei aikaa.

### Virhe #5: Ei välimuistia Webpack Buildings

Varhaisrakennukset kestivät 30-60 sekuntia, koska en ollut ottanut käyttöön Webpackin tiedostojärjestelmää. Kun lisäsin välimuistin, rekonstruktioajat laskivat 2-3 sekuntiin. Tämä oli kehitystyön muuttaja, mutta minulta kesti viikkoja löytää se.

### Virhe #6: Perätuulipuhdistuksen luokat, joita itse asiassa käytin

Suosikki vianetsintäkokemukseni: CSS-kurssit, jotka toimivat hyvin kehityksessä, katosivat yhtäkkiä tuotannossa. Tailwind puhdisti niitä, koska ne syntyivät dynaamisesti JavaScriptissa. Ratkaisuna oli `safelist` Konfiguraatio, mutta vasta sen jälkeen, kun olin hukannut tunteja miettiessäni, olenko tulossa hulluksi.
I MYÖS (esimerkiksi `mostlylucid.pagingtaghelper` Project) käytä "Dummy Blocksia", jossa yksinkertaistan Tailwind-konfigurointiani vain piilottamalla lohkon.

```html

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

```

Kun Tailwind rakentaa sen skannataan cshtml-tiedostoja luokille; jos se ei näe niitä, se poistaa ne. Jos tämä piilotettu lohko varmistaa, että se näkee tarvitsemani luokat, vaikka niitä käytettäisiin vain sulautetuissa kirjaston näkymissä. JS-kehittäjät yleensä myös skannattavat JS/TS-tiedostoja, jotta he voivat käyttää CSS-tyylejä, joita Tailwind muuten ei huomaa.

```json

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

}

```

Kuten edellä mainittiin, "Tailwind Hyväksytty" -lähestymistapa on lisätä tunnit turvaluetteloon, mutta olen ASP.NET-tyyppi, joten HTML voitti.

### Mikä on oikein: itsepintaisuus

Tein oikein, kun kieltäydyin luovuttamasta. Jokainen virheilmoitus oli oppimistilaisuus. Jokainen rikkinäinen rakennus opetti minulle jotain uutta näiden työkalujen toiminnasta. Minulla oli selkeä visio – nopeasti lastaavat, optimoidut voimavarat – ja jatkoin iterointia, kunnes pääsin perille.

## Päätelmät

Siirtyminen CDN-pohjaisista riippuvuuksista moderniin niputusputkeen on ollut tämän blogin kannalta mullistavaa, mutta se ei ollut helppoa. Suorituskyvyn parantuminen on merkittävää, kehittäjäkokemus on huomattavasti parempi, ja minulla on paljon suurempi kontrolli optimoinnissa, mutta tänne pääsy vaati kuukausien kokeilun ja virheen.

Avainruokia:

1. **Yhteenkytkeminen pienentää hyötykuorman kokoa**: Puiden ravistelu ja miinanraivaus poistavat käyttämätöntä koodia
2. **Koodien jakaminen parantaa suorituskykyä**: Selaimet lataavat vain tarvittavan
3. **Rakenna työkaluja, jotka mahdollistavat modernin JavaScriptin**: Baabelin transpilointi tukee uusimpia kieliominaisuuksia säilyttäen samalla selainyhteensopivuuden
4. **PostCSS-putki optimoi CSS:n**: Tailwindin JIT-kääntäjä ja cssnano toimittaa pieniä tyylejä
5. **Integrointi .NETiin on saumatonta**: MSBuilt-kohteet ajavat automaattisesti frondend-rakennukset
6. **Pysyvyys voittaa täydellisyyden**: Sinun ei tarvitse olla etukäteisasiantuntija – tarvitset vain selkeän vision ja halun iteroida

ASP.NET Core -kehittäjille, jotka ovat tottuneet yksinkertaiseen CDN:ään, tämä tuntuu aluksi ylivoimaiselta. Se on normaalia. Tunsin samoin. Aloita pieni, siirry riippuvuuteen kerrallaan, äläkä pelkää rikkoa asioita kehitystyössä. Opit enemmän rikkinäisten rakennelmien vioista kuin koskaan.

Tässä jakamani kokoonpano edustaa kuukausien iterointia. Matkasi tulee olemaan erilainen, ja se on hyvä. Käytä tätä lähtökohtana, ei gospelina. Mukauta se tarpeisiisi, kokeiluun, äläkä lannistu epäonnistumisista – ne ovat osa prosessia.

**Onko tämä blogi ylikehittynyt?** Olisinko voinut käyttää CDN-levyjä ja käyttää aikaani muihin asioihin? Toki, mutta en olisi oppinut puoliakaan niin paljon, eikä minulla olisi tätä artikkelia kerrottavana kanssasi. Joskus ylisuunnittelussa ei ole kyse päämäärästä, vaan siitä, mitä matkan varrella oppii ja pystyy opettamaan siitä muille.

Jos luotat yhä CDN-levyihin etupään riippuvuudessasi, kannustan sinua kokeilemaan niputtamista. Aloita yhdestä kirjastosta. Katso, miltä se tuntuu. Ekosysteemi on kypsynyt merkittävästi, ja vaikka oppimiskäyrä on todellinen, kannattaa maksaa.

## Lisää luettavaa

- [Webpack-dokumentaatio](https://webpack.js.org/)
- [Perätuuli CSS: Optimointi tuotantoon](https://tailwindcss.com/docs/optimizing-for-production)
- [Baabelin käyttöopas](https://babeljs.io/docs/en/usage)
- [PostCSS-dokumentaatio](https://postcss.org/)
- [Edellinen artikkelini: ASP.NET Core Developers](/blog/tailwind)