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.
Related
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.
I and the team got a task to implement multiple themes for our project (keeping in mind that we have a single-theme project right now).
By theme I don't mean just CSS, I mean different markup files, certain features for certain things, etc. (If you don't like the word 'theme' in this context don't use it, I hope you get the idea: some things need to be reused across the codebase, some things need to be specific to a theme). Overall, we have no problem of structuring it in a maintainable way and implementing the most of it, but we don't know how to make multiple express-handlebars configs, which we can switch based on certain factors.
Right now we have a pretty standard express-handlebars project structure like such:
views/
index
layouts/
main.hbs
content.hbs
whatever.hbs
What we want is something like this:
views/
themes
theme1
layouts/
main.hbs
index.hbs
content.hbs
theme2
layouts/
main.hbs
index
We use a pretty standard way to initialize express-handlebars in our app startup file like this:
var handlebars = exphbs.create({
defaultLayout: 'main',
extname: '.hbs',
helpers: require('./server/hbsHelpers')
});
app.engine('.hbs', handlebars.engine);
app.set('view engine', '.hbs');
app.set('views', __dirname + '/views');
I was thinking to initialize several exphbs objects and switch them at runtime (request time) but this is not going to work since app.set would be an app-wide setting (long story short we host multiple websites with one app and can't afford change anything on app level, only on the express request level).
Desired behaviour is something like this:
res.render(‘/theme1/file.hbs’);
looks like there's no problem just to render directly without a configuration, but then we also need to specify that handlebars needs to grab layouts and partials for a specific theme from a specific source
or
res.render(‘file.hbs’, { theme: ‘theme1’ });
We're using this variant of express handlebars – https://github.com/ericf/express-handlebars
(May be you can advice an alternative that allows what I described above that we can interchange and not break a lot of things - our code base is pretty huge)?
Any of your help, ideas or advice would be highly appreciated.
You didn't mention how you would determine which theme each request would be rendered with but I think the simplest approach is to override the res.render() method.
app.use(function(req, res, next) {
// cache original render
var _render = res.render;
res.render = function(view, options, done) {
// custom logic to determine which theme to render
var theme = getThemeFromRequest(req);
// ends up rendering /themes/theme1/index.hbs
_render.call(this, 'themes/' + theme + '/' + view, options, done);
};
next();
});
function getThemeFromRequest(req) {
// in your case you probably would get this from req.hostname or something
// but this example will render the file from theme2 if you add ?theme=2 to the url
if(req.query && req.query.theme) {
return 'theme' + req.query.theme;
}
// default to theme1
return 'theme1';
}
The nice thing about this is your calls in your controller will still be clean - res.render('feature/index.hbs') as long as you have that file in each theme folder you should be good.
You could make getThemeFromRequest a lot smarter and it could check to see if the template exists for that theme and if not render the file from the default theme which may help prevent a bunch of duplicate html.
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!
I can't figure out how to use the dustjs-linkedin templates for express 3.x
#app.js
var dust = require('dustjs-linkedin');
app.set('view engine', 'dust');
app.get('/test1', routes.test1);
#./routes/test.js
exports.test1 = function(req, res){
res.locals.session = req.session;
res.render('test1', { title: 'Test 1' } );
};
#./views/test1.dust
{+base.dust/}
{<main}
Child Content
{/main}
#./views/base.dust
{+main}
Base Content
{/main}
I get the following error when going to /test1
500 Error: Cannot find module 'dust'
I had the same problems as you. And to ease the use of dustjs-linkedin together with express 3.x i put together the small library klei-dust. The library is simple to setup and you can set the root folder for views, which applies to base-templates and partials.
So if you have a views folder at views/ with home.dust and base.dust templates, the home.dust can look like this:
{>base/}
{<main}
Hello world
{/main}
So there's no need to write views/base.dust for it to work.
I've managed to get a working version of dustjs-linkedin with consolidate module.
https://github.com/chovy/express-template-demo
FYI, the layout has to be double quoted...that was a major gotcha for me, and its relative to app.js file, and it needs a trailing /
{+"views/base.dust"/}
<p>Page content here</p>
I will explain u how you should use express 3.x with dustjs-linkedin.
1) express has 2 config to set. 'view engine' and app.engine
"view engine" just sets the default and that app.engine just maps what engine to use for a given file extension.
so you should do something like this:
app.set('view engine', 'dustjs-linkedin');
app.set('views', __dirname + '/views');
app.engine('dust', dust.compileFromPath);
There is only one problem with this is that the method compileFromPath doesn't exist in dust :p.
You should add a method in the dust object with this signature that Express expects: (path, options, callback)
you can read more about this here: http://expressjs.com/api.html#app.engine.
Another option would be to use consolidate (http://spalatnik.com/blog/?p=54) but unfortunately Consolidate doesn't support the dustjs-linkedin version it only support the old dust version.
I'm trying to get Mustache working properly with Express, and as one can guess, I'm having troubles.
The following line initializes Mustache nice and clean. Variables render as expected.
app.register('html', require(__dirname+'/public/js/libs/mustache.js'));
However, the problems start to rise when partials are thrown in the mix. With Mustache, this here partial should invoke header view/partial.
{{> header}}
But alas, nothing happens. :/ Even when I offer the partial directly, Mustache fails to render it.
app.get('/', function(req, res) {
res.render('welcome', {
partials: {
header: '<h1>Header</h1>'
}
});
});
So, it seems that the partials aren't working at all. I've found one hack that get's the partials somewhat working:
http://bitdrift.com/post/2376383378/using-mustache-templates-in-express
After that the partials render when offered directly at render call (see above), but it still fails on rendering the partials directly to views/layouts:
Layout looks something like this:
Behold, the header
{{> header}}
<p>Let's move to more important stuff...</p>
Header view looks something like this:
<h1>Header</h1>
Express can load the views by themselves just ok, but it doesn't know how to handle the Mustache partials...
Using express (at least version 3) and mustache-express, You can load partials as usual using double mustaches that begin with a greater than sign.
First consider the following is appended within our app.js file:
/** import the module */
import mustache from 'mustache-express';
/** view engine setup */
app.engine('mst', mustache());
app.set('view engine', 'mst');
app.set('views', path.join(__dirname, 'mvc/views'));
/** the route where we will display the partials */
app.get('/', (req, res) => {
let view = {
title: 'Homepage',
// ...
};
/** we are going to use a file called template.mst for rendering */
res.render('template', view);
});
Any double mustaches that begin with a greater than sign (i.e. {{> file}}) will be considered a partial. The file within the partial will be rendered at runtime. Consider this file to be a partial we want to insert:
mvc/views/partial.mst
<h2>418 | I'm a teapot</h2>
And here is our template:
mvc/views/template.mst
<h1>template.mst file</h1>
<!-- output: <h2>418 | I'm a teapot</h2> -->
{{> partial}}
Managed to get this working with the latest version of hogan-express.
https://github.com/vol4ok/hogan-express
All that is needed is to install hogan-express and use it as template engine on express. No hacking or tuning required.
I'm not sure what's exactly in your ./public/js/libs/mustache.js ... I use the mustache module from npm + a variation of the templating object you linked to.
In any case, the object you pass to app.register needs to call mustache.to_html(template, locals, partials) at some point.
You need to pass the partials object as the third argument to mustache's to_html.