vite-plugin-shopify-theme-islands 0.5.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -100,7 +100,7 @@ The plugin detects the mixin import at build time and includes the file as a laz
100
100
 
101
101
  Both can be used together — directory scanning for new islands, the mixin for existing components you want to adopt without moving.
102
102
 
103
- ## Loading directives
103
+ ## Directives
104
104
 
105
105
  Add these attributes to your custom elements in Liquid to control when the JavaScript loads. Without a directive, the island loads immediately.
106
106
 
@@ -114,6 +114,15 @@ Loads the island when the element scrolls into view.
114
114
  </product-recommendations>
115
115
  ```
116
116
 
117
+ The attribute value overrides the global `rootMargin` for that element only:
118
+
119
+ ```html
120
+ <!-- load only once fully visible (no pre-load margin) -->
121
+ <product-recommendations client:visible="0px">
122
+ <!-- ... -->
123
+ </product-recommendations>
124
+ ```
125
+
117
126
  ### `client:media`
118
127
 
119
128
  Loads the island when a CSS media query matches.
@@ -134,6 +143,15 @@ Loads the island once the browser is idle (uses `requestIdleCallback` with a 500
134
143
  </recently-viewed>
135
144
  ```
136
145
 
146
+ The attribute value overrides the global `timeout` for that element only:
147
+
148
+ ```html
149
+ <!-- wait up to 2 seconds for idle time before loading -->
150
+ <recently-viewed client:idle="2000">
151
+ <!-- ... -->
152
+ </recently-viewed>
153
+ ```
154
+
137
155
  ### `client:defer`
138
156
 
139
157
  Loads the island after a fixed delay. The delay in milliseconds is read from the attribute value. If no value is given, the configured default (3000ms) is used.
@@ -151,7 +169,9 @@ Loads the island after a fixed delay. The delay in milliseconds is read from the
151
169
 
152
170
  Unlike `client:idle`, which waits for genuine browser idle time, `client:defer` always waits exactly the specified number of milliseconds.
153
171
 
154
- Directives can be combined — the element will wait for all conditions to be met before loading:
172
+ ### Combining directives
173
+
174
+ Directives can be combined — the element waits for all conditions to be met before loading:
155
175
 
156
176
  ```html
157
177
  <heavy-widget client:visible client:idle>
@@ -159,13 +179,83 @@ Directives can be combined — the element will wait for all conditions to be me
159
179
  </heavy-widget>
160
180
  ```
161
181
 
162
- ## Options
182
+ ### Custom directives
183
+
184
+ Register your own loading conditions via `directives.custom`. A custom directive is a function that receives a `load` callback and decides when to call it.
185
+
186
+ #### 1. Write the directive
187
+
188
+ ```ts
189
+ // src/directives/hover.ts
190
+ import type { ClientDirective } from "vite-plugin-shopify-theme-islands";
191
+
192
+ const hoverDirective: ClientDirective = (load, _opts, el) => {
193
+ el.addEventListener("mouseenter", load, { once: true });
194
+ };
195
+
196
+ export default hoverDirective;
197
+ ```
198
+
199
+ `mouseenter` fires before `click`, so the module starts downloading the moment the user moves their cursor toward the element — by the time they click it's already loaded.
200
+
201
+ The function signature is `(load, options, el) => void | Promise<void>`:
202
+
203
+ | Parameter | Type | Description |
204
+ | --------------- | ------------------------ | ----------------------------------------------------- |
205
+ | `load` | `() => Promise<unknown>` | Call this to trigger the island module load |
206
+ | `options.name` | `string` | The matched attribute name, e.g. `'client:hover'` |
207
+ | `options.value` | `string` | The attribute value; empty string if no value was set |
208
+ | `el` | `HTMLElement` | The island element |
209
+
210
+ #### 2. Register it in the plugin config
211
+
212
+ ```ts
213
+ // vite.config.ts
214
+ import shopifyThemeIslands from "vite-plugin-shopify-theme-islands";
215
+
216
+ export default defineConfig({
217
+ plugins: [
218
+ shopifyThemeIslands({
219
+ directives: {
220
+ custom: [{ name: "client:hover", entrypoint: "./src/directives/hover.ts" }],
221
+ },
222
+ }),
223
+ ],
224
+ });
225
+ ```
226
+
227
+ The `entrypoint` supports Vite aliases.
228
+
229
+ #### 3. Use it in Liquid
230
+
231
+ ```html
232
+ <quick-add client:hover>
233
+ <!-- ... -->
234
+ </quick-add>
235
+ ```
236
+
237
+ #### Ordering
238
+
239
+ Built-in directives always run first. A custom directive is only invoked after all built-in conditions on the element have been met. This means you can gate a custom directive behind `client:visible` to avoid wiring event listeners for off-screen elements:
240
+
241
+ ```html
242
+ <!-- element must enter the viewport before the hover handler is registered -->
243
+ <quick-add client:visible client:hover>
244
+ <!-- ... -->
245
+ </quick-add>
246
+ ```
247
+
248
+ The custom directive owns the `load()` call — the built-in chain never calls it directly when a custom directive is matched.
249
+
250
+ > Only one custom directive can be active per element. If multiple custom directive attributes are present, the first registered one is used and a console warning is emitted. Combining multiple custom directives on one element is not yet supported.
251
+
252
+ ## Configuration
163
253
 
164
- | Option | Type | Default | Description |
165
- | ------------- | -------------------- | --------------------------- | ----------------------------------------------------------------------------------- |
166
- | `directories` | `string \| string[]` | `['/frontend/js/islands/']` | Directories to scan for island files. Accepts Vite aliases. |
167
- | `directives` | `object` | see below | Per-directive configuration. Each directive has an `attribute` name and extra options. |
168
- | `debug` | `boolean` | `false` | Log discovered islands at build time and directive events in the browser console. |
254
+ | Option | Type | Default | Description |
255
+ | ------------- | -------------------- | --------------------------- | ---------------------------------------------------------------------------------- |
256
+ | `directories` | `string \| string[]` | `['/frontend/js/islands/']` | Directories to scan for island files. Accepts Vite aliases. |
257
+ | `directives` | `object` | see below | Per-directive configuration attribute names, timing options, and custom entries. |
258
+ | `debug` | `boolean` | `false` | Log discovered islands at build time and directive events in the browser console. |
169
259
 
170
260
  ### Directive defaults
171
261
 
@@ -174,20 +264,21 @@ shopifyThemeIslands({
174
264
  directives: {
175
265
  visible: {
176
266
  attribute: "client:visible", // HTML attribute name
177
- rootMargin: "200px", // passed to IntersectionObserver — pre-loads before scrolling into view
178
- threshold: 0, // passed to IntersectionObserver — ratio of element that must be visible
267
+ rootMargin: "200px", // passed to IntersectionObserver — pre-loads before scrolling into view
268
+ threshold: 0, // passed to IntersectionObserver — ratio of element that must be visible
179
269
  },
180
270
  idle: {
181
- attribute: "client:idle", // HTML attribute name
182
- timeout: 500, // deadline (ms) for requestIdleCallback; also the setTimeout fallback delay
271
+ attribute: "client:idle", // HTML attribute name
272
+ timeout: 500, // deadline (ms) for requestIdleCallback; also the setTimeout fallback delay
183
273
  },
184
274
  media: {
185
- attribute: "client:media", // HTML attribute name
275
+ attribute: "client:media", // HTML attribute name
186
276
  },
187
277
  defer: {
188
- attribute: "client:defer", // HTML attribute name
189
- delay: 3000, // fallback delay (ms) when the attribute has no value
278
+ attribute: "client:defer", // HTML attribute name
279
+ delay: 3000, // fallback delay (ms) when the attribute has no value
190
280
  },
281
+ custom: [], // custom directives — see Custom directives above
191
282
  },
192
283
  });
193
284
  ```
package/dist/index.d.ts CHANGED
@@ -1,4 +1,48 @@
1
1
  import type { Plugin } from "vite";
2
+ /** A function that triggers the load of an island module. */
3
+ export type ClientDirectiveLoader = () => Promise<unknown>;
4
+ /** Options passed to a custom client directive function. */
5
+ export interface ClientDirectiveOptions {
6
+ /** The matched attribute name, e.g. `'client:on-click'` */
7
+ name: string;
8
+ /** The attribute value; empty string if no value was set */
9
+ value: string;
10
+ }
11
+ /**
12
+ * A custom client directive function.
13
+ *
14
+ * Called by the runtime when a matching attribute is found on an island element.
15
+ * The function is responsible for calling `load()` when the desired condition is met.
16
+ *
17
+ * @example
18
+ * ```ts
19
+ * // src/directives/hover.ts
20
+ * import type { ClientDirective } from 'vite-plugin-shopify-theme-islands';
21
+ *
22
+ * const hoverDirective: ClientDirective = (load, _opts, el) => {
23
+ * el.addEventListener('mouseenter', load, { once: true });
24
+ * };
25
+ *
26
+ * export default hoverDirective;
27
+ * ```
28
+ *
29
+ * Register it in `vite.config.ts`:
30
+ * ```ts
31
+ * shopifyThemeIslands({
32
+ * directives: {
33
+ * custom: [{ name: 'client:hover', entrypoint: './src/directives/hover.ts' }],
34
+ * },
35
+ * })
36
+ * ```
37
+ */
38
+ export type ClientDirective = (load: ClientDirectiveLoader, options: ClientDirectiveOptions, el: HTMLElement) => void | Promise<void>;
39
+ /** Plugin option entry for registering a custom client directive. */
40
+ export interface ClientDirectiveDefinition {
41
+ /** HTML attribute name, e.g. `'client:on-click'` */
42
+ name: string;
43
+ /** Path to the directive module (supports Vite aliases) */
44
+ entrypoint: string;
45
+ }
2
46
  /** Shared directive configuration shape used by both the plugin and the runtime. */
3
47
  export interface DirectivesConfig {
4
48
  /** Configuration for the `client:visible` directive (IntersectionObserver). */
@@ -29,6 +73,8 @@ export interface DirectivesConfig {
29
73
  /** Fallback delay (ms) when the attribute has no value. Default: `3000` */
30
74
  delay?: number;
31
75
  };
76
+ /** Custom client directives to register. Each entry maps an attribute name to a module entrypoint. */
77
+ custom?: ClientDirectiveDefinition[];
32
78
  }
33
79
  export interface ShopifyThemeIslandsOptions {
34
80
  /** Directories to scan for island files. Accepts paths or Vite aliases. Default: `['/frontend/js/islands/']` */
package/dist/index.js CHANGED
@@ -9,6 +9,7 @@ var runtimePath = fileURLToPath(new URL("./runtime.js", import.meta.url));
9
9
  var islandPath = fileURLToPath(new URL("./island.js", import.meta.url));
10
10
  var ISLAND_IMPORT_RE = /from\s+['"]vite-plugin-shopify-theme-islands\/island['"]/;
11
11
  var TS_JS_RE = /\.(ts|js)$/;
12
+ var SKIP_DIRS = new Set(["node_modules", "dist", "build", "public", "assets", ".cache"]);
12
13
  var defaults = {
13
14
  directories: ["/frontend/js/islands/"],
14
15
  directives: {
@@ -33,6 +34,23 @@ function resolveAliases(dirs, config) {
33
34
  return dir;
34
35
  });
35
36
  }
37
+ function collectTagNames(dir, names) {
38
+ let entries;
39
+ try {
40
+ entries = readdirSync(dir, { withFileTypes: true });
41
+ } catch {
42
+ return;
43
+ }
44
+ for (const entry of entries) {
45
+ if (entry.name.startsWith(".") || SKIP_DIRS.has(entry.name))
46
+ continue;
47
+ if (entry.isDirectory()) {
48
+ collectTagNames(join(dir, entry.name), names);
49
+ } else if (TS_JS_RE.test(entry.name)) {
50
+ names.push(entry.name.replace(/\.(ts|js)$/, ""));
51
+ }
52
+ }
53
+ }
36
54
  function scanForIslandFiles(dir, found) {
37
55
  let entries;
38
56
  try {
@@ -41,7 +59,7 @@ function scanForIslandFiles(dir, found) {
41
59
  return;
42
60
  }
43
61
  for (const entry of entries) {
44
- if (entry.name.startsWith(".") || entry.name === "node_modules")
62
+ if (entry.name.startsWith(".") || SKIP_DIRS.has(entry.name))
45
63
  continue;
46
64
  const full = join(dir, entry.name);
47
65
  if (entry.isDirectory()) {
@@ -63,46 +81,51 @@ function shopifyThemeIslands(options = {}) {
63
81
  media: { ...defaults.directives.media, ...options.directives?.media },
64
82
  defer: { ...defaults.directives.defer, ...options.directives?.defer }
65
83
  };
84
+ const clientDirectiveDefinitions = options.directives?.custom ?? [];
66
85
  const debug = options.debug ?? false;
67
- const log = (...args) => {
68
- if (debug)
69
- console.log("[islands]", ...args);
70
- };
86
+ const log = debug ? (...args) => console.log("[islands]", ...args) : () => {};
71
87
  let resolvedDirs = rawDirs;
72
88
  let root = process.cwd();
89
+ let absDirs = rawDirs;
73
90
  const islandFiles = new Set;
74
91
  let scanned = false;
75
- const inDirectory = (file) => resolvedDirs.some((dir) => {
76
- const absDir = dir.startsWith(root) ? dir : join(root, dir.replace(/^\//, ""));
77
- return file.startsWith(absDir);
78
- });
92
+ const inDirectory = (file) => absDirs.some((dir) => file.startsWith(dir));
79
93
  return {
80
94
  name: "vite-plugin-shopify-theme-islands",
81
95
  enforce: "pre",
82
96
  configResolved(config) {
83
97
  root = config.root;
84
98
  resolvedDirs = resolveAliases(rawDirs, config);
99
+ absDirs = resolvedDirs.map((d) => d.startsWith(root) ? d : join(root, d.replace(/^\//, "")));
85
100
  },
86
101
  buildStart() {
87
102
  if (scanned)
88
103
  return;
89
104
  scanned = true;
105
+ const t0 = performance.now();
90
106
  scanForIslandFiles(root, islandFiles);
107
+ const scanMs = (performance.now() - t0).toFixed(1);
91
108
  for (const f of islandFiles)
92
109
  if (inDirectory(f))
93
110
  islandFiles.delete(f);
94
111
  if (debug) {
112
+ log(`Scanned in ${scanMs}ms`);
95
113
  log("Scanning directories:", resolvedDirs.map((d) => d + "**/*.{ts,js}").join(", "));
96
- log("Directives:", directives);
114
+ const dirNames = [];
115
+ for (const dir of absDirs)
116
+ collectTagNames(dir, dirNames);
117
+ if (dirNames.length)
118
+ log(`Found ${dirNames.length} directory island(s): [${dirNames.join(", ")}]`);
97
119
  if (islandFiles.size) {
98
120
  log(`Found ${islandFiles.size} island file(s) via mixin import:`);
99
121
  for (const f of islandFiles)
100
122
  log(" ", relative(root, f));
101
123
  }
124
+ log("Directives:", directives);
102
125
  }
103
126
  },
104
127
  transform(code, id) {
105
- if (!id.endsWith(".ts") && !id.endsWith(".js"))
128
+ if (!TS_JS_RE.test(id))
106
129
  return;
107
130
  if (code.includes("shopify-theme-islands/island") && ISLAND_IMPORT_RE.test(code) && !inDirectory(id)) {
108
131
  islandFiles.add(id);
@@ -137,21 +160,40 @@ function shopifyThemeIslands(options = {}) {
137
160
  if (id === ISLAND_ID)
138
161
  return islandPath;
139
162
  },
140
- load(id) {
163
+ async load(id) {
141
164
  if (id !== RESOLVED_ID)
142
165
  return;
143
166
  const globs = resolvedDirs.map((dir) => `...import.meta.glob(${JSON.stringify(dir + "**/*.{ts,js}")})`);
144
- const islandPaths = [...islandFiles].map((file) => "/" + relative(root, file).replace(/\\/g, "/"));
145
- const islandsEntries = [
146
- globs.length ? `{ ${globs.join(", ")} }` : null,
147
- islandFiles.size ? `import.meta.glob(${JSON.stringify(islandPaths)})` : null
148
- ].filter(Boolean);
149
- return [
167
+ const islandPaths = islandFiles.size ? [...islandFiles].map((file) => "/" + relative(root, file).replace(/\\/g, "/")) : null;
168
+ const islandsEntries = [`{ ${globs.join(", ")} }`];
169
+ if (islandPaths)
170
+ islandsEntries.push(`import.meta.glob(${JSON.stringify(islandPaths)})`);
171
+ const directiveImports = [];
172
+ const mapEntries = [];
173
+ for (const [i, def] of clientDirectiveDefinitions.entries()) {
174
+ const resolved = await this.resolve(def.entrypoint);
175
+ if (!resolved) {
176
+ throw new Error(`[vite-plugin-shopify-theme-islands] Cannot resolve custom directive entrypoint: "${def.entrypoint}"`);
177
+ }
178
+ directiveImports.push(`import _directive${i} from ${JSON.stringify(resolved.id)};`);
179
+ mapEntries.push(` [${JSON.stringify(def.name)}, _directive${i}]`);
180
+ }
181
+ const lines = [
182
+ ...directiveImports,
150
183
  `import { revive as _islands } from ${JSON.stringify(runtimePath)};`,
151
184
  `const islands = Object.assign({}, ${islandsEntries.join(", ")});`,
152
- `const options = ${JSON.stringify({ directives, debug })};`,
153
- `_islands(islands, options);`
154
- ].join(`
185
+ `const options = ${JSON.stringify({ directives, debug })};`
186
+ ];
187
+ if (mapEntries.length) {
188
+ lines.push(`const customDirectives = new Map([
189
+ ${mapEntries.join(`,
190
+ `)}
191
+ ]);`);
192
+ lines.push(`_islands(islands, options, customDirectives);`);
193
+ } else {
194
+ lines.push(`_islands(islands, options);`);
195
+ }
196
+ return lines.join(`
155
197
  `);
156
198
  }
157
199
  };
package/dist/runtime.d.ts CHANGED
@@ -12,5 +12,5 @@
12
12
  * Directives can be combined; all conditions must be met before loading.
13
13
  * A MutationObserver re-runs the same logic for elements added dynamically.
14
14
  */
15
- import type { ReviveOptions } from "./index.js";
16
- export declare function revive(islands: Record<string, () => Promise<unknown>>, options?: ReviveOptions): void;
15
+ import type { ClientDirective, ReviveOptions } from "./index.js";
16
+ export declare function revive(islands: Record<string, () => Promise<unknown>>, options?: ReviveOptions, customDirectives?: Map<string, ClientDirective>): void;
package/dist/runtime.js CHANGED
@@ -41,7 +41,7 @@ function idle(timeout) {
41
41
  var customElementFilter = {
42
42
  acceptNode: (node) => node.tagName.includes("-") ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP
43
43
  };
44
- function revive(islands, options) {
44
+ function revive(islands, options, customDirectives) {
45
45
  const attrVisible = options?.directives?.visible?.attribute ?? "client:visible";
46
46
  const attrMedia = options?.directives?.media?.attribute ?? "client:media";
47
47
  const attrIdle = options?.directives?.idle?.attribute ?? "client:idle";
@@ -50,7 +50,8 @@ function revive(islands, options) {
50
50
  const threshold = options?.directives?.visible?.threshold ?? 0;
51
51
  const idleTimeout = options?.directives?.idle?.timeout ?? 500;
52
52
  const deferDelay = options?.directives?.defer?.delay ?? 3000;
53
- const log = options?.debug ? (...args) => console.debug("[islands]", ...args) : () => {};
53
+ const debug = options?.debug ?? false;
54
+ const log = debug ? (...args) => console.log("[islands]", ...args) : () => {};
54
55
  const islandMap = new Map;
55
56
  for (const [key, loader] of Object.entries(islands)) {
56
57
  const tagName = key.split("/").pop().replace(/\.(ts|js)$/, "");
@@ -61,44 +62,79 @@ function revive(islands, options) {
61
62
  if (!islandMap.has(tagName))
62
63
  islandMap.set(tagName, loader);
63
64
  }
65
+ log(`revive() ready — ${islandMap.size} island(s)`);
64
66
  const queued = new Set;
65
67
  const pendingVisible = new Map;
66
68
  async function loadIsland(tagName, el, loader) {
67
69
  log(`<${tagName}> activating`);
70
+ const msgs = [];
71
+ const note = debug ? (msg) => {
72
+ msgs.push(msg);
73
+ } : () => {};
74
+ const flush = debug ? (final) => {
75
+ if (msgs.length === 0) {
76
+ console.log("[islands]", `<${tagName}> ${final}`);
77
+ } else {
78
+ console.groupCollapsed(`[islands] <${tagName}>`);
79
+ for (const m of msgs)
80
+ console.log(m);
81
+ console.log(final);
82
+ console.groupEnd();
83
+ }
84
+ } : () => {};
68
85
  try {
69
86
  if (el.hasAttribute(attrVisible)) {
70
- log(`<${tagName}> waiting for ${attrVisible}`);
71
- await visible(el, rootMargin, threshold, pendingVisible);
72
- log(`<${tagName}> ${attrVisible} resolved`);
87
+ const elRootMargin = el.getAttribute(attrVisible) || rootMargin;
88
+ note(`waiting for ${attrVisible}`);
89
+ await visible(el, elRootMargin, threshold, pendingVisible);
73
90
  }
74
- const q = el.getAttribute(attrMedia);
75
- if (q) {
76
- log(`<${tagName}> waiting for ${attrMedia}="${q}"`);
77
- await media(q);
78
- log(`<${tagName}> ${attrMedia} resolved`);
91
+ const query = el.getAttribute(attrMedia);
92
+ if (query) {
93
+ note(`waiting for ${attrMedia}="${query}"`);
94
+ await media(query);
79
95
  }
80
96
  if (el.hasAttribute(attrIdle)) {
81
- log(`<${tagName}> waiting for ${attrIdle} (timeout: ${idleTimeout}ms)`);
82
- await idle(idleTimeout);
83
- log(`<${tagName}> ${attrIdle} resolved`);
97
+ const rawIdle = parseInt(el.getAttribute(attrIdle), 10);
98
+ const elTimeout = Number.isNaN(rawIdle) ? idleTimeout : rawIdle;
99
+ note(`waiting for ${attrIdle} (timeout: ${elTimeout}ms)`);
100
+ await idle(elTimeout);
84
101
  }
85
102
  const d = el.getAttribute(attrDefer);
86
103
  if (d !== null) {
87
104
  const raw = parseInt(d, 10);
88
- const ms = d === "" || Number.isNaN(raw) ? deferDelay : raw;
105
+ const ms = Number.isNaN(raw) ? deferDelay : raw;
89
106
  if (d !== "" && Number.isNaN(raw)) {
90
107
  console.warn(`[islands] <${tagName}> invalid ${attrDefer} value "${d}" — using default ${deferDelay}ms`);
91
108
  }
92
- log(`<${tagName}> waiting for ${attrDefer} (${ms}ms)`);
109
+ note(`waiting for ${attrDefer} (${ms}ms)`);
93
110
  await defer(ms);
94
- log(`<${tagName}> ${attrDefer} resolved`);
95
111
  }
96
112
  } catch {
97
- log(`<${tagName}> aborted (element removed)`);
113
+ flush("aborted (element removed)");
98
114
  return;
99
115
  }
100
- log(`<${tagName}> loading`);
101
- loader().catch((err) => console.error(`[islands] Failed to load <${tagName}>:`, err));
116
+ const run = () => loader().catch((err) => {
117
+ console.error(`[islands] Failed to load <${tagName}>:`, err);
118
+ queued.delete(tagName);
119
+ });
120
+ if (customDirectives?.size) {
121
+ const matched = [];
122
+ for (const [attrName, directiveFn] of customDirectives) {
123
+ if (el.hasAttribute(attrName))
124
+ matched.push([attrName, directiveFn]);
125
+ }
126
+ if (matched.length > 1) {
127
+ console.warn(`[islands] <${tagName}> has multiple custom directives (${matched.map(([a]) => a).join(", ")}) — only "${matched[0][0]}" will be used. Combining custom directives is not yet supported.`);
128
+ }
129
+ if (matched.length > 0) {
130
+ const [attrName, directiveFn] = matched[0];
131
+ flush(`dispatching to custom directive ${attrName}`);
132
+ directiveFn(run, { name: attrName, value: el.getAttribute(attrName) }, el);
133
+ return;
134
+ }
135
+ }
136
+ flush("triggered");
137
+ run();
102
138
  }
103
139
  function activate(el) {
104
140
  const tagName = el.tagName.toLowerCase();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vite-plugin-shopify-theme-islands",
3
- "version": "0.5.0",
3
+ "version": "0.6.1",
4
4
  "description": "Vite plugin for island architecture in Shopify themes",
5
5
  "type": "module",
6
6
  "packageManager": "bun@1.3.10",
@@ -58,7 +58,9 @@
58
58
  "test:plugin": "bun test src/__tests__/plugin.test.ts",
59
59
  "test:runtime": "bun test ./src/__tests__/runtime.test.ts",
60
60
  "test": "bun test",
61
- "test:watch": "bun test --watch"
61
+ "test:watch": "bun test --watch",
62
+ "lint": "oxlint src/",
63
+ "format": "oxfmt src/"
62
64
  },
63
65
  "peerDependencies": {
64
66
  "vite": ">=6"
@@ -67,6 +69,8 @@
67
69
  "@happy-dom/global-registrator": "^20.8.3",
68
70
  "@types/bun": "latest",
69
71
  "@types/node": "^25.3.3",
72
+ "oxfmt": "^0.38.0",
73
+ "oxlint": "^1.53.0",
70
74
  "typescript": "^5.0.0",
71
75
  "vite": "^6.0.0"
72
76
  }