{"id":13525915,"url":"https://github.com/mwood23/preact-island","last_synced_at":"2025-05-16T10:06:28.219Z","repository":{"id":40243576,"uuid":"491629359","full_name":"mwood23/preact-island","owner":"mwood23","description":"🏝 Create your own slice of paradise on any website.","archived":false,"fork":false,"pushed_at":"2024-12-21T20:42:26.000Z","size":4623,"stargazers_count":230,"open_issues_count":2,"forks_count":10,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-05-13T05:27:46.341Z","etag":null,"topics":["habitat","preact","shopify-app"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/mwood23.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-05-12T18:38:56.000Z","updated_at":"2025-05-11T14:06:33.000Z","dependencies_parsed_at":"2025-01-06T22:11:11.982Z","dependency_job_id":"343ec70e-b584-455d-a299-cec13cf130ef","html_url":"https://github.com/mwood23/preact-island","commit_stats":{"total_commits":43,"total_committers":3,"mean_commits":"14.333333333333334","dds":"0.046511627906976716","last_synced_commit":"0477e47f15ebdab1b6360d9a750c71d89adbe031"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mwood23%2Fpreact-island","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mwood23%2Fpreact-island/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mwood23%2Fpreact-island/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/mwood23%2Fpreact-island/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/mwood23","download_url":"https://codeload.github.com/mwood23/preact-island/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254509475,"owners_count":22082891,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["habitat","preact","shopify-app"],"created_at":"2024-08-01T06:01:23.476Z","updated_at":"2025-05-16T10:06:28.194Z","avatar_url":"https://github.com/mwood23.png","language":"TypeScript","funding_links":[],"categories":["Frameworks","TypeScript","Uncategorized"],"sub_categories":["Preact","Uncategorized"],"readme":"\u003cdiv align=\"center\"\u003e\n  \u003cimg src=\"./docs/preact-island.svg\" align=\"center\" /\u003e\n\u003c/div\u003e\n\u003cdiv align=\"center\"\u003e\n  \u003ch1 align=\"center\"\u003e🏝 Preact Island\u003c/h1\u003e\n  \u003cp align=\"center\"\u003e\n    A 1.3kB module that helps you ship your own slice of paradise to any website. Especially useful for Shopify apps or CMS websites.\n  \u003c/p\u003e\n\n[![downloads][downloads-badge]][npmcharts]\n[![version][version-badge]][package]\n[![Supports Preact and React][preact-badge]][preact]\n[![MIT License][license-badge]][license]\n\n\u003c/div\u003e\n\n\u003e 🚀 Looking for a zero config starter to build islands fast? Check out [preact-island-starter](https://github.com/mwood23/preact-island-starter)\n\nSometimes 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.\n\nPreact Island helps you build these experiences by adding a lightweight layer on top of Preact. For \u003c5kB, you get a React style workflow (with hooks!), and a framework for rendering your widget with reactive props.\n\n## Features\n\n- 🚀 Render by selector, inline, or by a specific attribute given on the executed script\n- ⚛️ Based on Preact, no special compiler or anything needed to render an island\n- 🙏 5 ways to pass in props to your component\n- 🧩 Experimental support for web components (including web component portals)\n- 🪄 All components are reactive to prop changes causing rerenders (not remounts)\n- 👯‍♀️ Create as many instances of your component as you need with a single island\n- 🧼 Does not mutate the `window`. Use as many islands as you'd like on one page!\n- 🐣 Less than 1.3kB\n- ☠️ Supports replacing the target selector\n- 🏔 React friendly with `preact/compat`\n- 🔧 Manually trigger rerenders with props\n- 🐙 Fully tested with Preact testing library\n- 👔 Fully typed with TypeScript\n\n## Examples\n\n- [Basic element placement with props](https://codesandbox.io/s/preact-island-element-placement-with-props-8kzmj5)\n- [Reactive props](https://codesandbox.io/s/reactive-prop-updates-uqvof7)\n- [Multiple islands](https://codesandbox.io/s/multiple-islands-8xvjqw)\n- [Props script](https://codesandbox.io/s/props-selector-70ks1g)\n- [Replace selector](https://codesandbox.io/s/replace-selector-z2rogw)\n- [Multiple host elements](https://codesandbox.io/s/multiple-host-elements-q6ot5q)\n- [Clean host element](https://codesandbox.io/s/clean-host-element-i35nlt)\n- [Global island](https://codesandbox.io/s/global-island-zqco9p)\n- [Inline script](https://codesandbox.io/s/inline-script-1qm5q8)\n- [Executed script props](https://codesandbox.io/s/preact-island-element-placement-current-script-props-0dwlyo)\n- [Interior script props](https://codesandbox.io/s/interior-script-props-z5rcxg)\n- [Mount In script attribute](https://codesandbox.io/s/mount-in-property-z5rcxg)\n- [React with Props](https://codesandbox.io/s/preact-island-element-placement-with-props-react-49mjrg)\n- [React Portals with Preact Island](https://codesandbox.io/s/preact-island-element-placement-with-props-react-tdss2p)\n\n## Installation\n\n```sh\nnpm install --save preact-island\n```\n\n## Usage\n\n```tsx\nimport { createIsland } from 'preact-island'\n\nconst Widget = () =\u003e {\n  return \u003cdiv\u003eawesome widget!\u003c/div\u003e\n}\n\nconst island = createIsland(Widget)\nisland.render({\n  selector: '[data-island=\"widget\"]',\n})\n```\n\n## API\n\n### createIsland\n\nCreates a new island instance with a passed in component. Returns a bag of props/methods to work with your island.\n\n```tsx\nimport { createIsland } from 'preact-island'\n\nconst Widget = () =\u003e {\n  return \u003cdiv\u003eawesome widget!\u003c/div\u003e\n}\n\nconst island = createIsland(Widget)\n```\n\n### createIsland().render\n\nRenders an island to the DOM given options.\n\n```ts\nconst island = createIsland(Widget)\nisland.render({...})\n```\n\n#### options\n\n````ts\n  /**\n   * A query selector target to create the widget. This is ignored if inline is passed or if a `data-mount-in` attribute\n   * is appended onto the executed script.\n   *\n   * @example '[data-island=\"widget\"]'\n   */\n  selector?: string\n  /**\n   * If true, removes all children of the element before rendering the component.\n   *\n   * @default false\n   *\n   * @example\n   * ```html\n   * \u003cdiv data-island=\"widget\"\u003e\n   *    \u003cdiv\u003esome other content\u003c/div\u003e\n   *    \u003cdiv\u003esome other content\u003c/div\u003e\n   *    \u003cdiv\u003esome other content\u003c/div\u003e\n   * \u003c/div\u003e\n   * ```\n   *\n   * // turns into\n   *\n   * ```html\n   * \u003cdiv data-island=\"widget\"\u003e\n   *    \u003cdiv\u003eyour-widget\u003c/div\u003e\n   * \u003c/div\n   * ```\n   */\n  clean?: boolean\n  /**\n   * If true, replaces the contents of the selector with the component given. If you use replace,\n   * you will not be able to add props to the host element (since it will be replaced). You will also\n   * not be able to use child props script either (since they will be replaced).\n   *\n   * Use script tag props or a props selector for handling props when in replace mode.\n   *\n   * @default false\n   *\n   * @example\n   * ```html\n   * \u003cdiv data-island=\"widget\"\u003e\u003c/div\u003e\n   * ```\n   *\n   * // turns into\n   *\n   * ```html\n   * \u003cdiv\u003eyour-widget\u003c/div\u003e\n   * ```\n   */\n  replace?: boolean\n\n  /**\n   * Renders the widget at the current position of the script in the HTML document.\n   *\n   * @default false\n   *\n   * @example\n   * ```html\n   * \u003cdiv\u003e\n   *    \u003cdiv\u003esome content here\u003c/div\u003e\n   *    \u003cscript src=\"https://preact-island.netlify.app/islands/pokemon.inline.island.umd.js\"\u003e\u003c/script\u003e\n   *    \u003cdiv\u003esome content here\u003c/div\u003e\n   * \u003c/div\u003e\n   * ```\n   *\n   * // turns into\n   *\n   * ```html\n   * \u003cdiv\u003e\n   *    \u003cdiv\u003esome content here\u003c/div\u003e\n   *    \u003cscript src=\"https://preact-island.netlify.app/islands/pokemon.inline.island.umd.js\"\u003e\u003c/script\u003e\n   *    \u003cdiv\u003eyour widget\u003c/div\u003e\n   *    \u003cdiv\u003esome content here\u003c/div\u003e\n   * \u003c/div\u003e\n   * ```\n   */\n  inline?: boolean\n  /**\n   * Initial props to pass to the component. These props do not cause updates to the island if changed. Use `createIsland().rerender` instead.\n   */\n  initialProps?: P\n  /**\n   * A valid selector to a script tag located in the HTML document with a type of either `text/props` or `application/json`\n   * containing props to pass into the component. If there are multiple scripts found with the selector, all props are merged with\n   * the last script found taking priority.\n   */\n  propsSelector?: string\n````\n\n### createIsland().rerender\n\nTriggers a rerenders of the island with the new props given.\n\n```ts\nconst island = createIsland(Widget)\nisland.render({ selector: '[data-island=\"widget\"]' })\nisland.rerender({ new: 'props' })\n```\n\n### createIsland().destroy\n\nDestroys all instances of the island on the page and disconnects any associated observers.\n\n```ts\nconst island = createIsland(Widget)\nisland.render({ selector: '[data-island=\"widget\"]' })\nisland.destroy()\n```\n\n## Selecting Mount Point from Script\n\nYou can override the `selector` given to render by passing `data-mount-in` to the script.\n\n[Example](https://codesandbox.io/s/mount-in-property-z5rcxg)\n\n```html\n\u003cdiv data-island=\"pokemon\"\u003e\n  \u003cscript type=\"text/props\"\u003e\n    {\"pokemon\": \"3\"}\n  \u003c/script\u003e\n\u003c/div\u003e\n\u003ch2\u003eSpecial mount\u003c/h2\u003e\n\u003c!-- This takes priority over the other placement --\u003e\n\u003c!-- Props are scoped to placement so that's why --\u003e\n\u003c!-- Venosaur (pokemon number 3) doesn't appear --\u003e\n\u003cdiv data-island=\"mount-here-actually\"\u003e\u003c/div\u003e\n\n\u003cscript\n  async\n  data-mount-in='[data-island=\"mount-here-actually\"]'\n  src=\"https://preact-island.netlify.app/islands/pokemon.island.umd.js\"\n\u003e\u003c/script\u003e\n```\n\n## Passing Props\n\nProps 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.\n\n### Merging Order\n\nProps are merged in the following order (from lowest to highest specificity):\n\n1. Initial props\n2. Element props\n3. Executed script props\n4. Props selector props\n5. Interior script props\n\n### Data Props\n\nAll props located on an HTML element use `data-`. You can name them any of the following ways:\n\n- `data-background-color` =\u003e `backgroundColor`\n- `data-prop-background-color` =\u003e `backgroundColor`\n- `data-props-background-color` =\u003e `backgroundColor`\n\nUnder the hood all of these element will be transformed to `camelCase` and passed to your component.\n\n### Initial Props\n\nDefault props can be passed on render to an island. You can render many islands and give them all different initial props. Initial props\ndo not cause rerenders if updated. Use `createIsland.rerender` instead.\n\n```ts\nimport { createIsland } from 'preact-island'\n\nconst Widget = () =\u003e {\n  return \u003cdiv\u003eawesome widget!\u003c/div\u003e\n}\n\nconst island = createIsland(Widget)\n\nisland.render({\n  selector: '[data-island=\"widget\"]',\n  initialProps: {\n    color: '#000000',\n  },\n})\n\nisland.render({\n  selector: '[data-island=\"widget\"]',\n  initialProps: {\n    color: '#ffffff',\n  },\n})\n\n// Will render two instances of the island with different color props\n```\n\n### Element Props\n\nProps can be placed on host elements and passed to your component. These props are reactive and will cause rerenders on changes.\n\n[Example](https://codesandbox.io/s/preact-island-element-placement-with-props-8kzmj5)\n\n```html\n\u003cdiv data-island=\"pokemon\" data-pokemon=\"130\"\u003e\u003c/div\u003e\n\u003cscript\n  async\n  src=\"https://preact-island.netlify.app/islands/pokemon.island.umd.js\"\n\u003e\u003c/script\u003e\n```\n\n### Executed Script Props\n\nProps 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.\n\n[Example](https://codesandbox.io/s/preact-island-element-placement-current-script-props-0dwlyo)\n\n```html\n\u003cdiv data-island=\"pokemon\"\u003e\u003c/div\u003e\n\u003cscript\n  data-pokemon=\"130\"\n  async\n  src=\"https://preact-island.netlify.app/islands/pokemon.island.umd.js\"\n\u003e\u003c/script\u003e\n```\n\n### Props Selector Props\n\nProps can be placed inside of a `\u003cscript\u003e` 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.\n\n[Example](https://codesandbox.io/s/props-selector-70ks1g)\n\n```tsx\nconst island = createIsland(Pokemon)\nisland.render({\n  selector: '[data-island=\"pokemon\"]',\n  // Make sure you set this in your island's render method!\n  propsSelector: '[data-island-props=\"test-island\"]',\n})\n```\n\n```html\n\u003cdiv data-island=\"pokemon\"\u003e\u003c/div\u003e\n\n\u003c!-- Can be located anywhere in the document! --\u003e\n\u003cscript data-island-props=\"test-island\" type=\"text/props\"\u003e\n  {\"pokemon\": \"3\"}\n\u003c/script\u003e\n\n\u003cscript\n  async\n  src=\"https://preact-island.netlify.app/islands/pokemon.props-selector.island.umd.js\"\n\u003e\u003c/script\u003e\n```\n\n### Interior Script Props\n\nProps can be placed inside of a `\u003cscript\u003e` and nested instead of a selector. These props are reactive and will cause rerenders on changes. If\nmultiple interior script props are found, all props are merged with the last script found taking priority.\n\n[Example](https://codesandbox.io/s/interior-script-props-z5rcxg)\n\n```html\n\u003cdiv data-island=\"pokemon\"\u003e\n  \u003cscript type=\"text/props\"\u003e\n    {\"pokemon\": \"3\"}\n  \u003c/script\u003e\n\u003c/div\u003e\n\n\u003cscript\n  async\n  src=\"https://preact-island.netlify.app/islands/pokemon.island.umd.js\"\n\u003e\u003c/script\u003e\n```\n\n## React Compatibility\n\n- [React with Props](https://codesandbox.io/s/preact-island-element-placement-with-props-react-49mjrg)\n- [React Portals with Preact Island](https://codesandbox.io/s/preact-island-element-placement-with-props-react-tdss2p)\n\nPreact Island fully supports React using [preact/compat](https://preactjs.com/guide/v10/switching-to-preact). 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.\n\nDepending 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.\n\n### Bundle Sizes\n\n#### React\n\n![react only](./docs/bundle-react-only.png)\n\n#### React + Preact/compat\n\n![react with preact/compat](./docs/bundle-react-with-compat.png)\n\n#### Preact\n\n![preact](./docs/bundle-preact.png)\n\n## Adding Styles\n\nYou 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! 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`.\n\n### Including Styles\n\nPreact Island takes no opinions on how CSS is included for your islands. There are two main options:\n\n**Inline the CSS into the bundle**\n\nThis is what the `/example` islands do.\n\n```tsx\nimport { createIsland } from 'preact-island'\nimport style from './email-subscribe.island.css'\n\ndocument.head.insertAdjacentHTML('beforeend', `\u003cstyle\u003e${style}\u003c/style\u003e`)\n\nconst Widget = () =\u003e {\n  return (\n    \u003cdiv className=\"email__container\"\u003e\n      \u003cp className=\"email__title\"\u003eJoin our newsletter\u003c/p\u003e\n      {/* ... */}\n    \u003c/div\u003e\n  )\n}\n\nconst island = createIsland(Widget)\nisland.render({\n  selector: '[data-island=\"email-subscribe\"]',\n})\n```\n\n```html\n\u003cscript\n  src=\"https://your-domain/snippets/fancy-widget.island.umd.js\"\n  async\n\u003e\u003c/script\u003e\n```\n\n**Pros:**\n\n- The consumer of the script doesn't need to include an external stylesheet\n- There's only one request for rendering the entire widget\n\n**Cons:**\n\n- Bloats the bundle\n- The CSS file itself won't be able to be cached\n\n**Use an external stylesheet**\n\n```html\n\u003c!-- Start island --\u003e\n\u003clink\n  href=\"https://your-domain/snippets/fancy-widget.island.css\"\n  rel=\"stylesheet\"\n/\u003e\n\u003cscript\n  src=\"https://your-domain/snippets/fancy-widget.island.umd.js\"\n  async\n\u003e\u003c/script\u003e\n\u003c!-- End island --\u003e\n```\n\n**Pros:**\n\n- The CSS can be cached in the browser\n- Doesn't bloat the JS bundle\n\n**Cons:**\n\n- Unless your script creates an element to automatically request the stylesheet the consumer of your script will need to add two things not one\n\n### CSS Libraries\n\nIt'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.\n\nIf you need a CSS library, use something that has a just in time compilation step like [Tailwind](https://tailwindcss.com/docs/installation) to minimize the excess CSS. Or you can use something like [Vanilla-Extract](https://vanilla-extract.style/) to build your own zero runtime cost Tailwind.\n\n## Building Your Islands\n\nAny 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 `webpack`. It works extremely well if you have multiple islands because it can produce multi-entry point bundles.\n\n### Naming Conventions\n\nMake sure to name your bundles `kebab-case` since they'll be served over HTTP. Case sensitive URLs can be fiddly depending on browser!\n\n## Hosting Your Islands\n\nYou can host your files on anywhere you would typically host websites. Vercel, Cloudflare Workers, and Netlify all work great. Netlify recently [changed their prices](https://answers.netlify.com/t/upcoming-changes-to-netlify-plans/52482/158) causing it to be prohibitively expensive depending on your team size.\n\n\u003e Make sure to use your own domain when hosting these assets. If you use their subdomain like `something.mynetlify.com` and embed those onto third party websites you may get locked in.\n\n## The Callsite for Your Island\n\nWhen 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](https://javascript.info/script-async-defer) for more information.\n\n\u003e 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](https://codesandbox.io/s/global-island-zqco9p) for an example.\n\nDo this:\n\n```html\n\u003cscript\n  src=\"https://your-domain/snippets/fancy-widget.island.umd.js\"\n  async\n\u003e\u003c/script\u003e\n```\n\nNot this:\n\n```html\n\u003cscript src=\"https://your-domain/snippets/fancy-widget.island.umd.js\"\u003e\u003c/script\u003e\n```\n\n## Differences to Preact Habitat\n\nThis library was heavily inspired by [Preact Habitat](https://github.com/zouhir/preact-habitat).\n\nKey differences:\n\n- 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.\n- You can add props to script element itself and they're passed to the component\n- You can add props to a script tag that lives anywhere in the document and sync it up to the component\n- You can replace the target container instead of rendering inside of it if you choose\n- No double loading when mounting the component\n- Calling render multiple times create many components\n- There is a `rerender` method that allows you to manually alter props for the component\n- All dataset attributes (`data-` on an element) are passed as props\n- 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`.\n\n## Alternatives\n\n- [StencilJS](https://stenciljs.com/): 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.\n- [Lit](https://lit.dev/docs/): 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.\n\n## Credits\n\nA huge thank you to [zouhir](https://github.com/zouhir) who is the author of [preact-habitat](https://github.com/zouhir/preact-habitat). This library is heavily inspired by his work on that library.\n\nArtwork by [vik4graphic](https://lottiefiles.com/vik4graphic)\n\n## License\n\n[MIT](LICENSE) - Copyright (c) [Marcus Wood](https://www.marcuswood.io/)\n\n[version-badge]: https://img.shields.io/npm/v/preact-island.svg?style=flat-square\n[package]: https://www.npmjs.com/package/preact-island\n[downloads-badge]: https://img.shields.io/npm/dm/preact-island.svg?style=flat-square\n[npmcharts]: http://npmcharts.com/compare/preact-island\n[license-badge]: https://img.shields.io/npm/l/preact-island.svg?style=flat-square\n[license]: https://github.com/mwood23/preact-island/blob/master/LICENSE\n[preact-badge]: https://img.shields.io/badge/%E2%9A%9B%EF%B8%8F-preact-6F2FBF.svg?style=flat-square\n[preact]: https://preactjs.com\n[module-formats-badge]: https://img.shields.io/badge/module%20formats-umd%2C%20cjs%2C%20es-green.svg?style=flat-square\n[github-star]: https://github.com/mwood23/preact-island/stargazers\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmwood23%2Fpreact-island","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmwood23%2Fpreact-island","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmwood23%2Fpreact-island/lists"}