vite-plugin-shopify-theme-islands 1.2.0 → 1.2.2
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/config-policy.d.ts +11 -0
- package/dist/directive-orchestration.d.ts +27 -0
- package/dist/events.d.ts +1 -1
- package/dist/events.js +67 -6
- package/dist/index.d.ts +2 -65
- package/dist/index.js +117 -70
- package/dist/options.d.ts +64 -0
- package/dist/revive-bootstrap.d.ts +31 -0
- package/dist/runtime-surface.d.ts +20 -0
- package/dist/runtime.js +226 -166
- package/package.json +1 -1
- package/skills/custom-directives/SKILL.md +12 -9
- package/skills/directives/SKILL.md +20 -21
- package/skills/lifecycle/SKILL.md +8 -5
- package/skills/setup/SKILL.md +5 -3
- package/skills/writing-islands/SKILL.md +1 -1
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { type ReviveOptions } from "./contract.js";
|
|
2
|
+
import type { ClientDirectiveDefinition, DirectivesConfig, ShopifyThemeIslandsOptions } from "./options.js";
|
|
3
|
+
export interface ResolvedThemeIslandsPolicy {
|
|
4
|
+
plugin: {
|
|
5
|
+
directives: DirectivesConfig;
|
|
6
|
+
customDirectives: ClientDirectiveDefinition[];
|
|
7
|
+
debug: boolean;
|
|
8
|
+
};
|
|
9
|
+
runtime: ReviveOptions;
|
|
10
|
+
}
|
|
11
|
+
export declare function resolveThemeIslandsPolicy(options?: ShopifyThemeIslandsOptions): ResolvedThemeIslandsPolicy;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ClientDirective, NormalizedReviveOptions } from "./contract.js";
|
|
2
|
+
import type { RuntimeLogger } from "./runtime-surface.js";
|
|
3
|
+
export interface DirectiveWaiters {
|
|
4
|
+
waitVisible(element: Element, rootMargin: string, threshold: number, watch: (el: Element, cancel: () => void) => () => void): Promise<void>;
|
|
5
|
+
waitMedia(query: string): Promise<void>;
|
|
6
|
+
waitIdle(timeout: number): Promise<void>;
|
|
7
|
+
waitDelay(ms: number): Promise<void>;
|
|
8
|
+
waitInteraction(element: Element, events: string[], watch: (el: Element, cancel: () => void) => () => void): Promise<void>;
|
|
9
|
+
}
|
|
10
|
+
export interface DirectiveRunContext {
|
|
11
|
+
tagName: string;
|
|
12
|
+
element: HTMLElement;
|
|
13
|
+
directives: NormalizedReviveOptions["directives"];
|
|
14
|
+
customDirectives?: Map<string, ClientDirective>;
|
|
15
|
+
directiveTimeout: number;
|
|
16
|
+
watchCancellable: (el: Element, cancel: () => void) => () => void;
|
|
17
|
+
log: RuntimeLogger;
|
|
18
|
+
run: () => Promise<void>;
|
|
19
|
+
onError(attrName: string, err: unknown): void;
|
|
20
|
+
}
|
|
21
|
+
export interface DirectiveOrchestrator {
|
|
22
|
+
run(ctx: DirectiveRunContext): Promise<boolean>;
|
|
23
|
+
}
|
|
24
|
+
export declare class DirectiveCancelledError extends Error {
|
|
25
|
+
constructor();
|
|
26
|
+
}
|
|
27
|
+
export declare function createDirectiveOrchestrator(waiters?: DirectiveWaiters): DirectiveOrchestrator;
|
package/dist/events.d.ts
CHANGED
package/dist/events.js
CHANGED
|
@@ -1,13 +1,74 @@
|
|
|
1
|
+
// src/runtime-surface.ts
|
|
2
|
+
var SILENT_LOGGER = {
|
|
3
|
+
note() {},
|
|
4
|
+
flush() {}
|
|
5
|
+
};
|
|
6
|
+
function addListener(target, name, handler) {
|
|
7
|
+
const listener = (event) => handler(event.detail);
|
|
8
|
+
target.addEventListener(name, listener);
|
|
9
|
+
return () => target.removeEventListener(name, listener);
|
|
10
|
+
}
|
|
11
|
+
function dispatch(target, name, detail) {
|
|
12
|
+
target.dispatchEvent(new CustomEvent(name, { detail }));
|
|
13
|
+
}
|
|
14
|
+
function createRuntimeSurface(deps) {
|
|
15
|
+
return {
|
|
16
|
+
dispatchLoad(detail) {
|
|
17
|
+
dispatch(deps.target, "islands:load", detail);
|
|
18
|
+
},
|
|
19
|
+
dispatchError(detail) {
|
|
20
|
+
dispatch(deps.target, "islands:error", detail);
|
|
21
|
+
},
|
|
22
|
+
onLoad(handler) {
|
|
23
|
+
return addListener(deps.target, "islands:load", handler);
|
|
24
|
+
},
|
|
25
|
+
onError(handler) {
|
|
26
|
+
return addListener(deps.target, "islands:error", handler);
|
|
27
|
+
},
|
|
28
|
+
createLogger(tagName, debug) {
|
|
29
|
+
if (!debug)
|
|
30
|
+
return SILENT_LOGGER;
|
|
31
|
+
const msgs = [];
|
|
32
|
+
return {
|
|
33
|
+
note(msg) {
|
|
34
|
+
msgs.push(msg);
|
|
35
|
+
},
|
|
36
|
+
flush(summary) {
|
|
37
|
+
if (msgs.length === 0) {
|
|
38
|
+
deps.console.log("[islands]", `<${tagName}> ${summary}`);
|
|
39
|
+
} else {
|
|
40
|
+
deps.console.groupCollapsed(`[islands] <${tagName}> ${summary}`);
|
|
41
|
+
for (const msg of msgs)
|
|
42
|
+
deps.console.log(msg);
|
|
43
|
+
deps.console.groupEnd();
|
|
44
|
+
}
|
|
45
|
+
msgs.length = 0;
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
},
|
|
49
|
+
beginReadyLog(islandCount, debug) {
|
|
50
|
+
if (!debug)
|
|
51
|
+
return () => {};
|
|
52
|
+
deps.console.groupCollapsed(`[islands] ready — ${islandCount} island(s)`);
|
|
53
|
+
return () => deps.console.groupEnd();
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
var runtimeSurface;
|
|
58
|
+
function getRuntimeSurface() {
|
|
59
|
+
runtimeSurface ??= createRuntimeSurface({
|
|
60
|
+
target: document,
|
|
61
|
+
console
|
|
62
|
+
});
|
|
63
|
+
return runtimeSurface;
|
|
64
|
+
}
|
|
65
|
+
|
|
1
66
|
// src/events.ts
|
|
2
67
|
function onIslandLoad(handler) {
|
|
3
|
-
|
|
4
|
-
document.addEventListener("islands:load", listener);
|
|
5
|
-
return () => document.removeEventListener("islands:load", listener);
|
|
68
|
+
return getRuntimeSurface().onLoad(handler);
|
|
6
69
|
}
|
|
7
70
|
function onIslandError(handler) {
|
|
8
|
-
|
|
9
|
-
document.addEventListener("islands:error", listener);
|
|
10
|
-
return () => document.removeEventListener("islands:error", listener);
|
|
71
|
+
return getRuntimeSurface().onError(handler);
|
|
11
72
|
}
|
|
12
73
|
export {
|
|
13
74
|
onIslandLoad,
|
package/dist/index.d.ts
CHANGED
|
@@ -1,71 +1,8 @@
|
|
|
1
|
+
import type { ShopifyThemeIslandsOptions } from "./options.js";
|
|
1
2
|
import type { Plugin } from "vite";
|
|
2
3
|
/** A function that triggers the load of an island module. */
|
|
3
4
|
export type ClientDirectiveLoader = () => Promise<void>;
|
|
4
5
|
export type { ClientDirective, ClientDirectiveOptions } from "./contract.js";
|
|
5
|
-
|
|
6
|
-
export interface ClientDirectiveDefinition {
|
|
7
|
-
/** HTML attribute name, e.g. `'client:on-click'` */
|
|
8
|
-
name: string;
|
|
9
|
-
/** Path to the directive module (supports Vite aliases) */
|
|
10
|
-
entrypoint: string;
|
|
11
|
-
}
|
|
12
|
-
/** Shared directive configuration shape used by both the plugin and the runtime. */
|
|
13
|
-
export interface DirectivesConfig {
|
|
14
|
-
/** Configuration for the `client:visible` directive (IntersectionObserver). */
|
|
15
|
-
visible?: {
|
|
16
|
-
/** HTML attribute name. Default: `'client:visible'` */
|
|
17
|
-
attribute?: string;
|
|
18
|
-
/** Passed to IntersectionObserver — loads islands before they scroll into view. Default: `'200px'` */
|
|
19
|
-
rootMargin?: string;
|
|
20
|
-
/** Passed to IntersectionObserver — ratio of element that must be visible. Default: `0` */
|
|
21
|
-
threshold?: number;
|
|
22
|
-
};
|
|
23
|
-
/** Configuration for the `client:idle` directive (requestIdleCallback). */
|
|
24
|
-
idle?: {
|
|
25
|
-
/** HTML attribute name. Default: `'client:idle'` */
|
|
26
|
-
attribute?: string;
|
|
27
|
-
/** Deadline (ms) passed to requestIdleCallback; also used as the setTimeout fallback delay. Default: `500` */
|
|
28
|
-
timeout?: number;
|
|
29
|
-
};
|
|
30
|
-
/** Configuration for the `client:media` directive (matchMedia). */
|
|
31
|
-
media?: {
|
|
32
|
-
/** HTML attribute name. Default: `'client:media'` */
|
|
33
|
-
attribute?: string;
|
|
34
|
-
};
|
|
35
|
-
/** Configuration for the `client:defer` directive (fixed setTimeout delay). */
|
|
36
|
-
defer?: {
|
|
37
|
-
/** HTML attribute name. Default: `'client:defer'` */
|
|
38
|
-
attribute?: string;
|
|
39
|
-
/** Fallback delay (ms) when the attribute has no value. Default: `3000` */
|
|
40
|
-
delay?: number;
|
|
41
|
-
};
|
|
42
|
-
/** Configuration for the `client:interaction` directive (mouseenter/touchstart/focusin). */
|
|
43
|
-
interaction?: {
|
|
44
|
-
/** HTML attribute name. Default: `'client:interaction'` */
|
|
45
|
-
attribute?: string;
|
|
46
|
-
/** DOM event names to listen for. Default: `['mouseenter', 'touchstart', 'focusin']` */
|
|
47
|
-
events?: string[];
|
|
48
|
-
};
|
|
49
|
-
/** Custom client directives to register. Each entry maps an attribute name to a module entrypoint. */
|
|
50
|
-
custom?: ClientDirectiveDefinition[];
|
|
51
|
-
}
|
|
52
|
-
/** Event detail and runtime options (single source of truth in contract). */
|
|
53
|
-
import type { RetryConfig } from "./contract.js";
|
|
6
|
+
export type { ClientDirectiveDefinition, DirectivesConfig, ShopifyThemeIslandsOptions, } from "./options.js";
|
|
54
7
|
export type { IslandLoadDetail, IslandErrorDetail, ReviveOptions, RetryConfig, RuntimeDirectivesConfig, } from "./contract.js";
|
|
55
|
-
export interface ShopifyThemeIslandsOptions {
|
|
56
|
-
/** Directories to scan for island files. Accepts paths or Vite aliases. Default: `['/frontend/js/islands/']` */
|
|
57
|
-
directories?: string | string[];
|
|
58
|
-
/** Log discovered islands and generated virtual module. Default: `false` */
|
|
59
|
-
debug?: boolean;
|
|
60
|
-
/** Per-directive configuration. */
|
|
61
|
-
directives?: DirectivesConfig;
|
|
62
|
-
/** Automatic retry behaviour for failed island loads. */
|
|
63
|
-
retry?: RetryConfig;
|
|
64
|
-
/**
|
|
65
|
-
* Milliseconds before a custom directive that never calls `load()` is considered timed out.
|
|
66
|
-
* When exceeded, `islands:error` is dispatched and the island is abandoned.
|
|
67
|
-
* Default: `0` (disabled).
|
|
68
|
-
*/
|
|
69
|
-
directiveTimeout?: number;
|
|
70
|
-
}
|
|
71
8
|
export default function shopifyThemeIslands(options?: ShopifyThemeIslandsOptions): Plugin;
|
package/dist/index.js
CHANGED
|
@@ -54,39 +54,6 @@ function collectTagNames(dir) {
|
|
|
54
54
|
return names;
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
// src/revive-module.ts
|
|
58
|
-
function buildReviveModuleSource(params) {
|
|
59
|
-
const { runtimePath, directoryGlobs, islandPaths, customDirectives, reviveOptions } = params;
|
|
60
|
-
const directiveImportLines = customDirectives?.map(({ entrypoint }, index) => `import _directive${index} from ${JSON.stringify(entrypoint)};`) ?? [];
|
|
61
|
-
const globEntries = [
|
|
62
|
-
`{ ${directoryGlobs.map((glob) => `...import.meta.glob(${JSON.stringify(glob)})`).join(", ")} }`
|
|
63
|
-
];
|
|
64
|
-
if (islandPaths?.length)
|
|
65
|
-
globEntries.push(`import.meta.glob(${JSON.stringify(islandPaths)})`);
|
|
66
|
-
const lines = [
|
|
67
|
-
...directiveImportLines,
|
|
68
|
-
`import { revive as _islands } from ${JSON.stringify(runtimePath)};`,
|
|
69
|
-
`const islands = Object.assign({}, ${globEntries.join(", ")});`,
|
|
70
|
-
`const options = ${JSON.stringify(reviveOptions)};`
|
|
71
|
-
];
|
|
72
|
-
if (customDirectives?.length) {
|
|
73
|
-
const customDirectivesMapLines = customDirectives.map(({ name }, index) => ` [${JSON.stringify(name)}, _directive${index}]`);
|
|
74
|
-
lines.push(`const customDirectives = new Map([
|
|
75
|
-
${customDirectivesMapLines.join(`,
|
|
76
|
-
`)}
|
|
77
|
-
]);`);
|
|
78
|
-
lines.push(`const payload = { islands, options, customDirectives };`);
|
|
79
|
-
} else {
|
|
80
|
-
lines.push(`const payload = { islands, options };`);
|
|
81
|
-
}
|
|
82
|
-
lines.push(`export const { disconnect } = _islands(payload);`);
|
|
83
|
-
return lines.join(`
|
|
84
|
-
`);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// src/index.ts
|
|
88
|
-
import { fileURLToPath } from "node:url";
|
|
89
|
-
|
|
90
57
|
// src/contract.ts
|
|
91
58
|
var DEFAULT_DIRECTIVES = {
|
|
92
59
|
visible: { attribute: "client:visible", rootMargin: "200px", threshold: 0 },
|
|
@@ -137,13 +104,17 @@ function buildIslandMap(payload) {
|
|
|
137
104
|
return map;
|
|
138
105
|
}
|
|
139
106
|
|
|
140
|
-
// src/
|
|
141
|
-
var VIRTUAL_ID = "vite-plugin-shopify-theme-islands/revive";
|
|
142
|
-
var RESOLVED_ID = "\x00" + VIRTUAL_ID;
|
|
143
|
-
var ISLAND_ID = "vite-plugin-shopify-theme-islands/island";
|
|
144
|
-
var runtimePath = fileURLToPath(new URL("./runtime.js", import.meta.url));
|
|
145
|
-
var islandPath = fileURLToPath(new URL("./island.js", import.meta.url));
|
|
107
|
+
// src/config-policy.ts
|
|
146
108
|
var PREFIX = "[vite-plugin-shopify-theme-islands]";
|
|
109
|
+
function mergeDirectives(directives) {
|
|
110
|
+
return {
|
|
111
|
+
visible: { ...DEFAULT_DIRECTIVES.visible, ...directives?.visible },
|
|
112
|
+
idle: { ...DEFAULT_DIRECTIVES.idle, ...directives?.idle },
|
|
113
|
+
media: { ...DEFAULT_DIRECTIVES.media, ...directives?.media },
|
|
114
|
+
defer: { ...DEFAULT_DIRECTIVES.defer, ...directives?.defer },
|
|
115
|
+
interaction: { ...DEFAULT_DIRECTIVES.interaction, ...directives?.interaction }
|
|
116
|
+
};
|
|
117
|
+
}
|
|
147
118
|
function validateOptions(options, directives) {
|
|
148
119
|
const customDefs = options.directives?.custom ?? [];
|
|
149
120
|
if (Array.isArray(options.directories) && options.directories.length === 0) {
|
|
@@ -180,6 +151,93 @@ function validateOptions(options, directives) {
|
|
|
180
151
|
seen.add(def.name);
|
|
181
152
|
}
|
|
182
153
|
}
|
|
154
|
+
function resolveThemeIslandsPolicy(options = {}) {
|
|
155
|
+
const directives = mergeDirectives(options.directives);
|
|
156
|
+
validateOptions(options, directives);
|
|
157
|
+
const customDirectives = options.directives?.custom ?? [];
|
|
158
|
+
const debug = options.debug ?? false;
|
|
159
|
+
const runtime = {
|
|
160
|
+
directives,
|
|
161
|
+
debug,
|
|
162
|
+
...options.retry !== undefined ? { retry: options.retry } : {},
|
|
163
|
+
...options.directiveTimeout !== undefined ? { directiveTimeout: options.directiveTimeout } : {}
|
|
164
|
+
};
|
|
165
|
+
return {
|
|
166
|
+
plugin: {
|
|
167
|
+
directives,
|
|
168
|
+
customDirectives,
|
|
169
|
+
debug
|
|
170
|
+
},
|
|
171
|
+
runtime
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// src/revive-module.ts
|
|
176
|
+
function buildReviveModuleSource(params) {
|
|
177
|
+
const { runtimePath, directoryGlobs, islandPaths, customDirectives, reviveOptions } = params;
|
|
178
|
+
const directiveImportLines = customDirectives?.map(({ entrypoint }, index) => `import _directive${index} from ${JSON.stringify(entrypoint)};`) ?? [];
|
|
179
|
+
const globEntries = [
|
|
180
|
+
`{ ${directoryGlobs.map((glob) => `...import.meta.glob(${JSON.stringify(glob)})`).join(", ")} }`
|
|
181
|
+
];
|
|
182
|
+
if (islandPaths?.length)
|
|
183
|
+
globEntries.push(`import.meta.glob(${JSON.stringify(islandPaths)})`);
|
|
184
|
+
const lines = [
|
|
185
|
+
...directiveImportLines,
|
|
186
|
+
`import { revive as _islands } from ${JSON.stringify(runtimePath)};`,
|
|
187
|
+
`const islands = Object.assign({}, ${globEntries.join(", ")});`,
|
|
188
|
+
`const options = ${JSON.stringify(reviveOptions)};`
|
|
189
|
+
];
|
|
190
|
+
if (customDirectives?.length) {
|
|
191
|
+
const customDirectivesMapLines = customDirectives.map(({ name }, index) => ` [${JSON.stringify(name)}, _directive${index}]`);
|
|
192
|
+
lines.push(`const customDirectives = new Map([
|
|
193
|
+
${customDirectivesMapLines.join(`,
|
|
194
|
+
`)}
|
|
195
|
+
]);`);
|
|
196
|
+
lines.push(`const payload = { islands, options, customDirectives };`);
|
|
197
|
+
} else {
|
|
198
|
+
lines.push(`const payload = { islands, options };`);
|
|
199
|
+
}
|
|
200
|
+
lines.push(`export const { disconnect } = _islands(payload);`);
|
|
201
|
+
return lines.join(`
|
|
202
|
+
`);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// src/revive-bootstrap.ts
|
|
206
|
+
function createReviveBootstrapCompiler(ports, runtimePath) {
|
|
207
|
+
return {
|
|
208
|
+
async plan(input) {
|
|
209
|
+
const islandPaths = input.islandFiles.size > 0 ? ports.toLoadPaths(input.islandFiles, input.root) : null;
|
|
210
|
+
const customDirectives = input.customDirectives?.length ? await Promise.all(input.customDirectives.map(async ({ name, entrypoint }) => ({
|
|
211
|
+
name,
|
|
212
|
+
entrypoint: await ports.resolveEntrypoint(entrypoint)
|
|
213
|
+
}))) : null;
|
|
214
|
+
return {
|
|
215
|
+
runtimePath,
|
|
216
|
+
directoryGlobs: input.directories.map((dir) => dir + "**/*.{ts,js}"),
|
|
217
|
+
islandPaths,
|
|
218
|
+
customDirectives,
|
|
219
|
+
reviveOptions: input.reviveOptions
|
|
220
|
+
};
|
|
221
|
+
},
|
|
222
|
+
emit(plan) {
|
|
223
|
+
return buildReviveModuleSource({
|
|
224
|
+
runtimePath: plan.runtimePath,
|
|
225
|
+
directoryGlobs: plan.directoryGlobs,
|
|
226
|
+
islandPaths: plan.islandPaths,
|
|
227
|
+
customDirectives: plan.customDirectives?.length ? plan.customDirectives : undefined,
|
|
228
|
+
reviveOptions: plan.reviveOptions
|
|
229
|
+
});
|
|
230
|
+
}
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// src/index.ts
|
|
235
|
+
import { fileURLToPath } from "node:url";
|
|
236
|
+
var VIRTUAL_ID = "vite-plugin-shopify-theme-islands/revive";
|
|
237
|
+
var RESOLVED_ID = "\x00" + VIRTUAL_ID;
|
|
238
|
+
var ISLAND_ID = "vite-plugin-shopify-theme-islands/island";
|
|
239
|
+
var runtimePath = fileURLToPath(new URL("./runtime.js", import.meta.url));
|
|
240
|
+
var islandPath = fileURLToPath(new URL("./island.js", import.meta.url));
|
|
183
241
|
var defaultDirectories = ["/frontend/js/islands/"];
|
|
184
242
|
function normalizeDir(dir) {
|
|
185
243
|
return dir.endsWith("/") ? dir : dir + "/";
|
|
@@ -198,16 +256,9 @@ function resolveAliases(dirs, config) {
|
|
|
198
256
|
}
|
|
199
257
|
function shopifyThemeIslands(options = {}) {
|
|
200
258
|
const rawDirs = (Array.isArray(options.directories) ? options.directories : [options.directories ?? defaultDirectories[0]]).map(normalizeDir);
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
media: { ...DEFAULT_DIRECTIVES.media, ...options.directives?.media },
|
|
205
|
-
defer: { ...DEFAULT_DIRECTIVES.defer, ...options.directives?.defer },
|
|
206
|
-
interaction: { ...DEFAULT_DIRECTIVES.interaction, ...options.directives?.interaction }
|
|
207
|
-
};
|
|
208
|
-
const clientDirectiveDefinitions = options.directives?.custom ?? [];
|
|
209
|
-
validateOptions(options, directives);
|
|
210
|
-
const debug = options.debug ?? false;
|
|
259
|
+
const policy = resolveThemeIslandsPolicy(options);
|
|
260
|
+
const { directives, customDirectives: clientDirectiveDefinitions, debug } = policy.plugin;
|
|
261
|
+
const { runtime: reviveOptions } = policy;
|
|
211
262
|
const log = debug ? (...args) => console.log("[islands]", ...args) : () => {};
|
|
212
263
|
let resolvedDirs = rawDirs;
|
|
213
264
|
let root = process.cwd();
|
|
@@ -284,28 +335,24 @@ function shopifyThemeIslands(options = {}) {
|
|
|
284
335
|
async load(id) {
|
|
285
336
|
if (id !== RESOLVED_ID)
|
|
286
337
|
return;
|
|
287
|
-
const
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
customDirectives,
|
|
302
|
-
reviveOptions
|
|
303
|
-
directives,
|
|
304
|
-
debug,
|
|
305
|
-
retry: options.retry,
|
|
306
|
-
directiveTimeout: options.directiveTimeout
|
|
307
|
-
}
|
|
338
|
+
const compiler = createReviveBootstrapCompiler({
|
|
339
|
+
resolveEntrypoint: async (entrypoint) => {
|
|
340
|
+
const resolved = await this.resolve(entrypoint);
|
|
341
|
+
if (!resolved) {
|
|
342
|
+
throw new Error(`[vite-plugin-shopify-theme-islands] Cannot resolve custom directive entrypoint: "${entrypoint}"`);
|
|
343
|
+
}
|
|
344
|
+
return resolved.id;
|
|
345
|
+
},
|
|
346
|
+
toLoadPaths: getIslandPathsForLoad
|
|
347
|
+
}, runtimePath);
|
|
348
|
+
const plan = await compiler.plan({
|
|
349
|
+
root,
|
|
350
|
+
directories: resolvedDirs,
|
|
351
|
+
islandFiles,
|
|
352
|
+
customDirectives: clientDirectiveDefinitions,
|
|
353
|
+
reviveOptions
|
|
308
354
|
});
|
|
355
|
+
return compiler.emit(plan);
|
|
309
356
|
}
|
|
310
357
|
};
|
|
311
358
|
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import type { RetryConfig } from "./contract.js";
|
|
2
|
+
/** Plugin option entry for registering a custom client directive. */
|
|
3
|
+
export interface ClientDirectiveDefinition {
|
|
4
|
+
/** HTML attribute name, e.g. `'client:on-click'` */
|
|
5
|
+
name: string;
|
|
6
|
+
/** Path to the directive module (supports Vite aliases) */
|
|
7
|
+
entrypoint: string;
|
|
8
|
+
}
|
|
9
|
+
/** Shared directive configuration shape used by both the plugin and the runtime. */
|
|
10
|
+
export interface DirectivesConfig {
|
|
11
|
+
/** Configuration for the `client:visible` directive (IntersectionObserver). */
|
|
12
|
+
visible?: {
|
|
13
|
+
/** HTML attribute name. Default: `'client:visible'` */
|
|
14
|
+
attribute?: string;
|
|
15
|
+
/** Passed to IntersectionObserver — loads islands before they scroll into view. Default: `'200px'` */
|
|
16
|
+
rootMargin?: string;
|
|
17
|
+
/** Passed to IntersectionObserver — ratio of element that must be visible. Default: `0` */
|
|
18
|
+
threshold?: number;
|
|
19
|
+
};
|
|
20
|
+
/** Configuration for the `client:idle` directive (requestIdleCallback). */
|
|
21
|
+
idle?: {
|
|
22
|
+
/** HTML attribute name. Default: `'client:idle'` */
|
|
23
|
+
attribute?: string;
|
|
24
|
+
/** Deadline (ms) passed to requestIdleCallback; also used as the setTimeout fallback delay. Default: `500` */
|
|
25
|
+
timeout?: number;
|
|
26
|
+
};
|
|
27
|
+
/** Configuration for the `client:media` directive (matchMedia). */
|
|
28
|
+
media?: {
|
|
29
|
+
/** HTML attribute name. Default: `'client:media'` */
|
|
30
|
+
attribute?: string;
|
|
31
|
+
};
|
|
32
|
+
/** Configuration for the `client:defer` directive (fixed setTimeout delay). */
|
|
33
|
+
defer?: {
|
|
34
|
+
/** HTML attribute name. Default: `'client:defer'` */
|
|
35
|
+
attribute?: string;
|
|
36
|
+
/** Fallback delay (ms) when the attribute has no value. Default: `3000` */
|
|
37
|
+
delay?: number;
|
|
38
|
+
};
|
|
39
|
+
/** Configuration for the `client:interaction` directive (mouseenter/touchstart/focusin). */
|
|
40
|
+
interaction?: {
|
|
41
|
+
/** HTML attribute name. Default: `'client:interaction'` */
|
|
42
|
+
attribute?: string;
|
|
43
|
+
/** DOM event names to listen for. Default: `['mouseenter', 'touchstart', 'focusin']` */
|
|
44
|
+
events?: string[];
|
|
45
|
+
};
|
|
46
|
+
/** Custom client directives to register. Each entry maps an attribute name to a module entrypoint. */
|
|
47
|
+
custom?: ClientDirectiveDefinition[];
|
|
48
|
+
}
|
|
49
|
+
export interface ShopifyThemeIslandsOptions {
|
|
50
|
+
/** Directories to scan for island files. Accepts paths or Vite aliases. Default: `['/frontend/js/islands/']` */
|
|
51
|
+
directories?: string | string[];
|
|
52
|
+
/** Log discovered islands and generated virtual module. Default: `false` */
|
|
53
|
+
debug?: boolean;
|
|
54
|
+
/** Per-directive configuration. */
|
|
55
|
+
directives?: DirectivesConfig;
|
|
56
|
+
/** Automatic retry behaviour for failed island loads. */
|
|
57
|
+
retry?: RetryConfig;
|
|
58
|
+
/**
|
|
59
|
+
* Milliseconds before a custom directive that never calls `load()` is considered timed out.
|
|
60
|
+
* When exceeded, `islands:error` is dispatched and the island is abandoned.
|
|
61
|
+
* Default: `0` (disabled).
|
|
62
|
+
*/
|
|
63
|
+
directiveTimeout?: number;
|
|
64
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { ReviveOptions } from "./contract.js";
|
|
2
|
+
export interface ResolvedCustomDirective {
|
|
3
|
+
name: string;
|
|
4
|
+
entrypoint: string;
|
|
5
|
+
}
|
|
6
|
+
export interface ReviveBootstrapInputs {
|
|
7
|
+
root: string;
|
|
8
|
+
directories: string[];
|
|
9
|
+
islandFiles: Set<string>;
|
|
10
|
+
customDirectives?: Array<{
|
|
11
|
+
name: string;
|
|
12
|
+
entrypoint: string;
|
|
13
|
+
}>;
|
|
14
|
+
reviveOptions: ReviveOptions;
|
|
15
|
+
}
|
|
16
|
+
export interface ReviveBootstrapPlan {
|
|
17
|
+
runtimePath: string;
|
|
18
|
+
directoryGlobs: string[];
|
|
19
|
+
islandPaths: string[] | null;
|
|
20
|
+
customDirectives: ResolvedCustomDirective[] | null;
|
|
21
|
+
reviveOptions: ReviveOptions;
|
|
22
|
+
}
|
|
23
|
+
export interface ReviveBootstrapCompilerPorts {
|
|
24
|
+
resolveEntrypoint(entrypoint: string): Promise<string>;
|
|
25
|
+
toLoadPaths(islandFiles: Set<string>, root: string): string[];
|
|
26
|
+
}
|
|
27
|
+
export interface ReviveBootstrapCompiler {
|
|
28
|
+
plan(input: ReviveBootstrapInputs): Promise<ReviveBootstrapPlan>;
|
|
29
|
+
emit(plan: ReviveBootstrapPlan): string;
|
|
30
|
+
}
|
|
31
|
+
export declare function createReviveBootstrapCompiler(ports: ReviveBootstrapCompilerPorts, runtimePath: string): ReviveBootstrapCompiler;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { IslandErrorDetail, IslandLoadDetail } from "./contract.js";
|
|
2
|
+
export interface RuntimeLogger {
|
|
3
|
+
note(msg: string): void;
|
|
4
|
+
flush(summary: string): void;
|
|
5
|
+
}
|
|
6
|
+
export interface RuntimeSurface {
|
|
7
|
+
dispatchLoad(detail: IslandLoadDetail): void;
|
|
8
|
+
dispatchError(detail: IslandErrorDetail): void;
|
|
9
|
+
onLoad(handler: (detail: IslandLoadDetail) => void): () => void;
|
|
10
|
+
onError(handler: (detail: IslandErrorDetail) => void): () => void;
|
|
11
|
+
createLogger(tagName: string, debug: boolean): RuntimeLogger;
|
|
12
|
+
beginReadyLog(islandCount: number, debug: boolean): () => void;
|
|
13
|
+
}
|
|
14
|
+
interface RuntimeSurfaceDeps {
|
|
15
|
+
target: Document;
|
|
16
|
+
console: Pick<Console, "log" | "groupCollapsed" | "groupEnd">;
|
|
17
|
+
}
|
|
18
|
+
export declare function createRuntimeSurface(deps: RuntimeSurfaceDeps): RuntimeSurface;
|
|
19
|
+
export declare function getRuntimeSurface(): RuntimeSurface;
|
|
20
|
+
export {};
|