Let's assume we have a website, that should show a reactjs application.
The following points are necessary:
The ReactJS application should be embedded by using a short snippet (script / html)
The ReactJS app should be updated without changing the snippet itself
The ReactJS app is hosted on a completely different server
It should not be an iFrame if possible
So what I want to achieve is similary to a Google Map for instance. You have a small snippet and you can show an application on your side.
What are the best practices to do so ? What do I have to take into consideration ?
"Micro frontends":
https://medium.com/#tomsoderlund/micro-frontends-a-microservice-approach-to-front-end-web-development-f325ebdadc16
I came across this idea only recently. So, I don't have much to tell you regarding your requirements. But it looks promising. But also may be an overkill.
And by following links you'll be able to find some code examples.
E.g. https://single-spa.js.org/docs/examples/
simple example without iframe
<script>
(function(window,document, id, scriptUrl, appId){
// create container where to render app
var containerDiv = document.createElement("DIV");
containerDiv.id=appId
document.getElementById(id).appendChild(node);
// add script tag
var scriptTag = document.createElement('script');
scriptTag.type = 'text/javascript';
scriptTag.src = scriptUrl;
document.body.appendChild(scriptTag);
// also you may need to send you app info about where should render (id)
window.MY_WIDGET_ID = appId
})(window,document, 'where-to-render-app-id', 'script-url', 'app-id');</script>
<script >
// inside your react app you should add
render( document.getElementById(window.MY_WIDGET_ID ))
A regular React application is a set of JS(let's ignore the CSS, images, other assets this time) files. And there is a file called the entry which mounts the entire application to a specific dom. You might be familiar with the below code.
ReactDOM.render(<App/>, document.getElementById('app'))
The above code is auto executed usually once the entry is loaded onto a predefined dom. We can expose it as an initial handler of the application.
window.apps = {} // This better be executed in a script hosted by the website
window.apps['my-app'] = dom => ReactDOM.render(<App/>, dom)
The script hosted by the website then is able to start the application by calling the function above.
window.apps['my-app'](document.getElementById('root'))
In this way, the website takes the control of initial a React application, at any time, onto any dom, or even any instance.
ps. Ignore type checks, null checks. You should add it as you need to make sure no runtime error happens.
As an other option, you could wrap your react app into web component. Here's the example. But it could be overengineering, from case to case, mpc's approach could be more reasonable easily.
you can create a shell app that will load your remote code and run it.
btw, check out fronty, it is a micro-frontend tool that can help you with that with no hassle.
This is one of the feature React offers. If you take the Basic files provided by React it is an HTML page with a <div id="root"></div>. By default, React is built as a single page App. In fact, you can edit directly this HTML file (located in Public folder) and the React will still run.
So to achieve what you are looking for, build your React project include it to your HTML project (The same include present in the public/index.html -> <script src="/static/js/main.******.chunk.js"></script>.
In the React project, you add the same render condition:
if (document.getElementById("root")) {
ReactDOM.render(
<App />
document.getElementById("root")
);
}
Wrapping the ReactDom.render in an if is to make sure the desired ID is present in your dom.
That's it, it should be working.
Have fun.
Related
I've got a (small) React app (vanilla create-react-app), that I would like to appear in a modal (bootstrap or similar) on another site. Is there a library that will simplify this process?
Specifically, the entire use case is that if my Javascript file is loaded (and just one javascript file), it will insert a "Click Me" type call to action, and when clicked my App component will be loaded into a new modal. It will need the CSS (for the app) to be included in some form as well.
I think all of this (excluding the call-to-action which is fairly simple) could be done during Babel/Webpack transpilation but I can't find anything off-the-shelf that seems to do this.
This functionality is built into ReactDOM.render. Simply add an id to your element.
For example:
<!-- index.html -->
<script src="url/to/react/build/asset" as="script" />
<div id="button-and-modal"></div>
Then to render your react app inside the div:
// index.js
import { render } from 'react-dom';
import App from './App'
function renderReact() {
const element = document.getElementById('button-and-modal');
render(<App/>, element)
}
document.addEventListener('DOMContentLoaded', renderReact);
Then your react app would look something like this:
const App = () => (
<div>
<Button/>
<Modal/>
</div>
)
You can also code the button and modal outside of the react app and only have the modal content rendered by react. If you want to do that, then follow the same directions but add the javascript for the button+modal inside the renderReact function.
You can use for example https://direflow.io/ to build your react app as a web component that you can render anywhere on any site.
Using your current project you can do
direflow create
cd <project-name>
npm install
and then
copy your whole app in folder into direflow-components so your project tree would look like:
/public
/src
/direflow-components
/<project-name>
// leave here only index.ts from example and copy all your project files here
index.ts
component-exports.ts
index.ts
react-app-env.d.ts
.eslintrc
...
If needed you can change
...
component: App,
configuration: {
tagname: 'example-component',
},
...
to your component that you want to render and tagname by which app will be accessible.
After all that you just do
npm run build
copy direflowBundle.js from build folder to your website
and render your app on some website like so:
<body>
<script src="./direflowBundle.js"></script>
<awesome-component></awesome-component>
</body>
I feel like I deal with this issue at every Front End job. It's definitely not easy, but I've found a number of ways to do it. I've tried the bundling idea you suggested but that one gave me the hardest time. The easiest way imo without a lot of hassle is to host your react app on a blank web page, then load it into an iframe where you need it.
At my last job, we wanted to migrate our shopify website to react, but with the way the shopify architecture was set up at the time, it made it difficult to us host a server-side rendered react app. So we built the web pages using Next.js and then deployed it to Vercel. We then inserted this as an iframe into the shopify website. It worked beautifully.
So I have a users.js JSX file with some exported component:
... return <MainContainer keywords="users"> export default Users
when using SSR/SSG, I get the users HTML (just a bunch of <li> tags) in the browser as expected
the browser also receives a .next/static/chunks/pages/users.js with digested/lower-level representation of that React component as client-side js. This gets imported via <script> in HTML.
AssumptionL that js file is for rendering, CSR-style, of the users dataset, into HTML.
Because it contains stuff like
_components_MainContainer__WEBPACK_IMPORTED_MODULE_3 ... react_jsx_dev_runtime__WEBPACK_IMPORTED_MODULE_0__["jsxDEV"])("li", ....
So, clearly the js in <script> can create <li> elements as well as the server. I think it can create the whole page content, if executed.
Question: why the apparent duplication of effort? Does the browser, with SSR/G, get BOTH HTML and js and js ALSO runs producing HTML - surely not? I am using getStaticProps in my users.js
If the assumption why we have a compiled/digested React js (under .next/static) in the browser, is incorrect, then why does NextJS need this file pulled in via <script> ?
Next.js is used for server-side rendering of the react apps. React with another framework like angular and vue.js are client-side frameworks, they run in browsers but there are some technologies to run this framework on the server-side, and next.js is a solution for running react application server-side. It also makes react development very simple features of Next.js are:
Server rendering React Apps.
Automatic code splitting and lazy loading.
Built-in CSS support.
Hot Reloading support.
I am learning web development, and I found that to render a change in a web-site, we will have to make a request to the server (say it's built using express.js), and the server will render the new page, from the EJS templates provided. But in this case, whenever a change has to be reflected on the client-side, the entire web-page must be received and rendered in the browser.
I want to know if there is a way in the vanilla JS, through which I can render only a part of the page, instead of rendering the complete page again and again?
Thank you!
Yes, you can. Check this video. It's an ecommerce tutorial using javascript only in a ...react-kinda-way. The same guy has another one where he builds the exact same project but with React. So you can compare both methods.
Basic structure is
const layoutComponent = {
render: async () => {
...function content
}
return `
<div> things </div>
and html stuff
`
https://www.youtube.com/watch?v=N3FDyheHVMM&t=8349s
I'm working on a project where I'd like to load an Ember.js application from another website, hosted on a different server and using a different domain name. In other terms, I'd like to include an Ember app in other website like I would do with an iFrame, but without an iFrame.
I built my Ember.js application using Yeoman and the Ember generator.
In the origin website, I just have a simple markup like this:
<body>
<h1>My website</h1>
<p>Lorem ipsum...</p>
<div id="myEmberApp"></div>
<p>Lorem ipsum...</p>
</body>
I know how to call an external JS file, but don't know from here how to execute or to load my Ember.js app. I tried also with CORS, but I don't think it will suit my needs.
For the records, I can't use an iFrame. On the origin website, I don't want to have any dependencies against jQuery or whatever. In the future, I'd like to be able to offer a step by step how-to to integrate this app on any websites.
Is there any solution? Or should I have to plan to do my app in full JS without Ember.js?
Let me know if you need more information.
Thanks in advance
--- Edit ---
Here's how I call my JS file from the origin website:
<!-- The JS script to be included by the client -->
<script>
(function () {
var eh = document.createElement('script');
eh.type = 'text/javascript';
eh.async = true;
eh.src = '//localhost:9000/scripts/edenhome.js';
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(eh, s)
})();
</script>
I hope this helps.
Perhaps I am missing something, but why not just include your application in a script tag? I'm not familiar with the yeoman generator, but most of these build tools kick out an ember application with templates precompiled into JavaScript functions and then all your files concatenated together in a single file.
For example:
// this would be the precompiler output generated by yeoman, not exactly like
// this, but same idea, the template is part of the JS file
Ember.TEMPLATES['application'] = Ember.Handlebars.compile('<h1>I am an ember app</h1>');
// make an app and set the rootElement
var App = Ember.Application.create({
rootElement: '#myEmberApp'
});
You can see here I'm loading in the ember app from a different domain:
http://emberjs.jsbin.com/mawuv/1/edit
edit follow up
Injecting the script works just as well as a script with a src attribute:
http://emberjs.jsbin.com/mawuv/2/
I would suggest the following,
disclaimer: i have not actually tried what you are asking, but i do usually load everything from separate files but within the context of the same domain.
create a function that loads all templates and appends them to the document.
create a function that runs the complete ember.js app.
create the file that will be referenced by the remote site and will search for a specific element (this could also be parametrized / be a jquery plugin etc) and once found loads all templates calling function from (1) and start the application calling function from (2) with rootElement the specific element mentioned earlier. (Prior to any of the previous loadings, all dependencies of the ember.js app should be loaded e.g. jquery, handlebars.js, ember.js etc. probably in a no conflict state)
All ajax requests to the server from the ember app should be carried out using jsonp.
simplified example,
http://emberjs.jsbin.com/nelosese/1/edit
js
/*it is possible to load templates from independent files, using promises, however here it is just demonstrated the loading of templates*/
/*a callback is added since they are loaded async and only when this is done should the app complete initialization */
function initTemplates(callback){
var templates ='<script type="text/x-handlebars"><h2> Welcome to Ember.js</h2>{{outlet}}</script><script type="text/x-handlebars" data-template-name="index"><ul>{{#each item in model}}<li>{{item}}</li>{{/each}}</ul></script>';
$('body').append(templates);
callback();
}
function initApp(){
App = Ember.Application.create({
rootElement: '#myEmberApp'
});
App.Router.map(function() {});
App.IndexRoute = Ember.Route.extend({model: function() {
return ['red', 'yellow', 'blue'];
}
});
}
/*a reference to your bootstrap script, that executes the following*/
$(document).ready(function(){
if($('#myEmberApp').length>0){
$.get("/",function(){initTemplates(function(){
initApp();
});});
}
});
html
<body>
<div id="myEmberApp"></div>
</body>
With a single page app, where I change the hash and load and change only the content of the page, I'm trying to decide on how to manage the JavaScript that each "page" might need.
I've already got a History module monitoring the location hash which could look like domain.com/#/company/about, and a Page class that will use XHR to get the content and insert it into the content area.
function onHashChange(hash) {
var skipCache = false;
if(hash in noCacheList) {
skipCache = true;
}
new Page(hash, skipCache).insert();
}
// Page.js
var _pageCache = {};
function Page(url, skipCache) {
if(!skipCache && (url in _pageCache)) {
return _pageCache[url];
}
this.url = url;
this.load();
}
The cache should let pages that have already been loaded skip the XHR. I also am storing the content into a documentFragment, and then pulling the current content out of the document when I insert the new Page, so I the browser will only have to build the DOM for the fragment once.
Skipping the cache could be desired if the page has time sensitive data.
Here's what I need help deciding on: It's very likely that any of the pages that get loaded will have some of their own JavaScript to control the page. Like if the page will use Tabs, needs a slide show, has some sort of animation, has an ajax form, or what-have-you.
What exactly is the best way to go around loading that JavaScript into the page? Include the script tags in the documentFragment I get back from the XHR? What if I need to skip the cache, and re-download the fragment. I feel the exact same JavaScript being called a second time might cause conflicts, like redeclaring the same variables.
Would the better way be to attach the scripts to the head when grabbing the new Page? That would require the original page know all the assets that every other page might need.
And besides knowing the best way to include everything, won't I need to worry about memory management, and possible leaks of loading so many different JavaScript bits into a single page instance?
If I understand the case correctly, you are trying to take a site that currently has pages already made for normal navigation, and you want to pull them down via ajax, to save yourself the page-reload?
Then, when this happens, you need to not reload the script tags for those pages, unless they're not loaded onto the page already?
If that is the case, you could try to grab all the tags from the page before inserting the new html into the dom:
//first set up a cache of urls you already have loaded.
var loadedScripts = [];
//after user has triggered the ajax call, and you've received the text-response
function clearLoadedScripts(response){
var womb = document.createElement('div');
womb.innerHTML = response;
var scripts = womb.getElementsByTagName('script');
var script, i = scripts.length;
while (i--) {
script = scripts[i];
if (loadedScripts.indexOf(script.src) !== -1) {
script.parentNode.removeChild(script);
}
else {
loadedScripts.push(script.src);
}
}
//then do whatever you want with the contents.. something like:
document.body.innerHTML = womb.getElementsByTagName('body')[0].innerHTML);
}
Oh boy are you in luck. I just did all of this research for my own project.
1: The hash event / manager you should be using is Ben Alman's BBQ:
http://benalman.com/projects/jquery-bbq-plugin/
2: To make search engines love you, you need to follow this very clear set of rules:
http://code.google.com/web/ajaxcrawling/docs/specification.html
I found this late and the game and had to scrap a lot of my code. It sounds like you're going to have to scrap some too, but you'll get a lot more out of it as a consequence.
Good luck!
I have never built such a site so I don't know if that is nbest practice, but I would put some sort of control information (like a comment or a HTTP header) in the response, and let the loader script handle redundancy/dependency cheching and adding the script tags to the header.
Do you have control over those pages being loaded? If not, I would recommend inserting the loaded page in an IFrame.
Taking the page scripts out of their context and inserting them in the head or adding them to another HTML element may cause problems unless you know exactly how the page is build.
If you have full control of the pages being loaded, I would recommend that you convert all your HTML to JS. It may sound strange but actually, a HTML->JS converter is not that far away. You could start of with Pure JavaScript HTML Parser and then let the parser output JS code, that builds the DOM using JQuery for example.
I was actually about to go down that road for a while ago on a webapp that I started working on, but now I handed it over to a contractor who converted all my pure JS pages into HTML+JQuery, whatever makes his daily work productive, I dont care, but I was really into that pure JS webapp approach and will definitely try it.
To me it sounds like you are creating a single-page app from the start (i.e. not re-factoring an existing site).
Several options I can think of:
Let the server control which script tags are included. pass a list of already-loaded script tags with the XHR request and have the server sort out which additional scripts need to be loaded.
Load all scripts before-hand (perhaps add them to the DOM after the page has loaded to save time) and then forget about it. For scripts that need to initialize UI, just have each requested page call include a script tag that calls a global init function with the page name.
Have each requested page call a JS function that deals with loading/caching scripts. This function would be accessible from the global scope and would look like this: require_scripts('page_1_init', 'form_code', 'login_code') Then just have the function keep a list of loaded scripts and only append DOM script tags for scripts that haven't been loaded yet.
You could use a script loader like YUI Loader, LAB.js or other like jaf
Jaf provides you with mechanism to load views (HTML snippets) and their respective js, css files to create single page apps. Check out the sample todo list app. Although its not complete, there's still a lot of useful libraries you can use.
Personally, I would transmit JSON instead of raw HTML:
{
"title": "About",
"requires": ["navigation", "maps"],
"content": "<div id=…"
}
This lets you send metadata, like an array of required scripts, along with the content. You'd then use a script loader, like one of the ones mentioned above, or your own, to check which ones are already loaded and pull down the ones that aren't (inserting them into the <head>) before rendering the page.
Instead of including scripts inline for page-specific logic, I'd use pre-determined classes, ids, and attributes on elements that need special handling. You can fire an "onrender" event or let each piece of logic register an on-render callback that your page loader will call after a page is rendered or loaded for the first time.