Allow to output either raw or transformed AST, fix several transformation bugs with...
[jst.git] / README.md
1 # JavaScript Template system
2
3 An NDCODE project.
4
5 ## Overview
6
7 The `jst` package exports a single function
8   `jst(text, options)`
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.
13
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>`.
19
20 ## Template syntax
21
22 ### HTML tags in templates
23
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.
26
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
31   `attrname`
32 or
33   `attrname=value`
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).
38
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:
41
42 ```
43 html(lang="en") {
44   head {}
45   body {}
46 }
47 ```
48 and this would translate to the HTML file:
49 ```htm
50 <html lang="en"><head></head><body></body></html>
51 ```
52
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.
58
59 ### Regular text in templates
60
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.
65
66 For example:
67 ```js
68 html(lang="en") {
69   body {'Hello, world'}
70 }
71 ```
72
73 This generates the text:
74 ```html
75 <html lang="en"><body>Hello, world</body></html>
76 ```
77
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.
82
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.
86
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.
91
92 ### Special tags in templates
93
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.
99
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.
104
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.
110
111 ### HTML class and id shorthand
112
113 The tag name can be followed by `#name` as shorthand for the HTML attribute
114   `id="name"`
115 or by `.name` as shorthand for the HTML attribute
116   `class="name"`
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).
120
121 ### Parser limitations
122
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.
132
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,
135   `div.my-style {...}`
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.
140
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.
146
147 ## Expression vs statement templates
148
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.
151
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.
154
155 Here is a complete example showing template substitution and HTML expressions:
156 ```js
157 let lang = 'en'
158 let name = 'John'
159 console.log(
160   html(lang=lang) {
161     body {`Hello, ${name}`}
162   }
163 )
164 ```
165
166 Running the above program will generate the output:
167 ```html
168 <html lang="en"><body>Hello, John</body></html>
169 ```
170
171 ## Template output buffer
172
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.
178
179 If there is dynamically generated text in the template, then it will be esacped
180 by the sequence `.replace("&", "&amp;").replace("<", "&lt;")`, 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.
185
186 For example, consider a realistic template which we use on our demo server:
187 ```js
188 html(lang=lang) {
189   head {
190     link(rel="stylesheet" type="text/css" href="css/styles.css") {}
191   }
192   body {
193     p {`Hello, ${name}`}
194   }
195 }
196 ```
197
198 This compiles to the following plain JavaScript code:
199 ```js
200 _out.push("<html lang=\"" + lang.toString().replace("&", "&amp;").replace("\"", "&quot;") + "\">");
201 {
202   _out.push("<head>");
203   _out.push("<link rel=\"stylesheet\" type=\"text/css\" href=\"css/styles.css\">");
204   _out.push("</head>");
205 }
206 {
207   _out.push("<body>");
208   {
209     _out.push("<p>");
210     _out.push(`Hello, ${name}`.replace("&", "&amp;").replace("<", "&lt;"));
211     _out.push("</p>");
212   }
213   _out.push("</body>");
214 }
215 _out.push("</html>");
216 ```
217
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.
221
222 ## Usage example
223
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:
227 ```js
228 let lang = 'en'
229 let name = 'John'
230 console.log(
231   html(lang=lang) {
232     body {`Hello, ${name}`}
233   }
234 )
235 ```
236 We could then create an example JavaScript program `example.js` to read,
237 compile and then execute the template, as follows:
238 ```js
239 let fs = require('fs')
240 let jst = require('@ndcode/jst')
241
242 eval(jst(fs.readFileSync('mytemplate.jst')))
243 ```
244 To run this, we would have to run the `node` interpreter at the command line:
245 ```sh
246 node example.js
247 ```
248 This produces the output described earlier:
249 ```html
250 <html lang="en"><body>Hello, John</body></html>
251 ```
252
253 ## Command line preprocessor
254
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.
258
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:
261 ```
262 jst <mytemplate.jst >mytemplate.js
263 node mytemplate.js
264 ```
265 Inspecting the JavaScript file `mytemplate.js` shows how the HTML is generated:
266 ```
267 let lang = 'en';
268 let name = 'John';
269 console.log((() => {
270   let _out = [];
271   _out.push("<html lang=\"" + lang.toString().replace("&", "&amp;").replace("\"", "&quot;") + "\">");
272   {
273     _out.push("<body>");
274     _out.push(`Hello, ${name}`.replace("&", "&amp;").replace("<", "&lt;"));
275     _out.push("</body>");
276   }
277   _out.push("</html>");
278   return _out.join('');
279 })());
280 ```
281
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`.
287
288 ## JSTizer program
289
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.
294
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.
302
303 ## GIT repository
304
305 The development version can be cloned, downloaded, or browsed with `gitweb` at:
306 https://git.ndcode.org/public/jst.git
307
308 ## License
309
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.
313
314 ## Contributions
315
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.
321
322 Contact: Nick Downing <nick@ndcode.org>