UnoCSS Webpack 集成实战unocss/webpack 插件的配置方式与源码级实现解析【免费下载链接】unocssThe instant on-demand atomic CSS engine.项目地址: https://gitcode.com/GitHub_Trending/un/unocss本文围绕 UnoCSS 官方文档中的 Webpack 集成指南unocss/webpack插件包展开完整覆盖从安装、webpack 4/5 配置、Vue CLI 框架接入到uno.css虚拟模块使用的全流程并结合 packages-integrations/webpack 目录下的实际源码插件入口、unplugin 实现、Rspack 适配层与仓库测试用例解释该插件如何扫描 token、如何注入 CSS、如何处理持久化缓存的底层机制。读完本文你可以在现有 Webpack 项目中正确接入 UnoCSS并理解插件各钩子的职责与版本限制遇到构建问题时能对照源码快速定位。1. 插件概述unocss/webpack 是什么unocss/webpack是 UnoCSS 提供的 Webpack 插件官方包 README 见 packages-integrations/webpack/README.md对应的完整使用文档在 docs/integrations/webpack.md。关于该插件官方文档明确了两个重要前提仅支持global模式。UnoCSS 的集成分为不同运行模式而 Webpack 插件目前只实现并支持 global 模式即通过入口引入全局uno.css的方式生成样式这一点在官方文档中给出了对 vite/src/types.ts 中模式定义的引用。不附带任何默认预设。插件本身不会替你加载preset-mini、preset-wind3等预设你需要在自己的uno.config.ts中自行配置presets、rules、theme等选项。从 package.json 可以看到包的元信息包名为unocss/webpacktype: moduleESM-only 构建产物同时提供 CJS 入口peerDependencies声明webpack: ^5核心依赖包括unocss/config、unocss/core、unplugin、chokidar、webpack-sources等。值得注意的是虽然 peer 依赖只声明了 webpack 5但插件源码里对 webpack 4 的钩子optimizeAssets做了兼容处理文档也保留了 webpack 4 的完整配置示例。包内源码结构非常精简共 3 个文件文件职责src/index.ts默认导出WebpackPlugin对外暴露插件选项类型src/unplugin.ts核心实现基于unplugin构建实现 token 提取、虚拟模块、资源注入、watch 更新src/rspack.tsRspack 适配层导出UnoCSSRspackPlugin2. 前置依赖与安装官方文档Prerequisite一节明确unocss/webpack依赖style-loader和css-loader来处理 CSS 文件。因为插件产出的虚拟模块uno.css是一个标准 CSS 模块Webpack 不会自动处理 CSS必须由你在 loader 链中配置这两个 loader。安装命令以官方文档支持的 4 种包管理器为例# pnpm pnpm add -D unocss/webpack # yarn yarn add -D unocss/webpack # npm npm install -D unocss/webpack # bun bun add -D unocss/webpack3. Webpack 配置ESM-only 带来的动态 import 写法这是 Webpack 集成中最高频的踩坑点。官方文档指出从 UnoCSSv0.59.0起UnoCSS 已迁移为 ESM-only而 Webpack 配置通常运行在 CJS 环境module.exports中因此不能直接require插件必须以动态 import的方式加载配置。这一点也体现在包的构建形态上——tsdown.config.ts 同时输出esm与cjs两种格式动态import()在 Node CJS 环境中的运行时才安全。3.1 UnoCSS ≥ v0.59.0 的配置webpack 5// webpack.config.js module.exports function () { return import(unocss/webpack).then(({ default: UnoCSS }) ({ plugins: [ UnoCSS() ], optimization: { realContentHash: true } })) }其中optimization.realContentHash: true是 webpack 5 下推荐开启的选项UnoCSS 的样式内容是构建过程中才生成的若内容哈希参与文件名可以保证产物文件名与实际内容一致。3.2 UnoCSS ≥ v0.59.0 的配置webpack 4// webpack.config.js module.exports function () { return import(unocss/webpack).then(({ default: UnoCSS }) ({ plugins: [ UnoCSS() ], css: { extract: { filename: [name].[hash:9].css }, }, })) }官方文档的 warning 提示了 webpack 4 的限制optimization.realContentHash在 webpack4.x 不受支持此时需要用css.extract.filename自定义 CSS 文件名示例中使用哈希码的前 9 位[hash:9]代替 contenthash。同时文档提醒该用法存在与打包相关的已知问题UnoCSS issue #1728 以及 webpack 自身的 issue #9520在 webpack 4 项目中如遇样式产物异常可对照这两个 issue 排查。3.3 旧版本 UnoCSS v0.59.0的 CJS 写法如果你的项目仍在使用迁移前的 UnoCSS 版本可以直接require// webpack.config.jswebpack 5 const UnoCSS require(unocss/webpack).default module.exports { plugins: [ UnoCSS() ], optimization: { realContentHash: true } }// webpack.config.jswebpack 4 const UnoCSS require(unocss/webpack).default module.exports { plugins: [ UnoCSS() ], css: { extract: { filename: [name].[hash:9].css } } }3.4 插件参数从 src/index.ts 的类型定义可以看到插件签名的完整参数export interface WebpackPluginOptionsTheme extends object object extends UserConfigTheme { /** * Manually enable watch mode * * default false */ watch?: boolean } export default function WebpackPluginTheme extends object( configOrPath?: WebpackPluginOptionsTheme | string, defaults?: UserConfigDefaults, ): WebpackPluginInstance三个要点configOrPath既可以传内联配置对象继承核心UserConfigTheme的全部选项也可以传一个字符串路径指向你的配置文件如./uno.config.ts在配置对象中额外提供watch?: boolean默认false用于手动开启内容监听——源码中这里有一条TODO: detect webpacks watch mode and enable watcher见 src/unplugin.ts说明当前版本还无法自动感知 webpack 的 watch 模式需要你在 watch 场景下显式传watch: true第二个参数defaults用于传入UserConfigDefaults作为配置的默认值基础。对应的uno.config.ts文件内容即标准 UnoCSS 配置// uno.config.ts import { defineConfig } from unocss export default defineConfig({ // ...UnoCSS options })4. 使用方式在入口引入uno.css配置好插件后按照官方文档Usage一节的说明在应用主入口中引入虚拟模块uno.css// main.ts import uno.cssuno.css并不是真实文件而是由插件通过resolveId钩子解析出的虚拟模块。所有出现在代码中被提取器识别的 UnoCSS 类名token会在此处统一生成对应的 CSS。由于插件只支持global模式全局样式的注入点就是这个uno.css入口它会被你配置好的css-loaderstyle-loader开发环境注入style标签或MiniCssExtractPlugin生产环境抽取为文件接管处理。5. 框架集成Vue CLIVue webpack 4/5官方文档为 Vue CLI 项目给出了完整方案。前提条件同样强调使用 UnoCSSv0.59.0时需要Vue CLI Servicev5.0.8才能正确支持以动态 import 方式加载的 Webpack 配置因为 Vue CLI 允许vue.config.js导出一个返回 Promise 的函数。5.1 UnoCSS ≥ v0.59.0 的 vue.config.jswebpack 5// vue.config.js const process require(node:process) module.exports function () { return import(unocss/webpack).then(({ default: UnoCSS }) ({ configureWebpack: { devtool: inline-source-map, plugins: [ UnoCSS() ], optimization: { realContentHash: true } }, chainWebpack(config) { config.module.rule(vue).uses.delete(cache-loader) config.module.rule(tsx).uses.delete(cache-loader) config.merge({ cache: false }) }, css: { extract: process.env.NODE_ENV development ? { filename: css/[name].css, chunkFilename: css/[name].css } : true } })) }5.2 UnoCSS ≥ v0.59.0 的 vue.config.jswebpack 4// vue.config.js const process require(node:process) module.exports function () { return import(unocss/webpack).then(({ default: UnoCSS }) ({ configureWebpack: { plugins: [ UnoCSS({}) ] }, chainWebpack(config) { config.module.rule(vue).uses.delete(cache-loader) config.module.rule(tsx).uses.delete(cache-loader) config.merge({ cache: false }) }, css: { extract: process.env.NODE_ENV development ? { filename: [name].css, chunkFilename: [name].[hash:9].css } : true } })) }配置中几个关键动作的作用chainWebpack中删除cache-loader并关闭cacheVue CLI 默认对.vue/.tsx模块使用cache-loader而 UnoCSS 依赖 transform 阶段收集 token缓存层可能跳过 loader 执行导致 token 漏扫因此官方示例显式禁用缓存链路css.extract按环境切换开发环境输出固定文件名便于 HMR 与调试生产环境交还 Vue CLI 默认的抽取与命名策略配合前文的realContentHash或hash:9方案。5.3 旧版本 UnoCSS 的 vue.config.js对迁移前的 UnoCSS 版本则直接使用require形式其余逻辑相同// vue.config.jswebpack 5旧版本 UnoCSS const process require(node:process) const UnoCSS require(unocss/webpack).default module.exports { configureWebpack: { devtool: inline-source-map, plugins: [ UnoCSS() ], optimization: { realContentHash: true } }, chainWebpack(config) { config.module.rule(vue).uses.delete(cache-loader) config.module.rule(tsx).uses.delete(cache-loader) config.merge({ cache: false }) }, css: { extract: process.env.NODE_ENV development ? { filename: css/[name].css, chunkFilename: css/[name].css } : true }, }// vue.config.jswebpack 4旧版本 UnoCSS const process require(node:process) const UnoCSS require(unocss/webpack).default module.exports { configureWebpack: { plugins: [ UnoCSS({}), ] }, chainWebpack(config) { config.module.rule(vue).uses.delete(cache-loader) config.module.rule(tsx).uses.delete(cache-loader) config.merge({ cache: false, }) }, css: { extract: process.env.NODE_ENV development ? { filename: [name].css, chunkFilename: [name].[hash:9].css, } : true, }, }6. 源码深度解析unplugin.ts 中的核心实现文档层面只描述了怎么配src/unplugin.ts 则展示了怎么跑。以下按执行顺序梳理关键实现。6.1 上下文创建与环境模式const ctx createContextWebpackPluginOptions(configOrPath as any, { envMode: process.env.NODE_ENV development ? dev : build, ...defaults, })插件通过共享的集成层virtual-shared/integration/src/context.ts创建上下文并以NODE_ENV区分 dev/build 两种环境模式分别加载对应的 UnoCSS 配置。ctx提供tokens收集到的类名集合、filter判断文件是否在 UnoCSS 关注范围内、extract从代码中提取 token、tasks/flushTasks异步任务队列等能力。6.2 transform 阶段token 提取transform 钩子是 UnoCSS 按需生成的入口transform: { filter: { id: { exclude: [/\.html$/, BINARY_ASSET_RE], }, }, async handler(code, id) { if (!filter(, id)) return const result await applyTransformers(ctx, code, id, pre) if (isCssId(id)) return result if (result null) tasks.push(extract(code, id)) else tasks.push(extract(result.code, id)) return result }, },逻辑分三步先用filter过滤掉 UnoCSS 不处理的文件再执行enforce: pre的 transformer见 6.6 节关于该限制的说明最后将提取任务压入异步队列tasks——注意提取是异步批处理的并非同步阻塞 transform真正消费队列的时机在 6.5 节的 assets 阶段。filter排除二进制资源BINARY_ASSET_RE覆盖 png/jpg/webp/svg/字体等见 src/unplugin.ts这一处修复了 unplugin 上游一个把非过滤模块当文本处理、从而损坏二进制资源的问题源码注释指向 issue #5164 / unjs/unplugin#524。仓库测试 test/webpack-assets.test.ts 正是针对该修复做了回归用 test/fixtures/webpack-assets 夹具跑完整 webpack 构建断言输出的logo.png字节与源文件完全一致PNG 魔数0x89 0x50校验同时断言打包结果中包含.text-red样式。6.3 resolveId 与 load虚拟模块uno.css的注入resolveId钩子src/unplugin.ts将uno.css这类 id 解析为内部的虚拟模块入口并把入口注册进entries集合保留原始 queryload钩子则负责返回虚拟模块内容。这里有一个关键设计load 只匹配虚拟 CSS 模块 id默认前缀__uno即正则/[/\\]__uno(?:_.*?)?\.css(\?.*)?$/返回的是hash 占位符 layer 占位符的桩内容而不是真实 CSS// serve the placeholders in virtual module async handler(id) { const layer await getLayer(ctx, id) if (!layer) return const hash hashes.get(id) return (hash ? getHashPlaceholder(hash) : ) getLayerPlaceholder(layer) },真实 CSS 的替换发生在后续的资源阶段6.5 节。这个先占位、后替换的两段式设计正是配合 webpack 的realContentHash占位符参与初始打包最终资源阶段再注入真实内容并重建 source map。load钩子限定只处理虚拟 CSS id 同样是为了避免全局 load 钩子污染二进制资源与 6.2 节的二进制保护同属一个上游问题的修复。6.4 Webpack 5 持久化缓存的处理webpack(compiler)钩子里有一段专门针对compiler.options.cache的补偿逻辑src/unplugin.tsFor Webpack 5 persistent cache, we need to manually read the file content if the module is restored from cache, as loaders might be skipped.也就是说当 webpack 5 持久化缓存命中、模块从缓存恢复时loader包括 UnoCSS 的 transform可能被跳过导致 token 没有被收集。插件在finishModules钩子中对每个非 CSS、未被 resolve 过且通过 UnoCSS filter 的模块用compiler.inputFileSystem.readFile手动重读磁盘文件内容并重新执行ctx.extract保证缓存场景下 token 不丢失。这也解释了为什么 Vue CLI 示例中显式关闭了缓存——两种策略禁缓存 vs 补偿扫描二选一。6.5 资源阶段CSS 生成与占位符替换核心替换逻辑挂在资源处理钩子上同时兼容 webpack 4 与 5const optimizeAssetsHook compilation.hooks.processAssets /* webpack 5 6 */ || compilation.hooks.optimizeAssets /* webpack 4 */ optimizeAssetsHook.tapPromise(PLUGIN_NAME, async () { await ctx.ready const files Object.keys(compilation.assets) await flushTasks() const result await ctx.uno.generate(tokens, { minify: true }) // ...遍历每个 asset把 LAYER 占位符替换为该层的真实 CSS })执行顺序flushTasks()先消费之前 transform 阶段积压的提取任务然后调用核心的ctx.uno.generate(tokens, { minify: true })一次性生成全部 CSS最后遍历compilation.assets中的每个产物用LAYER_PLACEHOLDER_RE找到占位符并替换为对应 layer 的 CSS__uno全量入口取所有已解析 layer 的合集并用webpack-sources的SourceMapSource重建产物以保留 source map。这里的 layer 概念与 UnoCSS 配置中的layers选项对应虚拟模块 id 中编码了它所属的 layer实现一个入口、多文件、按 layer 分片注入的能力。6.6 已知限制只支持pre类型的 transformer在beforeCompile钩子中插件会检查用户配置的 transformers若存在非preenforce 的项会输出警告[unocss] webpack integration only supports pre enforce transformers currently. the following transformers will be ignored从源码结构看这是因为 webpack 的 transform 时机只能挂在一个阶段插件只在 transform handler 中调用applyTransformers(ctx, code, id, pre)post 阶段没有对应的注入点所以像transformer-variant-group这类默认 post 的 transformer 需要自行确认 enforce 设置。6.7 Watch 模式下的热更新const UPDATE_DEBOUNCE 10 onInvalidate(() { clearTimeout(timer) timer setTimeout(updateModules, UPDATE_DEBOUNCE) })当内容监听即 3.4 节中手动开启的watch: true检测到文件变化时onInvalidate触发 10ms 防抖的updateModulessrc/unplugin.ts重新flushTasks、重新generate、对每个虚拟模块 id 计算新 hash 并通过plugin.__vfs.writeModule(id, code)写入 unplugin 的虚拟文件系统从而让 webpack 感知虚拟模块内容变化并触发增量重建。hash 未变化token 集合 size 相同时提前返回避免无谓的重编译。6.8 Rspack 支持src/rspack.ts 提供UnoCSSRspackPlugin其实现直接复用同一套 unpluginunplugin.ts 中get rspack() { return this.webpack }见 src/unplugin.ts并通过包的exports以unocss/webpack/rspack子路径单独发布见 package.json 中的exports[./rspack]。因此在 Rspack 项目中可以以几乎相同的方式接入。7. 版本与适用前提小结综合文档与源码使用该插件时需要确认以下前提事项说明依据UnoCSS 版本≥ v0.59.0 必须用动态import()加载插件旧版本可requiredocs/integrations/webpack.mdWebpack 版本peer 依赖^5源码同时兼容 webpack 4optimizeAssets钩子但 webpack 4 不支持realContentHash需用css.extract.filename控制产物名package.json、src/unplugin.tsCSS 处理链必须配置style-loadercss-loaderdocs/integrations/webpack.md预设插件不带默认预设需在uno.config.ts中自行配置presets官方文档 info 提示运行模式仅支持 global 模式入口import uno.css官方文档transformer仅enforce: pre的 transformer 生效其余会被忽略并告警src/unplugin.tswatch 更新不会自动感知 webpack watch 模式需显式UnoCSS({ watch: true })src/index.ts、src/unplugin.ts8. 快速接入清单按官方文档与仓库实现一次标准的 Webpack 5 接入只需四步pnpm add -D unocss/webpack连同style-loader、css-loaderwebpack.config.js导出返回 Promise 的函数动态import(unocss/webpack)并挂UnoCSS()插件开启optimization.realContentHash创建uno.config.ts配置所需的presets与theme在main.ts入口import uno.css。若遇到样式不更新优先排查两处是否在 webpack 5 持久化缓存开启而 token 未被重新提取源码已有补偿逻辑见 6.4 节以及是否在 Vue CLI 场景下cache-loader未禁用见 5.1 节示例。若遇到PNG/字体等二进制资源损坏确认使用的是包含BINARY_ASSET_RE过滤修复的版本test/webpack-assets.test.ts 中的断言方式可以直接借鉴为自查手段。【免费下载链接】unocssThe instant on-demand atomic CSS engine.项目地址: https://gitcode.com/GitHub_Trending/un/unocss创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考