1 # JavaScript Template system
7 The `jst` package exports a single function
9 which parses the given text in a JavaScript dialect which can contain HTML-like
10 constructs, then generates equivalent plain JavaScript code which when run,
11 would generate the requested HTML. As well as the HTML generation, there can be
12 normal JavaScript logic to influence the generated page or substitute into it.
14 The system is inspired by Pug (formerly Jade) templates, but emphasizing the
15 JavaScript, for instance you do not need a `-` sign at the start of each line,
16 and the parser automatically follows the JavaScript block scope. It is also
17 inspired by JSX templates, but the embedded HTML is less verbose, there are no
18 closing tags since HTML is bracketed by `{ }` rather than by `<tag>...</tag>`.
22 ### HTML tags in templates
24 The plain JavaScript in the file is absolutely normal and is expected to use
25 CommonJS conventions, for instance, `require(...)` is supported and so forth.
27 The embedded HTML uses a syntax similar to JavaScript function definitions,
28 `function(arg1, arg2, ...) {...}`
29 except that instead of the word "function" any valid HTML (or other) tag name
30 is allowed, and instead of just argument names, HTML attributes of the form
34 are supported. In the `attrname=value` syntax, `value` is any valid JavaScript
35 expression. No commas are required between attributes, so the parser has to
36 automatically detect where one expression ends and another begins (similarly to
37 how automatic semicolon insertion works in regular JavaScript).
39 The `( )` around the attributes is not necessary when there are no attributes.
40 Thus a simple HTML file could be written as the following JavaScript template:
48 and this would translate to the HTML file:
50 <html lang="en"><head></head><body></body></html>
53 For certain particular HTML tags such as `img`, no closing tag is generated in
54 the output HTML. However, the empty `{ }` must still be given in the template.
55 This is partly for the parser's convenience, since it depends on recognizing
56 the `{ }` to distinguish between an HTML template and ordinary JavaScript code.
57 It is also more uniform: there is no need to remember which tags are special.
59 ### Regular text in templates
61 Regular text is placed in the generated HTML by simply quoting it. That is, if
62 a statement is seen consisting of only a JavaScript string or template string,
63 the string is converted to HTML by escaping it (helping to guard against any
64 HTML injection attacks), and then output as part of some enclosing template.
73 This generates the text:
75 <html lang="en"><body>Hello, world</body></html>
78 If the text is multi-line, backquoted (template) strings are used, to allow
79 embedded newlines. When the entire text is held in a variable, e.g. `myvar`, a
80 template string such as ```${myvar}``` should be used, to convert it into a
81 standalone statement consisting of only a string constant, and hence into HTML.
83 Note that ordinary single- or double-quoted string constants should be used in
84 preference to template strings where possible, since the constant strings will
85 be HTML-escaped at template compile time rather than at template run-time.
87 Note that in ordinary HTML, certain tags are more sensitive to whitespace than
88 others, according to complicated rules about inline block elements and so on.
89 This is never an issue with JavaScript templates, we can use as much indenting
90 and other whitespace as we want, and only quoted whitespace will be output.
92 ### Special tags in templates
94 The HTML `script` tag is treated specially, because it contains JavaScript,
95 which is understood directly by the template parser. The JavaScript inside the
96 script tag is minified (comments stripped, unnecessary braces and parentheses
97 removed, semicolons added and so on), and then converted to a string constant,
98 which will be copied out to the HTML file whenever the template is executed.
100 The HTML `style` tag is treated specially, because it contains CSS, which we
101 handle by switching temporarily to another parser that understands CSS. We use
102 a slightly modified version of the `clean-css` package to do this. The result
103 is then minified and converted to a string constant, similar to `script` tags.
105 Note that the `script` and `style` tags cannot contain substitutions, in the
106 way that ordinary JST code can. This is because (i) the entire contents of the
107 special tag are minified and stringified at template compile time, so the
108 content of the special tag is the same each time the page is generated, and
109 (ii) it runs elsewhere, which doesn't have access to the template variables.
111 ### HTML class and id shorthand
113 The tag name can be followed by `#name` as shorthand for the HTML attribute
115 or by `.name` as shorthand for the HTML attribute
117 and these can be repeated as needed, the `id` attribute collects all `#name`
118 separated by spaces and the `class` attribute collects all `.name` similarly.
119 These must come before any ordinary attributes (before an opening parenthesis).
121 ### Parser limitations
123 Certain tag or attribute names present difficulty since they contain `-` signs
124 or other characters invalid in JavaScript identifiers, or they may be reserved
125 words in JavaScript. The parser copes with this quite well (most syntax errors
126 can already be re-parsed as tag or attribute names), but in difficult cases it
127 could be necessary to quote the tag and/or attribute names. For example,
128 `div.class-1.class-2 {}`
129 doesn't compile because "1." is a floating point number, for now we write it
130 `div.'class-1'.class-2 {}`
131 although we expect that this restriction can be lifted in a future version.
133 Also, the `style` parser for CSS code can be confused by tag, `id` or `class`
134 names that end with `-style`. So we should be careful of code like this,
136 since the `div.my-` prefix won't be seen until parsing is complete, hence the
137 parser will switch to CSS parsing inside the braces. Therefore, quote it like
138 `div.'my-style' {...}`
139 although again, we expect that this restriction can be lifted in the future.
141 Another slight limitation of the current parser is that it is more permissive
142 than normal in parsing regular JavaScript code, for instance commas are not
143 required between function arguments, because an HTML template is basically
144 parsed as a function call until the opening `{` is seen to identify it as a
145 template. This can also likely be improved in a future version of the system.
147 ## Expression vs statement templates
149 HTML templates occuring at expression level will be completely rendered to a
150 string, and the resulting string returned as the value of the expression.
152 HTML templates occurring at statement level are treated somewhat like quoted
153 strings, in that the generated HTML will become part of an enclosing template.
155 Here is a complete example showing template substitution and HTML expressions:
161 body {`Hello, ${name}`}
166 Running the above program will generate the output:
168 <html lang="en"><body>Hello, John</body></html>
171 ## Template output buffer
173 It is also possible to use statement-level HTML templates or strings to build
174 up an output buffer, which can be used for whatever purpose. The output buffer
175 must be called `_out` and must be a JavaScript array of string fragments. As
176 each output fragment (such as an opening or closing tag or an embedded string)
177 is generated, it will be sent to the output buffer by an `_out.push(...)` call.
179 If there is dynamically generated text in the template, then it will be esacped
180 by the sequence `.replace("&", "&").replace("<", "<")`, this is chosen
181 so that no external dependency such as the `html-entities` package is needed,
182 and so that unnecessary escaping of characters such as `'` or `>` is not done.
183 Attributes are done similarly, except that `"` is replaced instead of `<`. We
184 assume a `UTF-8` environment, so there is really little need for HTML entities.
186 For example, consider a realistic template which we use on our demo server:
190 link(rel="stylesheet" type="text/css" href="css/styles.css") {}
198 This compiles to the following plain JavaScript code:
200 _out.push("<html lang=\"" + lang.toString().replace("&", "&").replace("\"", """) + "\">");
203 _out.push("<link rel=\"stylesheet\" type=\"text/css\" href=\"css/styles.css\">");
204 _out.push("</head>");
210 _out.push(`Hello, ${name}`.replace("&", "&").replace("<", "<"));
213 _out.push("</body>");
215 _out.push("</html>");
218 If invoked as an expression, the same code will be generated, but wrapped in an
219 Immediately-Invoked Function Expression (IIFE) that creates the `_out` buffer,
220 executes the above, then returns the buffer concatenated into a single string.
224 Suppose we want to try out the JST template processor. Returning to the example
225 from the section "Expression vs Statement templates", we could create a file
226 called `mytemplate.jst` containing the example JST code to print a simple page:
232 body {`Hello, ${name}`}
236 We could then create an example JavaScript program `example.js` to read,
237 compile and then execute the template, as follows:
239 let fs = require('fs')
240 let jst = require('@ndcode/jst')
242 eval(jst(fs.readFileSync('mytemplate.jst')))
244 To run this, we would have to run the `node` interpreter at the command line:
248 This produces the output described earlier:
250 <html lang="en"><body>Hello, John</body></html>
253 ## Command line preprocessor
255 Using the programmatic interface is overkill in many situations, e.g. if we
256 just want to see the generated JavaScript code, or if we want to compile all
257 pages of our website to JavaScript ahead of time by a `Gruntfile` or similar.
259 Hence, we provide the command line interface `jst` as a convenient way to
260 convert JST to JavaScript. Using the command-line, the above example becomes:
262 jst <mytemplate.jst >mytemplate.js
265 Inspecting the JavaScript file `mytemplate.js` shows how the HTML is generated:
271 _out.push("<html lang=\"" + lang.toString().replace("&", "&").replace("\"", """) + "\">");
274 _out.push(`Hello, ${name}`.replace("&", "&").replace("<", "<"));
275 _out.push("</body>");
277 _out.push("</html>");
278 return _out.join('');
282 Various command line options are available for setting indentation and similar.
283 There is also a `--wrap` option which adds some prologue and epilogue code,
284 which is part of a larger system we have designed, for allowing templates to
285 embed each other and so on. See the `jst_server` and `jst_cache` modules for
286 more information. If the extra complexity isn't needed, simply omit `--wrap`.
290 In development, we often want to refer to example code from the Web, e.g. if we
291 want to incorporate the Bootstrap front-end framework or if we want to create a
292 web-form or some other common web-development task. The example code is usually
293 written in HTML. We can drop this straight into a JST project by JSTizing it.
295 See the `jstize` module for more information. Essentially the workflow is (i)
296 get some HTML in a file `*.html`, it does not need to be a complete page, but
297 must be a syntactically valid and complete HTML fragment, (ii) use the JSTizer
298 to convert this into an equivalent `*.jst` file, (iii) use the JST system, by
299 either the programmatic API or the command-line tool, to convert this into an
300 equivalent `*.js` file, (iv) run the `*.js` file to recreate the original HTML.
301 Of course, the `*.jst` template can be modified to add any needed extra logic.
305 The development version can be cloned, downloaded, or browsed with `gitweb` at:
306 https://git.ndcode.org/public/jst.git
310 All of our NPM packages are MIT licensed, please see LICENSE in the repository.
311 We also gratefully acknowledge the `acorn` team for the original MIT license of
312 their JavaScript parser, which we've heavily modified to create the JST parser.
316 The JST system is under active development (and is part of a larger project
317 that is also under development) and thus the API is tentative. Please go ahead
318 and incorporate the system into your project, or try out our example webserver
319 built on the system, subject to the caution that the API could change. Please
320 send us your experience and feedback, and let us know of improvements you make.
322 Contact: Nick Downing <nick@ndcode.org>