Webpack + React: Passing arbitrary key-value configuration data into JSX - reactjs

Pretty straightforward thing I want to do: I'm building a React app, using webpack to bundle it. I have some properties that want to pass through, from a configuration JSON file, and be able to refer to those values in my React code.
I figured out a way to do it, though it seems like there should be a more direct way to do it. Looking for suggestions on how to do it more cleanly.
Here's a simplified version of what I'm doing, and it works.
The idea is that I'm threading this value into a hidden element of the HTML, and then passing it into my main React element as props. I'd prefer a way to pass this value directly into React props but have not been able to find a way to do that.
properties.json
{
"myKey": "foo (possibly dynamically generated by a build step)"
}
webpack.config.js
const config = require(__dirname + '/properties.json');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const HTMLWebpackPluginConfig = new HtmlWebpackPlugin({
template: __dirname + '/app/index.html',
filename: 'index.html',
inject: 'body',
metadata: config
});
// ... Rest of normal-looking webpack with babel-loader and react preset
index.html
<html>
<head><!-- normal head contents --></head>
<body>
<!-- One of these per key-value pair in my properties -->
<div id="config-myKey" style="display: none">
<%= htmlWebpackPlugin.options.metadata.myKey %>
</div>
<div id="app"></div>
</body>
</html>
React app (index.js):
const Main = React.createClass({
render: function() {
return(<p>This is the value for myKey: ${this.props.myKey}</p>);
}
});
// Read in the value from the hidden HTML element, and pass it through to the
// React app as props. This part feels like there should be a better way to
// do it.
const myValue = document.getElementById('config-myKey').innerHTML.trim();
ReactDOM.render(
<Main myKey=${myValue}/>,
document.getElementById('app')
);

Turns out DefinePlugin is exactly what I wanted. Thanks #azium.
For completeness, here's exactly how I have it working now. Much cleaner.
properties.json. Note the escaped quotes; those are necessary because I want this to appear as a string literal.
{
"REPLACE_ME_WITH_FOO_VALUE": "\"foo\""
}
webpack.config.js
const config = require(__dirname + '/properties.json');
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const HTMLWebpackPluginConfig = new HtmlWebpackPlugin({
template: __dirname + '/app/index.html',
filename: 'index.html',
inject: 'body'
});
const DefinePlugin = new webpack.DefinePlugin(config);
module.exports = {
entry: [ './app/index.js' ],
// ... Rest of normal-looking webpack with babel-loader and react preset
plugins: [HTMLWebpackPluginConfig, DefinePlugin]
});
index.js
const myValue = REPLACE_ME_WITH_FOO_VALUE;
ReactDOM.render(<Main myKey={myValue}/>, document.getElementById('app'));

Related

React app injecting into js instead of index.html

I would like to create a React app that will not be a standalone site, but rather an extension to an existing site. The site is built using asp.net and I cannot make edits to it. I do have an entry with javascript and can run code such as the following:
$(document).onload(() => {
$(body).parent().append(`
<script src='{...}'></script>
`);
});
Is there any way I can change the standard Index.html output from the default create-react-app to a .js file and have the react chunks added into the append? It does not necessarily need to follow the above direction, but it's as far as I got.
I believe you could just attach it to the body using ReactDOM.render
ReactDOM.render(
<React>
<App />
</React>,
document.getElementByTagName('body')
)
But you might have to switch away from create-react-app to using webpack, and then use something like HTMLWebpackPlugin within your webpack.config.js to specify the entry point
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
target: 'web',
mode: 'development',
entry: './src/index.jsx',
devtool: "source-map",
output: {
path: path.resolve(__dirname, 'dist'),
publicPath: '/',
filename: "[name]/[name].js",
},
...
new HtmlWebpackPlugin({
template: './html/index.html',
filename: './index.html'
}),
...
}
You would have to install html-webpack-plugin as well as webpack and anything you need with npm install html-webpackplugin webpack ...
If you look up tutorials on this, try to find React 17 and Webpack 5 they're fairly new. Webpack 5 has some differences with hot reloading compared to Webpack 4

HTMLwebpackPlugin - Is it possible to require partials while in partial?

Earlier I've been using gulp-nunjucks for templating - content extends layout and includes partials. So I've tried to sorta replicate this with html-webpack-plugin
webpack.config.js
const pages = ['home', 'page1'];
const pagesToHTMLPlugin = function(arr) {
return arr.map(function(page) {
return new HtmlWebpackPlugin({
page: page,
template: path.resolve(__dirname, 'src/_layout.html'),
filename: page + '.html',
inject: 'body'
});
});
};
let plugins = pagesToHTMLPlugin(pages).concat(new MiniCssExtractPlugin({ filename: 'main.css' }));
module.exports = {
//...
plugins: plugins
}
at _layout.html i'm successfully requiring partials pages gets rendered with required html files.
//...
<body>
<%= require('html-loader!./partials/header.html') %>
<%= require('html-loader!./pages/' + htmlWebpackPlugin.options.page + '.html') %>
</body>
yet same approach doesn't work if I'm trying to require partial inside already required partial at _layout.html - require('html-loader!./pages/' + htmlWebpackPlugin.options.page + '.html') .
/pages/home.html
<%= require('html-loader!./partials/test-partial.html') %>
Is there any way require html-wepback-plugin partial within another partial? Should I apply some kind of a loader to enable it?

How to use react as a component library rather than application

I'm trying to learn react and also introduce it in already existing project. Thus I want to use react in a few places where it makes sense, instead of using it everywhere.
The problem is the entry-point concept that I can't wrap my head around. I'd prefer to have:
ReactDOM.render(
<MyComponent/>,
document.getElementById("componentExample")
);
inside my cshtml near <div id='componentExample'></div> rather than in a separate jsx. I understand that I can't use jsx syntax in cshtml, but if I write
<script src="#Url.Content("~/Scripts/dist/bundle.js")"></script>
<script>
ReactDOM.render(
React.createElement(MyComponent, {}, null), document.getElementById("componentExample")
);
</script>
This doesn't really work because MyComponent is undefined. It is included in my bundle.js (I had to add it as entry point), but it is inside the scope of some webpack stuff and is not exposed.
What is the proper way of doing this?
My goal is to write some components for my app where it is more suitable and use it here and there, without completely going into react-based architecture.
My setup is webpack + babel polyfill + typescript + react and this is in ASP.NET MVC.
My webpack.config.js is:
var config = {
entry: ['babel-polyfill', "./SiteScripts/React/index.tsx", "./SiteScripts/React/Components/MyComponent.tsx"],
output: {
filename: "./Scripts/dist/bundle.js",
},
// Enable sourcemaps for debugging webpack's output.
devtool: "source-map",
resolve: {
// Add '.ts' and '.tsx' as resolvable extensions.
extensions: [".webpack.js", ".web.js", ".ts", ".tsx", ".js"]
},
module: {
rules: [
// All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'.
{
enforce: 'pre',
test: /\.js$/,
loader: "source-map-loader"
},
// All files with a '.ts' or '.tsx' extension will be handled by 'ts-loader'.
{
test: /\.tsx?$/, loaders: ['babel-loader', "ts-loader"], exclude: /node_modules/
}
]
},
plugins : []
}
if (process.env.NODE_ENV === "production") {
config.devtool = "cheap-module-source-map";
}
module.exports = config;
Also this is somewhat related to the question (as question about architecture): I'd like to make my bundle.js smaller - it is 30k lines now. It is probably because I'm trying to use react in a wrong way?
I finally found the problem. I had a few mistakes (and didn't completely understand the whole theory behind webpack) and the answer is definetely beyound the code I gave in the question - so I do not have any answers.
First I had to make by bundle a library, in order to export stuff defined in it (webpack.config.js):
entry: ['babel-polyfill', "./SiteScripts/React/index.tsx"],
output: {
filename: "./Scripts/dist/bundle.js",
libraryTarget: "var",
library: "WebPack"
},
This will make types/variables defined in index.tsx (I think only last file from entry list is taken) and that are exported - available via WebPack.<name>
I needed to export my component. I've done it like this - but there are probably better ways (index.tsx):
import { MyComponent} from "./Components/mycomponent";
export var myComponent = MyComponent;
I also needed to ensure all my subcomponents are modules. This means using import everywhere, and adding a keyword export to all element, props and states.
After that I was able to render my component from:
<script src="#Url.Content("~/Scripts/dist/bundle.js")"></script>
<script>
ReactDOM.render(
React.createElement(WebPack.myComponent,
{
PropA = 2, PropB = "3"
}, null),
document.getElementById("componentExample")
);
</script>
One more thing to watch for if I am doing this is that the line <script src="#... does not happen more than once in my result html (this is very likely as I am using many components embedded in a single page). I use this javascript function to ensure I load bundle.js only once:
function loadJsOnce(url, whenFinish) {
if (!window.loadedScripts) {
window.loadedScripts = {};
}
if (!window.loadedScripts[url]) {
var script = document.createElement('script');
//script.async = true; // remove this if you don't want the script to be async
script.src = url;
script.onload = function() {
whenFinish();
}
document.getElementsByTagName('head')[0].appendChild(script);
window.loadedScripts[url] = true;
} else {
whenFinish();
}
}
In url I specify the path to bundle.js and in whenFinish function I put a function that calls ReactDOM.render(... stuff

Standalone React Component with Webpack

I've got a component I'd like to share/reuse in some projects. I'm trying to build/bundle this component so it doesn't take the large setup that react normally does (webpack/babel/npm/ect).
I want to
Include React/ReactDOM from a cdn somewhere on an html page.
Include my component js file (we'll call it standalone.js).
Little bit of initialization code to render this into the dom. No Babel, No Webpack, No JSX.
That's all.
I feel like I've gotton pretty close, though am stuck on item 3. I cannot figure out how render my component to the DOM.
Here's the relevant part of demo html page:
index.html (relevant parts)
<div id="app" />
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.1/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.1/react-dom.min.js"></script>
<!--My Component -->
<script src="build/standalone.js" type="text/javascript"></script>
<script>
// I believe I'm doing something wrong here
var myComponent = new MyLibrary.default();
var myStandaloneElement = React.createElement(myComponent, { message: "Testing Standalone Component" });
ReactDOM.render(myStandaloneElement, document.getElementById('app'));
</script>
standalone.jsx
import React, {PropTypes} from 'react';
class Standalone extends React.Component {
render() {
return <p>{this.props.message}</p>;
}
}
Standalone.PropTypes = {
message: PropTypes.string.isRequired
}
export default Standalone;
webpack.config.js (relevant parts)
var config = {
entry: APP_DIR + '/standalone.jsx',
output: {
library: 'MyLibrary',
libraryTarget: 'umd',
path: BUILD_DIR,
filename: 'standalone.js'
},
module: {
loaders: [
{
test: /\.jsx?/,
include: APP_DIR,
loader: 'babel'
}
]
},
externals: {
react: 'React',
"react-dom": 'ReactDOM'
},
}
With trying to render the component with basic html I've tried a bunch of variations of similar things. Looking in my debugger, I can tell the object is something 'close' to a react-type object. I just don't know what to do with it.
Any pointers appreciated
You should not instantiate components with a new, rather they should be instantiated with React.createElement factory. So you just pass reference to the element class/function to createElement, see modified part of yout html:
...
// get reference to my component constructor
var myComponent = MyLibrary.default;
var myStandaloneElement = React.createElement(myComponent, { message: "Testing Standalone Component" });
ReactDOM.render(myStandaloneElement, document.getElementById('app'));
...
On a side note, to simplify debugging while in development (and only in development!) I suggest to use non minified version of react.js and react-dom.js, they are located under node_modules, for instance:
<script src="/node_modules/react/dist/react.js"></script>
<script src="/node_modules/react-dom/dist/react-dom.js"></script>
You may want to consider exposing your React component as a webcomponent, such as with https://www.npmjs.com/package/reactive-elements
<body>
<my-react-component item="{window.someValue}"></my-react-component>
</body>

How can I render the react app with redux using the ReactJS.NET?

I have created the Single page application where I have used the React, Redux and React-Router.
I want to render this SPA using ReactJS.NET for improve perfomance of loading the application.
The SPA is compiled as one Bundle.js using Webpack.
I have an ASP.NET MVC application where I can paste this bundle which will be rendered through the ReactJS.NET. Everything in this SPA will be worked in one ASP.NET view but with the React-router.
How can I do that? I can't find any project where is solved this combination with redux.
Thank you for the example or help.
I know this is an old question but if you're still having issues incorporating your React app into React.NET you should try the following template and have a look at how this fellow has done it.
He uses webpack to first build and compile a server specific set of code then pulls the compiled js into React.NET
app.UseReact(config =>
{
config
.SetLoadBabel(false)
.AddScriptWithoutTransform("~/js/react.server.bundle.js");
});
The webpack config looks like this.
var path = require('path');
var webpack = require('webpack');
var WebpackNotifierPlugin = require('webpack-notifier');
module.exports = {
entry: {
server: './React/server.jsx',
client: './React/client.jsx',
clientWithRender: './React/clientWithRender.jsx',
},
output: { path: __dirname + '/wwwroot/js/', filename: 'react.[name].bundle.js' },
module: {
loaders: [
{
test: /.jsx?$/,
loader: "babel-loader",
exclude: /node_modules/,
query: {
presets: ['es2015', 'react'],
plugins: ['react-html-attrs', 'transform-class-properties', 'transform-decorators-legacy']
}
}
]
},
plugins: [
new WebpackNotifierPlugin()
]
};
And heres the index
#{
Layout = null;
}
<html>
<head>
<title>Hello React</title>
</head>
<body>
<div id="app">
#{
if (Environment.GetEnvironmentVariables()["ASPNETCORE_ENVIRONMENT"].ToString() != "Development")
{
#Html.React("ReactComponents.App", new { val1 = "Top text" });
}
}
</div>
#{
if (Environment.GetEnvironmentVariables()["ASPNETCORE_ENVIRONMENT"].ToString() != "Development")
{
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.2/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.4.2/react-dom.min.js"></script>
<script src="#Url.Content("~/js/react.client.bundle.js")"></script>
#Html.ReactInitJavaScript()
}
else
{
<script src="#Url.Content("~/js/react.clientWithRender.bundle.js")"></script>
}
}
</body>
</html>
Why use ReactJS.NET? There is nothing special about an MVC.NET stack which requires a separate project to get React up and running in it. I would use a combination of Babel, Webpack, React, React-Dom and React-Router instead. MVC.NET should just deliver the bundle everything else should be react and it's dependencies.
The problem with the default tutorial of React.NET is that it does not consider the fact you are using webpack to bundle your dependencies and instead has examples of adding them manually. This is not really the preferred way of writing React and makes a complicated process even more complicated by trying to hide away the initial complexity of setting up your React project.
Suggestion:
Webpack will bundle your react-router, react and react-dom amongst other stuff. You need MVC to be setup in a way that every url request is handled by the same controller action that way React-Router can handle the url changes. This answer explains how to do this. ASP.NET MVC - Catch All Route And Default Route
Without doing this, MVC will try to handle all url route changes instead of React-Router doing it's thing.

Resources