JavaScript Runtime
| Property | Value |
|---|---|
| Tag | <javascript> |
| Output method | console.log(), process.stdout.write(), print(), println() |
| Default binary | /usr/bin/node |
| Shared scope | true (default) |
Overview
The JavaScript runtime executes code using Node.js. Whatever the code writes to stdout is captured and placed at the tag's position in the output document. Salata injects two convenience helpers, print() and println(), that complement the standard Node.js output methods.
Injected Helpers
Before your code runs, Salata prepends two helper functions:
const print = (...args) => process.stdout.write(args.join(' '));
const println = (...args) => process.stdout.write(args.join(' ') + '\n');
These are additive. Nothing is overridden. console.log() and process.stdout.write() continue to work exactly as they always do.
| Function | Trailing Newline | Notes |
|---|---|---|
print("hello") | No | Injected by Salata |
println("hello") | Yes | Injected by Salata |
console.log("hello") | Yes | Standard Node.js |
process.stdout.write(s) | No | Standard Node.js, accepts strings/buffers |
Use print() and println() when you want concise output calls. Use console.log() and process.stdout.write() if you prefer sticking to standard Node.js idioms. They all work.
Basic Usage
<javascript>
println("<h1>Welcome</h1>");
println("<p>Generated by JavaScript at " + new Date().toISOString() + "</p>");
</javascript>
Shared Scope
With shared scope enabled (the default), all <javascript> blocks on the same page share a single Node.js process. Variables, functions, and classes persist across blocks:
<javascript>
const formatter = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
});
function formatRow(item) {
return `<tr><td>${item.name}</td><td>${formatter.format(item.price)}</td></tr>`;
}
const items = [
{ name: "Keyboard", price: 79.99 },
{ name: "Mouse", price: 34.50 },
{ name: "Monitor", price: 449.00 },
];
</javascript>
<table>
<tr><th>Item</th><th>Price</th></tr>
<javascript>
items.forEach(item => println(formatRow(item)));
const total = items.reduce((sum, i) => sum + i.price, 0);
println(`<tr><td><strong>Total</strong></td><td><strong>${formatter.format(total)}</strong></td></tr>`);
</javascript>
</table>
JSON Handling
JavaScript's native JSON support makes it a natural fit for working with structured data:
<javascript>
const data = {
api: "v2",
endpoints: [
{ path: "/users", methods: ["GET", "POST"] },
{ path: "/users/:id", methods: ["GET", "PUT", "DELETE"] },
{ path: "/health", methods: ["GET"] },
],
};
println("<h2>API Reference</h2>");
for (const ep of data.endpoints) {
println(`<div class="endpoint">`);
println(` <code>${ep.path}</code>`);
println(` <span>${ep.methods.join(", ")}</span>`);
println(`</div>`);
}
</javascript>
Template Literals
JavaScript template literals provide a readable way to build multi-line output:
<javascript>
const users = [
{ name: "Alice", role: "admin", active: true },
{ name: "Bob", role: "editor", active: false },
{ name: "Carol", role: "viewer", active: true },
];
for (const user of users) {
const badge = user.active ? "active" : "inactive";
print(`<div class="user-card ${badge}">
<h3>${user.name}</h3>
<p>Role: ${user.role}</p>
<span class="badge">${badge}</span>
</div>
`);
}
</javascript>
Cross-Runtime Data Bridge
JavaScript integrates with other runtimes through #set and #get:
<python>
analytics = {
"page_views": 15230,
"unique_visitors": 4891,
"bounce_rate": 0.342,
"top_pages": ["/", "/about", "/pricing"]
}
#set("analytics", analytics)
</python>
<javascript>
const data = #get("analytics");
println(`<div class="stats">`);
println(` <p>Page views: ${data.page_views.toLocaleString()}</p>`);
println(` <p>Unique visitors: ${data.unique_visitors.toLocaleString()}</p>`);
println(` <p>Bounce rate: ${(data.bounce_rate * 100).toFixed(1)}%</p>`);
println(` <p>Top pages: ${data.top_pages.join(", ")}</p>`);
println(`</div>`);
</javascript>
Configuration
[runtimes.javascript]
enabled = true
path = "/usr/bin/node"
shared_scope = true
| Field | Type | Default | Description |
|---|---|---|---|
enabled | bool | true | Enable or disable the JavaScript runtime |
path | string | /usr/bin/node | Absolute path to the Node.js binary |
shared_scope | bool | true | All blocks share one process per page |
display_errors | bool | (global fallback) | Override the global display_errors setting |
Isolated Scope
To run a block in its own Node.js process:
<javascript scope="isolated">
// This block has its own process.
const x = 42;
println(x);
</javascript>
Or set shared_scope = false in the config for all JavaScript blocks.
Important: <script> vs <javascript>
Do not confuse <javascript> with <script>. They are completely different:
<javascript>is a Salata runtime tag. The code runs server-side in Node.js, and the stdout output replaces the tag.<script>is a standard HTML tag. Salata passes it through untouched. The code runs client-side in the browser.
<!-- Server-side: runs in Node.js, output replaces this tag -->
<javascript>
println('<div id="data" data-count="42"></div>');
</javascript>
<!-- Client-side: runs in the browser, passed through as-is -->
<script>
const count = document.getElementById("data").dataset.count;
alert("Count is " + count);
</script>
Tips
- Modern ES features (destructuring, async/await, optional chaining) are available depending on your Node.js version.
- Use
print()for inline output without trailing newlines. Useprintln()orconsole.log()when you want newlines. - The working directory during execution is the directory containing the
.sltfile being processed.