Jade.escape is undefined on compiled Jade template - node.js

I've compiled a jade template like:
jade --client --no-debug ...
Then on client side included jade.js and the compiled template file. But jade.escape is undefined. I notice the compiled template function looks like:
function anonymous(locals, attrs, escape, rethrow, merge) {
attrs = attrs || jade.attrs; escape = escape || jade.escape; rethrow = rethrow || jade.rethrow; merge = merge || jade.merge;
var buf = [];
with (locals || {}) {
var interp;
buf.push('<h1>');
var __val__ = title
buf.push(escape(null == __val__ ? "" : __val__));
buf.push('</h1>');
}
return buf.join("");
}
Notice how escape is passed in as a parameter. So how might the expected usage be like?
Suppose I have a very simple template:
h1= title
Then I use it like:
html = anonymous({title: "Hello World!"})
But it means escape will be undefined? I notice jade.escape is also undefined although I included jade.js

you need to add runtime.js along with jade.js inorder to work with jade.escape.

Related

How can I read a file in node js, find all instances of a function and then extract each function's argument?

I'm trying to write a node script that identifies unused translation strings in my React project.
First, I want to get a list of all the translations that are used. To do this, I am getting a list of each JS file in my /src/components folder and then reading the file.
My translation strings look like this: t('some.translation.key'), so basically, I want to identify each instance of t('...') using RegEx and then get the key in between those parentheses (i.e. "some.translation.key"). From there, I should be able to compare the keys to the ones in my translation JSON file and remove the ones that aren't being used.
unused.js
const path = require('path');
const fs = require('fs');
let files = [];
// https://stackoverflow.com/a/63111390/2262604
function getFiles(dir) {
fs.readdirSync(dir).forEach(file => {
const absolute = path.join(dir, file);
if (fs.statSync(absolute).isDirectory()) {
getFiles(absolute);
} else {
if (absolute.includes('.js')) {
files.push(absolute);
}
}
});
return files;
}
function getTranslations() {
const pathComponents = path.join(__dirname, '../../src/components');
// get all js files in components directory
const files = getFiles(pathComponents);
const translationKeys = [];
// for each js file
for(let i = 0; i < files.length; i++) {
// read contents of file
const contents = fs.readFileSync(files[i]).toString();
// search contents for all instances of t('...')
// and get the key between the parentheses
}
}
getTranslations();
How can I use RegEx to find all instances of t('...') in contents and then extract the ... string between the parentheses?
Yes, you could use a regular expression:
for (const [, str] of contents.matchAll(/\bt\(['"](.*?)['"]\)/g)) {
console.log('t called with string argument:', str)
}
However, with regular expressions the problem will be that they don't understand the code and would cause trouble with matching strings that contain ( ) or \' themselves, have issues with concatenated strings or extra whitespace, etc., and you'd then also get the contents literally, including possible escape sequences.
A more robust way would be to create an AST (abstract syntax tree) from the code and look for calls to t in it.
A popular AST parser would be acorn. There is also the supplementary module acorn-walk that helps walking through the whole syntax tree without building your own recursive algorithm.
import acorn from 'acorn'
import walk from 'acorn-walk'
// Example
const contents = "function a () { if (123) { t('hello') } return t('world') }"
// The arguments to acorn.parse would have to be adjusted based
// on what kind of syntax your files can use.
const result = acorn.parse(contents, {ecmaVersion: 2020})
walk.full(result, node => {
if (node.type === 'CallExpression' && node.callee.type === 'Identifier' && node.callee.name === 't') {
if (node.arguments.length === 1 && node.arguments[0].type === 'Literal' && typeof node.arguments[0].value === 'string') {
// This is for the case `t` is called with a single string
// literal as argument.
console.log('t called with string argument:', node.arguments[0].value)
} else {
// In case you have things like template literals as well,
// or multiple arguments, you'd need to handle them here too.
console.log('t called with unknown arguments:', node.arguments)
}
}
})
// Will output:
// t called with string argument: hello
// t called with string argument: world

How to use templating (handlebars, or any alternative) with Node.js and without using a framework (ex = express)?

For example, I have this JSON document "foo.json":
{
"foo": [
{
"bar": "Hello World!"
},
{
"bar": "The End"
}
]
}
In Node.js, I would like to use templating (handlebars or any) to generate a string from the JSON document, such as:
<p>Hello World!</p><p>The End</p>
... And then assign that string value to a variable in Node.js. Finally, I'll concatenate more values to the variable and output the final variable value as an html document.
Can this be done without using a framework like Express?
If you want to use handlebars, just grab the npm module:
npm install handlebars
Then in your script, you can use handlebars to render your output based on a simple template that iterates over the array foo and creates a <p> for each item, containing the text of the bar property:
var handlebars = require('handlebars');
// get your data into a variable
var fooJson = require('foo.json');
// set up your handlebars template
var source = '{{#each foo}}<p>{{this.bar}}</p>{{/each}}';
// compile the template
var template = handlebars.compile(source);
// call template as a function, passing in your data as the context
var outputString = template(fooJson);
If you want to use a .hbs template file instead of a string source you can use the fs module to read the file with fs.readFile, call toString() on the returned buffer, and use that to call a rendering function. Try this:
var handlebars = require('handlebars');
var fs = require('fs');
// get your data into a variable
var fooJson = require('path/to/foo.json');
// read the file and use the callback to render
fs.readFile('path/to/source.hbs', function(err, data){
if (!err) {
// make the buffer into a string
var source = data.toString();
// call the render function
renderToString(source, fooJson);
} else {
// handle file read error
}
});
// this will be called after the file is read
function renderToString(source, data) {
var template = handlebars.compile(source);
var outputString = template(data);
return outputString;
}

How to include a file which is not a module in Node.js (to make a module of it)?

Purpose: making a Node.js module or a Requirejs AMD module, from a plain old JavaScript file like this:
var foo = function () {};
I have no control and I can't edit foo.js.
Whyfoo is is a plain empty object inside the first if? How can I "include" a plain JavaScript file in my module? File mymodule.js:
(function(root) {
if(typeof exports === 'object') { // Node.js
var foo = require('./foo');
console.log(foo); // { }
} else if (typeof define === 'function' && define.amd) { // RequireJS AMD
define(['foo'], function () {
return foo;
});
}
}());
Node modules loaded with require must populate an exports object with everything that the module wants to make public. In your case, when you require the file nothing is added to exports, hence it shows up as empty. So the short answer is that you need to modify the contents of foo.js somehow. If you can't do it manually, then you need to do it programmatically. Why do you have no control over it?
The easiest being that you programmatically wrap the contents if foo.js in the code needed to make it a proper module.
// Add an 'exports' line to the end of the module before loading it.
var originalFoo = fs.readFileSync('./foo.js', 'utf8');
fs.writeFileSync('./foo-module.js', originalFoo + "\nexports.foo = foo;");
var foo = require('./foo-module.js');
You Could Try:
var fs = require('fs');
var foo = fs.readFileSync('foo.js', 'utf8') // Or request it somehow, just get it as a string
foo += "module.exports.foo = module.exports = foo;";
fs.writeFileSync('fooModule.js',foo);
var foo = require('./fooModule');
// foo() and foo.foo() are both the same
Note: This requires node.

Serving client side Jade templates

I want to use Jade templates at the client side with Backbone. How can I do that?
For now, I have successfully configured Backbone (Marionette) to compile Jade templates for use in its Views:
Marionette.TemplateCache.prototype.compileTemplate = (tmplStr) ->
console.log "jade stuff: ", jade.compile(tmplStr)
return jade.compile(tmplStr)
The "problem" is: I am currently writing templates like:
script(type="text/template", id="tmplMainView")
| h1= title
| p= content
Notice the pipes (|) those are to prevent Jade from trying to interpret/parse them on server side. How can I eliminate those?
UPDATE
Perhaps I can use the jade --client flag ... but it gives a single compiled function: for example
h1= title
Becomes
function anonymous(locals, attrs, escape, rethrow, merge) {
attrs = attrs || jade.attrs; escape = escape || jade.escape; rethrow = rethrow || jade.rethrow; merge = merge || jade.merge;
var buf = [];
with (locals || {}) {
var interp;
buf.push('<h1>');
var __val__ = title
buf.push(escape(null == __val__ ? "" : __val__));
buf.push('</h1>');
}
return buf.join("");
}
That means I have to have 1 Jade/compiled JS for each template? How might I use it? Also I think many JS files is a slow way to work? But since template functions are all named anonymous, how can I then concat or somehow work with them effectively?
Check the ClientJade project.
From their site:
clientjade is a command line tool to compile your jade templates into client side templates for use in the browser. It will automatically include everything you need to render the templates, no need to include jade.js or runtime.js.
$ clientjade test1.jade test2.jade > templates.js
And then include template.js file in your html. To render the templates, just make a call like this:
//jade.render(domNode, templateName, data);
jade.render(document.getElementById('test1'), 'test1', { name: 'Bob' });
After looking at Jadify and ClientJade, and encountering a few problems along the way ... (perhaps its just somethings I missed out), I decided to explore simply compiling the templates on server side.
I defined a Node module (used by ExpressJS) which does the compilation and returns the compiled JS source (which I served with /js/templates.js).
fs = require "fs"
jade = require "jade"
async = require "async"
# done callback will be passed (source, err)
exports.compile = (done, templatesDir) ->
js = "var Templates = {}; \n\n"
# get all files in templates directory
fs.readdir templatesDir, (err, files) ->
# keep only ".jade" files
jadeFiles = files.filter (file) ->
file.substr(-5) == ".jade"
# function to compile jade templates (appending to js source)
compileTmpl = (file, doneCompile) ->
# "test.jade" becomes "test"
key = file.substr(0, file.indexOf("."))
filePath = templatesDir + file
fs.readFile filePath, (err, src) ->
# store js function source into Templates.{key}
js += "Templates." + key + " = " + jade.compile(src, { debug: false, client: true }).toString() + "; \n\n"
doneCompile(err)
# foreach jadeFile, compile template, then write templates.js file
async.forEach jadeFiles, compileTmpl, (err) ->
done(js, err)
And I use the precompiled templates on client side by including the templates.js and use templates like:
Templates.tmpl1()
Templates.tmpl2({ something: "Hello world", ... })
More on https://coderwall.com/p/hlojkw

How to check express local variable exists when in script tag of jade?

I have a data variable sent to client-side, but it may not always be included as a variable in the express locals. If it doesn't exist, var data = !{JSON.stringify(data)}; returns var data = ; which causes a js error.
I've tried using conditionals prefixed with '-' but that doesn't seem to work.
script(type='text/javascript')
- if locals.data
var data = !{JSON.stringify(data)};
- else
var data = {};
How do I give it a default if locals.data is undefined?
Don't you hate it when you wrack your brain, then ask for help on SO, only to figure it out 5min later...
Looks like the following keeps the jade and javascript happy:
var data = !{ JSON.stringify(locals.data || '') };

Resources