JavaScript template engine for mixing actual JavaScript and text/markup.
<html>
<body>
<?js process.stdout.write('Hello, World from JsHtml!'); ?>
</body>
</html>
JsHtml script syntax is fairly straightforward.
<?js
opens a code block, ?>
closes it. A whitespace character must follow the opening tag, but is not required before the closing tag. (e.g. <?jsvar test=0;?>
is invalid, but <?js var test=0;?>
is okay).
When a code block is only going to be used to output a variable you can use <?js: ?>
syntax. Everything contained inside one of these blocks is directly passed to the output function. Spaces are not required for this open tag since :
is used to separate the <?js
tag and the code block.
There are a few different methods provided of interfacing with JsHtml scripts. Here's one way showing a quick example of the engine in use:
<html>
<head>
<title><?js:title?></title>
<head>
<body>
<ul><?js
for(var i = 0; i < 10; i++) {
?>
<li>#<?js:i?></li><?js
}
?>
</ul>
</body>
</html>
var jshtml = require('js-html');
var script = jshtml.script({
context: {
title: 'Example'
}
});
script.setScriptFile('./example.html');
console.log(script.render());
<html>
<head>
<title>Example</title>
<head>
<body>
<ul>
<li>#0</li>
<li>#1</li>
<li>#2</li>
<li>#3</li>
<li>#4</li>
<li>#5</li>
<li>#6</li>
<li>#7</li>
<li>#8</li>
<li>#9</li>
</ul>
</body>
</html>
jshtml.compile(script) - Takes input JsHtml script string and returns JavaScript string.
jshtml.render(script, [options], [callback]) - Quick method for rendering JsHtml script. This should only be used for one time uses. If a script will be called multiple times use the jshtml.script
object instead (see below).
jshtml.cache([cache], script, [options]) - This is the same as jshtml.script() below, except when you'd rather not manage the script objects yourself. Returns a jshtml.script
object.
The cache
parameter is an identifier used to store the script object. When not provided instead used the script string (so be careful when using this).
jshtml.script([script], [options]) - Create a jshtml.script
object. This will cache compiled scripts, contexts, and vm functions and it's use it highly recommended when performance over multiple renders is required.
Options and their defaults:
{
syntaxCheck: true,
format: false,
mangle: false,
optimize: false,
minify: false,
context: undefined,
filename: undefined,
isolate: true
}
Probably the only options that need explanations are context
, filename
, and isolate
.
context
expects an object and allows for passing of additional global variables that JsHtml scripts can use. This also allows for some scripts to share context objects, depending on what variables to make available to them.
filename
sets the name of the file to be used when running the JsHtml script. This should be set to an absolute path and is useful for scripts that require __dirname
or __filename
to be set.
isolate
is a bool that, when true
, will execute the JsHtml script in a separate context from the calling context, at the cost of a slight amount of execution overhead by the JavaScript engine. If speed is important and context separation isn't a worry, you can disable this.
jshtml.script().setOptions(options) - Set options (see above) for the jshtml.script
object.
jshtml.script().setScript(script) - Sets the script string to use for compilation. This calls jshtml.script().clear() before setting the new script string.
jshtml.script().setScriptFile - Quick method for synchronously loading a file as a script. Automatically sets the filename
option.
jshtml.script().clear() - Completely reset the script object. This will clear any compiled/uncompiled script, compiled functions, and context. It will only keep the options set either at creation or with jshtml.script().setOptions().
jshtml.script().compile() - Similar to jshtml.compile(), except this will be cached and adds a few more options to be used along with it (such as reformatting script output, minification, etc...).
jshtml.script().makeFunction() - This will compile the currently set script (if it hasn't been already), create a context (again, if it hasn't), and will return function([callback])
that can be called to render the compiled script.
If a callback parameter isn't supplied, the script will be ran synchronously and the return value will be the rendered script. Conversely, passing a callback with an argument for the rendered string will set the script to be ran asynchronously. JsHtml scripts that run asynchronously will need to call this.complete();
when they are finished rendering. Otherwise the callbacks will never be returned.
Here's a simple example with an async JsHtml script:
Hello<?js process.nextTick(function() {
this.complete();
}); ?>, World
jshtml.script().render([callback]) - A shortcut for calling function([callback])
returned by jshtml.script().makeFunction().
Command line use has also been provided. Personally I find it useful for testing a script before I incorporate it into a project.
$ jshtml --help
Usage: js-html [files] [options] .. or
js-html [options] -- [files]
Options
-r, --render Render the compiled output instead of stopping after
compilation.
-o, --out Write output to file instead of stdout.
--syntax Enables syntax checking.
--format By default compiled code has no formatting applied. This
switch enables formatting. Implies --syntax.
--mangle Turns on obfuscation for compiled code. Implies --format.
--optimize Enables compiled script optimization. Implies --format.
--minify Minify compiled output. Implies --mangle.
-v, --version Prints the version/author info in the output header
-h, --help Prints this help information.
Notice that I used jshtml
, but the help says js-html
. You can use both as they are both registered to the same command. This was done since the project name is registered as js-html
, but referred to as jshtml
.
The engine itself is pretty simple, practically to the point where it's almost unfair to call it an engine at all and more of an extension library. There are 4 stages to rendering a script: compilation, context initialization, runtime compilation, and lastly execution/rendering.
Input scripts are compiled directly to JavaScript code, with the text sections being passed as strings to the process.stdout.write
function. Take the following input:
This <?js:'is'?> a "simple" <?js process.stdout.write('example');
Without any post-compilation formatting, this get's compiled into:
process.stdout.write(('This ')+('is')+(' a \"simple\" ')); process.stdout.write('example');
The JavaScript sections go completely untouched by the compiler. After the compilation, syntax errors are checked by default (although this can be disabled by setting the jshtml.script()
object options).
The next step is to create the execution context. Similar to how require()
works, the JsHtml script object calls its makeFunction()
method which will initialize the context for the script to execute in.
If the isolate
option is set to true
, the created context is the only scope the script is allowed to access. This makes it so the JsHtml script being executed will not have access to anything from the calling scope unless it is passed specifically. This also means that separate JsHtml scripts do not share a scope with each other unless they are given a shared context object.
After the context creation comes VM compilation. This is where the compiled script is turned from a script string into an executable JavaScript function. This is done using the vm
functions provided by NodeJS.
Finally, if both context generation and VM compilation are successful, the script can be executed and the rendered results returned.
npm
npm install js-html
git
git clone https://github.com/bryanwayb/js-html.git && cd js-html && npm install
JsHtml has been configured for numerous tests to test compatibility with a specific NodeJS version. These test not only ensure basic functionality will be available, but also security in separation of contexts.
Note: NodeJS version 0.11.6 and lower fail tests because contexts are not properly separated in those versions. Passing the { isolate: true }
will not yield the expected results. This problem was fixed in 0.11.7 and later.
If you would like to run these tests run the below command while in the modules root directory:
npm test
Benchmarking can also be performed to view how fast the test scripts will execute under different hardware and operating systems (as well as between versions).
npm run bench