How to load a component separately from webpack bundles javascript? - javascript

I want to load a component to my React application externally from my webpack bundled file. To elaborate my point, here is a sample HTML file that I want to have:
<!doctype html>
<html>
<head> ... </head>
<body>
<div id="app"></div>
<script src="/dist/app.js"></script>
<script src="/external-component/timer-component.js"></script>
</body>
</html>
I basically want to be able to load timer component but still make that component to be dependent on app.js (webpack bundled file) since app.js has React. In other words, I want timer component to know that React exists. My current timer-component file is the following (taken from ReactJS website):
class Timer extends React.Component {
constructor(props) {
super(props);
this.state = { secondsElapsed: 0 };
}
tick() {
this.setState(prevState => ({
secondsElapsed: prevState.secondsElapsed + 1
}));
}
componentDidMount() {
this.interval = setInterval(() => this.tick(), 1000);
}
componentWillUnmount() {
clearInterval(this.interval);
}
render() {
return React.createElement(
"div",
null,
"Seconds Elapsed: ",
this.state.secondsElapsed
);
}
}
I keep getting error that React doesn't exist. If I try to import React:
const React = require('react');
// or
import React from 'react';
I get an error that require (or import) is undefined. What's the proper way to do it? My understanding is that webpack app.js file kind of creates namespaces, so React component does not really see the outside world. Only the components inside webpack file see React. If this is the case, how can I expose react to the outside world?

Somewhere in your app.js, declare react as a global variable.
window.React = React
This way you can access it from other components.
Another way to do this would be externalizing react from the webpack build and including it via a script tag in the page.
To externalize react, include this in your webpack config
externals: {'react': 'React'}
Include this in your html to access react globally.
<script src="https://unpkg.com/react#15/dist/react.js"></script>
<script src="https://unpkg.com/react-dom#15/dist/react-dom.js"></script>

I think you want to use the "script-loader" plugin for Webpack. I use it to do just this and it works great.
https://github.com/webpack/script-loader
It's a great way to keep all the stuff out of index.html and put all your requires in one place, e.g. my seed project index.js has all this in it, and in other projects all I have to do is add scripts here when I need. They don't even have to be npm installed, you can just have a script in your project and load it into the bundle this way.
(One of my projects using webpack's script-loader)
require('!!script!core-js/client/shim.js');
require('!!script!zone.js/dist/zone.js');
require('!!script!reflect-metadata/temp/Reflect.js');
require('!!script!rxjs/bundles/Rx.js');
require('!!script!#angular/core/bundles/core.umd.js');
require('!!script!#angular/common/bundles/common.umd.js');
require('!!script!#angular/compiler/bundles/compiler.umd.js');
require('!!script!#angular/platform-browser/bundles/platform-browser.umd.js');
require('!!script!#angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js');
require('!!script!#angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js');
require('!style!css!./css/styles.css');
require('!style!css!./css/animate.css');
require('!style!css!bootstrap/dist/css/bootstrap.css');
require('./app/main');

Related

How to use React components as part of legacy HTML/JS app?

I have an old web application running with HTML/JS.
I have created a new react application and my goal is to start developing new features / pages using React, and implement them in the current application.
The main goal is to use React components outside react, for example:
<html>
<head>
<title> My old web applciation</title>
// Imports for my react app / components
</head>
<body>
<!-- Old staff -->
<Footer someProp="1"/>
</body>
</html>
What I have so far:
For full-page features I used Iframe (and created a route in React for it)
Using element ID, e.g::
and in react:
const GiveItem = ({ ...props }) => {
console.log("init give item component");
return (
<section>
Give Item
</section>
);
};
try {
const domContainer = document.querySelector('#giveItem');
render(e(GiveItem), domContainer);
} catch (e) {
console.error('Failed to render give item', e);
}
export default GiveItem;
Is there any legitimate way to "export" react components to HTML (outside react) and use them as native components like:
<GiveItem someProp="1"/>
Using Vue I managed to do it, just by wrapping the highest-level element with:
<div id="vapp">
.... My old code
<VueComponent />
So back to the question - How I can show/use React components in different parts of legacy app as native HTML components?
Thanks!
I will divide my answer into 3 parts:
simple inclusion of react in general
make your example work
modules
(I will use yarn below. You can use npm as well, I assume you are familiar with these.)
1. simple inclusion of react in general
See Add React to a Website.
index.html:
<html><head>
<title>My old web applciation</title>
</head><body>
<h1>old stuff</h1>
<p>Some old HTML content.</p>
<!-- add a container, where you want to include react components -->
<div id="injected-react-content"></div>
<!-- import the react libraray -->
<script src="https://unpkg.com/react#16/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom#16/umd/react-dom.development.js" crossorigin></script>
<!-- import your react component -->
<script src="GiveItem.js"></script>
</body></html>
GiveItem.js:
(modified from JSX to JS. For JSX see section 2.)
const GiveItem = (props) => {
console.log("init give item component");
return React.createElement(
"section",
null,
"Give item content"
);
};
const domContainer = document.querySelector('#injected-react-content');
ReactDOM.render( React.createElement(GiveItem), domContainer );
2. make your example work
Your component uses JSX. You need to transpile it to JS, using babel.
GiveItem.jsx:
(it is not necessary to call it .jsx, but it makes sense)
const GiveItem = (props) => {
return (
<section>
Give Item
</section>
);
};
const domContainer = document.querySelector('#injected-react-content');
ReactDOM.render( React.createElement(GiveItem), domContainer );
Install babel:
yarn add #babel/cli #babel/core #babel/plugin-transform-react-jsx #babel/preset-react --dev
Transpile your JSX file ./src/GiveItem.jsx into a JS file in ./dist/GiveItem.js:
yarn babel ./src/GiveItem.jsx --presets=#babel/preset-react --out-dir dist
(the dist folder will be created if it doesn't exist)
If you now copy the index.html into ./dist,
you should have the same code as in section 1.,
and opening ./dist/index.html in the browser should work.
3. modules
Of course, you would want to import other react components and sub-components.
(And you probably don't want to import everything individually with <script> tags.)
if an environment is set up already
Maybe your old app already runs in a yarn (or npm) environment, then you might be fine with
install yarn add react and yarn add react-dom
removing the two <script src="https://unpkg.com/react... lines (as react is probably already imported inside your components),
then import some root component (instead of GiveItem.js before),
where you do the ReactDOM.render(...) and import further modules:
E.g.:
index.html:
<!-- import your react component -->
<script type="module" src="EntryIntoReactWorld.js"></script>
EntryIntoReactWorld.js:
import SomeComponent from './SomeComponent';
import SomeOtherComponent from './SomeOtherComponent';
const ReactRoot = props => {
return React.createElement(
"div",
null,
React.createElement(SomeComponent, null),
React.createElement(SomeOtherComponent, null)
);
};
ReactDOM.render(
React.createElement(ReactRoot),
document.querySelector('#injected-react-content')
);
if environment is not set up already
If your old app is not already running in a yarn (or npm) environment, you have to set one up.
Unfortunately, this is not trivial.
(I tried to write a short working minimal example here, but I failed.)
For that you have to enter the world of Javascript modules.
But modern browsers don't allow many things from the local file system.
(e.g. see CORS problems, MIME problem, ...)
You have to install a server for development. You might start with e.g. http-server, but there you probably still will have problems, so I suggest to already start with writing a server.js file and use node http.
(Maybe expressjs as well. It is not necessary, but everybody uses it, and it is easier to find help and tutorials.)
You also might need webpack.
Of course you also have to install yarn add react and yarn add react-dom.

How to load a React.Component from a CDN and render into another React.Component

Note: None of the answers actually work [DO NOT DELETE THIS NOTE]
simple question, I got a project,
npx create-react-app react-project (consider this Project Y)
now, inside this project's App.js
import React, { Component } from 'react'
export default class App extends Component {
render() {
return (
<div>
HELLO
</div>
)
}
}
now in CDN I have another Comp.js (Consider this Project X)
https://codepen.io/sirakc/pen/ZEWEMjQ.js
import React, { Component } from 'react'
export default class Comp extends Component {
render() {
return (
<div>
WORLD
</div>
)
}
}
now I want to show the Comp.js into App.js as if you are taking it from local source folder
so
import React, { Component } from 'react'
//somehow somewhere import Comp.js and then <Comp/>
export default class Comp extends Component {
render() {
return (
<div>
HELLO <Comp/>
</div>
)
}
}
and ofc the output should be
HELLO WORLD
when I run the project react-project and if in the CDN I change WORLD to EARTH it should change WORLD to EARTH in the output as well
so now react-project's output is HELLO EARTH
I am putting all my rep into a bounty for this, upvote if you like this question to help me attract attention.
NOTE: my need is to show React project X inside React project Y without touching much of project Y and ofc update the project X without updating anything inside project Y, so yea the <script src='chunk.js'/> isn't gonna work here, the chunk name changes, if you can find a way to not make it change, then its great, do share. If you know a working way to do this bundled into chunk.js DO SHARE!
ANY WAY OF DOING THIS IS WELCOMED, as long as Project X is independent of Project Y and I can make changes to Project X without changing Project Y
There are a few options you have at hand.
Option 1 - Create a NPM Package
Turn Project X into a module.
This will mean you will go to Project X development folder, and run npm login and npm publish. You can read more about that here
After that, once your package is on NPM You can go to Project Y and do the following:
import React, { Component } from 'react'
import Comp from 'my-package'
export default class Comp extends Component {
render() {
return (
<div>
HELLO <Comp/>
</div>
)
}
}
Option 2 - Load a bundled JS
Instead of having your script load the following:
import React, { Component } from 'react'
export default class Comp extends Component {
render() {
return (
<div>
WORLD
</div>
)
}
}
This is JSX Syntax. And it cannot be read in plain Vanilla JS - thus you cannot just import it like <script src="myscript.js" /> since this is not valid JS without a parser like Babel.
I would go to Project X and run npm run build. After that I would get the bundle.js - bundled and minified script written in Plain JS. It would look something like this:
(this.webpackJsonpchrome_extension=this.webpackJsonpchrome_extension||[]).push([[2],[function(e,t,n){"use strict";e.exports=n(99)},,function(e,t,n){"use strict";function r(){return(r=Object.assign||function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e}).apply(this,arguments)}n.d(t,"a",(function(){return r}))},function(e,t,n){"use strict";function r(e,t){if(null==e)return{};var n,r,i={},o=Object.keys(e);for(r=0;r<o.length;r++)n=o[r],t.indexOf(n)
Basically non-human readable code, which is parsable by <script src="myscript.js" /> tag. And then you would have to go to your index.html and inject it there, or use some of modules like react-script-tag
I would highly highly recommend going with Option #1. Since this is the preferred way to go. Look into creating NPM packages from your React project, and follow step by step.
Some more useful links about Option #1:
Create a simple React npm package in simple steps using CRA
How to publish your React component on npm
Hope this will guide you in the right direction, but the current way you are doing it is a no-go
EDIT - Tehnically there is an option #3, but I wouldn't recommend it.
Option 3 - Make your url provide just JSX, and load it in dangerouslySetInnerHtml.
Let's say your https://codepen.io/sirakc/pen/ZEWEMjQ.js would provide with this only:
<div>
WORLD
</div>
Technically, you could then turn your link into something more readable, like .txt extension, fetch it, store it in a variable, and load it in dangerouslySetInnerHTML.
for example:
const otherProjectJSX = somehowLoad(https://codepen.io/sirakc/pen/ZEWEMjQ.js)
const MyApp = () => <div dangrouslySetInnerHtml={otherProjectJSX} />
Basically it would be this:
const otherProjectJSX = '<div>WORLD</div>'
const MyApp = () => <div dangrouslySetInnerHtml={otherProjectJSX} />
I would not recommend this, but if it is only what is inside render() you care about - it might work after all.

Importing javascript file for use within vue component

I am working on a project that requires using a js plugin. Now that we're using vue and we have a component to handle the plugin based logic, I need to import the js plugin file within the vue component in order to initialize the plugin.
Previously, this was handled within the markup as follows:
<script src="//api.myplugincom/widget/mykey.js
"></script>
This is what I tried, but I am getting a compile time error:
MyComponent.vue
import Vue from 'vue';
import * from '//api.myplugincom/widget/mykey.js';
export default {
data: {
My question is, what is the proper way to import this javascript file so I can use it within my vue component?
...
Include an external JavaScript file
Try including your (external) JavaScript into the mounted hook of your Vue component.
<script>
export default {
mounted() {
const plugin = document.createElement("script");
plugin.setAttribute(
"src",
"//api.myplugincom/widget/mykey.js"
);
plugin.async = true;
document.head.appendChild(plugin);
}
};
</script>
Reference: How to include a tag on a Vue component
Import a local JavaScript file
In the case that you would like to import a local JavaScript in your Vue component, you can import it this way:
MyComponent.vue
<script>
import * as mykey from '../assets/js/mykey.js'
export default {
data() {
return {
message: `Hello ${mykey.MY_CONST}!` // Hello Vue.js!
}
}
}
</script>
Suppose your project structure looks like:
src
- assets
- js
- mykey.js
- components
MyComponent.vue
And you can export variables or functions in mykey.js:
export let myVariable = {};
export const MY_CONST = 'Vue.js';
export function myFoo(a, b) {
return a + b;
}
Note: checked with Vue.js version 2.6.10
try to download this script
import * from '{path}/mykey.js'.
or import script
<script src="//api.myplugincom/widget/mykey.js"></script>
in <head>, use global variable in your component.
For scripts you bring in the browser way (i.e., with tags), they generally make some variable available globally.
For these, you don't have to import anything. They'll just be available.
If you are using something like Webstorm (or any of the related JetBrains IDEs), you can add /* global globalValueHere */ to let it know that "hey, this isn't defined in my file, but it exists." It isn't required, but it'll make the "undefined" squiggly lines go away.
For example:
/* global Vue */
is what I use when I am pulling Vue down from a CDN (instead of using it directly).
Beyond that, you just use it as you normally would.
I wanted to embed a script on my component and tried everything mentioned above, but the script contains document.write. Then I found a short article on Medium about using postscribe which was an easy fix and resolved the matter.
npm i postscribe --save
Then I was able to go from there. I disabled the useless escape from eslint and used #gist as the template's single root element id:
import postscribe from 'postscribe';
export default {
name: "MyTemplate",
mounted: function() {
postscribe(
"#gist",
/* eslint-disable-next-line */
`<script src='...'><\/script>`
);
},
The article is here for reference:
https://medium.com/#gaute.meek/how-to-add-a-script-tag-in-a-vue-component-34f57b2fe9bd
For anyone including an external JS file and having trouble accessing the jQuery prototype method(s) inside of the loaded script.
Sample projects I saw in vanilla JS, React and Angular were simply using:
$("#someId").somePlugin(options)
or
window.$("#someId").somePlugin(options)
But when I try either of those in my VueJS component I receive:
Error: _webpack_provided_window_dot$(...).somePluginis not a function
I examined the window object after the resources had loaded I was able to find the jQuery prototype method in the window.self read-only property that returns the window itself:
window.self.$("#someId").somePlugin(options)
Many examples show how to load the external JS file in VueJS but not actually using the jQuery prototype methods within the component.

Use react without a router component

If I want to make a web application using reactjs that is not a single page.
Should I compile all the react code into a single file and load it on all pages of the application, then use the function that I expose to render the necessary components?
Example of an html file
<div id="Clock" data-react="Clock"></div>
<div id="HelloWorld" data-react="HelloWorld"></div>
example of index.js
import React from 'react';
import ReactDOM from 'react-dom';
import Clock from './Clock';
import HelloWorld from './HelloWorld';
import OtherComponent from './OtherComponent';
const APPS = {
Clock,
HelloWorld,
OtherComponent
};
const MyReactRender = react => {
let component = react.getAttribute('data-react');
let App = APPS[component];
if(App != undefined) {
ReactDOM.render(<App />, document.getElementById(component));
}
}
document.querySelectorAll('[data-react]').forEach(MyReactRender);
I'd see two ways, of increasing quality and difficulty. In both cases, you use good old anchors elements to redirect the page to a url, to which different templates correspond.
Manually check for the existence of divs id's
In this case, each template includes the same javascript bundle that contains everything in the app and a single element with an id corresponding to the specific component. The idea is to check wether or not an element is present in the page, then activate its corresponding react component if it is.
if (document.getElementById('component-root')) {
ReactDOM.render(<Component />, document.getElementById('component-root'));
}
On the up side, it's quite easily implemented. On the down side, the bundle will always get bigger and bigger, and the list of ifs grows each time you add a new "page".
Separate your modules in actual bundles
Different bundle managers exist, but I'd recommend using Webpack to create multiple bundles that contain only specific part of your application. Then, each template contains only the corresponding div element, as well as that specific bundle.
<head><script src="/js/clock.js"></head>
<body><div id="root-clock"></div></body>
<head><script src="/js/otherComponent.js"></head>
<body><div id="root-other-component"></div></body>
How to package multiple bundles with webpack is out of the scope of this answer, but look here.
I've tried making a react application without a router. I used ternary operators to switch from component to component.
// App Component
class App extends Component {
constructor(props) {
super(props)
this.state = {
inClockComponent: true,
inHelloWorldComponent: false,
inOtherComponent: false
}
}
render() {
const {inClockComponent, inHelloWorldComponent, inOtherComponent} = this.state
return (
<div>
{
inClockComponent
? <Clock> : inHelloWorldComponent
? <HelloWorld> : inOtherComponent ? <OtherComponent> :
<div>No Component Here</div>
}
</div>
}
You could pass a function from the App component that would change the display state to each child component of App
Example
// in App Component
showHelloWorldComponent() {
this.setState({
inClockComponent: false,
inHelloWorldComponent: true,
inOtherComponent: false
)}
}
You insert that function onto a button that would navigate to a different component
Example
// in Clock Component
render() {
return (
<div>
<h2>Time is 5:15 P.M.</h2>
<button onClick={this.props.showHelloWorldComponent}>
Go To Hello World
</button>
)
}
It's a messy solution, and I wouldn't suggest using it in a big application, but I hope this answers your question!

How do you import a javascript package from a cdn/script tag in React?

I'd like to import this javascript package in React
<script src="https://cdn.dwolla.com/1/dwolla.js"></script>
However, there is no NPM package, so I can't import it as such:
import dwolla from 'dwolla'
or
import dwolla from 'https://cdn.dwolla.com/1/dwolla.js'
so whenver I try
dwolla.configure(...)
I get an error saying that dwolla is undefined. How do I solve this?
Thanks
Go to the index.html file and import the script
<script src="https://cdn.dwolla.com/1/dwolla.js"></script>
Then, in the file where dwolla is being imported, set it to a variable
const dwolla = window.dwolla;
This question is getting older, but I found a nice way to approach this using the react-helmet library which I feel is more idiomatic to the way React works. I used it today to solve a problem similar to your Dwolla question:
import React from "react";
import Helmet from "react-helmet";
export class ExampleComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
myExternalLib: null
};
this.handleScriptInject = this.handleScriptInject.bind(this);
}
handleScriptInject({ scriptTags }) {
if (scriptTags) {
const scriptTag = scriptTags[0];
scriptTag.onload = () => {
// I don't really like referencing window.
console.log(`myExternalLib loaded!`, window.myExternalLib);
this.setState({
myExternalLib: window.myExternalLib
});
};
}
}
render() {
return (<div>
{/* Load the myExternalLib.js library. */}
<Helmet
script={[{ src: "https://someexternaldomain.com/myExternalLib.js" }]}
// Helmet doesn't support `onload` in script objects so we have to hack in our own
onChangeClientState={(newState, addedTags) => this.handleScriptInject(addedTags)}
/>
<div>
{this.state.myExternalLib !== null
? "We can display any UI/whatever depending on myExternalLib without worrying about null references and race conditions."
: "myExternalLib is loading..."}
</div>
</div>);
}
}
The use of this.state means that React will automatically be watching the value of myExternalLib and update the DOM appropriately.
Credit: https://github.com/nfl/react-helmet/issues/146#issuecomment-271552211
for typescript developers
const newWindowObject = window as any; // cast it with any type
let pushNotification = newWindowObject.OneSignal; // now OneSignal object will be accessible in typescript without error
You can't require or import modules from a URL.
ES6: import module from URL
What you can do is make an HTTP request to get the script content & execute it, as in the answer for how to require from URL in Node.js
But this would be a bad solution since your code compilation would depend on an external HTTP call.
A good solution would be to download the file into your codebase and import it from there.
You could commit the file to git if the file doesn't change much & are allowed to do it. Otherwise, a build step could download the file.
var _loaded = {};
function addScript(url) {
if (!loaded[url]) {
var s = document.createElement('script');
s.src = url;
document.head.appendChild(s);
_loaded[url] = true;
}
}
how to load javascript file from cdn server in a component
Add the script tag in your index.html and if you are using Webpack, you can use this webpack plugin https://webpack.js.org/plugins/provide-plugin/

Categories

Resources