Dynamically loading React components - javascript

I'm thinking about building a web application, where people can install plugins. I'd like plugins to be able to define React components that will be rendered to the page, without recompiling the main JavaScript bundle after installing the plugin.
So here's the approach I'm thinking of:
Bundle the main JavaScript with React as an external library, using webpack.
Have plugin authors compile their components with React as an external library as well.
This way, I'm only running one instance of React. I could probably do the same with some other frequently used libraries.
The problem then, is how to dynamically load these plugin components from the server. Let's say I have the following component:
class PluginRenderer extends React.Component{
componentWillMount() {
getPluginComponent(`/plugins/${this.props.plugin}/component.js`).then((com) => {
this.setState({pluginComponent: com});
})
}
render() {
var Plugin = this.state.pluginComponent;
return Plugin ? <Plugin {...this.props} /> : "Loading..."
}
}
How could getPluginComponent be implemented?

It's an interesting problem I also faced some months ago for customer work, and I didn't see too many document approaches out there. What we did is:
Individual plugins will be separate Webpack projects, for which we provide either a template or a CLI tool that generates project templates.
In this project we define Webpack externals for shared vendor libraries already used in the core application: React, Redux, etc. This tells the plugin to not include those in the bundle but to grab them from a variable in window we set in the core app. I know, sounds like sucks, but it's much better than having all plugins re-include 1000s of shared modules.
Reusing this concept of external, the core app also provides some services via window object to plugins. Most important one is a PluginService.register() method which your plugin must call when it's initialized. We're inverting control here: the plugin is responsible to say "hi I'm here, this is my main export (the Component if it's a UI plugin)" to the core application.
The core application has a PluginCache class/module which simply holds a cache for loaded plugins (pluginId -> whatever the plugin exported, fn, class, whatever). If some code needs a plugin to render, it asks this cache for it. This has the benefit of allowing to return a <Loading /> or <Error /> component when a plugin did not load correctly, and so on.
For plugin loading, this PluginService/Manager loads the plugin configuration (which plugins should I load?) and then creates dynamically injected script tags to load each plugin bundle. When the bundle is finished, the register call described in step 3 will be called and your cache in step 4 will have the component.
Instead of trying to load the plugin directly from your component, ask for it from the cache.
This is a very high level overview which is pretty much tied to our requirements back then (it was a dashboard-like application where users could add/remove panels on the fly, and all those widgets were implemented as plugins).
Depending on your case, you could even wrap the plugins with a <Provider store={ theCoreStore }> so they have to access to Redux, or setup an event bus of some kind so that plugins can interact with each other... There is plenty of stuff to figure out ahead. :)
Good luck, hope it helped somehow!

There is a HOC component that you can import to do this. Components are dynamically loaded as micro apps into your host application.
// index.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
window.React = React;
window.ReactDOM = ReactDOM;
ReactDOM.render(<App />, document.getElementById('root'));
// app.js
import React from 'react';
import ReactDOM from 'react-dom';
import MicroApp from '#schalltech/honeycomb-react-microapp';
const App = () => {
return (
<MicroApp
config={{
View: {
Name: 'redbox-demo',
Scope: 'beekeeper',
Version: 'latest'
}
}}
/>
);
});
export default App;
The components are not installed or known at design time. If you get creative, using this approach you could update your components without needing to redeploy your host application.
https://github.com/Schalltech/honeycomb-marketplace#using-micro-apps

I presented an alternative approach on a similar question. Recapping:
On your app
import(/* webpackIgnore: true */'https://any.url/file.js')
.then((plugin) => {
plugin.main({ /* stuff from app plugins need... */ });
});
On your plugin...
const main = (args) => console.log('The plugin was started.');
export { main };
export default main;
See more details on the other question's page.

Related

Share React-Native components

I am newbie in react, react-native and nodejs.
I tried create node module via npm init. In this module i created a component - for start styled button. I packed this via npm pack a link in application in package.json file by "file:../shared/_dist/shared-1.0.0.tgz" in dependency section.
in my shared index.js is
import MyButtonFirst from './components/buttons/MyButtonFirst';
module.exports = { MyButtonFirst };
in react application is
import React from 'react;
import { MyButtonFirst } from 'shared';
export default function MySharedButton()
{
return <MyButtonFirst />;
}
It works!
Then i tried create component which using react-native-async-storage/async-storage (via npm install in shared project). After increase version, npm pack, link and install new version of package I get error that AsyncStorage is null after android run.
Why AsyncStorage is null? Have I create dependecy in both projects? (that's a weird solution - it doesn't feel right to me, although it works)
How to share for example resources like icons, images etc.
We need to develop three applications on the same data (API) in the field of sports for different types of users (athlete, referee, administrator of the sports ground) and a lot of code we need to share - icons, contexts (user, theme etc...), error handling, API calls etc... We don't want develop it as one big rights-controlled application, but as several small applications for individual roles.
What is the best way how to share code between more react-native apps?
AsyncStorage is deprecated, see here.
You could create a start.js that links to different apps based on the feedback of your database (the roles of your users).
Else, it will route to a welcome component with, for example, a login and registration child-component.
import ReactDOM from "react-dom";
import Welcome from "./welcome";
import AppAth from "./app-ath";
import AppRef from "./app-ref";
import AppAdmin from "./app-admin";
fetch("api/users/role.json")
.then((res) => res.json)
.then((user_role) => {
if (user_role == "ath") {
ReactDOM.render(<AppAth />, document.getElementById("root"));
} else if (user_role == "ref") {
ReactDOM.render(<AppRef />, document.getElementById("root"));
} else if (user_role == "admin") {
ReactDOM.render(<AppAdmin />, document.getElementById("root"));
} else {
ReactDOM.render(<Welcome />, document.getElementById("root"));
}
})
.catch((err) => console.log(err));
Like that, you can keep the standard folder tree of an application in React and share all child-components, hooks and files in the public folder between them:
To keep the users separate, you store the role of the user in a cookie and check for the role on the server side.
If the cookie is empty, it will always lead back to the welcome component.
Side note: of course, the folder structure is always dependent on the bundler setup! So the folder structure of your app could differ from the one on the image.

Is it possible to load webpack externals without using a script tag?

Scenario:
I would like to embed React components in a web page as isolated react apps. As an example, lets say a date picker and also a colour picker.
I don't want to bundle React, or other common dependencies twice (or as many times as I end up using this approach) for each isolated bundle. I understand that I can use webpack externals to achieve this.
Why using webpack externals is not ideal
webpack externals means loading one version of React via a script tag and making it available as a global object. For the lifetime of the website I'm working on, I'd be stuck:
Using one version of React
Painfully updating all of the components if I want to use a new version
Ideal solution
Webpack bundles the individual applications into their own scope, if I package up different React versions without making them external it appears to work without issue as each instance is isolated.
Is there some way of configuring webpack to look for an external module (e.g. a specific version of React from a CDN) at runtime but crucially not attach it to the global namespace and load it within the scope of the bundled code?
The major benefit would be allowing React to be cached by the browser and avoiding adding the same download footprint every time I build a new component.
Thanks for your time and consideration.
Not 100% sure this is what you want but I am doing something similar:
I've got multiple webpack entries in a single config which render different react components. I then split out the common dependency react into a different chunk. This means that there are no externals polluting the global namespace. If I want to render at least one of these components, I need to include the react-common chunk as well as the very small file(s) containing the component(s).
The important parts of the webpack 5 config are like this:
{
entry: [
'my-component-1': './react-dir/entries/component-1.jsx',
'my-component-2': './react-dir/entries/component-2.jsx',
],
// ... loaders, plugins
optimization: {
splitChunks: {
cacheGroups: {
reactDependencies: {
test: /([\\/]node_modules[\\/](react[\\/]|react-(dom|toastify|transition-group))/, //defining all common react dependencies that should be split off because they are in more than one component
name: "react-common",
chunks: "all",
}
}
}
}
}
I then define my component entries like so:
//react-dir/entries/component-1.jsx`
import React from 'react';
import ReactDOM from 'react-dom';
import Component1 from '../components/Component1';
const container = document.getElementById("idOfContainerWhereComponentShouldBeRenderedInto");
ReactDom.render(<Component1 />, container);

Nuxt plugin imports abuse vendors

I am trying to use vuejs-datepicker in a nuxt app, everything is done by nuxt plugin usage standarts.
plugins/vue-datepicker.js
import Vue from 'vue'
import Datepicker from 'vuejs-datepicker'
Vue.component('Datepicker', Datepicker)
nuxt.config.js
plugins: [
{ src: '~/plugins/vue-datepicker', ssr: false }
],
But even when it is not used I am getting its dist uploaded in the vendors/app....js after the build. How can make nuxt create a separate chunk for it and import that chunk only in the pages which are using it?
So yeah, there is basically a feature request open for this kind of use-case.
But looking at the Nuxt lifecycle, it looks like the plugins are imported even before the VueJS instance is done. So, you cannot lazy load it if it's done ahead of Vue.
But, you can totally import vuejs-datepicker on the page itself, rather than on the whole project. This may be enough
import Datepicker from 'vuejs-datepicker' // then simply use `Datepicker` in the code below
If it's not, you can maybe try this solution: https://github.com/nuxt/nuxt.js/issues/2727#issuecomment-362213022
// plugins/my-plugin
import Vue from 'vue'
export default () => {
// ...
Vue.use(....)
}
// adminLayouts
import myPlugin from '~/plugins/my-plugin'
export default {
created() {
myPlugin()
}
}
So, the downside is that you have to import the component each time that you need it rather than having it globally but it also allows you to load it only on the concerned pages too and have it chunked per page/component.
If you were trying to find a way to split the component from vendor but you were getting a document is not defined error you can use this syntax to import your component, it will create a separate chunk with your component and use it only in client-side.
components: {
Datepicker: () => import('vue-datepicker')
}
Also, it would be helpful to wrap your component in <client-only> tag for most of the cases.
The plugin I was trying to import used window. For this reason, any other suggested workaround still caused nuxt to crash or error in my case. I searched the whole wide web and the solution below is the only one that allows my app to run.
Instead of importing the plugin with an ES6 import, you can import it in your mounted hook, which should run in the client only. So:
async mounted() {
const Datepicker = await import('vuejs-datepicker');
Vue.use(Datepicker);
}
I do not know about the specific plugin you are trying to use, but in my case I had to call Vue.use() on the default property of the plugin, resulting in Vue.use(MyPlugin.default).

Is importing only required components per component a good idea in Vue

I was wondering if it is beneficial for performance in Vue to only import necessary dependencies per component or if all dependencies that are used in some components should just be loaded globally? How does Vue compile the components? Is it the case that all of them are loaded anyway when one page of the app is loaded, or are components also loaded on-the-go?
More concrete:
Is it better to do this:
<template>
... Some template code
</template>
<script>
import { MdDialog, MdContent, MdButton } from 'vue-material/dist/components'
export default {
...
}
</script>
Or is it better to import these things globally in app.js, even if some components only use a fraction of them?
This should be a matter of preference.
It won't have a noticeable impact on performance as the build process handles these multiple imports.
If you chose to import locally you will see where things come from... Otherwise on larger codebase it could lead to a lot of confusion.
Another thing is if you decide to make async component import. If the imports are only used in the dynamically imported component they should come with it's chunk, otherwise if they get imported in more than one component local importing would mean code duplication...
I hope I was helpful.

How to include third party js libraries in React app

I am new to React and am looking for the React equivalent of this JQuery approach to including analytics throughout my application.
Typically I would:
Include the 3rd party library on the html pages. Easy enough to put on the index.html page but I don't know if that is best practice.
<script src="http://path/to/script/utag.js" />
Then I can interact with the library as long as it has loaded, which I can verify using JQuery window.load. This script will run fine on a plain html page, but I am trying to find the equivalent best practice way of doing this in my react app. I don't want to introduce jquery and currently my React container will tell me that utag is not defined if I try referencing utag in a function.
<script>
$(window).load(function() {
utag.link({ "event_name" : "locale_select", "language" :
utag_data.language, "currency" : utag_data.currency } );
});
</script>
I'm new to React so any help would be great. I know that my project is not using webpack, it's using react-scripts and was started using the create-react-app utility.
According to this issue on GitHub if you are using create-react-app, if you want to use global variables that imported or created in your index.html file in your react script, you must use window.variable_name.
In your case, this will probably work
import React from "react"
class App extends React.Component {
componentDidMount() {
window.utag.link({ "event_name" : "locale_select", "language" :
window.utag_data.language, "currency" : window.utag_data.currency } );
}
render() {
return <div />;
}
}
import someLibrary from 'some-library';
class App extends React.Component {
componentDidMount() {
someLibrary();
}
render() {
return <div />;
}
}
Its important to understand the statement inside the Component offered above. componentDidMount() is a lifecycle method that gets called automatically after the Component has been rendered to the screen. Then inside of it you call your someLibrary(). Depending on what type of third-party library you are talking about will dictate what you may need to also pass into someLibrary().
This is how Reactjs interacts with third-party libraries, because typically third-party libraries do not know how to be in a React ecosystem. They don't have any idea what a render() method is or what JSX is. So this is the general way of making third-party libraries work nicely with React.
If you are using create-react-app, then webpack is being used to bundle your javascript. Here's the documentation on installing dependencies with create-react-app.
To include you library, you should install it as an npm package, and import it into the file where you want to use it. Webpack will include it in the bundle and everything should just work.
So, Install the library with npm install some-library. Import it into a file and call it from a component:
import someLibrary from 'some-library';
class App extends React.Component {
componentDidMount() {
someLibrary();
}
render() {
return <div />;
}
}

Categories