How to embed React component on other sites? - javascript

I've created React application and I need to generate embed code to other websites. It's large app where I want to do only one component as a widget for other domains. I have read Writing embeddable Javascript plugin with React & Webpack and How to embed react component on other domains?. I set my webpack, but still can't render the widget on another domain.
This widget should:
be available via <script> tag, not iframe
show only one part of the app (path /referral), but not all app
The widget is a button and popup, which displays by click on the button.
Here is my webpack.config.js in client folder:
const path = require('path');
const bundleOutputDir = './referral';
const env = require('yargs').argv.env;
module.exports = env => {
const isDevBuild = !(env && env.prod);
if (env === 'build') {
mode = 'production';
} else {
mode = 'development';
}
return [
{
mode: mode,
entry: './src/components/early-referrals/Referral.js',
output: {
filename: 'widget.js',
path: path.resolve(bundleOutputDir),
library: 'Referral',
libraryTarget: 'umd',
umdNamedDefine: true
},
devServer: {
contentBase: bundleOutputDir
},
module: {
rules: [
{ test: /\.html$/i, use: 'html-loader' },
{
test: /\.css$/i,
use: [
'style-loader',
'css-loader' + (isDevBuild ? '' : '?minimize')
]
},
{
test: /\.(png|jpe?g|gif)$/i,
use: [
{
loader: 'file-loader'
}
]
},
{
test: /(\.jsx|\.js)$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [
[
'#babel/env',
{
targets: {
browsers: ['ie 6', 'safari 7']
}
}
]
]
}
}
}
]
}
}
];
};
Here is component RegisterReferral.js, which is entry point in webpack:
import PopupRef from '../popup/PopupRef';
const RegisterReferral = ({
...
}) => {
const [...] = useState();
useEffect(() => {
...
}, []);
return (
<div>
// some styles for button that displays popup
<PopupRef />
</div>
);
};
const mapStateToProps = state => ({
...
});
export default connect(
mapStateToProps,
{ ... }
)(RegisterReferral);
PopupRef.js is a component with popup, which should displays on other websites within the button.
In the App.js, I have the route for this component that I want to create as an embeddable:
<Route exact path="/referral" component={RegisterReferral} />
And this is a way how I paste the code on another domain:
// the URL in script is an URL of bundle file from webpack
<html>
<head>
<script src="http://example.com/client/referral/widget.js" type="text/javascript"></script>
</head>
<body>
<p>Another website</p>
<div id='app'></div>
</body>
</html>
Also, I've tried to do entry point in webpack.config.js with Referral.js. And here is this component:
// Referral.js
import React from 'react';
import { render } from 'react-dom';
import App from './RegisterReferral.js';
render(<App />, document.getElementById('app'));
// At this case, webpack.config.js has 2 changes:
entry: './src/components/early-referrals/Referral.js',
output: {
...
library: 'Referral',
},
Nothing works. My component doesn't display on other websites.
Please help me to figure out what's wrong and how to embed one component (not all app) from React app on other websites.

Assume that you correctly config your webpack to bundle react js code
This is how I render the component in any page I want
Assume that I have this component
import React, {Component} from "react";
import {render} from "react-dom";
class LoginForm extends Component { }
render(<LoginForm/>, document.getElementById("loginForm")); // use render method
Edit:
Just saw your code I think you need also change the id in index.html
<div id="root"></div>
to
<div id="referral"></div>

Related

Why am I getting Uncaught ReferenceError: React is not defined?

I am setting up webpack and react in a SpringMVC webapp and I'm getting this error when hitting my /index page.
The relevant controller method is here
#RequestMapping("/index")
public ModelAndView index(final HttpServletRequest request) {
return new ModelAndView("index");
}
The template at src/main/resource/templates/index.html looks like this
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Webpack App</title>
</head>
<body>
<span>React Content Below:</span>
<div id="content"/>
</body>
<script type="text/javascript" src="http://localhost:3000/dist/app.js">
</script>
</html>
Now onto the meat. My webpack.config.js looks like this
// Generated using webpack-cli http://github.com/webpack-cli
const path = require('path');
const webpack = require("webpack");
module.exports = {
mode: 'development',
entry: './src-js/app.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'app.js',
},
devServer: {
open: true,
host: 'localhost',
port:3000
},
externals: {
'react': 'React'
},
module: {
rules: [
{
test: /\.js|jsx$/,
loader: 'babel-loader',
options: {
presets: [
'#babel/preset-env',
'#babel/preset-react',
]
}
},
{
test: /\.css$/i,
use: ['style-loader','css-loader'],
},
{
test: /\.s[ac]ss$/i,
use: ['style-loader', 'css-loader', 'sass-loader'],
},
{
test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/,
type: 'asset',
},
// Add your rules for custom modules here
// Learn more about loaders from https://webpack.js.org/loaders/
],
},
};
and my ./src-js/app.js looks like this.
import * as ReactDOM from "react-dom";
ReactDOM.render(<h1>Hello, world</h1>, document.getElementById('content'));
I later ammended app.js to look like this
import * as React from 'react';
import * as ReactDOM from 'react-dom'
class Component extends React.PureComponent {
constructor(props) {
super(props);
}
render() {
return <h1>Hello, world</h1>
}
}
ReactDOM.render(<Component/>, document.getElementById('content'));
This works for now but long term I would prefer to not need to import React from this file and wrap everything in a Component.
Adding import React from 'react' to the top of app.js has no effect and neither does var React = require('react'). I'm assuming I'm just missing something in my webpack config but I can't seem to find what, anyone know why this is happening?
In the webpack documentation (https://webpack.js.org/configuration/externals/) it looks like there are no quotes before the colon:
externals: {
react: 'React'
},
A few lines below (https://webpack.js.org/configuration/externals/#object) you can find react in lowerCase:
externals: {
react: 'react'
},

React not rendering components

I am dealing with an electron BrowserWindow that should render an HTML file filled with some react components. However the React components are not showing.
I have an html file which is:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta http-equiv="Content-Security-Policy" content="script-src 'self'" />
</head>
<body>
<div id='QA_Dialog'></div>
<script src="../js/index.js"></script>
</body>
</html>
My script source file "../js/index.js" contains some easy React rendering:
import ReactDOM from 'react-dom';
import QAWindow from './QAWindow';
document.body.style.overflow = "hidden";
document.body.style.borderRadius = "5px";
ReactDOM.render(<QAWindow />, document.getElementById('QA_Dialog'))
Where QAWindow is :
import React from 'react';
import { electron } from 'webpack';
import { CloseButton, StyledButton} from '../templates/style';
const useState = React.useState
function QuestionForm() {
const [question, setQuestion] = useState()
function handleSubmit(e) {
e.preventDefault()
electron.QandA.askQuestionSignal(question);
}
function handleClose(e){
e.preventDefault()
electron.QandA.closeWindowSignal('Dio')
}
return (
<>
<CloseButton onClick={handleClose}>
<img src="../templates/close.svg" />
</CloseButton>
<form onSubmit={handleSubmit}>
<input value={question} onChange={e => setQuestion(e.target.value)} placeholder="Ask a question..." />
<span>
<StyledButton>Find Answer</StyledButton>
</span>
</form>
</>
)
}
export default function QAWindow() {
return(
<>
<QuestionForm />
</>
)
}
If I change the above file to only export a simple element it doesn't work anyways. So I assume that the problem is not in QAWindow.
These files are copied in the build/ folder, and there, the reference '../js/index.js' is still valid (the structure of the files dosn't change).
../js/index.js got compiled by web-pack using a babel-loader.
Why does this render a white page???
EDIT:
To better debug this, I am also providing my webpack.config.js:
// This first configuration bundles the script that renders the react components
const QandAExtension= {
mode: 'development',
entry: './src/preloads/QandA/js/index.js', // This entry point match the correct react script that needs to be bundled.
devtool: 'inline-source-map',
target: 'electron-renderer',
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: 'babel-loader',
options: {
presets: [[
'#babel/preset-env', {
targets: {
esmodules: true
}
}],
'#babel/preset-react']
}
}
},
{
test: [/\.s[ac]ss$/i, /\.css$/i],
use: [
// Creates `style` nodes from JS strings
'style-loader',
// Translates CSS into CommonJS
'css-loader',
// Compiles Sass to CSS
'sass-loader',
],
},
{
test: /\.svg$/,
use: [
{
loader: 'svg-url-loader',
options: {
limit: 10000,
},
},
],
}
]
},
resolve: {
extensions: ['.js'],
},
output: {
filename: 'index.js',
path: path.resolve(__dirname, 'build', 'QandA', 'js'),
},
};
// This second configuration copies the folder containing the HTML file
// into the build/ folder of this app.
const preloadConfig = getConfig({
target: 'electron-renderer',
devtool: false,
watch: dev,
entry: {
'view-preload': './src/preloads/view-preload',
},
// Here in plugin you can specify what you want to copy in the build/ folder.
plugins: [
new CopyPlugin({
patterns: [
{
from: join(__dirname, "src", "preloads", "QandA", "templates"),
to: "QandA/templates",
toType: "dir",
}
],
}),
],
},);
module.exports = [preloadConfig, QandAExtension];
You need to import React's Javascript bundles, which React does through the command line react-scripts start (and isn't explicitly defined in the index.html file).
e.g.
<script src="/static/js/bundle.js"></script>
<script src="/static/js/0.chunk.js"></script>
<script src="/static/js/main.chunk.js"></script>
are imports on functioning index.html React pages.

HMR not triggering sometimes for components

This has been baffling me for a few hours now & I can't seem to work out why. My project structure is below. Sometimes a change in views/Dashboard/index.jsx will trigger a hot reload & other times it will not. It's weird but when it works, after about an hour or so, it will just suddenly stop working...
index.js
import 'react-hot-loader'
import 'bootstrap/dist/css/bootstrap.min.css'
import React from 'react'
import ReactDOM from 'react-dom'
import { AppContainer } from 'react-hot-loader'
import { library } from '#fortawesome/fontawesome-svg-core'
import { faChartBar, faThList, faCogs, faClipboard, faFilter, faNotEqual } from "#fortawesome/free-solid-svg-icons"
import configureStore, { history } from './redux/store'
import App from './App'
library.add(faChartBar, faThList, faCogs, faClipboard, faFilter, faNotEqual)
let store
function render(Component) {
ReactDOM.render(
<AppContainer>
<Component store={store} />
</AppContainer>,
document.getElementById('root')
)
}
configureStore()
.then(_store => {
store = _store
render(App)
if (module.hot) {
// Reload components
module.hot.accept('./App', () => {
render(App)
})
}
})
.catch(err => {
console.log(err)
})
App.jsx
import { hot } from 'react-hot-loader/root'
import React from 'react'
import { Provider } from 'react-redux'
import { BrowserRouter as Router } from 'react-router-dom'
import PropTypes from 'prop-types'
// Routes
import routes from './routes'
const App = ({ store }) => {
return (
<Provider store={store}>
<Router>{routes}</Router>
</Provider>
)
}
App.propTypes = {
history: PropTypes.object,
}
export default hot(module)(App)
routes/index.js
import React from 'react'
import { Route, Switch } from 'react-router'
// Components
import Dashboard from '../views/Dashboard'
// Views
import NoMatch from '../views/NoMatch'
export default (
<Switch>
<Route path="/" component={Dashboard} exact={true} />
<Route component={NoMatch} />
</Switch>
)
views/Dashboard/index.jsx
import React, { Component } from 'react'
export default class Dashboard extends Component {
constructor(props) {
super(props)
}
render() {
return (
<div>
Hello World
</div>
)
}
}
If I make any change to the first 4 files listed (index.js, App.jsx, routes/index.js, containers/Dashboard.js) then the hot reloading works & the page is reloaded. However, if I attempt to make a change to views/Dashboard/index.jsx, for example, change "Hello World" to "Foo Bar" then the hot reloading is not honoured.
However I have now taken out the intermediary step of redux & changed the routers/index.js to use the direct component from views/Dashboard/index.jsx & it still does not reload.
How can this be? If I set the component on the Route to the container listed above, it hot reloads for that file but not this JSX one? Is this to do with the structure inside my main component views/Dashboard/index.jsx? If so, what? As it looks fine to me.
EDIT
My complete webpack config is:
webpack.common.js
const path = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const DotenvPlugin = require('dotenv-webpack')
module.exports = {
output: {
path: path.resolve(__dirname, 'dist'),
publicPath: '/',
filename: 'js/[name].bundle.js',
},
resolve: {
extensions: ['.js', '.jsx'],
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
loader: 'babel-loader', // Using Babel to compile JS files
},
{
test: /\.(css|scss|sass)$/,
use: ['style-loader', 'css-loader', 'sass-loader'],
},
{
test: /\.(png|svg|jpg|gif|woff|woff2|eot|ttf|otf)$/,
use: [
{
loader: 'url-loader',
options: {
limit: 1000000,
name: 'img/[hash].[ext]',
},
},
],
},
{
test: /\.html$/,
loader: 'html-loader'
}
],
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, 'public', 'index.html'),
filename: './index.html',
}),
new DotenvPlugin({
systemvars: true,
}),
],
optimization: {
runtimeChunk: 'single',
splitChunks: {
cacheGroups: {
vendor: {
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all',
},
},
},
},
}
webpack.dev.js
const webpack = require('webpack')
const merge = require('webpack-merge')
const path = require('path')
const common = require('./webpack.common.js')
module.exports = merge(common, {
mode: 'development',
entry: path.resolve(__dirname, 'src', 'index.js'),
// https://github.com/gaearon/react-hot-loader#source-maps
devtool: 'eval',
devServer: {
contentBase: path.join(__dirname, 'dist'),
open: true,
compress: true,
hot: true,
historyApiFallback: true
},
resolve: {
alias: {
'react-dom': '#hot-loader/react-dom'
}
},
plugins: [
new webpack.NamedModulesPlugin(),
new webpack.EnvironmentPlugin({
NODE_ENV: 'development',
}),
],
optimization: {
usedExports: true,
},
})
.babelrc
{
"presets": [
[
"#babel/preset-env",
{
"useBuiltIns": "entry",
"corejs": "3.0.0",
"targets": {
"esmodules": true
}
}
],
"#babel/preset-react"
],
"plugins": [
"react-hot-loader/babel"
]
}
I have updated the files listed to show what I currently have. This is a very weird error as sometimes the hot reloading will work & other times it will not. Hot reloading always works for files; index.js, App.jsx, & routes/index.jsx but sometimes it will work for views/Dashboard/index.jsx & sometimes it will not.
I was previously following the Getting Started guide but this thread helped me realise this was out of date. I have now just followed the README as is mentioned. I have also attempted to take snippets from this article as well on setting up HMR with Webpack 4.
Versions
webpack: 4.41.6
webpack-dev-server: 3.10.3
babel-loader: 8.0.6
#hot-loader/react-dom: 16.11.0
react: 16.12.0
react-hot-loader: 4.12.19
I have the below response in the console so HMR is clearly enabled
I am trying to replicate exactly what is in the examples as recommended

React-hot-loader doesn't work with React-router-dom

So I've finally setup a working project with:
Electron (2.0.2)
React (16.4.0)
React-router-dom (4.2.2)
Webpack (4.11.0)
React-hot-loader (4.2.0)
And just when I started to develop some react components I noticed my project won't hot reload correctly. If I adjust something on the base url (/) it is updated correctly, but if I update something on a secondary url, say /test the webpack compiles, but I get the message Cannot GET /test.
I've tried a lot and I cannot seem to figure out what I am doing wrong. I looked into react-router-dom, since hot-reloading was an issue back in version 3.x, but they say it should be resolved now (in 4.x --> It works here..). Also i've added <base href="/"/> in my index.html so that is not it.
Can anyone tell me what I am doing wrong?
Webpack.common.js (This is merged into Webpack.dev.js)
module.exports = {
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].bundle.js'
},
resolve: {
modules: [path.resolve(__dirname), 'node_modules']
},
module: {
rules: [
{
test: /\.jsx?$/,
loader: 'babel-loader',
exclude: /node_modules/,
options: {
cacheDirectory: true,
presets: ['env', 'react'],
plugins: ['transform-runtime'],
env: {
development: {
plugins: ['react-hot-loader/babel']
},
production: {
presets: ['react-optimize']
}
}
}
}
]
}
};
Webpack.dev.js
module.exports = merge(common, {
mode: 'development',
devtool: 'eval-source-map',
entry: {
'app': [
'babel-polyfill',
'react-hot-loader/patch',
path.join(__dirname, 'src', 'index.js')
]
},
plugins: [
new webpack.HotModuleReplacementPlugin() // Enable hot module replacement
]
});
Index.js
import React from "react";
import ReactDOM from "react-dom";
import { AppContainer } from "react-hot-loader";
import { App } from "./app";
const render = Component => {
ReactDOM.render(
<AppContainer>
<Component/>
</AppContainer>,
document.getElementById("root")
);
};
render(App);
if (module.hot) {
module.hot.accept("./app", () => {
render(App);
});
}
App.js (my main entry point for my app, thus where I define my base routing)
import React, { Component } from 'react';
import { BrowserRouter, Route, NavLink } from 'react-router-dom';
import { Test } from './components/test';
import { Test2 } from './components/test2';
export class App extends Component {
render() {
return (
<BrowserRouter>
<div>
<NavLink to="/">Home</NavLink>
<NavLink to="/test">Test</NavLink>
<div>
<Route exact path="/" component={Test}/>
<Route path="/test" component={Test2}/>
</div>
</div>
</BrowserRouter>
);
}
}
And the components 'test' and 'test2' are just plain simple react components with a 'hello world' text.
Anyone who sees anything that I am missing or doing wrong?
Thanks to this tutorial I found a way to adapt my project and get hot loading to work. It even made my code a bit cleaner and my build scripts simpeler.
Webpack.common.js
The first thing I needed to change was the babel-loader. I stole it from some tutorial, and it worked, but I did not know exactly what it did so I got rid of that code. I've also made the compilation of my code faster through the webpack.DllReferencePlugin.
Here is the updated webpack.common.js:
const path = require('path');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const AddAssetHtmlPlugin = require('add-asset-html-webpack-plugin');
module.exports = {
entry: {
app: [
'babel-polyfill',
'./src/index.js',
],
},
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist'),
},
module: {
rules: [
{
test: /\.jsx?$/,
loader: 'babel-loader',
exclude: /node_modules/,
options: {
plugins: ['react-hot-loader/babel'],
cacheDirectory: true,
presets: ['env', 'react'],
},
}
],
},
plugins: [
new webpack.DllReferencePlugin({
context: path.join(__dirname),
manifest: require('../dist/vendor-manifest.json'),
}),
new HtmlWebpackPlugin({
title: '<my-app-name>',
filename: 'index.html',
template: './public/index.html',
}),
new AddAssetHtmlPlugin({
filepath: path.resolve(__dirname, '../dist/*.dll.js'),
includeSourcemap: false // add this parameter
})
],
};
The AddAssetHtmlPlugin is required since the index.html is dynamically created (by the HtmlWebpackPlugin) for the dev server and you cannot hardcode the correct bundle import for the vendor.dll and the app.bundle (more here).
webpack.dev.js
const merge = require('webpack-merge');
const common = require('./webpack.common.js');
const webpack = require('webpack');
const path = require('path');
module.exports = merge(common, {
mode: 'development',
devtool: 'eval-source-map',
devServer: {
hot: true,
contentBase: path.resolve(__dirname, 'dist'),
historyApiFallback: true // Allow refreshing of the page
},
plugins: [
new webpack.HotModuleReplacementPlugin(), // Enable hot reloading
]
});
What did I change:
I moved the entry point up to webpack.common.
I Removed the 'react-hot-loader/patch' from the entry
(optional) I've added some config options for the webpack-dev-server.
Index.js
This is the file that caused the hot-reload to fail. Especially the if(module.hot) part caused it to fail. So I've changed it to the following:
import React from 'react';
import ReactDOM from 'react-dom';
import { AppContainer } from 'react-hot-loader';
import { App } from './app';
const render = () => {
ReactDOM.render(
<AppContainer>
<App/>
</AppContainer>,
document.getElementById('app'),
);
};
render(App);
if (module.hot) {
module.hot.accept('./app', () => {
const NextApp = require('./app').default; // Get the updated code
render(NextApp);
});
}
The reason it works now is because now I fetch the new app and replace the old one, thus telling the hot-loader there has been a change. I could also just use module.hot.accept(), but that would make the react-hot-loader useless (you make use of the webpack hot-reloader) and this way I would also lose the state within my components every time I updated some code.
So there you go. I hope this will help anyone (other then myself).

React do not render new components, new webpack setup

Today I was setting up my first Webpack Bebel React project and I got some strange case here.I don't know why, but every single Component that I make is not recognized by React. I can see it directly in the inspector, and It seems like it's not getting compiled. All standard HTML elements are getting rendered. Even console.log inside of constructor function of a component that I have created is not called. I run Hot mode with webpack -p
Here is my Webpack config:
const ExtractTextPlugin = require('extract-text-webpack-plugin')
const webpack = require('webpack')
const path = require('path')
const isProduction = process.env.NODE_ENV === 'production'
const cssDeveloperLoaders = ['style-loader', 'css-loader', 'sass-loader']
const cssProduction = ExtractTextPlugin.extract({
fallback: 'style-loader',
loader: ['css-loader', 'sass-loader'],
publicPath: '/dist'
})
const cssConfig = isProduction ? cssProduction : cssDeveloperLoaders
module.exports = {
entry: {
app: './src/app.jsx'
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].bundle.js'
},
module: {
rules: [
{
test: /\.scss$/,
use: cssConfig
},
{
test: /\.jsx$/,
exclude: path.resolve(__dirname + '/node_modules/'),
use: 'babel-loader',
query: {
presents: ['es2015','react']
}
}
]
},
devServer: {
contentBase: path.join(__dirname, 'dist'),
compress: true,
hot: true,
open: true,
openPage: '' //Fix to webpack version 3.0.0 after removing redirection to /undefined
},
plugins: [
new ExtractTextPlugin({
filename: 'app.css',
disable: !isProduction, //So if production is running it will generate file otherwise not
allChunks: true
}),
new webpack.HotModuleReplacementPlugin(),
new webpack.NamedModulesPlugin()
]
}
My .bablerc
{
"presets": [
"es2015",
"react"
]
}
App.jsx:
import './app.scss'
import React from 'react';
import { render } from 'react-dom';
import engine from './engine.jsx'
render(
<engine/>,
document.getElementById('root')
);
engine.jsx
import React from 'react';
class engine extends React.Component{
constructor(){
super();
console.log('Component has been constructed ')
}
render(){
return(
<div>xD</div>
)
}
}
export default engine;
The picture of React Chrome extension.
Please notice, console.log is not been called.
My html is empty, I see only engine element (Not compiled.)
Any suggestions about this problem?
HTML:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
<link rel="stylesheet" href="style.css">
</head>
<body>
<div id="root"></div>
<script src="app.bundle.js"></script>
</body>
</html>
In your webpack config file add
resolve : {
extensions: [".js", ".jsx"]
}
So that you won't need to import your jsx file with extenstion.
Class names should start with Capital letters otherwise methods in react components will not be invoked and also no error will be thrown.
engine.jsx
class Engine extends React.Component{
constructor(){
super();
console.log('Component has been constructed ')
}
render(){
return(
<div>xD</div>
)
}
}
export default Engine;
App.jsx
import Engine from './engine'
render(
<Engine/>,
document.getElementById('root')
);
Please verify https://codesandbox.io/s/D9rpvWWG6
Also you can refer https://github.com/facebook/react/issues/4695

Categories

Resources