vite-plugin-react-shopify 1.1.0 → 2.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/dist/index.js CHANGED
@@ -1,31 +1,38 @@
1
- // src/options.ts
1
+ // src/core/options.ts
2
2
  import path from "path";
3
- var defaultImportMap = {
4
- react: "https://esm.sh/react@19",
5
- reactDomClient: "https://esm.sh/react-dom@19/client"
6
- };
7
3
  var defaultPrefix = {
8
4
  template: "page.react-",
9
5
  section: "react-",
10
- block: "react-"
6
+ block: "react-",
7
+ snippet: "react-"
11
8
  };
9
+ function assetRef(buildDir, filename) {
10
+ if (buildDir === "assets") return filename;
11
+ const sub = buildDir.startsWith("assets/") ? buildDir.slice(7) : buildDir;
12
+ return `${sub}/${filename}`;
13
+ }
14
+ function liquidAssetUrl(ref) {
15
+ return `{{ '${ref}' | asset_url }}`;
16
+ }
12
17
  var resolveOptions = (options = {}) => {
13
18
  const themeRoot = options.themeRoot ?? "./";
14
19
  const sourceCodeDir = options.sourceCodeDir ?? "frontend";
15
20
  const snippetFile = options.snippetFile ?? "shopify-importmap.liquid";
16
21
  const buildDir = options.buildDir ?? "assets";
17
22
  const ssg = {
18
- directories: options.ssg?.directories ?? ["sections", "blocks", "templates"],
23
+ directories: options.ssg?.directories ?? ["sections", "blocks", "templates", "snippets"],
19
24
  prefix: {
20
25
  template: options.ssg?.prefix?.template ?? defaultPrefix.template,
21
26
  section: options.ssg?.prefix?.section ?? defaultPrefix.section,
22
- block: options.ssg?.prefix?.block ?? defaultPrefix.block
27
+ block: options.ssg?.prefix?.block ?? defaultPrefix.block,
28
+ snippet: options.ssg?.prefix?.snippet ?? defaultPrefix.snippet
23
29
  },
24
- outputName: options.ssg?.outputName ?? ""
30
+ outputName: options.ssg?.outputName ?? "",
31
+ cssPrefix: options.ssg?.cssPrefix ?? "css"
25
32
  };
26
33
  const importMap = {
27
- react: options.importMap?.react ?? defaultImportMap.react,
28
- reactDomClient: options.importMap?.reactDomClient ?? defaultImportMap.reactDomClient
34
+ react: options.importMap?.react ?? liquidAssetUrl(assetRef(buildDir, "react.js")),
35
+ reactDomClient: options.importMap?.reactDomClient ?? liquidAssetUrl(assetRef(buildDir, "react-dom.js"))
29
36
  };
30
37
  return {
31
38
  themeRoot: path.resolve(themeRoot),
@@ -33,13 +40,12 @@ var resolveOptions = (options = {}) => {
33
40
  snippetFile,
34
41
  buildDir,
35
42
  debug: options.debug ?? false,
36
- hash: options.hash ?? false,
37
43
  ssg,
38
44
  importMap
39
45
  };
40
46
  };
41
47
 
42
- // src/logger.ts
48
+ // src/core/logger.ts
43
49
  import createDebugger from "debug";
44
50
  var NAMESPACE = "vite-plugin-shopify";
45
51
  var _debugEnabled = false;
@@ -61,7 +67,7 @@ function logger(ns) {
61
67
  };
62
68
  }
63
69
 
64
- // src/config.ts
70
+ // src/core/config.ts
65
71
  import path2 from "path";
66
72
  var log = logger("config");
67
73
  function isWatchMode() {
@@ -73,32 +79,38 @@ function shopifyConfig(options) {
73
79
  config(config) {
74
80
  const sourceDirAbs = path2.resolve(options.themeRoot, options.sourceCodeDir);
75
81
  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);
82
+ log.debug("watch=%s", watch);
80
83
  const generated = {
81
84
  base: config.base ?? "./",
82
85
  publicDir: config.publicDir ?? false,
83
86
  build: {
84
87
  outDir: config.build?.outDir ?? path2.join(options.themeRoot, options.buildDir),
85
88
  assetsDir: config.build?.assetsDir ?? "",
86
- emptyOutDir: config.build?.emptyOutDir ?? false,
89
+ emptyOutDir: config.build?.emptyOutDir ?? true,
87
90
  manifest: config.build?.manifest ?? true,
88
91
  minify: config.build?.minify ?? (watch || options.debug ? false : void 0),
89
92
  sourcemap: config.build?.sourcemap ?? (watch || options.debug ? "inline" : void 0),
90
- rollupOptions: {
91
- ...config.build?.rollupOptions,
92
- external: [
93
- ...Array.isArray(config.build?.rollupOptions?.external) ? config.build.rollupOptions.external : [],
94
- "react",
95
- "react-dom/client"
96
- ],
93
+ rolldownOptions: {
94
+ ...config.build?.rolldownOptions ?? config.build?.rollupOptions,
95
+ external: Array.isArray((config.build?.rolldownOptions ?? config.build?.rollupOptions)?.external) ? (config.build?.rolldownOptions ?? config.build?.rollupOptions).external : [],
97
96
  output: {
98
- ...config.build?.rollupOptions?.output,
99
- entryFileNames,
100
- chunkFileNames,
101
- assetFileNames
97
+ ...(config.build?.rolldownOptions ?? config.build?.rollupOptions)?.output,
98
+ entryFileNames: "[name]-[hash].js",
99
+ chunkFileNames(chunkInfo) {
100
+ if (["react", "react-dom"].includes(chunkInfo.name)) {
101
+ return `${chunkInfo.name}.js`;
102
+ }
103
+ return "[name]-[hash].js";
104
+ },
105
+ assetFileNames: "[name]-[hash][extname]",
106
+ manualChunks(id) {
107
+ if (id.includes("/node_modules/react-dom/")) {
108
+ return "react-dom";
109
+ }
110
+ if (id.includes("/node_modules/react/") || id.includes("/node_modules/scheduler/")) {
111
+ return "react";
112
+ }
113
+ }
102
114
  }
103
115
  }
104
116
  },
@@ -134,7 +146,7 @@ function shopifyConfig(options) {
134
146
  };
135
147
  }
136
148
 
137
- // src/entries.ts
149
+ // src/core/entries.ts
138
150
  import path4 from "path";
139
151
  import { normalizePath as normalizePath2 } from "vite";
140
152
 
@@ -145,7 +157,8 @@ import { normalizePath } from "vite";
145
157
  var TYPE_BY_DIR = {
146
158
  templates: "template",
147
159
  sections: "section",
148
- blocks: "block"
160
+ blocks: "block",
161
+ snippets: "snippet"
149
162
  };
150
163
  function scanEntries(options) {
151
164
  const sourceDir = path3.resolve(options.themeRoot, options.sourceCodeDir);
@@ -164,7 +177,7 @@ function scanEntries(options) {
164
177
  componentName,
165
178
  kebabName,
166
179
  targetType,
167
- meta: { name: componentName }
180
+ meta: { name: deriveName(fileName) }
168
181
  });
169
182
  }
170
183
  }
@@ -173,8 +186,64 @@ function scanEntries(options) {
173
186
  function toKebabCase(str) {
174
187
  return str.replace(/([a-z])([A-Z])/g, "$1-$2").replace(/([A-Z])([A-Z][a-z])/g, "$1-$2").replace(/[\s_]+/g, "-").toLowerCase();
175
188
  }
189
+ function deriveName(fileName) {
190
+ const readable = fileName.replace(/([a-z])([A-Z])/g, "$1 $2").replace(/([A-Z])([A-Z][a-z])/g, "$1 $2").replace(/[-_]/g, " ").replace(/\s+/g, " ").trim();
191
+ return readable.length > 25 ? readable.slice(0, 25) : readable;
192
+ }
176
193
 
177
- // src/entries.ts
194
+ // src/core/entry-template.ts
195
+ function generateEntryModule(entry, componentRel) {
196
+ const { kebabName } = entry;
197
+ return [
198
+ `import { createElement } from 'react'`,
199
+ `import Component from '~/${componentRel}'`,
200
+ `import { hydrateRoot } from 'react-dom/client'`,
201
+ `import { LiquidDataProvider } from 'vite-plugin-react-shopify/runtime'`,
202
+ ``,
203
+ `const SELECTOR = '[data-ssg-component="${kebabName}"]'`,
204
+ `const roots = new Map()`,
205
+ ``,
206
+ `function readLiquidData(el) {`,
207
+ ` const script = el.querySelector(':scope > script[data-ssg-liquid]')`,
208
+ ` if (!script) return {}`,
209
+ ` try { return JSON.parse(script.textContent || '{}') } catch { return {} }`,
210
+ `}`,
211
+ ``,
212
+ `function hydrate(el) {`,
213
+ ` const h = el.querySelector(':scope > [data-ssg-hydrate]') || (el.matches('[data-ssg-hydrate]') ? el : null)`,
214
+ ` if (!h || roots.has(h)) return`,
215
+ ` const liquidData = readLiquidData(el)`,
216
+ ` roots.set(h, hydrateRoot(h, createElement(LiquidDataProvider, { value: liquidData }, createElement(Component))))`,
217
+ `}`,
218
+ ``,
219
+ `function unmount(el) {`,
220
+ ` const h = el.querySelector(':scope > [data-ssg-hydrate]') || (el.matches('[data-ssg-hydrate]') ? el : null)`,
221
+ ` if (h && roots.has(h)) { roots.get(h).unmount(); roots.delete(h) }`,
222
+ `}`,
223
+ ``,
224
+ `function scan(target) {`,
225
+ ` if (target.matches?.(SELECTOR)) hydrate(target)`,
226
+ ` target.querySelectorAll(SELECTOR).forEach(hydrate)`,
227
+ `}`,
228
+ ``,
229
+ `function sweep(target) {`,
230
+ ` if (target.matches?.(SELECTOR)) unmount(target)`,
231
+ ` target.querySelectorAll(SELECTOR).forEach(unmount)`,
232
+ `}`,
233
+ ``,
234
+ `scan(document)`,
235
+ ``,
236
+ `document.addEventListener('shopify:section:load', (e) => {`,
237
+ ` scan(e.target)`,
238
+ `})`,
239
+ ``,
240
+ `document.addEventListener('shopify:section:unload', (e) => {`,
241
+ ` sweep(e.target)`,
242
+ `})`
243
+ ].join("\n");
244
+ }
245
+
246
+ // src/core/entries.ts
178
247
  var log2 = logger("entries");
179
248
  function shopifyEntries(options) {
180
249
  let entries = [];
@@ -210,74 +279,316 @@ function shopifyEntries(options) {
210
279
  if (!entry) return;
211
280
  const sourceDir = path4.resolve(options.themeRoot, options.sourceCodeDir);
212
281
  const componentRel = normalizePath2(path4.relative(sourceDir, entry.filePath));
213
- return [
214
- `import { createElement } from 'react'`,
215
- `import Component from '~/${componentRel}'`,
216
- `import { hydrateRoot } from 'react-dom/client'`,
217
- `import { SettingsProvider } from 'vite-plugin-react-shopify/runtime/settings'`,
218
- `import { ParamsProvider } from 'vite-plugin-react-shopify/runtime/settings'`,
219
- ``,
220
- `const SELECTOR = '[data-ssg-component="${kebabName}"]'`,
221
- `const roots = new Map()`,
222
- ``,
223
- `function hydrate(el) {`,
224
- ` const h = el.querySelector(':scope > [data-ssg-hydrate]') || (el.matches('[data-ssg-hydrate]') ? el : null)`,
225
- ` if (!h || roots.has(h)) return`,
226
- ` const propsEl = el.querySelector(':scope > script[data-ssg-props]')`,
227
- ` const props = propsEl ? JSON.parse(propsEl.textContent || '{}') : {}`,
228
- ` const paramsEl = el.querySelector(':scope > script[data-ssg-params]')`,
229
- ` const params = paramsEl ? JSON.parse(paramsEl.textContent || '{}') : {}`,
230
- ` roots.set(h, hydrateRoot(h, createElement(SettingsProvider, { value: props }, createElement(ParamsProvider, { value: params }, createElement(Component)))))`,
231
- `}`,
232
- ``,
233
- `function unmount(el) {`,
234
- ` const h = el.querySelector(':scope > [data-ssg-hydrate]') || (el.matches('[data-ssg-hydrate]') ? el : null)`,
235
- ` if (h && roots.has(h)) { roots.get(h).unmount(); roots.delete(h) }`,
236
- `}`,
237
- ``,
238
- `function scan(target) {`,
239
- ` if (target.matches?.(SELECTOR)) hydrate(target)`,
240
- ` target.querySelectorAll(SELECTOR).forEach(hydrate)`,
241
- `}`,
242
- ``,
243
- `function sweep(target) {`,
244
- ` if (target.matches?.(SELECTOR)) unmount(target)`,
245
- ` target.querySelectorAll(SELECTOR).forEach(unmount)`,
246
- `}`,
247
- ``,
248
- `scan(document)`,
249
- ``,
250
- `document.addEventListener('shopify:section:load', (e) => {`,
251
- ` scan(e.target)`,
252
- `})`,
253
- ``,
254
- `document.addEventListener('shopify:section:unload', (e) => {`,
255
- ` sweep(e.target)`,
256
- `})`
257
- ].join("\n");
282
+ return generateEntryModule(entry, componentRel);
258
283
  }
259
284
  };
260
285
  }
261
286
 
262
287
  // src/ssg/index.ts
263
- import fs2 from "fs";
264
- import path7 from "path";
288
+ import fs4 from "fs";
289
+ import path10 from "path";
265
290
 
266
291
  // src/ssg/compiler.ts
292
+ import fs3 from "fs";
293
+ import path9 from "path";
294
+
295
+ // src/ssg/css-manager.ts
267
296
  import fs from "fs";
297
+ import path5 from "path";
298
+ var log3 = logger("ssg:css");
299
+ function collectCssFiles(manifestKey, manifest) {
300
+ const collected = /* @__PURE__ */ new Set();
301
+ const visited = /* @__PURE__ */ new Set();
302
+ collectCssFilesRecursive(manifestKey, manifest, collected, visited);
303
+ return [...collected];
304
+ }
305
+ function collectCssFilesRecursive(chunkKey, manifest, collected, visited) {
306
+ if (visited.has(chunkKey)) return;
307
+ visited.add(chunkKey);
308
+ const chunk = manifest[chunkKey];
309
+ if (!chunk) return;
310
+ if (chunk.css && Array.isArray(chunk.css)) {
311
+ for (const cssFile of chunk.css) {
312
+ collected.add(cssFile);
313
+ }
314
+ }
315
+ if (chunk.imports && Array.isArray(chunk.imports)) {
316
+ for (const imported of chunk.imports) {
317
+ collectCssFilesRecursive(imported, manifest, collected, visited);
318
+ }
319
+ }
320
+ }
321
+ function readCssFileContents(cssFiles, buildDir, themeRoot) {
322
+ const assetsDir = path5.resolve(themeRoot, buildDir);
323
+ return cssFiles.map((file) => {
324
+ try {
325
+ return fs.readFileSync(path5.join(assetsDir, file), "utf-8");
326
+ } catch {
327
+ return "";
328
+ }
329
+ }).filter(Boolean);
330
+ }
331
+ function getCssBaseName(cssFile) {
332
+ const name = cssFile.replace(/\.css$/, "");
333
+ const lastHyphen = name.lastIndexOf("-");
334
+ if (lastHyphen > 0) {
335
+ const possibleHash = name.slice(lastHyphen + 1);
336
+ if (/^[A-Za-z0-9_-]{8,}$/.test(possibleHash)) {
337
+ return name.slice(0, lastHyphen);
338
+ }
339
+ }
340
+ return name;
341
+ }
342
+ function analyzeCssDistribution(entries, manifest) {
343
+ const entryCssFiles = /* @__PURE__ */ new Map();
344
+ const cssRefCount = /* @__PURE__ */ new Map();
345
+ for (const entry of entries) {
346
+ const manifestKey = `shopify:entry:${entry.kebabName}`;
347
+ const files = collectCssFiles(manifestKey, manifest);
348
+ entryCssFiles.set(entry.kebabName, files);
349
+ for (const f of files) {
350
+ cssRefCount.set(f, (cssRefCount.get(f) || 0) + 1);
351
+ }
352
+ log3.debug("entry %s has %d CSS files", entry.kebabName, files.length);
353
+ }
354
+ return { entryCssFiles, cssRefCount };
355
+ }
356
+ function generateSharedCssSnippets(cssRefCount, options) {
357
+ const cssSnippetMap = /* @__PURE__ */ new Map();
358
+ for (const [cssFile, count] of cssRefCount) {
359
+ if (count > 1) {
360
+ const snippetName = `${options.ssg.cssPrefix}-${getCssBaseName(cssFile)}`;
361
+ cssSnippetMap.set(cssFile, snippetName);
362
+ const snippetPath = path5.join(
363
+ path5.resolve(options.themeRoot),
364
+ "snippets",
365
+ `${snippetName}.liquid`
366
+ );
367
+ const cssPath = path5.join(
368
+ path5.resolve(options.themeRoot, options.buildDir),
369
+ cssFile
370
+ );
371
+ try {
372
+ const cssContent = fs.readFileSync(cssPath, "utf-8");
373
+ fs.mkdirSync(path5.dirname(snippetPath), { recursive: true });
374
+ fs.writeFileSync(snippetPath, `{% stylesheet %}
375
+ ${cssContent.trim()}
376
+ {% endstylesheet %}
377
+ `);
378
+ log3.debug("generated shared CSS snippet %s (used by %d entries)", snippetName, count);
379
+ } catch {
380
+ log3.warn("failed to write CSS snippet for %s", cssFile);
381
+ }
382
+ }
383
+ }
384
+ return cssSnippetMap;
385
+ }
386
+ function categorizeCss(cssFiles, cssSnippetMap) {
387
+ const snippets = cssFiles.filter((f) => cssSnippetMap.has(f)).map((f) => cssSnippetMap.get(f));
388
+ const inlineFiles = cssFiles.filter((f) => !cssSnippetMap.has(f));
389
+ return { inline: inlineFiles, snippets };
390
+ }
391
+
392
+ // src/ssg/bundler.ts
393
+ import fs2 from "fs";
268
394
  import path6 from "path";
269
395
  import { createRequire } from "module";
270
396
 
397
+ // src/ssg/hydration-fix.ts
398
+ var log4 = logger("hydration-fix");
399
+ function autoFixAdjacentText(source, filePath) {
400
+ let fixCount = 0;
401
+ const lines = source.split("\n");
402
+ const fixed = [];
403
+ for (let i = 0; i < lines.length; i++) {
404
+ const line = lines[i];
405
+ const replaced = line.replace(
406
+ /<(\w+)([^>]*?)>([^<]*?\{[^}]*\}[^<]*?)<\/\1>/g,
407
+ (match, tagName, attrs, content) => {
408
+ const trimmed = content.trim();
409
+ if (!needsFix(trimmed)) return match;
410
+ fixCount++;
411
+ const tpl = trimmed.replace(/\{([^}]+)\}/g, "${$1}");
412
+ return `<${tagName}${attrs}>{\`${tpl}\`}</${tagName}>`;
413
+ }
414
+ );
415
+ fixed.push(replaced);
416
+ }
417
+ if (fixCount > 0) {
418
+ log4.warn(
419
+ `auto-fixed ${fixCount} adjacent text+expression issue(s) in ${filePath}`
420
+ );
421
+ }
422
+ return { result: fixed.join("\n"), fixCount };
423
+ }
424
+ function needsFix(content) {
425
+ const trimmed = content.trim();
426
+ if (!trimmed) return false;
427
+ if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
428
+ const inner = trimmed.slice(1, -1).trim();
429
+ if (inner.startsWith("`") && inner.endsWith("`")) return false;
430
+ if (inner.length > 0 && !/<[a-zA-Z]/.test(inner)) return false;
431
+ }
432
+ if (!/\{/.test(trimmed)) return false;
433
+ if (/<[a-zA-Z]/.test(trimmed)) return false;
434
+ return true;
435
+ }
436
+
437
+ // src/ssg/bundler.ts
438
+ var log5 = logger("ssg:bundler");
439
+ async function bundleEntry(entry, projectRoot, sourceDir) {
440
+ const projectRequire = createRequire(path6.join(projectRoot, "package.json"));
441
+ let esbuild;
442
+ try {
443
+ esbuild = projectRequire("esbuild");
444
+ } catch {
445
+ log5.warn("esbuild not found, skipping SSR for %s", entry.kebabName);
446
+ return null;
447
+ }
448
+ const sourceCode = fs2.readFileSync(entry.filePath, "utf-8");
449
+ const { result: fixedSource, fixCount } = autoFixAdjacentText(sourceCode, entry.filePath);
450
+ const finalSource = fixCount > 0 ? fixedSource : sourceCode;
451
+ const ts = Date.now();
452
+ const tmpDir = path6.join(sourceDir, ".ssg-tmp");
453
+ fs2.mkdirSync(tmpDir, { recursive: true });
454
+ const tmpFile = path6.join(tmpDir, `.ssg-entry-${ts}.mjs`);
455
+ log5.debug("bundling %s via esbuild", entry.kebabName);
456
+ const startBundled = Date.now();
457
+ await esbuild.build({
458
+ stdin: {
459
+ contents: finalSource,
460
+ resolveDir: path6.dirname(entry.filePath),
461
+ loader: path6.extname(entry.filePath).slice(1)
462
+ },
463
+ outfile: tmpFile,
464
+ bundle: true,
465
+ format: "esm",
466
+ jsx: "automatic",
467
+ platform: "node",
468
+ external: [
469
+ "react",
470
+ "react-dom",
471
+ "react-dom/*",
472
+ "vite-plugin-react-shopify",
473
+ "vite-plugin-react-shopify/*"
474
+ ],
475
+ write: true,
476
+ allowOverwrite: true,
477
+ plugins: [
478
+ {
479
+ name: "ssg-hydration-fix",
480
+ setup(build) {
481
+ build.onLoad({ filter: /\.(tsx|jsx)$/ }, (args) => {
482
+ try {
483
+ const source = fs2.readFileSync(args.path, "utf-8");
484
+ const { result, fixCount: fixCount2 } = autoFixAdjacentText(source, args.path);
485
+ if (fixCount2 > 0) {
486
+ return { contents: result, loader: args.path.endsWith(".tsx") ? "tsx" : "jsx" };
487
+ }
488
+ } catch {
489
+ }
490
+ return void 0;
491
+ });
492
+ }
493
+ },
494
+ {
495
+ name: "ssg-strip-css",
496
+ setup(build) {
497
+ build.onResolve({ filter: /\.module\.css$/ }, (args) => ({
498
+ namespace: "ssg-css-module",
499
+ path: args.path
500
+ }));
501
+ build.onResolve({ filter: /\.css$/ }, (args) => ({
502
+ namespace: "ssg-css-plain",
503
+ path: args.path
504
+ }));
505
+ build.onLoad({ filter: /.*/, namespace: "ssg-css-module" }, () => ({
506
+ contents: "export default new Proxy({},{get:(_,k)=>k});",
507
+ loader: "js"
508
+ }));
509
+ build.onLoad({ filter: /.*/, namespace: "ssg-css-plain" }, () => ({
510
+ contents: "",
511
+ loader: "js"
512
+ }));
513
+ }
514
+ }
515
+ ]
516
+ });
517
+ log5.debug("esbuild bundle took %dms", Date.now() - startBundled);
518
+ return { tmpFile };
519
+ }
520
+
521
+ // src/ssg/renderer.ts
522
+ import path7 from "path";
523
+ import { createRequire as createRequire2 } from "module";
524
+
271
525
  // src/ssg/post-process.ts
272
- var REACT_LIQUID_REGEX = /<\/?react-liquid>/g;
273
- function stripReactLiquidTags(html) {
274
- return html.replace(REACT_LIQUID_REGEX, "");
526
+ var VOID_ELEMENTS = /<(area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)([^>]*)\/>/g;
527
+ function normalizeVoidElements(html) {
528
+ return html.replace(VOID_ELEMENTS, "<$1$2>");
529
+ }
530
+ function normalizeStyleAttributes(html) {
531
+ return html.replace(/ style="([^"]+)"/g, (_match, content) => {
532
+ const normalized = content.replace(/:(\S)/g, ": $1").replace(/;\s*$/, "");
533
+ return ` style="${normalized};"`;
534
+ });
275
535
  }
276
536
  function unwrapHtmlEntities(html) {
277
537
  return html.replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#x27;/g, "'");
278
538
  }
279
539
 
280
- // src/ssg/schema-gen.ts
540
+ // src/ssg/renderer.ts
541
+ function pathToFileURL(filePath) {
542
+ const absPath = path7.resolve(filePath);
543
+ if (process.platform === "win32") {
544
+ return "file:///" + absPath.replace(/\\/g, "/");
545
+ }
546
+ return "file://" + absPath;
547
+ }
548
+ var log6 = logger("ssg:renderer");
549
+ function renderEntry(tmpFile, entry, projectRoot) {
550
+ return import(pathToFileURL(tmpFile)).then((mod) => {
551
+ const Component = mod.default;
552
+ const shopifyMeta = mod.shopifyMeta;
553
+ if (!Component) {
554
+ log6.warn("No default export found in %s, skipping", entry.filePath);
555
+ return null;
556
+ }
557
+ if (shopifyMeta) {
558
+ entry.meta = { ...entry.meta, ...shopifyMeta, name: shopifyMeta.name ?? entry.meta.name };
559
+ }
560
+ const projectRequire = createRequire2(path7.join(projectRoot, "package.json"));
561
+ let createElement;
562
+ let renderToStaticMarkup;
563
+ try {
564
+ createElement = projectRequire("react").createElement;
565
+ renderToStaticMarkup = projectRequire("react-dom/server").renderToStaticMarkup;
566
+ } catch {
567
+ log6.warn("react/react-dom not found, skipping SSR for %s", entry.kebabName);
568
+ return null;
569
+ }
570
+ globalThis.__shopify_ssg_target = entry.targetType;
571
+ const trackedExpressions = /* @__PURE__ */ new Set();
572
+ globalThis.__shopify_ssg_liquid_track = trackedExpressions;
573
+ const element = createElement(Component);
574
+ let html = renderToStaticMarkup(element);
575
+ delete globalThis.__shopify_ssg_liquid_track;
576
+ html = normalizeVoidElements(html);
577
+ html = normalizeStyleAttributes(html);
578
+ html = unwrapHtmlEntities(html);
579
+ return { html, trackedExpressions, entryMeta: entry.meta };
580
+ });
581
+ }
582
+ function resolveScriptAsset(kebabName, manifest) {
583
+ const manifestKey = `shopify:entry:${kebabName}`;
584
+ const entryChunk = manifest[manifestKey];
585
+ if (!entryChunk) return null;
586
+ const file = entryChunk.file;
587
+ if (!file) return null;
588
+ return path7.basename(file);
589
+ }
590
+
591
+ // src/ssg/schema.ts
281
592
  function serializeSetting(setting) {
282
593
  const s = { type: setting.type };
283
594
  if ("id" in setting) s.id = setting.id;
@@ -345,10 +656,35 @@ ${json}
345
656
  `;
346
657
  }
347
658
 
348
- // src/ssg/liquid.ts
349
- import path5 from "path";
659
+ // src/ssg/liquid-paths.ts
660
+ import path8 from "path";
661
+ function getOutputPath(entry, options) {
662
+ const type = entry.meta.type ?? entry.targetType;
663
+ const dirName = typeToDir(type);
664
+ const fileName = resolveFileName(entry, type, options);
665
+ return path8.join(options.themeRoot, dirName, fileName);
666
+ }
667
+ function typeToDir(type) {
668
+ if (type === "snippet") return "snippets";
669
+ if (type === "block") return "blocks";
670
+ return `${type}s`;
671
+ }
672
+ function getAssetRelativePath(buildDir, filename) {
673
+ if (!buildDir.startsWith("assets/")) return filename;
674
+ const prefix = buildDir.slice("assets/".length);
675
+ return prefix ? `${prefix}/${filename}` : filename;
676
+ }
677
+ function resolveFileName(entry, type, options) {
678
+ if (options.outputName) {
679
+ return options.outputName.replace(/\{type\}/g, type).replace(/\{kebab\}/g, entry.kebabName).replace(/\{pascal\}/g, entry.componentName).replace(/\{target\}/g, entry.targetType) + ".liquid";
680
+ }
681
+ const prefix = options.prefix[type] ?? "react-";
682
+ return `${prefix}${entry.kebabName}.liquid`;
683
+ }
684
+
685
+ // src/ssg/liquid-assembler.ts
350
686
  var DISCLAIMER = "{% comment %}\n IMPORTANT: This file is automatically generated by vite-plugin-shopify.\n Do not attempt to modify this file directly, as any changes will be overwritten by the next build.\n{% endcomment %}\n";
351
- function assembleLiquidFile(html, entry, scriptAsset, cssContents, options) {
687
+ function assembleLiquidFile(html, entry, scriptAsset, cssContents, options, trackedExpressions = []) {
352
688
  const type = entry.meta.type ?? entry.targetType;
353
689
  const parts = [DISCLAIMER];
354
690
  switch (type) {
@@ -356,13 +692,16 @@ function assembleLiquidFile(html, entry, scriptAsset, cssContents, options) {
356
692
  parts.push(html);
357
693
  break;
358
694
  case "section":
359
- parts.push(...buildSection(html, entry));
695
+ parts.push(...buildSection(html, entry, trackedExpressions));
360
696
  break;
361
697
  case "block":
362
- parts.push(...buildBlock(html, entry));
698
+ parts.push(...buildBlock(html, entry, trackedExpressions));
699
+ break;
700
+ case "snippet":
701
+ parts.push(...buildSnippet(html, entry, trackedExpressions));
363
702
  break;
364
703
  default:
365
- parts.push(...buildSection(html, entry));
704
+ parts.push(...buildSection(html, entry, trackedExpressions));
366
705
  break;
367
706
  }
368
707
  for (const snippet of cssContents.snippets) {
@@ -383,21 +722,27 @@ function assembleLiquidFile(html, entry, scriptAsset, cssContents, options) {
383
722
  `<script type="module" src="{{ '${assetPath}' | asset_url }}"></script>`
384
723
  );
385
724
  }
386
- parts.push(generateSchema(entry.meta));
725
+ if (type !== "snippet") {
726
+ parts.push(generateSchema(entry.meta));
727
+ }
387
728
  return parts.join("\n") + "\n";
388
729
  }
389
730
  var hasBlocks = (entry) => !!entry.meta.blocks && entry.meta.blocks.length > 0;
390
- var SETTINGS_SECTION = ` <script type="application/json" data-ssg-props>{{ section.settings | json }}</script>`;
391
- var SETTINGS_BLOCK = ` <script type="application/json" data-ssg-props>{{ block.settings | json }}</script>`;
392
- function buildParamsBridge(params) {
393
- const entries = params.map((p) => ` "${p}": {{ ${p} | json }}`).join(",\n");
394
- return ` <script type="application/json" data-ssg-params>
395
- {
396
- ${entries}
397
- }
398
- </script>`;
731
+ function buildLiquidBridge(trackedExpressions) {
732
+ if (trackedExpressions.length === 0) return "";
733
+ const entries = trackedExpressions.map((expr, i) => {
734
+ const comma = i < trackedExpressions.length - 1 ? "," : "";
735
+ return ` "${expr}": {{ ${expr} | json }}${comma}`;
736
+ });
737
+ return [
738
+ ' <script type="application/json" data-ssg-liquid>',
739
+ " {",
740
+ entries.join("\n"),
741
+ " }",
742
+ " </script>"
743
+ ].join("\n");
399
744
  }
400
- function buildSection(html, entry) {
745
+ function buildSection(html, entry, trackedExpressions) {
401
746
  const tag = entry.meta.tag ?? "div";
402
747
  const cls = entry.meta.class ?? "";
403
748
  const lines = [
@@ -408,23 +753,17 @@ function buildSection(html, entry) {
408
753
  ` data-ssg-component="${entry.kebabName}"`
409
754
  ];
410
755
  if (cls) lines.push(` class="${cls}"`);
756
+ lines.push(`>`);
757
+ const liquidBridge = buildLiquidBridge(trackedExpressions);
758
+ if (liquidBridge) lines.push(liquidBridge);
411
759
  lines.push(
412
- `>`,
413
- SETTINGS_SECTION
414
- );
415
- if (entry.meta.params?.length) {
416
- lines.push(buildParamsBridge(entry.meta.params));
417
- }
418
- lines.push(
419
- ` <div data-ssg-hydrate>`,
420
- ` ${html}`,
421
- ` </div>`
760
+ ` <div data-ssg-hydrate>${html}</div>`
422
761
  );
423
762
  if (hasBlocks(entry)) lines.push(` {% content_for 'blocks' %}`);
424
763
  lines.push(`</${tag}>`);
425
764
  return lines;
426
765
  }
427
- function buildBlock(html, entry) {
766
+ function buildBlock(html, entry, trackedExpressions) {
428
767
  const tag = entry.meta.tag ?? "div";
429
768
  const cls = entry.meta.class ?? "";
430
769
  const lines = [
@@ -442,282 +781,138 @@ function buildBlock(html, entry) {
442
781
  if (cls) lines.push(` class="${cls}"`);
443
782
  lines.push(
444
783
  ` {{ block.shopify_attributes }}`,
445
- `>`,
446
- SETTINGS_BLOCK
784
+ `>`
447
785
  );
448
- if (entry.meta.params?.length) {
449
- lines.push(buildParamsBridge(entry.meta.params));
450
- }
786
+ const liquidBridge = buildLiquidBridge(trackedExpressions);
787
+ if (liquidBridge) lines.push(liquidBridge);
451
788
  lines.push(
452
- ` <div data-ssg-hydrate>`,
453
- ` ${html}`,
454
- ` </div>`
789
+ ` <div data-ssg-hydrate>${html}</div>`
455
790
  );
456
791
  if (hasBlocks(entry)) lines.push(` {% content_for 'blocks' %}`);
457
792
  lines.push(`</${tag}>`);
458
793
  return lines;
459
794
  }
460
- function getOutputPath(entry, options) {
461
- const type = entry.meta.type ?? entry.targetType;
462
- const dirName = type === "block" ? "blocks" : `${type}s`;
463
- const fileName = resolveFileName(entry, type, options);
464
- return path5.join(options.themeRoot, dirName, fileName);
795
+ function buildSnippet(html, entry, trackedExpressions) {
796
+ const lines = [
797
+ "",
798
+ `<div data-ssg-component="${entry.kebabName}">`
799
+ ];
800
+ const liquidBridge = buildLiquidBridge(trackedExpressions);
801
+ if (liquidBridge) lines.push(liquidBridge);
802
+ lines.push(
803
+ ` <div data-ssg-hydrate>`,
804
+ ` ${html}`,
805
+ ` </div>`,
806
+ `</div>`
807
+ );
808
+ return lines;
465
809
  }
466
- function getAssetRelativePath(buildDir, filename) {
467
- if (!buildDir.startsWith("assets/")) return filename;
468
- const prefix = buildDir.slice("assets/".length);
469
- return prefix ? `${prefix}/${filename}` : filename;
810
+
811
+ // src/validate/rules.ts
812
+ var MAX_NAME_LENGTH = 25;
813
+ function checkNameLength(meta, kebabName) {
814
+ if (meta.name.length > MAX_NAME_LENGTH) {
815
+ return `[${kebabName}] shopifyMeta.name "${meta.name}" is ${meta.name.length} chars (Shopify limit: ${MAX_NAME_LENGTH})`;
816
+ }
817
+ return null;
470
818
  }
471
- function resolveFileName(entry, type, options) {
472
- if (options.outputName) {
473
- return options.outputName.replace(/\{type\}/g, type).replace(/\{kebab\}/g, entry.kebabName).replace(/\{pascal\}/g, entry.componentName).replace(/\{target\}/g, entry.targetType) + ".liquid";
819
+ function checkEmptyStringDefault(setting) {
820
+ if (setting.default === "") {
821
+ const label = "id" in setting && setting.id ? setting.id : "(no id)";
822
+ return `Setting "${label}" (type: ${setting.type}) has empty string default`;
474
823
  }
475
- const prefix = options.prefix[type] ?? "react-";
476
- return `${prefix}${entry.kebabName}.liquid`;
824
+ return null;
825
+ }
826
+
827
+ // src/validate/index.ts
828
+ var log7 = logger("validate");
829
+ function validateShopifyMeta(meta, context) {
830
+ const warnings = [];
831
+ const nameWarning = checkNameLength(meta, context.kebabName);
832
+ if (nameWarning) warnings.push(nameWarning);
833
+ if (meta.settings) {
834
+ for (const s of meta.settings) {
835
+ const w = checkEmptyStringDefault(s);
836
+ if (w) warnings.push(w);
837
+ }
838
+ }
839
+ for (const w of warnings) {
840
+ log7.warn(w);
841
+ }
842
+ return warnings;
477
843
  }
478
844
 
479
845
  // src/ssg/compiler.ts
480
- var log3 = logger("ssg:compiler");
481
- var SNIPPET_PREFIX = "react-css";
846
+ var log8 = logger("ssg:compiler");
482
847
  async function compileAllEntries(options, manifest) {
483
848
  const entries = scanEntries(options);
484
849
  if (entries.length === 0) return;
485
- log3.debug("found %d entries to compile", entries.length);
486
- const projectRoot = path6.resolve(options.themeRoot);
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
- }
850
+ log8.debug("found %d entries to compile", entries.length);
851
+ const projectRoot = path9.resolve(options.themeRoot);
852
+ const sourceDir = path9.resolve(options.themeRoot, options.sourceCodeDir);
853
+ const { entryCssFiles, cssRefCount } = analyzeCssDistribution(entries, manifest);
854
+ const cssSnippetMap = generateSharedCssSnippets(cssRefCount, options);
526
855
  for (const entry of entries) {
527
856
  try {
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);
857
+ await compileEntry(entry, options, manifest, projectRoot, sourceDir, entryCssFiles, cssSnippetMap);
540
858
  } catch (err) {
541
- log3.error("Failed to compile %s:", entry.filePath, err);
859
+ log8.error("Failed to compile %s:", entry.filePath, err);
542
860
  }
543
861
  }
544
- log3.info("Compiled %d entries", entries.length);
545
- const tmpDir = path6.join(sourceDir, ".ssg-tmp");
862
+ log8.info("Compiled %d entries", entries.length);
863
+ const tmpDir = path9.join(sourceDir, ".ssg-tmp");
546
864
  try {
547
- fs.rmSync(tmpDir, { recursive: true, force: true });
865
+ fs3.rmSync(tmpDir, { recursive: true, force: true });
548
866
  } catch {
549
867
  }
550
868
  }
551
- async function compileEntry(entry, options, manifest, projectRoot, sourceDir, cssInline, cssSnippets) {
552
- const projectRequire = createRequire(path6.join(projectRoot, "package.json"));
553
- let createElement;
554
- let renderToStaticMarkup;
555
- try {
556
- createElement = projectRequire("react").createElement;
557
- renderToStaticMarkup = projectRequire("react-dom/server").renderToStaticMarkup;
558
- } catch {
559
- log3.warn("react/react-dom not found, skipping SSR for %s", entry.kebabName);
560
- return;
561
- }
562
- const sourceCode = fs.readFileSync(entry.filePath, "utf-8");
563
- let esbuild;
564
- try {
565
- esbuild = projectRequire("esbuild");
566
- } catch {
567
- log3.warn("esbuild not found, skipping SSR for %s", entry.kebabName);
568
- return;
569
- }
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,
584
- format: "esm",
585
- jsx: "automatic",
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
- ]
619
- });
620
- log3.debug("esbuild bundle took %dms", Date.now() - startBundled);
869
+ async function compileEntry(entry, options, manifest, projectRoot, sourceDir, entryCssFiles, cssSnippetMap) {
870
+ const bundleResult = await bundleEntry(entry, projectRoot, sourceDir);
871
+ if (!bundleResult) return;
621
872
  try {
622
- const mod = await import(pathToFileURL(tmpFile));
623
- const Component = mod.default;
624
- const shopifyMeta = mod.shopifyMeta;
625
- if (!Component) {
626
- log3.warn("No default export found in %s, skipping", entry.filePath);
627
- return;
628
- }
629
- if (shopifyMeta) {
630
- entry.meta = { ...entry.meta, ...shopifyMeta };
631
- }
632
- globalThis.__shopify_ssg_target = entry.targetType;
633
- const element = createElement(Component);
634
- let html = renderToStaticMarkup(element);
635
- html = stripReactLiquidTags(html);
636
- html = unwrapHtmlEntities(html);
873
+ const renderResult = await renderEntry(bundleResult.tmpFile, entry, projectRoot);
874
+ if (!renderResult) return;
875
+ const { html, trackedExpressions } = renderResult;
876
+ validateShopifyMeta(entry.meta, { kebabName: entry.kebabName, filePath: entry.filePath });
877
+ const cssFiles = entryCssFiles.get(entry.kebabName) || [];
878
+ const { inline: cssInlineFiles, snippets: cssSnippets } = categorizeCss(cssFiles, cssSnippetMap);
879
+ const cssInline = readCssFileContents(cssInlineFiles, options.buildDir, options.themeRoot);
880
+ log8.debug(
881
+ "compiling %s (type=%s, css inline=%d, css snippets=%d)",
882
+ entry.kebabName,
883
+ entry.targetType,
884
+ cssInline.length,
885
+ cssSnippets.length
886
+ );
637
887
  const scriptAsset = resolveScriptAsset(entry.kebabName, manifest);
638
- const liquidContent = assembleLiquidFile(html, entry, scriptAsset, { inline: cssInline, snippets: cssSnippets }, {
888
+ const liquidContent = assembleLiquidFile(html, entry, scriptAsset, {
889
+ inline: cssInline,
890
+ snippets: cssSnippets
891
+ }, {
639
892
  prefix: options.ssg.prefix,
640
893
  outputName: options.ssg.outputName || void 0,
641
894
  buildDir: options.buildDir
642
- });
895
+ }, [...trackedExpressions]);
643
896
  const outputPath = getOutputPath(entry, {
644
897
  prefix: options.ssg.prefix,
645
898
  outputName: options.ssg.outputName || void 0,
646
899
  themeRoot: options.themeRoot
647
900
  });
648
- const dir = path6.dirname(outputPath);
649
- if (!fs.existsSync(dir)) {
650
- fs.mkdirSync(dir, { recursive: true });
901
+ const dir = path9.dirname(outputPath);
902
+ if (!fs3.existsSync(dir)) {
903
+ fs3.mkdirSync(dir, { recursive: true });
651
904
  }
652
- fs.writeFileSync(outputPath, liquidContent);
905
+ fs3.writeFileSync(outputPath, liquidContent);
653
906
  } finally {
654
907
  try {
655
- fs.unlinkSync(tmpFile);
656
- } catch {
657
- }
658
- }
659
- }
660
- function resolveScriptAsset(kebabName, manifest) {
661
- const manifestKey = `shopify:entry:${kebabName}`;
662
- const entryChunk = manifest[manifestKey];
663
- if (!entryChunk) return null;
664
- const file = entryChunk.file;
665
- if (!file) return null;
666
- return path6.basename(file);
667
- }
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) {
691
- const assetsDir = path6.resolve(themeRoot, buildDir);
692
- return cssFiles.map((file) => {
693
- try {
694
- return fs.readFileSync(path6.join(assetsDir, file), "utf-8");
908
+ fs3.unlinkSync(bundleResult.tmpFile);
695
909
  } catch {
696
- return "";
697
910
  }
698
- }).filter(Boolean);
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
- }
711
- function pathToFileURL(filePath) {
712
- const absPath = path6.resolve(filePath);
713
- if (process.platform === "win32") {
714
- return "file:///" + absPath.replace(/\\/g, "/");
715
911
  }
716
- return "file://" + absPath;
717
912
  }
718
913
 
719
914
  // src/ssg/index.ts
720
- var log4 = logger("ssg");
915
+ var log9 = logger("ssg");
721
916
  function shopifySSG(options) {
722
917
  return {
723
918
  name: "vite-plugin-shopify:ssg",
@@ -726,23 +921,23 @@ function shopifySSG(options) {
726
921
  return {};
727
922
  },
728
923
  async closeBundle() {
729
- const manifestPath = path7.resolve(
924
+ const manifestPath = path10.resolve(
730
925
  options.themeRoot,
731
926
  options.buildDir,
732
927
  ".vite",
733
928
  "manifest.json"
734
929
  );
735
- if (!fs2.existsSync(manifestPath)) {
736
- log4.warn("No manifest.json found, skipping SSG");
930
+ if (!fs4.existsSync(manifestPath)) {
931
+ log9.warn("No manifest.json found, skipping SSG");
737
932
  return;
738
933
  }
739
- log4.debug("reading manifest from %s", manifestPath);
740
- const manifest = JSON.parse(fs2.readFileSync(manifestPath, "utf-8"));
741
- log4.info("Starting SSG compilation...");
934
+ log9.debug("reading manifest from %s", manifestPath);
935
+ const manifest = JSON.parse(fs4.readFileSync(manifestPath, "utf-8"));
936
+ log9.info("Starting SSG compilation...");
742
937
  await compileAllEntries(options, manifest);
743
- log4.info("SSG compilation complete");
938
+ log9.info("SSG compilation complete");
744
939
  writeImportMapSnippet(options);
745
- log4.debug("wrote import map snippet");
940
+ log9.debug("wrote import map snippet");
746
941
  },
747
942
  resolveId(id) {
748
943
  if (id === "vite-plugin-shopify/runtime") {
@@ -751,13 +946,17 @@ function shopifySSG(options) {
751
946
  },
752
947
  load(id) {
753
948
  if (id === "\0vite-plugin-shopify:runtime") {
754
- return `export { Liquid } from 'vite-plugin-shopify/runtime/Liquid'`;
949
+ const exports = [
950
+ `export { LiquidDataProvider, LiquidDataContext } from 'vite-plugin-shopify/runtime'`,
951
+ `export { useLiquid, useLiquidValues, useSectionSettings, useBlockSettings, useSnippetParams, useBlockParams } from 'vite-plugin-shopify/runtime'`
952
+ ];
953
+ return exports.join("\n");
755
954
  }
756
955
  }
757
956
  };
758
957
  }
759
958
  function writeImportMapSnippet(options) {
760
- const snippetPath = path7.resolve(
959
+ const snippetPath = path10.resolve(
761
960
  options.themeRoot,
762
961
  "snippets",
763
962
  options.snippetFile
@@ -774,11 +973,11 @@ function writeImportMapSnippet(options) {
774
973
  "</script>",
775
974
  ""
776
975
  ].join("\n");
777
- const dir = path7.dirname(snippetPath);
778
- if (!fs2.existsSync(dir)) {
779
- fs2.mkdirSync(dir, { recursive: true });
976
+ const dir = path10.dirname(snippetPath);
977
+ if (!fs4.existsSync(dir)) {
978
+ fs4.mkdirSync(dir, { recursive: true });
780
979
  }
781
- fs2.writeFileSync(snippetPath, content);
980
+ fs4.writeFileSync(snippetPath, content);
782
981
  }
783
982
 
784
983
  // src/index.ts