JavaScript
The @onlabsorg/texl package compiles the Texl engine to WebAssembly, with a
JavaScript API for web pages and web workers. It renders templates from
strings, loads documents over HTTP, and lets templates call your JavaScript
functions.
A package for Node.js is coming soon.
Installing the package
Install the package from npm, and import it in code that a bundler, such as Vite or webpack, builds:
npm install @onlabsorg/texl
import init, { TexlEngine } from "@onlabsorg/texl";
The package contains the module texl_wasm.js, the WebAssembly binary
texl_wasm_bg.wasm, and the TypeScript declarations texl_wasm.d.ts.
Without a bundler, import the module from a CDN that serves npm packages, with the version you want:
import init, { TexlEngine } from "https://cdn.jsdelivr.net/npm/@onlabsorg/texl@0.3.0/texl_wasm.js";
You can also build the package from a clone of the repository with
wasm-pack, and copy the
texl-wasm/pkg folder that it creates to your web site:
wasm-pack build texl-wasm --target web
Quick start
<script type="module">
import init, { TexlEngine } from "https://cdn.jsdelivr.net/npm/@onlabsorg/texl@0.3.0/texl_wasm.js";
await init(); // load the WebAssembly module
const engine = new TexlEngine({ user: { name: "Ada" } });
const text = await engine.render("Hello <% user.name %>!");
console.log(text); // Hello Ada!
</script>
The page must be served over HTTP: browsers do not load WebAssembly modules,
or let them fetch documents, from file:// pages.
Loading documents
engine.load fetches a document by URL, renders it, and returns its
rendering, source and data:
const doc = await engine.load("templates/report.texl");
if (doc) {
document.querySelector("#report").textContent = doc.rendering;
}
A relative URL is resolved against the engine's base URL, which is the URL of the page by default. Inside a document, imports are resolved against the URL of the document:
In https://example.com/docs/report.texl, texl.import(...) of | fetches |
|---|---|
"header.texl" | https://example.com/docs/header.texl |
"../lib/format.texl" | https://example.com/lib/format.texl |
"/lib/format.texl" | https://example.com/lib/format.texl |
"https://cdn.example.org/a.texl" | https://cdn.example.org/a.texl |
Templates rendered with engine.render, or with a context created without a
URL, have no URL of their own: their imports are resolved against the root of
the base URL, so "lib/format.texl" and "/lib/format.texl" both fetch
https://example.com/lib/format.texl. A context created with a URL resolves
them against that URL, like a document (see Contexts).
Documents are fetched with fetch, so the usual browser rules apply: a
document on another origin must allow it with CORS headers. A response with an
error status counts as a failed load: load gives null, and texl.import
gives Nothing.
Each engine loads and renders each document once, and reuses the result for later loads and imports. Create a new engine to see changes to the documents.
Globals and context values
The objects you pass to the engine and to contexts become Swan values:
| JavaScript | Swan |
|---|---|
null, undefined | Nothing |
| boolean | Bool |
| number | Numb |
| string | Text |
| array | List |
Promise, or any object with a then method | Promise; a rejection resolves to Nothing |
| function | Func (see below) |
| other object | Namespace of its own enumerable properties |
Values that come back to JavaScript, from context.get, from document data
and as function arguments, are converted the other way:
| Swan | JavaScript |
|---|---|
Nothing | null |
Bool, Numb, Text | boolean, number, string |
List, Tuple | array |
Namespace | plain object with its members |
Promise | Promise |
Func, Undefined | undefined |
Functions
A JavaScript function becomes a Swan function of one argument. A Swan call with several arguments passes them as one array, so destructure it:
const engine = new TexlEngine({
shout: (s) => s.toUpperCase() + "!",
add: ([a, b]) => a + b,
});
await engine.render('<% shout "hi" %> <% add (2, 3) %>'); // "HI! 5"
The return value is converted to a Swan value. If the function throws, the
result is Nothing. An async function, or a function that returns a
promise, gives a Swan promise, and the tag waits for it:
const engine = new TexlEngine({
weather: async (city) => (await fetch(`/api/weather/${city}`)).text(),
});
await engine.render('Today in Rome: <% weather "rome" %>');
Contexts
engine.render uses a fresh scope each time, so the names a template binds
are lost when it ends. A context keeps them: it is a scope that you render
several templates in, and that you can read and write from JavaScript.
const context = engine.createContext({ price: 12.5 });
await context.render("<% total = price * 4 %>");
context.get("total"); // 50
await context.render("Total: <% total %>"); // "Total: 50"
A context can also stand for a document: pass its URL as the second argument, and the imports of the templates rendered in the context resolve against it. Use it to render a document whose source you already have, such as the text of an editor, without fetching it:
const context = engine.createContext(null, "docs/report.texl");
const rendering = await context.render(source); // imports resolve in docs/
API reference
init
default function init(
module_or_path?: { module_or_path: InitInput | Promise<InitInput> }
): Promise<InitOutput>
Loads and instantiates the WebAssembly module. Call it once, and wait for it, before using any other export.
By default, it fetches texl_wasm_bg.wasm from the folder of
texl_wasm.js. Pass { module_or_path: url } to load it from somewhere else.
The module also exports initSync({ module }), which instantiates an already
compiled module synchronously.
import init from "@onlabsorg/texl";
await init();
TexlEngine
An engine renders templates and loads documents. Its globals, documents and
engine-wide names (bound with bind) are
shared by everything it renders.
new TexlEngine(globals, baseUrl)
constructor(globals?: object | null, baseUrl?: string | null)
Creates an engine.
| Argument | Description |
|---|---|
globals | An object whose properties become globals of every template and document. Optional. |
baseUrl | The URL that relative URLs resolve against. Optional; defaults to location.href, the URL of the page or worker. |
Throws a string with an error message if baseUrl is not an absolute URL.
const engine = new TexlEngine(
{ site: { name: "My site" } },
"https://example.com/templates/",
);
engine.baseUrl
readonly baseUrl: string | undefined
Returns the base URL of the engine, normalized. It is undefined only when no
base URL was given and the global scope has no location.
new TexlEngine().baseUrl; // the URL of the page
engine.set(name, value)
set(name: string, value: any): void
Binds a global, visible to every template and document of the engine, including contexts created earlier. A global with the same name is replaced.
| Argument | Description |
|---|---|
name | The name of the global. |
value | Its value, converted to a Swan value. |
engine.set("today", new Date().toISOString().slice(0, 10));
engine.load(uri)
load(uri: string): Promise<TexlDocument | null>
Loads and renders the document at uri.
| Argument | Description |
|---|---|
uri | The URL of the document, resolved against baseUrl. |
Returns a promise of the document, or of null if it cannot
be loaded. The promise rejects, with a string message, only if uri cannot be
resolved to a URL. Each document is loaded once per engine; later calls with
the same URL return the same result.
const doc = await engine.load("templates/report.texl");
console.log(doc?.rendering ?? "not found");
engine.render(template)
render(template: string): Promise<string>
Renders a template in a fresh scope, and returns a promise of the rendered
text. The names the template binds with = or : are discarded afterwards.
| Argument | Description |
|---|---|
template | The template source. |
await engine.render("2 + 2 = <% 2 + 2 %>"); // "2 + 2 = 4"
engine.createContext(vars, uri)
createContext(vars?: object | null, uri?: string | null): TexlContext
Creates a context: a scope that keeps its names across renders.
| Argument | Description |
|---|---|
vars | An object whose properties are bound in the context. Optional. |
uri | The URL of the document that the context stands for, resolved against baseUrl. Optional. |
With a uri, imports in the context resolve against it, as in a document
loaded with load; nothing is fetched from it. Without one, they resolve
against the root of baseUrl, as in render. Throws, with a string message,
if uri cannot be resolved to a URL.
const context = engine.createContext({ user: "Ada" });
const report = engine.createContext(null, "templates/report.texl");
engine.free()
free(): void
Releases the memory of the engine. The engine cannot be used afterwards. You
only need it for engines that you create and discard many times; the engine
also supports using declarations through Symbol.dispose.
TexlContext
A scope of an engine, created with engine.createContext. Templates rendered
in a context share its names, and so do get and set. The context also sees
the globals of its engine.
context.get(name)
get(name: string): any
Returns the value of a name, converted to a
JavaScript value. The name is looked up in the context, then in the engine
globals and the built-ins. A name that is not bound gives null.
| Argument | Description |
|---|---|
name | The name to look up. |
await context.render("<% x = (1, 2) %>");
context.get("x"); // [1, 2]
context.set(name, value)
set(name: string, value: any): void
Binds a name in the context, replacing any previous value.
| Argument | Description |
|---|---|
name | The name to bind. |
value | Its value, converted to a Swan value. |
context.set("items", ["a", "b"]);
context.names()
names(): string[]
Returns the names bound in the context, sorted. Engine globals and built-ins
are not included. The list always includes texl, the namespace that holds
texl.import.
engine.createContext({ b: 1, a: 2 }).names(); // ["a", "b", "texl"]
context.render(template)
render(template: string): Promise<string>
Renders a template in the context, and returns a promise of the rendered text. The names the template binds stay in the context.
| Argument | Description |
|---|---|
template | The template source. |
await context.render("<% count = 1 %>");
await context.render("Count: <% count %>"); // "Count: 1"
context.free()
free(): void
Releases the memory of the context, like engine.free.
TexlDocument
interface TexlDocument {
uri: string;
source: string;
data: Record<string, any>;
rendering: string;
}
A loaded document, as returned by engine.load.
| Property | Description |
|---|---|
uri | The URL of the document. |
source | The template source. |
data | The names the document's tags bound, converted to JavaScript values. Functions are undefined. |
rendering | The rendered text. |
const doc = await engine.load("settings.texl");
console.log(doc.data.title, doc.rendering);