18 · Módulos
JavaScript e TypeScript com Node
Funções tipadas, módulos Node e callbacks assíncronos com extern.
Nesta página
Importe javascript ou typescript do plugin node. A assinatura usa tipos
Zolo; o corpo usa a linguagem escolhida. Os dois providers compartilham Node
dentro do processo e aceitam aliases como qualquer outro import.
Declare uma função pequena, agrupe funções TypeScript, capture um valor Zolo
em um template JavaScript e importe node:crypto. Regex e templates aninhados
mantêm seu significado nativo.
// JavaScript and TypeScript use the same embedded Node runtime.
use plugin node::{javascript as js, typescript as ts}
extern js fn add(a: int, b: int) -> int {
return a + b;
}
extern ts {
fn normalize(text: str) -> str {
return text.trim().toLowerCase();
}
fn root(value: float) -> float {
return Math.sqrt(value);
}
}
let original = " Zolo "
let greeting: str = extern js {
return `Hi ${`${original.trim()}`}`;
}
let lexical: bool = extern js {
// Braces and slashes in native literals remain inside this body.
return /[{}\/]/.test("}") && `literal } ${original.trim()}`.endsWith("Zolo");
}
extern ts fn native_import() -> str {
import { createHash } from "node:crypto";
return createHash("sha256").update("Zolo").digest("hex").slice(0, 8);
}
print(add(20, 22))
print(normalize(original))
print(root(81.0))
print(greeting)
print(lexical)
print(native_import().len())
// expected:
// 42
// zolo
// 9
// Hi Zolo
// true
// 8
int e i64 chegam como bigint, preservando 64 bits. Escreva 1n ao somar
um literal nativo a esses valores. Tipos de ponto flutuante usam number.
Transfira records, bytes e arrays com valores nulos; passe uma closure Zolo, receba uma função nativa e mantenha o estado de uma instância TypeScript.
use plugin node::{typescript as ts}
struct Payload { value: int, optional: int?, items: [int] }
extern ts {
fn echo(value: Payload) -> Payload {
if (!Object.hasOwn(value, "optional") || value.optional !== null) {
throw new Error("Nullable DTO field was lost");
}
return value;
}
fn nullable(values: [int?]) -> [int?] { return values; }
fn empty() -> [int] { return []; }
fn binary() -> bytes { return Buffer.from([0, 255, 42]); }
fn call(callback: fn(int) -> int) -> int { return callback(41n); }
fn factory(delta: int) -> fn(int) -> int { return value => value + delta; }
fn counter() -> Any {
class Counter {
value = 0n;
add(delta: bigint): bigint { this.value += delta; return this.value; }
}
return new Counter();
}
}
let copy = echo(Payload {
value: 9223372036854775807,
optional: nil,
items: [7, 8, 9],
})
print(copy.value)
print(copy.optional == nil)
print(copy.items.len())
let values = nullable([7, nil, nil])
print(values.len())
print(values[1] == nil)
print(empty().len())
print(binary().len())
print(call(|value: int| value + 1))
let increment = factory(1)
print(increment(41))
let live = counter()
print(live.add(40))
live.value = 41
print(live.add(1))
print(live.value)
// expected:
// 9223372036854775807
// true
// 3
// 3
// true
// 0
// 3
// 42
// 42
// 40
// 42
// 42
Importe funções e classes TypeScript, CommonJS com JSDoc e um módulo ESM com await de topo. Use um alias de classe, um método async e uma propriedade.
use plugin node::{javascript as js, typescript as ts}
extern ts from "./node_fixtures/tools.ts" use {twice, later, Counter as Count}
extern js from "./node_fixtures/common.cjs" use {twiceCjs}
extern js from "./node_fixtures/esm.mjs" use {fromEsm}
extern ts fn local_offset(value: int) -> int {
import {offset} from './node_fixtures/offset.ts';
return value + offset;
}
extern js fn local_common(value: int) -> int {
const {twiceCjs} = require('./node_fixtures/common.cjs');
return twiceCjs(value);
}
extern ts mod Tools {
const {twiceCjs} = require('./node_fixtures/common.cjs');
export function greet(name: string): string { return `Hello ${name}`; }
export function twice(value: bigint): bigint { return twiceCjs(value); }
}
extern js mod CommonTools {
const {twiceCjs} = require('./node_fixtures/common.cjs');
/** @param {bigint} value @returns {bigint} */
export function twice(value) { return twiceCjs(value); }
}
print(twice(21))
print(await later(41))
let counter = Count(40)
print(counter.add(1))
print(await counter.addLater(1))
print(counter.value)
print(counter.total)
print(twiceCjs(21))
print(fromEsm(41))
print(Tools.greet("Zolo"))
print(Tools.twice(21))
print(CommonTools.twice(21))
print(local_offset(41))
print(local_common(21))
let value = 41
let captured: int = extern ts {
import {offset} from './node_fixtures/offset.ts';
return value + offset;
}
print(captured)
// expected:
// 42
// 42
// 41
// 42
// 42
// 42
// 42
// 42
// Hello Zolo
// 42
// 42
// 42
// 42
// 42
Um callback que retorna Future vira Promise e pode voltar a uma função Node assíncrona. Combine chamadas, capture erros, cancele uma tarefa e receba um timer.
use plugin node::{typescript as ts}
use std::async::Promise
extern ts async fn later(value: int) -> int {
await new Promise(resolve => setTimeout(resolve, 1));
return value + 1n;
}
extern ts async fn transform(value: int, callback: fn(int) -> Future<int>) -> int {
await new Promise(resolve => setTimeout(resolve, 1));
return await callback(value);
}
extern ts async fn fail() -> int {
await new Promise(resolve => setTimeout(resolve, 1));
throw new Error("Node failed");
}
extern ts async fn wait_for_cancel() -> int {
await new Promise(resolve => setTimeout(resolve, 60000));
return 0n;
}
extern ts async fn schedule(callback: fn(int) -> int) -> int {
setTimeout(() => callback(42n), 10);
return 42n;
}
async fn nested(value: int) -> int {
return await later(value)
}
async fn reject(value: int) -> int {
sleep 1ms
panic("callback failed")
return value
}
async fn cancel_soon(future: Future<int>) -> bool {
sleep 5ms
future.cancel()
return future.is_done()
}
let values = await Promise.all([
transform(41, nested),
transform(42, nested),
])
print(values[0])
print(values[1])
print(try { await transform(0, reject) } catch error { "callback caught" })
print(try { await fail() } catch error { "Node caught" })
let pending = wait_for_cancel()
print(await cancel_soon(pending))
let background: [int] = []
print(await schedule(|value: int| {
background.push(value)
return value
}))
sleep 40ms
print(background[0])
// expected:
// 42
// 43
// callback caught
// Node caught
// true
// 42
// 42
Callbacks executam na thread Zolo. Quando o callback precisa aguardar outra
função Node, declare fn(...) -> Future<T> e aguarde a Promise no corpo nativo.
Isso permite que o event loop continue avançando durante a chamada.
O adapter experimental usa a biblioteca compartilhada Node 26.10.0 para Windows
x64. Distribua node-runtime/ junto do plugin ou configure ZOLO_NODE_HOME.
O compilador TypeScript e seus tipos ficam embutidos no plugin. Pacotes npm
precisam das dependências normais da aplicação. O editor analisa o texto atual
sem executar código do usuário. Outros sistemas ainda aguardam validação.
Veja também