Access Node Environment Variables in Jade file - node.js

I am trying to figure out how to optionally display text in a jade file based on the environment. I can't seem to figure out how to access the NODE_ENV variable in my jade file.
In my index.jade file I am doing:
if process.env.NODE_ENV === 'development'
h1 I am in development mode
else
h1 I am not in development mode
The problem is that process.env.NODE_env is undefined.
When I try and do: h1 #{process.env} outside of the if statement, Jade outputs [Object Object] onto the page.
When I try and do: h1 #{process.env.NODE_ENV} outside of the if statement, Jade does not output anything onto the page.
I am not rendering my Jade files on fly, rather I am building them all as "static" files whenever I start the server.

Anything you want to access in the jade template has to be in the locals object sent down from the server. For something like the process environment, you can do this right when you fire up your app:
const express = require('express');
var app = express();
app.locals.env = process.env; // though you might prefer to clone this instead of setting them equal
Then in your jade template you can do
#{env.NODE_ENV}
UPDATE
Adding for direct use, rather than in an express server.
const pug = require('pug');
// Compile the source code
const compiledFunction = pug.compileFile('template.pug');
// Render a set of data
console.log(compiledFunction(process.env));
That'll log it, but of course you could just as easily write that to an HTML file using fs utilities instead.

Related

Node js, express and handlebars - where to apply logic to data before rendering

I am new to node js, express and the handlebar template engine.
I have managed to get data from a json feed and it is rendering correctly in the view. I am doing this by getting the data in the app, then passing the data into and from the router to the view.
However, I need to manipulate and add some logic to some of the json data and the render it.
The feed i have is of an article, it has a publish date value which is in a particular format and i need to format it accordingly and then render it.
My question is, where do i apply the logic for data manipulation so when i render it via the handlebars template, it renders the required format.
The boiler plate folder structure i have setup is;
/server
...
/src
...
/views
...
In the view, i have the following in the home.hndlebars file.
<div class="publish">{{{ article.post.date }}}</div>
I need to format that value.
Thanks
you can make a helper in handlebars it's very helpful in many cases. for yours I would do something like this:
const exphbs = require('express-handlebars');
const moment = require('moment')
var hbs = exphbs.create({
defaultLayout: 'main.handlebars',
// Specify helpers which are only registered on this instance.
helpers: {
moment: function(date) {
return moment(date).format('DD-MM-YYYY');
}
}
})
and then in the html you use the helpers like this:
<div class="publish">{{#moment article.post.date}}{{/moment}}</div>

Dynamic locals unavailable with Pug and Parcel

I have an Express app using Pug templates and Parcel for my bundler. According to the Parcel docs, I can have a config file that stores my locals pug.config.js, however, I need to dynamically add locals to my templates at runtime. Is it possible to do this?
Here are my files:
index.pug
...
h1 #{isAuthenticated}
h1 #{env}
...
pug.config.js
module.exports = {
locals: {
env: process.env.NODE_ENV
}
}
app.js
const Bundler = require('parcel-bundler')
const bundler = new Bundler(path.resolve(__dirname, 'views/index.pug'))
app
.set('views', path.join(__dirname, 'views'))
.set('view engine', 'pug')
.use(function(req, res, next) {
res.locals.isAuthenticated = 'true'
next()
})
.use(bundler.middleware())
Here I am attempting to set a locals var isAuthenticated to 'true', however when rendering index.pug the variable is empty, meanwhile, the env var from my pug.config.js file is present.
Now, if I try to render a page in my controller functions with res.render('index', {isAuthenticated: 'true'}) the variable isAuthenticated is now present, however env is empty.
I'm wondering if I am missing something here or if this works as expected?
I have briefly tried out parcel. The out of box experience is awesome, but when it comes to advanced configuration, it's terrible. That's kinda by design, parcel just oppose the idea of config. Everything bases on convention.
So i check the source code. Turns out parcel only takes pug config from '.pugrc', '.pugrc.js', 'pug.config.js'.
If you choose to stick with parcel way, you can try write your locals value back to one of these files on disk. Need to test it out, might have some async problems to debug.
I personally prefer following method. You use pug as a real template engine, directly use the pug.compile/render API to produce html. You then pipe these html further to parcel, it'll still do the "bundling" part of the job.

Using i18n + handlebars in an express app, but not to generate output html, how to localize?

I have an Express app in which I want to use handlebars to generate localised templated emails to be sent out. The problem is that handlebars requires a globally registered helper to translate items inside the .hbs files, while I need to use a construct such as app.use(i18n.init) to make sure my __ function translates according to the right locale in the context of the current request. Setting a locale globally will lead to concurrency issues.
The only 'solutions' (which aren't because they don't solve my problem) I found consist of using handlebars middleware to output html using Express, but that is not what I want to do. I want to generate content that's completely independent from what Express sends back to the client.
This is what I am currently doing, which obviously isn't the way to do it
const i18n = require("i18n")
const Handlebars = require('handlebars')
i18n.configure({
directory: './i18n',
defaultLocale: 'en',
objectNotation: true,
syncFiles: true
})
Handlebars.registerHelper('i18n',
function (str) {
if(!str) return str
return i18n.__(str)
}
)
to be used as
<td>
{{i18n "title"}}
</td>
A possible solution is to do the translations in code by calling i18n.__({phrase: "someText", locale: locale}) but I would like to keep this inside the template.
How can I make sure handlebars uses the i18n instance bound to the Express response object?

How to set process.env before a browserified script is run?

The initial html comes from the back-end. The server has a defined process.env.NODE_ENV (as well as other environment variables). The browserified code is built once and runs on multiple environments (staging, production, etc.), so it isn't possible to inline the environment variables into the browserified script (via envify for example). I'd like to be able to write out the environment variables in the rendered html and for browserified code to use those variables. Is that possible?
Here's how I imagine that being done:
<html>
<head>
<script>window.process = {env: {NODE_ENV: 'production'}};</script>
<script src="/build/browserified_app.js"></script>
</head>
</html>
Instead of hardcoding enviroment variables here and there, use the envify plugin.
npm install envify
This plugin automatically modifies the process.env.VARIABLE_HERE with what you passed as an argument to envify.
For example:
browserify index.js -t [ envify --DEBUG app:* --NODE_ENV production --FOO bar ] > bundle.js
In your application process.env.DEBUG will be replaced by app:*, process.env.NODE_ENV will be replaced by production and so on. This is a clean && elegant way in my opinion to deal with this.
You can change your entry point file, which would basically to do such setup and then require the original main file.
process.env.NODE_ENV = 'production';
require('app.js');
Other way (imo much cleaner) is to use transform like envify which replaces your NODE_ENV in the code with the string value directly.
Option 1
I think your approach should generally work, but I would't write directly to process.env since I am pretty much sure that it gets overwritten in the bundle. Instead you can make global variable like __env and then in the actual bundle code set it to process.env in your entry file. This is untested solution, but I believe it should work.
Option 2
Use localStorage and let your main script read variables from there upon initialization. You can set variables to localStorage manually or you can even let the server provide them if you have them in there. Developer would just open console and type something like loadEnv('production'), it would do XHR and store the result in the localStorage. Even with manual approach there is still an advantage that these doesn't need to hard-coded in html.
If manual doesn't sound good enough and server is a dead end too, you could just include all variables from all environments (if you have them somewhere) in the bundle and then use switch statement to choose correct ones based on some conditions (eg. localhost, production host).
Thinking about this, you are definitely out of scope of Browserify with your needs. It can make bundle for you, but if you don't want these information in the bundle, you are on your own.
So I've decided it's the web server's job to insert the environment variables. My scenario required different loggers per environment (e.g. 'local','test','prod').
code:
var express = require('express')
, replace = require('replace');
...
var app = express();
var fileToReplace = <your browserified js here>;
replace({
regex: 'ENV_ENVIRONMENT'
, replacement: process.env.ENVIRONMENT
, paths: [fileToReplace]
});
...
app.listen(process.env.PORT);
I hardcoded 'ENV_ENVIRONMENT', but you could create an object in your package.json and make it configurable.
This certainly works, and it makes sense because it's possibly the only server entry point you have.
I had success writing to a json file first, then importing that json file into anywhere that needed to read the environment.
So in my gulp file:
import settings from './settings';
import fs from 'fs';
...
fs.writeFileSync('./settings.json', JSON.stringify(settings));
In the settings.js file:
if(process.env.NODE_ENV) {
console.log('Starting ' + process.env.NODE_ENV + ' environment...');
} else {
console.log('No environment variable set.');
process.exit();
}
export default (() => {
let settings;
switch(process.env.NODE_ENV) {
case 'development':
settings = {
baseUrl: '...'
};
break;
case 'production':
settings = {
baseUrl: 'some other url...'
};
break;
}
return settings;
})();
Then you can import the settings.json file in any other file and it will be static, but contain your current environment:
import settings from './settings.json';
...
console.log(settings.baseUrl);
I came here looking for a cleaner solution...good luck!
I run into this problemn building isomorphic react apps. I use the following (ok, it's a little hacky) solution:
I assign the env to the window object, ofcourse I don't expose all env vars, only the ones that may be public (no secret keys of passwords and such).
// code...
const expose = ["ROOT_PATH", "ENDPOINT"];
const exposeEnv = expose.reduce(
(exposeEnv, key) => Object.assign(exposeEnv, { [key]: env[key] }), {}
);
// code...
res.send(`<DOCTYPE html>
// html...
<script>window.env = ${JSON.stringify(exposeEnv)}</script>
// html...
`);
// code...
then, in my applications clients entry point (oh yeah you have to have a single entry point) I do this:
process.env = window.env;
YMMV AKA WFM!

Adding views in Express/ejs

Basic question time:
I'm new to node.js/express/ejs.
How do I add a new ejs powered page to my server?
Example: I want to have a new page on my server that shows up as mysite.com/foo.html, and I want it to be rendered through app.router & ejs. How do I add this page and start to edit it?
I've started by working from the example of index.*js* that comes with the default express --ejs install. But digging into that code, 'find ./ -name "index.*js*"' comes up with no less than 25 different files that might be involved in producing that two-line index page.
Start me on the right path?
In your views directory add a file named foo.ejs and add the EJS you want to be rendered.
Then create another file named foo.js in the routes directory. Here is what the content
module.exports.index = function(req, res){
res.index('foo');
};
In the main express app file (the one you run via node app.js) first require the new route
var foo = require('./routes/foo');
then tell express about it
app.get('/foo.html', foo.index);

Resources