A 1.3kB module that helps you ship your own slice of paradise to any website

Last update: Jun 12, 2022

🏝 Preact Island

A 1.3kB module that helps you ship your own slice of paradise to any website. Especially useful for Shopify apps or CMS websites.

downloads version Supports Preact and React MIT License

Sometimes you need to embed a component onto someone else's website. This could be a Shopify widget, email sign up form, CMS comment list, social media share icon, etc. Creating these experiences are tedious and difficult because you aren't in control of the website your code will be executed on.

Preact Island helps you build these experiences by adding a lightweight layer on top of Preact. For <5kB, you get a React style workflow (with hooks!), and a framework for rendering your widget with reactive props.

Features

  • 🚀 Render by selector, inline, or by a specific attribute given on the executed script
  • ⚛️ Based on Preact, no special compiler or anything needed to render an island
  • 🙏 5 ways to pass in props to your component
  • 🪄 All components are reactive to prop changes causing rerenders (not remounts)
  • 👯‍♀️ Create as many instances of your component as you need with a single island
  • 🧼 Does not mutate the window. Use as many islands as you'd like on one page!
  • 🐣 Less than 1.3kB
  • ☠️ Supports replacing the target selector
  • 🏔 React friendly with preact/compat
  • 🔧 Manually trigger rerenders with props
  • 🐙 Fully tested with Preact testing library
  • 👔 Fully typed with TypeScript

Examples

Installation

npm install --save preact-island

Usage

import { h } from 'preact'
import { createIsland } from 'preact-island'

const Widget = () => {
  return <div>awesome widget!</div>
}

const island = createIsland(Widget)
island.render({
  selector: '[data-island="widget"]',
})

API

createIsland

Creates a new island instance with a passed in component. Returns a bag of props/methods to work with your island.

import { createIsland } from 'preact-island'

const Widget = () => {
  return <div>awesome widget!</div>
}

const island = createIsland(Widget)

createIsland().render

Renders an island to the DOM given options.

const island = createIsland(Widget)
island.render({...})

options

*
some other content
*
some other content
*
some other content
*
* ``` * * // turns into * * ```html *
*
your-widget
*
* ``` * * // turns into * * ```html *
your-widget
* ``` */ replace?: boolean /** * Renders the widget at the current position of the script in the HTML document. * * @default false * * @example * ```html *
*
some content here
* *
some content here
*
* ``` * * // turns into * * ```html *
*
some content here
* *
your widget
*
some content here
*
* ``` */ inline?: boolean /** * Initial props to pass to the component. These props do not cause updates to the island if changed. Use `createIsland().rerender` instead. */ initialProps?: P /** * A valid selector to a script tag located in the HTML document with a type of either `text/props` or `application/json` * containing props to pass into the component. If there are multiple scripts found with the selector, all props are merged with * the last script found taking priority. */ propsSelector?: string">
  /**
   * A query selector target to create the widget. This is ignored if inline is passed or if a `data-mount-in` attribute
   * is appended onto the executed script.
   *
   * @example '[data-island="widget"]'
   */
  selector?: string
  /**
   * If true, removes all children of the element before rendering the component.
   *
   * @default false
   *
   * @example
   * ```html
   * 
*
some other content
*
some other content
*
some other content
*
* ``` * * // turns into * * ```html *
*
your-widget
* * ``` */ clean?: boolean /** * If true, replaces the contents of the selector with the component given. If you use replace, * you will not be able to add props to the host element (since it will be replaced). You will also * not be able to use child props script either (since they will be replaced). * * Use script tag props or a props selector for handling props when in replace mode. * * @default false * * @example * ```html *
* ``` * * // turns into * * ```html *
your-widget
* ``` */ replace?: boolean /** * Renders the widget at the current position of the script in the HTML document. * * @default false * * @example * ```html *
*
some content here
* *
some content here
*
* ``` * * // turns into * * ```html *
*
some content here
* *
your widget
*
some content here
*
* ``` */ inline?: boolean /** * Initial props to pass to the component. These props do not cause updates to the island if changed. Use `createIsland().rerender` instead. */ initialProps?: P /** * A valid selector to a script tag located in the HTML document with a type of either `text/props` or `application/json` * containing props to pass into the component. If there are multiple scripts found with the selector, all props are merged with * the last script found taking priority. */ propsSelector?: string

createIsland().rerender

Triggers a rerenders of the island with the new props given.

const island = createIsland(Widget)
island.render({ selector: '[data-island="widget"]' })
island.rerender({ new: 'props' })

createIsland().destroy

Destroys all instances of the island on the page and disconnects any associated observers.

const island = createIsland(Widget)
island.render({ selector: '[data-island="widget"]' })
island.destroy()

Selecting Mount Point from Script

You can override the selector given to render by passing data-mount-in to the script.

Example

Special mount

">
<div data-island="pokemon">
  <script type="text/props">
    {"pokemon": "3"}
  script>
div>
<h2>Special mounth2>



<div data-island="mount-here-actually">div>

<script
  async
  data-mount-in='[data-island="mount-here-actually"]'
  src="https://preact-island.netlify.app/islands/pokemon.island.umd.js"
>script>

Passing Props

Props can be passed to your widget various ways. You can choose to pass props multiple different ways to your island where they'll be merged in a defined order.

Merging Order

Props are merged in the following order (from lowest to highest specificity):

  1. Initial props
  2. Element props
  3. Executed script props
  4. Props selector props
  5. Interior script props

Data Props

All props located on an HTML element use data-. You can name them any of the following ways:

  • data-background-color => backgroundColor
  • data-prop-background-color => backgroundColor
  • data-props-background-color => backgroundColor

Under the hood all of these element will be transformed to camelCase and passed to your component.

Initial Props

Default props can be passed on render to an island. You can render many islands and give them all different initial props. Initial props do not cause rerenders if updated. Use createIsland.rerender instead.

import { h } from 'preact'
import { createIsland } from 'preact-island'

const Widget = () => {
  return <div>awesome widget!</div>
}

const island = createIsland(Widget)

island.render({
  selector: '[data-island="widget"]',
  initialProps: {
    color: '#000000',
  },
})

island.render({
  selector: '[data-island="widget"]',
  initialProps: {
    color: '#ffffff',
  },
})

// Will render two instances of the island with different color props

Element Props

Props can be placed on host elements and passed to your component. These props are reactive and will cause rerenders on changes.

Example

">
<div data-island="pokemon" data-pokemon="130">div>
<script
  async
  src="https://preact-island.netlify.app/islands/pokemon.island.umd.js"
>script>

Executed Script Props

Props can be placed on the script tag that's evaluated to the create your island. These props are reactive and will cause rerenders on changes.

Example

">
<div data-island="pokemon">div>
<script
  data-pokemon="130"
  async
  src="https://preact-island.netlify.app/islands/pokemon.island.umd.js"
>script>

Props Selector Props

Props can be placed inside of a ">

<div data-island="pokemon">div>


<script data-island-props="test-island" type="text/props">
  {"pokemon": "3"}
script>

<script
  async
  src="https://preact-island.netlify.app/islands/pokemon.props-selector.island.umd.js"
>script>

Interior Script Props

Props can be placed inside of a

">
<div data-island="pokemon">
  <script type="text/props">
    {"pokemon": "3"}
  script>
div>

<script
  async
  src="https://preact-island.netlify.app/islands/pokemon.island.umd.js"
>script>

React Compatibility

Preact Island fully supports React using preact/compat. This allows you to bring your existing React components over to Preact to get great performance gains without needing to rewrite your components. Check out the example-react folder to a demo repo that reproduces some of the Preact islands as React islands.

Depending on what you import from React, using Preact + Preact Island can result in a 15x smaller bundle for the same functionality and no code changes needed on your end.

Bundle Sizes

React

react only

React + Preact/compat

react with preact/compat

Preact

preact

Adding Styles

You can add styles to your island just like any other component. If you're island will be running on someone else's website be mindful of the global CSS scope! Preact Island does not render into the shadow dom (yet) so your styles will impact the entire page. Prefix all of your classes with a name island__ or use CSS modules to make sure those styles don't leak. Do not use element selectors like p or h2.

Including Styles

Preact Island takes no opinions on how CSS is included for your islands. There are two main options:

Inline the CSS into the bundle

This is what the /example islands do.

Join our newsletter

{/* ... */}
) } const island = createIsland(Widget) island.render({ selector: '[data-island="email-subscribe"]', })">
import { createIsland } from '../../dist/index.module'
import { h } from 'preact'
import style from './email-subscribe.island.css'

document.head.insertAdjacentHTML('beforeend', ``)

const Widget = () => {
  return (
    <div className="email__container">
      <p className="email__title">Join our newsletter</p>
      {/* ... */}
    </div>
  )
}

const island = createIsland(Widget)
island.render({
  selector: '[data-island="email-subscribe"]',
})
">
<script
  src="https://your-domain/snippets/fancy-widget.island.umd.js"
  async
>script>

Pros:

Cons:

Use an external stylesheet

">

<link
  href="https://your-domain/snippets/fancy-widget.island.css"
  rel="stylesheet"
/>
<script
  src="https://your-domain/snippets/fancy-widget.island.umd.js"
  async
>script>

Pros:

Cons:

CSS Libraries

It's not recommending to use CSS libraries when developing islands since they're meant to be small and ran everywhere. Some libraries come with opinionated CSS resets and other global CSS styles that could break the consuming website of your island. They are also going to be large.

If you need a CSS library, use something that has a just in time compilation step like Tailwind to minimize the excess CSS.

Building Your Islands

Any modern bundler will work with Preact Island. If you are looking for a script that will run on a webpage you need the UMD format. The /example project has a demo setup using microbundle, a bundler by the same author as Preact. It works extremely well if you have multiple islands because it can produce multi-entry point bundles.

Naming Conventions

Make sure to name your bundles kebab-case since they'll be served over HTTP. Case sensitive URLs can be fiddly depending on browser!

Hosting Your Islands

You can host your files on anywhere you would typically host websites. Vercel, Cloudflare Workers, and Netlify all work great. Netlify recently changed their prices causing it to be prohibitively expensive depending on your team size.

The Callsite for Your Island

When you are consuming the bundled snippet it's important not to block rendering on a consuming page. When a browser loads a webpage and sees a script tag it executes that script immediately blocking render. Islands should be independent of the consuming page so it is safe to use the async property. See async vs defer for more information.

The only exception to this is if you are putting the island on the window and want to run a script after it. Check out global island for an example.

Do this:

">
<script
  src="https://your-domain/snippets/fancy-widget.island.umd.js"
  async
>script>

Not this:

">
<script src="https://your-domain/snippets/fancy-widget.island.umd.js">script>

Differences to Preact Habitat

This library was heavily inspired by Preact Habitat.

Key differences:

Alternatives

Preact Island does not leverage the shadow dom or custom elements API yet for building full fledged web components. It may at some point in the future, but the scope of the project is to deliver the best React style API for delivering one of widgets onto any web page under 5kB.

Credits

A huge thank you to zouhir who is the author of preact-habitat. This library is heavily inspired by his work on that library.

Artwork by vik4graphic

License

MIT - Copyright (c) Marcus Wood

To add dynamic props to replace you can add a script in the document and pass in data-props-for or you can add props inline to the script placed on the page.

GitHub

https://github.com/mwood23/preact-island
You might also like...

Awesome chat widget for your React App

Awesome chat widget for your React App

Awesome chat widget for your React App

Jun 14, 2022

Ringcentral React Component library, make your app have the same user experience as Ringcentral Apps.

Juno Ringcentral React Component library, make your app have the same user experience as Ringcentral Apps. base on MATERIAL-UI. Explore Juno with the

Mar 22, 2022

A UI to manage your Meilisearch instances

A UI to manage your Meilisearch instances

UIRecord A UI for managing your meilisearch instances Screenshots Features ✨ Create new indexes 🔦 Search within your indexes 📜 Add Documents to any

Jun 2, 2022

This is a react component which made your text does not wrap and dynamically adjust the font size.

This is a react component which made your text does not wrap and dynamically adjust the font size.

react-dynamic-font This is a react component which made your text does not wrap and dynamically adjust the font size Sometimes we want some text to ha

Dec 1, 2021

Option+Click a Component in the browser to instantly goto the source in your editor

Option+Click a Component in the browser to instantly goto the source in your editor

Option+Click a Component in the browser to instantly goto the source in your editor

Jun 23, 2022

Visually build pages and frontends, lightning-fast. Plug into your React codebase. Empower anyone to ship.

Visually build pages and frontends, lightning-fast. Plug into your React codebase. Empower anyone to ship.

Visually build pages and frontends, lightning-fast. Plug into your React codebase. Empower anyone to ship.

Jun 25, 2022

Tabbed navigation that you can swipe between, each tab can have its own ScrollView and maintain its own scroll position between swipes. Pleasantly animated. Customizable tab bar

react-native-scrollable-tab-view This is probably my favorite navigation pattern on Android, I wish it were more common on iOS! This is a very simple

Jun 16, 2022

Ship React Native .jsbundles compressed by Brotli algorithm.

Ship React Native .jsbundles compressed by Brotli algorithm.

May 22, 2022

Brainfeed: an app that helps you remember articles you want to read later

Brainfeed: an app that helps you remember articles you want to read later

Brainfeed 🧠 Brainfeed is an app that helps you remember articles you want to read later Features Light/dark mode toggle Auto-Copying url from clipboa

Feb 1, 2022

🥢 A minimalist-friendly ~1.3KB routing for React and Preact. Nothing else but HOOKS.

wouter is a tiny router for modern React and Preact apps that relies on Hooks. A router you wanted so bad in your project! Features Zero dependency, o

Jun 25, 2022

An easy-to-use keyboard event react component, Package size less than 3kb

An easy-to-use keyboard event react component, Package size less than 3kb

An easy-to-use keyboard event react component, Can achieve a variety of custom keyboard functions, Package size less than 3kb

May 5, 2022

Tiny (2.3kB) React component for image lazy loading

Tiny (2.3kB) React component for image lazy loading

React Pics Tiny (2.3kB) React component for image lazy loading Demo Table of Contents React Pics Table of Contents Installation Usage Basic With a pla

Dec 26, 2021

A tiny(around 1.3kb gzip) React library to generate colorful text placeholders 🎨

A tiny(around 1.3kb gzip) React library to generate colorful text placeholders 🎨

React Spectrum A tiny(around 1.3kb gzip) React library to generate colorful text placeholders 🎨 Inspired by this code illustration on CodeSandbox hom

Apr 10, 2022

⚛️ Fast 3kB React alternative with the same modern API. Components & Virtual DOM.

⚛️ Fast 3kB React alternative with the same modern API. Components & Virtual DOM.

Fast 3kB alternative to React with the same modern API. All the power of Virtual DOM components, without the overhead: Familiar React API & patterns:

Jun 22, 2022

A tiny but mighty 3kb list virtualization library, with zero dependencies 💪 Supports variable heights/widths, sticky items, scrolling to index, and more!

A tiny but mighty 3kb list virtualization library, with zero dependencies 💪 Supports variable heights/widths, sticky items, scrolling to index, and more!

react-tiny-virtual-list A tiny but mighty list virtualization library, with zero dependencies 💪 Tiny & dependency free – Only 3kb gzipped Render mill

Jun 17, 2022

A react-three-fiber component for rendering high quality 3d landscapes. If you prefer to use vanilla three.js you can also just use the custom material on its own.

A react-three-fiber component for rendering high quality 3d landscapes. If you prefer to use vanilla three.js you can also just use the custom material on its own.

A react-three-fiber component for rendering high quality 3d landscapes. If you prefer to use vanilla three.js you can also just use the custom material on its own.

Apr 29, 2022

Bookstore - a website similar to the Awesome Books website built in the previous module

The Bookstore is a website similar to the Awesome Books website built in the previous module. This MVP version allows you to: Display a list of books. Add a book. Remove a selected book. It is built with React, Redux, Javascript and CSS.

Apr 15, 2022

The Bookstore is a website similar to the "Awesome Books" website built in the previous module.

The Bookstore is a website similar to the "Awesome Books" website built in the previous module.

Jun 3, 2022
Comments
  • 1. docs: Correct `preact-compat` reference in the ReadMe

    Reviewed by rschristian at 2022-05-18 04:18
  • 2. Evaluate SSR and Hydration

    Preact offers a hydrate API and given the reactive nature of props inside of Preact Island I think SSR would work. I'm not sure about how this works so if you have a use case for this functionality please let me know!

    Reviewed by mwood23 at 2022-05-16 22:56
  • 3. Evaluate Web Components Functionality

    It would be nice to have support for web components for these island. Using the shadow DOM could simplify styling for islands and make it possible to use CSS libs. Custom elements could be nice too depending on the use case.

    Reviewed by mwood23 at 2022-05-16 22:55
Collection of headless components/hooks that are accessible, composable, customizable from low level to build your own UI & Design System

Collection of headless components/hooks that are accessible, composable, customizable from low level to build your own UI & Design System powered by Reakit System.

Jun 20, 2022
A React Native module that allows you to select a photo/video from the device library or camera.

A React Native module that allows you to use native UI to select media from the device library or directly from the camera.

Jun 18, 2022
InvaUI is a simple, modular and accessible component library that gives you the building blocks you need to build your React applications.

Inva UI Sponsored by English • Table of Contents Quick start Install Install The easiest way to use Inva UI is to install it from npm $ npm install @i

Nov 8, 2021
React standard library—must-have toolbox for any React project.
React standard library—must-have toolbox for any React project.

libreact React standard library—must-have toolbox for any React project. LAUNCH STORYBOOK ?? See documentation Most components implement Isomorphic -

Jun 15, 2022
A lightweight react component to add a mouse parallax effect to your website.

React Parallax Mouse ❤️ A lightweight react component to add a mouse parallax effect to your website ?? Efficient and lag free animations optimized fo

Jun 19, 2022
React tooltip is a React.JS Component that brings usefull UX and UI information in selected elements of your website.

React Tooltip ✅ React tooltip is a React.JS Component that brings usefull UX and UI information in elements of your website. Installation ⌨️ React Too

Dec 22, 2021
🎉 react-action-bar allows you to simplify form submission and data updation with just a few lines of code.
 🎉 react-action-bar allows you to simplify form submission and data updation with just a few lines of code.

react-action-bar Demo ?? react-action-bar allows you to simplify form submission and data updation with just a few lines of code. Installation : $ npm

Dec 28, 2021
A custom element that allows you to easily put a Dark Mode 🌒 toggle.

dark-mode A custom element that allows you to easily put a Dark Mode ?? toggle. so you can initially adhere to your users' preferences according to pr

Mar 17, 2022
A small Angular library that lets you use React components inside Angular projects

React in Angular and Angular in React This is a small Angular library that lets you use React components inside Angular projects. <react-wrapper [comp

Jun 2, 2022
⚡️ Simple, Modular & Accessible UI Components for your React Applications
⚡️ Simple, Modular & Accessible UI Components for your React Applications

Build Accessible React Apps with Speed ⚡️ Chakra UI provides a set of accessible, reusable, and composable React components that make it super easy to

Jun 19, 2022