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
在过去的一年里,我跌倒、实验、破碎了东西, 并逐渐现代化了这个博客的前端建设管道。这不是什么无懈可击的计划,而是试探和错误,许多错误,以及由我所希望实现的目标的清晰愿景所驱动的顽固不化。
我不是前端导师或Webpack巫师。我是网络开发者,因为CDN依赖性的限制而失望,决定必须有一个更好的方法。文章记录了从简单到混乱的、迭接的旅程。 <script> 指向一个实际起作用的现代捆绑管道的标签。
我在路上犯了很多错误(我会记录下来), 花了几个小时去解答加密网页包错误, node_modules 比我想承认的要多得多。 但坚持是有所回报的,而我通过坚定决心来纠正这一点而学到了巨大的知识。
如果你是一个.NET开发商 盯着Webpack配置文件 想知道你到底陷入了什么—— 这篇文章是给你的。
我是一个老的网络表演狂, 在拨号的最初几天里, 我为钱而建的第一个网站(在Perl.
在那些时候,人们的担忧是完全不同的。 JavaScript实际上并不为人所知。 div Netscsca 和 Netsca layer如果你的用户有56K连接,你会很走运。所以我学会了优化(即使菜单项的图象布局变平了)等等...
后来在微软,我甚至还在网络表格中工作了一个自动图像图示工具,这太棒了:它从一个页面中提取了小图像,自动生成了 CSS 和一个图示图象。但同我在微软时一样,它不是。
简言之,我整个职业生涯都沉迷其中 没人喜欢一个缓慢的网站!
公平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, 转而采用捆绑方法。
它们比较简单,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缩略 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跑步,
网络包配置是魔法发生的地方, 也是我大部分时间花在排除故障的地方。 这并没有完全形成。 这是无数次迭代的结果, 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',
},
}
此配置自动将您的代码分割成小块 :
在实践中,这产生多个文件 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 :
这意味着我可以写现代 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积极压缩生产,建造:
console.log() 报表报表报表这个博客通常会将 JavaScript 捆绑的大小减少60-70%,
尾风 CSS 由 PostCSS 和多个插件处理。 postcss.config.js与Webpack相比,它简单明了:
// postcss.config.js
module.exports = {
plugins: {
'postcss-import': {},
tailwindcss: {},
autoprefixer: {},
cssnano: { preset: 'default' }
}
}
管道工程如下:
@import 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 视图和电子邮件模板)尾风在构建时扫描这些文件, 只提取您实际使用的工具类 。 这就是为什么最终的 CSS 文件比完整的 Tailwind 库要小得多 。
缩略 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();
这种办法带来若干好处:
window内 _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者是:
以下是整个建设过程如何从源文件流向生产资产:
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 ” 。
除了业绩之外,现代建筑管道大大改进了发展工作流程:
通过Webpack捆绑使适当的 JavaScript 模块解析, 这意味着 :
当建筑破裂的时候, npm run watch我非常清楚, 再加上JS的测试, 意味着更紧密的反馈圈。
发展期间, npm run watch 启用接近即时反馈 :
npm run watch
此操作以监视模式运行 Webpack, 只重建已更改的模块 :
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>
有些图书馆包括需要进口的CSS:
import EasyMDE from "easymde";
import 'easymde/dist/easymde.min.css'; // Imported CSS is processed by Webpack
网页包 css-loader 和 style-loader 处理这些导入 :
<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 自动代码分割将动态导入输入成单独的块, 并按需装入 。
一些较老的图书馆使用共同司法服务,而不是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',
这样可以绘制源地图,便于调试。
如果倒风删除了您使用的课程, 请检查 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 无法解析模块, 请检查 :
./ 或 ../.mjs 至 resolve.extensions 必要时resolve.alias 复杂路径的复杂路径resolve: {
extensions: ['.js', '.mjs', '.json'],
alias: {
'@components': path.resolve(__dirname, 'src/js/components/'),
}
}
如果建设变得缓慢:
thread-loader 用于多线传转// Enable Webpack caching
cache: {
type: 'filesystem',
},
让我分享一下我在旅途中犯的一些错误, 这样你就可以避免这些错误:
我的第一个尝试是将所有CDN参考资料一一撕开,并试图通过Webpack将所有资料捆绑起来。建筑破损惨重。我了解到递增迁移是你的朋友——一次移动一个图书馆,彻底测试,然后转到下一个图书馆。
我浪费了数小时试图弄清楚为什么某些图书馆不载货。require()和ES6单元(import)而不知道Webpack如何操作它们导致加密错误。 experiments: { outputModule: true } 我的初始设置没有配置, 我无法确定为什么我的模块没有装入。
解决方案来自午夜在Webpack仓库读取GitHub议题。
有一次,我的组合为每件事情都创造了块。 minSize: 10000 (10KB) 意思是Webpack 正在为微小的通用功能创建单独的文件 。 被过度校正并转到 2MB... 页面负荷变成了50+小块请求或等待大块下载的瀑布 。 我了解到代码分割是好的, 但你需要明智的阈值 。 这就是为什么我目前的配置使用 。 minSize: 20000 (20KB)。
.mjs 延长一些 npm 软件包配有 ES6 模块的 npm 软件包 .mjs 扩展扩展扩展。 Webpack 无法默认解决这些 。 我花了很尴尬的很长时间调试为什么 @mostlring I needed to add 年 月 日to伸缩。
简单的修复一旦你知道它, 但发现它呢?
早期建设用了30-60秒, 因为我没有启用 Webpack 的文件系统缓存 。 一旦我添加了缓存, 重建时间会降到2-3秒。 这是一个发展工作流程的游戏变换器, 但花了我几周才发现 。
我最喜爱的调试经验:在发展中表现良好的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的依赖性向现代捆绑管道的移动,对这个博客来说已经产生了变革,但并非易事。 业绩的改善相当可观,开发者的经验要好得多,我对优化的控制要大得多,但经过数月的尝试和错误才来到这里。
密钥取走 :
对于习惯于简单 CDN 的 ASP.NET 核心开发商来说, 这首先会感到压倒性。 这是正常的。 我也有同样的感觉。 开始一个小的, 一次迁移一个依赖性, 不要害怕在开发过程中破坏东西。 您会从调试破碎的建筑中学到比阅读文件更多的东西。
我在此分享的配置代表了几个月的迭代。 您的旅程将会不同, 这样很好。 用它作为起点, 而不是福音。 让它适应您的需要, 实验, 并且不要因为失败而气馁 — — 它们是过程的一部分 。
这个博客是不是设计过度了? 当然,我能不能继续使用CDN 并花时间做其他事呢?当然。但我不会学到一半的东西,也不会有这篇文章和你们分享。有时,超额工程不是目的地,而是你从中学到什么,能够从中教别人。
如果您仍然依赖CDNs作为前端附属关系, 我鼓励您尝试捆绑。 从一个图书馆开始。 看看它的感受。 从那里开始。 生态系统已经大大成熟, 虽然学习曲线是真实的, 但回报是值得的 。
© 2026 Scott Galloway — Unlicense — All content and source code on this site is free to use, copy, modify, and sell.