Expose ReactClass using WebPack - javascript

I'm trying to expose a ReactClass, using WebPack, to reuse it at different html pages. Is it possible? What is the correct way? For simplicity, I'll do an example using two times at one html (But it should be used in a lot of different html in the real application)
All the source are available here:
https://github.com/mqueirozcorreia/React-Fundamentals/tree/video2
The webpack.config.js successfully creates the bundled file and the code is below:
var HtmlWebpackPlugin = require('html-webpack-plugin')
var HTMLWebpackPluginConfig = new HtmlWebpackPlugin({
template: __dirname + '/app/index.html',
filename: 'index.html',
inject: 'body'
});
module.exports = {
entry: [
'./app/index.js'
],
output: {
path: __dirname + '/dist',
filename: "index_bundle.js"
},
module: {
loaders: [
{test: /\.js$/, exclude: /node_modules/, loader: "babel-loader"}
]
},
plugins: [HTMLWebpackPluginConfig]
};
The ReactClass is at app\MyReactClass.js which the code is the following:
var React = require('react');
var MyReactClass = React.createClass({
render: function () {
return (
<div>MyReactClass = Hello {this.props.name}</div>
)
}
});
And the MyReactClass is exposed the app\index.js as follows:
...
require("expose?MyReactClass!./MyReactClass.js");
The global variable "MyReactClass" has an instance of an Object (As the alert shows), but when I try to use it at dist\index.html to ReactDOM.render(), it throws that error:
Warning: React.createElement: type should not be null, undefined,
boolean, or number. It should be a string (for DOM elements) or a
ReactClass (for composite components).
https://cdnjs.cloudflare.com/ajax/libs/react/0.14.7/react.js Line
18794
dist\index.html partial code:
...
<body>
<div id="app"></div>
<div id="component1"></div>
<div id="component2"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.7/react.js" type="text/javascript"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/0.14.7/react-dom.js" type="text/javascript"></script>
</body>
<script type="text/javascript">
alert(MyReactClass);
ReactDOM.render(React.createElement(MyReactClass, { name: 'Component1' }), document.getElementById('component1'));
ReactDOM.render(React.createElement(MyReactClass, { name: 'Component2' }), document.getElementById('component2'));
</script>
</html>
What am I doing wrong?

I just figure out what I was missing.
Just need to module.exports the MyReactClass variable to work:
module.exports = MyReactClass;
So the MyReactClass.js looks like this now
var React = require('react');
var MyReactClass = React.createClass({
render: function () {
return (
<div>MyReactClass = Hello {this.props.name}</div>
)
}
});
module.exports = MyReactClass;
https://github.com/mqueirozcorreia/React-Fundamentals/blob/ba19aa5cff21f0c4881901e0a96aed6ddaea8e18/app/MyReactClass.js

Related

Showing react app in my HTML Javascript app

I have a react app and I have generated a bundle.min.js by creating webapack.config.js which looks like the following:
const path = require('path');
const UglifyJsPlugin = require('uglifyjs-webpack-plugin');
const glob = require('glob');
module.exports = {
entry: {
"bundle.js": glob.sync("build/static/?(js|css)/main.*.?(js|css)").map(f => path.resolve(__dirname, f)),
},
output: {
filename: "build/static/js/bundle.min.js",
},
module: {
rules: [
{
test: /\.css$/,
use: ["style-loader", "css-loader"],
},
],
},
plugins: [new UglifyJsPlugin()],
}
I copied the bundle.min.js from the dist\build\static\js folder and included it in the script tag inside my HTML code index.html as shown below:
<html>
<head></head>
<body>
<h1>Bundle Testing</h1>
<p>
Here it comes:
</p>
<script src="bundle.min.js"/>
</body>
</html>
But it doesn't show my app. How should I render my app content inside this HTML code? I'm sure I'm missing something.

Variable masking

I have a class defined in an index.js file like this
const BLOG = BLOG || {};
BLOG.ComponentFactory = class {
}
window.BLOG = BLOG;
Then, in a file init.js in a bundle, I try to access that var, the file is like this
const BLOG = BLOG || {};
BLOG.init = function() {
var c = new BLOG.ComponentFactory()
}
I get BLOG.ComponentFactory is not a constructor and I cannot understand why. Is the BLOG definition inside the file init.js masking the global var?
There is something strange: using Chrome debugger in the init function, I see Blog.ComponentFactory defined inside "Global", but if I add to the properties to watch, I see Blog.ComponentFactory = undefined
I'd like to understand what's happening.
I need to defin BLOG as a global var as I'm using ES6 together with old javascritpt code.
EDIT: if I use the following code all works (init.js)
const BLOG = BLOG || {};
BLOG.init = function() {
var c = new window.BLOG.ComponentFactory()
}
So, it seems the local const BLOG is masking the global BLOG var, but I need to define BLOG because otherwise I get BLOG is undefined. So, how do I solve the problem?
EDIT2: my webpack config (the problem is in the bundling, the vars are defined inside functions which are generated while bundling)
const webpack = require('webpack');
const path = require('path');
var config = {
module: {
rules: [
{
test: /\.(js|jsx)$/,
exclude: /node_modules/,
use: ['babel-loader']
},
]
},
// workaround for
// ERROR in ./node_modules/wpcom/build/lib/site.media.js
// Module not found: Error: Can't resolve 'fs' in '/node_modules/wpcom/build/lib'
node: {
fs: 'empty'
},
resolve: {
extensions: ['*', '.js', '.jsx']
},
plugins: [
new webpack.HotModuleReplacementPlugin()
]
};
var aConfig = Object.assign({}, config, {
name: "a",
entry: "./WebContent/js/blog/index.js",
output: {
path: __dirname + '/WebContent/js/blogW',
filename: "bundle.js",
publicPath: 'http://localhost:8080/js/blogW'
},
devServer: {
historyApiFallback: true,
contentBase: './WebContent',
publicPath: "http://localhost:8080/js/blogW",
hot: true
}
});
module.exports = [
aConfig
];

Import and require issue with react- can't read property 'render' of undefined

I've been struggling with getting my react project started. I'm using react with babel, webpack, and a webpack dev server to quickly prototype.
First off, I can't get the import functions to work. I tried working around this by swapping from import ES6 syntax to Node's require syntax, which seemed to fix an issue of "import" not being recognized ("can't find token import"). I don't really know where to begin troubleshooting this and was looking to be pointed in the right way if possible.
Here is my webpack config:
var HTMLWebpackPlugin = require('html-webpack-plugin');
var HTMLWebpackPluginConfig = new HTMLWebpackPlugin({
template: __dirname + '/index.html',
filename: 'index.html',
inject: 'body'
});
module.exports = {
entry: __dirname + '/main.js',
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: 'babel-loader'
}
]
},
output: {
filename: 'transformed.js',
path: __dirname + '/build'
},
plugins: [HTMLWebpackPluginConfig]
};
My original code looks like:
const React = require('react').default;
const ReactDOM = require('react-dom');
//Components
const MainTable = require('./app/components/input_table/maintable').default;
const ButtonMenu = require('./app/components/button_menu/buttonmenu').default;
//App Logic
const onUndoAction = require('./app/undo').default;
const onRedoAction = require('./app/redo').default;
const onInputChange = require('./app/input').default;
class App extends React.Component {
constructor(props){
//properties
super(props);
this.props.inputId = 0
//state
this.state = {
gridState: [
{
rows: 2,
columns: 1,
type:"cell"
}
],
cellValues: [{
cellId: 0,
cellValue: "first",
type:"value"
}],
undo: [{rows: 2, columns: 1, type: "cell"},
{cellId: 0, cellValue: "first", type:"value"}],
redo: ["empty"],
}
... additional code omitted
You need to configure your babel setup in order to transpile es6 code.
Follow these steps for webpack: https://babeljs.io/setup#installation
It can be that your module loaders are not set up properly, usually when imports dont work its the module loaders setup from my expirience. Hope it helps

Polymer 3 in other project (like ASP.NET MVC) - create reusable components (PolymerElements)

I am trying to create and reuse Polymer 3 components in ASP.NET MVC application. Right now I am not sure if I am approaching the issue correctly.
So first thing, I want to run everything from IIS express.
Right now I have this issue:
Uncaught TypeError: Failed to resolve module specifier "#polymer/polymer/polymer-element.js".
Relative references must start with "/", "./", or "../".
Here is my code:
Index.cshtml:
<head>
<script src="~/Scripts/PolymerApp/node_modules/#("#webcomponents")/webcomponentsjs/webco
mponents-loader.js"></script>
<script type="module" src="~/Scripts/PolymerApp/first-element.js">
</script>
</head>
<h2>Index</h2>
<div>
<first-element></first-element>
</div>
This is my first-element.js:
import {html, PolymerElement} from '#polymer/polymer/polymer-element.js';
class FirstElement extends PolymerElement {
static get template() {
return html`
<style>
:host {
display: block;
}
</style>
<h2>Hello [[prop1]]!</h2>
`;
}
static get properties() {
return {
prop1: {
type: String,
value: 'first-element',
},
};
}
}
window.customElements.define('first-element', FirstElement);
I created this through cmd: polymer init and then chose element template.
When I run this through polymer serve on polymer`s localhost it works, so i guess there is some build process going on.
Thanks in advance. I hope that i described everything.
I've attempted to do a string replacement in the polymer generated html file using webpack and a plug-in, but it doesn't seem to find the file. Maybe someone more knowledgeable in Webpack-fu can figure out the rest.
// webpack.config.js
var webpack = require('webpack');
const ReplaceInFileWebpackPlugin = require('replace-in-file-webpack-plugin');
"use strict";
module.exports = {
entry: {
main: '/'
},
output: {
filename: "./wwwroot/dist/[name].bundle.js",
publicPath: "/temp/"
},
devServer: {
contentBase: ".",
host: "localhost",
port: 9000
}, mode: "development",
plugins: [
new ReplaceInFileWebpackPlugin([{
dir: './path/to/polymer-built-app/build/default/',
test: /\.html$/,
rules: [{
search: '/#webcomponents/',
replace: '/#{\'#webcomponents\'}/'
}]
}])
]
};
**EDIT: 08/04/2018 **
I've figured this much out:
/// <binding BeforeBuild='Run - Development' />
// webpack.config.js
var webpack = require('webpack');
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const HtmlWebpackStringReplacePlugin = require('html-webpack-string-replace-plugin');
"use strict";
module.exports = {
entry: {
main: './'
},
output: {
publicPath: '/',
path: path.resolve(__dirname, 'wwwroot'),
filename: "./dist/[name].bundle.js"
},
devServer: {
contentBase: ".",
host: "localhost",
port: 9000
},
mode: "development",
plugins: [
new HtmlWebpackPlugin({
"template": "./path/to/index.html",
"filename": "../path/to/Views/Shared/_PolymerLayout.cshtml"
}),
new HtmlWebpackStringReplacePlugin({
'#webcomponents':
'#Html.Raw("#webcomponents")',
'%40webcomponents':
'#Html.Raw("#webcomponents")',
'%40polymer':
'#Html.Raw("#polymer")',
'/node_modules/':
'/path/to/node_modules/',
'./src/path/to/polymer-app',
'<!-- See google short url -->':
'<!-- See google short url -->\r\n<base href="/custom/base/ref">'
})
]
};
If the .js import path starts like this:
from '#polymer/...'
Polymer 3 has a command "polymer build" that automatically translates the path to a real location:
Before:
from '#polymer/polymer/polymer-element.js';
After:
from "./node_modules/#polymer/polymer/polymer-element.js";
You can type ./node_modules/ in front to skip using the polymer build command line tool.

Text content did not match. Warning in React 16

I trying to build ReactJs application with server side rendering
My entry points for client and server:
client.jsx
const store = createStore(window.__INITIAL_STATE__);
hydrate(
<Provider store={store}>
<BrowserRouter>{renderRoutes(routes)}</BrowserRouter>
</Provider>,
document.querySelector('#root')
);
server.jsx
const app = express();
if (isDev) {
const webpack = require('webpack');
const webpackDevMiddleware = require('webpack-dev-middleware');
const config = require('../../webpack.config.js');
const compiler = webpack(config);
app.use(express.static('/public'));
app.use(
webpackDevMiddleware(compiler, {
publicPath: config.output.publicPath,
stats: 'errors-only',
})
);
}
app.get('*', (req, res) => {
const helmet = Helmet.renderStatic();
const htmlAttrs = helmet.htmlAttributes.toComponent();
const bodyAttrs = helmet.bodyAttributes.toComponent();
const context = {};
const data = {};
res.set('content-type', 'text/html');
res.send(
'<!DOCTYPE html>' +
renderToString(
<html {...htmlAttrs}>
<head>
{helmet.title.toComponent()}
{helmet.meta.toComponent()}
{helmet.link.toComponent()}
</head>
<body {...bodyAttrs}>
<div id="root">
<StaticRouter location={req.url} context={context}>
{renderRoutes(routes)}
</StaticRouter>
</div>
<script
dangerouslySetInnerHTML={{
__html: `window.__INITIAL_STATE__ = ${JSON.stringify(data)}`,
}}
/>
<script src="/public/vendor.js" />
<script src="/public/app.js" />
</body>
</html>
)
);
});
And component:
home.jsx
import React, { Component } from 'react';
class Home extends Component {
render() {
return <div>home</div>;
}
}
export default Home;
When I change in my component Home and refresh browser page I get this error:
Warning: Text content did not match. Server: "home" Client: "home1"
Its ok because server render old version of my code. How to reload the code on the server so that the client and server versions are equal?
For those discovering this error because your client and server purposefully render different content (like the server rendering a dark theme which gets replaced with the user preference on load), use suppressHydrationWarning to suppress the error.
For example:
<div suppressHydrationWarning>Ignore this</div>
The problem here is that your server-side application does not reflect code changes. To do that you have to configure your express app as a webpack entry.
Briefly, you need 2 webpack configurations one for server and another for client code.
The server one will look something like this
module.exports = {
entry: {
server: './server.js',
},
output: {
path: path.join(__dirname, 'dist'),
publicPath: '/',
filename: '[name].js'
},
target: 'node',
node: {
// Need this when working with express, otherwise the build fails
__dirname: false, // if you don't put this is, __dirname
__filename: false, // and __filename return blank or /
},
externals: [nodeExternals()], // Need this to avoid error when working with Express
module: {
rules: [
{
// Transpiles ES6-8 into ES5
test: /\.js$/,
exclude: /node_modules/,
use: {
loader: "babel-loader"
}
},
{
// Loads the javacript into html template provided.
// Entry point is set below in HtmlWebPackPlugin in Plugins
test: /\.html$/,
use: [{loader: "html-loader"}]
}
]
},
plugins: [
new HtmlWebPackPlugin({
template: "./index.html",
filename: "./index.html",
excludeChunks: [ 'server' ]
})
]
}
Here is a nice article explaining how to do that in details
No one said about but Cloudflare replaced double spaces with a space when you enable Auto Minify (HTML) in optimization.
And this error will happen.

Categories