/remote-component

Dynamically load a React Component from a URL

Primary LanguageJavaScriptMIT LicenseMIT

Remote Component coverage:100%

Planet with transmitter

Dynamically load a React Component from a URL.

Quick Look

Use Remote Components the same way you use a local React Component.

// Local
const HelloWorld = ({ name }) => <div>Hello {name}!</div>;

// Remote
const RemoteHelloWorld = ({ name }) => (
  <RemoteComponent
    url="https://raw.githubusercontent.com/Paciolan/remote-component/master/examples/remote-components/HelloWorld.js"
    name={name}
  />
);

// Same Same but Different
const Container = (
  <>
    <HelloWorld name="Local" />
    <RemoteHelloWorld name="Remote" />
  </>
);

Requirements

React 16.8 is required because this component uses React Hooks.

Install

npm install @paciolan/remote-component

Webpack

Using Webpack is the recommended and easiest way to use RemoteComponent.

Create a file in the root of your web application called remote-component.config.js.

/**
 * remote-component.config.js
 *
 * Dependencies for Remote Components
 */

module.exports = {
  resolve: {
    react: require("react")
  }
};

Add a Webpack alias so the RemoteComponent can load this file.

/**
 * webpack.config.js
 */

module.exports = {
  resolve: {
    alias: {
      "remote-component.config.js": __dirname + "/remote-component.config.js"
    }
  }
};

Now you are ready to use RemoteComponent!

import React from "react";
import ReactDOM from "react-dom";
import { RemoteComponent } from "@paciolan/remote-component";

const element = document.getElementById("app");
const url = "https://raw.githubusercontent.com/Paciolan/remote-component/master/examples/remote-components/HelloWorld.js"; // prettier-ignore

const HelloWorld = props => <RemoteComponent url={url} {...props} />;

ReactDOM.render(<HelloWorld name="Paciolan" />, element);

Manual Configuration

Remote Components will require some dependencies to be injected into them. At the minimum, we'll be injecting the React dependency.

remote-component.config.js

The web application can include dependencies and inject them into the RemoteComponent. At a minimum, you will probably need the react dependency.

Create a the file remote-component.config.js in the root of the web application.

/**
 * remote-component.config.js
 *
 * Dependencies for Remote Components
 */

module.exports = {
  resolve: {
    react: require("react")
  }
};

src/components/RemoteComponent.js

Export RemoteComponent with the requires from remote-component.config.js. This will inject the dependencies into the RemoteComponent.

/*
 * src/components/RemoteComponent.js
 */

import {
  createRemoteComponent,
  createRequires
} from "@paciolan/remote-component";
import { resolve } from "../../remote-component.config.js";

const requires = createRequires(resolve);
export const RemoteComponent = createRemoteComponent({ requires });

Basic Usage

For 99% of use-cases, the Basic Usage is enough.

import React from "react";
import ReactDOM from "react-dom";
import { RemoteComponent } from "./components/RemoteComponent";

const element = document.getElementById("app");
const url = "https://raw.githubusercontent.com/Paciolan/remote-component/master/examples/remote-components/HelloWorld.js"; // prettier-ignore

const HelloWorld = props => <RemoteComponent url={url} {...props} />;

ReactDOM.render(<HelloWorld name="Paciolan" />, element);

Render Props Usage

In the case you need more control over the error or rendering, you can use a render prop.

import React from "react";
import ReactDOM from "react-dom";
import { RemoteComponent } from "./components/RemoteComponent";

const element = document.getElementById("app");
const url = "https://raw.githubusercontent.com/Paciolan/remote-component/master/examples/remote-components/HelloWorld.js"; // prettier-ignore

const HelloWorld = props =>
  <RemoteComponent
    url={url}
    render={({ err, Component }) =>
      err ? <div>{err.toString()}</div> : <Component {...props} />
    }
  />
);

ReactDOM.render(<HelloWorld name="Paciolan" />, element);

React Hooks

If you need even more control, you can use useRemoteComponent React Hook.

import {
  createRequires,
  createUseRemoteComponent
} from "@paciolan/remote-component";
import { resolve } from "../../remote-component.config.js";

const url = "https://raw.githubusercontent.com/Paciolan/remote-component/master/examples/remote-components/HelloWorld.js"; // prettier-ignore
const requires = createRequires(resolve);
const useRemoteComponent = createUseRemoteComponent({ require });

const HelloWorld = props => {
  const [loading, err, Component] = useRemoteComponent(url);

  if (loading) {
    return <div>Loading...</div>;
  }

  if (err != null) {
    return <div>Unknown Error: {err.toString()}</div>;
  }

  return <Component {...props} />;
};

Prop Tables

RemoteComponent

Property Type Required Description
url String ✔️ Location of remote component bundle
fallback Component Component to render while loading
render render Render props function is called when exists

Creating Remote Components

src/index.js

Create src/index.js and expose your component as the default.

import React from "react";

const RemoteComponent = () => {
  return <div>Hello Remote World!</div>;
};

export default RemoteComponent;

webpack.config.js

The libraryTarget of the RemoteComponent must be set to commonjs.

Any dependencies that will not be bundled with the library must be added as an external.

It is recommended to add react as an external.

module.exports = {
  output: {
    libraryTarget: "commonjs"
  },
  externals: {
    react: "react"
  }
};

package.json

Set main to the webpacked entrypoint. This will probably be dist/main.js.

Dependencies you have marked as external should be removed from dependencies and added to both devDependencies (so they are available during development) and peerDependencies (so the upstream package knows it is responsible for installation).

{
  "main": "dist/main.js",
  "devDependencies": {
    "react": "^16.8"
  },
  "peerDependencies": {
    "react": "^16.8"
  }
}

How it works

The RemoteComponent React Component takes a url as a prop. The url is loaded and processed. This file must be a valid CommonJS Module that exports the component as default.

While the url is loading, the fallback will be rendered. This is a similar pattern to React.Suspense. If no fallback is provided, then nothing will be rendered while loading.

Once loaded, there will either be an err or a Component. The rendering will first be handled by the render callback function. If there is no render callback and err exists, a generic message will be shown.

The Component will be rendered either to the render callback if one exists, otherwise it will be rendered as a standard component.

Caveats

There are a few things to be aware of when using RemoteComponent.

  • Calls to a RemoteComponent add an additional HTTP call. Be aware of this and use wisely.
  • Dependencies could be included twice. If a dependency is included in the library and also in the Web App, there could be unknown effects.
  • The external dependencies of the library and Web Application must match. This makes upgrading 3rd party libraries that have breaking changes more complex.
  • The RemoteComponent and web application's browser targets must match.
  • Debugging could be more complicated as source map support does not (yet) exist.

Contributors

Joel Thoms (https://twitter.com/joelnet)

Icons made by Freepik from www.flaticon.com is licensed by CC 3.0 BY