11ty / is-land

A new performance-focused way to add interactive client-side components to your web site.
https://is-land.11ty.dev/
MIT License
528 stars 17 forks source link

<is-land>

A new performance-focused way to add interactive client-side components to your web site.

Or, more technically: a framework independent partial hydration islands architecture implementation.

Features:

Examples for:

Integrations in the wild:

Installation

npm install @11ty/is-land

Add is-land.js to your primary bundle. It can be deferred and/or loaded asynchronously.

When using with web components it must be the first custom element defined (via customElements.define) on the page. Choose your style:

<script type="module" src="https://github.com/11ty/is-land/raw/main/is-land.js"></script>
<script type="module">
import "/is-land.js";
</script>

The framework autoinit examples shown below now require a separate is-land-autoinit.js file too.

Usage

<is-land>This is an island.</is-land>

Add any number of loading conditions to this tag to control how and when the island is initialized. You can mix and match:

<is-land on:visible on:idle>
  <!-- your HTML here -->

  <is-land on:media="(min-width: 64em)">
    <!-- Islands can be nested -->
    <!-- Islands inherit all of their parents’ loading conditions -->
  </is-land>
</is-land>

Controlling Fallback Content

Pre-JS

<is-land on:visible on:idle>
  <vanilla-web-component>
    Put your pre-JS fallback content in your web component.
  </vanilla-web-component>
</is-land>

Post-JS with <template>

Place any post-JS content inside of one or more <template data-island> elements anywhere in the <is-land>. These will be swapped with their template content. You can nest an <is-land> in there if you want!

<is-land on:visible on:idle>
  <template data-island>
    <vanilla-web-component>
      This component is post-JS.
    </vanilla-web-component>
  </template>
</is-land>

Run your own custom JavaScript, load your own CSS

Embed a script inside the template to run custom JS when the island’s loading conditions have been satisfied!

<is-land on:visible>
  <template data-island>
    <!-- CSS -->
    <style>/* My custom CSS */</style>
    <link rel="stylesheet" href="https://github.com/11ty/is-land/blob/main/my-css-file.css">

    <!-- JS -->
    <script type="module">console.log("Hydrating!");</script>
    <script type="module" src="https://github.com/11ty/is-land/raw/main/my-js-file.js"></script>
  </template>
</is-land>

You can also use the ready attribute for styling, added to the <is-land> when the island has been hydrated.

<style>
is-land[ready] {
  background-color: lightgreen;
}
</style>

Framework Support

Demos and source in the HTML are available for each framework listed here. These examples require the is-land-autoinit.js JavaScript file (in addition to is-land.js).

autoinit

Use the autoinit and import attributes together to import a third party library (or component code). autoinit can be one of petite-vue, vue, preact, or svelte. It is recommended to use a self-hosted framework library (future Eleventy integrations will automate this for you).

<is-land on:visible autoinit="petite-vue" import="https://unpkg.com/petite-vue@0.4.1/dist/petite-vue.es.js" v-scope="{ name: 'post-JS' }">
  Hello from <span v-html="name">pre-JS</span>
</is-land>

<!-- when import maps support is better, this simplifies with an entry for petite-vue in your import map -->
<is-land on:visible import="petite-vue" v-scope="{ name: 'post-JS' }">
  Hello from <span v-html="name">pre-JS</span>
</is-land>

Petite Vue

<is-land on:visible autoinit="petite-vue" import="https://unpkg.com/petite-vue@0.4.1/dist/petite-vue.es.js" v-scope="{ name: 'post-JS' }">
  Hello from <span v-html="name">pre-JS</span>
</is-land>

Vue

<is-land on:visible>
  <div id="vue-app">
    Hello from <span v-html="name">pre-JS</span>
  </div>

  <template data-island>
    <script type="module">
    import { createApp } from "https://unpkg.com/vue@3.2.36/dist/vue.esm-browser.prod.js";

    createApp({
      data: () => ({ name: "post-JS" })
    }).mount("#vue-app")
    </script>
  </template>
</is-land>

Svelte

This example uses an Eleventy/Svelte integration to compile a Svelte component.

{% assign component = "./lib/svelte/my-component.svelte" | svelte %}
<is-land on:visible autoinit="svelte" import="{{ component.clientJsUrl }}"></is-land>
Example component code ./lib/svelte/my-component.svelte: ```html

Hello {name}

```

Preact

This example uses htm instead of JSX.

<is-land on:visible autoinit="preact" import="/lib/preact/preact-component.js"></is-land>
Example component code ./lib/preact/preact-component.js: ```js import { html, render } from 'https://unpkg.com/htm/preact/index.mjs?module' function App (props) { return html`

Hello ${props.name}!

`; } export default function(el) { render(html`<${App} name="from Preact" />`, el); } ```

Lit

<is-land on:visible import="./lib/lit/lit-component.js">
  <lit-component name="Post-JS">Pre-JS Content</lit-web-component>
</is-land>
Example component code ./lib/lit/lit-component.js: ```js import {html, css, LitElement} from "https://cdn.jsdelivr.net/gh/lit/dist@2/core/lit-core.min.js"; customElements.define('lit-component', class extends LitElement { static properties = { name: {type: String}, }; render() { return html`

Hello, ${this.name || "Stranger"}!

`; } }); ```

Alpine.js

<is-land on:visible import="https://unpkg.com/alpinejs">
  <div x-data="{ count: 0 }">
    Hello from Alpine.js!

    <button @click="count++">⬆️</button> <button @click="count--">⬇️</button> <span x-text="count"></span>
  </div>
</is-land>