命令行预览
English · 简体中文
改一个 designWidth、挪一次 fluid 区间,要看效果就得重新构建,再去浏览器里肉眼比对。这条命令把这一步缩短成一次回车:
npx adaptive-matrix src/styles/app.csssrc/styles/app.css
profiles: app (default), pc, +5 library canvases
.page
padding 16px → clamp(13.65333px, 4.26667vw, 20.48px)
font-size 16px → clamp(0.94867rem, calc(0.65rem + 1.49333vw), 1.098rem)
@media (min-width: 768px) › .page
padding 48px → clamp(34.13333px, 3.33333vw, 64px)
3 converted, 0 left as authored输出的是逐条声明的前后对照,不是整份 CSS。构建工具跑出来的产物动辄几千行,而你想确认的通常只有那么几个数字。
用法
adaptive-matrix <file...> [options]
adaptive-matrix [options] -- <file...>
cat app.css | adaptive-matrix --from src/app.css可以用 - 显式表示 stdin,但不能把它与文件路径混用,否则输入位置不同就会有一份源文件被静默忽略。同理,--from 只能用于一个输入;多个文件不可能共用一个逻辑路径后仍保持各自的文件路由含义。
| 选项 | 作用 |
|---|---|
-c, --config <path> | 默认导出插件选项的模块,或一个写着选项的 .json 文件 |
--from <path> | 把输入当作位于这个路径 |
--profile <name> | 覆盖 defaultProfile |
--targets <list> | 按你要支持的最低浏览器版本审计产物,如 "safari 14, ios_saf 13" |
--fail-on <list> | 遇到 warnings、continuity 或 compatibility 时以 1 退出;可逗号组合,或写 any |
--all | 连未改动的声明一起列出 |
--css | 打印编译后的完整 CSS,而不是对照表 |
--json | 输出一份带版本号的 JSON 报告,供 CI、编辑器与看板集成 |
--color / --no-color | 强制开/关颜色;都不写则跟随终端,并遵守 NO_COLOR |
-- | 停止解析选项;其后所有参数均视为文件路径,包括以 - 开头的文件名 |
-h, --help | 帮助 |
-v, --version | 输出当前安装包版本,不执行编译 |
版本查询与帮助一样属于信息输出:即使带 --json 也只打印纯文本版本行,不加载配置或输入文件。同时指定帮助和版本时,优先输出帮助。
需要值的长选项支持两种写法:--profile app 或 --profile=app。
退出码:编译及所有已请求门禁均通过为 0;门禁失败、参数错误、文件读不到、配置非法为 1。所以可以直接串进 shell 判断。
读配置
不传 -c 就用内置默认值,表头会告诉你当前是哪几个画布。
npx adaptive-matrix src/app.css -c adaptive.config.mjs配置模块要默认导出插件选项对象,不是 PostCSS 配置。两者不一样时单独写一个:
// adaptive.config.mjs
import { appPcPreset } from 'postcss-adaptive-matrix'
export default appPcPreset({ appDesignWidth: 375, pcDesignWidth: 1440 })TypeScript 配置需要一个加载器:
npx tsx node_modules/postcss-adaptive-matrix/dist/cli.js src/app.css -c adaptive.config.ts(Node 22.6+ 自带类型擦除,直接 npx adaptive-matrix -c adaptive.config.ts 也能跑,但会打一条 experimental 警告,且只支持可擦除的写法。)
用 JSON 写配置
路径以 .json 结尾时,按文件读取,不走 import。在 $schema 里写上已发布的 JSON Schema,编辑器就能补全选项名、显示每一项的说明,并在取值越界时当场标出来——不用等到跑起来:
{
"$schema": "https://moresyl.github.io/postcss-adaptive-matrix/schema/options.json",
"defaultProfile": "app",
"profiles": {
"app": { "designWidth": 375, "fluid": { "minWidth": 320, "maxWidth": 600 } },
"pc": { "designWidth": 1440, "fluid": { "minWidth": 1024, "maxWidth": 1920 } }
}
}$schema 不是配置项,读取时会被丢掉。
JSON 装不下正则和函数,所以按正则路由、或按文件动态算 designWidth 的配置只能继续用 JavaScript 写。Schema 里这些位置都用 x-also 标了出来。
配置在读第一个样式文件之前就会被校验,所以 defaultProfile 写错、fluid 区间反了,报的是编译器自己的那句话,不会先刷一屏对照表再报错。
default 漏写(export const options = {...})或预设忘了调用(export default appPcPreset 而不是 appPcPreset({...}))都会直接报错。这两种写法此前会静默地用内置默认值跑完——表头照样列出画布,对照表看着也对,没有任何地方说过你的配置根本没被读到。
验证文件路由
按路径判定画布是最容易配错、又最不容易发现的一项——写错了不报错,只是路由静默失效(见构建工具集成)。--from 就是用来在上线前把路由试一遍的:
# 同一份 CSS,假装它在 desktop 目录下
npx adaptive-matrix scratch.css -c adaptive.config.mjs --from src/desktop/scratch.css两次输出的数字不一样,说明路由命中了。一样,就是没命中。
Vue SFC 的路径带 query 串,照抄进去即可:
npx adaptive-matrix scratch.css --from 'src/views/mobile/home/index.vue?vue&type=style&lang.scss'断点处的倒退
两张设计稿各自都对,接缝处未必对。这个检查专门找一种情况:窗口变宽,某个尺寸反而变小了。
shrinks .card font-size gets smaller at 768px: 17.57px → 16.18px
clamp(0.94867rem, ...) → clamp(1.01125rem, ...). Widening the window makes this smaller — the two canvases disagree here..card 的字号在 App 稿上写 16px、PC 稿上写 18px,两个数字单独看都合理。但 App 稿到 767px 时已经流体放大到 17.57px,而 PC 稿从 768px 起步只有 16.18px。于是把浏览器拉宽一个像素,正文字号会突然变小。
之所以值得单独检查,是因为它只在那一个宽度上出现:两张稿子各自渲染都正常,日常调试的 375 和 1440 也都正常。
检查会在已知宽度断点两侧采样可求值的长度,并比较其绝对值。这是一项诊断,不是所有宽度上连续性的证明:无法解析的表达式、不支持的条件以及依赖布局的值仍可能无法判断。
哪些值会参与检查
作者原本写下的样式也可能在断点处缩小。例如,下面两侧都是纯像素值:
/* Quasar 2.19 自己的样式表 */
.q-tooltip { padding: 6px 10px }
@media (max-width: 599.98px) { .q-tooltip { padding: 8px 16px } }两侧都是纯像素的声明不会进入这项诊断。工具无法仅凭这些数值确定作者的意图。
至少一侧需要包含数学函数(calc、min、max 或 clamp)或视口长度。这种语法启发式包含 strategy: 'viewport' 的裸值输出,也包含作者手写的公式和视口值,并不追踪代码来源:发现的问题可能在源样式里就已存在。将问题归因于转换之前,应先核对原始声明。两侧采样值仍须都能求值,才会报告缩小。
代入发生在判断之前,所以「声明只写了 var(--x)、公式在 token 里」也算数。
比的是绝对值而不是数值:负长度(负外边距、外溢)本来就靠远离零来变大,-16px 编译出的公式是越宽越小的。断点两侧变号则一律不报——零是编译器自己不会跨过去的界,在这里遇到它说明两张稿的意图本就不同,谁对谁错不是这个检查能判断的。
怎么改由你决定——抬高 PC 稿的字号、把 pcFluidMin 从 1024 降到 768、或者收窄 App 稿的 fluid.maxWidth。工具只负责告诉你接缝在哪。
检查刻意收得很窄,宁可不报也不误报:只比对同一个选择器串、不做优先级推算、不展开简写;遇到 @supports、@container、非纯宽度的媒体查询、跨层叠层的分组,以及 env() / % / 容器单位这类算不出数值的值,整组跳过。
主题 token 会被代入
组件库的尺寸几乎不写字面量。Vant 4.10.0 的 3198 条普通声明里,有 1173 条完全经 var() 读取——检查若在第一个 var( 就放弃,看到的就只是一小片,而且恰好避开了本插件要适配的那一层。
所以同一份样式表里的自定义属性会先被代入,再求值:
:root,:host { --card-width: 40px }
.card { width: var(--card-width) }
@media (min-width: 768px) { .card { width: 20px } }这里 .card 在 768px 处从 40px 掉到 20px,代入之后才看得见。
代入只在值由视口宽度单独决定时进行,两条都要满足:
- 只声明在
:root/:host/html上,别处没有第二份。.van-theme-dark { --card-width: ... }一出现就整个放弃——元素的取值取决于祖先有没有那个 class,这不是宽度能回答的。 - 每一处声明要么无条件,要么位于纯像素宽度的
@media里。@supports、@container、方向查询里的声明同样放弃。
token 本身在断点处被改写也算一次接缝,即使消费它的规则只写了一次:
:root { --card-width: 40px }
@media (min-width: 768px) { :root { --card-width: 20px } }
.card { width: var(--card-width) } /* 只有一条,照样报 */var(--x, 16px) 的兜底值只在 --x 确实没被声明时使用——这与浏览器一致。若 --x 被声明了但取值不可知(比如上面的主题 class),则不报,而不是拿兜底值当答案。
自定义属性声明本身(--x: ... 这一条)仍然不检查。它不是屏幕上的长度,方向对不对由消费方决定——本插件自己的 --adaptive-root-width 就是反的:它喂给 max(0px, (100vw - var(--adaptive-root-width)) / 2),值变小正是为了让留白变大。跳过它不等于忽略它:消费它的那条声明会被检查,代入之后它的方向才有意义。
实测(Vant 4.10.0 完整样式表,195 KB):可求值的值分量从 622 条(17.6%)升到 1309 条(36.9%),收录 779 个 token。剩下的是关键字、颜色和百分比——本来就不是视口相关的长度。
不能把 shrinks 报告直接解释为编译器引入了回归:断点处的缩小可能已经存在于源样式表中。应对比源文件和编译产物的诊断,再检查相关规则。同样,零报告不证明零误报,也不认证布局连续性,因为无法求值的内容和条件会被跳过。请针对实际包版本重新运行组件库验证器,不要依赖历史汇总数量。
要让构建直接失败,同一个检查也从包里导出:
import postcss from 'postcss'
import adaptiveMatrix, { findContinuityIssues } from 'postcss-adaptive-matrix'
const result = await postcss([adaptiveMatrix(options)]).process(css, { from })
const issues = findContinuityIssues(result.root)
if (issues.length) throw new Error(`${issues.length} 处断点倒退`)目标浏览器读不懂的语法
--targets 收一串「浏览器 + 你打算支持的最低版本」,逐条对着编译产物核:
可以重复传入 --targets;各次会按浏览器合并并保留最低版本,分段生成的目标列表也不会意外放宽兼容范围。
npx adaptive-matrix src/app.css -c adaptive.config.mjs --targets "ios_saf 13, chrome 90" 需要 @layer — iOS Safari 13 < 15.4,Chrome 90 < 99
来源:root.layer,appPcPreset 将其设置为 'adaptive-matrix' …
发现:@layer adaptive-matrix { :where(#app) {
不支持时:整个 @layer 块会被丢弃,连同根级基础样式一起消失 …
替代:root.layer: false 会输出不带包裹层的相同规则 …
需要 clamp()、min()、max() — iOS Safari 13 < 13.4-13.7四行的顺序是有意的:先说丢什么,再说换成什么。「iOS Safari 13 太老了」单独拿出来没法行动,而 CSS 支持缺口真正要紧的一直是「跟着一起消失的有多少」——值读不懂丢一条声明,选择器读不懂丢一整条规则,@ 规则读不懂丢一整块。
上面 clamp() 那条差的是一个小版本:13.4 就有了。人工比对版本号时这种差距最容易看漏。
目标全都够用时只有一行:
所有目标浏览器都能读取本次输出中的 7 项 CSS 特性已知名字:chrome、edge、safari、firefox、ios_saf、samsung;android、webview 归到 chrome。名字不认识会报错并以 1 退出,不会静默跳过——被悄悄丢掉的目标比没有审计更糟,因为它读起来像通过了。
完整的特性 × 版本表、每一项的降级路径,以及编程式 API,见浏览器特性支持与降级。
CI 质量门禁
发现与阻断分离:普通预览会报告问题但退出 0;只把你希望阻断流水线的类别显式打开:
npx adaptive-matrix src/app.css \
--targets "ios_saf 15.4, chrome 99" \
--fail-on warnings,continuity,compatibilitywarnings:编译器诊断,例如未知@adaptiveprofile;continuity:编译后的长度在断点处倒退;compatibility:产物语法超出--targets声明的浏览器能力。选择它却没传--targets会直接报错,绝不会空跑成通过。
any 一次启用三类。完整发现仍会输出,随后命令以 1 退出,并把简洁计数写到 stderr。配合 --css 时 stdout 仍然只有 CSS;配合 --json 时编译仍为 "ok": true,独立的 gate 表示策略是否通过,调用方可以区分「输入非法」与「产物有效但违反策略」。
机器可读报告
结果交给程序而不是人读时,用 --json:
npx adaptive-matrix src/app.css src/admin.css \
-c adaptive.config.json \
--targets "ios_saf 13, chrome 90" \
--json > adaptive-report.json即使输入多个文件,命令也只写一份 JSON 文档。顶层结构稳定且带版本号:
{
"formatVersion": 1,
"ok": true,
"profiles": { "default": "app", "authored": ["app", "pc"], "libraries": 6 },
"targets": { "ios_saf": "13", "chrome": "90" },
"gate": { "failOn": ["continuity", "compatibility"], "passed": false },
"summary": {
"files": 2,
"declarations": 31,
"converted": 18,
"unchanged": 13,
"warnings": 0,
"continuityIssues": 1,
"compatibilityFindings": 2
},
"files": []
}每个文件都带声明变化(context、prop、before、after)、编译器告警、结构化断点连续性问题;传了 --targets 时,还会包含稳定特性 ID、实际语法样本、受影响浏览器、失败方式及降级方案。未传 --fail-on 时 gate 为 null。默认 changes 只含已转换/生成的声明;加 --all 才包含原样保留项。
失败同样可机器解析,并保留退出码 1:
{
"formatVersion": 1,
"ok": false,
"error": { "message": "defaultProfile \"ghost\" does not exist." }
}--json 与 --css 是互斥的输出协议。JSON 一律写 stdout,且不含 ANSI 颜色码。TypeScript 调用方可以直接从包中导入 CliJsonReport、CliSuccessReport、CliErrorReport、CliQualityGateReport 与 CLI_REPORT_FORMAT_VERSION,无需自行重复声明结构。
看整份产物
| 结果 | ok | gate | 退出码 |
|---|---|---|---|
| 处理成功,未请求门禁 | true | null | 0 |
| 处理成功,所选门禁通过 | true | passed: true | 0 |
| 处理成功,所选门禁失败 | true | passed: false | 1 |
| 参数、配置、读取或编译错误 | false | 不存在 | 1 |
对于成功解析且格式版本受支持的报告,只有 report.ok && report.gate?.passed !== false 才应接受;同时要求子进程正常退出且退出码为 0。这个条件不是运行时 JSON Schema 校验:无输出、非法 JSON 和进程失败仍需分别处理。
多文件 JSON 运行中,如果后续任一文件读取或编译失败,只会输出一份错误文档,不会返回部分成功报告;此前文件的结果也不会出现在这份错误响应中。相反,全部编译成功但所选质量门禁失败时,会保留完整文件列表和汇总,并返回 ok: true、gate.passed: false。自动化调用应同时检查退出码和这两类结果的区别。
要接着给别的工具处理,用 --css:
npx adaptive-matrix src/app.css --css > out.css这是 shell 重定向,不是原子写文件功能。编译开始前,shell 就可能清空已有的 out.css,因此绝不要重定向到输入文件本身。多文件 CSS 输出在后续文件失败时也可能只有部分内容;质量门禁失败仍会输出 CSS。用于构建产物时,应先把 stdout 保存到临时文件,确认退出码为 0 后再替换目标文件,失败则保留旧产物。若构建需要先检查结果再决定如何写入,可使用程序化 API。
--css 模式的 stdout 只有样式表文本;编译器警告、连续性发现和浏览器兼容证据全部走 stderr,包括门禁失败背后的详情。
逐文件 CSS 和对照输出遵守 stdout 背压:下游缓冲区满时,CLI 会等待后再处理下一个输入。等待期间流关闭或报错会让命令失败,并清理临时监听器。这不是恒定内存编译:每份样式表仍完整读取和解析,JSON 报告仍先汇总再序列化。下游失败后,已经送出的字节无法撤回。
多个输入会独立编译,按参数顺序输出,以换行分隔。这不是打包:不会解析或调整 import,不会重写相对资源 URL,也不会合并各文件生成的基础样式。不能假设拼接产物与分别加载样式表的行为相同。应让每份输入对应独立输出产物,或交给构建工具完成打包及资源解析。
读懂输出
JSON 报告、帮助文本和最终批次汇总同样等待 stdout 背压。如果 JSON 报告写入失败,CLI 会向 stderr 报告输出错误,不会尝试往已损坏的流追加另一个 JSON 对象。
16px → clamp(...)—— 换算了- 灰色无箭头的一行 —— 原样保留(
--all才显示)。细线、@font-face里的长度、被忽略注释标记的声明都在这里 + ...—— 编译器新增的声明,比如根容器基础样式、preserveOriginal的降级值@media (min-width: 768px) › .page—— 声明所在位置由外向内。@adaptive pc编译后就是这个样子,同一个.page出现两次是正常的warning ...—— 编译器的告警,原样透传shrinks ...—— 跨断点时尺寸倒退,见断点处的倒退needs ...—— 目标浏览器读不懂的语法,只在传了--targets时出现,见目标浏览器读不懂的语法
表头的 +5 library canvases 是内置组件库各自的画布。它们由注册表生成,不是你能在 @adaptive 里写的名字,所以只报个数。完整清单见组件库适配。