ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

使用 @stylexjs/unplugin 为 StyleX 构建通用打包器插件:Vite、Webpack、esbuild 等的一体化 CSS 聚合方案

使用 @stylexjs/unplugin 为 StyleX 构建通用打包器插件:Vite、Webpack、esbuild 等的一体化 CSS 聚合方案 前端【免费下载链接】stylexStyleX is the styling system for ambitious user interfaces.项目地址https://gitcode.com/gh_mirrors/st/stylex点击查看免费下载StyleX 的样式编译发生在构建期Babel 插件把stylex.create/stylex.attrs之类的调用编译成原子 CSS 类名并顺带产出所有样式规则。但这些规则本身不会自动出现在你的产物里——总得有人负责把它们收集起来、合并成一份确定的 CSS 文件。stylexjs/unplugin正是这个角色它基于unplugin构建提供一套适配 Vite/Rollup、Webpack/Rspack、esbuild、Bun、rolldown 与 farm 的通用打包器插件在构建期编译 StyleX、聚合所有被转换模块的 CSS并把结果追加进打包器已有的 CSS 资产中没有现成 CSS 资产时则输出一份稳定的回退stylex.css。读完本文你将掌握如何在各类主流构建工具中接入 StyleX、理解其 CSS 聚合与注入机制、devMode三种开发模式与虚拟模块的用法以及如何处理 browserslist 目标与light-dark()降级等实际工程问题。插件是什么unplugin 工厂与打包器适配层stylexjs/unplugin的核心并不是一份各打包器各写一套的代码而是通过 core.js 中createUnplugin(unpluginFactory)导出一个 unplugin 实例unplugin再由 index.js 及其各入口文件把它绑定到不同框架vite.js —— Vitedev 中间件、虚拟模块、optimizeDeps排除rollup.js —— Rollupwebpack.js —— Webpackrspack.js —— Rspack复用 Webpack 的attachWebpackHooksesbuild.js —— esbuildbun.js —— Bunrolldown.js、farm.js —— rolldown 与 farm从 package.json 的exports字段可以看出每个框架都有独立的子路径入口如stylexjs/unplugin/vite、stylexjs/unplugin/webpack并有对应的.d.ts类型声明。其依赖中包含了stylexjs/babel-plugin负责实际编译、lightningcss负责 CSS 降级与前缀与browserslist目标浏览器解析peer 依赖为unplugin^2.3.11。核心工作流程从 core.js 的unpluginFactory可以看到完整的执行链路识别transform钩子先用shouldHandle正则检查源码是否从importSources默认[stylex, stylexjs/stylex]导入无 StyleX 导入的文件直接返回null跳过对应 unplugin.test.js 中 ignores files without StyleX imports 的用例。编译通过transformAsync调用内部 Babel 转换注入语法插件JSX、Flow 或 TypeScript与stylexBabelPlugin.withOptions(...)把importSources、dev、treeshakeCompensation、unstable_moduleResolution等参数透传给 StyleX Babel 插件。收集Babel 转换的metadata.stylex规则被写入共享 MapstylexRulesById与globalThis.__stylex_unplugin_store实现跨模块、跨 Vite 环境的聚合。生成collectCss合并所有规则调用stylexBabelPlugin.processStylexRules生成 CSS再交给lightningcss的transform做目标浏览器降级。注入由各框架的打包器钩子把 CSS 追加进现有 CSS 资产或写出回退文件。安装与所有 StyleX 构建工具链组件一样stylexjs/unplugin属于开发依赖npm i -D stylexjs/unplugin仓库当前版本为 0.19.1与stylexjs/babel-plugin保持同版本联动。按打包器接入各框架入口的用法大同小异差异主要体现在配置对象与 dev 模式的支持上。ViteREADME 示例 中的vite.config.ts是最典型的接入方式// vite.config.ts import { defineConfig } from vite; import react from vitejs/plugin-react; import stylex from stylexjs/unplugin; export default defineConfig({ plugins: [ // devMode: full | css-only | off // externalPackages: [lib-using-stylex] // optional manual include stylex.vite(), react(), ], });有几个关键行为值得注意自动发现依赖 StyleX 的包插件会向上查找最近的package.json遍历其dependencies/devDependencies/peerDependencies/optionalDependencies见discoverStylexPackages解析每个包的 manifest若它声明依赖了stylexjs/stylex或任一importSources指定的包就把该包加入optimizeDeps.exclude与ssr.optimizeDeps.exclude见 vite.js 的config钩子确保这些依赖里的 StyleX 代码也被正确转换而不是被 Vite 预构建打包绕过。对应测试 marks StyleX deps as non-optimized in Vite 验证了这一点。externalPackages手动补充当第三方库虽然用了 StyleX 但未直接声明依赖时可用它强制把包名加入排除列表。必须产出 CSS 资产如果构建产物里没有任何 CSS 文件插件会回退写出stylex.css。插件顺序unplugin 工厂把enforce设为pre确保在 React refresh 等转换之前执行从而保持 HMR 完整。Rollup// rollup.config.mjs import stylex from stylexjs/unplugin; export default { plugins: [stylex.rollup()], };Rollup 适配器在generateBundle阶段从 bundle 中挑选 CSS 资产并注入在writeBundle阶段做兜底追加详见下文CSS 资产注入。Webpack// webpack.config.js const stylex require(stylexjs/unplugin).default; const MiniCssExtractPlugin require(mini-css-extract-plugin); module.exports { module: { rules: [ // your JS/TS loader here { test: /\.css$/, use: [MiniCssExtractPlugin.loader, css-loader] }, ], }, plugins: [stylex.webpack({ useCSSLayers: true }), new MiniCssExtractPlugin()], };Webpack 场景下需要配合mini-css-extract-plugin这类提取插件插件在processAssets阶段PROCESS_ASSETS_STAGE_SUMMARIZE把聚合 CSS 追加到已提取的.css资产上。如果编译产物中没有任何 CSS 资产会推入一条[stylex] No CSS asset found to inject into. Skipping.警告并跳过注入见 webpack.js。useCSSLayers: true会让输出 CSS 使用layer包裹。Rspackconst rspack require(rspack/core); const stylex require(stylexjs/unplugin).default; module.exports { plugins: [ stylex.rspack(), new rspack.CssExtractRspackPlugin({ filename: index.css }), ], };Rspack 适配器直接复用 Webpack 的钩子实现见 rspack.js因此同样要求先有 CSS 提取产物。esbuildimport esbuild from esbuild; import stylex from stylexjs/unplugin; esbuild.build({ entryPoints: [src/App.jsx], bundle: true, metafile: true, // lets the plugin locate CSS outputs if any plugins: [ stylex.esbuild({ importSources: [stylexjs/stylex], useCSSLayers: true, }), ], });esbuild 适配器见 esbuild.js在onEnd阶段收集 CSS优先利用metafile.outputs定位已产出的.css文件找不到时回退为扫描outdir目录。因此示例里特意建议开启metafile: true否则插件只能靠扫目录猜测注入目标。若没有任何 CSS 输出则在输出目录写出stylex.css。BunBun 入口在 bun.js 中实现与 esbuild 类似但更独立默认将聚合 CSS 写入dist/stylex.dev.css可用bunDevCssOutput覆盖并且在构建开始/结束时都会刷新该文件同时在每次onLoad转换后重写。Bun 适配器默认dev: true、runtimeInjection: false、useCSSLayers: true。rolldown 与 farm仓库还提供了 rolldown.js 与 farm.js 入口前者可配合 Rollup 生态使用unpluginFactory中treeshakeCompensation默认对vite、rollup、rolldown三个框架开启以补偿 tree-shaking 对样式元数据的影响。共享选项详解以下选项在所有打包器入口中通用来自 core.js 的unpluginFactory默认值与 README 说明选项类型 / 默认值说明devboolean默认依据NODE_ENV/BABEL_ENV是否为development控制 StyleX Babel 插件是否输出开发态代码importSources[stylex, stylexjs/stylex]需要扫描的 StyleX 导入源列表非 StyleX 模块会被shouldHandle快速跳过useCSSLayersboolean默认false是否把输出 CSS 包装进layerbabelConfig{ plugins, presets }合并进内部 Babel 调用的额外插件与预设会先于语法插件与 StyleX 插件执行unstable_moduleResolution{ type: commonJS, rootDir: process.cwd() }透传给 StyleX Babel 插件的模块解析配置lightningcssOptions对象lightningcss透传选项可覆盖targets、exclude等cssInjectionTarget(fileName: string) boolean自定义挑选要追加的 CSS 资产默认按index.css→style.css→ 第一个.css资产的优先级externalPackagesstring[]需要像应用代码一样被转换的node_modules包常用于分发 StyleX 代码的库会加入 Vite 依赖优化排除devModefull \| css-only \| off默认full仅 Vite开发模式full注入轻量运行时并随 HMR 重新拉取 dev CSScss-only只提供 CSS 端点off关闭 dev 中间件与虚拟模块devPersistToDiskboolean默认false开发期把已收集的规则持久化到node_modules/.stylex/rules.json让多个 Vite 环境如 client/SSR/RSC共享同一份 CSSruntimeInjection透传项是否由运行时注入样式开启时插件不再收集metadata.stylex规则见 core.jsdevMode 的三种形态与虚拟模块Vite 适配器通过 consts.js 中的常量与脚本实现 dev 支持虚拟 CSS 端点固定为/virtual:stylex.cssDEV_CSS_PATH由configureServer中间件返回当前聚合 CSSCache-Control: no-store确保每次请求都是最新内容。devMode: full额外提供virtual:stylex:runtime虚拟模块即VIRTUAL_STYLEX_RUNTIME_SCRIPT运行时维护一个__stylex_virtual__的style元素轮询/监听stylex:css-update事件拉取最新 CSS 并替换内容同时把服务器渲染的link样式表标记为禁用避免 hydration 期重复注入。devMode: css-only提供virtual:stylex:css-only虚拟模块VIRTUAL_STYLEX_CSS_ONLY_SCRIPT只做link的缓存击穿追加?t时间戳不注入style。devMode: off时apply钩子会在serve命令下直接返回false整个插件在 dev 下不生效。服务端还有一个独立的/virtual:stylex.js端点dev-inject-middleware.js返回DEV_RUNTIME_SCRIPT——这是带 800ms 轮询的运行时脚本与 Vite 虚拟模块版本略有差异。dev HTML 注入基线模板在 dev 环境的 HTML shell 中加入以下内容!-- Add in your HTML shell when import.meta.env.DEV -- link relstylesheet href/virtual:stylex.css / script typemodule import(virtual:stylex:runtime); // or virtual:stylex:css-only if you only need CSS /script如果环境能安全地通过虚拟模块 ID 加载运行时也可以把内联脚本替换为script typemodule src/id/virtual:stylex:runtime注意部分框架会以不同方式代理静态资源导致这种script src方式被 CORS 拦截此时应改回从一个小型客户端 shim 中import(virtual:stylex:runtime)或仅需 CSS 时使用virtual:stylex:css-only。另外当devMode: full时 Vite 适配器的transformIndexHtml会自动注入这两者上述手动注入主要针对css-only或自定义 HTML 壳的场景。CSS 资产注入机制这是插件的核心承诺——把 StyleX 输出保持得聚合且确定。不同框架的注入点不同但选目标资产的逻辑一致见 core.js 的pickCssAssetFromRollupBundle与 unplugin.test.js 的 CSS asset selection 测试组若配置了cssInjectionTarget函数先按它挑选如(f) f.includes(admin)会选中admin-*.css。否则优先匹配index(-[hash])?.cssINDEX_CSS_RE。其次匹配style(-[hash])?.cssSTYLE_CSS_RE。最后退化为第一个.css资产。两个正则都允许可选的 8 位以上内容哈希如assets/index-B5Jdbbfd.css且字符类覆盖了 base64url 全字母表——因为 Rollup 默认hashCharacters是 base64url哈希里可能含-与_测试用例index-BVm_Qe95.css、index-CnU-v52M.css正是从真实vite build产物里取的名字。同时正则要求哈希前必须有-避免把reset-index-BpQ2m1Zx.css这类名字误判为入口样式测试 does not treat a longer name ending in index as the entry。在 Rollup/Vite 的generateBundle阶段若找到了目标资产插件会把聚合 CSS 追加到其内容之后current \n css并通过replaceCssAssetWithHashedCopy用新的哈希文件名重新 emit 该资产同时更新 chunk 代码与viteMetadata.importedCss中的引用见 core.js。在writeBundle阶段则直接读写磁盘文件做兜底Vite 场景下如果generateBundle已注入writeBundle会跳过对应 writeBundle is skipped when generateBundle already injected CSS (Vite 8) 测试。回退文件如果打包器没有产出任何 CSS 资产插件会写出回退stylex.cssRollup/Vite写入assets/stylex.cssrollup.js、vite.js 的writeBundle实现esbuild写入输出目录下的stylex.cssesbuild.jsWebpack/Rspack不会写回退文件而是发出警告webpack.js——因此 README 特别提醒使用提取插件时务必保证它们真的运行否则 StyleX CSS 会丢失。测试 writes fallback CSS asset when no CSS bundle entry exists 验证了 Rollup 场景下writeBundle会在assets/stylex.css中产出形如.x1aif7nf { color: red; }的原子 CSS。多输出场景README 的 Notes 指出存在多个输出如 client/SSR时每个输出都会得到自己独立聚合的 StyleX CSS。devPersistToDisk正是为多进程/多环境的 dev 场景设计的桥梁把规则写到node_modules/.stylex/rules.jsoncollectCss会先合并磁盘规则、再合并globalThis共享 store 的规则见 core.js从而让 client/SSR/RSC 等多个 Vite 环境共享同一份 CSS。Browserslist 与 CSS 降级聚合完成后插件用lightningcss对整份 CSS 做 vendor 前缀与语法降级。目标浏览器通过browserslist自动解析遵循标准解析顺序见 core.js 的processCollectedRulesToCSS与 READMEBROWSERSLIST环境变量package.json中的browserslist字段项目根目录的.browserslistrc文件Browserslist 默认值 0.5%, last 2 versions, Firefox ESR, not dead测试 uses project browserslist by default (not hardcoded 1%) 确认了这一点插件不会硬编码浏览器目标而是读取项目真实的 browserslist 解析结果。控制light-dark()降级这是 README 着墨最多的坑值得单独说明。light-dark()是一个较新的 CSS 颜色函数如果 browserslist 目标包含不支持它的浏览器lightningcss 会把它降级为一个 polyfill该 polyfill 依赖color-scheme在同一次transform()调用中被定义。问题在于 StyleX 是从单个模块逐个提取 CSS 的color-scheme通常不在这些规则里于是 polyfill 生成的变量保持未定义暗色模式颜色会静默失效。有两种修复方式方式一把目标浏览器收窄到原生支持light-dark()的版本Chrome ≥ 123、Firefox ≥ 120、Safari ≥ 17.5# .browserslistrc last 2 Chrome versions last 2 Firefox versions last 2 Safari versions方式二无论目标如何显式禁用light-dark()降级import { Features } from lightningcss; stylex.vite({ lightningcssOptions: { exclude: Features.LightDark, }, });对应测试验证了三条路径unplugin.test.js 中 preserves light-dark() when Features.LightDark is excluded 与 preserves light-dark() with modern browser targets 都断言输出仍含light-dark(且无--lightningcss-light变量而 lowers light-dark() when targets do not support it目标Chrome 80断言输出含 polyfill 变量。如果需要完全接管目标浏览器也可以整体覆盖lightningcssOptions.targetsimport { browserslistToTargets } from lightningcss; import browserslist from browserslist; stylex.vite({ lightningcssOptions: { targets: browserslistToTargets(browserslist(last 1 Chrome version)), }, });与仓库其他资源的关系本插件的 Babel 编译能力来自 stylexjs/babel-pluginprocessStylexRules负责把收集的规则渲染成 CSS 字符串仓库内 example-vite、example-rspack、example-esbuild、example-webpack 等示例工程展示了各打包器的完整接入其中 example-webpack 与 example-rspack 的配置与本文的 Webpack/Rspack 段落相互印证。常见问题排查要点CSS 没有出现在产物里先确认构建是否真的产出了 CSS 资产Vite 默认会、纯 JS 的 esbuild 构建不会Webpack/Rspack 必须配置并运行 CSS 提取插件否则只会收到一条跳过警告。第三方库的 StyleX 样式丢失检查该库是否被 Vite 预构建优化绕过必要时用externalPackages手动加入排除或确认importSources是否覆盖了该库使用的导入路径。dev 模式下样式不热更新确认 HTML shell 中已加入link relstylesheet href/virtual:stylex.css /与运行时脚本若script src/id/virtual:stylex:runtime因 CORS/代理失败改用客户端 shim 的import(virtual:stylex:runtime)。暗色模式light-dark()失效按上文两种方式之一处理 browserslist 目标或Features.LightDark排除。多环境 dev 下 CSS 不一致为各环境开启devPersistToDisk让它们共享node_modules/.stylex/rules.json。掌握了上述机制你就能在任意主流构建工具中把 StyleX 的样式编译、CSS 聚合与开发态热更新流畅地接入现有工程并获得确定性的产物输出。赞分享前端【免费下载链接】stylexStyleX is the styling system for ambitious user interfaces.项目地址https://gitcode.com/gh_mirrors/st/stylex点击查看免费下载相关推荐使用 StyleX 与 esbuild通过 stylexjs/unplugin 在构建期编译并聚合样式使用 StyleX 与 esbuild通过 stylexjs/unplugin 在构建期编译并聚合样式 StyleX 是面向复杂用户界面的样式系统而 es前端在 Waku 应用中集成 StyleX基于 stylexjs/unplugin 的配置、CSS 聚合与 HMR 实战指南在 Waku 应用中集成 StyleX基于 stylexjs/unplugin 的配置、CSS 聚合与 HMR 实战指南 本文以仓库中的 example w前端PDF中文在手机上变方块用PDF补丁丁三步嵌入字体PDF中文在手机上变方块用PDF补丁丁三步嵌入字体 PDF在电脑上显示正常到手机上却变成一排方块这是字体缺失的典型症状。PDF补丁丁的字体嵌入功能会扫描文前端上一篇Bokeh 图表联动完全指南从联动平移、联动刷选到属性联动Linked Panning / Brushing / Crosshair / Properties下一篇LuaJIT字节码反编译终极指南快速掌握LJD完整工具链创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表