@ramonyx/bundler 1.0.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/assets.d.ts CHANGED
@@ -24,6 +24,8 @@ export interface AssetProcessorResult {
24
24
  readonly meta?: {
25
25
  readonly width?: number;
26
26
  readonly height?: number;
27
+ /** Data URI blur/LQIP untuk background anti-CLS. */
28
+ readonly placeholder?: string;
27
29
  };
28
30
  readonly outputs?: readonly AssetProcessorOutput[];
29
31
  }
package/dist/assets.js CHANGED
@@ -96,6 +96,7 @@ export async function collectAssets(assetsDir, clientOutDir, options = {}) {
96
96
  bytes: data.byteLength,
97
97
  ...(meta.width !== undefined ? { width: meta.width } : {}),
98
98
  ...(meta.height !== undefined ? { height: meta.height } : {}),
99
+ ...(meta.placeholder !== undefined ? { placeholder: meta.placeholder } : {}),
99
100
  ...(variants.length > 0 ? { variants } : {}),
100
101
  };
101
102
  }
@@ -131,6 +132,7 @@ export function assetInfoMap(manifest, options = {}) {
131
132
  url: fileToUrl(entry.file, prefix),
132
133
  ...(entry.width !== undefined ? { width: entry.width } : {}),
133
134
  ...(entry.height !== undefined ? { height: entry.height } : {}),
135
+ ...(entry.placeholder !== undefined ? { placeholder: entry.placeholder } : {}),
134
136
  ...(entry.variants !== undefined
135
137
  ? {
136
138
  variants: entry.variants.map((variant) => ({
@@ -1 +1 @@
1
- {"version":3,"file":"assets.js","sourceRoot":"","sources":["../../../packages/bundler/src/assets.ts"],"names":[],"mappings":"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","sourcesContent":["// Pipeline aset (ADR-0069 M1, varian ADR-0079 M2 — tanpa dependency): pindai `src/assets/**`,\n// salin ke **root keluaran klien** (`dist/client/**`) dengan **hash konten** (URL = `file`, ADR-0079),\n// jalankan prosesor plugin (mis. varian gambar dari `@ramonyx/plugin-image`), catat semuanya di\n// `ramonyx.assets.json`, dan sediakan resolver (`assetUrl`/`createAssetResolver`/`assetInfoMap`).\n//\n// Catatan desain: Node SSR tidak dapat `import foto.png` (hanya Vite/klien yang bisa). Karena itu\n// M1 memakai **manifest + helper** (bukan loader kustom) — paritas SSR ↔ klien tanpa mesin baru.\n\nimport { createHash } from \"node:crypto\";\nimport { copyFile, mkdir, readdir, readFile, stat, writeFile } from \"node:fs/promises\";\nimport { dirname, extname, join, posix, relative, sep } from \"node:path\";\nimport {\n type AssetInfo,\n type AssetResolver,\n createAssetResolverFromMap,\n type ResolvedImage,\n} from \"@ramonyx/core\";\nimport {\n type AssetEntry,\n type AssetManifest,\n type AssetMap,\n type AssetVariantEntry,\n readAssetManifest,\n} from \"./manifest.ts\";\n\nexport type { AssetEntry, AssetMap } from \"./manifest.ts\";\n\n/** Direktori aset default (relatif appDir) — dipindai otomatis bila ada. */\nexport const DEFAULT_ASSETS_DIR = \"src/assets\";\n\n// ── Kontrak prosesor aset (dipakai hook plugin `assets` — ADR-0079) ──────────────────────────\n\n/** Input prosesor: satu berkas `src/assets/**` (path relatif posix + isi). */\nexport interface AssetProcessorInput {\n readonly path: string;\n readonly data: Uint8Array;\n}\n\n/** Berkas turunan yang dihasilkan prosesor (mis. varian gambar ter-resize). */\nexport interface AssetProcessorOutput {\n /** Path relatif terhadap `src/assets` (mis. `img/hero-640.webp`) — bundler menambah hash konten. */\n readonly path: string;\n readonly data: Uint8Array;\n /** Lebar varian (dipakai `srcset`). */\n readonly width: number;\n readonly height?: number;\n /** Format berkas; default dari ekstensi `path`. */\n readonly format?: string;\n}\n\n/** Hasil prosesor: metadata entri asli + berkas turunan. */\nexport interface AssetProcessorResult {\n readonly meta?: { readonly width?: number; readonly height?: number };\n readonly outputs?: readonly AssetProcessorOutput[];\n}\n\nexport type AssetProcessor = (\n asset: AssetProcessorInput,\n) => AssetProcessorResult | undefined | Promise<AssetProcessorResult | undefined>;\n\nasync function collectFiles(dir: string, found: string[] = []): Promise<string[]> {\n const entries = await readdir(dir, { withFileTypes: true });\n for (const entry of entries) {\n if (entry.name.startsWith(\".\")) {\n continue;\n }\n const path = join(dir, entry.name);\n if (entry.isDirectory()) {\n await collectFiles(path, found);\n } else if (entry.isFile()) {\n found.push(path);\n }\n }\n return found;\n}\n\nfunction contentHash(data: Uint8Array): string {\n return createHash(\"sha256\").update(data).digest(\"hex\").slice(0, 10);\n}\n\n/**\n * `img/hero.png` + hash → `img/hero-<hash>.png` (relatif direktori keluaran klien).\n *\n * Catatan (koreksi M1, ADR-0079): berkas ditulis **di root keluaran klien** — konsisten dengan\n * URL `<prefix>/<path>` dan middleware `serveStatic({ dir: dist/client, prefix: \"/assets\" })`.\n * Layout M1 (`dist/client/assets/**`) tidak pernah dapat disajikan (URL → root), tertangkap\n * oleh test HTTP aset di create-app.\n */\nfunction hashedTarget(rel: string, hash: string): string {\n const ext = extname(rel);\n const tanpaExt = rel.slice(0, rel.length - ext.length);\n return `${tanpaExt}-${hash}${ext}`;\n}\n\nexport interface CollectAssetsOptions {\n /** Prosesor plugin (dijalankan berurutan per berkas — ADR-0079). */\n readonly processors?: readonly AssetProcessor[];\n}\n\nexport interface CollectAssetsResult {\n readonly assets: AssetMap;\n readonly files: readonly string[];\n}\n\n/**\n * Salin aset dari `assetsDir` ke `<clientOutDir>/**` dengan nama ber-hash konten.\n * Key manifest = path relatif posix terhadap `assetsDir` (mis. `img/logo.png`);\n * `file` = path relatif terhadap keluaran klien → URL publik = `<prefix>/<file>` (ADR-0079).\n * Prosesor (bila ada) dapat menambah varian + metadata dimensi (ADR-0079).\n */\nexport async function collectAssets(\n assetsDir: string,\n clientOutDir: string,\n options: CollectAssetsOptions = {},\n): Promise<CollectAssetsResult> {\n const files = await collectFiles(assetsDir);\n const assets: Record<string, AssetEntry> = {};\n const copied = new Set<string>();\n\n for (const file of files) {\n const data = await readFile(file);\n const rel = relative(assetsDir, file).split(sep).join(posix.sep);\n\n let meta: { width?: number; height?: number } = {};\n const outputs: AssetProcessorOutput[] = [];\n for (const processor of options.processors ?? []) {\n const hasil = await processor({ path: rel, data });\n if (hasil === undefined) {\n continue;\n }\n if (hasil.meta !== undefined) {\n meta = { ...meta, ...hasil.meta };\n }\n if (hasil.outputs !== undefined) {\n outputs.push(...hasil.outputs);\n }\n }\n\n const targetRel = hashedTarget(rel, contentHash(data));\n const target = join(clientOutDir, targetRel);\n await mkdir(dirname(target), { recursive: true });\n await copyFile(file, target);\n copied.add(targetRel);\n\n const variants: AssetVariantEntry[] = [];\n for (const output of outputs) {\n const outputRel = hashedTarget(output.path, contentHash(output.data));\n const outputTarget = join(clientOutDir, outputRel);\n await mkdir(dirname(outputTarget), { recursive: true });\n await writeFile(outputTarget, output.data);\n copied.add(outputRel);\n variants.push({\n file: outputRel,\n bytes: output.data.byteLength,\n width: output.width,\n ...(output.height !== undefined ? { height: output.height } : {}),\n format: (output.format ?? extname(output.path).slice(1)).toLowerCase(),\n });\n }\n\n assets[rel] = {\n file: targetRel,\n bytes: data.byteLength,\n ...(meta.width !== undefined ? { width: meta.width } : {}),\n ...(meta.height !== undefined ? { height: meta.height } : {}),\n ...(variants.length > 0 ? { variants } : {}),\n };\n }\n\n return { assets, files: [...copied] };\n}\n\nexport interface AssetUrlOptions {\n /** Prefix URL publik; default `/assets`. */\n readonly urlPrefix?: string;\n}\n\nfunction normalizeAssetPath(path: string): string {\n return path\n .replace(/^\\.?\\//, \"\")\n .split(sep)\n .join(posix.sep);\n}\n\n/** Berkas keluaran (relatif clientOutDir) → URL publik: `<prefix>/<file>` (1:1 — ADR-0079). */\nfunction fileToUrl(file: string, prefix: string): string {\n return `${prefix}/${file}`;\n}\n\n/** URL publik entri aset; `undefined` bila path tidak ada di manifest. */\nexport function assetUrl(\n manifest: AssetManifest | null,\n path: string,\n options: AssetUrlOptions = {},\n): string | undefined {\n const entry = manifest?.assets?.[normalizeAssetPath(path)];\n if (entry === undefined) {\n return undefined;\n }\n return fileToUrl(entry.file, (options.urlPrefix ?? \"/assets\").replace(/\\/$/, \"\"));\n}\n\n/**\n * Manifest → peta `AssetInfo` (URL + dimensi + varian) untuk resolver: dipakai\n * `createAssetResolver` (Node) dan artefak edge yang meng-inline peta (ADR-0069/0079).\n */\nexport function assetInfoMap(\n manifest: AssetManifest | null,\n options: AssetUrlOptions = {},\n): Record<string, AssetInfo> {\n const prefix = (options.urlPrefix ?? \"/assets\").replace(/\\/$/, \"\");\n const map: Record<string, AssetInfo> = {};\n for (const [path, entry] of Object.entries(manifest?.assets ?? {})) {\n map[path] = {\n url: fileToUrl(entry.file, prefix),\n ...(entry.width !== undefined ? { width: entry.width } : {}),\n ...(entry.height !== undefined ? { height: entry.height } : {}),\n ...(entry.variants !== undefined\n ? {\n variants: entry.variants.map((variant) => ({\n url: fileToUrl(variant.file, prefix),\n width: variant.width,\n ...(variant.height !== undefined ? { height: variant.height } : {}),\n format: variant.format,\n bytes: variant.bytes,\n })),\n }\n : {}),\n };\n }\n return map;\n}\n\nexport interface CreateAssetResolverOptions extends AssetUrlOptions {\n /**\n * Fallback ke `${prefix}/<path>` (tanpa hash) bila manifest belum ada/tidak memuat entri —\n * untuk **dev** (Vite menyajikan `src/assets/**` apa adanya) — default false.\n */\n readonly fallbackToPath?: boolean;\n}\n\n/**\n * Resolver aset untuk server (dipakai loader lewat `createPageRoutes({ assets })`):\n * membaca manifest sekali; di dev pakai `fallbackToPath: true` (Vite menyajikan file mentah;\n * tanpa varian — ADR-0079).\n */\nexport async function createAssetResolver(\n clientDir: string,\n options: CreateAssetResolverOptions = {},\n): Promise<AssetResolver> {\n const manifest = await readAssetManifest(clientDir);\n const prefix = (options.urlPrefix ?? \"/assets\").replace(/\\/$/, \"\");\n const base = createAssetResolverFromMap(assetInfoMap(manifest, { urlPrefix: prefix }));\n\n return {\n url(path: string): string | undefined {\n const found = base.url(path);\n if (found !== undefined) {\n return found;\n }\n if (options.fallbackToPath === true) {\n return `${prefix}/${normalizeAssetPath(path)}`;\n }\n return undefined;\n },\n image(path: string): ResolvedImage | undefined {\n const found = base.image(path);\n if (found !== undefined) {\n return found;\n }\n if (options.fallbackToPath === true) {\n // Dev: berkas disajikan apa adanya (tanpa hash/varian/dimensi).\n return { src: `${prefix}/${normalizeAssetPath(path)}` };\n }\n return undefined;\n },\n };\n}\n\n/** Direktori aset ada? (dipakai buildClient untuk memindai `src/assets` secara otomatis). */\nexport async function assetsDirExists(assetsDir: string): Promise<boolean> {\n try {\n return (await stat(assetsDir)).isDirectory();\n } catch {\n return false;\n }\n}\n\n/** Tag `<link rel=\"preload\">` untuk font (masukkan lewat `metadata.head` halaman/dokumen). */\nexport function fontPreloadTag(\n href: string,\n options: { readonly type?: string; readonly crossorigin?: boolean } = {},\n): string {\n const type = options.type ?? \"font/woff2\";\n const crossorigin = (options.crossorigin ?? true) ? \" crossorigin\" : \"\";\n return `<link rel=\"preload\" as=\"font\" type=\"${type}\" href=\"${href}\"${crossorigin} />`;\n}\n"]}
1
+ {"version":3,"file":"assets.js","sourceRoot":"","sources":["../../../packages/bundler/src/assets.ts"],"names":[],"mappings":"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","sourcesContent":["// Pipeline aset (ADR-0069 M1, varian ADR-0079 M2 — tanpa dependency): pindai `src/assets/**`,\n// salin ke **root keluaran klien** (`dist/client/**`) dengan **hash konten** (URL = `file`, ADR-0079),\n// jalankan prosesor plugin (mis. varian gambar dari `@ramonyx/plugin-image`), catat semuanya di\n// `ramonyx.assets.json`, dan sediakan resolver (`assetUrl`/`createAssetResolver`/`assetInfoMap`).\n//\n// Catatan desain: Node SSR tidak dapat `import foto.png` (hanya Vite/klien yang bisa). Karena itu\n// M1 memakai **manifest + helper** (bukan loader kustom) — paritas SSR ↔ klien tanpa mesin baru.\n\nimport { createHash } from \"node:crypto\";\nimport { copyFile, mkdir, readdir, readFile, stat, writeFile } from \"node:fs/promises\";\nimport { dirname, extname, join, posix, relative, sep } from \"node:path\";\nimport {\n type AssetInfo,\n type AssetResolver,\n createAssetResolverFromMap,\n type ResolvedImage,\n} from \"@ramonyx/core\";\nimport {\n type AssetEntry,\n type AssetManifest,\n type AssetMap,\n type AssetVariantEntry,\n readAssetManifest,\n} from \"./manifest.ts\";\n\nexport type { AssetEntry, AssetMap } from \"./manifest.ts\";\n\n/** Direktori aset default (relatif appDir) — dipindai otomatis bila ada. */\nexport const DEFAULT_ASSETS_DIR = \"src/assets\";\n\n// ── Kontrak prosesor aset (dipakai hook plugin `assets` — ADR-0079) ──────────────────────────\n\n/** Input prosesor: satu berkas `src/assets/**` (path relatif posix + isi). */\nexport interface AssetProcessorInput {\n readonly path: string;\n readonly data: Uint8Array;\n}\n\n/** Berkas turunan yang dihasilkan prosesor (mis. varian gambar ter-resize). */\nexport interface AssetProcessorOutput {\n /** Path relatif terhadap `src/assets` (mis. `img/hero-640.webp`) — bundler menambah hash konten. */\n readonly path: string;\n readonly data: Uint8Array;\n /** Lebar varian (dipakai `srcset`). */\n readonly width: number;\n readonly height?: number;\n /** Format berkas; default dari ekstensi `path`. */\n readonly format?: string;\n}\n\n/** Hasil prosesor: metadata entri asli + berkas turunan. */\nexport interface AssetProcessorResult {\n readonly meta?: {\n readonly width?: number;\n readonly height?: number;\n /** Data URI blur/LQIP untuk background anti-CLS. */\n readonly placeholder?: string;\n };\n readonly outputs?: readonly AssetProcessorOutput[];\n}\n\nexport type AssetProcessor = (\n asset: AssetProcessorInput,\n) => AssetProcessorResult | undefined | Promise<AssetProcessorResult | undefined>;\n\nasync function collectFiles(dir: string, found: string[] = []): Promise<string[]> {\n const entries = await readdir(dir, { withFileTypes: true });\n for (const entry of entries) {\n if (entry.name.startsWith(\".\")) {\n continue;\n }\n const path = join(dir, entry.name);\n if (entry.isDirectory()) {\n await collectFiles(path, found);\n } else if (entry.isFile()) {\n found.push(path);\n }\n }\n return found;\n}\n\nfunction contentHash(data: Uint8Array): string {\n return createHash(\"sha256\").update(data).digest(\"hex\").slice(0, 10);\n}\n\n/**\n * `img/hero.png` + hash → `img/hero-<hash>.png` (relatif direktori keluaran klien).\n *\n * Catatan (koreksi M1, ADR-0079): berkas ditulis **di root keluaran klien** — konsisten dengan\n * URL `<prefix>/<path>` dan middleware `serveStatic({ dir: dist/client, prefix: \"/assets\" })`.\n * Layout M1 (`dist/client/assets/**`) tidak pernah dapat disajikan (URL → root), tertangkap\n * oleh test HTTP aset di create-app.\n */\nfunction hashedTarget(rel: string, hash: string): string {\n const ext = extname(rel);\n const tanpaExt = rel.slice(0, rel.length - ext.length);\n return `${tanpaExt}-${hash}${ext}`;\n}\n\nexport interface CollectAssetsOptions {\n /** Prosesor plugin (dijalankan berurutan per berkas — ADR-0079). */\n readonly processors?: readonly AssetProcessor[];\n}\n\nexport interface CollectAssetsResult {\n readonly assets: AssetMap;\n readonly files: readonly string[];\n}\n\n/**\n * Salin aset dari `assetsDir` ke `<clientOutDir>/**` dengan nama ber-hash konten.\n * Key manifest = path relatif posix terhadap `assetsDir` (mis. `img/logo.png`);\n * `file` = path relatif terhadap keluaran klien → URL publik = `<prefix>/<file>` (ADR-0079).\n * Prosesor (bila ada) dapat menambah varian + metadata dimensi (ADR-0079).\n */\nexport async function collectAssets(\n assetsDir: string,\n clientOutDir: string,\n options: CollectAssetsOptions = {},\n): Promise<CollectAssetsResult> {\n const files = await collectFiles(assetsDir);\n const assets: Record<string, AssetEntry> = {};\n const copied = new Set<string>();\n\n for (const file of files) {\n const data = await readFile(file);\n const rel = relative(assetsDir, file).split(sep).join(posix.sep);\n\n let meta: { width?: number; height?: number; placeholder?: string } = {};\n const outputs: AssetProcessorOutput[] = [];\n for (const processor of options.processors ?? []) {\n const hasil = await processor({ path: rel, data });\n if (hasil === undefined) {\n continue;\n }\n if (hasil.meta !== undefined) {\n meta = { ...meta, ...hasil.meta };\n }\n if (hasil.outputs !== undefined) {\n outputs.push(...hasil.outputs);\n }\n }\n\n const targetRel = hashedTarget(rel, contentHash(data));\n const target = join(clientOutDir, targetRel);\n await mkdir(dirname(target), { recursive: true });\n await copyFile(file, target);\n copied.add(targetRel);\n\n const variants: AssetVariantEntry[] = [];\n for (const output of outputs) {\n const outputRel = hashedTarget(output.path, contentHash(output.data));\n const outputTarget = join(clientOutDir, outputRel);\n await mkdir(dirname(outputTarget), { recursive: true });\n await writeFile(outputTarget, output.data);\n copied.add(outputRel);\n variants.push({\n file: outputRel,\n bytes: output.data.byteLength,\n width: output.width,\n ...(output.height !== undefined ? { height: output.height } : {}),\n format: (output.format ?? extname(output.path).slice(1)).toLowerCase(),\n });\n }\n\n assets[rel] = {\n file: targetRel,\n bytes: data.byteLength,\n ...(meta.width !== undefined ? { width: meta.width } : {}),\n ...(meta.height !== undefined ? { height: meta.height } : {}),\n ...(meta.placeholder !== undefined ? { placeholder: meta.placeholder } : {}),\n ...(variants.length > 0 ? { variants } : {}),\n };\n }\n\n return { assets, files: [...copied] };\n}\n\nexport interface AssetUrlOptions {\n /** Prefix URL publik; default `/assets`. */\n readonly urlPrefix?: string;\n}\n\nfunction normalizeAssetPath(path: string): string {\n return path\n .replace(/^\\.?\\//, \"\")\n .split(sep)\n .join(posix.sep);\n}\n\n/** Berkas keluaran (relatif clientOutDir) → URL publik: `<prefix>/<file>` (1:1 — ADR-0079). */\nfunction fileToUrl(file: string, prefix: string): string {\n return `${prefix}/${file}`;\n}\n\n/** URL publik entri aset; `undefined` bila path tidak ada di manifest. */\nexport function assetUrl(\n manifest: AssetManifest | null,\n path: string,\n options: AssetUrlOptions = {},\n): string | undefined {\n const entry = manifest?.assets?.[normalizeAssetPath(path)];\n if (entry === undefined) {\n return undefined;\n }\n return fileToUrl(entry.file, (options.urlPrefix ?? \"/assets\").replace(/\\/$/, \"\"));\n}\n\n/**\n * Manifest → peta `AssetInfo` (URL + dimensi + varian) untuk resolver: dipakai\n * `createAssetResolver` (Node) dan artefak edge yang meng-inline peta (ADR-0069/0079).\n */\nexport function assetInfoMap(\n manifest: AssetManifest | null,\n options: AssetUrlOptions = {},\n): Record<string, AssetInfo> {\n const prefix = (options.urlPrefix ?? \"/assets\").replace(/\\/$/, \"\");\n const map: Record<string, AssetInfo> = {};\n for (const [path, entry] of Object.entries(manifest?.assets ?? {})) {\n map[path] = {\n url: fileToUrl(entry.file, prefix),\n ...(entry.width !== undefined ? { width: entry.width } : {}),\n ...(entry.height !== undefined ? { height: entry.height } : {}),\n ...(entry.placeholder !== undefined ? { placeholder: entry.placeholder } : {}),\n ...(entry.variants !== undefined\n ? {\n variants: entry.variants.map((variant) => ({\n url: fileToUrl(variant.file, prefix),\n width: variant.width,\n ...(variant.height !== undefined ? { height: variant.height } : {}),\n format: variant.format,\n bytes: variant.bytes,\n })),\n }\n : {}),\n };\n }\n return map;\n}\n\nexport interface CreateAssetResolverOptions extends AssetUrlOptions {\n /**\n * Fallback ke `${prefix}/<path>` (tanpa hash) bila manifest belum ada/tidak memuat entri —\n * untuk **dev** (Vite menyajikan `src/assets/**` apa adanya) — default false.\n */\n readonly fallbackToPath?: boolean;\n}\n\n/**\n * Resolver aset untuk server (dipakai loader lewat `createPageRoutes({ assets })`):\n * membaca manifest sekali; di dev pakai `fallbackToPath: true` (Vite menyajikan file mentah;\n * tanpa varian — ADR-0079).\n */\nexport async function createAssetResolver(\n clientDir: string,\n options: CreateAssetResolverOptions = {},\n): Promise<AssetResolver> {\n const manifest = await readAssetManifest(clientDir);\n const prefix = (options.urlPrefix ?? \"/assets\").replace(/\\/$/, \"\");\n const base = createAssetResolverFromMap(assetInfoMap(manifest, { urlPrefix: prefix }));\n\n return {\n url(path: string): string | undefined {\n const found = base.url(path);\n if (found !== undefined) {\n return found;\n }\n if (options.fallbackToPath === true) {\n return `${prefix}/${normalizeAssetPath(path)}`;\n }\n return undefined;\n },\n image(path: string): ResolvedImage | undefined {\n const found = base.image(path);\n if (found !== undefined) {\n return found;\n }\n if (options.fallbackToPath === true) {\n // Dev: berkas disajikan apa adanya (tanpa hash/varian/dimensi).\n return { src: `${prefix}/${normalizeAssetPath(path)}` };\n }\n return undefined;\n },\n };\n}\n\n/** Direktori aset ada? (dipakai buildClient untuk memindai `src/assets` secara otomatis). */\nexport async function assetsDirExists(assetsDir: string): Promise<boolean> {\n try {\n return (await stat(assetsDir)).isDirectory();\n } catch {\n return false;\n }\n}\n\n/** Tag `<link rel=\"preload\">` untuk font (masukkan lewat `metadata.head` halaman/dokumen). */\nexport function fontPreloadTag(\n href: string,\n options: { readonly type?: string; readonly crossorigin?: boolean } = {},\n): string {\n const type = options.type ?? \"font/woff2\";\n const crossorigin = (options.crossorigin ?? true) ? \" crossorigin\" : \"\";\n return `<link rel=\"preload\" as=\"font\" type=\"${type}\" href=\"${href}\"${crossorigin} />`;\n}\n"]}
package/dist/build.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { existsSync } from "node:fs";
2
2
  import { mkdir, readdir, readFile, writeFile } from "node:fs/promises";
3
3
  import { join } from "node:path";
4
- import { generateClientEntry } from "@ramonyx/compiler";
4
+ import { generateClientEntry, lintClientBoundaries } from "@ramonyx/compiler";
5
5
  import { errorHint } from "@ramonyx/core";
6
6
  import { build } from "vite";
7
7
  import { collectAssets, DEFAULT_ASSETS_DIR } from "./assets.js";
@@ -71,6 +71,15 @@ export async function buildClient(options) {
71
71
  const outDir = join(options.appDir, options.outDir ?? "dist/client");
72
72
  const entryDir = join(options.appDir, ".ramonyx");
73
73
  const entryFile = join(entryDir, "client-entry.ts");
74
+ const boundaryViolations = await lintClientBoundaries(options.pages);
75
+ if (boundaryViolations.length > 0) {
76
+ throw new Error("Batas server/client dilanggar (ADR-0063): modul halaman/layout/error mengimpor API server:\n" +
77
+ boundaryViolations
78
+ .map((violation) => ` - ${violation.file} → ${violation.specifier}`)
79
+ .join("\n") +
80
+ "\nPindahkan impor tersebut ke modul `*.server.ts` yang hanya dipakai loader/action/route API. " +
81
+ errorHint("RMX-BUILD-1"));
82
+ }
74
83
  await assertClientFilesBrowserSafe(options.pages);
75
84
  await mkdir(entryDir, { recursive: true });
76
85
  await writeFile(entryFile, generateClientEntry(options.pages, { from: entryDir }));
package/dist/build.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"build.js","sourceRoot":"","sources":["../../../packages/bundler/src/build.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync } from \"node:fs\";\nimport { mkdir, readdir, readFile, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { generateClientEntry } from \"@ramonyx/compiler\";\nimport { errorHint, type LoadedPage } from \"@ramonyx/core\";\nimport { build, type PluginOption } from \"vite\";\nimport { type AssetProcessor, collectAssets, DEFAULT_ASSETS_DIR } from \"./assets.ts\";\nimport {\n type AssetManifest,\n CLIENT_MANIFEST_FILE,\n readAssetManifest,\n writeAssetManifest,\n} from \"./manifest.ts\";\nimport { serverOnlyPlugin } from \"./server-only.ts\";\n\nexport type { AssetEntry, AssetManifest, AssetMap } from \"./manifest.ts\";\nexport { CLIENT_MANIFEST_FILE, readAssetManifest } from \"./manifest.ts\";\n\nconst SERVER_ONLY_IMPORT = /(?:from|import\\s*\\(|require\\s*\\()\\s*[\"'](node:[^\"']+)[\"']/g;\nconst BROWSER_EXTERNAL_MARKER = \"__vite-browser-external\";\n\nexport interface BuildClientOptions {\n readonly appDir: string;\n readonly pages: readonly LoadedPage[];\n /** Relatif terhadap appDir; default \"dist/client\". */\n readonly outDir?: string;\n /** Hasilkan source map (`.js.map` + `sourcesContent`); default true. */\n readonly sourcemap?: boolean;\n /** Prefix URL publik aset (untuk `scriptPath` hasil); default \"/assets\". */\n readonly assetsUrlPrefix?: string;\n readonly minify?: boolean;\n /** Plugin Vite tambahan (dari plugin Ramonyx — ADR-0067). */\n readonly plugins?: readonly PluginOption[];\n /** Prosesor aset (hook `assets` plugin — ADR-0079). */\n readonly assetProcessors?: readonly AssetProcessor[];\n /**\n * Direktori aset (relatif appDir) yang dipindai & disalin ber-hash ke `assets/**` +\n * dicatat di manifest (ADR-0069). Default `src/assets` bila ada; `false` mematikan.\n */\n readonly assetsDir?: string | false;\n}\n\nexport interface BuildClientResult {\n readonly outDir: string;\n /** URL bundle klien ber-fingerprint, mis. `/assets/client-D3fK2p.js`. */\n readonly scriptPath: string;\n readonly entryFile: string;\n /** Nama berkas (relatif terhadap outDir) yang dihasilkan build. */\n readonly files: readonly string[];\n}\n\n/**\n * Membundel client (React + registry halaman) memakai Vite (ADR-0005).\n *\n * Nama berkas memuat **hash konten** (`client-<hash>.js`, ADR-0055) sehingga dapat disajikan\n * dengan `cache-control: immutable`; peta nama ditulis ke `ramonyx.assets.json` dan dibaca\n * server lewat `readClientScript`.\n */\n/** Pre-check (ADR-0063): modul yang masuk bundle klien (page/layout) dilarang mengimpor `node:*`. */\nasync function assertClientFilesBrowserSafe(pages: readonly LoadedPage[]): Promise<void> {\n const files = new Set<string>();\n for (const page of pages) {\n if (page.pageFile !== undefined) {\n files.add(page.pageFile);\n }\n for (const layout of page.layoutFiles ?? []) {\n files.add(layout);\n }\n }\n\n const violations: string[] = [];\n for (const file of files) {\n let source: string;\n try {\n source = await readFile(file, \"utf8\");\n } catch {\n continue;\n }\n for (const match of source.matchAll(SERVER_ONLY_IMPORT)) {\n violations.push(`${file} → ${match[1] ?? \"?\"}`);\n }\n }\n\n if (violations.length > 0) {\n throw new Error(\n \"Batas server/client dilanggar (ADR-0063): modul yang masuk bundle klien mengimpor API server:\\n\" +\n violations.map((entry) => ` - ${entry}`).join(\"\\n\") +\n \"\\nPindahkan kode server ke modul `*.server.ts` yang hanya diimpor dari loader/action/route API, \" +\n `atau akses lewat API route. ${errorHint(\"RMX-BUILD-1\")}`,\n );\n }\n}\n\n/** Post-build (ADR-0063): output tidak boleh memuat shim browser-external Vite atau `node:*` (impor transitif). */\nasync function assertBundleBrowserSafe(outDir: string): Promise<void> {\n const files = (await readdir(outDir, { recursive: true })).filter((file) => file.endsWith(\".js\"));\n const violations = new Set<string>();\n for (const file of files) {\n const source = await readFile(join(outDir, file), \"utf8\");\n if (source.includes(BROWSER_EXTERNAL_MARKER)) {\n violations.add(file);\n }\n for (const match of source.matchAll(SERVER_ONLY_IMPORT)) {\n violations.add(`${file} → ${match[1] ?? \"?\"}`);\n }\n }\n\n if (violations.size > 0) {\n throw new Error(\n \"Bundel klien memuat API server (ADR-0063) — kemungkinan impor transitif dari modul klien:\\n\" +\n [...violations].map((entry) => ` - ${entry}`).join(\"\\n\") +\n `\\nPisahkan modul server (\\`*.server.ts\\`) dan impor hanya dari loader/action/route API. ${errorHint(\"RMX-BUILD-2\")}`,\n );\n }\n}\n\nexport async function buildClient(options: BuildClientOptions): Promise<BuildClientResult> {\n const outDir = join(options.appDir, options.outDir ?? \"dist/client\");\n const entryDir = join(options.appDir, \".ramonyx\");\n const entryFile = join(entryDir, \"client-entry.ts\");\n\n await assertClientFilesBrowserSafe(options.pages);\n\n await mkdir(entryDir, { recursive: true });\n await writeFile(entryFile, generateClientEntry(options.pages, { from: entryDir }));\n\n await build({\n configFile: false,\n root: options.appDir,\n logLevel: \"warn\",\n esbuild: { jsx: \"automatic\" },\n plugins: [...(options.plugins ?? []), serverOnlyPlugin()],\n build: {\n outDir,\n emptyOutDir: true,\n target: \"es2022\",\n minify: options.minify ?? false,\n // Source map produksi (default aktif): `sourcesContent` di-embed agar map tetap\n // berguna walau sumber TS tidak ikut disajikan (ADR-0046).\n sourcemap: options.sourcemap ?? true,\n modulePreload: false,\n rollupOptions: {\n input: entryFile,\n output: {\n entryFileNames: \"client-[hash].js\",\n chunkFileNames: \"chunks/[name]-[hash].js\",\n assetFileNames: \"[name]-[hash][extname]\",\n },\n },\n },\n });\n\n await assertBundleBrowserSafe(outDir);\n\n const files = (await readdir(outDir, { recursive: true })).filter(\n (file) => !file.endsWith(CLIENT_MANIFEST_FILE),\n );\n const clientFile = files.find((file) => /^client-.*\\.js$/.test(file));\n if (clientFile === undefined) {\n throw new Error(`build klien tidak menghasilkan client-<hash>.js di ${outDir}`);\n }\n\n // Pipeline aset (ADR-0069): pindai `src/assets/**` → salin ber-hash + catat di manifest.\n const assetsDir =\n options.assetsDir === false\n ? null\n : join(options.appDir, options.assetsDir ?? DEFAULT_ASSETS_DIR);\n let assets: Awaited<ReturnType<typeof collectAssets>>[\"assets\"] = {};\n if (assetsDir !== null && existsSync(assetsDir)) {\n const collected = await collectAssets(assetsDir, outDir, {\n processors: options.assetProcessors,\n });\n assets = collected.assets;\n }\n\n const manifest: AssetManifest = {\n client: clientFile,\n files: [...files].sort(),\n ...(Object.keys(assets).length > 0 ? { assets } : {}),\n };\n await writeAssetManifest(outDir, manifest);\n\n const prefix = (options.assetsUrlPrefix ?? \"/assets\").replace(/\\/$/, \"\");\n return { outDir, scriptPath: `${prefix}/${clientFile}`, entryFile, files: manifest.files };\n}\n\n/**\n * URL bundle klien dari manifest hasil build — dipakai server (`ramonyx start`, aplikasi)\n * agar tetap benar saat nama berkas ber-hash. Fallback `/assets/client.js` bila manifest\n * belum ada (mis. server dijalankan tanpa build — CLI sudah memperingatkan).\n */\nexport async function readClientScript(\n clientDir: string,\n options: { urlPrefix?: string } = {},\n): Promise<string> {\n const prefix = (options.urlPrefix ?? \"/assets\").replace(/\\/$/, \"\");\n const manifest = await readAssetManifest(clientDir);\n return manifest === null ? `${prefix}/client.js` : `${prefix}/${manifest.client}`;\n}\n"]}
1
+ {"version":3,"file":"build.js","sourceRoot":"","sources":["../../../packages/bundler/src/build.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync } from \"node:fs\";\nimport { mkdir, readdir, readFile, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { generateClientEntry, lintClientBoundaries } from \"@ramonyx/compiler\";\nimport { errorHint, type LoadedPage } from \"@ramonyx/core\";\nimport { build, type PluginOption } from \"vite\";\nimport { type AssetProcessor, collectAssets, DEFAULT_ASSETS_DIR } from \"./assets.ts\";\nimport {\n type AssetManifest,\n CLIENT_MANIFEST_FILE,\n readAssetManifest,\n writeAssetManifest,\n} from \"./manifest.ts\";\nimport { serverOnlyPlugin } from \"./server-only.ts\";\n\nexport type { AssetEntry, AssetManifest, AssetMap } from \"./manifest.ts\";\nexport { CLIENT_MANIFEST_FILE, readAssetManifest } from \"./manifest.ts\";\n\nconst SERVER_ONLY_IMPORT = /(?:from|import\\s*\\(|require\\s*\\()\\s*[\"'](node:[^\"']+)[\"']/g;\nconst BROWSER_EXTERNAL_MARKER = \"__vite-browser-external\";\n\nexport interface BuildClientOptions {\n readonly appDir: string;\n readonly pages: readonly LoadedPage[];\n /** Relatif terhadap appDir; default \"dist/client\". */\n readonly outDir?: string;\n /** Hasilkan source map (`.js.map` + `sourcesContent`); default true. */\n readonly sourcemap?: boolean;\n /** Prefix URL publik aset (untuk `scriptPath` hasil); default \"/assets\". */\n readonly assetsUrlPrefix?: string;\n readonly minify?: boolean;\n /** Plugin Vite tambahan (dari plugin Ramonyx — ADR-0067). */\n readonly plugins?: readonly PluginOption[];\n /** Prosesor aset (hook `assets` plugin — ADR-0079). */\n readonly assetProcessors?: readonly AssetProcessor[];\n /**\n * Direktori aset (relatif appDir) yang dipindai & disalin ber-hash ke `assets/**` +\n * dicatat di manifest (ADR-0069). Default `src/assets` bila ada; `false` mematikan.\n */\n readonly assetsDir?: string | false;\n}\n\nexport interface BuildClientResult {\n readonly outDir: string;\n /** URL bundle klien ber-fingerprint, mis. `/assets/client-D3fK2p.js`. */\n readonly scriptPath: string;\n readonly entryFile: string;\n /** Nama berkas (relatif terhadap outDir) yang dihasilkan build. */\n readonly files: readonly string[];\n}\n\n/**\n * Membundel client (React + registry halaman) memakai Vite (ADR-0005).\n *\n * Nama berkas memuat **hash konten** (`client-<hash>.js`, ADR-0055) sehingga dapat disajikan\n * dengan `cache-control: immutable`; peta nama ditulis ke `ramonyx.assets.json` dan dibaca\n * server lewat `readClientScript`.\n */\n/** Pre-check (ADR-0063): modul yang masuk bundle klien (page/layout) dilarang mengimpor `node:*`. */\nasync function assertClientFilesBrowserSafe(pages: readonly LoadedPage[]): Promise<void> {\n const files = new Set<string>();\n for (const page of pages) {\n if (page.pageFile !== undefined) {\n files.add(page.pageFile);\n }\n for (const layout of page.layoutFiles ?? []) {\n files.add(layout);\n }\n }\n\n const violations: string[] = [];\n for (const file of files) {\n let source: string;\n try {\n source = await readFile(file, \"utf8\");\n } catch {\n continue;\n }\n for (const match of source.matchAll(SERVER_ONLY_IMPORT)) {\n violations.push(`${file} → ${match[1] ?? \"?\"}`);\n }\n }\n\n if (violations.length > 0) {\n throw new Error(\n \"Batas server/client dilanggar (ADR-0063): modul yang masuk bundle klien mengimpor API server:\\n\" +\n violations.map((entry) => ` - ${entry}`).join(\"\\n\") +\n \"\\nPindahkan kode server ke modul `*.server.ts` yang hanya diimpor dari loader/action/route API, \" +\n `atau akses lewat API route. ${errorHint(\"RMX-BUILD-1\")}`,\n );\n }\n}\n\n/** Post-build (ADR-0063): output tidak boleh memuat shim browser-external Vite atau `node:*` (impor transitif). */\nasync function assertBundleBrowserSafe(outDir: string): Promise<void> {\n const files = (await readdir(outDir, { recursive: true })).filter((file) => file.endsWith(\".js\"));\n const violations = new Set<string>();\n for (const file of files) {\n const source = await readFile(join(outDir, file), \"utf8\");\n if (source.includes(BROWSER_EXTERNAL_MARKER)) {\n violations.add(file);\n }\n for (const match of source.matchAll(SERVER_ONLY_IMPORT)) {\n violations.add(`${file} → ${match[1] ?? \"?\"}`);\n }\n }\n\n if (violations.size > 0) {\n throw new Error(\n \"Bundel klien memuat API server (ADR-0063) — kemungkinan impor transitif dari modul klien:\\n\" +\n [...violations].map((entry) => ` - ${entry}`).join(\"\\n\") +\n `\\nPisahkan modul server (\\`*.server.ts\\`) dan impor hanya dari loader/action/route API. ${errorHint(\"RMX-BUILD-2\")}`,\n );\n }\n}\n\nexport async function buildClient(options: BuildClientOptions): Promise<BuildClientResult> {\n const outDir = join(options.appDir, options.outDir ?? \"dist/client\");\n const entryDir = join(options.appDir, \".ramonyx\");\n const entryFile = join(entryDir, \"client-entry.ts\");\n\n const boundaryViolations = await lintClientBoundaries(options.pages);\n if (boundaryViolations.length > 0) {\n throw new Error(\n \"Batas server/client dilanggar (ADR-0063): modul halaman/layout/error mengimpor API server:\\n\" +\n boundaryViolations\n .map((violation) => ` - ${violation.file} → ${violation.specifier}`)\n .join(\"\\n\") +\n \"\\nPindahkan impor tersebut ke modul `*.server.ts` yang hanya dipakai loader/action/route API. \" +\n errorHint(\"RMX-BUILD-1\"),\n );\n }\n\n await assertClientFilesBrowserSafe(options.pages);\n\n await mkdir(entryDir, { recursive: true });\n await writeFile(entryFile, generateClientEntry(options.pages, { from: entryDir }));\n\n await build({\n configFile: false,\n root: options.appDir,\n logLevel: \"warn\",\n esbuild: { jsx: \"automatic\" },\n plugins: [...(options.plugins ?? []), serverOnlyPlugin()],\n build: {\n outDir,\n emptyOutDir: true,\n target: \"es2022\",\n minify: options.minify ?? false,\n // Source map produksi (default aktif): `sourcesContent` di-embed agar map tetap\n // berguna walau sumber TS tidak ikut disajikan (ADR-0046).\n sourcemap: options.sourcemap ?? true,\n modulePreload: false,\n rollupOptions: {\n input: entryFile,\n output: {\n entryFileNames: \"client-[hash].js\",\n chunkFileNames: \"chunks/[name]-[hash].js\",\n assetFileNames: \"[name]-[hash][extname]\",\n },\n },\n },\n });\n\n await assertBundleBrowserSafe(outDir);\n\n const files = (await readdir(outDir, { recursive: true })).filter(\n (file) => !file.endsWith(CLIENT_MANIFEST_FILE),\n );\n const clientFile = files.find((file) => /^client-.*\\.js$/.test(file));\n if (clientFile === undefined) {\n throw new Error(`build klien tidak menghasilkan client-<hash>.js di ${outDir}`);\n }\n\n // Pipeline aset (ADR-0069): pindai `src/assets/**` → salin ber-hash + catat di manifest.\n const assetsDir =\n options.assetsDir === false\n ? null\n : join(options.appDir, options.assetsDir ?? DEFAULT_ASSETS_DIR);\n let assets: Awaited<ReturnType<typeof collectAssets>>[\"assets\"] = {};\n if (assetsDir !== null && existsSync(assetsDir)) {\n const collected = await collectAssets(assetsDir, outDir, {\n processors: options.assetProcessors,\n });\n assets = collected.assets;\n }\n\n const manifest: AssetManifest = {\n client: clientFile,\n files: [...files].sort(),\n ...(Object.keys(assets).length > 0 ? { assets } : {}),\n };\n await writeAssetManifest(outDir, manifest);\n\n const prefix = (options.assetsUrlPrefix ?? \"/assets\").replace(/\\/$/, \"\");\n return { outDir, scriptPath: `${prefix}/${clientFile}`, entryFile, files: manifest.files };\n}\n\n/**\n * URL bundle klien dari manifest hasil build — dipakai server (`ramonyx start`, aplikasi)\n * agar tetap benar saat nama berkas ber-hash. Fallback `/assets/client.js` bila manifest\n * belum ada (mis. server dijalankan tanpa build — CLI sudah memperingatkan).\n */\nexport async function readClientScript(\n clientDir: string,\n options: { urlPrefix?: string } = {},\n): Promise<string> {\n const prefix = (options.urlPrefix ?? \"/assets\").replace(/\\/$/, \"\");\n const manifest = await readAssetManifest(clientDir);\n return manifest === null ? `${prefix}/client.js` : `${prefix}/${manifest.client}`;\n}\n"]}
package/dist/dev.d.ts CHANGED
@@ -1,11 +1,23 @@
1
1
  import { type ModuleImporter } from "@ramonyx/compiler";
2
+ import type { FetchHandler } from "@ramonyx/core";
2
3
  import { type PluginOption } from "vite";
3
4
  import { type DevtoolsCollector } from "./devtools.js";
4
5
  export interface DevServerOptions {
5
6
  readonly appDir: string;
6
- /** URL server SSR yang diproksikan (mis. "http://localhost:3000"). */
7
- readonly target: string;
7
+ /**
8
+ * Mode terintegrasi: factory handler SSR. Vite berjalan sebagai middleware di server yang
9
+ * sama; `target` tidak dipakai.
10
+ */
11
+ readonly handler?: (context: {
12
+ readonly importer: ModuleImporter;
13
+ readonly devtools: DevtoolsCollector;
14
+ }) => FetchHandler | Promise<FetchHandler>;
15
+ /** Mode proxy lama: URL server SSR terpisah (mis. "http://localhost:3000"). */
16
+ readonly target?: string;
17
+ /** Port HTTP server (mode terintegrasi: port publik/dev). */
8
18
  readonly port?: number;
19
+ /** Interface bind; default localhost. */
20
+ readonly host?: string;
9
21
  /** Plugin Vite tambahan (dari plugin Ramonyx — ADR-0067). */
10
22
  readonly plugins?: readonly PluginOption[];
11
23
  }
@@ -13,13 +25,19 @@ export interface DevServer {
13
25
  readonly url: string;
14
26
  /** Importer untuk compiler: modul mengikuti module graph Vite (invalidasi saat file berubah). */
15
27
  readonly importer: ModuleImporter;
16
- readonly target: string;
28
+ /** Hanya ada di mode proxy lama. */
29
+ readonly target?: string;
17
30
  /** Kolektor panel devtools (ADR-0077) — pasang `hooks`/`middleware`-nya ke handler aplikasi. */
18
31
  readonly devtools: DevtoolsCollector;
19
32
  close(): Promise<void>;
20
33
  }
21
34
  /**
22
- * Dev server: Vite (transform + HMR) sebagai front, proxy ke server SSR.
35
+ * Dev server: Vite (transform + HMR) dan SSR.
36
+ *
37
+ * - Mode terintegrasi (`handler`): satu HTTP server/port; Vite middleware + HMR WebSocket
38
+ * menempel ke server yang sama, SSR menjadi middleware terakhir.
39
+ * - Mode proxy lama (`target`): Vite port terpisah memproksikan request ke SSR.
40
+ *
23
41
  * Modul halaman/manifest dimuat lewat `vite.ssrLoadModule` agar SSR ikut segar saat file berubah
24
42
  * (tanpa restart); perubahan di `src/routes` meregenerasi entry client + full-reload browser.
25
43
  */
package/dist/dev.js CHANGED
@@ -1,9 +1,11 @@
1
1
  import { existsSync } from "node:fs";
2
2
  import { mkdir, readFile, stat, writeFile } from "node:fs/promises";
3
+ import { createServer as createHttpServer } from "node:http";
3
4
  import { extname, join, relative } from "node:path";
4
5
  import { Readable } from "node:stream";
5
6
  import { pipeline } from "node:stream/promises";
6
- import { generateClientEntry, loadPageRoutes, RouteManifestError, } from "@ramonyx/compiler";
7
+ import { createNodeRequestListener } from "@ramonyx/adapter-runtime";
8
+ import { generateClientEntry, listApiRoutes, loadPageRoutes, RouteManifestError, } from "@ramonyx/compiler";
7
9
  import { createServer } from "vite";
8
10
  import { createDevtoolsCollector, devtoolsPanelHtml } from "./devtools.js";
9
11
  import { readAssetManifest } from "./manifest.js";
@@ -101,6 +103,8 @@ async function proxyToSsr(target, req, res, next) {
101
103
  headers.set(name, value);
102
104
  }
103
105
  }
106
+ // Tandai request yang datang lewat Vite. SSR langsung di port 3000 tidak punya entry klien.
107
+ headers.set("x-ramonyx-dev-proxy", "1");
104
108
  const method = req.method ?? "GET";
105
109
  const init = {
106
110
  method,
@@ -132,8 +136,28 @@ async function proxyToSsr(target, req, res, next) {
132
136
  next(error);
133
137
  }
134
138
  }
139
+ /** Mode proxy lama: Vite dan SSR berjalan di port berbeda. */
140
+ function ssrProxyPlugin(target) {
141
+ return {
142
+ name: "ramonyx-ssr-proxy",
143
+ configureServer(server) {
144
+ server.middlewares.use((req, res, next) => {
145
+ if (req.url === undefined || isViteRequest(req.url)) {
146
+ next();
147
+ return;
148
+ }
149
+ void proxyToSsr(target, req, res, next);
150
+ });
151
+ },
152
+ };
153
+ }
135
154
  /**
136
- * Dev server: Vite (transform + HMR) sebagai front, proxy ke server SSR.
155
+ * Dev server: Vite (transform + HMR) dan SSR.
156
+ *
157
+ * - Mode terintegrasi (`handler`): satu HTTP server/port; Vite middleware + HMR WebSocket
158
+ * menempel ke server yang sama, SSR menjadi middleware terakhir.
159
+ * - Mode proxy lama (`target`): Vite port terpisah memproksikan request ke SSR.
160
+ *
137
161
  * Modul halaman/manifest dimuat lewat `vite.ssrLoadModule` agar SSR ikut segar saat file berubah
138
162
  * (tanpa restart); perubahan di `src/routes` meregenerasi entry client + full-reload browser.
139
163
  */
@@ -169,13 +193,24 @@ export async function createDevServer(options) {
169
193
  }
170
194
  devtools.setAssets(entries);
171
195
  };
196
+ const integrated = options.handler !== undefined;
197
+ const proxyTarget = options.target;
198
+ if (!integrated && proxyTarget === undefined) {
199
+ throw new Error("createDevServer: butuh `handler` (mode terintegrasi) atau `target` (mode proxy)");
200
+ }
201
+ const integratedServer = integrated ? createHttpServer() : undefined;
172
202
  const vite = await createServer({
173
203
  configFile: false,
174
204
  root: appDir,
175
205
  appType: "custom",
176
206
  logLevel: "info",
177
207
  esbuild: { jsx: "automatic" },
178
- server: { port: options.port ?? 5173 },
208
+ server: integrated
209
+ ? {
210
+ middlewareMode: { server: integratedServer },
211
+ ws: { server: integratedServer },
212
+ }
213
+ : { port: options.port ?? 5173 },
179
214
  plugins: [
180
215
  ...(options.plugins ?? []),
181
216
  serverOnlyPlugin(),
@@ -205,18 +240,7 @@ export async function createDevServer(options) {
205
240
  });
206
241
  },
207
242
  },
208
- {
209
- name: "ramonyx-ssr-proxy",
210
- configureServer(server) {
211
- server.middlewares.use((req, res, next) => {
212
- if (req.url === undefined || isViteRequest(req.url)) {
213
- next();
214
- return;
215
- }
216
- void proxyToSsr(options.target, req, res, next);
217
- });
218
- },
219
- },
243
+ ...(integrated ? [] : [ssrProxyPlugin(proxyTarget)]),
220
244
  ],
221
245
  });
222
246
  const importer = async (absolute, label) => {
@@ -228,11 +252,23 @@ export async function createDevServer(options) {
228
252
  }
229
253
  };
230
254
  const regenerateEntry = async () => {
231
- const pages = await loadPageRoutes({ dir: routesDir, importer });
232
- devtools.setRoutes(pages.map((page) => ({
233
- path: page.path,
234
- ...(page.pageFile !== undefined ? { file: relative(appDir, page.pageFile) } : {}),
235
- })));
255
+ const [pages, apiRoutes] = await Promise.all([
256
+ loadPageRoutes({ dir: routesDir, importer }),
257
+ listApiRoutes({ dir: routesDir, importer }),
258
+ ]);
259
+ devtools.setRoutes([
260
+ ...pages.map((page) => ({
261
+ path: page.path,
262
+ kind: "page",
263
+ ...(page.pageFile !== undefined ? { file: relative(appDir, page.pageFile) } : {}),
264
+ })),
265
+ ...apiRoutes.map((route) => ({
266
+ path: route.routePath,
267
+ kind: "api",
268
+ methods: route.methods,
269
+ file: relative(appDir, route.file),
270
+ })),
271
+ ]);
236
272
  await mkdir(entryDir, { recursive: true });
237
273
  await writeFile(entryFile, generateClientEntry(pages, { from: entryDir }));
238
274
  };
@@ -247,6 +283,56 @@ export async function createDevServer(options) {
247
283
  vite.ws.send({ type: "full-reload" });
248
284
  });
249
285
  });
286
+ if (integrated) {
287
+ const server = integratedServer;
288
+ const fetchHandler = await options.handler?.({ importer, devtools });
289
+ if (fetchHandler === undefined) {
290
+ throw new Error("createDevServer: handler tidak mengembalikan FetchHandler");
291
+ }
292
+ // SSR adalah middleware terakhir: request yang tidak ditangani Vite/plugin masuk ke app.
293
+ vite.middlewares.use(createNodeRequestListener(fetchHandler, { onError: console.error }));
294
+ server.on("request", (req, res) => {
295
+ vite.middlewares(req, res, (error) => {
296
+ if (error) {
297
+ console.error(error);
298
+ if (!res.headersSent) {
299
+ res.writeHead(500, { "content-type": "application/json" });
300
+ }
301
+ res.end(JSON.stringify({ error: "Internal Server Error" }));
302
+ return;
303
+ }
304
+ if (!res.headersSent) {
305
+ res.writeHead(404, { "content-type": "text/plain; charset=utf-8" });
306
+ }
307
+ res.end("Not found");
308
+ });
309
+ });
310
+ await new Promise((resolve, reject) => {
311
+ server.once("error", reject);
312
+ server.listen(options.port ?? 3000, options.host ?? "localhost", () => {
313
+ server.off("error", reject);
314
+ resolve();
315
+ });
316
+ });
317
+ const address = server.address();
318
+ const port = typeof address === "object" && address !== null ? address.port : (options.port ?? 3000);
319
+ const host = options.host === undefined ||
320
+ options.host === "0.0.0.0" ||
321
+ options.host === "::" ||
322
+ options.host === "127.0.0.1"
323
+ ? "localhost"
324
+ : options.host;
325
+ return {
326
+ url: `http://${host}:${port}`,
327
+ importer,
328
+ devtools,
329
+ close: async () => {
330
+ server.closeAllConnections();
331
+ await new Promise((resolve) => server.close(() => resolve()));
332
+ await vite.close();
333
+ },
334
+ };
335
+ }
250
336
  await vite.listen();
251
337
  const address = vite.httpServer?.address();
252
338
  const port = typeof address === "object" && address !== null ? address.port : (options.port ?? 5173);
package/dist/dev.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"dev.js","sourceRoot":"","sources":["../../../packages/bundler/src/dev.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync } from \"node:fs\";\nimport { mkdir, readFile, stat, writeFile } from \"node:fs/promises\";\nimport type { ServerResponse } from \"node:http\";\nimport { extname, join, relative } from \"node:path\";\nimport { Readable } from \"node:stream\";\nimport { pipeline } from \"node:stream/promises\";\nimport {\n generateClientEntry,\n loadPageRoutes,\n type ModuleImporter,\n RouteManifestError,\n} from \"@ramonyx/compiler\";\nimport { type Connect, createServer, type Plugin, type PluginOption } from \"vite\";\nimport { createDevtoolsCollector, type DevtoolsCollector, devtoolsPanelHtml } from \"./devtools.ts\";\nimport { readAssetManifest } from \"./manifest.ts\";\nimport { serverOnlyPlugin } from \"./server-only.ts\";\n\nexport interface DevServerOptions {\n readonly appDir: string;\n /** URL server SSR yang diproksikan (mis. \"http://localhost:3000\"). */\n readonly target: string;\n readonly port?: number;\n /** Plugin Vite tambahan (dari plugin Ramonyx — ADR-0067). */\n readonly plugins?: readonly PluginOption[];\n}\n\nexport interface DevServer {\n readonly url: string;\n /** Importer untuk compiler: modul mengikuti module graph Vite (invalidasi saat file berubah). */\n readonly importer: ModuleImporter;\n readonly target: string;\n /** Kolektor panel devtools (ADR-0077) — pasang `hooks`/`middleware`-nya ke handler aplikasi. */\n readonly devtools: DevtoolsCollector;\n close(): Promise<void>;\n}\n\nconst ENTRY_FILE = \"client-entry.ts\";\n\nconst DEV_ASSET_TYPES: Record<string, string> = {\n \".svg\": \"image/svg+xml\",\n \".png\": \"image/png\",\n \".jpg\": \"image/jpeg\",\n \".jpeg\": \"image/jpeg\",\n \".gif\": \"image/gif\",\n \".webp\": \"image/webp\",\n \".avif\": \"image/avif\",\n \".ico\": \"image/x-icon\",\n \".woff\": \"font/woff\",\n \".woff2\": \"font/woff2\",\n \".ttf\": \"font/ttf\",\n \".otf\": \"font/otf\",\n};\n\n/**\n * Dev: sajikan `src/assets/**` apa adanya di `/assets/*` (tanpa hash) — paritas dengan\n * resolver aset dev (`createAssetResolver(..., { fallbackToPath: true })`, ADR-0069).\n */\nfunction assetsDevPlugin(appDir: string): Plugin {\n return {\n name: \"ramonyx-assets-dev\",\n configureServer(server) {\n server.middlewares.use((req, res, next) => {\n const url = req.url ?? \"\";\n if (!url.startsWith(\"/assets/\")) {\n next();\n return;\n }\n let rel: string;\n try {\n rel = decodeURIComponent(url.slice(\"/assets/\".length).split(\"?\")[0] ?? \"\");\n } catch {\n next();\n return;\n }\n if (rel.length === 0 || rel.includes(\"..\")) {\n next();\n return;\n }\n const file = join(appDir, \"src\", \"assets\", rel);\n if (!existsSync(file)) {\n next();\n return;\n }\n void readFile(file)\n .then((data) => {\n res.setHeader(\n \"content-type\",\n DEV_ASSET_TYPES[extname(file).toLowerCase()] ?? \"application/octet-stream\",\n );\n res.setHeader(\"cache-control\", \"no-cache\");\n res.end(data);\n })\n .catch(() => next());\n });\n },\n };\n}\n\n/** Path yang dimiliki Vite (jangan diproksikan ke SSR). */\nfunction isViteRequest(url: string): boolean {\n const path = url.split(\"?\")[0] ?? \"\";\n return (\n path.startsWith(\"/@\") ||\n path.startsWith(\"/node_modules/\") ||\n path.startsWith(\"/.ramonyx/\") ||\n path.startsWith(\"/src/\") ||\n path === \"/favicon.ico\"\n );\n}\n\nconst HOP_BY_HOP = new Set([\n \"connection\",\n \"keep-alive\",\n \"transfer-encoding\",\n \"content-length\",\n \"content-encoding\",\n]);\n\n/**\n * Proxy manual ke SSR (bukan `server.proxy` Vite): request non-Vite diteruskan dengan `fetch`,\n * respons dipipe kembali apa adanya. Body request non-GET/HEAD dialirkan via `Readable.toWeb`.\n */\nasync function proxyToSsr(\n target: string,\n req: Connect.IncomingMessage,\n res: ServerResponse,\n next: Connect.NextFunction,\n): Promise<void> {\n try {\n const url = new URL(req.url ?? \"/\", target);\n const headers = new Headers();\n for (const [name, value] of Object.entries(req.headers)) {\n if (value === undefined) continue;\n if (Array.isArray(value)) {\n for (const item of value) {\n headers.append(name, item);\n }\n } else {\n headers.set(name, value);\n }\n }\n\n const method = req.method ?? \"GET\";\n const init: RequestInit & { duplex?: \"half\" } = {\n method,\n headers,\n redirect: \"manual\",\n };\n if (method !== \"GET\" && method !== \"HEAD\") {\n init.body = Readable.toWeb(req) as unknown as BodyInit;\n init.duplex = \"half\";\n }\n\n const response = await fetch(url, init);\n res.statusCode = response.status;\n response.headers.forEach((value, name) => {\n if (name !== \"set-cookie\" && !HOP_BY_HOP.has(name)) {\n res.setHeader(name, value);\n }\n });\n const cookies = response.headers.getSetCookie();\n if (cookies.length > 0) {\n res.setHeader(\"set-cookie\", cookies);\n }\n\n if (response.body === null) {\n res.end();\n return;\n }\n await pipeline(Readable.fromWeb(response.body as never), res);\n } catch (error) {\n next(error);\n }\n}\n\n/**\n * Dev server: Vite (transform + HMR) sebagai front, proxy ke server SSR.\n * Modul halaman/manifest dimuat lewat `vite.ssrLoadModule` agar SSR ikut segar saat file berubah\n * (tanpa restart); perubahan di `src/routes` meregenerasi entry client + full-reload browser.\n */\nexport async function createDevServer(options: DevServerOptions): Promise<DevServer> {\n const appDir = options.appDir;\n const entryDir = join(appDir, \".ramonyx\");\n const entryFile = join(entryDir, ENTRY_FILE);\n const routesDir = join(appDir, \"src\", \"routes\");\n\n const devtools = createDevtoolsCollector();\n const clientDir = join(appDir, \"dist\", \"client\");\n\n /** Ukuran bundel/aset dari manifest build (bila sudah pernah dibangun). */\n const refreshAssets = async (): Promise<void> => {\n const manifest = await readAssetManifest(clientDir);\n if (manifest === null) {\n devtools.setAssets([]);\n return;\n }\n const names = new Set<string>([manifest.client, ...manifest.files]);\n const entries: { name: string; bytes: number }[] = [];\n for (const name of names) {\n try {\n const info = await stat(join(clientDir, name));\n entries.push({ name, bytes: info.size });\n } catch {\n // berkas hilang (mis. dibersihkan) — lewati\n }\n }\n if (manifest.assets !== undefined) {\n for (const entry of Object.values(manifest.assets)) {\n entries.push({ name: entry.file, bytes: entry.bytes });\n }\n }\n devtools.setAssets(entries);\n };\n\n const vite = await createServer({\n configFile: false,\n root: appDir,\n appType: \"custom\",\n logLevel: \"info\",\n esbuild: { jsx: \"automatic\" },\n server: { port: options.port ?? 5173 },\n plugins: [\n ...(options.plugins ?? []),\n serverOnlyPlugin(),\n assetsDevPlugin(appDir),\n {\n name: \"ramonyx-devtools\",\n configureServer(server) {\n server.middlewares.use((req, res, next) => {\n const path = (req.url ?? \"\").split(\"?\")[0] ?? \"\";\n if (path === \"/__ramonyx/devtools\") {\n res.setHeader(\"content-type\", \"text/html; charset=utf-8\");\n res.setHeader(\"cache-control\", \"no-store\");\n res.end(devtoolsPanelHtml());\n return;\n }\n if (path === \"/__ramonyx/data\") {\n void refreshAssets()\n .catch(() => undefined)\n .then(() => {\n res.setHeader(\"content-type\", \"application/json; charset=utf-8\");\n res.setHeader(\"cache-control\", \"no-store\");\n res.end(JSON.stringify(devtools.snapshot()));\n });\n return;\n }\n next();\n });\n },\n },\n {\n name: \"ramonyx-ssr-proxy\",\n configureServer(server) {\n server.middlewares.use((req, res, next) => {\n if (req.url === undefined || isViteRequest(req.url)) {\n next();\n return;\n }\n void proxyToSsr(options.target, req, res, next);\n });\n },\n },\n ],\n });\n\n const importer: ModuleImporter = async (absolute, label) => {\n try {\n return (await vite.ssrLoadModule(absolute)) as Record<string, unknown>;\n } catch (error) {\n throw new RouteManifestError(\n `[dev] gagal memuat ${label} (${relative(appDir, absolute) || absolute}) via Vite: ${\n error instanceof Error ? error.message : String(error)\n }`,\n { cause: error },\n );\n }\n };\n\n const regenerateEntry = async (): Promise<void> => {\n const pages = await loadPageRoutes({ dir: routesDir, importer });\n devtools.setRoutes(\n pages.map((page) => ({\n path: page.path,\n ...(page.pageFile !== undefined ? { file: relative(appDir, page.pageFile) } : {}),\n })),\n );\n await mkdir(entryDir, { recursive: true });\n await writeFile(entryFile, generateClientEntry(pages, { from: entryDir }));\n };\n await regenerateEntry();\n\n vite.watcher.on(\"all\", (_event, file) => {\n if (!file.startsWith(routesDir)) {\n return;\n }\n void regenerateEntry()\n .catch(() => undefined)\n .finally(() => {\n vite.ws.send({ type: \"full-reload\" });\n });\n });\n\n await vite.listen();\n const address = vite.httpServer?.address();\n const port =\n typeof address === \"object\" && address !== null ? address.port : (options.port ?? 5173);\n\n return {\n url: `http://localhost:${port}`,\n importer,\n target: options.target,\n devtools,\n close: () => vite.close(),\n };\n}\n"]}
1
+ {"version":3,"file":"dev.js","sourceRoot":"","sources":["../../../packages/bundler/src/dev.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync } from \"node:fs\";\nimport { mkdir, readFile, stat, writeFile } from \"node:fs/promises\";\nimport { createServer as createHttpServer, type ServerResponse } from \"node:http\";\nimport { extname, join, relative } from \"node:path\";\nimport { Readable } from \"node:stream\";\nimport { pipeline } from \"node:stream/promises\";\nimport { createNodeRequestListener } from \"@ramonyx/adapter-runtime\";\nimport {\n generateClientEntry,\n listApiRoutes,\n loadPageRoutes,\n type ModuleImporter,\n RouteManifestError,\n} from \"@ramonyx/compiler\";\nimport type { FetchHandler } from \"@ramonyx/core\";\nimport { type Connect, createServer, type Plugin, type PluginOption } from \"vite\";\nimport { createDevtoolsCollector, type DevtoolsCollector, devtoolsPanelHtml } from \"./devtools.ts\";\nimport { readAssetManifest } from \"./manifest.ts\";\nimport { serverOnlyPlugin } from \"./server-only.ts\";\n\nexport interface DevServerOptions {\n readonly appDir: string;\n /**\n * Mode terintegrasi: factory handler SSR. Vite berjalan sebagai middleware di server yang\n * sama; `target` tidak dipakai.\n */\n readonly handler?: (context: {\n readonly importer: ModuleImporter;\n readonly devtools: DevtoolsCollector;\n }) => FetchHandler | Promise<FetchHandler>;\n /** Mode proxy lama: URL server SSR terpisah (mis. \"http://localhost:3000\"). */\n readonly target?: string;\n /** Port HTTP server (mode terintegrasi: port publik/dev). */\n readonly port?: number;\n /** Interface bind; default localhost. */\n readonly host?: string;\n /** Plugin Vite tambahan (dari plugin Ramonyx — ADR-0067). */\n readonly plugins?: readonly PluginOption[];\n}\n\nexport interface DevServer {\n readonly url: string;\n /** Importer untuk compiler: modul mengikuti module graph Vite (invalidasi saat file berubah). */\n readonly importer: ModuleImporter;\n /** Hanya ada di mode proxy lama. */\n readonly target?: string;\n /** Kolektor panel devtools (ADR-0077) — pasang `hooks`/`middleware`-nya ke handler aplikasi. */\n readonly devtools: DevtoolsCollector;\n close(): Promise<void>;\n}\n\nconst ENTRY_FILE = \"client-entry.ts\";\n\nconst DEV_ASSET_TYPES: Record<string, string> = {\n \".svg\": \"image/svg+xml\",\n \".png\": \"image/png\",\n \".jpg\": \"image/jpeg\",\n \".jpeg\": \"image/jpeg\",\n \".gif\": \"image/gif\",\n \".webp\": \"image/webp\",\n \".avif\": \"image/avif\",\n \".ico\": \"image/x-icon\",\n \".woff\": \"font/woff\",\n \".woff2\": \"font/woff2\",\n \".ttf\": \"font/ttf\",\n \".otf\": \"font/otf\",\n};\n\n/**\n * Dev: sajikan `src/assets/**` apa adanya di `/assets/*` (tanpa hash) — paritas dengan\n * resolver aset dev (`createAssetResolver(..., { fallbackToPath: true })`, ADR-0069).\n */\nfunction assetsDevPlugin(appDir: string): Plugin {\n return {\n name: \"ramonyx-assets-dev\",\n configureServer(server) {\n server.middlewares.use((req, res, next) => {\n const url = req.url ?? \"\";\n if (!url.startsWith(\"/assets/\")) {\n next();\n return;\n }\n let rel: string;\n try {\n rel = decodeURIComponent(url.slice(\"/assets/\".length).split(\"?\")[0] ?? \"\");\n } catch {\n next();\n return;\n }\n if (rel.length === 0 || rel.includes(\"..\")) {\n next();\n return;\n }\n const file = join(appDir, \"src\", \"assets\", rel);\n if (!existsSync(file)) {\n next();\n return;\n }\n void readFile(file)\n .then((data) => {\n res.setHeader(\n \"content-type\",\n DEV_ASSET_TYPES[extname(file).toLowerCase()] ?? \"application/octet-stream\",\n );\n res.setHeader(\"cache-control\", \"no-cache\");\n res.end(data);\n })\n .catch(() => next());\n });\n },\n };\n}\n\n/** Path yang dimiliki Vite (jangan diproksikan ke SSR). */\nfunction isViteRequest(url: string): boolean {\n const path = url.split(\"?\")[0] ?? \"\";\n return (\n path.startsWith(\"/@\") ||\n path.startsWith(\"/node_modules/\") ||\n path.startsWith(\"/.ramonyx/\") ||\n path.startsWith(\"/src/\") ||\n path === \"/favicon.ico\"\n );\n}\n\nconst HOP_BY_HOP = new Set([\n \"connection\",\n \"keep-alive\",\n \"transfer-encoding\",\n \"content-length\",\n \"content-encoding\",\n]);\n\n/**\n * Proxy manual ke SSR (bukan `server.proxy` Vite): request non-Vite diteruskan dengan `fetch`,\n * respons dipipe kembali apa adanya. Body request non-GET/HEAD dialirkan via `Readable.toWeb`.\n */\nasync function proxyToSsr(\n target: string,\n req: Connect.IncomingMessage,\n res: ServerResponse,\n next: Connect.NextFunction,\n): Promise<void> {\n try {\n const url = new URL(req.url ?? \"/\", target);\n const headers = new Headers();\n for (const [name, value] of Object.entries(req.headers)) {\n if (value === undefined) continue;\n if (Array.isArray(value)) {\n for (const item of value) {\n headers.append(name, item);\n }\n } else {\n headers.set(name, value);\n }\n }\n // Tandai request yang datang lewat Vite. SSR langsung di port 3000 tidak punya entry klien.\n headers.set(\"x-ramonyx-dev-proxy\", \"1\");\n\n const method = req.method ?? \"GET\";\n const init: RequestInit & { duplex?: \"half\" } = {\n method,\n headers,\n redirect: \"manual\",\n };\n if (method !== \"GET\" && method !== \"HEAD\") {\n init.body = Readable.toWeb(req) as unknown as BodyInit;\n init.duplex = \"half\";\n }\n\n const response = await fetch(url, init);\n res.statusCode = response.status;\n response.headers.forEach((value, name) => {\n if (name !== \"set-cookie\" && !HOP_BY_HOP.has(name)) {\n res.setHeader(name, value);\n }\n });\n const cookies = response.headers.getSetCookie();\n if (cookies.length > 0) {\n res.setHeader(\"set-cookie\", cookies);\n }\n\n if (response.body === null) {\n res.end();\n return;\n }\n await pipeline(Readable.fromWeb(response.body as never), res);\n } catch (error) {\n next(error);\n }\n}\n\n/** Mode proxy lama: Vite dan SSR berjalan di port berbeda. */\nfunction ssrProxyPlugin(target: string): Plugin {\n return {\n name: \"ramonyx-ssr-proxy\",\n configureServer(server) {\n server.middlewares.use((req, res, next) => {\n if (req.url === undefined || isViteRequest(req.url)) {\n next();\n return;\n }\n void proxyToSsr(target, req, res, next);\n });\n },\n };\n}\n\n/**\n * Dev server: Vite (transform + HMR) dan SSR.\n *\n * - Mode terintegrasi (`handler`): satu HTTP server/port; Vite middleware + HMR WebSocket\n * menempel ke server yang sama, SSR menjadi middleware terakhir.\n * - Mode proxy lama (`target`): Vite port terpisah memproksikan request ke SSR.\n *\n * Modul halaman/manifest dimuat lewat `vite.ssrLoadModule` agar SSR ikut segar saat file berubah\n * (tanpa restart); perubahan di `src/routes` meregenerasi entry client + full-reload browser.\n */\nexport async function createDevServer(options: DevServerOptions): Promise<DevServer> {\n const appDir = options.appDir;\n const entryDir = join(appDir, \".ramonyx\");\n const entryFile = join(entryDir, ENTRY_FILE);\n const routesDir = join(appDir, \"src\", \"routes\");\n\n const devtools = createDevtoolsCollector();\n const clientDir = join(appDir, \"dist\", \"client\");\n\n /** Ukuran bundel/aset dari manifest build (bila sudah pernah dibangun). */\n const refreshAssets = async (): Promise<void> => {\n const manifest = await readAssetManifest(clientDir);\n if (manifest === null) {\n devtools.setAssets([]);\n return;\n }\n const names = new Set<string>([manifest.client, ...manifest.files]);\n const entries: { name: string; bytes: number }[] = [];\n for (const name of names) {\n try {\n const info = await stat(join(clientDir, name));\n entries.push({ name, bytes: info.size });\n } catch {\n // berkas hilang (mis. dibersihkan) — lewati\n }\n }\n if (manifest.assets !== undefined) {\n for (const entry of Object.values(manifest.assets)) {\n entries.push({ name: entry.file, bytes: entry.bytes });\n }\n }\n devtools.setAssets(entries);\n };\n\n const integrated = options.handler !== undefined;\n const proxyTarget = options.target;\n if (!integrated && proxyTarget === undefined) {\n throw new Error(\n \"createDevServer: butuh `handler` (mode terintegrasi) atau `target` (mode proxy)\",\n );\n }\n const integratedServer = integrated ? createHttpServer() : undefined;\n const vite = await createServer({\n configFile: false,\n root: appDir,\n appType: \"custom\",\n logLevel: \"info\",\n esbuild: { jsx: \"automatic\" },\n server: integrated\n ? {\n middlewareMode: { server: integratedServer as ReturnType<typeof createHttpServer> },\n ws: { server: integratedServer as ReturnType<typeof createHttpServer> },\n }\n : { port: options.port ?? 5173 },\n plugins: [\n ...(options.plugins ?? []),\n serverOnlyPlugin(),\n assetsDevPlugin(appDir),\n {\n name: \"ramonyx-devtools\",\n configureServer(server) {\n server.middlewares.use((req, res, next) => {\n const path = (req.url ?? \"\").split(\"?\")[0] ?? \"\";\n if (path === \"/__ramonyx/devtools\") {\n res.setHeader(\"content-type\", \"text/html; charset=utf-8\");\n res.setHeader(\"cache-control\", \"no-store\");\n res.end(devtoolsPanelHtml());\n return;\n }\n if (path === \"/__ramonyx/data\") {\n void refreshAssets()\n .catch(() => undefined)\n .then(() => {\n res.setHeader(\"content-type\", \"application/json; charset=utf-8\");\n res.setHeader(\"cache-control\", \"no-store\");\n res.end(JSON.stringify(devtools.snapshot()));\n });\n return;\n }\n next();\n });\n },\n },\n ...(integrated ? [] : [ssrProxyPlugin(proxyTarget as string)]),\n ],\n });\n\n const importer: ModuleImporter = async (absolute, label) => {\n try {\n return (await vite.ssrLoadModule(absolute)) as Record<string, unknown>;\n } catch (error) {\n throw new RouteManifestError(\n `[dev] gagal memuat ${label} (${relative(appDir, absolute) || absolute}) via Vite: ${\n error instanceof Error ? error.message : String(error)\n }`,\n { cause: error },\n );\n }\n };\n\n const regenerateEntry = async (): Promise<void> => {\n const [pages, apiRoutes] = await Promise.all([\n loadPageRoutes({ dir: routesDir, importer }),\n listApiRoutes({ dir: routesDir, importer }),\n ]);\n devtools.setRoutes([\n ...pages.map((page) => ({\n path: page.path,\n kind: \"page\" as const,\n ...(page.pageFile !== undefined ? { file: relative(appDir, page.pageFile) } : {}),\n })),\n ...apiRoutes.map((route) => ({\n path: route.routePath,\n kind: \"api\" as const,\n methods: route.methods,\n file: relative(appDir, route.file),\n })),\n ]);\n await mkdir(entryDir, { recursive: true });\n await writeFile(entryFile, generateClientEntry(pages, { from: entryDir }));\n };\n await regenerateEntry();\n\n vite.watcher.on(\"all\", (_event, file) => {\n if (!file.startsWith(routesDir)) {\n return;\n }\n void regenerateEntry()\n .catch(() => undefined)\n .finally(() => {\n vite.ws.send({ type: \"full-reload\" });\n });\n });\n\n if (integrated) {\n const server = integratedServer as ReturnType<typeof createHttpServer>;\n const fetchHandler = await options.handler?.({ importer, devtools });\n if (fetchHandler === undefined) {\n throw new Error(\"createDevServer: handler tidak mengembalikan FetchHandler\");\n }\n // SSR adalah middleware terakhir: request yang tidak ditangani Vite/plugin masuk ke app.\n vite.middlewares.use(createNodeRequestListener(fetchHandler, { onError: console.error }));\n server.on(\"request\", (req, res) => {\n vite.middlewares(req, res, (error: unknown) => {\n if (error) {\n console.error(error);\n if (!res.headersSent) {\n res.writeHead(500, { \"content-type\": \"application/json\" });\n }\n res.end(JSON.stringify({ error: \"Internal Server Error\" }));\n return;\n }\n if (!res.headersSent) {\n res.writeHead(404, { \"content-type\": \"text/plain; charset=utf-8\" });\n }\n res.end(\"Not found\");\n });\n });\n\n await new Promise<void>((resolve, reject) => {\n server.once(\"error\", reject);\n server.listen(options.port ?? 3000, options.host ?? \"localhost\", () => {\n server.off(\"error\", reject);\n resolve();\n });\n });\n const address = server.address();\n const port =\n typeof address === \"object\" && address !== null ? address.port : (options.port ?? 3000);\n const host =\n options.host === undefined ||\n options.host === \"0.0.0.0\" ||\n options.host === \"::\" ||\n options.host === \"127.0.0.1\"\n ? \"localhost\"\n : options.host;\n\n return {\n url: `http://${host}:${port}`,\n importer,\n devtools,\n close: async () => {\n server.closeAllConnections();\n await new Promise<void>((resolve) => server.close(() => resolve()));\n await vite.close();\n },\n };\n }\n\n await vite.listen();\n const address = vite.httpServer?.address();\n const port =\n typeof address === \"object\" && address !== null ? address.port : (options.port ?? 5173);\n\n return {\n url: `http://localhost:${port}`,\n importer,\n target: options.target,\n devtools,\n close: () => vite.close(),\n };\n}\n"]}
@@ -2,6 +2,9 @@ import type { AppHooks, Middleware } from "@ramonyx/core";
2
2
  export interface DevtoolsRoute {
3
3
  readonly path: string;
4
4
  readonly file?: string;
5
+ /** `page` atau `api`; default page untuk kompatibilitas. */
6
+ readonly kind?: "page" | "api";
7
+ readonly methods?: readonly string[];
5
8
  }
6
9
  export interface DevtoolsAsset {
7
10
  readonly name: string;
@@ -34,9 +37,8 @@ export interface DevtoolsCollector {
34
37
  snapshot(): DevtoolsSnapshot;
35
38
  }
36
39
  /**
37
- * Kolektor devtools. Catatan kejujuran: korelasi loader/render → request memakai
38
- * "request terbuka terakhir dengan path sama" (event hook tidak membawa identitas request);
39
- * cukup untuk dev berkonkurensi rendah, didokumentasikan sebagai aproksimasi.
40
+ * Kolektor devtools. Korelasi loader/render → request memakai AsyncLocalStorage (Node dev),
41
+ * sehingga request konkuren dengan path sama tidak saling menimpa.
40
42
  */
41
43
  export declare function createDevtoolsCollector(): DevtoolsCollector;
42
44
  /** Halaman panel (HTML mandiri, tanpa build) — polling `/__ramonyx/data`. */
package/dist/devtools.js CHANGED
@@ -2,17 +2,18 @@
2
2
  // status payload hydration, ukuran bundel/aset. Ditenagai primitif yang sudah ada
3
3
  // (`AppHooks` ADR-0065 + middleware ADR-0067). **Tidak pernah aktif di produksi** — hanya
4
4
  // `createDevServer` yang membuatnya.
5
+ import { AsyncLocalStorage } from "node:async_hooks";
5
6
  const MAX_REQUESTS = 50;
6
7
  function round2(value) {
7
8
  return Math.round(value * 100) / 100;
8
9
  }
9
10
  /**
10
- * Kolektor devtools. Catatan kejujuran: korelasi loader/render → request memakai
11
- * "request terbuka terakhir dengan path sama" (event hook tidak membawa identitas request);
12
- * cukup untuk dev berkonkurensi rendah, didokumentasikan sebagai aproksimasi.
11
+ * Kolektor devtools. Korelasi loader/render → request memakai AsyncLocalStorage (Node dev),
12
+ * sehingga request konkuren dengan path sama tidak saling menimpa.
13
13
  */
14
14
  export function createDevtoolsCollector() {
15
15
  const requests = [];
16
+ const requestContext = new AsyncLocalStorage();
16
17
  let routes = [];
17
18
  let assets = [];
18
19
  const catat = (request) => {
@@ -37,33 +38,35 @@ export function createDevtoolsCollector() {
37
38
  path: new URL(context.request.url).pathname,
38
39
  };
39
40
  catat(record);
40
- const response = await next();
41
- record.status = response.status;
42
- record.ms = round2(performance.now() - mulai);
43
- const contentType = response.headers.get("content-type") ?? "";
44
- if (contentType.includes("text/html")) {
45
- try {
46
- const html = await response.clone().text();
47
- record.hydration = html.includes('id="ramonyx-payload"') ? "payload" : "none";
48
- }
49
- catch {
50
- // body tidak terbaca (streaming diputus) — biarkan undefined
41
+ return requestContext.run(record, async () => {
42
+ const response = await next();
43
+ record.status = response.status;
44
+ record.ms = round2(performance.now() - mulai);
45
+ const contentType = response.headers.get("content-type") ?? "";
46
+ if (contentType.includes("text/html")) {
47
+ try {
48
+ const html = await response.clone().text();
49
+ record.hydration = html.includes('id="ramonyx-payload"') ? "payload" : "none";
50
+ }
51
+ catch {
52
+ // body tidak terbaca (streaming diputus) — biarkan undefined
53
+ }
51
54
  }
52
- }
53
- return response;
55
+ return response;
56
+ });
54
57
  };
55
58
  return {
56
59
  middleware,
57
60
  hooks: {
58
61
  onLoaderEnd: (event) => {
59
- const record = terakhirDenganPath(event.path);
62
+ const record = requestContext.getStore() ?? terakhirDenganPath(event.path);
60
63
  if (record !== undefined) {
61
64
  record.loaderMs = round2(event.durationMs);
62
65
  record.cached = event.cached;
63
66
  }
64
67
  },
65
68
  onRenderEnd: (event) => {
66
- const record = terakhirDenganPath(event.path);
69
+ const record = requestContext.getStore() ?? terakhirDenganPath(event.path);
67
70
  if (record !== undefined) {
68
71
  record.renderMs = round2(event.durationMs);
69
72
  }
@@ -113,7 +116,7 @@ export function devtoolsPanelHtml() {
113
116
  <h2>Bundel & aset</h2>
114
117
  <table id="aset"><thead><tr><th>Nama</th><th>Ukuran</th></tr></thead><tbody></tbody></table>
115
118
  <h2>Rute</h2>
116
- <table id="rute"><thead><tr><th>Path</th><th>Berkas</th></tr></thead><tbody></tbody></table>
119
+ <table id="rute"><thead><tr><th>Jenis</th><th>Path</th><th>Method</th><th>Berkas</th></tr></thead><tbody></tbody></table>
117
120
  <h2>Request terakhir</h2>
118
121
  <table id="req"><thead><tr><th>Method</th><th>Path</th><th>Status</th><th>Total</th><th>Loader</th><th>Render</th><th>Cache</th><th>Hydration</th></tr></thead><tbody></tbody></table>
119
122
  </div>
@@ -127,7 +130,7 @@ async function segarkan() {
127
130
  .map((a) => "<tr><td><code>" + a.name + "</code></td><td>" + kb(a.bytes) + "</td></tr>")
128
131
  .join("");
129
132
  document.querySelector("#rute tbody").innerHTML = data.routes
130
- .map((r) => "<tr><td><code>" + r.path + "</code></td><td class='dim'>" + (r.file ?? "–") + "</td></tr>")
133
+ .map((r) => "<tr><td>" + (r.kind ?? "page") + "</td><td><code>" + r.path + "</code></td><td>" + ((r.methods ?? []).join(", ") || "GET") + "</td><td class='dim'>" + (r.file ?? "–") + "</td></tr>")
131
134
  .join("");
132
135
  document.querySelector("#req tbody").innerHTML = data.requests
133
136
  .map((r) => "<tr><td>" + r.method + "</td><td><code>" + r.path + "</code></td><td class='" + ((r.status ?? 0) >= 400 ? "bad" : "ok") + "'>" + (r.status ?? "…") + "</td><td>" + fmt(r.ms) + "</td><td>" + fmt(r.loaderMs) + "</td><td>" + fmt(r.renderMs) + "</td><td>" + (r.cached === true ? "hit" : r.cached === false ? "miss" : "–") + "</td><td>" + (r.hydration === "payload" ? "payload ✓" : r.hydration === "none" ? "tidak" : "–") + "</td></tr>")
@@ -1 +1 @@
1
- {"version":3,"file":"devtools.js","sourceRoot":"","sources":["../../../packages/bundler/src/devtools.ts"],"names":[],"mappings":"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","sourcesContent":["// Panel devtools (ADR-0077): kolektor data dev-only — rute, timing request/loader/render,\n// status payload hydration, ukuran bundel/aset. Ditenagai primitif yang sudah ada\n// (`AppHooks` ADR-0065 + middleware ADR-0067). **Tidak pernah aktif di produksi** — hanya\n// `createDevServer` yang membuatnya.\n\nimport type { AppHooks, Middleware } from \"@ramonyx/core\";\n\nexport interface DevtoolsRoute {\n readonly path: string;\n readonly file?: string;\n}\n\nexport interface DevtoolsAsset {\n readonly name: string;\n readonly bytes: number;\n}\n\nexport interface DevtoolsRequest {\n readonly method: string;\n readonly path: string;\n status?: number;\n ms?: number;\n loaderMs?: number;\n renderMs?: number;\n cached?: boolean;\n /** `payload` = HTML memuat payload hydration; `none` = tidak; undefined = bukan HTML. */\n hydration?: \"payload\" | \"none\";\n}\n\nexport interface DevtoolsSnapshot {\n readonly generatedAt: string;\n readonly routes: readonly DevtoolsRoute[];\n readonly assets: readonly DevtoolsAsset[];\n readonly requests: readonly DevtoolsRequest[];\n}\n\nexport interface DevtoolsCollector {\n /** Pasang ke `createHandler`/`createPageRoutes` (gabung `mergeHooks`). */\n readonly hooks: AppHooks;\n /** Bungkus handler (paling luar) agar semua request tercatat. */\n readonly middleware: Middleware;\n setRoutes(routes: readonly DevtoolsRoute[]): void;\n setAssets(assets: readonly DevtoolsAsset[]): void;\n snapshot(): DevtoolsSnapshot;\n}\n\nconst MAX_REQUESTS = 50;\n\nfunction round2(value: number): number {\n return Math.round(value * 100) / 100;\n}\n\n/**\n * Kolektor devtools. Catatan kejujuran: korelasi loader/render → request memakai\n * \"request terbuka terakhir dengan path sama\" (event hook tidak membawa identitas request);\n * cukup untuk dev berkonkurensi rendah, didokumentasikan sebagai aproksimasi.\n */\nexport function createDevtoolsCollector(): DevtoolsCollector {\n const requests: DevtoolsRequest[] = [];\n let routes: readonly DevtoolsRoute[] = [];\n let assets: readonly DevtoolsAsset[] = [];\n\n const catat = (request: DevtoolsRequest): void => {\n requests.push(request);\n if (requests.length > MAX_REQUESTS) {\n requests.shift();\n }\n };\n\n const terakhirDenganPath = (path: string): DevtoolsRequest | undefined => {\n for (let index = requests.length - 1; index >= 0; index -= 1) {\n const record = requests[index];\n if (record !== undefined && record.path === path && record.status === undefined) {\n return record;\n }\n }\n return undefined;\n };\n\n const middleware: Middleware = async (context, next) => {\n const mulai = performance.now();\n const record: DevtoolsRequest = {\n method: context.request.method,\n path: new URL(context.request.url).pathname,\n };\n catat(record);\n\n const response = await next();\n record.status = response.status;\n record.ms = round2(performance.now() - mulai);\n\n const contentType = response.headers.get(\"content-type\") ?? \"\";\n if (contentType.includes(\"text/html\")) {\n try {\n const html = await response.clone().text();\n record.hydration = html.includes('id=\"ramonyx-payload\"') ? \"payload\" : \"none\";\n } catch {\n // body tidak terbaca (streaming diputus) — biarkan undefined\n }\n }\n return response;\n };\n\n return {\n middleware,\n hooks: {\n onLoaderEnd: (event) => {\n const record = terakhirDenganPath(event.path);\n if (record !== undefined) {\n record.loaderMs = round2(event.durationMs);\n record.cached = event.cached;\n }\n },\n onRenderEnd: (event) => {\n const record = terakhirDenganPath(event.path);\n if (record !== undefined) {\n record.renderMs = round2(event.durationMs);\n }\n },\n },\n setRoutes(next) {\n routes = [...next];\n },\n setAssets(next) {\n assets = [...next];\n },\n snapshot() {\n return {\n generatedAt: new Date().toISOString(),\n routes,\n assets,\n requests: [...requests].reverse(),\n };\n },\n };\n}\n\n/** Halaman panel (HTML mandiri, tanpa build) — polling `/__ramonyx/data`. */\nexport function devtoolsPanelHtml(): string {\n return `<!doctype html>\n<html lang=\"id\">\n<head>\n<meta charset=\"utf-8\" />\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n<title>Ramonyx Devtools</title>\n<style>\n :root { --bg:#0f1115; --fg:#e6e8ee; --redup:#9aa2b1; --aksen:#7cc4ff; --garis:#262b36; }\n * { box-sizing: border-box; }\n body { margin:0; background:var(--bg); color:var(--fg); font:14px/1.5 ui-sans-serif, system-ui, sans-serif; }\n .shell { max-width: 1100px; margin: 0 auto; padding: 20px; }\n h1 { font-size: 20px; margin: 0 0 4px; } h2 { font-size: 14px; color: var(--redup); text-transform: uppercase; letter-spacing: .08em; margin: 24px 0 8px; }\n table { border-collapse: collapse; width: 100%; font-size: 13px; }\n th, td { border: 1px solid var(--garis); padding: 5px 8px; text-align: left; }\n th { color: var(--redup); font-weight: 500; }\n code { font-family: ui-monospace, monospace; color: var(--aksen); }\n .ok { color: #7ddc9a; } .bad { color: #ff8080; } .dim { color: var(--redup); }\n</style>\n</head>\n<body>\n<div class=\"shell\">\n <h1>Ramonyx Devtools <span class=\"dim\">(dev-only)</span></h1>\n <p class=\"dim\">Data dari <code>/__ramonyx/data</code> — rute, timing request/loader/render, hydration, ukuran bundel. Diperbarui tiap 2 dtk.</p>\n <h2>Bundel & aset</h2>\n <table id=\"aset\"><thead><tr><th>Nama</th><th>Ukuran</th></tr></thead><tbody></tbody></table>\n <h2>Rute</h2>\n <table id=\"rute\"><thead><tr><th>Path</th><th>Berkas</th></tr></thead><tbody></tbody></table>\n <h2>Request terakhir</h2>\n <table id=\"req\"><thead><tr><th>Method</th><th>Path</th><th>Status</th><th>Total</th><th>Loader</th><th>Render</th><th>Cache</th><th>Hydration</th></tr></thead><tbody></tbody></table>\n</div>\n<script>\nconst fmt = (n) => (typeof n === \"number\" ? n + \" ms\" : \"–\");\nconst kb = (n) => (n / 1024).toFixed(1) + \" KB\";\nasync function segarkan() {\n try {\n const data = await (await fetch(\"/__ramonyx/data\", { cache: \"no-store\" })).json();\n document.querySelector(\"#aset tbody\").innerHTML = data.assets\n .map((a) => \"<tr><td><code>\" + a.name + \"</code></td><td>\" + kb(a.bytes) + \"</td></tr>\")\n .join(\"\");\n document.querySelector(\"#rute tbody\").innerHTML = data.routes\n .map((r) => \"<tr><td><code>\" + r.path + \"</code></td><td class='dim'>\" + (r.file ?? \"–\") + \"</td></tr>\")\n .join(\"\");\n document.querySelector(\"#req tbody\").innerHTML = data.requests\n .map((r) => \"<tr><td>\" + r.method + \"</td><td><code>\" + r.path + \"</code></td><td class='\" + ((r.status ?? 0) >= 400 ? \"bad\" : \"ok\") + \"'>\" + (r.status ?? \"…\") + \"</td><td>\" + fmt(r.ms) + \"</td><td>\" + fmt(r.loaderMs) + \"</td><td>\" + fmt(r.renderMs) + \"</td><td>\" + (r.cached === true ? \"hit\" : r.cached === false ? \"miss\" : \"–\") + \"</td><td>\" + (r.hydration === \"payload\" ? \"payload ✓\" : r.hydration === \"none\" ? \"tidak\" : \"–\") + \"</td></tr>\")\n .join(\"\");\n } catch (error) {\n console.error(\"devtools: gagal memuat data\", error);\n }\n}\nsegarkan();\nsetInterval(segarkan, 2000);\n</script>\n</body>\n</html>\n`;\n}\n"]}
1
+ {"version":3,"file":"devtools.js","sourceRoot":"","sources":["../../../packages/bundler/src/devtools.ts"],"names":[],"mappings":"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","sourcesContent":["// Panel devtools (ADR-0077): kolektor data dev-only — rute, timing request/loader/render,\n// status payload hydration, ukuran bundel/aset. Ditenagai primitif yang sudah ada\n// (`AppHooks` ADR-0065 + middleware ADR-0067). **Tidak pernah aktif di produksi** — hanya\n// `createDevServer` yang membuatnya.\n\nimport { AsyncLocalStorage } from \"node:async_hooks\";\nimport type { AppHooks, Middleware } from \"@ramonyx/core\";\n\nexport interface DevtoolsRoute {\n readonly path: string;\n readonly file?: string;\n /** `page` atau `api`; default page untuk kompatibilitas. */\n readonly kind?: \"page\" | \"api\";\n readonly methods?: readonly string[];\n}\n\nexport interface DevtoolsAsset {\n readonly name: string;\n readonly bytes: number;\n}\n\nexport interface DevtoolsRequest {\n readonly method: string;\n readonly path: string;\n status?: number;\n ms?: number;\n loaderMs?: number;\n renderMs?: number;\n cached?: boolean;\n /** `payload` = HTML memuat payload hydration; `none` = tidak; undefined = bukan HTML. */\n hydration?: \"payload\" | \"none\";\n}\n\nexport interface DevtoolsSnapshot {\n readonly generatedAt: string;\n readonly routes: readonly DevtoolsRoute[];\n readonly assets: readonly DevtoolsAsset[];\n readonly requests: readonly DevtoolsRequest[];\n}\n\nexport interface DevtoolsCollector {\n /** Pasang ke `createHandler`/`createPageRoutes` (gabung `mergeHooks`). */\n readonly hooks: AppHooks;\n /** Bungkus handler (paling luar) agar semua request tercatat. */\n readonly middleware: Middleware;\n setRoutes(routes: readonly DevtoolsRoute[]): void;\n setAssets(assets: readonly DevtoolsAsset[]): void;\n snapshot(): DevtoolsSnapshot;\n}\n\nconst MAX_REQUESTS = 50;\n\nfunction round2(value: number): number {\n return Math.round(value * 100) / 100;\n}\n\n/**\n * Kolektor devtools. Korelasi loader/render → request memakai AsyncLocalStorage (Node dev),\n * sehingga request konkuren dengan path sama tidak saling menimpa.\n */\nexport function createDevtoolsCollector(): DevtoolsCollector {\n const requests: DevtoolsRequest[] = [];\n const requestContext = new AsyncLocalStorage<DevtoolsRequest>();\n let routes: readonly DevtoolsRoute[] = [];\n let assets: readonly DevtoolsAsset[] = [];\n\n const catat = (request: DevtoolsRequest): void => {\n requests.push(request);\n if (requests.length > MAX_REQUESTS) {\n requests.shift();\n }\n };\n\n const terakhirDenganPath = (path: string): DevtoolsRequest | undefined => {\n for (let index = requests.length - 1; index >= 0; index -= 1) {\n const record = requests[index];\n if (record !== undefined && record.path === path && record.status === undefined) {\n return record;\n }\n }\n return undefined;\n };\n\n const middleware: Middleware = async (context, next) => {\n const mulai = performance.now();\n const record: DevtoolsRequest = {\n method: context.request.method,\n path: new URL(context.request.url).pathname,\n };\n catat(record);\n\n return requestContext.run(record, async () => {\n const response = await next();\n record.status = response.status;\n record.ms = round2(performance.now() - mulai);\n\n const contentType = response.headers.get(\"content-type\") ?? \"\";\n if (contentType.includes(\"text/html\")) {\n try {\n const html = await response.clone().text();\n record.hydration = html.includes('id=\"ramonyx-payload\"') ? \"payload\" : \"none\";\n } catch {\n // body tidak terbaca (streaming diputus) — biarkan undefined\n }\n }\n return response;\n });\n };\n\n return {\n middleware,\n hooks: {\n onLoaderEnd: (event) => {\n const record = requestContext.getStore() ?? terakhirDenganPath(event.path);\n if (record !== undefined) {\n record.loaderMs = round2(event.durationMs);\n record.cached = event.cached;\n }\n },\n onRenderEnd: (event) => {\n const record = requestContext.getStore() ?? terakhirDenganPath(event.path);\n if (record !== undefined) {\n record.renderMs = round2(event.durationMs);\n }\n },\n },\n setRoutes(next) {\n routes = [...next];\n },\n setAssets(next) {\n assets = [...next];\n },\n snapshot() {\n return {\n generatedAt: new Date().toISOString(),\n routes,\n assets,\n requests: [...requests].reverse(),\n };\n },\n };\n}\n\n/** Halaman panel (HTML mandiri, tanpa build) — polling `/__ramonyx/data`. */\nexport function devtoolsPanelHtml(): string {\n return `<!doctype html>\n<html lang=\"id\">\n<head>\n<meta charset=\"utf-8\" />\n<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n<title>Ramonyx Devtools</title>\n<style>\n :root { --bg:#0f1115; --fg:#e6e8ee; --redup:#9aa2b1; --aksen:#7cc4ff; --garis:#262b36; }\n * { box-sizing: border-box; }\n body { margin:0; background:var(--bg); color:var(--fg); font:14px/1.5 ui-sans-serif, system-ui, sans-serif; }\n .shell { max-width: 1100px; margin: 0 auto; padding: 20px; }\n h1 { font-size: 20px; margin: 0 0 4px; } h2 { font-size: 14px; color: var(--redup); text-transform: uppercase; letter-spacing: .08em; margin: 24px 0 8px; }\n table { border-collapse: collapse; width: 100%; font-size: 13px; }\n th, td { border: 1px solid var(--garis); padding: 5px 8px; text-align: left; }\n th { color: var(--redup); font-weight: 500; }\n code { font-family: ui-monospace, monospace; color: var(--aksen); }\n .ok { color: #7ddc9a; } .bad { color: #ff8080; } .dim { color: var(--redup); }\n</style>\n</head>\n<body>\n<div class=\"shell\">\n <h1>Ramonyx Devtools <span class=\"dim\">(dev-only)</span></h1>\n <p class=\"dim\">Data dari <code>/__ramonyx/data</code> — rute, timing request/loader/render, hydration, ukuran bundel. Diperbarui tiap 2 dtk.</p>\n <h2>Bundel & aset</h2>\n <table id=\"aset\"><thead><tr><th>Nama</th><th>Ukuran</th></tr></thead><tbody></tbody></table>\n <h2>Rute</h2>\n <table id=\"rute\"><thead><tr><th>Jenis</th><th>Path</th><th>Method</th><th>Berkas</th></tr></thead><tbody></tbody></table>\n <h2>Request terakhir</h2>\n <table id=\"req\"><thead><tr><th>Method</th><th>Path</th><th>Status</th><th>Total</th><th>Loader</th><th>Render</th><th>Cache</th><th>Hydration</th></tr></thead><tbody></tbody></table>\n</div>\n<script>\nconst fmt = (n) => (typeof n === \"number\" ? n + \" ms\" : \"–\");\nconst kb = (n) => (n / 1024).toFixed(1) + \" KB\";\nasync function segarkan() {\n try {\n const data = await (await fetch(\"/__ramonyx/data\", { cache: \"no-store\" })).json();\n document.querySelector(\"#aset tbody\").innerHTML = data.assets\n .map((a) => \"<tr><td><code>\" + a.name + \"</code></td><td>\" + kb(a.bytes) + \"</td></tr>\")\n .join(\"\");\n document.querySelector(\"#rute tbody\").innerHTML = data.routes\n .map((r) => \"<tr><td>\" + (r.kind ?? \"page\") + \"</td><td><code>\" + r.path + \"</code></td><td>\" + ((r.methods ?? []).join(\", \") || \"GET\") + \"</td><td class='dim'>\" + (r.file ?? \"–\") + \"</td></tr>\")\n .join(\"\");\n document.querySelector(\"#req tbody\").innerHTML = data.requests\n .map((r) => \"<tr><td>\" + r.method + \"</td><td><code>\" + r.path + \"</code></td><td class='\" + ((r.status ?? 0) >= 400 ? \"bad\" : \"ok\") + \"'>\" + (r.status ?? \"…\") + \"</td><td>\" + fmt(r.ms) + \"</td><td>\" + fmt(r.loaderMs) + \"</td><td>\" + fmt(r.renderMs) + \"</td><td>\" + (r.cached === true ? \"hit\" : r.cached === false ? \"miss\" : \"–\") + \"</td><td>\" + (r.hydration === \"payload\" ? \"payload ✓\" : r.hydration === \"none\" ? \"tidak\" : \"–\") + \"</td></tr>\")\n .join(\"\");\n } catch (error) {\n console.error(\"devtools: gagal memuat data\", error);\n }\n}\nsegarkan();\nsetInterval(segarkan, 2000);\n</script>\n</body>\n</html>\n`;\n}\n"]}
@@ -17,6 +17,8 @@ export interface AssetEntry {
17
17
  /** Dimensi intrinsik (dari prosesor — ADR-0079); dipakai `Image` anti-CLS. */
18
18
  readonly width?: number;
19
19
  readonly height?: number;
20
+ /** Data URI blur/LQIP (plugin image) untuk background anti-CLS. */
21
+ readonly placeholder?: string;
20
22
  /** Varian resize/format (ADR-0079). */
21
23
  readonly variants?: readonly AssetVariantEntry[];
22
24
  }
@@ -1 +1 @@
1
- {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../../../packages/bundler/src/manifest.ts"],"names":[],"mappings":"AAAA,yFAAyF;AACzF,4FAA4F;AAE5F,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,wEAAwE;AACxE,MAAM,CAAC,MAAM,oBAAoB,GAAG,qBAAqB,CAAC;AAmC1D,kDAAkD;AAClD,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,YAAoB,EACpB,QAAuB;IAEvB,MAAM,SAAS,CACb,IAAI,CAAC,YAAY,EAAE,oBAAoB,CAAC,EACxC,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CACzC,CAAC;AACJ,CAAC;AAED,6DAA6D;AAC7D,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,SAAiB;IACvD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,oBAAoB,CAAC,EAAE,MAAM,CAAC,CAAC;QAC1E,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAA2B,CAAC;QACzD,IAAI,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACpE,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO;YACL,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE;YACtD,GAAG,CAAC,MAAM,CAAC,MAAM,KAAK,SAAS,IAAI,MAAM,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC5F,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC","sourcesContent":["// Manifest aset klien (`dist/client/ramonyx.assets.json`) — dipakai build, resolver aset\n// (ADR-0069), dan pembaca runtime (`readClientScript`). Dipisah agar tanpa circular import.\n\nimport { readFile, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\n/** Nama berkas manifest aset di direktori keluaran klien (ADR-0055). */\nexport const CLIENT_MANIFEST_FILE = \"ramonyx.assets.json\";\n\n/** Varian satu aset (hasil prosesor plugin — ADR-0079). */\nexport interface AssetVariantEntry {\n /** Path relatif terhadap direktori keluaran klien, mis. `assets/img/hero-640-ab12.webp`. */\n readonly file: string;\n readonly bytes: number;\n readonly width: number;\n readonly height?: number;\n /** Format: `webp`, `avif`, atau ekstensi asli. */\n readonly format: string;\n}\n\nexport interface AssetEntry {\n /** Path relatif terhadap direktori keluaran klien, mis. `assets/img/logo-abc123.png`. */\n readonly file: string;\n readonly bytes: number;\n /** Dimensi intrinsik (dari prosesor — ADR-0079); dipakai `Image` anti-CLS. */\n readonly width?: number;\n readonly height?: number;\n /** Varian resize/format (ADR-0079). */\n readonly variants?: readonly AssetVariantEntry[];\n}\n\nexport type AssetMap = Readonly<Record<string, AssetEntry>>;\n\n/** Manifest aset hasil build (`dist/client/ramonyx.assets.json`). */\nexport interface AssetManifest {\n /** Nama berkas bundle klien (relatif terhadap direktori keluaran), mis. `client-abc.js`. */\n readonly client: string;\n readonly files: readonly string[];\n /** Aset dari `src/assets/**` (key = path relatif posix) — ADR-0069. */\n readonly assets?: AssetMap;\n}\n\n/** Tulis manifest ke direktori keluaran klien. */\nexport async function writeAssetManifest(\n clientOutDir: string,\n manifest: AssetManifest,\n): Promise<void> {\n await writeFile(\n join(clientOutDir, CLIENT_MANIFEST_FILE),\n `${JSON.stringify(manifest, null, 2)}\\n`,\n );\n}\n\n/** Baca manifest aset; `null` bila belum pernah dibangun. */\nexport async function readAssetManifest(clientDir: string): Promise<AssetManifest | null> {\n try {\n const raw = await readFile(join(clientDir, CLIENT_MANIFEST_FILE), \"utf8\");\n const parsed = JSON.parse(raw) as Partial<AssetManifest>;\n if (typeof parsed.client !== \"string\" || parsed.client.length === 0) {\n return null;\n }\n return {\n client: parsed.client,\n files: Array.isArray(parsed.files) ? parsed.files : [],\n ...(parsed.assets !== undefined && parsed.assets !== null ? { assets: parsed.assets } : {}),\n };\n } catch {\n return null;\n }\n}\n"]}
1
+ {"version":3,"file":"manifest.js","sourceRoot":"","sources":["../../../packages/bundler/src/manifest.ts"],"names":[],"mappings":"AAAA,yFAAyF;AACzF,4FAA4F;AAE5F,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,wEAAwE;AACxE,MAAM,CAAC,MAAM,oBAAoB,GAAG,qBAAqB,CAAC;AAqC1D,kDAAkD;AAClD,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,YAAoB,EACpB,QAAuB;IAEvB,MAAM,SAAS,CACb,IAAI,CAAC,YAAY,EAAE,oBAAoB,CAAC,EACxC,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CACzC,CAAC;AACJ,CAAC;AAED,6DAA6D;AAC7D,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,SAAiB;IACvD,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,SAAS,EAAE,oBAAoB,CAAC,EAAE,MAAM,CAAC,CAAC;QAC1E,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAA2B,CAAC;QACzD,IAAI,OAAO,MAAM,CAAC,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACpE,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO;YACL,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE;YACtD,GAAG,CAAC,MAAM,CAAC,MAAM,KAAK,SAAS,IAAI,MAAM,CAAC,MAAM,KAAK,IAAI,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC5F,CAAC;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC","sourcesContent":["// Manifest aset klien (`dist/client/ramonyx.assets.json`) — dipakai build, resolver aset\n// (ADR-0069), dan pembaca runtime (`readClientScript`). Dipisah agar tanpa circular import.\n\nimport { readFile, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\n\n/** Nama berkas manifest aset di direktori keluaran klien (ADR-0055). */\nexport const CLIENT_MANIFEST_FILE = \"ramonyx.assets.json\";\n\n/** Varian satu aset (hasil prosesor plugin — ADR-0079). */\nexport interface AssetVariantEntry {\n /** Path relatif terhadap direktori keluaran klien, mis. `assets/img/hero-640-ab12.webp`. */\n readonly file: string;\n readonly bytes: number;\n readonly width: number;\n readonly height?: number;\n /** Format: `webp`, `avif`, atau ekstensi asli. */\n readonly format: string;\n}\n\nexport interface AssetEntry {\n /** Path relatif terhadap direktori keluaran klien, mis. `assets/img/logo-abc123.png`. */\n readonly file: string;\n readonly bytes: number;\n /** Dimensi intrinsik (dari prosesor — ADR-0079); dipakai `Image` anti-CLS. */\n readonly width?: number;\n readonly height?: number;\n /** Data URI blur/LQIP (plugin image) untuk background anti-CLS. */\n readonly placeholder?: string;\n /** Varian resize/format (ADR-0079). */\n readonly variants?: readonly AssetVariantEntry[];\n}\n\nexport type AssetMap = Readonly<Record<string, AssetEntry>>;\n\n/** Manifest aset hasil build (`dist/client/ramonyx.assets.json`). */\nexport interface AssetManifest {\n /** Nama berkas bundle klien (relatif terhadap direktori keluaran), mis. `client-abc.js`. */\n readonly client: string;\n readonly files: readonly string[];\n /** Aset dari `src/assets/**` (key = path relatif posix) — ADR-0069. */\n readonly assets?: AssetMap;\n}\n\n/** Tulis manifest ke direktori keluaran klien. */\nexport async function writeAssetManifest(\n clientOutDir: string,\n manifest: AssetManifest,\n): Promise<void> {\n await writeFile(\n join(clientOutDir, CLIENT_MANIFEST_FILE),\n `${JSON.stringify(manifest, null, 2)}\\n`,\n );\n}\n\n/** Baca manifest aset; `null` bila belum pernah dibangun. */\nexport async function readAssetManifest(clientDir: string): Promise<AssetManifest | null> {\n try {\n const raw = await readFile(join(clientDir, CLIENT_MANIFEST_FILE), \"utf8\");\n const parsed = JSON.parse(raw) as Partial<AssetManifest>;\n if (typeof parsed.client !== \"string\" || parsed.client.length === 0) {\n return null;\n }\n return {\n client: parsed.client,\n files: Array.isArray(parsed.files) ? parsed.files : [],\n ...(parsed.assets !== undefined && parsed.assets !== null ? { assets: parsed.assets } : {}),\n };\n } catch {\n return null;\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ramonyx/bundler",
3
- "version": "1.0.0",
3
+ "version": "1.2.0",
4
4
  "description": "Build pipeline for Ramonyx: Vite client bundles, edge builds, static prerender, and plugin hooks.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -19,8 +19,9 @@
19
19
  "node": ">=24"
20
20
  },
21
21
  "dependencies": {
22
- "@ramonyx/compiler": "^1.0.0",
23
- "@ramonyx/core": "^1.0.0",
22
+ "@ramonyx/adapter-runtime": "^1.2.0",
23
+ "@ramonyx/compiler": "^1.2.0",
24
+ "@ramonyx/core": "^1.2.0",
24
25
  "vite": "8.3.4"
25
26
  },
26
27
  "publishConfig": {