vite-plugin-react-shopify 2.1.0 → 2.2.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 +87 -23
- package/dist/runtime/index.d.ts +16 -13
- package/dist/runtime/index.js +79 -21
- package/package.json +4 -2
package/dist/index.js
CHANGED
|
@@ -394,32 +394,75 @@ import fs2 from "fs";
|
|
|
394
394
|
import path6 from "path";
|
|
395
395
|
import { createRequire } from "module";
|
|
396
396
|
|
|
397
|
-
// src/
|
|
397
|
+
// src/hydration-fix/index.ts
|
|
398
|
+
import { parseSync } from "oxc-parser";
|
|
399
|
+
import { walk } from "oxc-walker";
|
|
398
400
|
var log4 = logger("hydration-fix");
|
|
399
401
|
function autoFixAdjacentText(source, filePath) {
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
402
|
+
const parseResult = parseSync(filePath, source);
|
|
403
|
+
if (parseResult.errors.length > 0) {
|
|
404
|
+
log4.debug("OXC parse errors for %s, skipping hydration fix", filePath);
|
|
405
|
+
return { result: source, fixCount: 0 };
|
|
406
|
+
}
|
|
407
|
+
const replacements = [];
|
|
408
|
+
walk(parseResult.program, {
|
|
409
|
+
enter(node) {
|
|
410
|
+
if (node.type === "JSXElement" || node.type === "JSXFragment") {
|
|
411
|
+
const children = node.children;
|
|
412
|
+
if (children.length > 0) {
|
|
413
|
+
processChildren(children, source, replacements);
|
|
414
|
+
}
|
|
413
415
|
}
|
|
414
|
-
|
|
415
|
-
|
|
416
|
+
}
|
|
417
|
+
});
|
|
418
|
+
if (replacements.length === 0) {
|
|
419
|
+
return { result: source, fixCount: 0 };
|
|
416
420
|
}
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
);
|
|
421
|
+
replacements.sort((a, b) => b.start - a.start);
|
|
422
|
+
let fixed = source;
|
|
423
|
+
for (const { start, end, replacement } of replacements) {
|
|
424
|
+
fixed = fixed.slice(0, start) + replacement + fixed.slice(end);
|
|
425
|
+
}
|
|
426
|
+
log4.warn(
|
|
427
|
+
`auto-fixed ${replacements.length} adjacent text+expression issue(s) in ${filePath}`
|
|
428
|
+
);
|
|
429
|
+
return { result: fixed, fixCount: replacements.length };
|
|
430
|
+
}
|
|
431
|
+
function processChildren(children, source, replacements) {
|
|
432
|
+
let i = 0;
|
|
433
|
+
while (i < children.length) {
|
|
434
|
+
if (children[i].type !== "JSXText" && children[i].type !== "JSXExpressionContainer") {
|
|
435
|
+
i++;
|
|
436
|
+
continue;
|
|
437
|
+
}
|
|
438
|
+
let runEnd = i;
|
|
439
|
+
let hasText = children[i].type === "JSXText";
|
|
440
|
+
let hasExpr = children[i].type === "JSXExpressionContainer";
|
|
441
|
+
while (runEnd + 1 < children.length && (children[runEnd + 1].type === "JSXText" || children[runEnd + 1].type === "JSXExpressionContainer")) {
|
|
442
|
+
runEnd++;
|
|
443
|
+
if (children[runEnd].type === "JSXText") hasText = true;
|
|
444
|
+
if (children[runEnd].type === "JSXExpressionContainer") hasExpr = true;
|
|
445
|
+
}
|
|
446
|
+
if (!hasText || !hasExpr) {
|
|
447
|
+
i = runEnd + 1;
|
|
448
|
+
continue;
|
|
449
|
+
}
|
|
450
|
+
const sliceStart = children[i].start;
|
|
451
|
+
const sliceEnd = children[runEnd].end;
|
|
452
|
+
const runText = source.slice(sliceStart, sliceEnd);
|
|
453
|
+
const trimmed = runText.trim();
|
|
454
|
+
if (!needsFix(trimmed)) {
|
|
455
|
+
i = runEnd + 1;
|
|
456
|
+
continue;
|
|
457
|
+
}
|
|
458
|
+
const tpl = trimmed.replace(/\{([^}]+)\}/g, "${$1}");
|
|
459
|
+
replacements.push({
|
|
460
|
+
start: sliceStart,
|
|
461
|
+
end: sliceEnd,
|
|
462
|
+
replacement: `{\`${tpl}\`}`
|
|
463
|
+
});
|
|
464
|
+
i = runEnd + 1;
|
|
421
465
|
}
|
|
422
|
-
return { result: fixed.join("\n"), fixCount };
|
|
423
466
|
}
|
|
424
467
|
function needsFix(content) {
|
|
425
468
|
const trimmed = content.trim();
|
|
@@ -485,7 +528,8 @@ async function bundleEntry(entry, projectRoot, sourceDir) {
|
|
|
485
528
|
if (fixCount2 > 0) {
|
|
486
529
|
return { contents: result, loader: args.path.endsWith(".tsx") ? "tsx" : "jsx" };
|
|
487
530
|
}
|
|
488
|
-
} catch {
|
|
531
|
+
} catch (e) {
|
|
532
|
+
log5.debug("SSG hydration-fix failed for %s: %s", args.path, e);
|
|
489
533
|
}
|
|
490
534
|
return void 0;
|
|
491
535
|
});
|
|
@@ -948,7 +992,7 @@ function shopifySSG(options) {
|
|
|
948
992
|
if (id === "\0vite-plugin-shopify:runtime") {
|
|
949
993
|
const exports = [
|
|
950
994
|
`export { LiquidDataProvider, LiquidDataContext } from 'vite-plugin-shopify/runtime'`,
|
|
951
|
-
`export {
|
|
995
|
+
`export { useLiquidValue, useLiquidValues, useSectionSettings, useBlockSettings, useSnippetParams, useBlockParams } from 'vite-plugin-shopify/runtime'`
|
|
952
996
|
];
|
|
953
997
|
return exports.join("\n");
|
|
954
998
|
}
|
|
@@ -980,6 +1024,25 @@ function writeImportMapSnippet(options) {
|
|
|
980
1024
|
fs4.writeFileSync(snippetPath, content);
|
|
981
1025
|
}
|
|
982
1026
|
|
|
1027
|
+
// src/hydration-fix/vite-plugin.ts
|
|
1028
|
+
import path11 from "path";
|
|
1029
|
+
function hydrationFix(options) {
|
|
1030
|
+
const sourceDir = path11.resolve(options.themeRoot, options.sourceCodeDir);
|
|
1031
|
+
return {
|
|
1032
|
+
name: "vite-plugin-shopify:hydration-fix",
|
|
1033
|
+
enforce: "pre",
|
|
1034
|
+
transform(code, id) {
|
|
1035
|
+
if (!/\.(tsx|jsx)$/.test(id)) return;
|
|
1036
|
+
if (!id.startsWith(sourceDir)) return;
|
|
1037
|
+
const { result, fixCount } = autoFixAdjacentText(code, id);
|
|
1038
|
+
if (fixCount > 0) {
|
|
1039
|
+
return result;
|
|
1040
|
+
}
|
|
1041
|
+
return;
|
|
1042
|
+
}
|
|
1043
|
+
};
|
|
1044
|
+
}
|
|
1045
|
+
|
|
983
1046
|
// src/index.ts
|
|
984
1047
|
var vitePluginShopify = (options = {}) => {
|
|
985
1048
|
const resolvedOptions = resolveOptions(options);
|
|
@@ -987,6 +1050,7 @@ var vitePluginShopify = (options = {}) => {
|
|
|
987
1050
|
enableDebug();
|
|
988
1051
|
}
|
|
989
1052
|
return [
|
|
1053
|
+
hydrationFix(resolvedOptions),
|
|
990
1054
|
shopifyConfig(resolvedOptions),
|
|
991
1055
|
shopifyEntries(resolvedOptions),
|
|
992
1056
|
shopifySSG(resolvedOptions)
|
package/dist/runtime/index.d.ts
CHANGED
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
|
|
3
|
-
declare function
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
declare function parseLiquidBoolean(value: string | boolean | undefined | null): boolean;
|
|
4
|
+
declare function parseLiquidNumber(value: string | number | undefined | null, defaultVal?: number): number;
|
|
5
|
+
type LiquidTypeMode = "string" | "number" | "boolean";
|
|
6
|
+
type Setter<T> = (val: T | ((prev: T) => T)) => void;
|
|
7
|
+
type ValueForMode<M extends LiquidTypeMode | undefined> = M extends "number" ? number : M extends "boolean" ? boolean : string | undefined;
|
|
8
|
+
declare function useLiquidValue(expr: string): [string | undefined, Setter<string | undefined>];
|
|
9
|
+
declare function useLiquidValue(expr: string, type: "string"): [string | undefined, Setter<string | undefined>];
|
|
10
|
+
declare function useLiquidValue(expr: string, type: "number"): [number, Setter<number>];
|
|
11
|
+
declare function useLiquidValue(expr: string, type: "boolean"): [boolean, Setter<boolean>];
|
|
12
|
+
type TypeModes<T extends Record<string, string>> = Partial<{
|
|
13
|
+
[K in keyof T & string]: LiquidTypeMode;
|
|
14
|
+
}>;
|
|
15
|
+
type InferValues<T extends Record<string, string>, Types extends TypeModes<T>> = {
|
|
16
|
+
[K in keyof T & string]: ValueForMode<Types[K]>;
|
|
10
17
|
};
|
|
18
|
+
declare function useLiquidValues<T extends Record<string, string>, const Types extends TypeModes<T> = {}>(map: T, types?: Types): InferValues<T, Types>;
|
|
11
19
|
declare function useSectionSettings(key: string): {
|
|
12
20
|
value: string | undefined;
|
|
13
21
|
};
|
|
@@ -21,12 +29,7 @@ declare function useBlockParams(key: string): {
|
|
|
21
29
|
value: string | undefined;
|
|
22
30
|
};
|
|
23
31
|
|
|
24
|
-
/** SSR-safe boolean parser: treats Liquid expression strings as truthy, real booleans as-is */
|
|
25
|
-
declare function parseLiquidBoolean(value: string | boolean | undefined | null): boolean;
|
|
26
|
-
/** SSR-safe number parser: returns defaultVal for unparseable SSR placeholders */
|
|
27
|
-
declare function parseLiquidNumber(value: string | number | undefined | null, defaultVal?: number): number;
|
|
28
|
-
|
|
29
32
|
declare const LiquidDataContext: react.Context<Record<string, any>>;
|
|
30
33
|
declare const LiquidDataProvider: react.Provider<Record<string, any>>;
|
|
31
34
|
|
|
32
|
-
export { LiquidDataContext, LiquidDataProvider, parseLiquidBoolean, parseLiquidNumber, useBlockParams, useBlockSettings,
|
|
35
|
+
export { LiquidDataContext, LiquidDataProvider, type LiquidTypeMode, parseLiquidBoolean, parseLiquidNumber, useBlockParams, useBlockSettings, useLiquidValue, useLiquidValues, useSectionSettings, useSnippetParams };
|
package/dist/runtime/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// src/runtime/hooks.ts
|
|
2
|
-
import { useContext } from "react";
|
|
2
|
+
import { useContext, useEffect, useState } from "react";
|
|
3
3
|
|
|
4
4
|
// src/runtime/provider.ts
|
|
5
5
|
import { createContext } from "react";
|
|
@@ -7,19 +7,19 @@ var LiquidDataContext = createContext({});
|
|
|
7
7
|
var LiquidDataProvider = LiquidDataContext.Provider;
|
|
8
8
|
|
|
9
9
|
// src/runtime/hooks.ts
|
|
10
|
-
function
|
|
10
|
+
function useLiquidRaw(expr) {
|
|
11
11
|
const data = useContext(LiquidDataContext);
|
|
12
12
|
if (typeof globalThis.document === "undefined") {
|
|
13
13
|
const tracker = globalThis.__shopify_ssg_liquid_track;
|
|
14
14
|
if (tracker) tracker.add(expr);
|
|
15
|
-
return
|
|
15
|
+
return `{{ ${expr} }}`;
|
|
16
16
|
}
|
|
17
17
|
if (Object.prototype.hasOwnProperty.call(data, expr)) {
|
|
18
|
-
return
|
|
18
|
+
return data[expr];
|
|
19
19
|
}
|
|
20
|
-
return
|
|
20
|
+
return void 0;
|
|
21
21
|
}
|
|
22
|
-
function
|
|
22
|
+
function useLiquidRawValues(map) {
|
|
23
23
|
const data = useContext(LiquidDataContext);
|
|
24
24
|
if (typeof globalThis.document === "undefined") {
|
|
25
25
|
const tracker = globalThis.__shopify_ssg_liquid_track;
|
|
@@ -28,25 +28,13 @@ function useLiquidValues(map) {
|
|
|
28
28
|
if (tracker) tracker.add(expr);
|
|
29
29
|
values2[key] = `{{ ${expr} }}`;
|
|
30
30
|
}
|
|
31
|
-
return
|
|
31
|
+
return values2;
|
|
32
32
|
}
|
|
33
33
|
const values = {};
|
|
34
34
|
for (const [key, expr] of Object.entries(map)) {
|
|
35
35
|
values[key] = Object.prototype.hasOwnProperty.call(data, expr) ? data[expr] : void 0;
|
|
36
36
|
}
|
|
37
|
-
return
|
|
38
|
-
}
|
|
39
|
-
function useSectionSettings(key) {
|
|
40
|
-
return useLiquid(`section.settings.${key}`);
|
|
41
|
-
}
|
|
42
|
-
function useBlockSettings(key) {
|
|
43
|
-
return useLiquid(`block.settings.${key}`);
|
|
44
|
-
}
|
|
45
|
-
function useSnippetParams(key) {
|
|
46
|
-
return useLiquid(key);
|
|
47
|
-
}
|
|
48
|
-
function useBlockParams(key) {
|
|
49
|
-
return useLiquid(key);
|
|
37
|
+
return values;
|
|
50
38
|
}
|
|
51
39
|
function parseLiquidBoolean(value) {
|
|
52
40
|
if (typeof value === "boolean") return value;
|
|
@@ -59,6 +47,76 @@ function parseLiquidNumber(value, defaultVal = 0) {
|
|
|
59
47
|
const num = Number(value);
|
|
60
48
|
return Number.isNaN(num) ? defaultVal : num;
|
|
61
49
|
}
|
|
50
|
+
function useLiquidValue(expr, type = "string") {
|
|
51
|
+
const raw = useLiquidRaw(expr);
|
|
52
|
+
const isSSR = typeof globalThis.document === "undefined";
|
|
53
|
+
let initialVal;
|
|
54
|
+
if (type === "boolean") {
|
|
55
|
+
initialVal = false;
|
|
56
|
+
} else if (type === "number") {
|
|
57
|
+
initialVal = isSSR ? raw : parseLiquidNumber(raw, 0);
|
|
58
|
+
} else {
|
|
59
|
+
initialVal = raw;
|
|
60
|
+
}
|
|
61
|
+
const [val, setVal] = useState(initialVal);
|
|
62
|
+
useEffect(() => {
|
|
63
|
+
if (type === "number") setVal(parseLiquidNumber(raw, 0));
|
|
64
|
+
else if (type === "boolean") setVal(parseLiquidBoolean(raw));
|
|
65
|
+
else setVal(raw);
|
|
66
|
+
}, [raw]);
|
|
67
|
+
return [val, setVal];
|
|
68
|
+
}
|
|
69
|
+
function useLiquidValues(map, types) {
|
|
70
|
+
const raw = useLiquidRawValues(map);
|
|
71
|
+
const keys = Object.keys(map);
|
|
72
|
+
const rawDep = keys.map((k) => raw[k]).join("\0");
|
|
73
|
+
const isSSR = typeof globalThis.document === "undefined";
|
|
74
|
+
const [parsed, setParsed] = useState(() => {
|
|
75
|
+
const vals = {};
|
|
76
|
+
for (const k of keys) {
|
|
77
|
+
const mode = types?.[k] ?? "string";
|
|
78
|
+
if (mode === "boolean") {
|
|
79
|
+
vals[k] = false;
|
|
80
|
+
} else if (mode === "number") {
|
|
81
|
+
vals[k] = isSSR ? raw[k] : parseLiquidNumber(raw[k], 0);
|
|
82
|
+
} else {
|
|
83
|
+
vals[k] = raw[k];
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return vals;
|
|
87
|
+
});
|
|
88
|
+
useEffect(() => {
|
|
89
|
+
setParsed((prev) => {
|
|
90
|
+
let changed = false;
|
|
91
|
+
const next = { ...prev };
|
|
92
|
+
for (const k of keys) {
|
|
93
|
+
const mode = types?.[k] ?? "string";
|
|
94
|
+
let v;
|
|
95
|
+
if (mode === "number") v = parseLiquidNumber(raw[k], 0);
|
|
96
|
+
else if (mode === "boolean") v = parseLiquidBoolean(raw[k]);
|
|
97
|
+
else v = raw[k];
|
|
98
|
+
if (v !== prev[k]) {
|
|
99
|
+
next[k] = v;
|
|
100
|
+
changed = true;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return changed ? next : prev;
|
|
104
|
+
});
|
|
105
|
+
}, [rawDep]);
|
|
106
|
+
return parsed;
|
|
107
|
+
}
|
|
108
|
+
function useSectionSettings(key) {
|
|
109
|
+
return { value: useLiquidRaw(`section.settings.${key}`) };
|
|
110
|
+
}
|
|
111
|
+
function useBlockSettings(key) {
|
|
112
|
+
return { value: useLiquidRaw(`block.settings.${key}`) };
|
|
113
|
+
}
|
|
114
|
+
function useSnippetParams(key) {
|
|
115
|
+
return { value: useLiquidRaw(key) };
|
|
116
|
+
}
|
|
117
|
+
function useBlockParams(key) {
|
|
118
|
+
return { value: useLiquidRaw(key) };
|
|
119
|
+
}
|
|
62
120
|
export {
|
|
63
121
|
LiquidDataContext,
|
|
64
122
|
LiquidDataProvider,
|
|
@@ -66,7 +124,7 @@ export {
|
|
|
66
124
|
parseLiquidNumber,
|
|
67
125
|
useBlockParams,
|
|
68
126
|
useBlockSettings,
|
|
69
|
-
|
|
127
|
+
useLiquidValue,
|
|
70
128
|
useLiquidValues,
|
|
71
129
|
useSectionSettings,
|
|
72
130
|
useSnippetParams
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vite-plugin-react-shopify",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"description": "Vite plugin for React Shopify themes",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist"
|
|
@@ -19,7 +19,9 @@
|
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"debug": "^4.4.0",
|
|
22
|
-
"fast-glob": "^3.3.0"
|
|
22
|
+
"fast-glob": "^3.3.0",
|
|
23
|
+
"oxc-parser": "^0.133.0",
|
|
24
|
+
"oxc-walker": "^1.0.0"
|
|
23
25
|
},
|
|
24
26
|
"devDependencies": {
|
|
25
27
|
"@types/debug": "^4.1.0",
|