不能直接在libs/ui里写scss变量,因为nx/pnpm workspace不将其纳入依赖图,@import路径错误会静默失败,同名变量覆盖不可控;正确做法是抽离为tokens.json,通过工具生成sass map和:root css变量,确保依赖可追踪、构建可缓存、命名一致。

为什么不能直接在libs/ui里写SCSS变量
因为Nx(或pnpm workspace)默认不把SCSS/CSS文件当作“可构建资产”纳入依赖图。你在libs/ui里放一个tokens.scss,apps/web用@import "../libs/ui/src/lib/tokens.scss",构建时不会触发重构建、缓存失效、Turborepo跳过变更——更糟的是,SCSS @import是编译时拼接,路径一错就静默失败,连错误提示都不给。
同名变量(比如$color-primary)在多个包里重复定义,Sass会按@import顺序覆盖,runtime行为完全不可控;而CSS自定义属性(--color-primary)没有作用域,最终只有一个生效,调试时根本分不清是谁写的。
共享SCSS变量的正确姿势:JSON + Sass Map双输出
必须把设计Token从样式层抽离为数据层,再由工具转成样式。核心是让所有包消费同一份结构化数据源:
-
libs/tokens/src/lib/tokens.json:纯JSON,含嵌套结构和注释,如{"color": {"primary": "#007bff"}} -
libs/tokens/src/lib/_tokens.scss:用json2sass或自写脚本,将JSON转为Sass Map:$tokens: (color: (primary: #007bff)); -
libs/tokens/project.json中配置build target,确保dist/libs/tokens/_tokens.scss被生成并保留路径层级
业务包通过@use "tokens" as t from "@myorg/tokens"引用,而不是硬写路径。这样Nx能识别依赖,Turborepo缓存有效,TypeScript还能校验JSON结构是否合法。
如何让CSS变量也真正生效到浏览器
不能靠运行时fetch加载JSON再document.documentElement.style.setProperty——这会导致FOUC、SSR不兼容、无法被PostCSS处理。必须在构建阶段注入:
- Vite插件读取
dist/libs/tokens/tokens.json,递归扁平化生成:root { --color-primary: #007bff; } - 该
style标签必须插入在最前,且在任何CSS引入之前,否则会被PostCSS的minify或prefix阶段误删 - Webpack需用
DefinePlugin或HtmlWebpackPlugin的inject钩子做同样事,但注意process.env.NODE_ENV === 'production'时不能漏掉
关键点:generateCssVariables()函数必须处理嵌套键名转换(color.primary → --color-primary),且不能依赖ESM动态import()——Vite的transformIndexHtml是同步上下文。
多人协作时最容易忽略的命名同步问题
Token改名不是改一个地方的事:
- 改
tokens.json里的key,必须同步更新_tokens.scss里的Map结构和导出逻辑 - 如果已有项目在CSS里直接用了
var(--old-token-name),光改JSON没用,得在生成CSS变量时加alias映射:--old-token-name: var(--new-token-name); - TS类型文件(
tokens.type.ts)也要同步,否则import type { TokenSet } from '@myorg/tokens'会报错,且IDE补全失效
最隐蔽的坑是:SCSS变量名和CSS变量名不一致(比如SCSS用$primary-color,CSS用--color-primary),这种命名错位会在跨团队协作时引发大量重复排查——建议统一用kebab-case作为唯一事实源。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











