Bahasa DSL
DSL nuforge adalah bahasa deklaratif kecil untuk mendeskripsikan halaman. Sebuah program adalah daftar global dan komponen; nuforge mem-parsing-nya menjadi AST reaktif. Halaman ini menelusuri seluruh sintaksnya.
Global
Gunakan val di tingkat teratas untuk mendeklarasikan nilai bersama. Nilai tersebut dapat menampung ekspresi apa pun yang diizinkan, termasuk objek:
val brand = "nuforge";
val theme = { primary: "#0066DE" };
Komponen
Sebuah komponen memiliki props dengan nilai default di dalam tanda kurung, state lokal yang dideklarasikan dengan val di dalam kurung kurawal, dan template setelah =>:
component App(title = "Untitled") {
val count = 0;
val items = ["a", "b"];
} => (
<div>
<h1>{title}</h1>
</div>
)
Blok props, blok state, dan template masing-masing bersifat opsional, tetapi sebuah komponen selalu mendeklarasikan template-nya setelah panah =>.
Nilai default sebuah prop boleh berupa literal apa pun, termasuk array dan objek — mis. component Card(tags = ["a", "b"], cfg = { open: true }). Koma di dalamnya adalah bagian dari default, bukan pemisah parameter.
Teks dan ekspresi
Di dalam template, kurung kurawal menampung ekspresi. Sebuah literal string seperti {"a static string"} me-render teks statis, sedangkan {expr} mengevaluasi ekspresi dinamis seperti {count}. Template literal didukung — tulis di dalam backtick di dalam sebuah ekspresi:
<p>{`Hello, ${title}`}</p>
Atribut
Atribut menerima ekspresi di dalam kurung kurawal. Gunakan literal string untuk nilai konstan, atau ekspresi apa pun untuk nilai dinamis:
<div class={"page"} id={someId} data-count={count} />
Anda menulis nama atribut HTML — class dan for, bukan className / htmlFor. DSL mendeskripsikan markup, jadi ia tetap netral terhadap framework (HTML dan Vue juga memakai class). Adapter React memetakan class → className dan for → htmlFor saat render, dan ekspor kode melakukan hal yang sama — jadi class={"page"} menjadi className="page" pada React yang dihasilkan.
Inline styles
Tag <style> (atau <script>) mempertahankan isinya sebagai teks mentah, jadi Anda bisa menulis CSS asli — pakai string backtick untuk aturan multi-baris dan combinator:
<style>{`
.card { padding: 1rem; border-radius: 8px; }
.card > .title { font-weight: 700; }
`}</style>
Untuk sebagian besar styling, utamakan utility class (class={"..."}); pakai <style> saat butuh aturan CSS sungguhan. Untuk membatasi style hanya ke kanvas editor, gunakan prop srcDoc / baseCss pada IframeCanvas (lihat SDK Editor).
Direktif
Direktif adalah atribut khusus yang mengontrol rendering:
@if={cond}me-render elemen secara kondisional.@each={item in list}atau@each={(item, index) in list}mengulang elemen untuk setiap entri.@classList={{ active: count > 0 }}mengalihkan kelas berdasarkan kondisi.
<li @each={(item, index) in items} @classList={{ first: index == 0 }}>{item}</li>
Two-way binding
Binding value:={...} menjaga input tetap terkontrol dan menulis suntingan pengguna kembali ke state:
<input value:={name} />
Event
Penangan event adalah arrow function. Mereka dapat menulis ke state secara langsung:
<button onClick={() => count = count + 1}>{count}</button>
val yang diikat ke arrow function adalah method lokal sungguhan, bukan sekadar ekspresi inline — panggil langsung ({f(3)}) atau berikan sebagai referensi ke sebuah handler:
component Counter() {
val count = 0;
val inc = () => count = count + 1;
} => (
<button onClick={inc}>{count}</button>
)
Kedua bentuk ini bekerja identik saat runtime dan diekspor dengan bersih ke React (onClick={inc}, didukung oleh const inc = () => setCount(count + 1); biasa) dan Vue (@click="inc") — lihat Codegen.
Komposisi
Sebuah tag yang namanya diawali huruf kapital adalah instance komponen. Berikan props sebagai atribut:
<Card title={"Hi"} />
Externals
Identifier yang diawali $ merujuk ke nilai yang disediakan host. Gunakan $state untuk state eksternal, $func(...) untuk memanggil fungsi host, dan <$Component/> untuk me-render komponen eksternal:
<$Component label={$state} onPick={() => $func("pick")} />
Lihat halaman externals untuk cara mendaftarkan ini dari host.
Apa yang diizinkan
Demi keamanan, parser hanya menerima sekumpulan node ekspresi yang dikenal: literal, identifier, akses member, array, objek, operator binary / logical / unary, ekspresi kondisional (ternary), pemanggilan fungsi, arrow function (untuk event), dan template literal.
Parser MENOLAK apa pun di luar allow-list tersebut, termasuk:
new,function,this,typeof, dandelete- operator strict-equality — tulis
==/!=, yang saat diekspor dikompilasi menjadi===/!== - increment dan decrement
++/-- - comma sequence dan tagged template
- akses ke
constructor,__proto__, atauprototype
Lihat halaman security untuk jaminan lengkap di balik aturan-aturan ini.
Menulis: validasi & code editor
Parser.diagnose(source) memvalidasi DSL tanpa melempar error — ia mengembalikan daftar diagnostik terstruktur (kosong jika valid), tiap item berisi pesan, sebuah code, dan rentang offset sumber plus baris/kolom:
import { Parser } from '@nuforge/parser';
Parser.diagnose(`component App() {} => (<div>{new Date()}</div>)`);
// [{ severity: 'error', code: 'forbidden-syntax',
// message: 'Syntax "NewExpression" is not allowed',
// from, to, start: { line, column }, end }]
Komponen @nuforge/react-code-editor (CodeEditor) dibangun di atas ini: ia menyediakan autocomplete DSL nuforge (keyword, directive, tag HTML, serta nama component/state/prop/global dan $external dari program Anda) dan squiggle error langsung yang ditenagai diagnose.
Wajib satu salinan CodeMirror. CodeMirror 6 rusak diam-diam — highlight dan autocomplete hilang begitu saja, tanpa error — jika app Anda me-resolve lebih dari satu salinan
@codemirror/stateatau@codemirror/view. Cek dengannpm ls @codemirror/state; jika muncul dua versi, de-duplikasi:overridesnpm/pnpm (ataunpm dedupe/pnpm dedupe),resolutionsyarn, atauresolve.dedupeVite. Ini berlaku untuk integrasi CodeMirror mana pun, bukan khusus nuforge.