GithubHelp home page GithubHelp logo

zouhir / preact-habitat Goto Github PK

View Code? Open in Web Editor NEW
513.0 16.0 43.0 1.43 MB

Zero configuration Preact widgets renderer in any host DOM

License: MIT License

JavaScript 100.00%
preact portable vdom embed iframe

preact-habitat's Introduction

Build Status Code Coverage downloads version gzip size module formats: umd, cjs, and es Supports Preact and React MIT License

Preact Habitat

A 900 Bytes module for that will make plugging in Preact components and widgets in any CMS or website as fun as lego!

Demos

Login Widget Source Code πŸ’»

Login Widget Integration pen Codepen Demo πŸ–‹

Installation

npm install --save preact-habitat

Core Features

  • 2 ways to passing props from DOM.
  • Multiple rendering options.
  • Light weight ( < 1KB ).
  • Compatible with React widgets through preact-compat.
  • In use in high traffic web applications.

Basic Usage Example

import habitat from 'preact-habitat';
import WidgetAwesome from './components/WidgetAwesome';

const { render } = habitat(WidgetAwesome);

/**
** other selecors options:
**
** ".classname"  for querying DOM element by its class name
**
** "#div-id"  for querying DOM element by its ID value
**
** "[data-attribute-example='widget-here']"  for querying DOM element by its data attribute name & val
**
**/

render({
  selector: '.some-class', // Searches and mounts in <div class="some-class"></div>
  defaultProps: undefined, // Default props for all widgets
  inline: false,
  clean: false,
  clientSpecified: false
});

in webpack.config.js or any other build tool bundle output format should be UMD:

output: {
  libraryTarget: 'umd'
}

in the DOM you'd like to mount your widget in:

<div class="some-class"> <!-- as specified in render, habitat will mount the component in this-->
  <script type="application/json">
    {
      "title": "Widget Title passed as prop",
      "theme": "red",
      "anotherProp": "Thanks for trying this widget out!"
    }
  </script>
</div>

Now, build your production ready preact widget and you're all set, TADA! πŸŽ‰

API Docs

habitat(...)

accepts a single Preact component as its only argument

example:
import { h } form 'preact';
import habitat from 'preact-habitat';

const Widget = () => <h1>Hello, World!</h1>;

const { render } = habitat(Widget); // NOTE: pass Widget and not <Widget />

render({
  ...
});

render(options)

render function accepts an options Object which supports the following properties:

option.selector

String: .myclass, #myid, [data-selector="my-data-attr"]

DOM Element selector used to retrieve the DOM elements you want to mount the widget in

option.defaultProps

Object: {} || undefined (default)

Default props to be rendered throughout widgets, you can replace each value declaring props.

option.inline

Boolean: true || false (default)

Set to true if you want to use the parent DOM node as a host for your widget without specifing any selectors.

example:

<div class="beautiful-container">
  <!-- inline set to true will make this widget render in it's parent 
      wrapper class="beautiful-container" without using selector option-->
  <script async src="cdn.preactwidget..."></script>
</div>

option.clean

Boolean: true || false (default)

clean will remove all the innerHTML from the HTMl element the widget will mount in.

example:

if we set the widget to be mounted inside the selector ".beautiful-container" with {clean: true} it will remove the Loading div as soon as it renders.

<div class="beautiful-container">
  <div class="loader">LOADING...</div>
</div>

<script async src="cdn.preactwidget..."></script>

option.clientSpecified

Boolean: true || false (default)

This option allows who ever using the script to specifit the selector which they'd like to mount the widget in

<div class="beautiful-container">
  <div class="loader">LOADING...</div>
</div>

<script async src="cdn.preactwidget..." data-mount-in=".beautiful-container"></script>

Passing Props

There are 2 ways to pass props, either via data-attributes or application/json script tag

via props script

Simply add a <script> tag with type="application/json" or type="text/props" and ensure the content is valid JSON. multiple script tags will be merged together and passed down.

<div class="beautiful-container" data-prop-name="preact habitat" data-prop-version="v3.0.0" data-prop-theme-color="green">
  <script type="application/json">
    {
      "name": "preact habitat",
      "version":"v3.0.0",
      "themeColor": "green"
    }
  </script>
</div>

via data-attribute

the data attribute has to always start with data-prop- examples:

data-prop-name will be available in your component as name

data-prop-version will be available in your component as version

data-prop-theme-color will be available in your component as themeColor NOTE the lowerCamelCase when there's a -

<div class="beautiful-container" data-prop-name="preact habitat" data-prop-version="v3.0.0" data-prop-theme-color="green">
  
</div>

License

MIT - Copyright (c) Zouhir Chahoud

Credits

Artwork By: Oleg Turbaba, Dribble

preact-habitat's People

Contributors

aharris88 avatar amollusk avatar armand1m avatar ascorbic avatar benjick avatar felipewer avatar heldr avatar solarliner avatar trurl-master avatar zouhir avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

preact-habitat's Issues

Add maintainers to this project

@zouhir You've said on your profile that you're no longer able to maintain your open source projects. Would you be able to add maintainers to this project. I'm happy to help out if you give me access to the project on Github and NPM. I'm using this quite heavily in several important projects, so I am able to devote some time to it.

accept props

it's good to be able to pass props in widget configs

example: if we have a video player widget - would be great if the client\host DOM can pass props within the JSON config like what we already have in { "clone": "id-xyz" } but:

<script type="widget/config">
  {
    "props":{
      "videoId: "11223300",
      "playFromStart": true
    }
}
</script>

Widget double rendering

Hi,
First, thank you for this good lib.

I have a problem embedding a widget created with preact-habitat in one Wordpress site. I'm embedding the widget inline, inside a div.

It happens that 'loaded' function (https://github.com/zouhir/preact-habitat/blob/master/src/index.js#L23) is called twice and the last time is called 'window.currentScript' is null so the function 'getExecutedScript' (https://github.com/zouhir/preact-habitat/blob/master/src/lib.js#L20) returns the last script in the page, which is another one different from injection script and the widget is rendered another time at the bottom of the page.

I tryed to reproduce this bug in a clean environment, but without success. This wp site (like many) is a mess of scripts and I really don't know which is the one causing this problem.

I solved my issue adding a variable in 'render' function to control if widget was already rendered, see:

master...farzeni:master

do you have an idea what can be the cause of the problem? or do you think this solution can be merged?

Update doc example

import habitat from 'preact-habitat';
import WidgetAwesome from './components/WidgetAwesome';

let habitat = habitat(WidgetAwesome);

habitat is already used while we import package and it would be better if we don't override variable.
I think it needs to have different name like habitatTemplate or something else except habitat

Ability for host page to update properties on the widget

It'd be great if the host page could capture a reference to the Preact component, which it can then use to update the properties on the rendered component later.

An example use case might be:

  • A widget exists that lists items; the widget takes a property that can be used to filter the list.
  • The owner of the hosting website has configured the list to use a pre-determined filter, passed to the widget as custom props at startup.
  • The end user can click buttons or dropdowns in the hosting web page to update the filter prop in the widget, re-rendering the widget with the new filter.

Props removed with Clean Option

When using options.clean in conjunction with props from a script tag the clean operation removes the props before the preact component is instantiated.

Support Hydration

It would be great to also be able to use this to hydrate the existing markup when mounting.

I am unsure how that could play out with the text/props node -- perhaps it removes that and then hydrates what remains?

[Question] - Sharing logic between widgets

Hi,
I'm trying to build some e-commerce widgets. So I have a add to cart button, a button to open the cart and a cart. I need to share some logic between them. I was wondering how I can do that with preact-habitat ?

Thanks

Ability to replace the container rather than mount inside

Is it possible to replace the container you specify rather than mounting inside? I'm just trying to reduce DOM elements. I understand it may be a limitation of Preact/React - sorry, I'm old skool and still getting up to speed. If so, feel free to close this.

For example:

<div class="some-class">
  <script type="text/props">
    {
      "title": "Widget Title passed as prop",
      "theme": "red",
      "anotherProp": "Thanks for trying this widget out!"
    }
  </script>
</div>

... would render ...

<div class="my-widget">
  ...
</div>

... rather than ...

<div class="some-class">
  <div class="my-widget">
    ...
  </div>
</div>

Compatablity with Preact X

⚠ WARN BabelEsmPlugin: ../node_modules/preact-habitat/dist/preact-habitat.es.js 151:25-31
"export 'default' (imported as 'preact') was not found in 'preact

Cannot read properties of undefined (reading 'render')

Hey all,

hopefully just a small issue with my implementation but I've followed the docs close to the book but am bumping into this TypeError when trying to embed my component in another site.

I'm using the preact widget template that includes a component which is passed to the habitat method provided by this package and then calling the provided render function from there, however am always given this TypeError.

logging the object returned from habitat() returns an object with a render function as expected so I'm unsure why the browser seems to think the parent of render is undefined. the preact widget template uses microbundle and the host site is built in NextJS if they affect anything.

Any and all help is appreciated,

Thanks,

Cameron

image

image

image

Props removed with clean and inline options set to true

This is almost the same as #16, but in this case it happens when both clean and inline options are set to true.

With this config:

  _habitat.render({
    inline: false,
    clientSpecified: true,
    clean: true
  });

This will work:

  <div class="container">
    <div>LOADING...</div>
    <script type="application/json">
      {
        "token": "kdj4JLH$Kjhdljkio8erO",
      }
    </script>
  </div>

  <script async src="/bundle.js" data-mount-in=".container"></script>

But if you config it like this:

  _habitat.render({
    inline: true,
    clean: true
  });

The following implementation will remove the props.

  <div class="container">
    <div>LOADING...</div>
    <script type="application/json">
      {
        "token": "kdj4JLH$Kjhdljkio8erO",
      }
    </script>
    <script async src="/bundle.js"></script>
  </div>

Clean up habitat widget

When I want to remove a habitat widget from DOM, I expect the componentWillUnmount() will be called to release internal resources, but in actually, it does not.

How can I clean up its internal resources in this case?

Generate file per component

If application exposes dozen ob components it would be nice to build one bundle per component rather then global huge bundle with all components. Users may want to embed single block, not all.

Do not include major components in the bundle

There should be an option to omit certain components from the bundle i.e. preact.min.js should be declared as a script in the example so that is cached separately of the bundle.js. The same for large components that may be duplicated in different bundles

Does this make sense?
Thanks

Define props on script tag for "inline" render?

Hey! Love this project in a major way! While tinkering around I noticed that the data-prop-propname attributes are ignored on the script tag. Can this be allowed?

<script async src="/bundle.js" data-prop-my-prop="yes please"></script>

Also, by accident I noticed that habitat will pull in ALL props defined in a script tag document-wide. Is this by design? Maybe we could pass a selector for the props instead of assuming all JSON in script tags belong to habitat?

... Now I have to say "All your JSONs are belong to us!" ... sorry if you don't get the reference. 😜

<script class="this-belongs-to-my-habitat" type="application/json">
{
  "forMyHabitat": true
}
</script>
<script type="application/json">
{
  "forMyHabitat": false
}
</script>

dynamically imported module

Hey,

is it possible to use habitat() for dynamically imported ES modules like:

import habitat from "preact-habitat";
...
const module = await import("/js/module.js"); // module.js is a compiled preact component
const { render } = habitat(module);

my use case is that:

  • I want to init multiple widgets on a site
  • A server response decides which widgets to init
  • I only want to request/load the widgets that are supposed to be mounted

I tried:

const modules: Record<Modules, string> = {
  widget1: "/js/modules/1.js",
  widget2: "/js/modules/2.js",
  widget3: "/js/modules/3.js",
};

const module = await import(modules[feature]); // feature is "widget1" | "widget2" | "widget3"
const { render } = habitat(module.default); // each widget is a bundled preact functional component with default export
render({ selector: "..." })

But I keep getting errors a la:

TypeError: Cannot read properties of undefined (reading '__H')

When I import the module directly, it works ofc

Asynchronously added widget not showing up

I'm using the widget inside a modal and several other components in a react project. So when the render function executes it could not find the widgets since it's still not added to the dom.

Can I call render function again? If so how is it? Is it the right method?

Compatibility with Unistore

Hi guys I'm trying to use preact habitat with a widget that uses unistore https://github.com/developit/unistore
Do you have any ideas how to use them together, as unistore require me to have a Provider wrapper:

  <Provider store={store}>
    <App />
  </Provider>

and habitat require me to pass my app as a parameter:

const { render } = habitat(App);
render();

clientSpecified option does not render widget

@zouhir
I tried the login example under this repo.
But, however we try using clientSpecified, it does not render the plugin.

using data-mount-in and:
niceLogin.render({ clientSpecified: true, inline: false, clean: false });

Is it released as stated in the readme or is it under another branch? πŸ€”

PS: Btw, Zouhir thanks for Preact habitat. Works great for all our purposes at work❀️

Passed props bleed to subsequent instances

<div class="my-component">
    <script type="text/props">
        {"a": 1, "b": 2}
    </script>
</div>

<div class="my-component">
    <script type="text/props">
        {"a": 3}
    </script>
</div>


habitat(MyComponent).render({
    selector: '.my-component'
});

The second instance will receive {"a": 3, "b": 2}

If I understand correctly it is caused by directly mutating props in collectPropsFromElement

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.