Preact: What is the best way to publish a generalized preact/react component?

Created on 18 Nov 2016  路  15Comments  路  Source: preactjs/preact

I have a simple react link component

At the top it has:

var React = require('react');

But it does not bundle react, instead it defines it as a peerDependency.

Installing that module with core preact (without compat) causes a 'react not found' error, because browserify, aliasify and friends don't act on node_modules.

What is the best way to publish a component that is compatible with core preact and react?

compat discussion question

Most helpful comment

There are a couple of options, depending on what formats you need to support. If you're just doing commonjs, you can try this:

var React;
try {
  React = require('react');
} catch(e) {}
if (!react) {
  try {
    React = require('preact');
    React.createElement = React.h;
  } catch(e) {}
}

export default class Link extends React.Component {
  render() {
    return <foo>bar</foo>
  }
}

Another option, possibly the preferable one, would be to export a factory that produces your component. Eric Elliot promotes this approach as a form of Dependency Injection.

/** @jsx h */
export default vdom => {
  let h = vdom.h || vdom.createElement;
  return class Link extends vdom.Component {
    render() {
      return <foo>bar</foo>
    }
  }
};

The usage would then be:

import createLink from 'link';
import preact from 'preact';
const Link = createLink(preact);

Lastly, most people are pretty used to aliasing react out for preact in their bundler of choice (browserify, webpack, etc). If they have aliased React:preact, all you'd have to do is make sure to normalize h() vs createElement():

/**  @jsx h */
var React = require('react');
var h = React.createElement || React.h;

One thing to note: preact (without preact-compat, which most people alias) doesn't support createClass(), only Component. Currently your Link component doesn't work with Preact core because it relies on createClass. There's hope though: the React team is looking to split createClass into its own independent library soon :)

All 15 comments

There are a couple of options, depending on what formats you need to support. If you're just doing commonjs, you can try this:

var React;
try {
  React = require('react');
} catch(e) {}
if (!react) {
  try {
    React = require('preact');
    React.createElement = React.h;
  } catch(e) {}
}

export default class Link extends React.Component {
  render() {
    return <foo>bar</foo>
  }
}

Another option, possibly the preferable one, would be to export a factory that produces your component. Eric Elliot promotes this approach as a form of Dependency Injection.

/** @jsx h */
export default vdom => {
  let h = vdom.h || vdom.createElement;
  return class Link extends vdom.Component {
    render() {
      return <foo>bar</foo>
    }
  }
};

The usage would then be:

import createLink from 'link';
import preact from 'preact';
const Link = createLink(preact);

Lastly, most people are pretty used to aliasing react out for preact in their bundler of choice (browserify, webpack, etc). If they have aliased React:preact, all you'd have to do is make sure to normalize h() vs createElement():

/**  @jsx h */
var React = require('react');
var h = React.createElement || React.h;

One thing to note: preact (without preact-compat, which most people alias) doesn't support createClass(), only Component. Currently your Link component doesn't work with Preact core because it relies on createClass. There's hope though: the React team is looking to split createClass into its own independent library soon :)

Great answer, thanks!

The first method with try/catch appealed to me most, but after trying it out, browserify throws an error about not being able to find react - it seems like the requires are processed independent of the code?

Similarly with the 3rd option, an error is thrown about not being able to find react - I don't think aliasify goes into node_modules.

I can re-write the component without createClass, no problem on that front.

I think the dependency injection is really the only way to go there. Or provide multiple entrypoints.

@thinkloop I created a tooltip component which is compatible with Preact/React/Inferno https://github.com/slmgc/react-hint. I did not add React to peerDependencies so it won't throw any warnings. To use it with Preact you need to add a compatibility layer preact-compat and alias it to react. The same goes for Inferno. This solution should be good enough, IMHO.

Yup. Also worth noting - this is what preact/aliases is good for - if a library is idiomatic react its quite possible all that is needed to make it work is preact + createElement, which is what aliases exports.

Think we can close this one out @thinkloop? Or maybe it needs to be bubbled up to some sort of an organization that we create to work towards generalized VDOM components? I know @nekr was trying to do that with JSX some time ago.

@developit I like the idea of generalized components which could be used with different libraries without any changes. Do you have any thoughts on how to promote it and how to make it work across existing implementations?

I think we need some sort of an abstraction to handle the imports, or at least documentation somewhere on how to. I'd be thrilled to contribute, but it should probably happen in some central shared place. Not sure if there's one that exists currently that would work?

We could create a new organization account on GitHub, register a new subdomain at https://js.org/, create a new technical committee and use it to promote the idea of universal components. @developit @trueadm what do you think?

I'm down for that - think it's the kind of thing that would involve the Web components folks too? Or too specific to VDOM components?

Not sure about WebComponents ATM. But making a standardized way of reusing components with different VDOM libraries would be a good first step forward, helpful for the whole React community, IMHO.

I think web components is the way to go, at least in future. But anyway, those generalized components most likely should be implemented in a plain JS/DOM without VDOM, unless VDOM renderer will be magically provided somehow.

Having components atomic/independent of a renderer seems better though since this day they could be used everywhere (as web components for example).

Closing since we need to move this discussion outside of Preact itself. Probably needs a github org?

@developit Did the discussion move into its own GitHub organisation?

Is the method described here, still the preferred way of writing components which support both react and preact?

I'm currently working on a convention to build re-usable components and this is a problem which I want to either solve or write about.

@sebinsua eventually, I've switched to this solution in my tooltip library: https://github.com/slmgc/react-hint/blob/master/src/index.js#L1

There is a factory which returns a component. The factory doesn't assume which rendering library is being used and expects a compatible interface implementation of createElement and Component, e.g.:

// for react
import React from 'react'
const component = componentFactory(React) // passes createElement & Component

// for preact
import {h as createElement, Component} from 'preact'
const component = componentFactory({createElement, Component})

// for preact-compat
import preact from 'preact-compat'
const component = componentFactory(preact) // passes createElement & Component

// for inferno-compat
import inferno from 'inferno-compat'
const component = componentFactory(inferno) // passes createElement & Component
Was this page helpful?
0 / 5 - 0 ratings