更新您的前端构建管道: 从 CDNs 到捆绑 (中文 (Chinese Simplified))

更新您的前端构建管道: 从 CDNs 到捆绑

Tuesday, 11 November 2025

//

15 minute read

一. 导言 导言 导言 导言 导言 导言 一,导言 导言 导言 导言 导言 导言

在过去的一年里,我跌倒、实验、破碎了东西, 并逐渐现代化了这个博客的前端建设管道。这不是什么无懈可击的计划,而是试探和错误,许多错误,以及由我所希望实现的目标的清晰愿景所驱动的顽固不化。

我不是前端导师或Webpack巫师。我是网络开发者,因为CDN依赖性的限制而失望,决定必须有一个更好的方法。文章记录了从简单到混乱的、迭接的旅程。 <script> 指向一个实际起作用的现代捆绑管道的标签。

我在路上犯了很多错误(我会记录下来), 花了几个小时去解答加密网页包错误, node_modules 比我想承认的要多得多。 但坚持是有所回报的,而我通过坚定决心来纠正这一点而学到了巨大的知识。

如果你是一个.NET开发商 盯着Webpack配置文件 想知道你到底陷入了什么—— 这篇文章是给你的。

我是一个老的网络表演狂, 在拨号的最初几天里, 我为钱而建的第一个网站(在Perl.

在那些时候,人们的担忧是完全不同的。 JavaScript实际上并不为人所知。 div Netscsca 和 Netsca layer如果你的用户有56K连接,你会很走运。所以我学会了优化(即使菜单项的图象布局变平了)等等...

后来在微软,我甚至还在网络表格中工作了一个自动图像图示工具,这太棒了:它从一个页面中提取了小图像,自动生成了 CSS 和一个图示图象。但同我在微软时一样,它不是。

简言之,我整个职业生涯都沉迷其中 没人喜欢一个缓慢的网站!

公平CDN并非天生的邪恶

充分披露:这个博客是故意设计过度的。 需要 需要 如此复杂。 但这样构建它让我学习现代前端工具的正确使用,并且,关键是,给我一些真实的写作和教学。 有时,最好的学习方法就是构建一些略为荒谬的东西,记录旅程。

CDN 的问题

起初,我的方法简单明了:

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

虽然这项工作取得了成效,但有几个缺点越来越令人沮丧:

  • 可靠性问题: CDN 确实会下降。 我见过 unpkkg , jsdelivr 有断断裂。 当他们倒塌时, 您的网站被打破了 。
  • 难以预测的装载行为: CDN 的响应可能大不相同。有时是快速的,有时是缓慢的,有时是引入种族条件,而图书馆则按错误的顺序负荷。
  • 时间安排问题:您无法控制脚本彼此相相对负荷的时间。这导致我无休止的头痛,因为图书馆初始化
  • HTTP 多重 HTTP 请求:每个图书馆都要求单独提出请求,即使HTTP/2也要求单独提出请求。
  • 不摇树,不摇树:你下载整个图书馆,即使你只使用其中的一小部分
  • 版本流流: CDNs 可能提供不同的版本,除非您将它们明确锁定,甚至被钉住的版本也可以在 CCDN 提供方中表现不同。
  • 建筑时间不确定性:在运行时间之前(经常当用户报告时),不可能发现不兼容性。
  • 有限优化:无法跨越边界或删除死代码
  • 离线发展: 需要互联网连通,这在火车或飞机上工作时令人烦恼。

所以我想要控制... 控制我的网站使用和需要的正确性能。 我想只包我需要的东西, 保证可靠的装货订单, 并优化性能。 这意味着要远离CDN, 转而采用捆绑方法。

它们比较简单,HTTP/2 和 HTTP/3 多重转换, 多重请求的间接费用远不如以往那么重要。 对于许多项目, 特别是小项目或原型, CDN仍是一个非常合理的选择。 但是对于一个我想要控制、 可靠性和优化的生产网站来说,捆绑更有意义。

现代捆包办法

我目前的设置捆绑了 JavaScript 所有依赖者 通过网络包 和通过邮政支助系统处理 CSS。

为什么要用网络包代替维特?

在潜入技术细节之前,我应该先谈谈房间里的大象:为什么我在Vite、Rollup、Esbuild和其他现代包装商存在时使用网页包?

诚实的回答很简单: 我已经知道Webpack了.

在教授我的“开始网络开发”课程时,我广泛使用网络包,这是我理解的工具。当我决定将这个博客的建设管道现代化时,我已经面临着一个陡峭的学习曲线 — — 理解树层、代码分割、模块系统、邮政和安全局的管道,以及如何将这一切与ASP.NET核心结合起来。

一般来说,在项目工作时,这是一个好的做法; 将“ 新的” 限制在“ 管理” 的范围内。 限制不确定性的方法比较容易。

添加“ 学习一个全新的建筑工具 ” 。Weback works. it's 成熟。 它有极好的文献记录和一个庞大的生态系统。 最重要的是, 我可以把学习的能量集中在 概念概念 而不是某种工具的特质。

维特更快吗? 绝对。 Vite 带有本地ES 模块和 esbuild-how 捆绑的开发服务器比Webpack要快得多。 对于拥有数百个模块的大型项目来说,差别是巨大的。

您应该使用 Vite 进行新工程吗 ? 可能吧,是的。如果你是刚开始的,并且没有现有的网页软件知识, Vite 可能是更好的选择。它更快捷,更简单,更便于配置, 并且代表了前端工具的现代方法。

我后悔使用Webpack吗? 完全没有,它让我走到我需要的地方。我学到的关于捆绑、代码分割和优化的教训可以转移到任何建筑工具。坦率地说,对于这个博客的规模来说,Webpack和Vite之间的性能差异微乎其微——我们谈论的是发展重建时代的几毫秒。

它是我如何建造东西的关键方面; 从什么是“容易”开始, 从这个基础来建立。

这里更广泛的教训是 进度胜于完美。 我本可以花几周时间研究“最佳”捆包机,比较基准,阅读比较文章,为决定而苦恼。相反,我选择了我所知道的工具,使它发挥作用,并向前推进。这种实用主义使我无法无休止地思考,而不是无休止的思考。

也许有一天我可能会移居维特,也许我不会。不管怎样,这个博客都有一个现代的、最理想的管道,可以可靠地运作,这才是最重要的。

包包结构结构

缩略 package.json 最初,Nuget和npm与KINDA相似,但在添加pacakkees时,nppm则不太友好。

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

依赖性 在运行时需要图书馆(阿尔卑斯山、HTMX、EmpleMDE等),而 D. 依赖性 它们是建筑工具(Webpack、babel、PostCSS处理器等)。

构建脚本

缩略 package.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"
  }
}

这一体制将以下关注问题分开:

  • 清洁:删除使用前一个构建输出 rimraf
  • 副本 :* 任务: 复制不需处理的静态 CSS 文件( 要么是“ imports ” ) , 用于装入我的发亮的 Raleway 字体等特殊目的, 要么是主题切换增强等适应性文件 。
  • tw: 湿度 :* 任务:通过 PostCSS 处理尾风 CSS
  • js:* 任务:通过Webpack捆绑 JavaScript
  • Dev/观察/建设:将整个输油管管统管化

缩略 npm-run-all 软件包允许平行执行, 以便快速构建 。

您可以设置 npm run build 在您本地建设期间自动运行,但对于 CI 来说有点麻烦( 您需要确保它被禁用等 ) 。

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

要做到这一点,你只需在Csproj上加上这个字...你甚至可以说只在调试期间运行,等等...但我发现它很乱,我宁可手动运行。所以我更喜欢手动运行它。 npm run watch 当任何 cs / js 文件更改时, 它会自动运行构建 。

说明:在联署材料世界里, 他们几乎使用热重载Dev跑步,

Webpack 配置深潜水

网络包配置是魔法发生的地方, 也是我大部分时间花在排除故障的地方。 这并没有完全形成。 这是无数次迭代的结果, Stack overflow 搜索, 以及凌晨2点通过网络包读取文件, 试图理解为什么我的建筑产生47块文件。

以下是当前配置 (来自 webpack.config.js详细解释每一部分的工作以及为什么存在:

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

此配置自动将您的代码分割成小块 :

  • 块块 : “ 全部 ”:分析同步和非同步进口
  • 最小大小: 20000:只为大于 20KB 的模块创建块
  • 最大大小: 100 000:试图分割大于 100KB 的块
  • 运行时间 chunk:将Webpack 时间运行到一个单独的文件中,以改善长期缓存

在实践中,这产生多个文件 wwwroot/js/dist/:

  • main.js - 您的申请入境点
  • runtime.js - 网页软件包模块装载逻辑
  • [vendor].[contenthash].js - 自动分割销售商块块
  • 代码分割路径或懒惰装入模块的额外块块

婴儿出生

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

支持旧浏览器的现代 JavaScript :

  • 指标指标指标指标指标指标指标指标指标指标指标:支持仍然维持 > 0.25%市场份额的浏览器
  • 模块: 错误:保留ES6模块,用于Webpack树刷
  • 使用 : “ 使用 ”:只对您使用的特性自动包含多填充量
  • 核心j:3:将核心js 版本3用于多填料

这意味着我可以写现代 JavaScript (async/wait, 可选的链条, 无效的连结) , 同时保持广泛的浏览器支持 。

生产最集中化

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积极压缩生产,建造:

  • 滴滴出后台式(_D):全部删除 console.log() 报表报表报表
  • 通行证: 3: 运行压缩三次以最大缩放大小
  • 顶层: 真实: 排列顶层变量名称
  • 纯funcs:删除特定的控制台方法,即使指定给变量
  • 注释:虚假:从输出中删除所有评论

这个博客通常会将 JavaScript 捆绑的大小减少60-70%,

PSCSS 尾风管道管道

尾风 CSS 由 PostCSS 和多个插件处理。 postcss.config.js与Webpack相比,它简单明了:

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

管道工程如下:

  1. 进口决 定: @import CSS 文件中的语句
  2. 尾风:处理尾风指令并生成公用事业类
  3. 自动前缀器:为浏览器兼容性添加供应商前缀
  4. cssnano , cssnano , cssnano , cssnano , cssnano , cssnano , cssnano , cssnano , csssnano , cssnano , cssnano , cssnano , csssnano , cssnano , csssnano , csssnano , csssnano , cssnano , csssnano csnano , cssnano ccsnano , csnano ccsnano , csssnano cccccsnano , csnano csnano cccccsnano ccsnano ccsnano cccccccccccsnano cccsnano csnano csnano ccsnano ccccccccccccccccccccsnano ccccccccsnno ccccccccccsnno cccsnano ccccccccccccccccsnano ccsnno ccsnno ccccccccccccsnano csnano cccccsnano ccccccccccccccsnano cccccsnano ccccccccccccc cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc cccccccccccccccccccccccccccc: 最小化 CSS 最终产出

尾风配置

缩略 tailwind.config.js 指定“反尾风”文件要查找类名称的位置。 content 右侧路径是关键—— 缺少一条路径, 尾风不会为这些文件生成类 :

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

关键特征:

  • 内容内容内容:扫描 .cshtml 类名文件(包括 Razor 视图和电子邮件模板)
  • 安全列表:即使扫描中未找到这些类,也总是包含这些类。
  • 暗模式: "类": 启用基于类的暗模式切换
  • 主题。 extend:添加自定义颜色和间距值
  • 插件插件:包括尾风插件和 DisaiUI 组件库

尾风在构建时扫描这些文件, 只提取您实际使用的工具类 。 这就是为什么最终的 CSS 文件比完整的 Tailwind 库要小得多 。

JavaScript 进入点

缩略 main.js 文件是所有事物聚集的地方。 此文件导入和初始化所有依赖关系, 并且随着我给博客添加的特性而有机地成长 :

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

这种办法带来若干好处:

  1. 单一进口点:所有依赖关系都通过一个条目文件装入
  2. 公开文本: 套件.json 锁定特定版本
  3. 树木摇动: Webpack 从图书馆中删除未使用的代码
  4. 全球接触:Razor浏览的图书馆 window
  5. 自定义模块: 将特定应用程序代码组织成可导入模块

布局模板集成

_Layout.cshtml,我现在只提及捆绑的资产:

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

附注一具体说明 module 使浏览器知道 JS 文件的类型( I) asp-append-version a 整洁的小 ASP. NET 标签助手,该助手将文件内容的散列版本附加到查询字符串中;当文件更改时,实际上会破坏缓存 。

余下的唯一依赖CDN者是:

  • Google 签名: 需要外部 CDN 的 OAUT 功能
  • 信条:图标字体(可以捆绑,但效益最小)
  • Umami分析分析器第三方分析脚本 - 自我主机,让我看到你的访问,而不是谷歌?

构建管道可视化

以下是整个建设过程如何从源文件流向生产资产:

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

它看起来相当复杂,但真的这是WebPack做的, 它自己处理大部分这些事情(复杂的方式, Vite 喜欢的人不需要...)

业绩改进

重覆, 并不真正是 POINT 。 但是它现在的确像一颗绝对子弹一样载荷了。 您仍然可以对 Cloudflare 的“ 滚筒装载器” (该装载器的页数多少会加载事件) 产生问题, 但是它是“ ight ” 。

开发者经验收益

除了业绩之外,现代建筑管道大大改进了发展工作流程:

安全类型和 Intelli 感知

通过Webpack捆绑使适当的 JavaScript 模块解析, 这意味着 :

  • VS 代码中的 Intelli 感知:进口模块自动完成
  • 跳转到定义: 直接导航到图书馆源代码
  • 内线文件图书馆的JSDoc评论出现在IDE

当建筑破裂的时候, npm run watch我非常清楚, 再加上JS的测试, 意味着更紧密的反馈圈。

热模块替换

发展期间, npm run watch 启用接近即时反馈 :

npm run watch

此操作以监视模式运行 Webpack, 只重建已更改的模块 :

  • 典型重建时间:200-400米
  • 自动更新浏览器:使用浏览器同步或类似工具
  • 预先保留的申请状态:工管关系在更新期间(在支持的情况下)保持申请状态

扶养管理

npm 锁定文件( N)package-lock.json(b) 确保环境的一致建设:

npm ci  # Clean install from lock file

这保证了开发、CI/CD和生产环境方面相同的依赖性版本。

这些在JS世界被称为“翻滚 ” , 其依赖关系被固定在一块石头上。 您可以不用包锁就可以做, 但随后您要依赖帕卡奇的作者; 通常由不同的人组成, 而不是破坏一些小的释放。

构建脚本

组织起来的 npm 脚本很容易完成共同的任务 :

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

共同模式和解决办法

全球全球博览馆

有些图书馆需要全球曝光, 以便用于 Razor 视图或内线脚本:

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

然后在 .cshtml 文件 :

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

从 JavaScript 模块处理 CSS

有些图书馆包括需要进口的CSS:

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

网页包 css-loaderstyle-loader 处理这些导入 :

  • CSS 是在建造期间提取的
  • 通过电子邮件输入页面 <style> 标签或单独的 CSS 文件
  • 自动前置和最小化

懒惰装货重型图书馆

对于仅需要特定网页的图书馆,请使用动态进口:

// 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 自动代码分割将动态导入输入成单独的块, 并按需装入 。

处理共同 JS 模块

一些较老的图书馆使用共同司法服务,而不是ES6模块:

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

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

Webpack 以透明方式处理两个模块系统,必要时将通用JS转换为ES6。

解决共同问题

发展过程中缺少的原始地图

确保确保 devtool 被配置在 webpack.config.js:

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

这样可以绘制源地图,便于调试。

CSS 与尾风的班级净化问题

如果倒风删除了您使用的课程, 请检查 content 配置 :

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

或者,使用 safelist 对于动态类 :

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

模块模块未找到错误

如果 Webpack 无法解析模块, 请检查 :

  1. 校正导入路径:相对路径开始于 ./../
  2. 文件扩展名: 加: 加: .mjsresolve.extensions 必要时
  3. 异式:使用 resolve.alias 复杂路径的复杂路径
resolve: {
    extensions: ['.js', '.mjs', '.json'],
    alias: {
        '@components': path.resolve(__dirname, 'src/js/components/'),
    }
}

建设绩效问题

如果建设变得缓慢:

  1. 启用缓存: Webpack 的缓存大大加快了重建的速度
  2. 减少贝贝范围: 从 Babel 处理中排除更多目录
  3. 升级工具:新版本的Webpack 和 Babel 更快
  4. 平行建设:使用 thread-loader 用于多线传转
// Enable Webpack caching
cache: {
    type: 'filesystem',
},

A. 汲取的艰难经验教训

让我分享一下我在旅途中犯的一些错误, 这样你就可以避免这些错误:

错误 #1: 试图同时包装一切

我的第一个尝试是将所有CDN参考资料一一撕开,并试图通过Webpack将所有资料捆绑起来。建筑破损惨重。我了解到递增迁移是你的朋友——一次移动一个图书馆,彻底测试,然后转到下一个图书馆。

错误 # 2: 不理解模块类型

我浪费了数小时试图弄清楚为什么某些图书馆不载货。require()和ES6单元(import)而不知道Webpack如何操作它们导致加密错误。 experiments: { outputModule: true } 我的初始设置没有配置, 我无法确定为什么我的模块没有装入。

解决方案来自午夜在Webpack仓库读取GitHub议题。

错误 # 3: 过度侵略的代码分割

有一次,我的组合为每件事情都创造了块。 minSize: 10000 (10KB) 意思是Webpack 正在为微小的通用功能创建单独的文件 。 被过度校正并转到 2MB... 页面负荷变成了50+小块请求或等待大块下载的瀑布 。 我了解到代码分割是好的, 但你需要明智的阈值 。 这就是为什么我目前的配置使用 。 minSize: 20000 (20KB)。

4号错误:忘记 .mjs 延长

一些 npm 软件包配有 ES6 模块的 npm 软件包 .mjs 扩展扩展扩展。 Webpack 无法默认解决这些 。 我花了很尴尬的很长时间调试为什么 @mostlring I needed to add 年 月 日to伸缩。

简单的修复一旦你知道它, 但发现它呢?

错误 # 5: 不是 Caching Webpack 构建

早期建设用了30-60秒, 因为我没有启用 Webpack 的文件系统缓存 。 一旦我添加了缓存, 重建时间会降到2-3秒。 这是一个发展工作流程的游戏变换器, 但花了我几周才发现 。

错误6:我实际使用的尾风净化类

我最喜爱的调试经验:在发展中表现良好的CSS课程在生产过程中突然消失。 尾风正在净化它们,因为它们是在JavaScript动态生成的。 解决方案是: safelist 但只是在我浪费了时间 想知道我是不是疯了 国际法协会(例如,在 mostlylucid.pagingtaghelper ) 使用“ 软块块 ” 来简化我的尾风配置变化, 只需有一个隐藏的块 。


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

当尾风构建时, 它会扫描各班的 cshtml 文件; 如果它看不到的话, 它会移除它们。 因此, 有了这个隐藏的块可以确保它看到我需要的班级, 即使它们只用于嵌入的图书馆视图中。 JS 开发者通常也会扫描 JS/ TS 文件, 以便使用 CSS 样式, 否则, Ladywind 将会错过这些样式 。


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

}

如前所述,“Taillwind Approved”的方法是把班级添加到安全名单中... 但我是一个ASP.NET的家伙,所以HTML赢了

我得到的是对的:持久性

我做对的一件事就是拒绝放弃。每一个错误信息都是一个学习机会。每个破碎的建筑都教给我一些关于这些工具如何运作的新的东西。我有了一个清晰的愿景 — — 集成的、最优化的资产,这些资产装得很快 — — 并且我一直不停地重复直到我到达那里。

结论 结论 结论 结论 结论

从基于CDN的依赖性向现代捆绑管道的移动,对这个博客来说已经产生了变革,但并非易事。 业绩的改善相当可观,开发者的经验要好得多,我对优化的控制要大得多,但经过数月的尝试和错误才来到这里。

密钥取走 :

  1. 捆绑减少有效载荷尺寸:植树和植树去除未使用的代码
  2. 代码分割会改善业绩:浏览器只装载需要的东西
  3. 构建工具启用现代 JavaScript 工具: Babel 移植支持最新的语言功能,同时保持浏览器兼容性
  4. CSS 中央支助: 尾风的 JIT 编译器和 csnano 提供微小样式表
  5. 与.NET的整合是无缝的: MSBilud 目标自动运行前端构建
  6. 持久性胜过完美你不需要做前端专家 你只需要一个清晰的视野 和愿意转动

对于习惯于简单 CDN 的 ASP.NET 核心开发商来说, 这首先会感到压倒性。 这是正常的。 我也有同样的感觉。 开始一个小的, 一次迁移一个依赖性, 不要害怕在开发过程中破坏东西。 您会从调试破碎的建筑中学到比阅读文件更多的东西。

我在此分享的配置代表了几个月的迭代。 您的旅程将会不同, 这样很好。 用它作为起点, 而不是福音。 让它适应您的需要, 实验, 并且不要因为失败而气馁 — — 它们是过程的一部分 。

这个博客是不是设计过度了? 当然,我能不能继续使用CDN 并花时间做其他事呢?当然。但我不会学到一半的东西,也不会有这篇文章和你们分享。有时,超额工程不是目的地,而是你从中学到什么,能够从中教别人。

如果您仍然依赖CDNs作为前端附属关系, 我鼓励您尝试捆绑。 从一个图书馆开始。 看看它的感受。 从那里开始。 生态系统已经大大成熟, 虽然学习曲线是真实的, 但回报是值得的 。

继续阅读

Finding related posts...
logo

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