create-shopify-scss-autofill 0.6.1 → 0.6.3

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
  {
2
2
  "name": "create-shopify-scss-autofill",
3
- "version": "0.6.1",
3
+ "version": "0.6.3",
4
4
  "description": "Scaffold scss-kit (SCSS workflow + responsive autofill) for Shopify theme development.",
5
5
  "keywords": [
6
6
  "shopify",
@@ -1,6 +1,6 @@
1
1
  # scss-kit 维护备忘(持续迭代)
2
2
 
3
- 更新时间:2026-03-19
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
- - 并行:responsive:watch + css:watch + theme:watch
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 依赖是“可选按需加载”,保证“先 init 再 install”的接入流程可用。
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
- return entries.filter(Boolean)
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
- ` :root {\n --px-to-vw: calc(100vw / ${desktopWidth});\n }\n\n` +
772
- ` @media screen and (max-width: ${mobileMax}px) {\n :root {\n --px-to-vw-mb: calc(100vw / ${mobileWidth});\n }\n }\n` +
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
- ` :root {\n --px-to-vw: calc(100vw / ${desktopWidth});\n }\n\n` +
819
+ pcVarsBlock +
820
+ `\n` +
797
821
  ` @media screen and (max-width: ${mobileMax}px) {\n` +
798
- ` :root {\n --px-to-vw-mb: calc(100vw / ${mobileWidth});\n }\n\n` +
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
- const scss = generateAutofillScss(config, collected)
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
- (f) => path.resolve(f) !== path.resolve(outputAbs)
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) {
@@ -1579,15 +1621,16 @@ function main() {
1579
1621
  JSON.stringify(
1580
1622
  {
1581
1623
  action: 'responsive:generate:entries',
1582
- ok: false,
1624
+ ok: true,
1625
+ skipped: true,
1583
1626
  reason:
1584
- 'missing autofill.entries in scss-kit.config.json (expected array of entry scss paths)',
1627
+ 'autofill.entries is empty — nothing to generate. Create .scss files in src/styles/ and the responsive:watch watcher will register them automatically.',
1585
1628
  },
1586
1629
  null,
1587
1630
  2
1588
1631
  )
1589
1632
  )
1590
- process.exit(1)
1633
+ process.exit(0)
1591
1634
  }
1592
1635
 
1593
1636
  // Load incremental cache
@@ -1596,7 +1639,7 @@ function main() {
1596
1639
  const nextCache = { _configHash: configHash }
1597
1640
 
1598
1641
  const results = []
1599
- for (const entryRel of entries) {
1642
+ for (const { file: entryRel, varScope: entryVarScope } of entries) {
1600
1643
  const entryAbs = path.isAbsolute(entryRel)
1601
1644
  ? entryRel
1602
1645
  : path.join(ROOT, entryRel)
@@ -1638,7 +1681,7 @@ function main() {
1638
1681
 
1639
1682
  try {
1640
1683
  const collected = scanScssForAutofill(entryAbs, autofill)
1641
- const scss = generateAutofillScss(config, collected)
1684
+ const scss = generateAutofillScss(config, collected, entryVarScope)
1642
1685
  const res = writeFileSafely(outAbs, scss, {
1643
1686
  overwriteIfContains: 'Generated by scss-kit',
1644
1687
  })
@@ -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 = [] // 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 }
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 // index in output where the marker line sits
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 (blockText.includes('--px-to-vw:') && !blockText.includes('--px-to-vw-mb')) {
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('sass', sassArgs, {
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": {