vite-plugin-shopify-theme-islands
Version:
Vite plugin for island architecture in Shopify themes
188 lines (133 loc) • 5.87 kB
Markdown
---
name: lifecycle
description: >
Island lifecycle events and SPA teardown. onIslandLoad and onIslandError
helpers from vite-plugin-shopify-theme-islands/events — prefer these over
raw document.addEventListener for guaranteed type safety. Raw DOM events
islands:load and islands:error on document. islands:load detail includes tag,
duration (ms), and attempt (1-based). islands:error detail includes tag,
error, and attempt. disconnect() from the virtual module revive for SPA
navigation teardown.
type: core
library: vite-plugin-shopify-theme-islands
library_version: "1.1.0"
sources:
- Rees1993/vite-plugin-shopify-theme-islands:src/events.ts
- Rees1993/vite-plugin-shopify-theme-islands:src/index.ts
- Rees1993/vite-plugin-shopify-theme-islands:src/runtime.ts
---
## Setup
```ts
import { onIslandLoad, onIslandError } from "vite-plugin-shopify-theme-islands/events";
const offLoad = onIslandLoad(({ tag, duration, attempt }) => {
console.log("loaded:", tag, `${duration.toFixed(1)}ms`, `attempt ${attempt}`);
});
const offError = onIslandError(({ tag, error, attempt }) => {
console.error("failed:", tag, `attempt ${attempt}`, error);
});
// Remove listeners when no longer needed
offLoad();
offError();
```
## Core Patterns
### Track island load for analytics
```ts
import { onIslandLoad } from "vite-plugin-shopify-theme-islands/events";
onIslandLoad(({ tag, duration, attempt }) => {
analytics.track("island_loaded", { component: tag, duration, attempt });
});
```
`tag` is the lowercased custom element tag name (e.g. `"product-form"`). `duration` is the time in milliseconds from when all directives resolved to when the module finished loading. `attempt` is 1 on the first successful load, 2 if it succeeded on the first retry, etc.
### Track load performance
```ts
import { onIslandLoad } from "vite-plugin-shopify-theme-islands/events";
onIslandLoad(({ tag, duration }) => {
if (duration > 3000) {
performance.mark(`island-slow:${tag}`);
}
});
```
`duration` measures only the chunk fetch time — time spent waiting on directives (e.g. `client:visible`) is not included.
### Report errors to a monitoring service
```ts
import { onIslandError } from "vite-plugin-shopify-theme-islands/events";
onIslandError(({ tag, error, attempt }) => {
Sentry.captureException(error, { extra: { island: tag, attempt } });
});
```
`onIslandError` fires on each retry attempt and on custom directive failures. `attempt` tells you which attempt failed — 1 is the initial load, 2 is the first retry, etc.
### Teardown for SPA navigation
```ts
import { disconnect } from "vite-plugin-shopify-theme-islands/revive";
// Before navigating away / unmounting the page
disconnect();
```
`disconnect()` stops the MutationObserver and prevents new islands from activating. Call it before SPA page transitions to avoid activating islands from the previous page's DOM.
### Raw DOM events (when type augmentation is in scope)
```ts
// DocumentEventMap augmentation is exported from the main package
import type {} from "vite-plugin-shopify-theme-islands";
document.addEventListener("islands:load", (e) => {
console.log(e.detail.tag, e.detail.duration, e.detail.attempt);
});
```
The `DocumentEventMap` augmentation is declared in the main package's `index.ts`. It is only in scope when the import is present in the same tsconfig compilation.
## Common Mistakes
### HIGH Raw `addEventListener` without types — `e.detail` is untyped
Wrong:
```ts
// No import from the package — e is Event, detail is unknown
document.addEventListener("islands:load", (e) => {
console.log(e.detail.tag); // TypeScript error or any
});
```
Correct:
```ts
import { onIslandLoad } from "vite-plugin-shopify-theme-islands/events";
onIslandLoad(({ tag }) => {
console.log(tag); // string, always typed
});
```
`onIslandLoad` and `onIslandError` are typed unconditionally regardless of tsconfig setup. Use them instead of raw `document.addEventListener` unless the `DocumentEventMap` augmentation is confirmed to be in scope.
Source: src/events.ts
### CRITICAL `disconnect` imported from wrong entry point
Wrong:
```ts
import { disconnect } from "vite-plugin-shopify-theme-islands/runtime";
import { disconnect } from "vite-plugin-shopify-theme-islands/island";
```
Correct:
```ts
import { disconnect } from "vite-plugin-shopify-theme-islands/revive";
```
Only the virtual module (`/revive`) exports the `disconnect` bound to the plugin-managed `revive()` instance. Importing from other entry points references a different or nonexistent instance.
Source: src/index.ts — virtual module `export const { disconnect } = _islands(...)`
### MEDIUM `onIslandError` fires on every retry, not just final failure
Wrong:
```ts
onIslandError(({ tag }) => {
// Assuming this fires once when the island permanently fails
markIslandBroken(tag);
});
```
Correct:
```ts
onIslandError(({ tag, error, attempt }) => {
// attempt === 1 is the first failure; higher values are retries
if (attempt === 1) {
reportFirstFailure(tag, error);
}
});
```
With `retry: { retries: 3 }`, a single island can fire `islands:error` up to 4 times before exhausting retries. Use `attempt` to distinguish the initial failure from retries.
Source: src/runtime.ts — `dispatch("islands:error", ...)` inside `.catch()` before retry check
### MEDIUM `islands:error` fires for custom directive failures too
Wrong assumption:
```ts
onIslandError(({ tag, error }) => {
// Assuming this only fires for failed dynamic import()
reportChunkLoadFailure(tag);
});
```
`islands:error` fires when any custom directive throws or rejects, not only when the island module's `import()` fails. The `error` value may be a directive error rather than a network or chunk error.
Source: src/runtime.ts — handleDirectiveError dispatches `islands:error`