Externals

Externals adalah nilai yang disediakan aplikasi host Anda untuk DSL: state, fungsi, dan komponen. Di dalam DSL semuanya diakses dengan awalan $. Inilah cara sebuah halaman menjangkau dunia luar — pengguna yang login, helper formatDate, atau komponen ikon dari design system Anda.

Daftarkan saat membuat runtime:

import { Nu } from '@nuforge/core';
import * as t from '@nuforge/types';

const nu = Nu.create({
  externals: {
    states: [
      t.externalState({ name: 'user', init: { name: 'Ada', plan: 'Pro' } }),
    ],
    functions: (nu) => [
      t.externalFunc({ name: 'shout', func: (s) => String(s).toUpperCase() }),
    ],
    components: [
      t.externalComponent({
        name: 'Badge',
        render: Badge, // komponen React
        props: [t.componentProp({ name: 'label' })],
      }),
    ],
  },
});

Memakai externals di DSL

JenisDideklarasikanDipakai di DSL
Statet.externalState({ name, init })$user, $user.name
Fungsit.externalFunc({ name, func })$shout($user.name)
Komponent.externalComponent({ name, render, props })<$Badge label={$user.plan} />
component App() {} => (
  <div>
    <h2>{$shout($user.name)}</h2>
    <p>{"Paket saat ini:"} <$Badge label={$user.plan} /></p>
  </div>
)

Tag komponen eksternal memakai awalan $ dan diresolusi ke komponen React yang Anda berikan sebagai render. Hanya prop yang Anda deklarasikan di props yang diteruskan.

State bersifat read-only bagi DSL

State eksternal di-deep-freeze, jadi ekspresi hanya bisa membaca-nya — tidak pernah bisa mengubah state host. Mencoba menetapkan nilai ke external (mis. $user.name = "x") ditolak oleh evaluator.

Hanya host yang dapat memperbarui state eksternal, dan melakukannya akan me-render ulang setiap view yang membacanya:

nu.externals.updateStateValue('user', { name: 'Grace', plan: 'Team' });

Inilah jembatan untuk mendorong data host secara live (auth, store, hasil query) ke halaman yang sedang berjalan: jaga state eksternal tetap sinkron dengan state aplikasi Anda, dan halaman bereaksi otomatis.

Fungsi eksternal

Fungsi eksternal adalah fungsi host biasa. Ia menerima argumen yang sudah dievaluasi dan nilai kembaliannya mengalir kembali ke ekspresi:

t.externalFunc({ name: 'formatPrice', func: (n) => `$${Number(n).toFixed(2)}` });
// DSL:  <span>{$formatPrice(item.price)}</span>

Bentuk factory functions: (nu) => [...] memberi Anda instance Nu bila sebuah fungsi perlu membaca atau mengubah dokumen.

Komponen eksternal

Komponen eksternal memungkinkan DSL me-render komponen React asli dari basis kode Anda — primitif design system, chart, apa pun. Frame meneruskan prop yang dideklarasikan (dan children) ke komponen Anda:

function Badge({ label }: { label?: string }) {
  return <span className="badge">{label}</span>;
}
// didaftarkan dengan props: [t.componentProp({ name: 'label' })]
// DSL:  <$Badge label={$user.plan} />

Lihat langsung (dengan kontrol host yang memperbarui $user) di halaman Contoh.

Komponen eksternal berbasis hook

render sebuah komponen eksternal adalah komponen fungsi React biasa — ia bisa memakai hook apa pun secara internal, persis seperti di mana pun di pohon React lainnya. Tidak ada adapter khusus yang perlu diakali; efek scroll-linked reveal yang dibangun di atas useScroll/useTransform dari motion didaftarkan sebagai external dengan cara yang sama seperti Badge di atas:

import { motion, useScroll, useTransform } from 'motion/react';
import { useRef } from 'react';

function FadeInOnScroll({ children }: { children?: React.ReactNode }) {
  const ref = useRef<HTMLDivElement>(null);
  const { scrollYProgress } = useScroll({ target: ref });
  const opacity = useTransform(scrollYProgress, [0, 1], [0, 1]);
  return <motion.div ref={ref} style={{ opacity }}>{children}</motion.div>;
}
// didaftarkan dengan props: [] — konten datang lewat children
// DSL:  <$FadeInOnScroll><h2>{"Muncul saat discroll"}</h2></$FadeInOnScroll>

Catatan — kanvas editor yang di-render ke dalam <iframe>. IframeCanvas milik @nuforge/editor mem-portal frame ke <iframe> terisolasi demi isolasi CSS. Sebuah React portal cuma memindahkan node DOM ke dalam iframe itu — JavaScript yang membangunnya tetap berjalan di realm halaman host. Hook apa pun yang membaca global window/document polos (posisi scroll, matchMedia, default ResizeObserver/IntersectionObserver) akan diam-diam mengamati halaman host, bukan iframe yang sebenarnya dilihat dan discroll pengguna. Resolve window/document asli kanvas dengan useCanvasWindow() (dari @nuforge/editor/@nuforge/editor/react), bukan global ambient — ia mengembalikan { doc, window } milik iframe sendiri di dalam IframeCanvas, dan cukup fallback ke global ambient di tempat lain (mis. render <NuFrame/> langsung tanpa iframe):

import { useCanvasWindow } from '@nuforge/editor/react';
import { useEffect, useRef } from 'react';

const { doc } = useCanvasWindow();
const container = useRef<HTMLElement | null>(null);
useEffect(() => {
  container.current = doc?.scrollingElement as HTMLElement | null;
}, [doc]);

const { scrollYProgress } = useScroll({ target: ref, container });

Langkah berikutnya