create-shopify-scss-autofill 0.2.0 → 0.2.1

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.
@@ -104,6 +104,7 @@ function makeDefaultConfig() {
104
104
  },
105
105
  autofill: {
106
106
  function: 'r.resp',
107
+ vwFunction: 'r.vw',
107
108
  mobileMax: 850,
108
109
  scanDirs: ['src/styles'],
109
110
  output: 'src/styles/_responsive-autofill.generated.scss',
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-shopify-scss-autofill",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
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
- 目的:在 Shopify ThemeKit 开发里实现“写 SCSS(src/styles)→ 编译 CSS(assets)→ theme watch 上传”,并提供一套基于 `r.resp(pc, mobile, desktopType[, mobileType])` 的移动端覆盖自动生成能力。
3
+ 目的:在 Shopify ThemeKit 开发里实现“写 SCSS(src/styles)→ 编译 CSS(assets)→ theme watch 上传”,并提供一套基于 `r.resp(pc, mobile, desktopType[, mobileType])` 与 `r.vw(pc, mobile)` 的移动端覆盖自动生成能力。
4
4
 
5
5
  ## 拷贝接入(新项目)
6
6
 
@@ -57,6 +57,67 @@
57
57
  - 顶部 `@use "./_responsive-autofill.<page>.generated" as auto;`
58
58
  - 底部 `@include auto.responsive_autofill_overrides();`(确保覆盖顺序)
59
59
 
60
+ 推荐约定:
61
+
62
+ - 字体/需要下限兜底的值:使用 `r.resp(pc, mobile, desktopType[, mobileType])`
63
+ - 大多数间距(padding/margin/gap):使用 `r.vw(pc, mobile)`
64
+
65
+ `r.vw(pc, mobile)` 的行为:
66
+
67
+ - PC 输出 `min(vw, px)`,例如 `r.vw(40px, 24px)` → `min(2.0833vw, 40px)`
68
+ - 自动生成的移动端覆盖直接输出 `vw`,例如 `3.2vw`
69
+
70
+ ## Responsive 函数目录(完整)
71
+
72
+ 以下函数都定义在 `src/styles/_responsive.scss`(由 `scss-kit` 生成):
73
+
74
+ ### 业务侧常用(推荐直接调用)
75
+
76
+ - `r.resp($pc, $mobile, $type, $mobile-type: null)`
77
+ - 用途:用于“字体/需要下限兜底”的场景。
78
+ - PC 输出:`clamp_pc($pc, min_px($pc, $type, desktop))`。
79
+ - Mobile 输出:由自动生成器扫描 `r.resp(...)` 后,在 `@media` 中生成 `clamp_mb(...)` 覆盖。
80
+ - 第 3/4 参数:第 3 个是 PC type;第 4 个是 mobile type(可省略,省略时沿用第 3 个)。
81
+ - 示例:`font-size: r.resp(40px, 24px, h1, h2);`
82
+
83
+ - `r.vw($pc, $mobile)`
84
+ - 用途:用于“间距/尺寸优先流式”的场景(如 padding/margin/gap/宽高)。
85
+ - PC 输出:`min(vw, px)`(防止超大屏继续放大)。
86
+ - Mobile 输出:由自动生成器扫描 `r.vw(...)` 后,在 `@media` 中生成纯 `vw` 覆盖。
87
+ - 示例:`margin-top: r.vw(40px, 24px);`
88
+
89
+ ### 由 `r.resp` / `r.vw` 间接使用(一般不直接写)
90
+
91
+ - `r.min_px($mobile, $type, $range: mobile, $override-coef: null)`
92
+ - 用途:计算 clamp 最小值。
93
+ - 规则:
94
+ - 在 `mobile` 且 `type` 属于 `h1/h2/h3/body/small/button-text` 时,走可读性规则(按 375 缩放并套 floor)。
95
+ - 其他情况按系数表 `coef(type, range)` 计算。
96
+
97
+ - `r.coef($type, $range: mobile)`
98
+ - 用途:读取 `coefficients.mobile/desktop` 中对应 type 的系数。
99
+
100
+ - `r.clamp_pc($pc, $min)`
101
+ - 用途:生成 PC clamp。
102
+ - 结构:`clamp($min, calc($pc * var(--px-to-vw)), $pc)`。
103
+
104
+ - `r.clamp_mb($mobile, $min)`
105
+ - 用途:生成移动端 clamp。
106
+ - 结构:`clamp($min, calc($mobile * var(--px-to-vw-mb)), $mobile)`。
107
+
108
+ - `r.vw_pc($pc)`
109
+ - 用途:生成 PC 端 `vw` 并带上限。
110
+ - 结构:`min(calc($pc * var(--px-to-vw)), $pc)`。
111
+
112
+ - `r.vw_mb($mobile)`
113
+ - 用途:生成移动端纯 `vw`。
114
+ - 结构:`calc($mobile * var(--px-to-vw-mb))`。
115
+
116
+ ### 内部工具函数(不建议业务直接使用)
117
+
118
+ - `_to-px($value)`:把无单位数字转为 `px`。
119
+ - `_to-num($value)`:把 `px` 或无单位数字转为纯数字(其他单位会报错)。
120
+
60
121
  ## CSS 编译(safe mode)
61
122
 
62
123
  `npm run css:watch` 默认走 safe mode:
@@ -67,6 +67,7 @@ function getMobileMax(cfg) {
67
67
 
68
68
  function getAutofill(cfg) {
69
69
  const fn = cfg?.autofill?.function ?? 'r.resp'
70
+ const vwFn = cfg?.autofill?.vwFunction
70
71
  const mobileMax = getMobileMax(cfg)
71
72
  const scanDirs = cfg?.autofill?.scanDirs ?? [
72
73
  cfg?.paths?.scssSrcDir ?? 'src/styles',
@@ -82,9 +83,27 @@ function getAutofill(cfg) {
82
83
  }
83
84
  const ns = parts[0]
84
85
  const name = parts[1]
86
+
87
+ const defaultVwFn = `${ns}.vw`
88
+ const vwParts = String(vwFn ?? defaultVwFn).split('.')
89
+ if (vwParts.length !== 2 || !vwParts[0] || !vwParts[1]) {
90
+ throw new Error(
91
+ `Invalid autofill.vwFunction: ${String(
92
+ vwFn
93
+ )}. Expected format: <ns>.<name> (e.g. ${defaultVwFn})`
94
+ )
95
+ }
96
+ if (vwParts[0] !== ns) {
97
+ throw new Error(
98
+ `autofill.vwFunction namespace must match autofill.function namespace (${ns})`
99
+ )
100
+ }
101
+ const vwName = vwParts[1]
102
+
85
103
  return {
86
104
  ns,
87
105
  name,
106
+ vwName,
88
107
  mobileMax,
89
108
  scanDirs,
90
109
  outputAbs: path.join(ROOT, output),
@@ -139,8 +158,7 @@ function splitTopLevelArgs(argsStr) {
139
158
  return args
140
159
  }
141
160
 
142
- function replaceRespCalls(value, { ns, name }) {
143
- const token = `${ns}.${name}(`
161
+ function replaceFunctionCalls(value, token, mapArgsToReplacement) {
144
162
  let out = ''
145
163
  let idx = 0
146
164
  let changed = false
@@ -183,11 +201,9 @@ function replaceRespCalls(value, { ns, name }) {
183
201
 
184
202
  const argsStr = value.slice(argsStart, i)
185
203
  const args = splitTopLevelArgs(argsStr)
186
- if (args.length >= 3) {
187
- const mobile = args[1]
188
- const desktopType = args[2]
189
- const mobileType = args.length >= 4 ? args[3] : desktopType
190
- out += `${ns}.clamp_mb(${mobile}, ${ns}.min_px(${mobile}, ${mobileType}, mobile))`
204
+ const replacement = mapArgsToReplacement(args)
205
+ if (replacement != null) {
206
+ out += replacement
191
207
  changed = true
192
208
  } else {
193
209
  // Not enough args: keep original call.
@@ -200,6 +216,33 @@ function replaceRespCalls(value, { ns, name }) {
200
216
  return { value: out.trim(), changed }
201
217
  }
202
218
 
219
+ function replaceAutofillCalls(value, { ns, name, vwName }) {
220
+ const respToken = `${ns}.${name}(`
221
+ const replacedResp = replaceFunctionCalls(value, respToken, (args) => {
222
+ if (args.length < 3) return null
223
+ const mobile = args[1]
224
+ const desktopType = args[2]
225
+ const mobileType = args.length >= 4 ? args[3] : desktopType
226
+ return `${ns}.clamp_mb(${mobile}, ${ns}.min_px(${mobile}, ${mobileType}, mobile))`
227
+ })
228
+
229
+ const vwToken = `${ns}.${vwName}(`
230
+ const replacedVw = replaceFunctionCalls(
231
+ replacedResp.value,
232
+ vwToken,
233
+ (args) => {
234
+ if (args.length < 2) return null
235
+ const mobile = args[1]
236
+ return `${ns}.vw_mb(${mobile})`
237
+ }
238
+ )
239
+
240
+ return {
241
+ value: replacedVw.value.trim(),
242
+ changed: replacedResp.changed || replacedVw.changed,
243
+ }
244
+ }
245
+
203
246
  function combineSelectors(parents, children) {
204
247
  const out = []
205
248
  for (const p of parents) {
@@ -216,7 +259,7 @@ function combineSelectors(parents, children) {
216
259
  return out
217
260
  }
218
261
 
219
- function scanScssForAutofill_legacy(absFilePath, { ns, name }) {
262
+ function scanScssForAutofill_legacy(absFilePath, { ns, name, vwName }) {
220
263
  const raw = fs.readFileSync(absFilePath, 'utf8')
221
264
  const lines = raw.split(/\r?\n/)
222
265
 
@@ -275,7 +318,7 @@ function scanScssForAutofill_legacy(absFilePath, { ns, name }) {
275
318
  value = value.replace(/\s!important\s*$/i, '').trim()
276
319
  }
277
320
 
278
- const replaced = replaceRespCalls(value, { ns, name })
321
+ const replaced = replaceAutofillCalls(value, { ns, name, vwName })
279
322
  if (!replaced.changed) continue
280
323
 
281
324
  const finalValue = important
@@ -290,7 +333,7 @@ function scanScssForAutofill_legacy(absFilePath, { ns, name }) {
290
333
  return rules
291
334
  }
292
335
 
293
- function scanScssForAutofill_ast(absFilePath, { ns, name }) {
336
+ function scanScssForAutofill_ast(absFilePath, { ns, name, vwName }) {
294
337
  // Lazy-load optional deps so `init` can run before `npm install`.
295
338
  /** @type {typeof import('postcss')} */
296
339
  let postcss
@@ -332,7 +375,7 @@ function scanScssForAutofill_ast(absFilePath, { ns, name }) {
332
375
  const property = String(child.prop)
333
376
  let value = String(child.value ?? '').trim()
334
377
 
335
- const replaced = replaceRespCalls(value, { ns, name })
378
+ const replaced = replaceAutofillCalls(value, { ns, name, vwName })
336
379
  if (!replaced.changed) continue
337
380
 
338
381
  const finalValue = child.important
@@ -362,12 +405,12 @@ function scanScssForAutofill_ast(absFilePath, { ns, name }) {
362
405
  return rules
363
406
  }
364
407
 
365
- function scanScssForAutofill(absFilePath, { ns, name }) {
408
+ function scanScssForAutofill(absFilePath, { ns, name, vwName }) {
366
409
  try {
367
- return scanScssForAutofill_ast(absFilePath, { ns, name })
410
+ return scanScssForAutofill_ast(absFilePath, { ns, name, vwName })
368
411
  } catch (e) {
369
412
  // Fallback for edge cases where parser can't handle a file.
370
- return scanScssForAutofill_legacy(absFilePath, { ns, name })
413
+ return scanScssForAutofill_legacy(absFilePath, { ns, name, vwName })
371
414
  }
372
415
  }
373
416
 
@@ -388,7 +431,7 @@ function generateAutofillScss(cfg, collectedRules) {
388
431
  const header = `@use "./responsive" as ${ns};
389
432
 
390
433
  // Generated by scss-kit from ${CONFIG_NAME}
391
- // Source: scanned ${ns}.resp(pc, mobile, type) markers in scss.
434
+ // Source: scanned ${ns}.resp(pc, mobile, desktopType[, mobileType]) and ${ns}.vw(pc, mobile) markers in scss.
392
435
  // Do not edit this file directly; re-run: npm run scss-kit:responsive:generate
393
436
  \n`
394
437
 
@@ -606,6 +649,31 @@ $coef-desktop: ${desktopMap};
606
649
  @return clamp(#{$min}, calc(#{$n} * var(--px-to-vw-mb)), #{$v});
607
650
  }
608
651
 
652
+ // 直接输出 PC 段 vw,并用设计稿 px 做上限兜底。
653
+ @function vw_pc($pc) {
654
+ $v: _to-px($pc);
655
+ $n: _to-num($pc);
656
+ @return min(calc(#{$n} * var(--px-to-vw)), #{$v});
657
+ }
658
+
659
+ // 直接输出移动段 vw(无 clamp)。
660
+ @function vw_mb($mobile) {
661
+ $n: _to-num($mobile);
662
+ @return calc(#{$n} * var(--px-to-vw-mb));
663
+ }
664
+
665
+ // vw(): spacing-first helper
666
+ // - PC: min(vw, px)
667
+ // - Mobile: pure vw (via autofill overrides)
668
+ // - arg1: pc
669
+ // - arg2: mobile (used by autofill scanner for mobile overrides)
670
+ @function vw($pc, $mobile) {
671
+ @if meta.type-of($pc) != number or meta.type-of($mobile) != number {
672
+ @error "vw() expects numeric px values for both pc and mobile";
673
+ }
674
+ @return vw_pc($pc);
675
+ }
676
+
609
677
  // resp():
610
678
  // - arg3: desktop type
611
679
  // - arg4 (optional): mobile type, defaults to desktop type
@@ -25,6 +25,7 @@
25
25
  "type": "object",
26
26
  "properties": {
27
27
  "function": {"type": "string"},
28
+ "vwFunction": {"type": "string"},
28
29
  "mobileMax": {"type": "integer", "minimum": 1},
29
30
  "scanDirs": {"type": "array", "items": {"type": "string"}},
30
31
  "output": {"type": "string"},