Use ...arguments syntax instaed of .apply() and .concat()
[jst_cache.git] / README.md
1 # Build Cache wrapper for JavaScript Template system
2
3 An NDCODE project.
4
5 ## Overview
6
7 The `jst_cache` package exports a single constructor
8   `JSTCache(root, args, diag)`
9 which must be called with the `new` operator. The resulting cache object stores
10 compiled JavaScript templates. Each template is, in general, a function that
11 can be called with certain arguments, and which will generate the HTML text for
12 a particular page or part of a page. The arguments can vary with each template.
13
14 The `JSTCache` object takes care of loading template source code, compiling it
15 using the `jst` package, and then executing the resulting JavaScript code. This
16 initial execution is intended to allow the template to load any resources it
17 needs, and generally set things up so that it is ready to generate HTML. The
18 initial execution returns the object which is stored in the cache. As mentioned
19 this is normally a function, but could be something else, e.g. any JSON object.
20
21 See the `build_cache`, `disk_build` and `jst` packages for more information.
22 The `JSTCache` object is essentially a wrapper object which routes the request 
23 between these packages, to ensure that the compiled template is retrieved from
24 either RAM or disk if available, or is compiled and stored to RAM and to disk.
25
26 As well as wrapping the `build_cache`, `disk_build` and `jst` functionality
27 into a convenient "point and shoot" interface for template loading, `jst_cache`
28 also provides a dependency resolution service for the compiled template. If the
29 compiled template wants to load further templates during initialization or run-
30 time, it calls back into `jst_cache`, which recursively loads the dependency.
31
32 ## Calling API
33
34 Suppose one has a `JSTCache` instance named `jc`. It behaves somewhat like an
35 ES6 `Map` object, except that it only has the `jc.get()` function, because new
36 objects are added to the cache by attempting to `get` them.
37
38 The interface for the `JSTCache`-provided instance function `jc.get()` is:
39
40 `await jc.get(key)` — retrieves the object stored under `key`, where
41 `key` is the on-disk pathname to a JST file. What `jc.get()` returns depends on
42 how the template is written, usually it is a function that generates HTML code.
43 This function (or other result) is cached for the next `jc.get()` of same key.
44
45 Before returning the cached copy, the existence and modification time of the
46 JST file on disk is checked to make sure that the cache is up-to-date.
47 Otherwise, if the file doesn't exist an `ENOENT` exception is thrown, or if the
48 file exists it is re-loaded and re-compiled and re-cached for next time. The
49 compilation is via disk and is skipped if an up-to-date disk result is present.
50
51 ## Template examples
52
53 See the `jst` package for more information about what can be in a template.
54 Essentially, the templates are written in a dialect of JavaScript which allows
55 HTML constructs to be specified directly, returning a string containing HTML.
56
57 HTML templates occuring at expression level will be completely rendered to a
58 string, and the resulting string returned as the value of the expression.
59
60 HTML templates or strings (either single or double quoted strings, or template
61 strings) occurring at statement level, become part of an enclosing template.
62
63 For example, the JavaScript code `let text = p {'Hello, world'}` sets `text`
64 to the value `<p>Hello, world</p>`. This consists of an HTML template `p { }`
65 occurring at expression level, then the string `'Hello, world'` at statement
66 level inside the `p { }` template (any valid JavaScript statement is allowed).
67
68 Here is a complete example showing template substitution and HTML expressions:
69 ```
70 let lang = 'en'
71 let name = 'John'
72 console.log(
73   html(lang=lang) {
74     body {`Hello, ${name}`}
75   }
76 }
77 ```
78
79 Running the above program will generate the output:
80 ```
81 <html lang="en"><body>Hello, John</body></html>
82 ```
83
84 ## Template output buffer
85
86 It is also possible to use statement-level HTML templates or strings to build
87 up an output buffer, which can be used for whatever purpose. The output buffer
88 must be called `_out` and must be a JavaScript array of string fragments. As
89 each output fragment (such as an opening or closing tag or an embedded string)
90 is generated, it will be sent to the output buffer by an `_out.push(...)` call.
91
92 If there is dynamically generated text in the template, then it will be esacped
93 by the sequence `.replace("&", "&amp;").replace("<", "&lt;")`, this is chosen
94 so that no external dependency such as the `html-entities` package is needed,
95 and so that unnecessary escaping of characters such as `'` or `>` is not done.
96 Attributes are done similarly, except that `"` is replaced instead of `<`. We
97 assume a `UTF-8` environment, so there is really little need for HTML entities.
98
99 For example, consider a realistic template which we use on our demo server:
100 ```
101 html(lang=lang) {
102   head {
103     link(rel="stylesheet" type="text/css" href="css/styles.css") {}
104   }
105   body {
106     p {`Hello, ${name}`}
107   }
108 }
109 ```
110
111 This compiles to the following plain JavaScript code:
112 ```
113 _out.push("<html lang=\"" + lang.toString().replace("&", "&amp;").replace("\"", "&quot;") + "\">");
114 {
115   _out.push("<head>");
116   _out.push("<link rel=\"stylesheet\" type=\"text/css\" href=\"css/styles.css\">");
117   _out.push("</head>");
118 }
119 {
120   _out.push("<body>");
121   {
122     _out.push("<p>");
123     _out.push(`Hello, ${name}`.replace("&", "&amp;").replace("<", "&lt;"));
124     _out.push("</p>");
125   }
126   _out.push("</body>");
127 }
128 _out.push("</html>");
129 ```
130
131 If invoked as an expression, the same code will be generated, but wrapped in an
132 Immediately-Invoked Function Expression (IIFE) that creates the `_out` buffer,
133 executes the above, then returns the buffer concatenated into a single string.
134
135 ## Template dependencies
136
137 Templates may depend on other templates. Templates are imported imported using
138 the syntax `_require(...)`, similarly to the syntax `require(...)` in CommonJS.
139
140 We use `_require()` for templates instead of the normal `require()` because:
141
142 (1) We do not want to interfere with other symbols in the namespace, you can
143 also use `require()` in your templates to import ordinary CommonJS modules;
144
145 (2) Absolute paths have a different meaning in `_require()` versus `require()`,
146 for `_require()` they will be taken relative to the webserver's document root;
147
148 (3) Using `_require()` says that the file is in `*.jst` format, and hence it
149 should be parsed and any HTML constructs converted into regular JavaScript; and
150
151 (4) The template importing is an asynchronous operation, so you normally use
152 `await _require(...)`, whereas `require()` is synchronous. Because templates
153 which have been edited are recompiled automatically by the running webserver,
154 we don't want to stop the world, especially as templates are slow to compile.
155
156 A slight difference between CommonJS exports and JavaScript template exports,
157 is that where CommonJS modules set the dedicated symbol `module.exports` to
158 whatever is exported, JavaScript templates instead `return` what is exported.
159
160 ## Template paths and variables
161
162 Similarly to CommonJS, the variable `_require` is injected by `jst_cache` and
163 is available to all templates. Other variables injected by `jst_cache` are
164 `_pathname` which is the path to the currently executing template, and `_root`
165 which is the root directory for absolute pathname references in `_require()`.
166
167 Thus, paths sent to `_require()` are resolved relative to either `_root` if
168 absolute (beginning with `/`) or the directory part of `_pathname` if relative
169 (not beginning with `/`). Each template gets its own `_require()` function,
170 which knows the `_pathname` and `_root` values of the calling template.
171
172 If further variables should be made available to the template, they can be
173 included as a dictionary in the `args` parameter to `JSTCache()`. For example,
174 ```js
175 let jst_cache = new JSTCache(
176   '/home/myname/www',
177   {myvar: 'some value'}
178 )
179 let page = jst_cache.get('/home/myname/www/blog/page.jst')
180 ```
181 parses and executes the template `/home/myname/www/blog/page.jst`, with the
182 variables `_pathname` set to `'/home/myname/www/blog/page.jst'`, `_root` set to
183 `'/home/myname/www'`, and `myvar` set to `'some value'`. Suppose `page.jst` is:
184 ```js
185 let footer = _require('/site/footer.jst')
186 ```
187 or
188 ```js
189 let footer = _require('../site/footer.jst')
190 ```
191 then the template `/home/myname/www/site/footer.jst` is parsed and executed,
192 with `_pathname` set to `'/home/myname/www/site/footer.jst'`, and the other
193 injected variables `_root` and `myvar` set from the original `JSTCache()` call.
194
195 ## Multi-part template examples
196
197 ### Templating with HTML text strings
198
199 Here is an example of a template which takes the body of a page (as text which
200 may contain HTML tags), and wraps it in the standard `html` and `body` tags to
201 create a complete page. The idea is to use this to generate every page, so that
202 additional commands, such as viewport commands or script tags, can be set here.
203
204 Save this as `page.jst`, since it's the template for all pages in the system:
205 ```
206 return body_text => html {
207   head {
208     link(rel="stylesheet" type="text/css" href="css/styles.css") {}
209   }
210   body {
211     _out(body_text)
212   }
213 }
214 ```
215 Save this as `index.html.jst`, as it's the template to generate `index.html`:
216 ```
217 let page = await _require('/page.jst')
218
219 return page(p {'Hello, world'})
220 ```
221 Note that here we have somewhat hacked into the internals of the templating
222 system by directly appending the body text onto the output buffer `_out`. It is
223 intentional that you can access the internals for efficiency reasons, and that
224 you can, for example, avoid HTML escaping of particular text where appropriate.
225
226 ### Templating with callbacks
227
228 Another way, which is more sophisticated, is to use a callback function to
229 generate the body. If doing this, we pass the output buffer `_out` into the
230 callback function, so that the entire HTML of the page can be generated in an
231 unbroken stream, without having to concatenate the partial page repeatedly.
232
233 The page wrapper template `page.jst` which takes a body callback and calls it:
234 ```
235 return body_callback => html {
236   head {
237     link(rel="stylesheet" type="text/css" href="css/styles.css") {}
238   }
239   body {
240     body_callback(_out)
241   }
242 }
243 ```
244
245 Then the specific page template `index.html.jst` which provides the callback:
246 ```
247 let page = await _require('/page.jst')
248
249 return html(
250   _out => {
251     p {'Hello, world'}
252   }
253 )
254 ```
255
256 Note that in the above example, the `{ }` around the callback function body is
257 essential, unlike in regular JavaScript, so that `p {...}` occurs in statement
258 rather than expression context. As an expression it would return a string which
259 would be ignored, since the page template is expecting output to be in `_out`.
260
261 ## File management
262
263 The `JSTCache` object offers a simple "point and shoot" interface which makes
264 it very easy to manage on-disk templates. You call `await jc.get()` stating a
265 pathname to a template, normally a `*.jst` file, and the template is parsed,
266 compiled to JavaScript, and evaluated, with the result cached for future reuse.
267
268 What the `JSTCache.get()` function returns depends on the template source code.
269 Template exports are similar but slightly different to CommonJS module exports,
270 as noted in examples above. Usually, `JSTCache.get()` returns a JavaScript
271 function, which you call to generate HTML each time the page is to be served.
272
273 The HTML-generating function is re-useable in this way for efficiency reasons,
274 since after the initial compilation, the cached version can be reused each time
275 the page is served, but called with different arguments, resulting in different
276 substitutions made on the page. For example, consider the following template:
277 ```
278 return (lang, name) => html(lang=lang) {
279   body {
280     p {`Hello, ${name}`}
281   }
282 }
283 ```
284 Suppose the above is saved as `/hello/index.html.jst` under your document root
285 `/var/www/html`. When you instantiate the `JSTCache` object giving the document
286 root, and then call the `get(pathname)` instance function, you receive a
287 function of two arguments `lang` and `name`, which returns the HTML string.
288
289 Note that `_require()` paths beginning with `/` are taken relative to the
290 document root passed into the `JSTCache()` constructor call, whereas paths
291 beginning with anything else are taken relative to the `dirname` extracted from
292 the current value of `_pathname` as seen by the template. Each template has its
293 own `_pathname` and thus its `_require()` calls are relative to its own source.
294
295 ## Memory vs disk caching of templates
296
297 Templates compiled using `JSTCache.get()` are cached in memory, as long as the
298 same `node` interpreter is running, so that they can be retrieved using either
299 `JSTCache.get()`, or equivalently `_require()` inside a template, and they will
300 not be re-executed. In the above example, if you call `JSTCache.get()` twice,
301 you get the same object twice (it is a 2-argument function returning `String`).
302
303 As well as this, the compiled templates are also cached on disk, which requires
304 the document root to be writeable. For instance if you compile `index.html.jst`
305 in a `hello/` subdirectory of the document root, you get a hidden file with the
306 same name prefixed by `.` in the same directory, here `hello/.index.html.jst`.
307
308 The main reason for the disk caching is really to fool the node.js `require()`
309 system into using correct relative paths, if the template uses `require()` as
310 well as `_require()`. However, it is also handy that the templates only need to
311 be re-evaluated and not recompiled if the webserver is stopped and restarted.
312
313 Before using either a disk-cached or a memory-cached template, the modification
314 times are checked and the template is recompiled if it is stale. Thus, you can
315 edit your website while it is live, and each page will be recompiled as needed
316 just before serving. Templates deleted on disk are not returned from the cache.
317
318 Note: If the document root is not writeable, a simple expedient is to make sure
319 that all `.*.jst` files are up to date, so no write is attempted. You could do
320 this by firing up a development instance of the server in a writeable directory
321 and then indexing the whole site, for example by a recursive `wget` invocation.
322
323 ## To be implemented
324
325 It is intended that we will shortly add a timer function (or possibly just a
326 function that the user should call periodically) to flush built templates from
327 the cache after a stale time, on the assumption that the template might not be
328 accessible or wanted anymore. For example, if the templates are HTML pages, the
329 link structure of the site may have changed to make some pages inaccessible.
330
331 ## GIT repository
332
333 The development version can be cloned, downloaded, or browsed with `gitweb` at:
334 https://git.ndcode.org/public/jst_cache.git
335
336 ## License
337
338 All of our NPM packages are MIT licensed, please see LICENSE in the repository.
339
340 ## Contributions
341
342 The JST system is under active development (and is part of a larger project
343 that is also under development) and thus the API is tentative. Please go ahead
344 and incorporate the system into your project, or try out our example webserver
345 built on the system, subject to the caution that the API could change. Please
346 send us your experience and feedback, and let us know of improvements you make.
347
348 Contact: Nick Downing <nick@ndcode.org>