npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@ifaya/remote-component

v0.0.1-semantic-versioning

Published

Dynamically load a React Component from a URL

Downloads

3

Readme

Remote Component coverage:100%

remote-component

Load a React Component from a URL at runtime.

Table of Contents

What is a Remote Component?

A Remote Components is loaded at runtime from a URL. It is used in the same way any other React Component is used.

const url =
  "https://raw.githubusercontent.com/Paciolan/remote-component/master/examples/remote-components/HelloWorld.js";

const HelloWorld = ({ name }) => <RemoteComponent url={url} name={name} />;

const Container = (
  <>
    <HelloWorld name="Remote" />
  </>
);

Install

npm install @paciolan/remote-component

Dependencies

The React Application and Remote Component can share dependencies. The dependencies must be configured explicitly.

Shared dependencies in the Remote Component must be marked as external so they are not bundled in the output.

All shared dependencies must be provided by the React Application.

Injecting Dependencies with Webpack

Create a file in the root of your React Application called remote-component.config.js. Some frameworks like Create React App (CRA) might need this file placed inside the src directory. The location can be changed inside of webpack.config.js.

This file will supply the Remote Components with their needed external dependencies.

/**
 * Dependencies for Remote Components
 */
module.exports = {
  resolve: {
    react: require("react")
  }
};

Add a Webpack alias inside of webpack.config.js so the RemoteComponent can load this file.

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

Injecting Dependencies without Webpack

Projects without webpack can still use a Remote Component through a manual configuration.

Follow the directions in Injecting Dependencies with Webpack to create the remote-component.config.js.

Create src/RemoteComponent.js and import the dependencies from remote-component.config.js.

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

const requires = createRequires(resolve);

export const RemoteComponent = createRemoteComponent({ requires });

Then you will change the import for RemoteComponent to point to this new file.

import { RemoteComponent } from "./RemoteComponent";

Custom Fetcher

The Custom Fetcher is a feature for advanced users only. It exposes the fetcher from the underlying @paciolan/remote-module-loader.

Refer to @paciolan/remote-module-loader documentation for more information about how to use the fetcher.

const fetcher = url => axios.get(url).then(request => request.data);

fetchRemoteComponent({ url, requires, fetcher });

Adding a Remote Component to a React App

Import RemoteComponent from either @paciolan/remote-component or your custom ./src/RemoteComponent.js (depending on your setup).

It is recommended to wrap <RemoteComponent /> in a component for better naming and separation. This is optional.

Pass the url to the <RemoteComponent />.

Use a RemoteComponent like a regular React Component.

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

Render Props

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

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

React Hooks

If you need even more control, you can create a custom useRemoteComponent React Hook.

Start by creating src/useRemoteComponent.js.

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

const requires = createRequires(resolve);

export const useRemoteComponent = createUseRemoteComponent({ requires });

Next, use the custom hook.

import { useRemoteComponent } from "./useRemoteComponent";

const url = "https://raw.githubusercontent.com/Paciolan/remote-component/master/examples/remote-components/HelloWorld.js"; // prettier-ignore

const HelloWorld = props => {
  const [loading, err, Component] = useRemoteComponent(url);
  
  // To use a named import, pass the name in as the second argument.
  // const [loading, err, Component] = useRemoteComponent(url, "customImportName");

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

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

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

Creating a Remote Component

Creating a Remote Component involves creating a CommonJS module. That module should have react and other shared dependencies excluded from the bundle. It should also already be transpiled for browser support.

Remote Component Starter Kit

Clone the remote-component-starter for a ready to go project.

Creating a Remote Component with Webpack

The Remote Component must be exported.

import React from "react";

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

export default RemoteComponent;

Inside of the webpack.config.js, the libraryTarget must be set to commonjs.

Any shared dependencies must be added as an external. This will prevent them from being bundled in the library.

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

Inside of the package.json, set main to the webpack entrypoint. This will probably be dist/main.js.

Shared 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).

The dependency version should match the version inside the React Application.

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

Create React App (CRA)

Start a new Create React App app or use an existing one. This is a React Application that will import Remote Components.

$ npx create-react-app my-react-app
$ cd my-react-app

Create src/remote-component.config.js. note: CRA requires this file to be placed inside of src.

/**
 * These dependencies will be made available to the Remote Components.
 */
module.exports = {
  resolve: {
    react: require("react")
  }
};

Create src/RemoteComponent.js. The RemoteComponent must manually be created because CRA does not provide access to webpack.config.js without ejection.

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

const requires = createRequires(resolve);

export const RemoteComponent = createRemoteComponent({ requires });

You may see the following warning. It is safe to ignore.

Compiled with warnings.

./node_modules/@paciolan/remote-component/dist/getDependencies.js
Module not found: Can't resolve 'remote-component.config.js' in '/et/repo/cra-remote-component/node_modules/@paciolan/remote-component/dist'

You can get rid of this warning by directly including the files. This is unsafe as these files paths could change.

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

const requires = createRequires(resolve);

export const RemoteComponent = createRemoteComponent({ requires });

Server Side Rendering with Next.js

Server Side Rendering with Next.js is currently (EXPERIMENTAL).

Follow the steps in Injecting Dependencies with Webpack to create the remote-component.config.js.

getServerSideProps

Add a getServerSideProps method to your Remote Component. This follows the Next.js pattern.

import React from "react";

const Person = ({ data }) => {
  const entries = Object.entries(data);
  const rows = entries.map(([key, value], i) => (
    <tr>
      <th>{key}</th>
      <td>{value}</td>
    </tr>
  ));

  return <table>{rows}</table>;
};

const getServerSideProps = async ({ data }) => {
  const response = await fetch(`https://swapi.dev/api/people/${data.id}`);
  return await response.json();
};

Person.getServerSideProps = getServerSideProps;

export default Person;

Calling getServerSideProps from Next.js

Modify the Next.js page that will contain the Remote Component.

Add these imports. Notice how getServerSideProps is renamed to getProps to prevent conflicts with the Next.js function of the same name.

import {
  createRequires,
  fetchRemoteComponent,
  getServerSideProps as getProps
} from "@paciolan/remote-component";
import dynamic from "next/dynamic";
import config from "../remote-component.config";

Create the requires for shared dependencies that will be provided to the Remote Component. Then pass url and requires into fetchRemoteComponent. Wrap this inside of dynamic.

const requires = createRequires(config.resolve);

const url = "http://localhost:5000/MyRemoteComponent.js";
const MyRemoteComponent = dynamic(() =>
  fetchRemoteComponent({ url, requires })
);

Create Next.js's getServerSideProps function. Pass the Next.js context (if the component needs the context) as well as any data in as context when calling getProps.

export async function getServerSideProps(context) {
  const data = { id: 1 };
  const myData = await getProps({
    url,
    requires,
    context: { ...context, data }
  });
  return { props: { myData } };
}

The props returned from Next.js's getServerSideProps function will be passed into the props. You can then use those props to send the data into the Remote Component.

export default function MyPage(props) {
  return (
    <div>
      <MyRemoteComponent data={props.myData} />
    </div>
  );
}

How it works

The RemoteComponent React Component takes a url as a prop. The url is loaded and evaluated. 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.

Content Security Policy (CSP)

Sites with a content_security_policy header set are likely to not work. CSP puts a restriction on using new Function, which remote-module-loader relies upon.

This library depends on @paciolan/remote-module-loader, which does not support CSP. Until CSP is supported in @paciolan/remote-module-loader, it cannot be supported.

Read more on CSP

Alternatives

Roadmap

  • Add support for multiple components import from a single URL.
  • Add TypeScript support

Caveats

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

  • Calls to a RemoteComponent add an additional HTTP call.
  • Dependencies could be included twice. If a dependency is included in the library and also in the Web App. This could have 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. If your React App targets IE11, but the Remote Component does not, then it will not work in IE11.
  • Debugging could be more complicated as source map support does not (yet) exist.
  • Nested RemoteComponents can get exponentially hard to manage (dependencies) and develop (running multiple repositories at the same time for localhost)
  • Content Security Policy (CSP) is not supported.

Contributors

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

Icons made by smalllikeart from www.flaticon.com

Icons made by turkkub from www.flaticon.com

Icons made by Freepik from www.flaticon.com