"We need to re-write EVERYTHING!"... It's understandable that a huge technical debt is created during the first period of building a company app, a lot of business needs, a lot of deadlines and smaller teams. And for webapps where a proper frontend solution was not considered, most of this technical debt falls on frontend and by time it becomes harder and harder to refactor till it reaches a point where the right way to refactor it is to re-write it. But that's not an option sometimes, isn't it?
As an alternative, This article will introduce two approaches to integrate lightweight react apps (we will call them widgets) that can be embedded in any project.
React widgets implemented directly in your codebase.
Create a similar folder structure to this:
.
├── server
└── ....
├── frontend
├── widgets
├── login.js
└── subscribe.js
└── components
├── Login.js
├── Subscribe.js
└── ....
├── dist #ignored
├── widget_login_bundle.js
└── widget_subscribe_bundle.js
├── webpack.config.js
└── package.json
/frontend/widgets
directory will contain entry points for all the widgets, each file inside should render a react app in a corresponding DOM element id
For example /frontend/widgets/login.js
import React from 'react';
import ReactDOM from 'react-dom';
import Login from '../components/Login';
ReactDOM.render(
<Login />,
document.getElementById('widget:login'), // <-- NOTE the element id
);
webpack.config.js
will look like this
module.exports = {
entry: [
'./frontend/widgets/login.js',
'./frontend/widgets/subscribe.js',
],
output: {
filename: 'widget_[name]_bundle.js',
}
...
}
Now running webpack
from the project folder will create two bundle files widget_login_bundle.js
and widget_subscribe_bundle.js
in the dist
folder.
Now you can use a widget in one of your views like this
...
<div id="widget:login"></div>
<script defer src="/dist/widget_login_bundle.js" />
...
In this approach we will keep all frontend in a separate repository and have an npm script to generate an html and bundle js file for each widget and upload them to a CMS.
But first, A new database model need to be introduced that will hold the widget data (name and html) and there should be a way to upload assets to a content server.
For example you may expose two web service APIs similar to this:
example.com/api/widget/save-or-update
(Save or upload a widget entry in the database)example.com/api/content-server/upload
(Upload an asset file to content server)The folder structure will be similar to the previous approach but this time in a separate repository:
.
├── frontend # Same as before
├── dist # Same as before
└── scripts
├── deployWidget.js
└── ....
├── webpack.config.js
└── package.json
/scripts/deployWidget.js
is a nodejs script that expects a --widget
argument and will do the following:
const path = require('path');
const argv = require('yargs').argv;
const uploadAssetToContentServer = require('./uploadAssetToContentServer');
const createOrUpdateWidgetEntry = require('./createOrUpdateWidgetEntry');
const deployWidget = async ({ widget }) => {
const bundleFile = path.join(
process.cwd(),
'dist',
`widget_${widget}_bundle.js`
);
const uploadedUrl = await uploadAssetToContentServer(bundleFile);
const html = `
<div id="widget:${widget}"></div>
<script defer src="${uploadedUrl}"></script>
`;
await createOrUpdateWidgetEntry({
name: widget,
html,
});
console.log(`widget ${widget} deployed successfully`);
};
deployWidget({
widget: argv.widget,
}).catch(console.error);
NOTE: I encapsulated the logic to upload the bundle file and save widget entry in uploadAssetToContentServer.js
and createOrUpdateWidgetEntry.js
respectively, which you will need to implement.
In package.json
...
"scripts": {
....
"deploy-widget": "webpack && ./scripts/deployWidget"
}
And now to deploy the login widget:
npm run deploy-widget -- --widget login
Going back to the backend repository, You can render a specific widget using the html value saved in DB, for example
...
{{ getWidgetHtmlFromDB('login') | raw }}
...
For Standalone Approach
deployWidget.js
script.deployWidget.js
to deploy widgets to QA env. and create the scary-red "Move to production" button in QA CMS to just copy the entry from QA DB to Production DB.