🏝 Create your own slice of paradise on any website.

Overview

🏝 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

  /**
   * 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
   * <div data-island="widget">
   *    <div>some other content</div>
   *    <div>some other content</div>
   *    <div>some other content</div>
   * </div>
   * ```
   *
   * // turns into
   *
   * ```html
   * <div data-island="widget">
   *    <div>your-widget</div>
   * </div
   * ```
   */
  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
   * <div data-island="widget"></div>
   * ```
   *
   * // turns into
   *
   * ```html
   * <div>your-widget</div>
   * ```
   */
  replace?: boolean

  /**
   * Renders the widget at the current position of the script in the HTML document.
   *
   * @default false
   *
   * @example
   * ```html
   * <div>
   *    <div>some content here</div>
   *    <script src="https://preact-island.netlify.app/islands/pokemon.inline.island.umd.js"></script>
   *    <div>some content here</div>
   * </div>
   * ```
   *
   * // turns into
   *
   * ```html
   * <div>
   *    <div>some content here</div>
   *    <script src="https://preact-island.netlify.app/islands/pokemon.inline.island.umd.js"></script>
   *    <div>your widget</div>
   *    <div>some content here</div>
   * </div>
   * ```
   */
  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

<div data-island="pokemon">
  <script type="text/props">
    {"pokemon": "3"}
  </script>
</div>
<h2>Special mount</h2>
<!-- This takes priority over the other placement -->
<!-- Props are scoped to placement so that's why -->
<!-- Venosaur (pokemon number 3) doesn't appear -->
<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 <script> and targeted with a given selector. These props are reactive and will cause rerenders on changes. If there are multiple scripts found with the selector, all props are merged with the last script found taking priority.

Example

const island = createIsland(Pokemon)
island.render({
  selector: '[data-island="pokemon"]',
  // Make sure you set this in your island's render method!
  propsSelector: '[data-island-props="test-island"]',
})
<div data-island="pokemon"></div>

<!-- Can be located anywhere in the document! -->
<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 <script> and nested instead of a selector. These props are reactive and will cause rerenders on changes. If multiple interior script props are found, all props are merged with the last script found taking priority.

Example

<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>

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.

import { createIsland } from '../../dist/index.module'
import { h } from 'preact'
import style from './email-subscribe.island.css'

document.head.insertAdjacentHTML('beforeend', `<style>${style}</style>`)

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:

  • The consumer of the script doesn't need to include an external stylesheet
  • There's only one request for rendering the entire widget

Cons:

  • Bloats the bundle
  • The CSS file itself won't be able to be cached

Use an external stylesheet

<!-- Start island -->
<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>
<!-- End island -->

Pros:

  • The CSS can be cached in the browser
  • Doesn't bloat the JS bundle

Cons:

  • Unless your script creates an element to automatically request the stylesheet the consumer of your script will need to add two things not one

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:

  • Components rerender based on prop changes. This can be a data-prop attribute change on a host element, inside of a props script tag, or event on the executed script.
  • You can add props to script element itself and they're passed to the component
  • You can add props to a script tag that lives anywhere in the document and sync it up to the component
  • You can replace the target container instead of rendering inside of it if you choose
  • No double loading when mounting the component
  • Calling render multiple times create many components
  • There is a rerender method that allows you to manually alter props for the component
  • All dataset attributes (data- on an element) are passed as props
  • There is no clientSpecified flag. If you declare a data-mount-in prop on a script it will take priority over the selector given at render.

Alternatives

  • StencilJS: A web components toolchain that feels sort of like React and Angular put together. The JSX core is lifted from Preact's internals. It has a lot of nice features like automatic documentation and other nice to haves since it has its own compiler. It feels tailored towards design system and doesn't have the flexibility of prop injection that Preact Island does. It's also another tool to adopt with it's own patterns. All you have to do with Preact Island is bring your component.
  • Lit: A web components framework by Google. Does not require a compilation step and weights in around 5Kb (roughly the same as Preact Island + Preact). Doesn't use a virtual dom for diffing and feels like a nice layer on top of web components. Does not support JSX or a React style workflow.

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.

Comments
Owner
Marcus Wood
Building products with Typescript, React, GraphQl, and Node. https://campsitetonight.app
Marcus Wood
This is a boilerplate for creating your own languages for various use cases. You can even create your own programming language from scratch!

Bootstrap compiler This is a bootstrap compiler that can be used to compile to compiler written in the target language. You can write a compiler in th

Kaan 3 Nov 14, 2022
zkPoB is a mobile compatible tool that lets anyone prove they own a Bufficorn (or any NFT) without revealing which Buffi they own or the address they are verifying themselves with

zkPoB is a mobile compatible tool that lets anyone prove they own a Bufficorn (or any NFT) without revealing which Buffi they own or the address they are verifying themselves with

Marto.eth 10 Aug 25, 2022
This React-Based WebPage allows the client/user system to create their own blog, where users can publish their own opinions.

Getting Started with Create React App This project was bootstrapped with Create React App. Available Scripts In the project directory, you can run: np

Gauri Bhand 4 Jul 28, 2022
📝 You Can Create Your Own Short Notes With The Help of Sticky-Notes Website.

Hi ?? , I'm Sneh Agrawal A passionate Web developer from India ?? I’m currently working on Chatting Website Chit-Chat ?? How to reach me on My Gmail A

Sneh (Smilyboyy) 1 Feb 23, 2022
may-be.gay is a service in which you can register your own sub-domain for your personal website

may-be.gay is a service in which you can register your own sub-domain for your personal website. How to register New method (Recommended) Create a new

may-be.gay 7 Dec 27, 2022
A tiny JavaScript library to easily toggle the state of any HTML element in any contexts, and create UI components in no time.

A tiny JavaScript library to easily toggle the state of any HTML element in any contexts, and create UI components in no time. Dropdown, navigation bu

Matthieu Bué 277 Nov 25, 2022
A highly customizable platform ready to be a portfolio website, and become a lot more with some of your own components

Vextra Elegant and animated portfolio website. Demo: vextra.vercel.app Vextra is a portfolio template, packed with animations with a satisfying flow t

null 3 Sep 19, 2022
To-do-expressJS-api - An ExpressJS API, where you can create your own To-Do's

ExpressJS to-do API What is this API about? This is an API where you can do the following: Log in. Sign up. Create task Read Task Update Task Delete T

Pértile Franco Giuliano 1 Jan 3, 2022
Create your own wrappings with optional key bindings for selected text, a set of useful defaults is also provided.

Create your own wrappings with optional key bindings for selected text, a set of useful defaults is also provided.

Seth Yuan 66 Jan 1, 2023
Create your own NFTs within seconds 🪄

MagicaNFT An automated process to create a number of NFTs At the moment, the program only makes pixelated NFTs, with only a certain number of characte

Jaival Patel 2 Feb 3, 2022
An easy and simply way to create your own image classifier AI using ml5.js and Google's Teachable Machine

An easy and simply way to create your own image classifier AI using ml5.js and Google's Teachable Machine

Mateus Vinícius de Lima 2 Apr 5, 2022
PokemonNFT started as Buildspace Project - "Create your own mini turn-based NFT browser game" - ROSE Emerald Paratime Testnet

Welcome to PokemonNFTGame ?? Buildspace Project - Create your own mini turn-based NFT browser game ✨ Demo Install npm install Usage npm run dev Blockc

Alberto Cruz Luis 7 Oct 3, 2022
Keep track of the movies you've watched and create your own movies lists!

Cinematek Keep track of the movies you've watched and create your own movies lists! All the movies informations are provided by The Movie Database Dep

Caroline Oliveira 8 May 23, 2022
Create your own custom NFT minting page using thirdweb's NFT Drop contract

Customizable NFT Drop Minting Page In this example, you can create your own NFT Drop minting page just by customising the template with your branding,

thirdweb examples 59 Dec 24, 2022
In our last repo we learnt how to create a DAO on your own and how to use governance tokens and NFTs for voting purposes.

In our last repo we learnt how to create a DAO on your own and how to use governance tokens and NFTs for voting purposes. Now we will be stepping into the world of games with NFTs where a user has to play games with their character being an NFT which has unique powers, unique traits etc etc.

Daksh Paleria 6 Oct 1, 2022
Create your own password generator using jQuery, Vanilla JS, and SASS.

Password Generator Create your own password generator using jQuery, Vanilla JS, and SASS. I have been working with JS for my last few projects so I th

The Dev Drawer 1 Jul 12, 2021
simple-remix-blog is a blog template built using Remix and TailwindCSS. Create your own blog in just a few minutes!

simple-remix-blog is a blog template built using remix.run and TailwindCSS. It supports markdown and MDX for the blog posts. You can clone it and star

José Miguel Álvarez Vañó 8 Dec 8, 2022
GifOs is a small app for you to find gifs and also be able to create your own.

GifOs GifOs is an small app for you to find gifs and also be able to create your own ones. Techs Used This project was built with Next.Js and Typescri

Carlos Beltrán R. 6 Oct 11, 2022
This extensions adds blocks to help you create your own carnival games in MakeCode Arcade using throwable balls, extra timer functions, and extra game-over options.

Usage This extensions adds blocks to help you create your own carnival games in MakeCode Arcade using throwable balls, extra timer functions, and extr

Microsoft 6 Nov 16, 2022