ExpressJS backed with mix of ejs and React frontend - javascript

I currently have a NodeJS application with an Express backend and all the frontend implemented with EJS. An example route looks like this currently:
router.get("/:name&:term", function(req, res) {
Course.find({ courseName: req.params.name, courseTerm: req.params.term }).populate("students")
.exec(function(err, foundCourse) {
if (err) {
console.log(err);
} else {
console.log(foundCourse)
res.render("courses/show", { course: foundCourse });
}
});
});
I have a lot of the pages of the website already implemented following the example route above. So the structure looks something like:
localhost:3000
-/courses
-/show
-/review
-/professors
-/show
-/rating
...
But I recently took a React course and want to use React for new pages like localhost:3000/groups
Is there a way to use React for some pages and still have EJS pages that I currently have? or do I have to rewrite all the EJS pages to React components? I'm still very new to web dev so any advice would be highly appreciated.
From my understanding, I would have to let the React app catch only certain requests/routes but I am not quite sure how to do that.

To answer this question thoroughly I'd have to write a small book, so I only show a basic case where the code is rendered client-side. I also assume that you know how to transpile jsx to js, so I'll treat all react files as already transpiled (that is all <> are converted to React.createElement()).
First, you have to create a route that will be handled by react (/groups), (I assume that you'll use some data for the rendering as in your example):
router.get("groups/:name&:term", function(req, res) {
Groups.find({ groupName: req.params.name, courseTerm: req.params.term }).populate("students")
.exec(function(err, foundGroup) {
if (err) {
console.log(err);
} else {
// here will be the react rendering code
}
});
});
Assuming that you have react code in index.js, you have to add it to the html code as well as data required by the app:
router.get("groups/:name&:term", function(req, res) {
Groups.find({ groupName: req.params.name, courseTerm: req.params.term }).populate("students")
.exec(function(err, foundGroup) {
if (err) {
console.log(err);
} else {
res.send(`<html>
<head>
<script>
const initialData=${JSON.stringify(foundGroup)};
</script>
</head>
<body>
<div id="react-root"></div>
<script src="index.js"></script>
</body>
</html>`);
}
});
});
Your App should consume the data like this:
React.render(<App initialData={initialData}/>, document.getElementById("react-root");
This should get your react code up and running. Going further, you can use react-dom/server to prerender html on the server, and mix-in ejs file as a template in which you inject react code to utilize your existing codebase.

Is there a way to use React for some pages and still have EJS pages that I currently have?
Yes you can, react is just javascript.
You can use create-react-app for a single page and serve that from the route of your choice but keep in mind that your front-end won't become a SPA by doing this you'll just be serving a react app on a certain route.
A somewhat better approach would be to not use create-react-app and instead setup your custom webpack build process to convert jsx to javascript, write a minimal ejs template to include your transpiled javascript code into the HTML, and serve that template from a route.

Reactjs component can be part of your backend code when using SSR(server side rendering) which is much more complicated. Or you can use a express static server to serve reactjs app which is ready to deploy, then express will only be a simple the static file router.

Related

Using react component in a different application

What I'm trying to achieve is:
Building simple react app - the template is create react app
Copying output file (main.*.js)
Pasting it in another react app
Importing render function to render the first app into the second one
Simple react app code:
interface Props {
greeting: string;
}
export module AppModule {
export const sendGreetings = ({ greeting }: Props) => {
return `Hello ${greeting}`;
};
}
Builder file code:
!function(){"use strict";var n;(n||(n={})).sendGreetings=function(n){var e=n.greeting;return"Hello ".concat(e)}}();
Trying to import this file into another app I get this error:
File 'c:/vscode/test-react-app/test-sc-react/src/main.783e0281.js' is not a module.ts(2306)
Which is obvious. I changed the output file manually to:
export function initApp(){"use strict";var n;(n||(n={})).sendGreetings=function(n){var e=n.greeting;return"Hello ".concat(e)}};
It works but the only function that I'm able to access is initApp but not sendGreetings
I've been struggling with this for a while now and I would really appreciate any helpful suggestions
I used Bit.dev for my components that are used across multiple applications & there is an article regarding your issue
https://blog.bitsrc.io/sharing-react-components-across-multiple-applications-a407b5a15186
I think it would help.
🎯 Solution #1
You can use an iframe to inject your react app:
<iframe src='path-to-your-app.html'/>
🎯 Solution #2
Go with micro-frontend architecture approach. Where a front-end app is decomposed into individual, semi-independent "microapps" working loosely together.
As a starting point, you can try npx create-mf-app instead of the CRA.
You can include your js code directly on run time. You can use window.addEventListener to load js/css incoming from an outside source. You just have to append that js to your document on the load event.

How to access json data file for all svelte components in sapper/polka

I'm trying to rewrite my website(Pug+Express) in Sapper(sveltejs). I'm a beginner in sveltejs , so kindly excuse if my question may come out really naive.
I have a template.json file that contains all the static data of my website. In the expressjs version I do const template = require('template.json') and render the page using pug template something like this
router.get('/:pageName', function(req, res, next) {
res.render('pages/About', {template: template})
What would be an equivalent version of achieving this in sveltejs/sapper?
So far I did an import template from 'template.json' in app/server.js file.
Then what? Since the sapper-template is using polka instead of express, I'm confused how to get it right.Any suggestions?
You'd put that data in the pages (i.e. components in routes) that use it:
<!-- routes/index.html -->
<h1>{{title}}</h1>
<script>
import data from './_template.json';
export default {
data() {
return {
title: data.title
};
}
};
</script>
Note that I've added an underscore to _template.json, to hide the file from the router — you could also just place the JSON file outside the routes directory.
You can use Express instead of Polka; just npm install express and replace every occurrence of polka in app/server.js with express.

How to include a vue component only when required in spa?

I have made components and saved into different '.vue' files and compiling them with the help of elixir / gulp and of course browserify.
I am including all the components into one js file,
I want to know that how to reduce the size of "build.js" file that is called each time in every page that contains all the components of the app.
It would be helpful to know such a way to include components only when they required.
This question is not related to Vue-router
I am new to vue.js
Here is your answer: http://router.vuejs.org/en/lazy.html
This is supported natively in Webpack. The example is:
require(['./MyComponent.vue'], function (MyComponent) {
// code here runs after MyComponent.vue is asynchronously loaded.
})
If you want to do it with Broswerify, you'll need https://github.com/substack/browserify-handbook/blob/master/readme.markdown#partition-bundle . The code looks like:
router.map({
'/async': {
component: function (resolve) {
loadjs(['./MyComponent.vue'], resolve)
}
}
})

react server side rendering with client side routing

An initial server rendering for my homepage route ( / ) works fine.
Also, subsequent client side navigation to ( /#/page2 ) works fine.
However, if I load /#/page2 directly from the address bar, the server rendered homepage loads in the browser first and then visibly transitions to /#/page2, which is not what I want. I want only /#/page2 to show up without first flashing the homepage.
What's happening is that node is serving up the homepage for the request to /, and then when the response hits the client, the client is running the route handler for /#/page2. Both are behaving correctly. But it's not what I want.
How do I avoid this behavior?
I think what I need is a way to for both the server and client to be aware of the different routes and both be able to handle them (isomorphically), however, the fragment part of the url is not known to the server.
Anyone else have this problem?
This problem isn't react specific. It is specific to SSR to a deep link.
My node router handles "/" as follows
router.get('/', function(req, res) {
var React = require('react');
var Router = require('react-router');
var Routes = require("../app/clapi-routes.jsx");
var router = Router.create({location: req.url, routes: Routes});
router.run(function(Handler, state) {
var html = React.renderToString(<Handler/>);
return res.render('index.ejs', {html:html});
})
});
index.ejs is just:
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="/css/json-inspector.css"/>
</head>
<body style="margin:0">
<%- html %>
<script src="/build/bundle.js"></script>
</body>
</html>
Stop using hash powered navigation. Everything that comes after # is client-side only and useless for something like this. So /#/page2 needs to become /page2.
I'm not sure about react, but the same issue exists with other routing systems and there it's really easy to turn off the # in urls.
In angular's ui-router is done like this $locationProvider.html5Mode(true);
Your server-side would need to know to react to all the URLs your client-side knows, but that's how robustness is achieved - doesn't matter how navigation happens (client-side event or link click), both client and server can both handle the scenario end-to-end.
I've found out that there are two steps:
First: Change the react router to use Router.HistoryLocation instead of the default (HashLocation). This makes your routes use html5 push state and changes your route paths from /#/page2 to /page2
// in app.jsx (client side routing)
Router.run(AppRoutes, Router.HistoryLocation, function(Handler) {
React.render(<Handler/>, document.body);
});
Second: Ensure that your node page routes all return the same "index.ejs". Otherwise your routes, like /page2 will 404 on a full page refresh (or deeplink)
// in server.js (server side routing)
router.get('*', function(req, res) {
Router.run(Routes, req.path, function(Handler) {
var html = React.renderToString(<Handler/>);
return res.render('index.ejs', {html: html});
});
});
Additionally: If you are serving "public" static assets, declare that before your routes, and remove any public/index.html you might have so that your node router handles / requests with the server rendered content.

Meteor: inject html into client-side file on startup

Using the SSR and inject initial packages,
I currently have the following server-side code:
Meteor.startup(function() {
.....
Inject.rawHead('importList', function(imports) {
return imports = Blaze.toHTML(Template.imports);
});
});
This injects a list of html imports into the head, and works perfectly.
I'd like to modify the function so that the code is injected into /client/imports.html instead of into the head... can this be done?
Looks close to this solution. Try this in the client folder of Meteor or use if(Meteor.isClient) for compactness:
Inject.rawHead('importList', function(imports){
return imports = Blaze.toHTML(Template.imports)
})
Meteor.startup(function(){
//...
})

Categories