编写软件包
发布共享 CSS 源代码,在各应用中覆盖公开令牌,并通过生产使用方验证打包后的软件包。
本页内容
#概览
发布 CSS 源代码以共享稳定的样式词汇。各使用方应用将来源导入自己的 Master CSS 入口,并编译所需令牌、工具类和原生组件。词汇在单个应用中正常工作后,再从这里开始;复用样式说明了前一步。
这种软件包形式应发布源定义,而非应用生成的样式表或 manifest。应用便能自行选择类名用法、令牌覆盖和交付模式。如果软件包也提供运行时集成,则有另一份产物契约。
共享定义,各应用自行编译
两个应用导入相同 CSS 源代码。原生组件默认包含;各应用仅生成自己标记请求的工具类。
文字说明
- 共享源代码. 软件包定义原生 .btn 组件和按需生成的 content-auto 工具类。
- 应用 A. 标记使用 btn,原生组件会包含在输出中,但未使用的 content-auto 工具类不会输出。
- 应用 B. 标记使用 btn 和 content-auto。输出包含原生组件和请求的工具类。
#创建软件包
本示例使用 @acme/ui-theme;发布前请将 scope 替换为你拥有的名称。使用方通过包名导入,导出的 CSS 文件为 master.css。
- my-master-theme
- package.json
- master.css
- README.md
将软件包根导出指向 master.css:
{ "name": "@acme/ui-theme", "version": "0.1.0", "files": ["master.css"], "style": "./master.css", "exports": { ".": { "style": "./master.css", "default": "./master.css" } }, "sideEffects": ["./master.css"]}这种软件包形式不需要打包工具、TypeScript 构建、manifest 生成或编译后的 CSS 输出。软件包发布的是 CSS 源代码,支持 Master CSS 的项目可以将其纳入自己的样式表图进行编译。
#编写 master.css
将使用方项目应可获取的共享词汇放入 master.css。
@theme { --color-brand: #4f46e5; --color-text-action: var(--color-text-strong); --color-focus-action: var(--color-text-strong); --spacing-action-x: 1rem; --radius-action: 0.375rem;}@custom-media --theme-motion (prefers-reduced-motion: no-preference);@utility content-auto { content-visibility: auto; contain-intrinsic-size: auto 32rem;}@layer components { .btn { display: inline-flex; align-items: center; justify-content: center; gap: var(--spacing-xs); min-height: 2.375rem; max-width: 100%; padding: .5rem var(--spacing-action-x); border: 1px solid var(--color-line-divider); border-radius: var(--radius-action); font-size: var(--font-size-sm); font-weight: 400; line-height: 1.2; background-color: var(--color-surface-raised); color: var(--color-text-action); box-shadow: var(--shadow-sm); &:hover { box-shadow: var(--shadow-md); } &:active { background-color: var(--color-surface-inset); box-shadow: none; } &:focus-visible { outline: 2px solid var(--color-focus-action); outline-offset: 3px; } }}此示例提供颜色标记、原生 .btn 组件、按需生成的 content-auto 工具类,以及具名 theme-motion 条件。组件拥有共享状态;应用选择是否需要过渡。原生组件 CSS 默认包含,工具类只在使用时生成规则。
选择使用方需要的最小公开词汇:
- 使用
@theme共享颜色、间距、圆角、文字排版、阴影、动效和容器令牌等值。 - 媒体名称使用
@custom-media;其他可复用包装使用带@contents的@mixin。 - 按需生成的类名使用
@utility;原生 CSS 内部复用使用@mixin。 - 仅当软件包刻意拥有
btn、field、card、toolbar这类语义 UI 词汇时,才使用原生@layer components。
令牌命名请参阅主题令牌,指令语法请参阅 CSS 指令,选择抽象方式请参阅全局样式。
#使用软件包
安装软件包后,在默认 Master CSS 入口之后导入:
@import '@master/css';@import '@acme/ui-theme';导入的源代码会成为项目 CSS 入口图的一部分。项目标记可将共享词汇作为工具类和原生 CSS 类名使用:
共享软件包词汇
<article class="p-lg b-solid b(1) b-line-divider r-lg content-auto"> <p class="flex items-center gap-xs m(0) text-xs fg-text-muted"><span aria-hidden="true" class="h(8) w(8) pill bg-brand"></span>Package brand</p> <h2 class="mb-xs mt-sm text-lg font-regular">Shared vocabulary</h2> <p class="mt(0) mb-md text-sm fg-text-muted">The app supplies the markup; the package supplies the shared action.</p> <button type="button" class="btn (transition-property:box-shadow,background-color)@theme-motion (transition-duration:var(--duration-fast))@theme-motion (transition-timing-function:var(--easing-smooth))@theme-motion">Preview action</button></article>生成的 CSS
@layer components { .btn { justify-content: center; align-items: center; gap: var(--spacing-xs); max-width: 100%; min-height: 2.375rem; padding: .5rem var(--spacing-action-x); border: 1px solid var(--color-line-divider); border-radius: var(--radius-action); font-size: var(--font-size-sm); background-color: var(--color-surface-raised); color: var(--color-text-action); box-shadow: var(--shadow-sm); font-weight: 400; line-height: 1.2; display: inline-flex; &:hover { box-shadow: var(--shadow-md); } &:active { background-color: var(--color-surface-inset); box-shadow: none; } &:focus-visible { outline: 2px solid var(--color-focus-action); outline-offset: 3px; } }}@layer theme { :root, :host { --color-zinc-5: oklch(96.5% .0011 265); --color-zinc-40: oklch(68.6% .0028 265); --color-zinc-60: oklch(48.6% .003 265); --color-zinc-90: oklch(24.6% .0022 265); --color-zinc-100: oklch(15% .0016 265) } :root, :host { --color-surface-inset: light-dark(var(--color-zinc-5), var(--color-zinc-100)); --color-surface-raised: light-dark(var(--color-white), var(--color-zinc-90)); --color-line-divider: light-dark(oklch(0% 0 none / .12), oklch(100% 0 none / .12)); --color-text-strong: light-dark(var(--color-zinc-100), var(--color-white)); --color-text-muted: light-dark(var(--color-zinc-60), var(--color-zinc-40)) } :root, :host { --color-white: oklch(100% 0 none) } :root, :host { --font-weight-regular: 400; --font-size-xs: .75rem; --font-size-sm: .875rem; --font-size-lg: 1.125rem; --text-xs: var(--font-size-xs); --text-xs--line-height: 1.5; --text-xs--letter-spacing: 0; --text-sm: var(--font-size-sm); --text-sm--line-height: 1.5; --text-sm--letter-spacing: 0; --text-lg: var(--font-size-lg); --text-lg--line-height: 1.5; --text-lg--letter-spacing: 0; --radius-lg: .5rem; --spacing-xs: .5rem; --spacing-sm: .75rem; --spacing-md: 1rem; --spacing-lg: 1.5rem; --duration-fast: .15s; --easing-smooth: cubic-bezier(.4, 0, .2, 1) } :root, :host { --shadow-sm: 0 0 0 1px light-dark(oklch(0% 0 none / .04), oklch(100% 0 none / .05)), 0 1px 1px -.5px light-dark(oklch(0% 0 none / .045), oklch(0% 0 none / .3)), 0 2px 2px -1px light-dark(oklch(0% 0 none / .04), oklch(0% 0 none / .26)), 0 4px 4px -2px light-dark(oklch(0% 0 none / .04), oklch(0% 0 none / .24)); --shadow-md: 0 0 0 1px light-dark(oklch(0% 0 none / .05), oklch(100% 0 none / .06)), 0 1px 1px -.5px light-dark(oklch(0% 0 none / .045), oklch(0% 0 none / .3)), 0 2px 2px -1px light-dark(oklch(0% 0 none / .04), oklch(0% 0 none / .26)), 0 4px 4px -2px light-dark(oklch(0% 0 none / .04), oklch(0% 0 none / .24)), 0 8px 8px -4px light-dark(oklch(0% 0 none / .045), oklch(0% 0 none / .26)) } :root, :host { --color-brand: #4f46e5; --color-text-action: var(--color-text-strong); --color-focus-action: var(--color-text-strong); --spacing-action-x: 1rem; --radius-action: .375rem }}@layer utilities { .text-lg { font-size: var(--text-lg); line-height: var(--text-lg--line-height, normal); letter-spacing: var(--text-lg--letter-spacing, normal) } .text-sm { font-size: var(--text-sm); line-height: var(--text-sm--line-height, normal); letter-spacing: var(--text-sm--letter-spacing, normal) } .text-xs { font-size: var(--text-xs); line-height: var(--text-xs--line-height, normal); letter-spacing: var(--text-xs--letter-spacing, normal) } .content-auto { content-visibility: auto; contain-intrinsic-size: auto 32rem } .items-center { align-items: center } .bg-brand { background-color: var(--color-brand) } .b-line-divider { border-color: var(--color-line-divider) } .r-lg { border-radius: var(--radius-lg) } .pill { border-radius: calc(infinity * 1px) } .b-solid { border-style: solid } .b\(1\) { border-width: calc(1 * 1px) } .fg-text-muted { color: var(--color-text-muted) } .flex { display: flex } .font-regular { font-weight: var(--font-weight-regular) } .gap-xs { gap: var(--spacing-xs) } .h\(8\) { height: calc(8 * 1px) } .m\(0\) { margin: calc(0 * 1px) } .mb-md { margin-bottom: var(--spacing-md) } .mb-xs { margin-bottom: var(--spacing-xs) } .mt-sm { margin-top: var(--spacing-sm) } .mt\(0\) { margin-top: calc(0 * 1px) } .p-lg { padding: var(--spacing-lg) } .w\(8\) { width: calc(8 * 1px) } @media (prefers-reduced-motion: no-preference) { .\(transition-duration\:var\(--duration-fast\)\)\@theme-motion { transition-duration: var(--duration-fast) } } @media (prefers-reduced-motion: no-preference) { .\(transition-property\:box-shadow\,background-color\)\@theme-motion { transition-property: box-shadow, background-color } } @media (prefers-reduced-motion: no-preference) { .\(transition-timing-function\:var\(--easing-smooth\)\)\@theme-motion { transition-timing-function: var(--easing-smooth) } }}项目入口已导入的软件包本就可供局部样式表使用。如果只想将软件包加入单个样式表的编译上下文,请使用 @reference:
@reference '@acme/ui-theme';.button { padding-inline: var(--spacing-action-x); border-radius: var(--radius-action); background-color: var(--color-brand);}局部 .button 使用软件包的间距、圆角和品牌令牌。编译此样式表时,@reference 提供定义,输出也包含必要主题资源。它不会复制软件包的原生 .btn 规则或生成的工具类。需要这些规则时,请另行加载软件包 CSS。局部样式表模式请参阅路由级样式。
#在使用方覆盖公开值
将应用专属选择放在软件包导入之后。例如,此应用加宽共享操作按钮,无需复制组件 CSS:
@import '@master/css';@import '@acme/ui-theme';@theme { --spacing-action-x: 1.5rem;}.btn 仍读取 var(--spacing-action-x)。应用将水平内边距从 1rem 改为 1.5rem;其他应用保留各自值。请记录使用方可依赖的令牌名称、组件选择器和工具类名称。即使软件包不含 JavaScript,重命名任何公开名称仍会影响使用方的标记或样式表。
覆盖值,保留组件
使用方覆盖位于软件包导入之后。内缩辅助线以 16px 根字体比较 1rem 和 1.5rem;.btn 规则仍然共享。
文字说明
软件包与应用共享同一份 .btn 定义。应用修改 --spacing-action-x,增大水平内边距,同时保留按钮本身的角色。
#应用策略留在应用内部
可复用的样式编写软件包不应决定某个应用如何扫描、筛选或发布源文件。
下列内容保留在使用方应用中:
- 应用专属的
@source和@source not规则。 - 应用专属的
@safelist和@blocklist策略。 - 路由、页面或组件的一次性类名。
- 生成的 manifest JSON 和编译后的 CSS 输出。
使用软件包提供共享词汇;使用应用 CSS 入口管理局部源代码边界、交付选择和应用专属策略。
#使用示例验证
从软件包目录打包源代码。npm pack 会创建本地归档文件,不会发布到包注册表:
npm pack按照示例名称和版本,归档文件为 acme-ui-theme-0.1.0.tgz。确认其中包含 master.css 和预期的 package.json 导出。files 列表会排除无关源代码和构建产物;npm 还会包含标准元数据文件。
将归档文件安装到已集成 Master CSS 的小型使用方应用:
npm install ../my-master-theme/acme-ui-theme-0.1.0.tgznpm run build将上方标记放入会扫描的源文件。在生产输出中检查原生 .btn 规则、请求的 content-auto 工具类和必要主题资源。再打开应用,检查按钮计算后的内边距,并加入局部覆盖后重复检查。除静止状态外,也要验证悬停、键盘焦点和减少动态效果。
使用框架集成的构建验证软件包解析。当前独立 CLI 的检查可能加载应用入口,却将第三方裸包导入留在样式表图之外。请检查列出的样式表依赖:如果不包含软件包的 master.css,该检查就尚未验证此词汇。生产使用方还可验证本地仅源码预览无法检查的文件和导出路径。



