Updating SystemJS from 0.21 to 6.x with webpack - javascript

There a UMD bundle with React application that is hosted on CDN and is loaded dynamically when needed. React and ReactDOM are not bundled and it's expected that environment will have them available. The whole application is built using Webpack. I have the following code with SystemJS#0.21 that works:
import React from 'react';
import ReactDOM from 'react-dom';
import SystemJS from 'systemjs/dist/system-production'; // 0.21
SystemJS.registry.set('React', SystemJS.newModule(React));
SystemJS.registry.set('ReactDOM', SystemJS.newModule(ReactDOM));
const URL = 'https://example.com/bundle.js'; // UMD bundle
class App extends React.Component {
componentDidMount() {
SystemJS.import(URL).then(module => this.setState({ module }));
}
render() {
const Component = this.state.module?.default;
return Component ? <Component /> : null;
}
}
Is there a way to make it work with SystemJS#6.0, or load any other way? It works now but using old version sits uneasily with me. Changing format of downloaded package from UMD to SystemJS is next to impossible because there's a lot of consumers that I don't control.

I managed to get SystemJS 6.6.1 working in an Angular project in order to laod a UMD bundle.
You can find the details over here: https://stackoverflow.com/a/63917606/2528609.
The most important part, I think, is that you need to load an additional script to handle UMD modules => "node_modules/systemjs/dist/extras/amd.js"

Related

react context does not work when imported from relative part

I have monorepo with a UI library inside packages which contains a lot of components. Every component also has a *.stories.tsx file. Under the apps folder is the storybook project, which loads the stories from #my-ui-library.
I have a decorator in the storybook config, so my ThemeContextProvider is wrapped around everything (ThemeContextProvider is also imported from #my-ui-library).
Does not work:
button.tsx
import { ThemeContext } from '../../ThemeContext'
Does work:
button.tsx
import { ThemeContext } from '#my-ui-library'
Using the relative import I get the error that theme, which is stored in ThemeContext, is undefined.
Now, that itself wouldn't be a problem if VS Code wouldn't auto import from the relative path and I didn't have to change it manually everywhere.

How webpack reads what to import?

I was wondering and trying to find out how webpack internally finds out which are the import statements a entry file has?
For example my index.js looks like
import React, { PropTypes, Component } from 'react';
import { findDOMNode } from 'react-dom';
import classnames from 'classnames';
import Loading from 'components/Loading';
import Button from 'components/Button';
import Header from 'components/Header';
import Footer from 'components/Footer';
class App extends Component {
render() {
return (
<div className="shopping-list">
<Header />
<h1>Shopping List for {this.props.name}</h1>
<ul>
<li>Instagram</li>
<li>WhatsApp</li>
<li>Oculus</li>
</ul>
<Footer />
</div>
);
}
};
export default App;
Now I am trying to understand how webpack is finding out what are the other imports that file has? And then one of the imported files has other imported files?
Basically, Webpack is build tool and it is design to convert your app into optimized solution like compressing the js/scss/css and many more files.
Webpack can be configurable according to your choice and there are many loaders and plugins developed, you can use them to add support for that type of file.
It's like telling a system to take list of source files (input) and compiling them by using related webpack plugin or loader and giving optimized solution as output.
by webpack configuration, you tell it that load this type of file and generate output here and it normally convert your advanced source code to native source code so, it can work on any system.
for example,
we know that scss file is not supported by browsers, we need to convert it into css and then we can import it, but using scss loader plugin and by configuring it with webpack you can directly use scss file no need to manually convert scss to css and directly can import scss file into component and webpack will read the file, see the type and convert it for us.
configuring the webpack is the difficult part but there are pretty good pre-configured solution available to use like create-react-app where you don't need to worry about the configurations
for more info see the webpack documentation

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 />;
}
}

Dynamically loading React components

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.

External react component cannot see React.Component

The external React component says Uncaught TypeError: Cannot read property 'Component' of undefined when I link as npm package.
I link a package in package.json as "react-mapbox": "https://github.com/varya/react-mapbox.git". Then I use it in code
import {render} from 'react-dom';
import MapBox from "react-mapbox";
render(
<div>
<h1>Hello, world!</h1>
<MapBox />
</div>
,
document.getElementById('example')
);
But nothing works, I get that error. The full repo is here https://github.com/varya/react-mapbox-test I made a small example to illustrate.
The react-mapbox package is my own, maybe I build it wrongly? This is its repo https://github.com/varya/react-mapbox
I built it with webpack, like that https://github.com/varya/react-mapbox/blob/master/webpack.config.js As I suppose, this build does not include react package assuming that this will be at the project which link it. But the component still do not see react object.
UPD
I added import React from 'react'; as #aulizko suggested, but this only provided React object onto a page. It still was not visible for the component.
To fix this I had to provide this changes https://github.com/varya/react-mapbox/commit/2687d66025aaa84553a79850aa8686e88f1f39d9
I required react in the code of the component as well.
And I have to build with Babel. For some reason the webpack build gives the same error even if react is required. I created a branch to illustrate this https://github.com/varya/react-mapbox/tree/webpack Now I'm happy with Babel, but with this branch you can see what's wrong with webpack if interested.
You're probably bundling your module as UMD which is causing the bundle to utilized a global React variable which doesn't exist in the consumer app. You need to export the bundle as a CommonJS or AMD module using https://webpack.github.io/docs/configuration.html#output-librarytarget. Simple add libraryTarget: 'commonjs2 or libraryTarget: 'amd' to the output key in the webpack config and make sure you are importing react in your component.
I added import React from 'react'; as #aulizko suggested, but this only provided React object onto a page. It still was not visible for the component.
To fix this I had to provide this changes https://github.com/varya/react-mapbox/commit/2687d66025aaa84553a79850aa8686e88f1f39d9
I required react in the code of the component as well.
And I have to build with Babel. For some reason the webpack build gives the same error even if react is required. I created a branch to illustrate this https://github.com/varya/react-mapbox/tree/webpack Now I'm happy with Babel, but with this branch you can see what's wrong with webpack if interested.
The thing is that the jsx-code that you see in your editor is not the code that will be executed by node or browser.
If you look into code that are generated by the babel, you'll see something like this:
(0, _reactDom.render)(React.createElement(
'div',
null,
React.createElement(
'h1',
null,
'Hello, world!'
),
React.createElement(_reactMapbox2['default'], null)
), document.getElementById('example'));
So as you can see it uses React constant under the hood.
You need to explicitely import React if you want to use jsx-code.
Add something like this in your code and it'll work:
import React from 'react'; // <!--- add this!!!
import {render} from 'react-dom';
import MapBox from "react-mapbox";
// the rest of your code goes here...
You have to import React's Component it this way:
import {Component} from 'react';
or even:
import React, { Component, PropTypes } from 'react';

Categories