JavaScript Runtime

PropertyValue
Tag<javascript>
Output methodconsole.log(), process.stdout.write(), print(), println()
Default binary/usr/bin/node
Shared scopetrue (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.

FunctionTrailing NewlineNotes
print("hello")NoInjected by Salata
println("hello")YesInjected by Salata
console.log("hello")YesStandard Node.js
process.stdout.write(s)NoStandard 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
FieldTypeDefaultDescription
enabledbooltrueEnable or disable the JavaScript runtime
pathstring/usr/bin/nodeAbsolute path to the Node.js binary
shared_scopebooltrueAll blocks share one process per page
display_errorsbool(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. Use println() or console.log() when you want newlines.
  • The working directory during execution is the directory containing the .slt file being processed.