create-shopify-scss-autofill 0.6.1 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# scss-kit 维护备忘(持续迭代)
|
|
2
2
|
|
|
3
|
-
更新时间:2026-03-
|
|
3
|
+
更新时间:2026-03-31
|
|
4
4
|
|
|
5
5
|
这份文档的目的:把 scss-kit 的“关键约束 / 设计决策 / 发布要点”固化下来,方便我们后续持续更新、迭代而不走回头路。
|
|
6
6
|
|
|
@@ -43,12 +43,14 @@
|
|
|
43
43
|
- 默认 safe mode:Sass 输出到 `src/.sass-out/` → 安全同步到 `assets/`
|
|
44
44
|
|
|
45
45
|
- `npm run dev:theme:auto`
|
|
46
|
-
-
|
|
46
|
+
- 启动前先运行 `scss-kit:generate` + `scss-kit:responsive:generate` + `scss-kit:responsive:generate:entries`
|
|
47
|
+
- 然后并行:responsive:watch + css:watch + theme:watch
|
|
47
48
|
|
|
48
49
|
## 扫描器策略(稳定性)
|
|
49
50
|
|
|
50
51
|
- 优先 AST(`postcss` + `postcss-scss`),失败回退 legacy 正则扫描。
|
|
51
|
-
- AST
|
|
52
|
+
- AST 依赖是"可选按需加载",保证"先 init 再 install"的接入流程可用。
|
|
53
|
+
- `responsive:generate` 扫描时会排除所有 `_responsive-autofill*.generated.scss` 文件,防止循环扫描。
|
|
52
54
|
|
|
53
55
|
## 发布/脚手架(npm create)
|
|
54
56
|
|
|
@@ -65,6 +67,7 @@
|
|
|
65
67
|
## 扩展能力
|
|
66
68
|
|
|
67
69
|
- **r.re() 短写映射**:`RE_SHORTHAND_MAP` 存放快捷写法(如 `grid-cols-N`、`span-N`),PC 侧由 SCSS `$_re-shorthands` map + `_expand-re()` 展开,Mobile 侧由 JS `expandReValue()` 展开。
|
|
70
|
+
- **varScope**:`autofill.entries` 支持 `{ file, varScope }` 对象格式,把 CSS 变量作用域限定到指定选择器。
|
|
68
71
|
- **VS Code 代码片段**:`.vscode/scss-kit.code-snippets` 提供函数签名补全。
|
|
69
72
|
- **增量缓存**:`.scss-kit-cache.json`(已加入 `.gitignore`)存储源文件哈希与配置哈希。
|
|
70
73
|
|
|
@@ -57,6 +57,23 @@
|
|
|
57
57
|
- 顶部 `@use "./_responsive-autofill.<page>.generated" as auto;`
|
|
58
58
|
- 底部 `@include auto.responsive_autofill_overrides();`(确保覆盖顺序)
|
|
59
59
|
|
|
60
|
+
### varScope(可选)
|
|
61
|
+
|
|
62
|
+
默认情况下,`--px-to-vw` 和 `--px-to-vw-mb` 变量声明在 `:root` 上。如果页面需要将变量作用域限定到特定选择器(例如 section 级别),可以在 `autofill.entries` 中使用对象格式:
|
|
63
|
+
|
|
64
|
+
```json
|
|
65
|
+
{
|
|
66
|
+
"autofill": {
|
|
67
|
+
"entries": [
|
|
68
|
+
"src/styles/page-a.scss",
|
|
69
|
+
{ "file": "src/styles/page-b.scss", "varScope": ".page-b-wrapper" }
|
|
70
|
+
]
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`page-b` 生成的 CSS 变量将挂载在 `.page-b-wrapper` 而非 `:root`。
|
|
76
|
+
|
|
60
77
|
推荐约定:
|
|
61
78
|
|
|
62
79
|
- 字体/需要下限兜底的值:使用 `r.resp(pc, mobile, desktopType[, mobileType])`
|
|
@@ -174,7 +174,7 @@ function loadConfig() {
|
|
|
174
174
|
}
|
|
175
175
|
|
|
176
176
|
function toPosix(p) {
|
|
177
|
-
return p.replaceAll('
|
|
177
|
+
return p.replaceAll('\\', '/')
|
|
178
178
|
}
|
|
179
179
|
|
|
180
180
|
function ensurePx(v) {
|
|
@@ -519,11 +519,10 @@ function generateReShorthandMap() {
|
|
|
519
519
|
}
|
|
520
520
|
// opacity shortcuts (0, 10, 20, ..., 100)
|
|
521
521
|
for (let o = 0; o <= 100; o += 10) {
|
|
522
|
-
entries.push(` opacity-${o}: ${o / 100}`)
|
|
522
|
+
entries.push(` opacity-${o}: ${o === 0 ? 0 : o / 100}`)
|
|
523
523
|
}
|
|
524
524
|
entries.push(` opacity-5: 0.05`)
|
|
525
525
|
entries.push(` opacity-25: 0.25`)
|
|
526
|
-
entries.push(` opacity-50: 0.5`)
|
|
527
526
|
entries.push(` opacity-75: 0.75`)
|
|
528
527
|
return '\n' + entries.join(',\n') + ',\n'
|
|
529
528
|
}
|
|
@@ -746,13 +745,22 @@ function getAutofillEntries(cfg) {
|
|
|
746
745
|
if (!Array.isArray(entries)) {
|
|
747
746
|
throw new Error('autofill.entries must be an array of entry scss paths')
|
|
748
747
|
}
|
|
749
|
-
|
|
748
|
+
// Each entry may be a string path or an object { file, varScope }
|
|
749
|
+
return entries.filter(Boolean).map((e) => {
|
|
750
|
+
if (typeof e === 'string') return { file: e, varScope: null }
|
|
751
|
+
if (e && typeof e === 'object' && typeof e.file === 'string')
|
|
752
|
+
return { file: e.file, varScope: e.varScope ?? null }
|
|
753
|
+
throw new Error(
|
|
754
|
+
'autofill.entries items must be a string path or { file, varScope }'
|
|
755
|
+
)
|
|
756
|
+
})
|
|
750
757
|
}
|
|
751
758
|
|
|
752
|
-
function generateAutofillScss(cfg, collectedRules) {
|
|
759
|
+
function generateAutofillScss(cfg, collectedRules, varScope) {
|
|
753
760
|
const { ns, mobileMax } = getAutofill(cfg)
|
|
754
761
|
const desktopWidth = cfg?.design?.desktopWidth ?? 1920
|
|
755
762
|
const mobileWidth = cfg?.design?.mobileWidth ?? 750
|
|
763
|
+
const scope = varScope ?? ':root'
|
|
756
764
|
|
|
757
765
|
const header = `@use "./responsive" as ${ns};
|
|
758
766
|
|
|
@@ -764,12 +772,27 @@ function generateAutofillScss(cfg, collectedRules) {
|
|
|
764
772
|
const mixinHeader = `@mixin responsive_autofill_overrides() {\n`
|
|
765
773
|
const mixinFooter = `\n}\n`
|
|
766
774
|
|
|
775
|
+
// PC vars block: 2-space indent inside mixin, 4-space for property
|
|
776
|
+
const pcVarsBlock =
|
|
777
|
+
` ${scope} {\n` +
|
|
778
|
+
` --px-to-vw: calc(100vw / ${desktopWidth});\n` +
|
|
779
|
+
` }\n`
|
|
780
|
+
|
|
781
|
+
// MB vars block: 4-space indent inside @media, 6-space for property
|
|
782
|
+
const mbVarsBlock =
|
|
783
|
+
` ${scope} {\n` +
|
|
784
|
+
` --px-to-vw-mb: calc(100vw / ${mobileWidth});\n` +
|
|
785
|
+
` }\n`
|
|
786
|
+
|
|
767
787
|
if (!collectedRules.length) {
|
|
768
788
|
return (
|
|
769
789
|
header +
|
|
770
790
|
mixinHeader +
|
|
771
|
-
|
|
772
|
-
|
|
791
|
+
pcVarsBlock +
|
|
792
|
+
`\n` +
|
|
793
|
+
` @media screen and (max-width: ${mobileMax}px) {\n` +
|
|
794
|
+
mbVarsBlock +
|
|
795
|
+
` }\n` +
|
|
773
796
|
mixinFooter
|
|
774
797
|
)
|
|
775
798
|
}
|
|
@@ -793,9 +816,11 @@ function generateAutofillScss(cfg, collectedRules) {
|
|
|
793
816
|
return (
|
|
794
817
|
header +
|
|
795
818
|
mixinHeader +
|
|
796
|
-
|
|
819
|
+
pcVarsBlock +
|
|
820
|
+
`\n` +
|
|
797
821
|
` @media screen and (max-width: ${mobileMax}px) {\n` +
|
|
798
|
-
|
|
822
|
+
mbVarsBlock +
|
|
823
|
+
`\n` +
|
|
799
824
|
blocks.map((b) => b.replaceAll(/^/gm, ' ')).join('\n\n') +
|
|
800
825
|
`\n }` +
|
|
801
826
|
mixinFooter
|
|
@@ -883,7 +908,7 @@ function toScssMap(obj) {
|
|
|
883
908
|
const entries = Object.entries(obj)
|
|
884
909
|
.map(([k, v]) => ` ${k}: ${v},`)
|
|
885
910
|
.join('\n')
|
|
886
|
-
return `(${entries}\n)`
|
|
911
|
+
return `(\n${entries}\n)`
|
|
887
912
|
}
|
|
888
913
|
|
|
889
914
|
function generateResponsiveScss(cfg) {
|
|
@@ -1184,7 +1209,7 @@ function patchPackageJson(cfg) {
|
|
|
1184
1209
|
'dev:theme':
|
|
1185
1210
|
'concurrently -k -n CSS,THEME "npm:css:watch" "npm:theme:watch"',
|
|
1186
1211
|
'dev:theme:auto':
|
|
1187
|
-
'npm run scss-kit:generate && npm run scss-kit:responsive:generate && concurrently -k -n AUTO,CSS,THEME "npm:responsive:watch" "npm:css:watch" "npm:theme:watch"',
|
|
1212
|
+
'npm run scss-kit:generate && npm run scss-kit:responsive:generate && npm run scss-kit:responsive:generate:entries && concurrently -k -n AUTO,CSS,THEME "npm:responsive:watch" "npm:css:watch" "npm:theme:watch"',
|
|
1188
1213
|
}
|
|
1189
1214
|
for (const [k, v] of Object.entries(watchScripts)) {
|
|
1190
1215
|
if (pkg.scripts[k] && pkg.scripts[k] !== v) conflicts.push(`scripts.${k}`)
|
|
@@ -1491,7 +1516,15 @@ function main() {
|
|
|
1491
1516
|
const backupPath = backupFile(outAbs)
|
|
1492
1517
|
try {
|
|
1493
1518
|
const collected = scanScssForAutofill(targetAbs, autofill)
|
|
1494
|
-
|
|
1519
|
+
// Look up varScope for this entry from config
|
|
1520
|
+
const entryVarScope =
|
|
1521
|
+
getAutofillEntries(config).find(
|
|
1522
|
+
(e) =>
|
|
1523
|
+
path.resolve(
|
|
1524
|
+
path.isAbsolute(e.file) ? e.file : path.join(ROOT, e.file)
|
|
1525
|
+
) === path.resolve(targetAbs)
|
|
1526
|
+
)?.varScope ?? null
|
|
1527
|
+
const scss = generateAutofillScss(config, collected, entryVarScope)
|
|
1495
1528
|
const res = writeFileSafely(outAbs, scss, {
|
|
1496
1529
|
overwriteIfContains: 'Generated by scss-kit',
|
|
1497
1530
|
})
|
|
@@ -1536,11 +1569,20 @@ function main() {
|
|
|
1536
1569
|
walkScssFiles(path.isAbsolute(d) ? d : path.join(ROOT, d), absFiles)
|
|
1537
1570
|
}
|
|
1538
1571
|
|
|
1539
|
-
// Avoid scanning the output file itself.
|
|
1572
|
+
// Avoid scanning the output file itself and any per-entry generated files.
|
|
1540
1573
|
const outputAbs = autofill.outputAbs
|
|
1541
|
-
const files = absFiles.filter(
|
|
1542
|
-
|
|
1543
|
-
|
|
1574
|
+
const files = absFiles.filter((f) => {
|
|
1575
|
+
const resolved = path.resolve(f)
|
|
1576
|
+
if (resolved === path.resolve(outputAbs)) return false
|
|
1577
|
+
// Skip all _responsive-autofill*.generated.scss files
|
|
1578
|
+
const base = path.basename(f)
|
|
1579
|
+
if (
|
|
1580
|
+
base.startsWith('_responsive-autofill') &&
|
|
1581
|
+
base.endsWith('.generated.scss')
|
|
1582
|
+
)
|
|
1583
|
+
return false
|
|
1584
|
+
return true
|
|
1585
|
+
})
|
|
1544
1586
|
|
|
1545
1587
|
const collected = []
|
|
1546
1588
|
for (const f of files) {
|
|
@@ -1596,7 +1638,7 @@ function main() {
|
|
|
1596
1638
|
const nextCache = { _configHash: configHash }
|
|
1597
1639
|
|
|
1598
1640
|
const results = []
|
|
1599
|
-
for (const entryRel of entries) {
|
|
1641
|
+
for (const { file: entryRel, varScope: entryVarScope } of entries) {
|
|
1600
1642
|
const entryAbs = path.isAbsolute(entryRel)
|
|
1601
1643
|
? entryRel
|
|
1602
1644
|
: path.join(ROOT, entryRel)
|
|
@@ -1638,7 +1680,7 @@ function main() {
|
|
|
1638
1680
|
|
|
1639
1681
|
try {
|
|
1640
1682
|
const collected = scanScssForAutofill(entryAbs, autofill)
|
|
1641
|
-
const scss = generateAutofillScss(config, collected)
|
|
1683
|
+
const scss = generateAutofillScss(config, collected, entryVarScope)
|
|
1642
1684
|
const res = writeFileSafely(outAbs, scss, {
|
|
1643
1685
|
overwriteIfContains: 'Generated by scss-kit',
|
|
1644
1686
|
})
|
|
@@ -145,12 +145,12 @@ function removeCommentOnlyBlocks(cssText) {
|
|
|
145
145
|
function hoistCssVars(cssText) {
|
|
146
146
|
const lines = cssText.split(/\r?\n/)
|
|
147
147
|
|
|
148
|
-
const pcVarLines = []
|
|
149
|
-
let mbMediaHeader = ''
|
|
150
|
-
const mbVarLines = []
|
|
148
|
+
const pcVarLines = [] // lines of :root { --px-to-vw }
|
|
149
|
+
let mbMediaHeader = '' // @media header for the --px-to-vw-mb block
|
|
150
|
+
const mbVarLines = [] // lines of :root { --px-to-vw-mb }
|
|
151
151
|
|
|
152
152
|
const output = []
|
|
153
|
-
let markerPos = -1
|
|
153
|
+
let markerPos = -1 // index in output where the marker line sits
|
|
154
154
|
|
|
155
155
|
let i = 0
|
|
156
156
|
while (i < lines.length) {
|
|
@@ -170,7 +170,10 @@ function hoistCssVars(cssText) {
|
|
|
170
170
|
const block = collectBlock(lines, i)
|
|
171
171
|
i += block.length
|
|
172
172
|
const blockText = block.join('\n')
|
|
173
|
-
if (
|
|
173
|
+
if (
|
|
174
|
+
blockText.includes('--px-to-vw:') &&
|
|
175
|
+
!blockText.includes('--px-to-vw-mb')
|
|
176
|
+
) {
|
|
174
177
|
pcVarLines.push(...block)
|
|
175
178
|
} else {
|
|
176
179
|
output.push(...block)
|
|
@@ -241,6 +244,8 @@ function hoistCssVars(cssText) {
|
|
|
241
244
|
|
|
242
245
|
return output.join('\n').replace(/\n{3,}/g, '\n\n')
|
|
243
246
|
}
|
|
247
|
+
|
|
248
|
+
function syncOne(tmpCssAbs) {
|
|
244
249
|
const relFromOut = path.relative(OUT_DIR, tmpCssAbs)
|
|
245
250
|
const targetAbs = path.join(ASSETS_DIR, relFromOut)
|
|
246
251
|
const targetRel = toPosix(path.relative(ROOT, targetAbs))
|
|
@@ -302,16 +307,18 @@ function hoistCssVars(cssText) {
|
|
|
302
307
|
function startSassWatch() {
|
|
303
308
|
ensureDir(OUT_DIR)
|
|
304
309
|
const sassArgs = [
|
|
310
|
+
'sass',
|
|
305
311
|
'--watch',
|
|
306
312
|
`${toPosix(path.relative(ROOT, STYLES_DIR))}:${toPosix(
|
|
307
313
|
path.relative(ROOT, OUT_DIR)
|
|
308
314
|
)}`,
|
|
309
315
|
'--style=expanded',
|
|
310
316
|
'--no-source-map',
|
|
317
|
+
'--silence-deprecation=if-function',
|
|
311
318
|
]
|
|
312
319
|
|
|
313
320
|
console.log('[scss-kit] starting sass watch (safe mode)')
|
|
314
|
-
const child = spawn('
|
|
321
|
+
const child = spawn('npx', sassArgs, {
|
|
315
322
|
stdio: 'inherit',
|
|
316
323
|
shell: process.platform === 'win32',
|
|
317
324
|
cwd: ROOT,
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"mobileMax": {"type": "integer", "minimum": 1},
|
|
46
46
|
"scanDirs": {"type": "array", "items": {"type": "string"}},
|
|
47
47
|
"output": {"type": "string"},
|
|
48
|
-
"entries": {"type": "array", "items": {"type": "string"}}
|
|
48
|
+
"entries": {"type": "array", "items": {"oneOf": [{"type": "string"}, {"type": "object", "required": ["file"], "properties": {"file": {"type": "string"}, "varScope": {"type": "string"}}}]}}
|
|
49
49
|
}
|
|
50
50
|
},
|
|
51
51
|
"themeKit": {
|