vite-plugin-react-shopify 2.2.4 → 2.2.8
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.d.ts +141 -14
- package/dist/index.js +159 -175
- package/dist/runtime/index.d.ts +69 -0
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import { Plugin } from 'vite';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* @file User-facing configuration options for the Vite plugin.
|
|
5
|
+
*
|
|
6
|
+
* All fields are optional with sensible defaults applied at resolution time
|
|
7
|
+
* by {@link resolveOptions} (core/options.ts).
|
|
8
|
+
*/
|
|
9
|
+
/** Top-level plugin options passed to `vitePluginShopify()`. */
|
|
3
10
|
interface Options {
|
|
4
11
|
themeRoot?: string;
|
|
5
12
|
sourceCodeDir?: string;
|
|
@@ -9,6 +16,7 @@ interface Options {
|
|
|
9
16
|
ssg?: SSGOptions;
|
|
10
17
|
importMap?: ImportMapOptions;
|
|
11
18
|
}
|
|
19
|
+
/** Static Site Generation configuration. */
|
|
12
20
|
interface SSGOptions {
|
|
13
21
|
directories?: string[];
|
|
14
22
|
prefix?: {
|
|
@@ -20,11 +28,20 @@ interface SSGOptions {
|
|
|
20
28
|
outputName?: string;
|
|
21
29
|
cssPrefix?: string;
|
|
22
30
|
}
|
|
31
|
+
/** CDN URLs for the import map snippet injected into the theme. */
|
|
23
32
|
interface ImportMapOptions {
|
|
24
33
|
react?: string;
|
|
25
34
|
reactDomClient?: string;
|
|
26
35
|
}
|
|
27
36
|
|
|
37
|
+
/**
|
|
38
|
+
* @file TypeScript type definitions for Shopify theme setting schemas.
|
|
39
|
+
*
|
|
40
|
+
* Covers every Shopify setting type (text, checkbox, color, image_picker,
|
|
41
|
+
* etc.) as well as informational sidebar types (header, paragraph). Also
|
|
42
|
+
* includes utility types for type-level validation ({@link AssertNoEmptyDefaults})
|
|
43
|
+
* and schema inference ({@link InferSettings}).
|
|
44
|
+
*/
|
|
28
45
|
interface BaseSettingSchema {
|
|
29
46
|
id: string;
|
|
30
47
|
label: string;
|
|
@@ -238,39 +255,126 @@ type InferSettings<T extends readonly {
|
|
|
238
255
|
[K in T[number] as K["id"]]: ValueForType<K["type"]>;
|
|
239
256
|
};
|
|
240
257
|
|
|
258
|
+
/**
|
|
259
|
+
* @file Shopify block/section metadata types.
|
|
260
|
+
*
|
|
261
|
+
* Defines {@link ShopifyMeta} — the shape of the `shopifyMeta` export from
|
|
262
|
+
* React components — along with supporting types for presets and block
|
|
263
|
+
* definitions. This metadata drives schema generation and Liquid output.
|
|
264
|
+
*/
|
|
265
|
+
|
|
266
|
+
/** The four Shopify theme component categories. */
|
|
241
267
|
type ShopifyBlockType = "template" | "section" | "block" | "snippet";
|
|
268
|
+
/**
|
|
269
|
+
* A block definition within a section's `blocks` attribute.
|
|
270
|
+
*
|
|
271
|
+
* Per the Shopify section schema, each block entry supports the following:
|
|
272
|
+
*
|
|
273
|
+
* | Attribute | Required | Description |
|
|
274
|
+
* | ---------- | -------- | -------------------------------------------------------------------- |
|
|
275
|
+
* | `type` | Yes | Free-form block type identifier. |
|
|
276
|
+
* | `name` | No | Block name shown as the block title in the theme editor. Auto-derived from `type` (e.g. `text-block` → `Text Block`) if omitted. |
|
|
277
|
+
* | `limit` | No | Max number of blocks of this type that can be used. |
|
|
278
|
+
* | `settings` | No | Input or sidebar settings exposed to the merchant for this block. |
|
|
279
|
+
*/
|
|
280
|
+
interface BlockDefinition {
|
|
281
|
+
type: string;
|
|
282
|
+
name?: string;
|
|
283
|
+
limit?: number;
|
|
284
|
+
settings?: SettingSchema[];
|
|
285
|
+
}
|
|
286
|
+
/**
|
|
287
|
+
* Template/group scope filter used by `enabled_on` / `disabled_on`.
|
|
288
|
+
*
|
|
289
|
+
* Per the Shopify section schema, at least one of `templates` or `groups`
|
|
290
|
+
* must be provided. `enabled_on` and `disabled_on` are mutually exclusive —
|
|
291
|
+
* a section may declare one but not both.
|
|
292
|
+
*
|
|
293
|
+
* | Attribute | Description |
|
|
294
|
+
* | ----------- | ---------------------------------------------------------------------------------------- |
|
|
295
|
+
* | `templates` | Page types the section is restricted to (or excluded from). Use `["*"]` for all. |
|
|
296
|
+
* | `groups` | Section group types: `header`, `footer`, `aside`, or `custom.<NAME>`. Use `["*"]` for all. |
|
|
297
|
+
*/
|
|
298
|
+
interface TemplateScope {
|
|
299
|
+
templates?: string[];
|
|
300
|
+
groups?: string[];
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Section-level translation overrides for the schema.
|
|
304
|
+
*
|
|
305
|
+
* Each entry maps a language code (e.g. `"en"`, `"fr"`) to a flat key/value
|
|
306
|
+
* translation map. When emitted, translations are accessed in Liquid via
|
|
307
|
+
* the `t` filter using the key `sections.<section-name>.<key>`.
|
|
308
|
+
*/
|
|
309
|
+
type SectionLocales = Record<string, Record<string, string>>;
|
|
310
|
+
/**
|
|
311
|
+
* Metadata for a Shopify section, block, snippet, or template.
|
|
312
|
+
*
|
|
313
|
+
* Exported as `shopifyMeta` from the React component file. Drives the
|
|
314
|
+
* generated {% schema %} block and influences Liquid wrapper output.
|
|
315
|
+
*/
|
|
242
316
|
interface ShopifyMeta {
|
|
243
317
|
type?: ShopifyBlockType;
|
|
244
318
|
name?: string;
|
|
245
|
-
|
|
319
|
+
/**
|
|
320
|
+
* HTML wrapper tag. Use `null` to render without a wrapper (blocks only).
|
|
321
|
+
*
|
|
322
|
+
* For sections Shopify only accepts a fixed set of tags (`article`,
|
|
323
|
+
* `aside`, `div`, `footer`, `header`, `section`); for blocks any string
|
|
324
|
+
* up to 50 chars is accepted.
|
|
325
|
+
*/
|
|
326
|
+
tag?: string | null;
|
|
246
327
|
class?: string;
|
|
247
328
|
limit?: number;
|
|
248
329
|
params?: string[];
|
|
249
330
|
settings?: SettingSchema[];
|
|
250
|
-
blocks?:
|
|
251
|
-
type: string;
|
|
252
|
-
name?: string;
|
|
253
|
-
settings?: SettingSchema[];
|
|
254
|
-
}[];
|
|
331
|
+
blocks?: BlockDefinition[];
|
|
255
332
|
max_blocks?: number;
|
|
256
333
|
presets?: PresetDefinition[];
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
334
|
+
/**
|
|
335
|
+
* Default configuration used when a section is statically rendered. Has
|
|
336
|
+
* the same shape as a {@link PresetDefinition}.
|
|
337
|
+
*/
|
|
338
|
+
default?: PresetDefinition;
|
|
339
|
+
/**
|
|
340
|
+
* Inline translation overrides for the section, scoped to the
|
|
341
|
+
* theme editor's **Sections** tab.
|
|
342
|
+
*/
|
|
343
|
+
locales?: SectionLocales;
|
|
344
|
+
enabled_on?: TemplateScope;
|
|
345
|
+
disabled_on?: TemplateScope;
|
|
346
|
+
}
|
|
347
|
+
/** A theme editor preset definition within the schema. */
|
|
261
348
|
interface PresetDefinition {
|
|
262
349
|
name: string;
|
|
263
350
|
category?: string;
|
|
264
351
|
settings?: InputSettings;
|
|
265
352
|
blocks?: PresetBlock[];
|
|
266
353
|
}
|
|
267
|
-
interface
|
|
354
|
+
interface CommonBlockPreset {
|
|
268
355
|
type: string;
|
|
269
|
-
|
|
270
|
-
static?: boolean;
|
|
356
|
+
name?: string;
|
|
271
357
|
settings?: InputSettings;
|
|
272
358
|
blocks?: PresetBlock[];
|
|
273
359
|
}
|
|
360
|
+
interface ShopifyNormalBlockPreset extends CommonBlockPreset {
|
|
361
|
+
id?: never;
|
|
362
|
+
static?: never;
|
|
363
|
+
}
|
|
364
|
+
interface ShopifyStaticBlockPreset extends CommonBlockPreset {
|
|
365
|
+
id: string;
|
|
366
|
+
static: true;
|
|
367
|
+
}
|
|
368
|
+
/** A nested block preset inside a parent preset. */
|
|
369
|
+
type PresetBlock = ShopifyNormalBlockPreset | ShopifyStaticBlockPreset;
|
|
370
|
+
|
|
371
|
+
/**
|
|
372
|
+
* @file SSG entry type representing a discovered React component target.
|
|
373
|
+
*
|
|
374
|
+
* Each entry maps to one Liquid output file (section, block, snippet, or
|
|
375
|
+
* template). The {@link SSGEntry.meta} field carries {@link ShopifyMeta}
|
|
376
|
+
* merged with auto-derived defaults.
|
|
377
|
+
*/
|
|
274
378
|
|
|
275
379
|
interface SSGEntry {
|
|
276
380
|
filePath: string;
|
|
@@ -280,6 +384,29 @@ interface SSGEntry {
|
|
|
280
384
|
meta: Required<Pick<ShopifyMeta, "name">> & ShopifyMeta;
|
|
281
385
|
}
|
|
282
386
|
|
|
387
|
+
/**
|
|
388
|
+
* @file Main entry point for the `vite-plugin-react-shopify` package.
|
|
389
|
+
*
|
|
390
|
+
* Composes four Vite plugins (hydration-fix, config, entries, SSG) into
|
|
391
|
+
* a single array returned to the user's `vite.config.ts`. Also re-exports
|
|
392
|
+
* all public types for consumers.
|
|
393
|
+
*
|
|
394
|
+
* @example
|
|
395
|
+
* ```ts
|
|
396
|
+
* // vite.config.ts
|
|
397
|
+
* import vitePluginShopify from 'vite-plugin-react-shopify'
|
|
398
|
+
* export default {
|
|
399
|
+
* plugins: [vitePluginShopify({ themeRoot: '.', sourceCodeDir: 'frontend' })],
|
|
400
|
+
* }
|
|
401
|
+
* ```
|
|
402
|
+
*/
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Create the complete set of Vite plugins for React Shopify theme development.
|
|
406
|
+
*
|
|
407
|
+
* @param options User configuration (all fields optional).
|
|
408
|
+
* @returns An array of Vite plugin instances.
|
|
409
|
+
*/
|
|
283
410
|
declare const vitePluginShopify: (options?: Options) => Plugin[];
|
|
284
411
|
|
|
285
|
-
export { type ArticleListSetting, type ArticleSetting, type AssertNoEmptyDefaults, type BlogSetting, type CheckboxSetting, type CollectionListSetting, type CollectionSetting, type ColorBackgroundSetting, type ColorSchemeGroupSetting, type ColorSchemeRole, type ColorSchemeSetting, type ColorSetting, type FontPickerSetting, type HeaderSetting, type HtmlSetting, type ImagePickerSetting, type ImportMapOptions, type InferSettings, type InlineRichtextSetting, type InputSettingSchema, type InputSettings, type LineBreakSetting, type LinkListSetting, type LiquidSetting, type MetaobjectListSetting, type MetaobjectSetting, type NumberSetting, type Options, type PageSetting, type ParagraphSetting, type PresetBlock, type PresetDefinition, type ProductListSetting, type ProductSetting, type RadioSetting, type RangeSetting, type RichtextSetting, type SSGEntry, type SSGOptions, type SchemaSetting, type SelectSetting, type SettingSchema, type SettingType, type SettingValue, type ShopifyBlockType, type ShopifyMeta, type SidebarSetting, type TextAlignmentSetting, type TextSetting, type TextareaSetting, type UrlSetting, type VideoSetting, type VideoUrlSetting, vitePluginShopify as default };
|
|
412
|
+
export { type ArticleListSetting, type ArticleSetting, type AssertNoEmptyDefaults, type BlockDefinition, type BlogSetting, type CheckboxSetting, type CollectionListSetting, type CollectionSetting, type ColorBackgroundSetting, type ColorSchemeGroupSetting, type ColorSchemeRole, type ColorSchemeSetting, type ColorSetting, type FontPickerSetting, type HeaderSetting, type HtmlSetting, type ImagePickerSetting, type ImportMapOptions, type InferSettings, type InlineRichtextSetting, type InputSettingSchema, type InputSettings, type LineBreakSetting, type LinkListSetting, type LiquidSetting, type MetaobjectListSetting, type MetaobjectSetting, type NumberSetting, type Options, type PageSetting, type ParagraphSetting, type PresetBlock, type PresetDefinition, type ProductListSetting, type ProductSetting, type RadioSetting, type RangeSetting, type RichtextSetting, type SSGEntry, type SSGOptions, type SchemaSetting, type SelectSetting, type SettingSchema, type SettingType, type SettingValue, type ShopifyBlockType, type ShopifyMeta, type SidebarSetting, type TextAlignmentSetting, type TextSetting, type TextareaSetting, type UrlSetting, type VideoSetting, type VideoUrlSetting, vitePluginShopify as default };
|
package/dist/index.js
CHANGED
|
@@ -154,6 +154,24 @@ import { normalizePath as normalizePath2 } from "vite";
|
|
|
154
154
|
import path3 from "path";
|
|
155
155
|
import glob from "fast-glob";
|
|
156
156
|
import { normalizePath } from "vite";
|
|
157
|
+
|
|
158
|
+
// src/validate/rules.ts
|
|
159
|
+
var MAX_NAME_LENGTH = 25;
|
|
160
|
+
function checkNameLength(meta, kebabName) {
|
|
161
|
+
if (meta.name.length > MAX_NAME_LENGTH) {
|
|
162
|
+
return `[${kebabName}] shopifyMeta.name "${meta.name}" is ${meta.name.length} chars (Shopify limit: ${MAX_NAME_LENGTH})`;
|
|
163
|
+
}
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
function checkEmptyStringDefault(setting) {
|
|
167
|
+
if (setting.default === "") {
|
|
168
|
+
const label = "id" in setting && setting.id ? setting.id : "(no id)";
|
|
169
|
+
return `Setting "${label}" (type: ${setting.type}) has empty string default`;
|
|
170
|
+
}
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// src/ssg/scanner.ts
|
|
157
175
|
var TYPE_BY_DIR = {
|
|
158
176
|
templates: "template",
|
|
159
177
|
sections: "section",
|
|
@@ -188,7 +206,7 @@ function toKebabCase(str) {
|
|
|
188
206
|
}
|
|
189
207
|
function deriveName(fileName) {
|
|
190
208
|
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 >
|
|
209
|
+
return readable.length > MAX_NAME_LENGTH ? readable.slice(0, MAX_NAME_LENGTH) : readable;
|
|
192
210
|
}
|
|
193
211
|
|
|
194
212
|
// src/core/entry-template.ts
|
|
@@ -295,98 +313,73 @@ import path9 from "path";
|
|
|
295
313
|
// src/ssg/css-manager.ts
|
|
296
314
|
import fs from "fs";
|
|
297
315
|
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
316
|
function analyzeCssDistribution(entries, manifest) {
|
|
343
317
|
const entryCssFiles = /* @__PURE__ */ new Map();
|
|
344
318
|
const cssRefCount = /* @__PURE__ */ new Map();
|
|
345
319
|
for (const entry of entries) {
|
|
346
320
|
const manifestKey = `shopify:entry:${entry.kebabName}`;
|
|
347
|
-
const
|
|
348
|
-
|
|
349
|
-
|
|
321
|
+
const chunk = manifest[manifestKey];
|
|
322
|
+
if (!chunk) continue;
|
|
323
|
+
const cssFiles = collectCssFiles(chunk, manifest, /* @__PURE__ */ new Set());
|
|
324
|
+
entryCssFiles.set(entry.kebabName, cssFiles);
|
|
325
|
+
for (const f of cssFiles) {
|
|
350
326
|
cssRefCount.set(f, (cssRefCount.get(f) || 0) + 1);
|
|
351
327
|
}
|
|
352
|
-
log3.debug("entry %s has %d CSS files", entry.kebabName, files.length);
|
|
353
328
|
}
|
|
354
329
|
return { entryCssFiles, cssRefCount };
|
|
355
330
|
}
|
|
356
331
|
function generateSharedCssSnippets(cssRefCount, options) {
|
|
357
332
|
const cssSnippetMap = /* @__PURE__ */ new Map();
|
|
333
|
+
const snippetsDir = path5.resolve(options.themeRoot, "snippets");
|
|
334
|
+
fs.mkdirSync(snippetsDir, { recursive: true });
|
|
358
335
|
for (const [cssFile, count] of cssRefCount) {
|
|
359
|
-
if (count
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
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()}
|
|
336
|
+
if (count < 2) continue;
|
|
337
|
+
const cssName = path5.basename(cssFile, path5.extname(cssFile));
|
|
338
|
+
const snippetName = `${options.ssg.cssPrefix || "css-"}${cssName}`;
|
|
339
|
+
const cssContent = readCssFile(cssFile, options.buildDir, options.themeRoot);
|
|
340
|
+
fs.writeFileSync(
|
|
341
|
+
path5.join(snippetsDir, `${snippetName}.liquid`),
|
|
342
|
+
`{% stylesheet %}
|
|
343
|
+
${cssContent}
|
|
376
344
|
{% endstylesheet %}
|
|
377
|
-
`
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
log3.warn("failed to write CSS snippet for %s", cssFile);
|
|
381
|
-
}
|
|
382
|
-
}
|
|
345
|
+
`
|
|
346
|
+
);
|
|
347
|
+
cssSnippetMap.set(cssFile, snippetName);
|
|
383
348
|
}
|
|
384
349
|
return cssSnippetMap;
|
|
385
350
|
}
|
|
386
351
|
function categorizeCss(cssFiles, cssSnippetMap) {
|
|
387
|
-
const
|
|
388
|
-
const
|
|
389
|
-
|
|
352
|
+
const inline = [];
|
|
353
|
+
const snippets = [];
|
|
354
|
+
for (const f of cssFiles) {
|
|
355
|
+
const snippetName = cssSnippetMap.get(f);
|
|
356
|
+
if (snippetName) {
|
|
357
|
+
snippets.push(snippetName);
|
|
358
|
+
} else {
|
|
359
|
+
inline.push(f);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
return { inline, snippets };
|
|
363
|
+
}
|
|
364
|
+
function readCssFileContents(cssFiles, buildDir, themeRoot) {
|
|
365
|
+
return cssFiles.map((f) => readCssFile(f, buildDir, themeRoot));
|
|
366
|
+
}
|
|
367
|
+
function readCssFile(cssFile, buildDir, themeRoot) {
|
|
368
|
+
return fs.readFileSync(path5.resolve(themeRoot, buildDir, cssFile), "utf-8");
|
|
369
|
+
}
|
|
370
|
+
function collectCssFiles(chunk, manifest, visited) {
|
|
371
|
+
if (visited.has(chunk.file)) return [];
|
|
372
|
+
visited.add(chunk.file);
|
|
373
|
+
const css = [...chunk.css || []];
|
|
374
|
+
if (chunk.imports) {
|
|
375
|
+
for (const imp of chunk.imports) {
|
|
376
|
+
const child = manifest[imp];
|
|
377
|
+
if (child) {
|
|
378
|
+
css.push(...collectCssFiles(child, manifest, visited));
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
return css;
|
|
390
383
|
}
|
|
391
384
|
|
|
392
385
|
// src/ssg/bundler.ts
|
|
@@ -397,11 +390,11 @@ import { createRequire } from "module";
|
|
|
397
390
|
// src/hydration-fix/index.ts
|
|
398
391
|
import { parseSync } from "oxc-parser";
|
|
399
392
|
import { walk } from "oxc-walker";
|
|
400
|
-
var
|
|
393
|
+
var log3 = logger("hydration-fix");
|
|
401
394
|
function autoFixAdjacentText(source, filePath) {
|
|
402
395
|
const parseResult = parseSync(filePath, source);
|
|
403
396
|
if (parseResult.errors.length > 0) {
|
|
404
|
-
|
|
397
|
+
log3.debug("OXC parse errors for %s, skipping hydration fix", filePath);
|
|
405
398
|
return { result: source, fixCount: 0 };
|
|
406
399
|
}
|
|
407
400
|
const replacements = [];
|
|
@@ -423,7 +416,7 @@ function autoFixAdjacentText(source, filePath) {
|
|
|
423
416
|
for (const { start, end, replacement } of replacements) {
|
|
424
417
|
fixed = fixed.slice(0, start) + replacement + fixed.slice(end);
|
|
425
418
|
}
|
|
426
|
-
|
|
419
|
+
log3.warn(
|
|
427
420
|
`auto-fixed ${replacements.length} adjacent text+expression issue(s) in ${filePath}`
|
|
428
421
|
);
|
|
429
422
|
return { result: fixed, fixCount: replacements.length };
|
|
@@ -478,14 +471,14 @@ function needsFix(content) {
|
|
|
478
471
|
}
|
|
479
472
|
|
|
480
473
|
// src/ssg/bundler.ts
|
|
481
|
-
var
|
|
474
|
+
var log4 = logger("ssg:bundler");
|
|
482
475
|
async function bundleEntry(entry, projectRoot, sourceDir) {
|
|
483
476
|
const projectRequire = createRequire(path6.join(projectRoot, "package.json"));
|
|
484
477
|
let esbuild;
|
|
485
478
|
try {
|
|
486
479
|
esbuild = projectRequire("esbuild");
|
|
487
480
|
} catch {
|
|
488
|
-
|
|
481
|
+
log4.warn("esbuild not found, skipping SSR for %s", entry.kebabName);
|
|
489
482
|
return null;
|
|
490
483
|
}
|
|
491
484
|
const sourceCode = fs2.readFileSync(entry.filePath, "utf-8");
|
|
@@ -495,7 +488,7 @@ async function bundleEntry(entry, projectRoot, sourceDir) {
|
|
|
495
488
|
const tmpDir = path6.join(sourceDir, ".ssg-tmp");
|
|
496
489
|
fs2.mkdirSync(tmpDir, { recursive: true });
|
|
497
490
|
const tmpFile = path6.join(tmpDir, `.ssg-entry-${ts}.mjs`);
|
|
498
|
-
|
|
491
|
+
log4.debug("bundling %s via esbuild", entry.kebabName);
|
|
499
492
|
const startBundled = Date.now();
|
|
500
493
|
await esbuild.build({
|
|
501
494
|
stdin: {
|
|
@@ -519,6 +512,7 @@ async function bundleEntry(entry, projectRoot, sourceDir) {
|
|
|
519
512
|
allowOverwrite: true,
|
|
520
513
|
plugins: [
|
|
521
514
|
{
|
|
515
|
+
// Re-apply hydration fix to all TSX/JSX files loaded during bundle
|
|
522
516
|
name: "ssg-hydration-fix",
|
|
523
517
|
setup(build) {
|
|
524
518
|
build.onLoad({ filter: /\.(tsx|jsx)$/ }, (args) => {
|
|
@@ -529,13 +523,14 @@ async function bundleEntry(entry, projectRoot, sourceDir) {
|
|
|
529
523
|
return { contents: result, loader: args.path.endsWith(".tsx") ? "tsx" : "jsx" };
|
|
530
524
|
}
|
|
531
525
|
} catch (e) {
|
|
532
|
-
|
|
526
|
+
log4.debug("SSG hydration-fix failed for %s: %s", args.path, e);
|
|
533
527
|
}
|
|
534
528
|
return void 0;
|
|
535
529
|
});
|
|
536
530
|
}
|
|
537
531
|
},
|
|
538
532
|
{
|
|
533
|
+
// Strip CSS imports — not needed for SSR, replaced by Liquid stylesheet
|
|
539
534
|
name: "ssg-strip-css",
|
|
540
535
|
setup(build) {
|
|
541
536
|
build.onResolve({ filter: /\.module\.css$/ }, (args) => ({
|
|
@@ -558,7 +553,7 @@ async function bundleEntry(entry, projectRoot, sourceDir) {
|
|
|
558
553
|
}
|
|
559
554
|
]
|
|
560
555
|
});
|
|
561
|
-
|
|
556
|
+
log4.debug("esbuild bundle took %dms", Date.now() - startBundled);
|
|
562
557
|
return { tmpFile };
|
|
563
558
|
}
|
|
564
559
|
|
|
@@ -589,7 +584,7 @@ function pathToFileURL(filePath) {
|
|
|
589
584
|
}
|
|
590
585
|
return "file://" + absPath;
|
|
591
586
|
}
|
|
592
|
-
var
|
|
587
|
+
var log5 = logger("ssg:renderer");
|
|
593
588
|
var DEFAULT_LIQUID_FILTERS = {
|
|
594
589
|
textarea: " | newline_to_br",
|
|
595
590
|
image_picker: " | img_url: 'master'"
|
|
@@ -610,7 +605,7 @@ function renderEntry(tmpFile, entry, projectRoot) {
|
|
|
610
605
|
const Component = mod.default;
|
|
611
606
|
const shopifyMeta = mod.shopifyMeta;
|
|
612
607
|
if (!Component) {
|
|
613
|
-
|
|
608
|
+
log5.warn("No default export found in %s, skipping", entry.filePath);
|
|
614
609
|
return null;
|
|
615
610
|
}
|
|
616
611
|
if (shopifyMeta) {
|
|
@@ -623,7 +618,7 @@ function renderEntry(tmpFile, entry, projectRoot) {
|
|
|
623
618
|
createElement = projectRequire("react").createElement;
|
|
624
619
|
renderToStaticMarkup = projectRequire("react-dom/server").renderToStaticMarkup;
|
|
625
620
|
} catch {
|
|
626
|
-
|
|
621
|
+
log5.warn("react/react-dom not found, skipping SSR for %s", entry.kebabName);
|
|
627
622
|
return null;
|
|
628
623
|
}
|
|
629
624
|
globalThis.__shopify_ssg_target = entry.targetType;
|
|
@@ -655,71 +650,76 @@ function resolveScriptAsset(kebabName, manifest) {
|
|
|
655
650
|
}
|
|
656
651
|
|
|
657
652
|
// src/ssg/schema.ts
|
|
658
|
-
function serializeSetting(setting) {
|
|
659
|
-
const s = { type: setting.type };
|
|
660
|
-
if ("id" in setting) s.id = setting.id;
|
|
661
|
-
if ("label" in setting) s.label = setting.label;
|
|
662
|
-
if ("default" in setting && setting.default !== void 0) {
|
|
663
|
-
s.default = setting.default;
|
|
664
|
-
}
|
|
665
|
-
if ("info" in setting && setting.info) s.info = setting.info;
|
|
666
|
-
if ("placeholder" in setting && setting.placeholder) {
|
|
667
|
-
s.placeholder = setting.placeholder;
|
|
668
|
-
}
|
|
669
|
-
if ("options" in setting && setting.options) s.options = setting.options;
|
|
670
|
-
if ("min" in setting && setting.min !== void 0) s.min = setting.min;
|
|
671
|
-
if ("max" in setting && setting.max !== void 0) s.max = setting.max;
|
|
672
|
-
if ("step" in setting && setting.step !== void 0) s.step = setting.step;
|
|
673
|
-
if ("unit" in setting && setting.unit) s.unit = setting.unit;
|
|
674
|
-
if ("accept" in setting && setting.accept) s.accept = setting.accept;
|
|
675
|
-
if ("metaobject_type" in setting && setting.metaobject_type) {
|
|
676
|
-
s.metaobject_type = setting.metaobject_type;
|
|
677
|
-
}
|
|
678
|
-
if ("limit" in setting && setting.limit !== void 0) s.limit = setting.limit;
|
|
679
|
-
if ("content" in setting && setting.content) s.content = setting.content;
|
|
680
|
-
if ("definition" in setting && setting.definition) {
|
|
681
|
-
s.definition = setting.definition.map(serializeSetting);
|
|
682
|
-
}
|
|
683
|
-
if ("role" in setting && setting.role) s.role = setting.role;
|
|
684
|
-
return s;
|
|
685
|
-
}
|
|
686
653
|
function generateSchema(meta) {
|
|
687
|
-
const schema =
|
|
688
|
-
name: meta.name
|
|
689
|
-
};
|
|
690
|
-
if (meta.tag) schema.tag = meta.tag;
|
|
691
|
-
if (meta.class) schema.class = meta.class;
|
|
692
|
-
if (meta.limit !== void 0) schema.limit = meta.limit;
|
|
693
|
-
if (meta.max_blocks !== void 0) schema.max_blocks = meta.max_blocks;
|
|
694
|
-
if (meta.settings && meta.settings.length > 0) {
|
|
695
|
-
schema.settings = meta.settings.map(serializeSetting);
|
|
696
|
-
}
|
|
697
|
-
if (meta.blocks && meta.blocks.length > 0) {
|
|
698
|
-
schema.blocks = meta.blocks.map((block) => {
|
|
699
|
-
const b = { type: block.type };
|
|
700
|
-
if (block.name) b.name = block.name;
|
|
701
|
-
if (block.settings) b.settings = block.settings;
|
|
702
|
-
return b;
|
|
703
|
-
});
|
|
704
|
-
}
|
|
705
|
-
if (meta.presets && meta.presets.length > 0) {
|
|
706
|
-
schema.presets = meta.presets.map((preset) => {
|
|
707
|
-
const p = { name: preset.name };
|
|
708
|
-
if (preset.category) p.category = preset.category;
|
|
709
|
-
if (preset.settings) p.settings = preset.settings;
|
|
710
|
-
if (preset.blocks) p.blocks = preset.blocks;
|
|
711
|
-
return p;
|
|
712
|
-
});
|
|
713
|
-
}
|
|
714
|
-
if (meta.enabled_on) schema.enabled_on = meta.enabled_on;
|
|
715
|
-
if (meta.disabled_on) schema.disabled_on = meta.disabled_on;
|
|
716
|
-
if (meta.templates) schema.templates = meta.templates;
|
|
654
|
+
const schema = buildSchema(meta);
|
|
717
655
|
const json = JSON.stringify(schema, null, 2);
|
|
718
656
|
return `
|
|
719
657
|
{% schema %}
|
|
720
658
|
${json}
|
|
721
|
-
{% endschema %}
|
|
722
|
-
|
|
659
|
+
{% endschema %}`;
|
|
660
|
+
}
|
|
661
|
+
function buildSchema(meta) {
|
|
662
|
+
return {
|
|
663
|
+
name: meta.name ?? "",
|
|
664
|
+
...meta.tag !== void 0 ? { tag: meta.tag } : {},
|
|
665
|
+
class: meta.class ?? "",
|
|
666
|
+
limit: meta.limit,
|
|
667
|
+
...meta.max_blocks != null ? { max_blocks: meta.max_blocks } : {},
|
|
668
|
+
settings: meta.settings || [],
|
|
669
|
+
blocks: meta.blocks ? meta.blocks.map((b) => serializeBlockDefinition(b)) : void 0,
|
|
670
|
+
presets: meta.presets ? meta.presets.map((p) => serializePreset(p)) : void 0,
|
|
671
|
+
...meta.default ? { default: serializePreset(meta.default) } : {},
|
|
672
|
+
...meta.locales ? { locales: meta.locales } : {},
|
|
673
|
+
...meta.enabled_on ? { enabled_on: meta.enabled_on } : {},
|
|
674
|
+
...meta.disabled_on ? { disabled_on: meta.disabled_on } : {}
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
function defaultBlockName(type) {
|
|
678
|
+
const name = type.replace(/^@/, "").split(/[-_\s]+/).filter(Boolean).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
679
|
+
return name.length > MAX_NAME_LENGTH ? name.slice(0, MAX_NAME_LENGTH) : name;
|
|
680
|
+
}
|
|
681
|
+
function serializeBlockDefinition(block) {
|
|
682
|
+
const { type, name, limit, settings } = block;
|
|
683
|
+
return {
|
|
684
|
+
type,
|
|
685
|
+
name: name ?? defaultBlockName(type),
|
|
686
|
+
...limit != null ? { limit } : {},
|
|
687
|
+
...settings ? { settings } : {}
|
|
688
|
+
};
|
|
689
|
+
}
|
|
690
|
+
function serializeInputSettings(settings) {
|
|
691
|
+
if (!settings) return void 0;
|
|
692
|
+
const out = {};
|
|
693
|
+
for (const [key, value] of Object.entries(settings)) {
|
|
694
|
+
out[key] = value === "" ? void 0 : value;
|
|
695
|
+
}
|
|
696
|
+
return out;
|
|
697
|
+
}
|
|
698
|
+
function serializePresetBlock(block) {
|
|
699
|
+
const p = { type: block.type };
|
|
700
|
+
if (block.name) p.name = block.name;
|
|
701
|
+
if (block.static) {
|
|
702
|
+
p.static = true;
|
|
703
|
+
if (block.id) p.id = block.id;
|
|
704
|
+
}
|
|
705
|
+
const settings = serializeInputSettings(
|
|
706
|
+
"settings" in block ? block.settings : void 0
|
|
707
|
+
);
|
|
708
|
+
if (settings) p.settings = settings;
|
|
709
|
+
if (!block.static && block.blocks?.length) {
|
|
710
|
+
p.blocks = block.blocks.map(serializePresetBlock);
|
|
711
|
+
}
|
|
712
|
+
return p;
|
|
713
|
+
}
|
|
714
|
+
function serializePreset(preset) {
|
|
715
|
+
const obj = { name: preset.name };
|
|
716
|
+
if (preset.category) obj.category = preset.category;
|
|
717
|
+
const settings = serializeInputSettings(preset.settings);
|
|
718
|
+
if (settings) obj.settings = settings;
|
|
719
|
+
if (preset.blocks?.length) {
|
|
720
|
+
obj.blocks = preset.blocks.map(serializePresetBlock);
|
|
721
|
+
}
|
|
722
|
+
return obj;
|
|
723
723
|
}
|
|
724
724
|
|
|
725
725
|
// src/ssg/liquid-paths.ts
|
|
@@ -878,24 +878,8 @@ function buildSnippet(html, entry, trackedExpressions, liquidPrepend = "") {
|
|
|
878
878
|
return lines;
|
|
879
879
|
}
|
|
880
880
|
|
|
881
|
-
// src/validate/rules.ts
|
|
882
|
-
var MAX_NAME_LENGTH = 25;
|
|
883
|
-
function checkNameLength(meta, kebabName) {
|
|
884
|
-
if (meta.name.length > MAX_NAME_LENGTH) {
|
|
885
|
-
return `[${kebabName}] shopifyMeta.name "${meta.name}" is ${meta.name.length} chars (Shopify limit: ${MAX_NAME_LENGTH})`;
|
|
886
|
-
}
|
|
887
|
-
return null;
|
|
888
|
-
}
|
|
889
|
-
function checkEmptyStringDefault(setting) {
|
|
890
|
-
if (setting.default === "") {
|
|
891
|
-
const label = "id" in setting && setting.id ? setting.id : "(no id)";
|
|
892
|
-
return `Setting "${label}" (type: ${setting.type}) has empty string default`;
|
|
893
|
-
}
|
|
894
|
-
return null;
|
|
895
|
-
}
|
|
896
|
-
|
|
897
881
|
// src/validate/index.ts
|
|
898
|
-
var
|
|
882
|
+
var log6 = logger("validate");
|
|
899
883
|
function validateShopifyMeta(meta, context) {
|
|
900
884
|
const warnings = [];
|
|
901
885
|
const nameWarning = checkNameLength(meta, context.kebabName);
|
|
@@ -910,17 +894,17 @@ function validateShopifyMeta(meta, context) {
|
|
|
910
894
|
}
|
|
911
895
|
}
|
|
912
896
|
for (const w of warnings) {
|
|
913
|
-
|
|
897
|
+
log6.warn(w);
|
|
914
898
|
}
|
|
915
899
|
return warnings;
|
|
916
900
|
}
|
|
917
901
|
|
|
918
902
|
// src/ssg/compiler.ts
|
|
919
|
-
var
|
|
903
|
+
var log7 = logger("ssg:compiler");
|
|
920
904
|
async function compileAllEntries(options, manifest) {
|
|
921
905
|
const entries = scanEntries(options);
|
|
922
906
|
if (entries.length === 0) return;
|
|
923
|
-
|
|
907
|
+
log7.debug("found %d entries to compile", entries.length);
|
|
924
908
|
const projectRoot = path9.resolve(options.themeRoot);
|
|
925
909
|
const sourceDir = path9.resolve(options.themeRoot, options.sourceCodeDir);
|
|
926
910
|
const { entryCssFiles, cssRefCount } = analyzeCssDistribution(entries, manifest);
|
|
@@ -929,10 +913,10 @@ async function compileAllEntries(options, manifest) {
|
|
|
929
913
|
try {
|
|
930
914
|
await compileEntry(entry, options, manifest, projectRoot, sourceDir, entryCssFiles, cssSnippetMap);
|
|
931
915
|
} catch (err) {
|
|
932
|
-
|
|
916
|
+
log7.error("Failed to compile %s:", entry.filePath, err);
|
|
933
917
|
}
|
|
934
918
|
}
|
|
935
|
-
|
|
919
|
+
log7.info("Compiled %d entries", entries.length);
|
|
936
920
|
const tmpDir = path9.join(sourceDir, ".ssg-tmp");
|
|
937
921
|
try {
|
|
938
922
|
fs3.rmSync(tmpDir, { recursive: true, force: true });
|
|
@@ -950,7 +934,7 @@ async function compileEntry(entry, options, manifest, projectRoot, sourceDir, en
|
|
|
950
934
|
const cssFiles = entryCssFiles.get(entry.kebabName) || [];
|
|
951
935
|
const { inline: cssInlineFiles, snippets: cssSnippets } = categorizeCss(cssFiles, cssSnippetMap);
|
|
952
936
|
const cssInline = readCssFileContents(cssInlineFiles, options.buildDir, options.themeRoot);
|
|
953
|
-
|
|
937
|
+
log7.debug(
|
|
954
938
|
"compiling %s (type=%s, css inline=%d, css snippets=%d)",
|
|
955
939
|
entry.kebabName,
|
|
956
940
|
entry.targetType,
|
|
@@ -985,7 +969,7 @@ async function compileEntry(entry, options, manifest, projectRoot, sourceDir, en
|
|
|
985
969
|
}
|
|
986
970
|
|
|
987
971
|
// src/ssg/index.ts
|
|
988
|
-
var
|
|
972
|
+
var log8 = logger("ssg");
|
|
989
973
|
function shopifySSG(options) {
|
|
990
974
|
return {
|
|
991
975
|
name: "vite-plugin-shopify:ssg",
|
|
@@ -1001,16 +985,16 @@ function shopifySSG(options) {
|
|
|
1001
985
|
"manifest.json"
|
|
1002
986
|
);
|
|
1003
987
|
if (!fs4.existsSync(manifestPath)) {
|
|
1004
|
-
|
|
988
|
+
log8.warn("No manifest.json found, skipping SSG");
|
|
1005
989
|
return;
|
|
1006
990
|
}
|
|
1007
|
-
|
|
991
|
+
log8.debug("reading manifest from %s", manifestPath);
|
|
1008
992
|
const manifest = JSON.parse(fs4.readFileSync(manifestPath, "utf-8"));
|
|
1009
|
-
|
|
993
|
+
log8.info("Starting SSG compilation...");
|
|
1010
994
|
await compileAllEntries(options, manifest);
|
|
1011
|
-
|
|
995
|
+
log8.info("SSG compilation complete");
|
|
1012
996
|
writeImportMapSnippet(options);
|
|
1013
|
-
|
|
997
|
+
log8.debug("wrote import map snippet");
|
|
1014
998
|
},
|
|
1015
999
|
resolveId(id) {
|
|
1016
1000
|
if (id === "vite-plugin-shopify/runtime") {
|
package/dist/runtime/index.d.ts
CHANGED
|
@@ -1,35 +1,104 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
|
|
3
|
+
/**
|
|
4
|
+
* Parses a Liquid value into a boolean following Shopify truthiness rules:
|
|
5
|
+
* `false`, `""`, `"0"`, `undefined`, and `null` are falsy; everything else is
|
|
6
|
+
* truthy.
|
|
7
|
+
*/
|
|
3
8
|
declare function parseLiquidBoolean(value: string | boolean | undefined | null): boolean;
|
|
9
|
+
/**
|
|
10
|
+
* Parses a Liquid value into a number, falling back to `defaultVal` (default 0)
|
|
11
|
+
* when the value is `undefined`, `null`, or unparseable.
|
|
12
|
+
*/
|
|
4
13
|
declare function parseLiquidNumber(value: string | number | undefined | null, defaultVal?: number): number;
|
|
14
|
+
/** Type mode hint for `useLiquidValue` — controls how the raw string is coerced. */
|
|
5
15
|
type LiquidTypeMode = "string" | "number" | "boolean";
|
|
6
16
|
type Setter<T> = (val: T | ((prev: T) => T)) => void;
|
|
7
17
|
type ValueForMode<M extends LiquidTypeMode | undefined> = M extends "number" ? number : M extends "boolean" ? boolean : string | undefined;
|
|
18
|
+
/**
|
|
19
|
+
* Reads a single Liquid setting and returns a React state tuple.
|
|
20
|
+
*
|
|
21
|
+
* Overloads allow compile-time type narrowing based on the `type` argument:
|
|
22
|
+
* - No `type` or `"string"` → `[string | undefined, Setter]`
|
|
23
|
+
* - `"number"` → `[number, Setter]`
|
|
24
|
+
* - `"boolean"` → `[boolean, Setter]`
|
|
25
|
+
*
|
|
26
|
+
* @param expr - Liquid expression to read (e.g. `"section.settings.title"`).
|
|
27
|
+
* @param type - Type coercion mode: `"string"` (default), `"number"`, or `"boolean"`.
|
|
28
|
+
*/
|
|
8
29
|
declare function useLiquidValue(expr: string): [string | undefined, Setter<string | undefined>];
|
|
9
30
|
declare function useLiquidValue(expr: string, type: "string"): [string | undefined, Setter<string | undefined>];
|
|
10
31
|
declare function useLiquidValue(expr: string, type: "number"): [number, Setter<number>];
|
|
11
32
|
declare function useLiquidValue(expr: string, type: "boolean"): [boolean, Setter<boolean>];
|
|
33
|
+
/** Maps individual keys in a Liquid expression map to a type mode (string/number/boolean). */
|
|
12
34
|
type TypeModes<T extends Record<string, string>> = Partial<{
|
|
13
35
|
[K in keyof T & string]: LiquidTypeMode;
|
|
14
36
|
}>;
|
|
37
|
+
/** Infers the resolved value types for each key in a Liquid expression map. */
|
|
15
38
|
type InferValues<T extends Record<string, string>, Types extends TypeModes<T>> = {
|
|
16
39
|
[K in keyof T & string]: ValueForMode<Types[K]>;
|
|
17
40
|
};
|
|
41
|
+
/**
|
|
42
|
+
* Reads multiple Liquid settings simultaneously and returns a single state object.
|
|
43
|
+
*
|
|
44
|
+
* Like `useLiquidValue` but batched — takes a name-to-expression mapping and an
|
|
45
|
+
* optional name-to-type-mode mapping. Returns a plain object with the resolved
|
|
46
|
+
* (and type-coerced) values.
|
|
47
|
+
*
|
|
48
|
+
* @param map - Object mapping keys to Liquid expression strings.
|
|
49
|
+
* @param types - Optional per-key type mode overrides (default: `"string"`).
|
|
50
|
+
*/
|
|
18
51
|
declare function useLiquidValues<T extends Record<string, string>, const Types extends TypeModes<T> = {}>(map: T, types?: Types): InferValues<T, Types>;
|
|
52
|
+
/**
|
|
53
|
+
* Reads a section-level setting value.
|
|
54
|
+
*
|
|
55
|
+
* Equivalent to `useLiquidRaw(\`section.settings.${key}\`)`.
|
|
56
|
+
*/
|
|
19
57
|
declare function useSectionSettings(key: string): {
|
|
20
58
|
value: string | undefined;
|
|
21
59
|
};
|
|
60
|
+
/**
|
|
61
|
+
* Reads a block-level setting value.
|
|
62
|
+
*
|
|
63
|
+
* Equivalent to `useLiquidRaw(\`block.settings.${key}\`)`.
|
|
64
|
+
*/
|
|
22
65
|
declare function useBlockSettings(key: string): {
|
|
23
66
|
value: string | undefined;
|
|
24
67
|
};
|
|
68
|
+
/**
|
|
69
|
+
* Reads a snippet parameter by name.
|
|
70
|
+
*/
|
|
25
71
|
declare function useSnippetParams(key: string): {
|
|
26
72
|
value: string | undefined;
|
|
27
73
|
};
|
|
74
|
+
/**
|
|
75
|
+
* Reads a block parameter by name.
|
|
76
|
+
*/
|
|
28
77
|
declare function useBlockParams(key: string): {
|
|
29
78
|
value: string | undefined;
|
|
30
79
|
};
|
|
80
|
+
/**
|
|
81
|
+
* Registers a block of raw Liquid code for injection into the generated
|
|
82
|
+
* `.liquid` file. During SSR the code (and any `{{ expr }}` references it
|
|
83
|
+
* contains) is accumulated in global registries; on the client it is a no-op.
|
|
84
|
+
*
|
|
85
|
+
* The registered Liquid code is injected **before** the JSON bridge in the
|
|
86
|
+
* assembled output so that any variables it defines are available for the
|
|
87
|
+
* bridge's `json` filter.
|
|
88
|
+
*
|
|
89
|
+
* @param code - Raw Liquid template code (may include `{{ }}` expressions).
|
|
90
|
+
* @returns Always returns an empty string — the Liquid code is rendered
|
|
91
|
+
* server-side only.
|
|
92
|
+
*/
|
|
31
93
|
declare function useLiquidBlock(code: string): string;
|
|
32
94
|
|
|
95
|
+
/**
|
|
96
|
+
* @file React context provider for Liquid data consumed by runtime hooks.
|
|
97
|
+
*
|
|
98
|
+
* The `LiquidDataContext` carries server-rendered Liquid values (from the JSON
|
|
99
|
+
* bridge) down to {@link useLiquidValue} and related hooks during client-side
|
|
100
|
+
* hydration. Without this provider, hooks return `undefined` on the client.
|
|
101
|
+
*/
|
|
33
102
|
declare const LiquidDataContext: react.Context<Record<string, any>>;
|
|
34
103
|
declare const LiquidDataProvider: react.Provider<Record<string, any>>;
|
|
35
104
|
|