vite-plugin-react-shopify 1.0.1 → 1.1.0

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/README.md CHANGED
@@ -40,6 +40,30 @@ export default function HelloWorld() {
40
40
 
41
41
  构建后生成 `sections/react-hello-world.liquid`。
42
42
 
43
+ ### 开发
44
+
45
+ 使用 `vite build --watch` 进行本地开发。Vite 8 的 Rolldown 内置增量构建缓存,文件变更时仅重新构建受影响的模块。
46
+
47
+ ```bash
48
+ # 终端 1: 启动 Vite 构建监听
49
+ pnpm dev # → vite build --watch
50
+
51
+ # 终端 2: 启动 Shopify 主题开发
52
+ shopify theme dev
53
+ ```
54
+
55
+ Vite 监听文件变化 → 增量构建 → 写入磁盘 → Shopify CLI 检测到变化 → 推送主题 → 编辑器热更新。watch 模式下产物不压缩、附带 inline sourcemap,方便在浏览器中调试。
56
+
57
+ ```json
58
+ // package.json
59
+ {
60
+ "scripts": {
61
+ "dev": "vite build --watch",
62
+ "build": "vite build"
63
+ }
64
+ }
65
+ ```
66
+
43
67
  ---
44
68
 
45
69
  ## 目录结构
@@ -298,6 +322,12 @@ vitePluginShopify({
298
322
  snippetFile: "shopify-importmap.liquid", // importmap 片段文件名
299
323
  buildDir: "assets", // Vite 构建产物输出目录(相对于 themeRoot)
300
324
 
325
+ // === 构建配置 ===
326
+ hash: false, // 产物文件名是否包含 content hash,默认 false
327
+
328
+ // === 调试 ===
329
+ debug: false, // 启用详细日志输出
330
+
301
331
  // === SSG 配置 ===
302
332
  ssg: {
303
333
  directories: ["sections", "blocks", "templates"], // 扫描的目录
@@ -332,6 +362,7 @@ vitePluginShopify({
332
362
  - 组件文件名通过 `toKebabCase` 转换为 kebab-case:
333
363
  - `HelloWorld` → `hello-world`
334
364
  - `FAQSection` → `faq-section`
365
+ - JS 产物默认使用稳定文件名(如 `assets/build/hello-world.js`),设置 `hash: true` 后启用 content hash(如 `assets/build/hello-world-aBc123.js`)
335
366
 
336
367
  ### 自定义命名(`outputName`)
337
368
 
@@ -396,6 +427,63 @@ export default function Section() {
396
427
 
397
428
  ---
398
429
 
430
+ ## 调试
431
+
432
+ 插件提供了两种方式启用详细日志输出,方便诊断构建问题。
433
+
434
+ ### 方式一:插件选项
435
+
436
+ ```ts
437
+ vitePluginShopify({
438
+ debug: true,
439
+ });
440
+ ```
441
+
442
+ ### 方式二:环境变量
443
+
444
+ ```bash
445
+ DEBUG=vite-plugin-shopify:* npx vite build
446
+ ```
447
+
448
+ 两种方式效果相同,都会输出详细的构建过程信息。
449
+
450
+ ### Debug 输出示例
451
+
452
+ 启用 debug 模式后,构建过程会输出:
453
+
454
+ ```
455
+ vite-plugin-shopify:entries scanned 5 entries: {"section":3,"block":2}
456
+ vite-plugin-shopify:ssg:compiler found 5 entries to compile
457
+ vite-plugin-shopify:ssg:compiler entry counter has 1 CSS files
458
+ vite-plugin-shopify:ssg:compiler entry hello-world has 1 CSS files
459
+ vite-plugin-shopify:ssg:compiler generated shared CSS snippet react-css-SharedCard (used by 2 entries)
460
+ vite-plugin-shopify:ssg:compiler compiling counter (type=section, css inline=0, css snippets=1)
461
+ vite-plugin-shopify:ssg:compiler bundling counter via esbuild
462
+ vite-plugin-shopify:ssg:compiler esbuild bundle took 53ms
463
+ ...
464
+ [vite-plugin-shopify] Starting SSG compilation...
465
+ [vite-plugin-shopify] Compiled 5 entries
466
+ [vite-plugin-shopify] SSG compilation complete
467
+ ```
468
+
469
+ ### 日志级别
470
+
471
+ | 级别 | 触发条件 | 可见性 |
472
+ |------|----------|--------|
473
+ | `info` | 始终可见 | 构建阶段摘要、完成计数 |
474
+ | `warn` | 始终可见 | 缺少依赖、跳过的组件 |
475
+ | `error` | 始终可见 | 编译失败的组件及堆栈 |
476
+ | `debug` | 仅 debug 模式 | 入口扫描结果、CSS 分发、esbuild 耗时、配置详情 |
477
+
478
+ ### 诊断场景
479
+
480
+ - **组件未被识别** → 开启 debug,检查 `scanned entries` 输出,确认文件和目录命名
481
+ - **CSS 未生效** → 开启 debug,检查 `has CSS files` 和 `css inline/snippets` 统计
482
+ - **SSG 渲染失败** → `error` 级别自动输出完整错误堆栈
483
+ - **构建缓慢** → 开启 debug,检查每个组件的 `esbuild bundle took Xms`
484
+
485
+ ---
486
+
399
487
  ## 注意事项
400
488
 
401
489
  1. **React / ReactDOM 不打包进 bundle**:通过 import map 从 CDN 加载,避免重复打包和体积膨胀
@@ -405,3 +493,5 @@ export default function Section() {
405
493
  5. **Section 必须有预设才能通过编辑器添加**:没有 `presets` 的 section 需要手动在 JSON 模板中引用,编辑器无法直接添加
406
494
  6. **`{% content_for 'blocks' %}` 自动插入**:当 `shopifyMeta.blocks` 非空时,插件自动在生成的 Liquid 中插入子 block 渲染标签
407
495
  7. **构建产物默认输出到 `assets/`**:如需与其他静态资源隔离,可设置 `buildDir: "assets/build"` 将产物输出到子目录,然后在 `.gitignore` 中添加 `assets/build/` 忽略该目录
496
+ 8. **Watch 模式自动关闭压缩**:`vite build --watch` 时自动设置 `minify: false` 并启用 inline sourcemap,方便在浏览器中调试。生产构建(`vite build`)则使用正常压缩
497
+ 9. **增量构建依赖 Rolldown 缓存**:watch 模式下 Rolldown 的 `ScanStageCache` 自动缓存模块图,文件变更时仅重新扫描变更模块,大幅加速构建。首次启动执行全量构建,后续为增量构建
@@ -0,0 +1,278 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width,initial-scale=1" />
6
+ <title>Shopify Vite</title>
7
+ <style>
8
+ :root {
9
+ --accent: 100 108 255;
10
+ --accent-2: 33 201 171;
11
+ --bg-2: 246 246 247;
12
+ --bg-1: 255 255 255;
13
+ --text: 60 60 67;
14
+ }
15
+
16
+ *,
17
+ *::before,
18
+ *::after {
19
+ box-sizing: inherit;
20
+ }
21
+
22
+ html {
23
+ box-sizing: border-box;
24
+ height: 100%;
25
+ background-color: rgb(var(--bg-1));
26
+ font-family: system-ui, sans-serif;
27
+ }
28
+
29
+ body {
30
+ display: flex;
31
+ justify-content: center;
32
+ align-items: center;
33
+ min-height: 100%;
34
+ padding: 0 1.5rem;
35
+ margin: 0;
36
+ color: rgba(var(--text) / 0.92);
37
+ }
38
+
39
+ code {
40
+ padding: 0.3em 0.45em;
41
+ background: rgba(var(--accent) / 12%);
42
+ color: rgb(var(--accent));
43
+ font-family:
44
+ Menlo,
45
+ Monaco,
46
+ Lucida Console,
47
+ Liberation Mono,
48
+ DejaVu Sans Mono,
49
+ Bitstream Vera Sans Mono,
50
+ Courier New,
51
+ monospace;
52
+ font-size: 0.875em;
53
+ font-weight: 700;
54
+ border-radius: 4px;
55
+ }
56
+
57
+ a {
58
+ color: rgb(var(--accent));
59
+ font-weight: bold;
60
+ text-decoration: none;
61
+ }
62
+
63
+ a:hover {
64
+ text-decoration: underline;
65
+ }
66
+
67
+ p {
68
+ margin-top: 1.5rem;
69
+ }
70
+
71
+ p:first-child {
72
+ margin-top: 0;
73
+ }
74
+
75
+ p:last-child {
76
+ margin-bottom: 0;
77
+ }
78
+
79
+ .icon-logo {
80
+ width: 4rem;
81
+ height: 4rem;
82
+ }
83
+
84
+ .icon-logo:hover {
85
+ filter: drop-shadow(0 0 1.5rem rgba(var(--accent) / 0.67));
86
+ }
87
+
88
+ .icon-logo--shopify:hover {
89
+ filter: drop-shadow(0 0 1.5rem rgba(var(--accent-2) / 0.67));
90
+ }
91
+
92
+ .icon-plus {
93
+ width: 1.5rem;
94
+ height: 1.5rem;
95
+ }
96
+
97
+ .head {
98
+ display: flex;
99
+ align-items: center;
100
+ justify-content: center;
101
+ gap: 1.5rem;
102
+ padding-bottom: 2rem;
103
+ }
104
+
105
+ .main {
106
+ padding: 2.5rem;
107
+ max-width: 36rem;
108
+ width: 100%;
109
+ background: rgb(var(--bg-2));
110
+ border-radius: 0.4rem;
111
+ line-height: 1.75;
112
+ }
113
+
114
+ .footer {
115
+ padding-top: 2rem;
116
+ border-top: 1px solid rgba(var(--accent) / 25%);
117
+ }
118
+
119
+ .content {
120
+ padding-bottom: 2rem;
121
+ }
122
+
123
+ @media (prefers-color-scheme: dark) {
124
+ :root {
125
+ --bg-2: 37 37 41;
126
+ --bg-1: 30 30 32;
127
+ --text: 255 255 245;
128
+ }
129
+
130
+ body {
131
+ color: rgba(var(--text) / 0.86);
132
+ }
133
+ }
134
+ </style>
135
+ </head>
136
+ <body>
137
+ <main class="main">
138
+ <section class="head">
139
+ <a href="https://shopify.dev/docs/themes">
140
+ <svg
141
+ fill="none"
142
+ viewBox="0 0 39 44"
143
+ xmlns="http://www.w3.org/2000/svg"
144
+ class="icon-logo icon-logo--shopify"
145
+ >
146
+ <path
147
+ d="M33.962 9.018a.417.417 0 0 0-.379-.35c-.156-.015-3.223-.06-3.223-.06s-2.563-2.49-2.816-2.743c-.254-.254-.747-.177-.941-.122-.004 0-.483.15-1.289.4a8.871 8.871 0 0 0-.618-1.515c-.913-1.743-2.25-2.663-3.865-2.667h-.007c-.111 0-.222.01-.337.021-.049-.055-.094-.115-.146-.167-.705-.753-1.608-1.118-2.688-1.087-2.087.06-4.164 1.567-5.848 4.244-1.188 1.882-2.087 4.25-2.344 6.081-2.396.743-4.074 1.26-4.109 1.271-1.208.379-1.246.417-1.406 1.556C3.824 14.741.66 39.21.66 39.21l26.522 4.588 11.495-2.858c.003.004-4.689-31.703-4.716-31.922Zm-9.978-2.462c-.611.187-1.306.403-2.06.639-.013-1.056-.142-2.528-.635-3.796 1.587.295 2.365 2.087 2.695 3.157ZM20.54 7.622l-4.428 1.372c.427-1.64 1.24-3.272 2.237-4.338.371-.396.889-.84 1.504-1.094.576 1.202.701 2.907.687 4.06ZM17.7 2.114c.489-.01.902.097 1.253.33-.563.292-1.108.712-1.622 1.26-1.326 1.421-2.34 3.63-2.747 5.759l-3.636 1.125c.719-3.348 3.529-8.38 6.751-8.474Z"
148
+ fill="url(#a)"
149
+ />
150
+ <path
151
+ d="M33.962 9.018a.417.417 0 0 0-.379-.35c-.156-.015-3.223-.06-3.223-.06s-2.563-2.49-2.816-2.743c-.254-.254-.747-.177-.941-.122-.004 0-.483.15-1.289.4a8.871 8.871 0 0 0-.618-1.515c-.913-1.743-2.25-2.663-3.865-2.667h-.007c-.111 0-.222.01-.337.021-.049-.055-.094-.115-.146-.167-.705-.753-1.608-1.118-2.688-1.087-2.087.06-4.164 1.567-5.848 4.244-1.188 1.882-2.087 4.25-2.344 6.081-2.396.743-4.074 1.26-4.109 1.271-1.208.379-1.246.417-1.406 1.556C3.824 14.741.66 39.21.66 39.21l26.522 4.588 11.495-2.858c.003.004-4.689-31.703-4.716-31.922Zm-9.978-2.462c-.611.187-1.306.403-2.06.639-.013-1.056-.142-2.528-.635-3.796 1.587.295 2.365 2.087 2.695 3.157ZM20.54 7.622l-4.428 1.372c.427-1.64 1.24-3.272 2.237-4.338.371-.396.889-.84 1.504-1.094.576 1.202.701 2.907.687 4.06ZM17.7 2.114c.489-.01.902.097 1.253.33-.563.292-1.108.712-1.622 1.26-1.326 1.421-2.34 3.63-2.747 5.759l-3.636 1.125c.719-3.348 3.529-8.38 6.751-8.474Z"
152
+ fill="#fff"
153
+ fill-opacity=".1"
154
+ />
155
+ <path
156
+ d="M33.583 8.667c-.156-.014-3.223-.06-3.223-.06s-2.563-2.49-2.816-2.743a.631.631 0 0 0-.358-.163v38.097l11.492-2.858-4.72-31.922a.417.417 0 0 0-.375-.351Z"
157
+ fill="url(#b)"
158
+ />
159
+ <path
160
+ d="M33.583 8.667c-.156-.014-3.223-.06-3.223-.06s-2.563-2.49-2.816-2.743a.631.631 0 0 0-.358-.163v38.097l11.492-2.858-4.72-31.922a.417.417 0 0 0-.375-.351Z"
161
+ fill="#fff"
162
+ fill-opacity=".4"
163
+ />
164
+ <path
165
+ d="m20.817 14.577-1.334 4.994s-1.49-.677-3.254-.566c-2.587.164-2.615 1.796-2.59 2.206.142 2.233 6.018 2.722 6.348 7.952.26 4.116-2.184 6.932-5.702 7.154-4.223.268-6.55-2.226-6.55-2.226l.896-3.81s2.34 1.768 4.216 1.647c1.222-.077 1.66-1.073 1.618-1.778-.184-2.914-4.97-2.744-5.272-7.53-.253-4.028 2.393-8.112 8.231-8.48 2.24-.136 3.393.437 3.393.437Z"
166
+ fill="#fff"
167
+ />
168
+ <defs>
169
+ <radialGradient
170
+ cx="0"
171
+ cy="0"
172
+ gradientTransform="matrix(45.33948 79.2891 -71.42537 40.84281 -10.388 -22.088)"
173
+ gradientUnits="userSpaceOnUse"
174
+ id="a"
175
+ r="1"
176
+ >
177
+ <stop stop-color="#CEF141" />
178
+ <stop offset=".586" stop-color="#79D7EC" />
179
+ <stop offset="1" stop-color="#130FD7" />
180
+ </radialGradient>
181
+ <radialGradient
182
+ cx="0"
183
+ cy="0"
184
+ gradientTransform="rotate(-98.682 37.062 9.771) scale(91.0466 41.9746)"
185
+ gradientUnits="userSpaceOnUse"
186
+ id="b"
187
+ r="1"
188
+ >
189
+ <stop stop-color="#130FD7" />
190
+ <stop offset=".428" stop-color="#21C9AB" />
191
+ <stop offset=".915" stop-color="#CEF141" />
192
+ </radialGradient>
193
+ </defs>
194
+ </svg>
195
+ </a>
196
+ <svg
197
+ xmlns="http://www.w3.org/2000/svg"
198
+ aria-hidden="true"
199
+ class="icon-plus"
200
+ fill="none"
201
+ viewBox="0 0 10 10"
202
+ >
203
+ <path
204
+ fill-rule="evenodd"
205
+ clip-rule="evenodd"
206
+ d="M1 4.51a.5.5 0 0 0 0 1h3.5l.01 3.5a.5.5 0 0 0 1-.01V5.5l3.5-.01a.5.5 0 0 0-.01-1H5.5L5.49.99a.5.5 0 0 0-1 .01v3.5l-3.5.01H1z"
207
+ fill="currentColor"
208
+ />
209
+ </svg>
210
+ <a href="https://vitejs.dev/">
211
+ <svg
212
+ xmlns="http://www.w3.org/2000/svg"
213
+ xmlns:xlink="http://www.w3.org/1999/xlink"
214
+ aria-hidden="true"
215
+ role="img"
216
+ class="icon-logo"
217
+ width="31.88"
218
+ height="32"
219
+ preserveAspectRatio="xMidYMid meet"
220
+ viewBox="0 0 256 257"
221
+ >
222
+ <defs>
223
+ <linearGradient
224
+ id="IconifyId1813088fe1fbc01fb466"
225
+ x1="-.828%"
226
+ x2="57.636%"
227
+ y1="7.652%"
228
+ y2="78.411%"
229
+ >
230
+ <stop offset="0%" stop-color="#41D1FF"></stop>
231
+ <stop offset="100%" stop-color="#BD34FE"></stop>
232
+ </linearGradient>
233
+ <linearGradient
234
+ id="IconifyId1813088fe1fbc01fb467"
235
+ x1="43.376%"
236
+ x2="50.316%"
237
+ y1="2.242%"
238
+ y2="89.03%"
239
+ >
240
+ <stop offset="0%" stop-color="#FFEA83"></stop>
241
+ <stop offset="8.333%" stop-color="#FFDD35"></stop>
242
+ <stop offset="100%" stop-color="#FFA800"></stop>
243
+ </linearGradient>
244
+ </defs>
245
+ <path
246
+ fill="url(#IconifyId1813088fe1fbc01fb466)"
247
+ d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"
248
+ ></path>
249
+ <path
250
+ fill="url(#IconifyId1813088fe1fbc01fb467)"
251
+ d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"
252
+ ></path>
253
+ </svg>
254
+ </a>
255
+ </section>
256
+ <section class="content">
257
+ <p>
258
+ This is the Vite development server that provides Hot Module Replacement for your Shopify
259
+ theme's assets, such as scripts or styles.
260
+ </p>
261
+ <p>
262
+ To access your Shopify theme, you will need to serve it locally using the
263
+ <a href="https://shopify.dev/docs/themes/tools/cli/commands">Shopify CLI</a> and the
264
+ <code>shopify theme dev</code> command.
265
+ </p>
266
+ </section>
267
+ <section class="footer">
268
+ <p>
269
+ Want more information on this Shopify Vite integration?
270
+ <br />
271
+ <a href="https://github.com/barrel/barrel-shopify/tree/main/packages/vite-plugin-shopify"
272
+ >Read the docs →</a
273
+ >
274
+ </p>
275
+ </section>
276
+ </main>
277
+ </body>
278
+ </html>
package/dist/index.d.ts CHANGED
@@ -5,6 +5,8 @@ interface Options {
5
5
  sourceCodeDir?: string;
6
6
  snippetFile?: string;
7
7
  buildDir?: string;
8
+ debug?: boolean;
9
+ hash?: boolean;
8
10
  ssg?: SSGOptions;
9
11
  importMap?: ImportMapOptions;
10
12
  }
package/dist/index.js CHANGED
@@ -32,21 +32,51 @@ var resolveOptions = (options = {}) => {
32
32
  sourceCodeDir,
33
33
  snippetFile,
34
34
  buildDir,
35
+ debug: options.debug ?? false,
36
+ hash: options.hash ?? false,
35
37
  ssg,
36
38
  importMap
37
39
  };
38
40
  };
39
41
 
42
+ // src/logger.ts
43
+ import createDebugger from "debug";
44
+ var NAMESPACE = "vite-plugin-shopify";
45
+ var _debugEnabled = false;
46
+ function enableDebug() {
47
+ if (_debugEnabled) return;
48
+ _debugEnabled = true;
49
+ const existing = process.env.DEBUG;
50
+ createDebugger.enable(existing ? `${existing},${NAMESPACE}:*` : `${NAMESPACE}:*`);
51
+ }
52
+ function logger(ns) {
53
+ const dbg = createDebugger(`${NAMESPACE}:${ns}`);
54
+ return {
55
+ debug: (formatter, ...args) => {
56
+ if (_debugEnabled) dbg(formatter, ...args);
57
+ },
58
+ info: (msg, ...args) => console.log(`[${NAMESPACE}] ${msg}`, ...args),
59
+ warn: (msg, ...args) => console.warn(`[${NAMESPACE}] ${msg}`, ...args),
60
+ error: (msg, ...args) => console.error(`[${NAMESPACE}] ${msg}`, ...args)
61
+ };
62
+ }
63
+
40
64
  // src/config.ts
41
65
  import path2 from "path";
42
- import createDebugger from "debug";
43
- var debug = createDebugger("vite-plugin-shopify:config");
66
+ var log = logger("config");
67
+ function isWatchMode() {
68
+ return process.argv.includes("--watch") || process.env.SHOPIFY_DEV_WATCH === "1";
69
+ }
44
70
  function shopifyConfig(options) {
45
- const isDebug = process.env.VITE_SHOPIFY_DEBUG === "true";
46
71
  return {
47
72
  name: "vite-plugin-shopify:config",
48
73
  config(config) {
49
74
  const sourceDirAbs = path2.resolve(options.themeRoot, options.sourceCodeDir);
75
+ const watch = isWatchMode();
76
+ const entryFileNames = options.hash ? "[name]-[hash].js" : "[name].js";
77
+ const chunkFileNames = options.hash ? "[name]-[hash].js" : "[name].js";
78
+ const assetFileNames = options.hash ? "[name]-[hash][extname]" : "[name][extname]";
79
+ log.debug("hash=%s watch=%s", options.hash, watch);
50
80
  const generated = {
51
81
  base: config.base ?? "./",
52
82
  publicDir: config.publicDir ?? false,
@@ -55,8 +85,8 @@ function shopifyConfig(options) {
55
85
  assetsDir: config.build?.assetsDir ?? "",
56
86
  emptyOutDir: config.build?.emptyOutDir ?? false,
57
87
  manifest: config.build?.manifest ?? true,
58
- minify: config.build?.minify ?? (isDebug ? false : void 0),
59
- sourcemap: config.build?.sourcemap ?? (isDebug ? true : void 0),
88
+ minify: config.build?.minify ?? (watch || options.debug ? false : void 0),
89
+ sourcemap: config.build?.sourcemap ?? (watch || options.debug ? "inline" : void 0),
60
90
  rollupOptions: {
61
91
  ...config.build?.rollupOptions,
62
92
  external: [
@@ -66,9 +96,9 @@ function shopifyConfig(options) {
66
96
  ],
67
97
  output: {
68
98
  ...config.build?.rollupOptions?.output,
69
- entryFileNames: "[name]-[hash].js",
70
- chunkFileNames: "[name]-[hash].js",
71
- assetFileNames: "[name]-[hash][extname]"
99
+ entryFileNames,
100
+ chunkFileNames,
101
+ assetFileNames
72
102
  }
73
103
  }
74
104
  },
@@ -98,7 +128,7 @@ function shopifyConfig(options) {
98
128
  modules: config.css?.modules
99
129
  }
100
130
  };
101
- debug(generated);
131
+ log.debug("generated config: %O", generated);
102
132
  return generated;
103
133
  }
104
134
  };
@@ -145,12 +175,18 @@ function toKebabCase(str) {
145
175
  }
146
176
 
147
177
  // src/entries.ts
178
+ var log2 = logger("entries");
148
179
  function shopifyEntries(options) {
149
180
  let entries = [];
150
181
  return {
151
182
  name: "vite-plugin-shopify:entries",
152
183
  config(config) {
153
184
  entries = scanEntries(options);
185
+ const byType = {};
186
+ for (const e of entries) {
187
+ byType[e.targetType] = (byType[e.targetType] || 0) + 1;
188
+ }
189
+ log2.debug("scanned %d entries: %s", entries.length, JSON.stringify(byType));
154
190
  if (entries.length === 0) return {};
155
191
  const input = {};
156
192
  for (const entry of entries) {
@@ -329,11 +365,14 @@ function assembleLiquidFile(html, entry, scriptAsset, cssContents, options) {
329
365
  parts.push(...buildSection(html, entry));
330
366
  break;
331
367
  }
332
- if (cssContents.length > 0) {
368
+ for (const snippet of cssContents.snippets) {
369
+ parts.push("", `{% render '${snippet}' %}`);
370
+ }
371
+ if (cssContents.inline.length > 0) {
333
372
  parts.push(
334
373
  "",
335
374
  "{% stylesheet %}",
336
- ...cssContents.map((c) => c.trim()),
375
+ ...cssContents.inline.map((c) => c.trim()),
337
376
  "{% endstylesheet %}"
338
377
  );
339
378
  }
@@ -438,21 +477,78 @@ function resolveFileName(entry, type, options) {
438
477
  }
439
478
 
440
479
  // src/ssg/compiler.ts
480
+ var log3 = logger("ssg:compiler");
481
+ var SNIPPET_PREFIX = "react-css";
441
482
  async function compileAllEntries(options, manifest) {
442
483
  const entries = scanEntries(options);
443
484
  if (entries.length === 0) return;
485
+ log3.debug("found %d entries to compile", entries.length);
444
486
  const projectRoot = path6.resolve(options.themeRoot);
445
487
  const sourceDir = path6.resolve(options.themeRoot, options.sourceCodeDir);
488
+ const entryCssFiles = /* @__PURE__ */ new Map();
489
+ const cssRefCount = /* @__PURE__ */ new Map();
490
+ for (const entry of entries) {
491
+ const manifestKey = `shopify:entry:${entry.kebabName}`;
492
+ const files = collectCssFiles(manifestKey, manifest);
493
+ entryCssFiles.set(entry.kebabName, files);
494
+ for (const f of files) {
495
+ cssRefCount.set(f, (cssRefCount.get(f) || 0) + 1);
496
+ }
497
+ log3.debug("entry %s has %d CSS files", entry.kebabName, files.length);
498
+ }
499
+ const cssSnippetMap = /* @__PURE__ */ new Map();
500
+ for (const [cssFile, count] of cssRefCount) {
501
+ if (count > 1) {
502
+ const snippetName = `${SNIPPET_PREFIX}-${getCssBaseName(cssFile)}`;
503
+ cssSnippetMap.set(cssFile, snippetName);
504
+ const snippetPath = path6.join(
505
+ path6.resolve(options.themeRoot),
506
+ "snippets",
507
+ `${snippetName}.liquid`
508
+ );
509
+ const cssPath = path6.join(
510
+ path6.resolve(options.themeRoot, options.buildDir),
511
+ cssFile
512
+ );
513
+ try {
514
+ const cssContent = fs.readFileSync(cssPath, "utf-8");
515
+ fs.mkdirSync(path6.dirname(snippetPath), { recursive: true });
516
+ fs.writeFileSync(snippetPath, `{% stylesheet %}
517
+ ${cssContent.trim()}
518
+ {% endstylesheet %}
519
+ `);
520
+ log3.debug("generated shared CSS snippet %s (used by %d entries)", snippetName, count);
521
+ } catch {
522
+ log3.warn("failed to write CSS snippet for %s", cssFile);
523
+ }
524
+ }
525
+ }
446
526
  for (const entry of entries) {
447
527
  try {
448
- await compileEntry(entry, options, manifest, projectRoot, sourceDir);
528
+ const cssFiles = entryCssFiles.get(entry.kebabName) || [];
529
+ const cssSnippets = cssFiles.filter((f) => cssSnippetMap.has(f)).map((f) => cssSnippetMap.get(f));
530
+ const cssInlineFiles = cssFiles.filter((f) => !cssSnippetMap.has(f));
531
+ const cssInline = readCssFileContents(cssInlineFiles, options.buildDir, options.themeRoot);
532
+ log3.debug(
533
+ "compiling %s (type=%s, css inline=%d, css snippets=%d)",
534
+ entry.kebabName,
535
+ entry.targetType,
536
+ cssInline.length,
537
+ cssSnippets.length
538
+ );
539
+ await compileEntry(entry, options, manifest, projectRoot, sourceDir, cssInline, cssSnippets);
449
540
  } catch (err) {
450
- console.error(`[vite-plugin-shopify] Failed to compile ${entry.filePath}:`, err);
541
+ log3.error("Failed to compile %s:", entry.filePath, err);
451
542
  }
452
543
  }
453
- console.log(`[vite-plugin-shopify] Compiled ${entries.length} entries`);
544
+ log3.info("Compiled %d entries", entries.length);
545
+ const tmpDir = path6.join(sourceDir, ".ssg-tmp");
546
+ try {
547
+ fs.rmSync(tmpDir, { recursive: true, force: true });
548
+ } catch {
549
+ }
454
550
  }
455
- async function compileEntry(entry, options, manifest, projectRoot, sourceDir) {
551
+ async function compileEntry(entry, options, manifest, projectRoot, sourceDir, cssInline, cssSnippets) {
456
552
  const projectRequire = createRequire(path6.join(projectRoot, "package.json"));
457
553
  let createElement;
458
554
  let renderToStaticMarkup;
@@ -460,41 +556,74 @@ async function compileEntry(entry, options, manifest, projectRoot, sourceDir) {
460
556
  createElement = projectRequire("react").createElement;
461
557
  renderToStaticMarkup = projectRequire("react-dom/server").renderToStaticMarkup;
462
558
  } catch {
463
- console.warn(`[vite-plugin-shopify] react/react-dom not found, skipping SSR`);
559
+ log3.warn("react/react-dom not found, skipping SSR for %s", entry.kebabName);
464
560
  return;
465
561
  }
466
562
  const sourceCode = fs.readFileSync(entry.filePath, "utf-8");
467
- const ssgSource = sourceCode.replace(
468
- /import\s+(\w+)\s+from\s+["'][^"']*\.module\.css["'];?\s*/g,
469
- (_, name) => `const ${name} = new Proxy({},{get:(_,k)=>k});`
470
- ).replace(
471
- /import\s+["'][^"']*\.css["'];?\s*/g,
472
- ""
473
- );
474
563
  let esbuild;
475
564
  try {
476
565
  esbuild = projectRequire("esbuild");
477
566
  } catch {
478
- console.warn(`[vite-plugin-shopify] esbuild not found, skipping SSR`);
567
+ log3.warn("esbuild not found, skipping SSR for %s", entry.kebabName);
479
568
  return;
480
569
  }
481
- const result = await esbuild.transform(ssgSource, {
482
- loader: path6.extname(entry.filePath).slice(1),
570
+ const ts = Date.now();
571
+ const tmpDir = path6.join(sourceDir, ".ssg-tmp");
572
+ fs.mkdirSync(tmpDir, { recursive: true });
573
+ const tmpFile = path6.join(tmpDir, `.ssg-entry-${ts}.mjs`);
574
+ log3.debug("bundling %s via esbuild", entry.kebabName);
575
+ const startBundled = Date.now();
576
+ await esbuild.build({
577
+ stdin: {
578
+ contents: sourceCode,
579
+ resolveDir: path6.dirname(entry.filePath),
580
+ loader: path6.extname(entry.filePath).slice(1)
581
+ },
582
+ outfile: tmpFile,
583
+ bundle: true,
483
584
  format: "esm",
484
585
  jsx: "automatic",
485
- sourcefile: entry.filePath
586
+ platform: "node",
587
+ external: [
588
+ "react",
589
+ "react-dom",
590
+ "react-dom/*",
591
+ "vite-plugin-react-shopify",
592
+ "vite-plugin-react-shopify/*"
593
+ ],
594
+ write: true,
595
+ allowOverwrite: true,
596
+ plugins: [
597
+ {
598
+ name: "ssg-strip-css",
599
+ setup(build) {
600
+ build.onResolve({ filter: /\.module\.css$/ }, (args) => ({
601
+ namespace: "ssg-css-module",
602
+ path: args.path
603
+ }));
604
+ build.onResolve({ filter: /\.css$/ }, (args) => ({
605
+ namespace: "ssg-css-plain",
606
+ path: args.path
607
+ }));
608
+ build.onLoad({ filter: /.*/, namespace: "ssg-css-module" }, () => ({
609
+ contents: "export default new Proxy({},{get:(_,k)=>k});",
610
+ loader: "js"
611
+ }));
612
+ build.onLoad({ filter: /.*/, namespace: "ssg-css-plain" }, () => ({
613
+ contents: "",
614
+ loader: "js"
615
+ }));
616
+ }
617
+ }
618
+ ]
486
619
  });
487
- const ts = Date.now();
488
- const tmpFile = path6.join(sourceDir, ".ssg-tmp-" + ts + ".mjs");
489
- fs.writeFileSync(tmpFile, result.code);
620
+ log3.debug("esbuild bundle took %dms", Date.now() - startBundled);
490
621
  try {
491
622
  const mod = await import(pathToFileURL(tmpFile));
492
623
  const Component = mod.default;
493
624
  const shopifyMeta = mod.shopifyMeta;
494
625
  if (!Component) {
495
- console.warn(
496
- `[vite-plugin-shopify] No default export found in ${entry.filePath}, skipping`
497
- );
626
+ log3.warn("No default export found in %s, skipping", entry.filePath);
498
627
  return;
499
628
  }
500
629
  if (shopifyMeta) {
@@ -506,8 +635,7 @@ async function compileEntry(entry, options, manifest, projectRoot, sourceDir) {
506
635
  html = stripReactLiquidTags(html);
507
636
  html = unwrapHtmlEntities(html);
508
637
  const scriptAsset = resolveScriptAsset(entry.kebabName, manifest);
509
- const cssContents = readCssAssets(entry.kebabName, manifest, options.buildDir, options.themeRoot);
510
- const liquidContent = assembleLiquidFile(html, entry, scriptAsset, cssContents, {
638
+ const liquidContent = assembleLiquidFile(html, entry, scriptAsset, { inline: cssInline, snippets: cssSnippets }, {
511
639
  prefix: options.ssg.prefix,
512
640
  outputName: options.ssg.outputName || void 0,
513
641
  buildDir: options.buildDir
@@ -537,22 +665,49 @@ function resolveScriptAsset(kebabName, manifest) {
537
665
  if (!file) return null;
538
666
  return path6.basename(file);
539
667
  }
540
- function readCssAssets(kebabName, manifest, buildDir, themeRoot) {
541
- const manifestKey = `shopify:entry:${kebabName}`;
542
- const entryChunk = manifest[manifestKey];
543
- if (!entryChunk) return [];
544
- const css = entryChunk.css;
545
- if (!css || !Array.isArray(css)) return [];
668
+ function collectCssFiles(manifestKey, manifest) {
669
+ const collected = /* @__PURE__ */ new Set();
670
+ const visited = /* @__PURE__ */ new Set();
671
+ collectCssFilesRecursive(manifestKey, manifest, collected, visited);
672
+ return [...collected];
673
+ }
674
+ function collectCssFilesRecursive(chunkKey, manifest, collected, visited) {
675
+ if (visited.has(chunkKey)) return;
676
+ visited.add(chunkKey);
677
+ const chunk = manifest[chunkKey];
678
+ if (!chunk) return;
679
+ if (chunk.css && Array.isArray(chunk.css)) {
680
+ for (const cssFile of chunk.css) {
681
+ collected.add(cssFile);
682
+ }
683
+ }
684
+ if (chunk.imports && Array.isArray(chunk.imports)) {
685
+ for (const imported of chunk.imports) {
686
+ collectCssFilesRecursive(imported, manifest, collected, visited);
687
+ }
688
+ }
689
+ }
690
+ function readCssFileContents(cssFiles, buildDir, themeRoot) {
546
691
  const assetsDir = path6.resolve(themeRoot, buildDir);
547
- return css.map((file) => {
548
- const cssPath = path6.join(assetsDir, file);
692
+ return cssFiles.map((file) => {
549
693
  try {
550
- return fs.readFileSync(cssPath, "utf-8");
694
+ return fs.readFileSync(path6.join(assetsDir, file), "utf-8");
551
695
  } catch {
552
696
  return "";
553
697
  }
554
698
  }).filter(Boolean);
555
699
  }
700
+ function getCssBaseName(cssFile) {
701
+ const name = cssFile.replace(/\.css$/, "");
702
+ const lastHyphen = name.lastIndexOf("-");
703
+ if (lastHyphen > 0) {
704
+ const possibleHash = name.slice(lastHyphen + 1);
705
+ if (/^[A-Za-z0-9_-]{8,}$/.test(possibleHash)) {
706
+ return name.slice(0, lastHyphen);
707
+ }
708
+ }
709
+ return name;
710
+ }
556
711
  function pathToFileURL(filePath) {
557
712
  const absPath = path6.resolve(filePath);
558
713
  if (process.platform === "win32") {
@@ -562,6 +717,7 @@ function pathToFileURL(filePath) {
562
717
  }
563
718
 
564
719
  // src/ssg/index.ts
720
+ var log4 = logger("ssg");
565
721
  function shopifySSG(options) {
566
722
  return {
567
723
  name: "vite-plugin-shopify:ssg",
@@ -577,14 +733,16 @@ function shopifySSG(options) {
577
733
  "manifest.json"
578
734
  );
579
735
  if (!fs2.existsSync(manifestPath)) {
580
- console.warn("[vite-plugin-shopify] No manifest.json found, skipping SSG");
736
+ log4.warn("No manifest.json found, skipping SSG");
581
737
  return;
582
738
  }
739
+ log4.debug("reading manifest from %s", manifestPath);
583
740
  const manifest = JSON.parse(fs2.readFileSync(manifestPath, "utf-8"));
584
- console.log("[vite-plugin-shopify] Starting SSG compilation...");
741
+ log4.info("Starting SSG compilation...");
585
742
  await compileAllEntries(options, manifest);
586
- console.log("[vite-plugin-shopify] SSG compilation complete");
743
+ log4.info("SSG compilation complete");
587
744
  writeImportMapSnippet(options);
745
+ log4.debug("wrote import map snippet");
588
746
  },
589
747
  resolveId(id) {
590
748
  if (id === "vite-plugin-shopify/runtime") {
@@ -626,6 +784,9 @@ function writeImportMapSnippet(options) {
626
784
  // src/index.ts
627
785
  var vitePluginShopify = (options = {}) => {
628
786
  const resolvedOptions = resolveOptions(options);
787
+ if (resolvedOptions.debug || process.env.DEBUG?.includes("vite-plugin-shopify")) {
788
+ enableDebug();
789
+ }
629
790
  return [
630
791
  shopifyConfig(resolvedOptions),
631
792
  shopifyEntries(resolvedOptions),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vite-plugin-react-shopify",
3
- "version": "1.0.1",
3
+ "version": "1.1.0",
4
4
  "description": "Vite plugin for React Shopify themes",
5
5
  "files": [
6
6
  "dist"
@@ -29,6 +29,16 @@
29
29
  "default": "./dist/runtime/settings.js"
30
30
  }
31
31
  },
32
+ "scripts": {
33
+ "dev": "tsup --watch",
34
+ "build": "tsup && cp dev-server-index.html dist/",
35
+ "typecheck": "tsc --noEmit",
36
+ "test": "vitest run",
37
+ "release": "bumpp && pnpm publish",
38
+ "release:patch": "bumpp --patch",
39
+ "release:minor": "bumpp --minor",
40
+ "release:major": "bumpp --major"
41
+ },
32
42
  "dependencies": {
33
43
  "debug": "^4.4.0",
34
44
  "fast-glob": "^3.3.0"
@@ -44,15 +54,5 @@
44
54
  },
45
55
  "peerDependencies": {
46
56
  "vite": "^8.0.0"
47
- },
48
- "scripts": {
49
- "dev": "tsup --watch",
50
- "build": "tsup && cp dev-server-index.html dist/",
51
- "typecheck": "tsc --noEmit",
52
- "test": "vitest run",
53
- "release": "bumpp && pnpm publish",
54
- "release:patch": "bumpp --patch",
55
- "release:minor": "bumpp --minor",
56
- "release:major": "bumpp --major"
57
57
  }
58
- }
58
+ }