UNPKG

vite-plugin-shopify-theme-islands

Version:
380 lines (283 loc) 10.5 kB
--- name: setup description: > Getting-started journey and plugin configuration. Covers the full path from install to first working island. Shopify-first setup: shopifyThemeIslands() options include directories (string | string[]), tagSource ("registeredTag" default — Tag derived from static customElements.define("...", ...) call; "filename" for v1.x compatibility), resolveTag({ filePath, defaultTag }) with unique final-tag requirements (defaultTag is the Registered Tag in registeredTag mode, filename-derived in filename mode), debug, directives deep-merge (visible, idle, media, defer, interaction, custom), retry (retries, delay with exponential backoff), directiveTimeout for hung custom directives, and the curated interaction-event config policy (`mouseenter`, `touchstart`, `focusin`; empty arrays rejected). Per-element `client:interaction` values are runtime-validated against the same curated set: unsupported tokens warn and are ignored; if no supported tokens remain, the runtime falls back to the configured default events. type: core library: vite-plugin-shopify-theme-islands library_version: "2.0.0" sources: - Rees1993/vite-plugin-shopify-theme-islands:src/index.ts - Rees1993/vite-plugin-shopify-theme-islands:src/contract.ts - Rees1993/vite-plugin-shopify-theme-islands:src/options.ts - Rees1993/vite-plugin-shopify-theme-islands:src/resolved-config.ts - Rees1993/vite-plugin-shopify-theme-islands:src/revive-compile.ts - Rees1993/vite-plugin-shopify-theme-islands:src/revive-module.ts - Rees1993/vite-plugin-shopify-theme-islands:src/interaction-events.ts --- ## Setup This plugin is Shopify-first and built for Liquid themes using custom elements. Most Shopify projects also use [vite-plugin-shopify](https://github.com/barrel/vite-plugin-shopify) to handle Shopify-specific asset serving if the project uses it, add this plugin alongside it in the existing `plugins` array. The package targets **Node.js 22+** and declares **Vite 6+** as a peer dependency. ### 1. Add the plugin to `vite.config.ts` ```ts // vite.config.ts import { defineConfig } from "vite"; import shopifyThemeIslands from "vite-plugin-shopify-theme-islands"; export default defineConfig({ plugins: [shopifyThemeIslands()], }); ``` All options are optional. The default islands directory is `/frontend/js/islands/`. ### 2. Import the virtual module in the theme JS entry point ```ts // frontend/js/theme.ts import "vite-plugin-shopify-theme-islands/revive"; ``` This activates the runtime islands are never loaded without this import. The same `/revive` module also exports `scan()`, `observe()`, `unobserve()`, and `disconnect()` for partial swaps and teardown. If `disconnect()` is called before `DOMContentLoaded`, the runtime cancels its pending startup listener so islands never initialize later against stale DOM. `/revive` is a shared page-level singleton, so later named imports reuse the same runtime instance instead of creating a second one. ### 3. Add directives to Liquid templates ```html <!-- sections/product.liquid --> <product-form client:visible></product-form> ``` That's a working setup. Any discovered Island whose effective Tag matches `<product-form>` is loaded lazily when the directive condition is met. In the default `registeredTag` mode, that Tag comes from the file's static `customElements.define("...", ...)` call; `tagSource: "filename"` restores the v1.x filename-based lookup. ## Core Patterns ### Configure multiple island directories ```ts shopifyThemeIslands({ directories: ["/frontend/js/islands/", "/frontend/js/components/"], }); ``` ### Override the derived Tag ```ts shopifyThemeIslands({ resolveTag({ filePath, defaultTag }) { if (filePath.endsWith("/legacy/widget.ts")) return "legacy-widget"; if (filePath.endsWith("/skip-me.ts")) return false; return defaultTag; }, }); ``` Use `resolveTag()` to override Tag derivation for specific files. In the default `registeredTag` mode, `defaultTag` is the Tag read from the file's static `customElements.define("...", ...)` call. Returning `false` excludes the file from the island map. Returning `defaultTag` keeps the default. If two different source files resolve to the same Tag, plugin compilation fails. Rename, adjust `resolveTag`, or return `false` to exclude one file. ### Override built-in directive defaults ```ts shopifyThemeIslands({ directives: { visible: { rootMargin: "0px", threshold: 0.5 }, idle: { timeout: 2000 }, defer: { delay: 5000 }, interaction: { events: ["mouseenter"] }, }, }); ``` Per-directive options are deep-merged overriding `visible.rootMargin` preserves `visible.threshold` at its default of `0`. For config, `directives.interaction.events` is intentionally narrow and only accepts `mouseenter`, `touchstart`, and `focusin`. Per-element `client:interaction="..."` values are checked at runtime against that same set. Unsupported tokens warn and are ignored; if all tokens are unsupported, the runtime warns and falls back to the configured default events. Per-element `client:idle` and `client:defer` values now require strict integer strings. Invalid values warn and fall back to the configured default timeout or delay. ### Enable automatic retry with exponential backoff ```ts shopifyThemeIslands({ retry: { retries: 3, delay: 1000 }, }); ``` `retries` is the number of attempts after the first failure. `delay` is the base ms each subsequent retry doubles it (1000ms 2000ms 4000ms). ### Guard against hung custom directives ```ts shopifyThemeIslands({ directiveTimeout: 5000, }); ``` When a custom directive never calls `load()`, the runtime normally waits forever. `directiveTimeout` turns that into an `islands:error` event and abandons the activation attempt after the configured number of milliseconds. ### Enable console debug output ```ts shopifyThemeIslands({ debug: true }); ``` Logs discovered islands, active directives per element, and load/error events at startup. ## Common Mistakes ### CRITICAL Virtual module not imported — islands never activate Wrong: ```ts // vite.config.ts plugin configured but virtual module never imported shopifyThemeIslands({ directories: ["/frontend/js/islands/"] }); ``` Correct: ```ts // frontend/js/theme.ts import "vite-plugin-shopify-theme-islands/revive"; ``` The plugin generates the virtual module but has no effect until it is imported in the browser entry point. Islands are silently never activated. Source: src/index.ts VIRTUAL_ID / RESOLVED_ID ### HIGH Agent hardcodes default values — unnecessary noise Wrong: ```ts shopifyThemeIslands({ directories: ["/frontend/js/islands/"], debug: false, directives: { visible: { attribute: "client:visible", rootMargin: "200px", threshold: 0 }, idle: { attribute: "client:idle", timeout: 500 }, media: { attribute: "client:media" }, defer: { attribute: "client:defer", delay: 3000 }, interaction: { attribute: "client:interaction", events: ["mouseenter", "touchstart", "focusin"] }, }, }); ``` Correct: ```ts shopifyThemeIslands(); ``` All options are optional and default to sensible values. Only include options that differ from the defaults. ### HIGH Agent overwrites existing `vite.config.ts` instead of appending Before adding the plugin, read the existing `vite.config.ts`. Projects commonly already have `vite-plugin-shopify` or other plugins the island plugin must be added to the existing `plugins` array, not replace it. Wrong: ```ts // Replaces existing plugins export default defineConfig({ plugins: [shopifyThemeIslands()], }); ``` Correct: ```ts // Appends to existing plugins export default defineConfig({ plugins: [ shopify(), // pre-existing plugin preserved shopifyThemeIslands(), ], }); ``` ### HIGH `retry` nested inside `directives` — no retries happen Wrong: ```ts shopifyThemeIslands({ directives: { retry: { retries: 2 }, // wrong nesting }, }); ``` Correct: ```ts shopifyThemeIslands({ retry: { retries: 2 }, // top-level option }); ``` `directives` accepts only `visible`, `idle`, `media`, `defer`, `interaction`, and `custom`. `retry` at `directives.retry` is silently ignored. Source: src/options.ts ShopifyThemeIslandsOptions ### HIGH Wrong key name for retry count Wrong: ```ts shopifyThemeIslands({ retry: { count: 3 } }); shopifyThemeIslands({ retry: { attempts: 3 } }); ``` Correct: ```ts shopifyThemeIslands({ retry: { retries: 3 } }); ``` Unknown keys are silently ignored. The correct field is `retries`. Source: src/contract.ts RetryConfig ### HIGH `directiveTimeout` nested inside `directives` — timeout guard never applies Wrong: ```ts shopifyThemeIslands({ directives: { directiveTimeout: 5000, }, }); ``` Correct: ```ts shopifyThemeIslands({ directiveTimeout: 5000, }); ``` `directiveTimeout` is a top-level plugin option, not part of the per-directive config object. Source: src/options.ts ShopifyThemeIslandsOptions ### HIGH Empty or unsupported `directives.interaction.events` values fail config resolution Wrong: ```ts shopifyThemeIslands({ directives: { interaction: { events: [] }, }, }); shopifyThemeIslands({ directives: { interaction: { events: ["click"] as never[] }, }, }); ``` Correct: ```ts shopifyThemeIslands({ directives: { interaction: { events: ["mouseenter", "focusin"] }, }, }); ``` The typed config surface only supports the package-owned interaction events `mouseenter`, `touchstart`, and `focusin`. An empty array is rejected because it would otherwise create an interaction gate that never resolves. Source: src/interaction-events.ts validateInteractionEvents() ### HIGH `directories: []` fails plugin validation Wrong: ```ts shopifyThemeIslands({ directories: [] }); ``` Correct: ```ts shopifyThemeIslands(); // or at least one path: shopifyThemeIslands({ directories: ["/frontend/js/islands/"] }); ``` An empty `directories` array is rejected at config resolution. Source: src/resolved-config.ts validateOptions() ### HIGH `directives.visible.threshold` outside 0–1 fails plugin validation Wrong: ```ts shopifyThemeIslands({ directives: { visible: { threshold: 1.5 }, }, }); ``` Correct: ```ts shopifyThemeIslands({ directives: { visible: { threshold: 0.5 }, }, }); ``` `threshold` must be between `0` and `1` inclusive. Source: src/resolved-config.ts validateOptions()