Injecting environment variables to a React application when serving it with an express server - node.js

I'm developing an app that has the backend and frontend as part of a single project and the backend, aside from being the API, it also serves the static files for the frontend by doing this:
const frontendDir = appRoot.resolve("../frontend/build")
app.use(express.static(frontendDir))
app.get("*", (req, res) => res.sendFile(path.resolve(frontendDir, "index.html")))
Is there a way to somehow pre-process those files before sending them to inject some of the environment variables the server has access to but the client obviously doesn't?
I'm trying to make some variables accessible, like SENTRY_DSN, HEROKU_RELEASE_VERSION, HEROKU_RELEASE_CREATED_AT, etc.

I don't know this would help but I think you could use a template engine for passing your data to the front end .like handlebars :
const Handlebars = require('handlebars')
Handlebars.registerHelper('prod', () => {
return process.env.NODE_ENV === 'production';
})
in your html file:
<div class="some-content">
{{#if prod}}
<p>Seen only in production</p>
{{/if}}
<p>This is some example text</p>
</div>

Related

Node Express i18next - How to send locale text to client side js file?

I have i18next setup in Node.js server side. What is the best practice to use i18next.t() in client side javascript files? I've already set up Express to render server side variables to be used in .ejs files. However, I can't transfer them to the js files that are imported in ejs.
Some possible ways I've thought of:
Export and load i18next that was initialized in the server to the client.
I know the <%= %> variables for ejs work in inline scripts. However, I'm avoiding to have them for content security policy. Perhaps there is a way to send this over to the js file?
Load and initialize i18next on the client-side again. I've tried this and it works, but then there are duplicated locale files for both server and client.
Export the locale files by specifying path. EX
app.use('/locale', express.static(path.join(__dirname, 'locale', {{lng}}.json)));
// app.js
const i18next = require('i18next');
const Backend = require('i18next-fs-backend');
const middleware = require('i18next-http-middleware');
i18next.use(Backend)
.use(middleware.LanguageDetector)
.init({
detection: detection_options,
fallbackLng: 'en',
backend: {
loadPath(lng, ns) {
if (lng === 'zh' || lng === 'zh-HK' || lng === 'zh-TW') {
return path.join(__dirname, 'locales/zh-hant.json');
} else if (lng === 'en-US') {
return path.join(__dirname, 'locales/en.json');
}
return path.join(__dirname, 'locales/{{lng}}.json');
}
}
})
app.use(middleware.handle(i18next));
// index.ejs
<%- include('../includes/head.ejs') %>
<link rel="stylesheet" href="/css/index.css">
</head>
<body>
<h1>
<%= t('locale-text-from-en.json-goes-here') %> // this works well
</h1>
<script src="/js/index.js"></script>
</body>
// index.js
console.log(t('locale-text-from-en.json-goes-here')) // how to use i18next.t() here?
To use i18next on the client you need to install the i18next package via npm or yarn on the client. Or download i18next library via their CDN https://unpkg.com/i18next/dist/umd/i18next.js.
i18next has many extensions for your project, like react-i18next for react project, jquery-i18next for jquery project.

How to achieve code splitting + SSR with #loadable/component ? Is webpack needed for the server code?

I'm trying to add code splitting + SSR to my React web app using #loadable/component.
My current situation: This is how I've implemented SSR for robots on my website. Since it's just for robots, I'm not using hydrate. Basically, I send either the index.html with the JS bundle's script tags for a regular user, or I send a fully rendered HTML page for the robots, without the need to hydrate.
My goal: I'd like to use #loadable/component to always return SSR pages from my server, and use hydrate to attach my JS bundle. And also achieve code splitting with that.
Here is how I currently build my app (pseudo code):
1. webpack BUILD FOR entry { app: "src/index.tsx" } AND OUTPUT BUNDLES TO MY /public FOLDER
2. babel TRANSPILE WHOLE `/src` FOLDER AND OUTPUT FILES TO MY /dist_app FOLDER
It's basically 2 builds, one of them is using webpack to bundle, and the other one basically transpiles the files from src to distApp.
And this is what my server does (pseudo code)
1. CHECK IF USER IS ROBOT (FROM THE USER AGENT STRING)
2. IF REGULAR USER
res.send("public/index.html"); // SEND REGULAR index.html WITH JS BUNDLES GENERATED BY WEBPACK
IF ROBOT
const App = require("./dist_app/App"); // THIS IS THE src/App COMPONENT TRANSPILED BY BABEL
const ssrHtml = ReactDOM.renderToString(<App/>);
// ADD <head> <helmet> <styles> ETC
res.send(ssrHtml);
The steps described above works just fine for my initial requirements (ssr just for robots).
But after I added #loadable/component to achieve code splitting + SSR, the set up above does not work anymore.
Because now I have dynamic imports on some of my routes. For example:
const AsyncPage = loadable(() => import("#app/pages/PageContainer"));
So my renderToString(<App/>) call comes back mostly empty, because it does not load those AsyncPages.
Over on the docs for Loadable components: server side rendering they have an example repo on how to achieve this.
But their example is kind of complex and it seems they are using webpack inside the server. I'll post what they do on their server below.
QUESTION
Do I really have to use webpack to bundle my app's server code in order to use #loadable/component for SSR like they are showing in their example? Can't I just use some kind of babel plugin to convert the dynamic imports into regular require calls? So that I'll be able to render it the way I was doing before?
It's weird, that they use webpack-dev-middleware. It's like this example should be used just for development. Does anybody know a repo with a production example of this?
import path from 'path'
import express from 'express'
import React from 'react'
import { renderToString } from 'react-dom/server'
import { ChunkExtractor } from '#loadable/server'
const app = express()
app.use(express.static(path.join(__dirname, '../../public')))
if (process.env.NODE_ENV !== 'production') {
/* eslint-disable global-require, import/no-extraneous-dependencies */
const { default: webpackConfig } = require('../../webpack.config.babel')
const webpackDevMiddleware = require('webpack-dev-middleware')
const webpack = require('webpack')
/* eslint-enable global-require, import/no-extraneous-dependencies */
const compiler = webpack(webpackConfig)
app.use(
webpackDevMiddleware(compiler, {
logLevel: 'silent',
publicPath: '/dist/web',
writeToDisk(filePath) {
return /dist\/node\//.test(filePath) || /loadable-stats/.test(filePath)
},
}),
)
}
const nodeStats = path.resolve(
__dirname,
'../../public/dist/node/loadable-stats.json',
)
const webStats = path.resolve(
__dirname,
'../../public/dist/web/loadable-stats.json',
)
app.get('*', (req, res) => {
const nodeExtractor = new ChunkExtractor({ statsFile: nodeStats })
const { default: App } = nodeExtractor.requireEntrypoint()
const webExtractor = new ChunkExtractor({ statsFile: webStats })
const jsx = webExtractor.collectChunks(<App />)
const html = renderToString(jsx)
res.set('content-type', 'text/html')
res.send(`
<!DOCTYPE html>
<html>
<head>
${webExtractor.getLinkTags()}
${webExtractor.getStyleTags()}
</head>
<body>
<div id="main">${html}</div>
${webExtractor.getScriptTags()}
</body>
</html>
`)
})
// eslint-disable-next-line no-console
app.listen(9000, () => console.log('Server started http://localhost:9000'))

Using javascript code in express-handlebars

I am using nodejs server to run my app along with express-handlebars engine as view engine . I wonder how can I use javascript code in the view in order to manipulate data given by the server as illustrated below :
// from server
app.get("/newcalendar", (req, res)=>{
rdv.find().
then(data=>{
res.render("newcalendar", {servData:data});
})
.catch(error=>{
return console.log(error)
})
});
// to view : newcalendar.hbs , I want to use javascript to manipulate the recieved data :servData
<div>
{{#each servData}}
{{this}} // how can I manupulate this using javascript ?
{{/each}}
</div>

How can i access a nodeJS variable in ejs views in a sails project?

My LoginController.js looks like this:
module.exports = {
getAuthorizationLink: function (req, res) {
res.send({
authorization_link: sails.config.FACEBOOK_LOGIN_URL
})
}
}
I need to redirect to the authorization_link when a button is clicked
<div class="col-md-4 text-center">
<button id="authenticate" class="btn btn-primary">Authenticate Page</button>
<script type="text/javascript">
document.getElementById("authenticate").onclick = function () {
...
};
</script>
</div>
Here you are looking to mix server-side (EJS) & client-side JS code.
It is possible, makes sense to do sometimes but it is not clean.
Once you understand you are doing this. Variable can be passed and accessed.
Using EJS, write JS code for client side e.g.
var auth_link = '<%= authorization_link %>';
this line will become something like below for client-side JS
var auth_link = 'https://fb.com/login';
Now you can use auth_link in client-side JS as required
Also, check res.view for responding with HTML page

Angular not updating html template variables when served via NodeJS server

This is a bit of a specific question, but I'm at a bit of a loss for an answer.
First, a little background. I've been trying to learn angular, and I wanted to start using Node as the backend. I currently have a working tutorial app that I can run locally that just returns data that is hard coded into the main controller.
When I moved the files to my NodeJS server, it stopped working though. Here is what works:
The files load correctly - there are no console errors, and I can view each of the files in the source (index.html, app.js, maincontroller.js)
The scope exists, and the variables are defined. I put a console.log($scope) inside the mainController.js file, and I can see all of the variables defined correctly.
Non-angular javascript works - I can place alerts outside/inside the mainController, and they all work correctly (also, console.log obviously works)
I am serving the files via a simple Node.js server. I am using express and hbs. I was originally using compression, and 0 cache length, but have since removed those with no change in the result.
The specific issue I'm having is that none of the template variables update. I've simplified it down to the following code for testing. When viewed locally, the page says 'I now understand how the scope works!', when served from Cloud 9, the structure exists, but the {{understand}} variable in the template doesn't work.
index.html
<!DOCTYPE html>
<head>
<title>Learning AngularJS</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>
<script src="js/app.js"></script>
<script src="js/maincontroller.js"></script>
</head>
<body>
<div id="content" ng-app="MyTutorialApp" ng-controller="MainController">
{{understand}}
</div>
</body>
app.js
var app = angular.module('MyTutorialApp',[]);
maincontroller.js
app.controller("MainController", function($scope){
$scope.understand = "I now understand how the scope works!";
});
server.js (Node server on Cloud 9)
var express = require('express');
var app = express();
var hbs = require('hbs');
app.set('view engine','html');
app.engine('html',hbs.__express);
app.configure(function() {
app.set('views', __dirname);
});
//app.use(express.compress());
app.use('/js',express.static(__dirname + '/client/js'));
app.use('/css',express.static(__dirname + '/client/css'));
app.use('/img',express.static(__dirname + '/client/img'));
//router
app.get('/',function(req,res){
res.render('client/index.html');
return;
});
//404 responses
app.use(function(req, res, next){
res.status(404);
// respond with html page
if (req.accepts('html')) {
res.render('client/404.html', { url: req.url });
return;
}
// respond with json
if (req.accepts('json')) {
res.send({ error: 'Not found' });
return;
}
// default to plain-text. send()
res.type('txt').send('Not found');
});
app.listen(process.env.PORT);
console.log('listening on port '+process.env.PORT);
everythin became clear when i read
"Handlebars.js is an extension to the Mustache templating language"
what this menas is that hbs uses {{}} as delimiters as well as angular so the {{understand}} in your html never gets to angular because is first parsed and substituted by hbs. if you want to use hbs with angular youll need to change your delimiters using your angulars $interpolateProvider in your app configuration something like
$interpolateProvider.startSymbol('{/{');
$interpolateProvider.endSymbol('}/}');
You can use \{{understand}} as this will counter your hbs and put your angular on top.

Resources