@ramonyx/bundler 0.2.0 → 0.4.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/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Paket framework Ramonyx.
4
4
 
5
- > **Alpha (0.2.0)** — framework Node.js ini adalah baseline produksi;
5
+ > **Alpha (0.4.0)** — framework Node.js ini adalah baseline produksi;
6
6
  > runtime Rust/V8 bersifat eksperimental. Butuh **Node.js ≥ 24**.
7
7
 
8
8
  ## Instalasi
package/dist/assets.d.ts CHANGED
@@ -1,23 +1,59 @@
1
- import type { AssetResolver } from "@ramonyx/core";
1
+ import { type AssetInfo, type AssetResolver } from "@ramonyx/core";
2
2
  import { type AssetManifest, type AssetMap } from "./manifest.js";
3
3
  export type { AssetEntry, AssetMap } from "./manifest.js";
4
4
  /** Direktori aset default (relatif appDir) — dipindai otomatis bila ada. */
5
5
  export declare const DEFAULT_ASSETS_DIR = "src/assets";
6
+ /** Input prosesor: satu berkas `src/assets/**` (path relatif posix + isi). */
7
+ export interface AssetProcessorInput {
8
+ readonly path: string;
9
+ readonly data: Uint8Array;
10
+ }
11
+ /** Berkas turunan yang dihasilkan prosesor (mis. varian gambar ter-resize). */
12
+ export interface AssetProcessorOutput {
13
+ /** Path relatif terhadap `src/assets` (mis. `img/hero-640.webp`) — bundler menambah hash konten. */
14
+ readonly path: string;
15
+ readonly data: Uint8Array;
16
+ /** Lebar varian (dipakai `srcset`). */
17
+ readonly width: number;
18
+ readonly height?: number;
19
+ /** Format berkas; default dari ekstensi `path`. */
20
+ readonly format?: string;
21
+ }
22
+ /** Hasil prosesor: metadata entri asli + berkas turunan. */
23
+ export interface AssetProcessorResult {
24
+ readonly meta?: {
25
+ readonly width?: number;
26
+ readonly height?: number;
27
+ };
28
+ readonly outputs?: readonly AssetProcessorOutput[];
29
+ }
30
+ export type AssetProcessor = (asset: AssetProcessorInput) => AssetProcessorResult | undefined | Promise<AssetProcessorResult | undefined>;
31
+ export interface CollectAssetsOptions {
32
+ /** Prosesor plugin (dijalankan berurutan per berkas — ADR-0079). */
33
+ readonly processors?: readonly AssetProcessor[];
34
+ }
6
35
  export interface CollectAssetsResult {
7
36
  readonly assets: AssetMap;
8
37
  readonly files: readonly string[];
9
38
  }
10
39
  /**
11
- * Salin aset dari `assetsDir` ke `<clientOutDir>/assets/**` dengan nama ber-hash konten.
12
- * Key manifest = path relatif posix terhadap `assetsDir` (mis. `img/logo.png`).
40
+ * Salin aset dari `assetsDir` ke `<clientOutDir>/**` dengan nama ber-hash konten.
41
+ * Key manifest = path relatif posix terhadap `assetsDir` (mis. `img/logo.png`);
42
+ * `file` = path relatif terhadap keluaran klien → URL publik = `<prefix>/<file>` (ADR-0079).
43
+ * Prosesor (bila ada) dapat menambah varian + metadata dimensi (ADR-0079).
13
44
  */
14
- export declare function collectAssets(assetsDir: string, clientOutDir: string): Promise<CollectAssetsResult>;
45
+ export declare function collectAssets(assetsDir: string, clientOutDir: string, options?: CollectAssetsOptions): Promise<CollectAssetsResult>;
15
46
  export interface AssetUrlOptions {
16
47
  /** Prefix URL publik; default `/assets`. */
17
48
  readonly urlPrefix?: string;
18
49
  }
19
50
  /** URL publik entri aset; `undefined` bila path tidak ada di manifest. */
20
51
  export declare function assetUrl(manifest: AssetManifest | null, path: string, options?: AssetUrlOptions): string | undefined;
52
+ /**
53
+ * Manifest → peta `AssetInfo` (URL + dimensi + varian) untuk resolver: dipakai
54
+ * `createAssetResolver` (Node) dan artefak edge yang meng-inline peta (ADR-0069/0079).
55
+ */
56
+ export declare function assetInfoMap(manifest: AssetManifest | null, options?: AssetUrlOptions): Record<string, AssetInfo>;
21
57
  export interface CreateAssetResolverOptions extends AssetUrlOptions {
22
58
  /**
23
59
  * Fallback ke `${prefix}/<path>` (tanpa hash) bila manifest belum ada/tidak memuat entri —
@@ -27,7 +63,8 @@ export interface CreateAssetResolverOptions extends AssetUrlOptions {
27
63
  }
28
64
  /**
29
65
  * Resolver aset untuk server (dipakai loader lewat `createPageRoutes({ assets })`):
30
- * membaca manifest sekali; di dev pakai `fallbackToPath: true` (Vite menyajikan file mentah).
66
+ * membaca manifest sekali; di dev pakai `fallbackToPath: true` (Vite menyajikan file mentah;
67
+ * tanpa varian — ADR-0079).
31
68
  */
32
69
  export declare function createAssetResolver(clientDir: string, options?: CreateAssetResolverOptions): Promise<AssetResolver>;
33
70
  /** Direktori aset ada? (dipakai buildClient untuk memindai `src/assets` secara otomatis). */
package/dist/assets.js CHANGED
@@ -1,12 +1,14 @@
1
- // Pipeline aset (ADR-0069, M1 — tanpa dependency): pindai `src/assets/**`, salin ke
2
- // `dist/client/assets/**` dengan **hash konten**, catat di `ramonyx.assets.json`, dan sediakan
3
- // resolver untuk server (`assetUrl`/`createAssetResolver`).
1
+ // Pipeline aset (ADR-0069 M1, varian ADR-0079 M2 — tanpa dependency): pindai `src/assets/**`,
2
+ // salin ke **root keluaran klien** (`dist/client/**`) dengan **hash konten** (URL = `file`, ADR-0079),
3
+ // jalankan prosesor plugin (mis. varian gambar dari `@ramonyx/plugin-image`), catat semuanya di
4
+ // `ramonyx.assets.json`, dan sediakan resolver (`assetUrl`/`createAssetResolver`/`assetInfoMap`).
4
5
  //
5
6
  // Catatan desain: Node SSR tidak dapat `import foto.png` (hanya Vite/klien yang bisa). Karena itu
6
7
  // M1 memakai **manifest + helper** (bukan loader kustom) — paritas SSR ↔ klien tanpa mesin baru.
7
8
  import { createHash } from "node:crypto";
8
- import { copyFile, mkdir, readdir, readFile, stat } from "node:fs/promises";
9
+ import { copyFile, mkdir, readdir, readFile, stat, writeFile } from "node:fs/promises";
9
10
  import { dirname, extname, join, posix, relative, sep } from "node:path";
11
+ import { createAssetResolverFromMap, } from "@ramonyx/core";
10
12
  import { readAssetManifest, } from "./manifest.js";
11
13
  /** Direktori aset default (relatif appDir) — dipindai otomatis bila ada. */
12
14
  export const DEFAULT_ASSETS_DIR = "src/assets";
@@ -26,28 +28,88 @@ async function collectFiles(dir, found = []) {
26
28
  }
27
29
  return found;
28
30
  }
31
+ function contentHash(data) {
32
+ return createHash("sha256").update(data).digest("hex").slice(0, 10);
33
+ }
29
34
  /**
30
- * Salin aset dari `assetsDir` ke `<clientOutDir>/assets/**` dengan nama ber-hash konten.
31
- * Key manifest = path relatif posix terhadap `assetsDir` (mis. `img/logo.png`).
35
+ * `img/hero.png` + hash → `img/hero-<hash>.png` (relatif direktori keluaran klien).
36
+ *
37
+ * Catatan (koreksi M1, ADR-0079): berkas ditulis **di root keluaran klien** — konsisten dengan
38
+ * URL `<prefix>/<path>` dan middleware `serveStatic({ dir: dist/client, prefix: "/assets" })`.
39
+ * Layout M1 (`dist/client/assets/**`) tidak pernah dapat disajikan (URL → root), tertangkap
40
+ * oleh test HTTP aset di create-app.
32
41
  */
33
- export async function collectAssets(assetsDir, clientOutDir) {
42
+ function hashedTarget(rel, hash) {
43
+ const ext = extname(rel);
44
+ const tanpaExt = rel.slice(0, rel.length - ext.length);
45
+ return `${tanpaExt}-${hash}${ext}`;
46
+ }
47
+ /**
48
+ * Salin aset dari `assetsDir` ke `<clientOutDir>/**` dengan nama ber-hash konten.
49
+ * Key manifest = path relatif posix terhadap `assetsDir` (mis. `img/logo.png`);
50
+ * `file` = path relatif terhadap keluaran klien → URL publik = `<prefix>/<file>` (ADR-0079).
51
+ * Prosesor (bila ada) dapat menambah varian + metadata dimensi (ADR-0079).
52
+ */
53
+ export async function collectAssets(assetsDir, clientOutDir, options = {}) {
34
54
  const files = await collectFiles(assetsDir);
35
55
  const assets = {};
36
- const copied = [];
56
+ const copied = new Set();
37
57
  for (const file of files) {
38
58
  const data = await readFile(file);
39
- const hash = createHash("sha256").update(data).digest("hex").slice(0, 10);
40
59
  const rel = relative(assetsDir, file).split(sep).join(posix.sep);
41
- const ext = extname(rel);
42
- const tanpaExt = rel.slice(0, rel.length - ext.length);
43
- const targetRel = posix.join("assets", `${tanpaExt}-${hash}${ext}`);
60
+ let meta = {};
61
+ const outputs = [];
62
+ for (const processor of options.processors ?? []) {
63
+ const hasil = await processor({ path: rel, data });
64
+ if (hasil === undefined) {
65
+ continue;
66
+ }
67
+ if (hasil.meta !== undefined) {
68
+ meta = { ...meta, ...hasil.meta };
69
+ }
70
+ if (hasil.outputs !== undefined) {
71
+ outputs.push(...hasil.outputs);
72
+ }
73
+ }
74
+ const targetRel = hashedTarget(rel, contentHash(data));
44
75
  const target = join(clientOutDir, targetRel);
45
76
  await mkdir(dirname(target), { recursive: true });
46
77
  await copyFile(file, target);
47
- assets[rel] = { file: targetRel, bytes: data.byteLength };
48
- copied.push(targetRel);
78
+ copied.add(targetRel);
79
+ const variants = [];
80
+ for (const output of outputs) {
81
+ const outputRel = hashedTarget(output.path, contentHash(output.data));
82
+ const outputTarget = join(clientOutDir, outputRel);
83
+ await mkdir(dirname(outputTarget), { recursive: true });
84
+ await writeFile(outputTarget, output.data);
85
+ copied.add(outputRel);
86
+ variants.push({
87
+ file: outputRel,
88
+ bytes: output.data.byteLength,
89
+ width: output.width,
90
+ ...(output.height !== undefined ? { height: output.height } : {}),
91
+ format: (output.format ?? extname(output.path).slice(1)).toLowerCase(),
92
+ });
93
+ }
94
+ assets[rel] = {
95
+ file: targetRel,
96
+ bytes: data.byteLength,
97
+ ...(meta.width !== undefined ? { width: meta.width } : {}),
98
+ ...(meta.height !== undefined ? { height: meta.height } : {}),
99
+ ...(variants.length > 0 ? { variants } : {}),
100
+ };
49
101
  }
50
- return { assets, files: copied };
102
+ return { assets, files: [...copied] };
103
+ }
104
+ function normalizeAssetPath(path) {
105
+ return path
106
+ .replace(/^\.?\//, "")
107
+ .split(sep)
108
+ .join(posix.sep);
109
+ }
110
+ /** Berkas keluaran (relatif clientOutDir) → URL publik: `<prefix>/<file>` (1:1 — ADR-0079). */
111
+ function fileToUrl(file, prefix) {
112
+ return `${prefix}/${file}`;
51
113
  }
52
114
  /** URL publik entri aset; `undefined` bila path tidak ada di manifest. */
53
115
  export function assetUrl(manifest, path, options = {}) {
@@ -55,28 +117,47 @@ export function assetUrl(manifest, path, options = {}) {
55
117
  if (entry === undefined) {
56
118
  return undefined;
57
119
  }
58
- const prefix = (options.urlPrefix ?? "/assets").replace(/\/$/, "");
59
- const tanpaPrefix = entry.file.startsWith("assets/")
60
- ? entry.file.slice("assets/".length)
61
- : entry.file;
62
- return `${prefix}/${tanpaPrefix}`;
120
+ return fileToUrl(entry.file, (options.urlPrefix ?? "/assets").replace(/\/$/, ""));
63
121
  }
64
- function normalizeAssetPath(path) {
65
- return path
66
- .replace(/^\.?\//, "")
67
- .split(sep)
68
- .join(posix.sep);
122
+ /**
123
+ * Manifest → peta `AssetInfo` (URL + dimensi + varian) untuk resolver: dipakai
124
+ * `createAssetResolver` (Node) dan artefak edge yang meng-inline peta (ADR-0069/0079).
125
+ */
126
+ export function assetInfoMap(manifest, options = {}) {
127
+ const prefix = (options.urlPrefix ?? "/assets").replace(/\/$/, "");
128
+ const map = {};
129
+ for (const [path, entry] of Object.entries(manifest?.assets ?? {})) {
130
+ map[path] = {
131
+ url: fileToUrl(entry.file, prefix),
132
+ ...(entry.width !== undefined ? { width: entry.width } : {}),
133
+ ...(entry.height !== undefined ? { height: entry.height } : {}),
134
+ ...(entry.variants !== undefined
135
+ ? {
136
+ variants: entry.variants.map((variant) => ({
137
+ url: fileToUrl(variant.file, prefix),
138
+ width: variant.width,
139
+ ...(variant.height !== undefined ? { height: variant.height } : {}),
140
+ format: variant.format,
141
+ bytes: variant.bytes,
142
+ })),
143
+ }
144
+ : {}),
145
+ };
146
+ }
147
+ return map;
69
148
  }
70
149
  /**
71
150
  * Resolver aset untuk server (dipakai loader lewat `createPageRoutes({ assets })`):
72
- * membaca manifest sekali; di dev pakai `fallbackToPath: true` (Vite menyajikan file mentah).
151
+ * membaca manifest sekali; di dev pakai `fallbackToPath: true` (Vite menyajikan file mentah;
152
+ * tanpa varian — ADR-0079).
73
153
  */
74
154
  export async function createAssetResolver(clientDir, options = {}) {
75
155
  const manifest = await readAssetManifest(clientDir);
76
156
  const prefix = (options.urlPrefix ?? "/assets").replace(/\/$/, "");
157
+ const base = createAssetResolverFromMap(assetInfoMap(manifest, { urlPrefix: prefix }));
77
158
  return {
78
159
  url(path) {
79
- const found = assetUrl(manifest, path, { urlPrefix: prefix });
160
+ const found = base.url(path);
80
161
  if (found !== undefined) {
81
162
  return found;
82
163
  }
@@ -85,6 +166,17 @@ export async function createAssetResolver(clientDir, options = {}) {
85
166
  }
86
167
  return undefined;
87
168
  },
169
+ image(path) {
170
+ const found = base.image(path);
171
+ if (found !== undefined) {
172
+ return found;
173
+ }
174
+ if (options.fallbackToPath === true) {
175
+ // Dev: berkas disajikan apa adanya (tanpa hash/varian/dimensi).
176
+ return { src: `${prefix}/${normalizeAssetPath(path)}` };
177
+ }
178
+ return undefined;
179
+ },
88
180
  };
89
181
  }
90
182
  /** Direktori aset ada? (dipakai buildClient untuk memindai `src/assets` secara otomatis). */
@@ -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 — tanpa dependency): pindai `src/assets/**`, salin ke\n// `dist/client/assets/**` dengan **hash konten**, catat di `ramonyx.assets.json`, dan sediakan\n// resolver untuk server (`assetUrl`/`createAssetResolver`).\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 } from \"node:fs/promises\";\nimport { dirname, extname, join, posix, relative, sep } from \"node:path\";\nimport type { AssetResolver } from \"@ramonyx/core\";\nimport {\n type AssetEntry,\n type AssetManifest,\n type AssetMap,\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\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\nexport interface CollectAssetsResult {\n readonly assets: AssetMap;\n readonly files: readonly string[];\n}\n\n/**\n * Salin aset dari `assetsDir` ke `<clientOutDir>/assets/**` dengan nama ber-hash konten.\n * Key manifest = path relatif posix terhadap `assetsDir` (mis. `img/logo.png`).\n */\nexport async function collectAssets(\n assetsDir: string,\n clientOutDir: string,\n): Promise<CollectAssetsResult> {\n const files = await collectFiles(assetsDir);\n const assets: Record<string, AssetEntry> = {};\n const copied: string[] = [];\n\n for (const file of files) {\n const data = await readFile(file);\n const hash = createHash(\"sha256\").update(data).digest(\"hex\").slice(0, 10);\n const rel = relative(assetsDir, file).split(sep).join(posix.sep);\n const ext = extname(rel);\n const tanpaExt = rel.slice(0, rel.length - ext.length);\n const targetRel = posix.join(\"assets\", `${tanpaExt}-${hash}${ext}`);\n const target = join(clientOutDir, targetRel);\n\n await mkdir(dirname(target), { recursive: true });\n await copyFile(file, target);\n\n assets[rel] = { file: targetRel, bytes: data.byteLength };\n copied.push(targetRel);\n }\n\n return { assets, files: copied };\n}\n\nexport interface AssetUrlOptions {\n /** Prefix URL publik; default `/assets`. */\n readonly urlPrefix?: string;\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 const prefix = (options.urlPrefix ?? \"/assets\").replace(/\\/$/, \"\");\n const tanpaPrefix = entry.file.startsWith(\"assets/\")\n ? entry.file.slice(\"assets/\".length)\n : entry.file;\n return `${prefix}/${tanpaPrefix}`;\n}\n\nfunction normalizeAssetPath(path: string): string {\n return path\n .replace(/^\\.?\\//, \"\")\n .split(sep)\n .join(posix.sep);\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 */\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\n return {\n url(path: string): string | undefined {\n const found = assetUrl(manifest, path, { urlPrefix: prefix });\n if (found !== undefined) {\n return found;\n }\n if (options.fallbackToPath === true) {\n return `${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?: { 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"]}
package/dist/build.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { type LoadedPage } from "@ramonyx/core";
2
2
  import { type PluginOption } from "vite";
3
+ import { type AssetProcessor } from "./assets.js";
3
4
  export type { AssetEntry, AssetManifest, AssetMap } from "./manifest.js";
4
5
  export { CLIENT_MANIFEST_FILE, readAssetManifest } from "./manifest.js";
5
6
  export interface BuildClientOptions {
@@ -14,6 +15,8 @@ export interface BuildClientOptions {
14
15
  readonly minify?: boolean;
15
16
  /** Plugin Vite tambahan (dari plugin Ramonyx — ADR-0067). */
16
17
  readonly plugins?: readonly PluginOption[];
18
+ /** Prosesor aset (hook `assets` plugin — ADR-0079). */
19
+ readonly assetProcessors?: readonly AssetProcessor[];
17
20
  /**
18
21
  * Direktori aset (relatif appDir) yang dipindai & disalin ber-hash ke `assets/**` +
19
22
  * dicatat di manifest (ADR-0069). Default `src/assets` bila ada; `false` mematikan.
package/dist/build.js CHANGED
@@ -111,7 +111,9 @@ export async function buildClient(options) {
111
111
  : join(options.appDir, options.assetsDir ?? DEFAULT_ASSETS_DIR);
112
112
  let assets = {};
113
113
  if (assetsDir !== null && existsSync(assetsDir)) {
114
- const collected = await collectAssets(assetsDir, outDir);
114
+ const collected = await collectAssets(assetsDir, outDir, {
115
+ processors: options.assetProcessors,
116
+ });
115
117
  assets = collected.assets;
116
118
  }
117
119
  const manifest = {
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 { 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 /**\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 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":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AACvE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,mBAAmB,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAE,SAAS,EAAmB,MAAM,eAAe,CAAC;AAC3D,OAAO,EAAE,KAAK,EAAqB,MAAM,MAAM,CAAC;AAChD,OAAO,EAAuB,aAAa,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACrF,OAAO,EAEL,oBAAoB,EACpB,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAGpD,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAExE,MAAM,kBAAkB,GAAG,4DAA4D,CAAC;AACxF,MAAM,uBAAuB,GAAG,yBAAyB,CAAC;AAgC1D;;;;;;GAMG;AACH,qGAAqG;AACrG,KAAK,UAAU,4BAA4B,CAAC,KAA4B;IACtE,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAChC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,IAAI,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;YAChC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC3B,CAAC;QACD,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,WAAW,IAAI,EAAE,EAAE,CAAC;YAC5C,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACpB,CAAC;IACH,CAAC;IAED,MAAM,UAAU,GAAa,EAAE,CAAC;IAChC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,MAAc,CAAC;QACnB,IAAI,CAAC;YACH,MAAM,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACxC,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;QACD,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,CAAC;YACxD,UAAU,CAAC,IAAI,CAAC,GAAG,IAAI,MAAM,KAAK,CAAC,CAAC,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC;QAClD,CAAC;IACH,CAAC;IAED,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CACb,iGAAiG;YAC/F,UAAU,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;YACpD,kGAAkG;YAClG,+BAA+B,SAAS,CAAC,aAAa,CAAC,EAAE,CAC5D,CAAC;IACJ,CAAC;AACH,CAAC;AAED,mHAAmH;AACnH,KAAK,UAAU,uBAAuB,CAAC,MAAc;IACnD,MAAM,KAAK,GAAG,CAAC,MAAM,OAAO,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;IAClG,MAAM,UAAU,GAAG,IAAI,GAAG,EAAU,CAAC;IACrC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,CAAC;QAC1D,IAAI,MAAM,CAAC,QAAQ,CAAC,uBAAuB,CAAC,EAAE,CAAC;YAC7C,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACvB,CAAC;QACD,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,QAAQ,CAAC,kBAAkB,CAAC,EAAE,CAAC;YACxD,UAAU,CAAC,GAAG,CAAC,GAAG,IAAI,MAAM,KAAK,CAAC,CAAC,CAAC,IAAI,GAAG,EAAE,CAAC,CAAC;QACjD,CAAC;IACH,CAAC;IAED,IAAI,UAAU,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CACb,6FAA6F;YAC3F,CAAC,GAAG,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;YACzD,2FAA2F,SAAS,CAAC,aAAa,CAAC,EAAE,CACxH,CAAC;IACJ,CAAC;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,OAA2B;IAC3D,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,aAAa,CAAC,CAAC;IACrE,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IAClD,MAAM,SAAS,GAAG,IAAI,CAAC,QAAQ,EAAE,iBAAiB,CAAC,CAAC;IAEpD,MAAM,4BAA4B,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;IAElD,MAAM,KAAK,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC3C,MAAM,SAAS,CAAC,SAAS,EAAE,mBAAmB,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC;IAEnF,MAAM,KAAK,CAAC;QACV,UAAU,EAAE,KAAK;QACjB,IAAI,EAAE,OAAO,CAAC,MAAM;QACpB,QAAQ,EAAE,MAAM;QAChB,OAAO,EAAE,EAAE,GAAG,EAAE,WAAW,EAAE;QAC7B,OAAO,EAAE,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC,EAAE,gBAAgB,EAAE,CAAC;QACzD,KAAK,EAAE;YACL,MAAM;YACN,WAAW,EAAE,IAAI;YACjB,MAAM,EAAE,QAAQ;YAChB,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,KAAK;YAC/B,gFAAgF;YAChF,2DAA2D;YAC3D,SAAS,EAAE,OAAO,CAAC,SAAS,IAAI,IAAI;YACpC,aAAa,EAAE,KAAK;YACpB,aAAa,EAAE;gBACb,KAAK,EAAE,SAAS;gBAChB,MAAM,EAAE;oBACN,cAAc,EAAE,kBAAkB;oBAClC,cAAc,EAAE,yBAAyB;oBACzC,cAAc,EAAE,wBAAwB;iBACzC;aACF;SACF;KACF,CAAC,CAAC;IAEH,MAAM,uBAAuB,CAAC,MAAM,CAAC,CAAC;IAEtC,MAAM,KAAK,GAAG,CAAC,MAAM,OAAO,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,MAAM,CAC/D,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,oBAAoB,CAAC,CAC/C,CAAC;IACF,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IACtE,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CAAC,sDAAsD,MAAM,EAAE,CAAC,CAAC;IAClF,CAAC;IAED,yFAAyF;IACzF,MAAM,SAAS,GACb,OAAO,CAAC,SAAS,KAAK,KAAK;QACzB,CAAC,CAAC,IAAI;QACN,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,SAAS,IAAI,kBAAkB,CAAC,CAAC;IACpE,IAAI,MAAM,GAAwD,EAAE,CAAC;IACrE,IAAI,SAAS,KAAK,IAAI,IAAI,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAChD,MAAM,SAAS,GAAG,MAAM,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE;YACvD,UAAU,EAAE,OAAO,CAAC,eAAe;SACpC,CAAC,CAAC;QACH,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC;IAC5B,CAAC;IAED,MAAM,QAAQ,GAAkB;QAC9B,MAAM,EAAE,UAAU;QAClB,KAAK,EAAE,CAAC,GAAG,KAAK,CAAC,CAAC,IAAI,EAAE;QACxB,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACtD,CAAC;IACF,MAAM,kBAAkB,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAE3C,MAAM,MAAM,GAAG,CAAC,OAAO,CAAC,eAAe,IAAI,SAAS,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACzE,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,MAAM,IAAI,UAAU,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC;AAC7F,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,SAAiB,EACjB,OAAO,GAA2B,EAAE;IAEpC,MAAM,MAAM,GAAG,CAAC,OAAO,CAAC,SAAS,IAAI,SAAS,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;IACnE,MAAM,QAAQ,GAAG,MAAM,iBAAiB,CAAC,SAAS,CAAC,CAAC;IACpD,OAAO,QAAQ,KAAK,IAAI,CAAC,CAAC,CAAC,GAAG,MAAM,YAAY,CAAC,CAAC,CAAC,GAAG,MAAM,IAAI,QAAQ,CAAC,MAAM,EAAE,CAAC;AACpF,CAAC","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"]}
package/dist/edge.js CHANGED
@@ -9,7 +9,7 @@ import { join, resolve } from "node:path";
9
9
  import { generateServerEntry, listApiRoutes, loadPageRoutes } from "@ramonyx/compiler";
10
10
  import { errorHint } from "@ramonyx/core";
11
11
  import { build } from "vite";
12
- import { assetUrl } from "./assets.js";
12
+ import { assetInfoMap } from "./assets.js";
13
13
  import { readClientScript } from "./build.js";
14
14
  import { readAssetManifest } from "./manifest.js";
15
15
  const NODE_IMPORT = /["'](node:[a-z0-9_/-]+)["']/g;
@@ -36,15 +36,9 @@ export async function buildEdge(options) {
36
36
  const middlewareFile = existsSync(middlewareCandidate) ? middlewareCandidate : undefined;
37
37
  const clientDir = join(appDir, "dist/client");
38
38
  const clientScript = options.clientScript ?? (await readClientScript(clientDir));
39
- // Peta aset di-inline (artefak edge tanpa fs — ADR-0069).
39
+ // Peta aset di-inline (artefak edge tanpa fs — ADR-0069): URL + dimensi + varian (ADR-0079).
40
40
  const manifest = await readAssetManifest(clientDir);
41
- const assets = {};
42
- for (const path of Object.keys(manifest?.assets ?? {})) {
43
- const url = assetUrl(manifest, path);
44
- if (url !== undefined) {
45
- assets[path] = url;
46
- }
47
- }
41
+ const assets = assetInfoMap(manifest);
48
42
  await mkdir(entryDir, { recursive: true });
49
43
  await writeFile(entryFile, generateServerEntry({
50
44
  from: entryDir,
package/dist/edge.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"edge.js","sourceRoot":"","sources":["../../../packages/bundler/src/edge.ts"],"names":[],"mappings":"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","sourcesContent":["// Build target edge/serverless (ADR-0058): bundel SSR satu berkas dari entry yang digenerate\n// (impor statis, tanpa `node:fs`) + guard agar tidak ada API Node yang lolos.\n//\n// Hasil: `<outDir>/index.js` — ESM dengan `export default { fetch(request) }`,\n// kontrak Cloudflare Workers / Vercel Edge / Deno Deploy / runtime fetch standar.\n\nimport { existsSync } from \"node:fs\";\nimport { mkdir, readFile, stat, writeFile } from \"node:fs/promises\";\nimport { join, resolve } from \"node:path\";\nimport { generateServerEntry, listApiRoutes, loadPageRoutes } from \"@ramonyx/compiler\";\nimport { errorHint } from \"@ramonyx/core\";\nimport { build, type PluginOption } from \"vite\";\nimport { assetUrl } from \"./assets.ts\";\nimport { readClientScript } from \"./build.ts\";\nimport { readAssetManifest } from \"./manifest.ts\";\n\nexport interface BuildEdgeOptions {\n readonly appDir: string;\n /** Relatif terhadap appDir; default \"dist/edge\". */\n readonly outDir?: string;\n /** URL bundle klien (default: manifest `dist/client`, fallback `/assets/client.js`). */\n readonly clientScript?: string;\n /** Aktifkan SSR streaming di artefak; default true (renderer React mendukung). */\n readonly streaming?: boolean;\n readonly minify?: boolean;\n /** Plugin Vite tambahan (harus edge-safe — ADR-0067). */\n readonly plugins?: readonly PluginOption[];\n}\n\nexport interface BuildEdgeResult {\n readonly outDir: string;\n readonly entryFile: string;\n readonly outputFile: string;\n readonly bytes: number;\n}\n\nconst NODE_IMPORT = /[\"'](node:[a-z0-9_/-]+)[\"']/g;\n\n/**\n * Membangun artefak edge dari aplikasi Ramonyx.\n *\n * Manifest rute dibangun saat build (impor statis hasil codegen) sehingga artefak tidak\n * menyentuh filesystem saat runtime; seluruh dependency dibundel (`ssr.noExternal`),\n * `process.env.NODE_ENV` di-define sebagai `\"production\"`, dan hasilnya diperiksa:\n * impor `node:*` yang lolos = error (runtime edge tidak menyediakannya).\n */\nexport async function buildEdge(options: BuildEdgeOptions): Promise<BuildEdgeResult> {\n // Path absolut: entry Vite diresolusi relatif terhadap `root`, path relatif akan tergandakan.\n const appDir = resolve(options.appDir);\n const routesDir = join(appDir, \"src/routes\");\n const outDir = resolve(appDir, options.outDir ?? \"dist/edge\");\n const entryDir = join(appDir, \".ramonyx\");\n const entryFile = join(entryDir, \"edge-entry.ts\");\n\n const [pages, apiRoutes] = await Promise.all([\n loadPageRoutes({ dir: routesDir }),\n listApiRoutes({ dir: routesDir }),\n ]);\n\n const middlewareCandidate = join(routesDir, \"middleware.ts\");\n const middlewareFile = existsSync(middlewareCandidate) ? middlewareCandidate : undefined;\n const clientDir = join(appDir, \"dist/client\");\n const clientScript = options.clientScript ?? (await readClientScript(clientDir));\n\n // Peta aset di-inline (artefak edge tanpa fs — ADR-0069).\n const manifest = await readAssetManifest(clientDir);\n const assets: Record<string, string> = {};\n for (const path of Object.keys(manifest?.assets ?? {})) {\n const url = assetUrl(manifest, path);\n if (url !== undefined) {\n assets[path] = url;\n }\n }\n\n await mkdir(entryDir, { recursive: true });\n await writeFile(\n entryFile,\n generateServerEntry({\n from: entryDir,\n pages,\n apiRoutes,\n middlewareFile,\n clientScript,\n streaming: options.streaming ?? true,\n assets,\n }),\n );\n\n await build({\n configFile: false,\n root: appDir,\n logLevel: \"warn\",\n esbuild: { jsx: \"automatic\" },\n plugins: [...(options.plugins ?? [])],\n // `*.server.ts` tetap nyata di edge (server) — plugin stub hanya untuk bundle klien.\n // Runtime edge tidak punya `process` — define agar React memakai build produksi.\n define: { \"process.env.NODE_ENV\": JSON.stringify(\"production\") },\n ssr: { noExternal: true },\n build: {\n outDir,\n emptyOutDir: true,\n target: \"es2022\",\n minify: options.minify ?? true,\n ssr: entryFile,\n rollupOptions: {\n output: { entryFileNames: \"index.js\", format: \"esm\", codeSplitting: false },\n },\n },\n });\n\n const outputFile = join(outDir, \"index.js\");\n const bundle = await readFile(outputFile, \"utf8\");\n const nodeImports = [\n ...new Set([...bundle.matchAll(NODE_IMPORT)].map((match) => match[1] ?? \"\")),\n ];\n if (nodeImports.length > 0) {\n throw new Error(\n `bundel edge memuat API Node (${nodeImports.join(\", \")}) — periksa middleware/kode aplikasi ` +\n `yang memakai \\`node:*\\` (mis. \\`guardedFetch\\` dari @ramonyx/security/ssrf). ${errorHint(\"RMX-EDGE-1\")}`,\n );\n }\n\n const info = await stat(outputFile);\n return { outDir, entryFile, outputFile, bytes: info.size };\n}\n"]}
1
+ {"version":3,"file":"edge.js","sourceRoot":"","sources":["../../../packages/bundler/src/edge.ts"],"names":[],"mappings":"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","sourcesContent":["// Build target edge/serverless (ADR-0058): bundel SSR satu berkas dari entry yang digenerate\n// (impor statis, tanpa `node:fs`) + guard agar tidak ada API Node yang lolos.\n//\n// Hasil: `<outDir>/index.js` — ESM dengan `export default { fetch(request) }`,\n// kontrak Cloudflare Workers / Vercel Edge / Deno Deploy / runtime fetch standar.\n\nimport { existsSync } from \"node:fs\";\nimport { mkdir, readFile, stat, writeFile } from \"node:fs/promises\";\nimport { join, resolve } from \"node:path\";\nimport { generateServerEntry, listApiRoutes, loadPageRoutes } from \"@ramonyx/compiler\";\nimport { errorHint } from \"@ramonyx/core\";\nimport { build, type PluginOption } from \"vite\";\nimport { assetInfoMap } from \"./assets.ts\";\nimport { readClientScript } from \"./build.ts\";\nimport { readAssetManifest } from \"./manifest.ts\";\n\nexport interface BuildEdgeOptions {\n readonly appDir: string;\n /** Relatif terhadap appDir; default \"dist/edge\". */\n readonly outDir?: string;\n /** URL bundle klien (default: manifest `dist/client`, fallback `/assets/client.js`). */\n readonly clientScript?: string;\n /** Aktifkan SSR streaming di artefak; default true (renderer React mendukung). */\n readonly streaming?: boolean;\n readonly minify?: boolean;\n /** Plugin Vite tambahan (harus edge-safe — ADR-0067). */\n readonly plugins?: readonly PluginOption[];\n}\n\nexport interface BuildEdgeResult {\n readonly outDir: string;\n readonly entryFile: string;\n readonly outputFile: string;\n readonly bytes: number;\n}\n\nconst NODE_IMPORT = /[\"'](node:[a-z0-9_/-]+)[\"']/g;\n\n/**\n * Membangun artefak edge dari aplikasi Ramonyx.\n *\n * Manifest rute dibangun saat build (impor statis hasil codegen) sehingga artefak tidak\n * menyentuh filesystem saat runtime; seluruh dependency dibundel (`ssr.noExternal`),\n * `process.env.NODE_ENV` di-define sebagai `\"production\"`, dan hasilnya diperiksa:\n * impor `node:*` yang lolos = error (runtime edge tidak menyediakannya).\n */\nexport async function buildEdge(options: BuildEdgeOptions): Promise<BuildEdgeResult> {\n // Path absolut: entry Vite diresolusi relatif terhadap `root`, path relatif akan tergandakan.\n const appDir = resolve(options.appDir);\n const routesDir = join(appDir, \"src/routes\");\n const outDir = resolve(appDir, options.outDir ?? \"dist/edge\");\n const entryDir = join(appDir, \".ramonyx\");\n const entryFile = join(entryDir, \"edge-entry.ts\");\n\n const [pages, apiRoutes] = await Promise.all([\n loadPageRoutes({ dir: routesDir }),\n listApiRoutes({ dir: routesDir }),\n ]);\n\n const middlewareCandidate = join(routesDir, \"middleware.ts\");\n const middlewareFile = existsSync(middlewareCandidate) ? middlewareCandidate : undefined;\n const clientDir = join(appDir, \"dist/client\");\n const clientScript = options.clientScript ?? (await readClientScript(clientDir));\n\n // Peta aset di-inline (artefak edge tanpa fs — ADR-0069): URL + dimensi + varian (ADR-0079).\n const manifest = await readAssetManifest(clientDir);\n const assets = assetInfoMap(manifest);\n\n await mkdir(entryDir, { recursive: true });\n await writeFile(\n entryFile,\n generateServerEntry({\n from: entryDir,\n pages,\n apiRoutes,\n middlewareFile,\n clientScript,\n streaming: options.streaming ?? true,\n assets,\n }),\n );\n\n await build({\n configFile: false,\n root: appDir,\n logLevel: \"warn\",\n esbuild: { jsx: \"automatic\" },\n plugins: [...(options.plugins ?? [])],\n // `*.server.ts` tetap nyata di edge (server) — plugin stub hanya untuk bundle klien.\n // Runtime edge tidak punya `process` — define agar React memakai build produksi.\n define: { \"process.env.NODE_ENV\": JSON.stringify(\"production\") },\n ssr: { noExternal: true },\n build: {\n outDir,\n emptyOutDir: true,\n target: \"es2022\",\n minify: options.minify ?? true,\n ssr: entryFile,\n rollupOptions: {\n output: { entryFileNames: \"index.js\", format: \"esm\", codeSplitting: false },\n },\n },\n });\n\n const outputFile = join(outDir, \"index.js\");\n const bundle = await readFile(outputFile, \"utf8\");\n const nodeImports = [\n ...new Set([...bundle.matchAll(NODE_IMPORT)].map((match) => match[1] ?? \"\")),\n ];\n if (nodeImports.length > 0) {\n throw new Error(\n `bundel edge memuat API Node (${nodeImports.join(\", \")}) — periksa middleware/kode aplikasi ` +\n `yang memakai \\`node:*\\` (mis. \\`guardedFetch\\` dari @ramonyx/security/ssrf). ${errorHint(\"RMX-EDGE-1\")}`,\n );\n }\n\n const info = await stat(outputFile);\n return { outDir, entryFile, outputFile, bytes: info.size };\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- export type { AssetUrlOptions, CollectAssetsResult, CreateAssetResolverOptions, } from "./assets.js";
2
- export { assetsDirExists, assetUrl, collectAssets, createAssetResolver, DEFAULT_ASSETS_DIR, fontPreloadTag, } from "./assets.js";
1
+ export type { AssetProcessor, AssetProcessorInput, AssetProcessorOutput, AssetProcessorResult, AssetUrlOptions, CollectAssetsOptions, CollectAssetsResult, CreateAssetResolverOptions, } from "./assets.js";
2
+ export { assetInfoMap, assetsDirExists, assetUrl, collectAssets, createAssetResolver, DEFAULT_ASSETS_DIR, fontPreloadTag, } from "./assets.js";
3
3
  export type { BuildClientOptions, BuildClientResult } from "./build.js";
4
4
  export { buildClient, readClientScript } from "./build.js";
5
5
  export type { DevServer, DevServerOptions } from "./dev.js";
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- export { assetsDirExists, assetUrl, collectAssets, createAssetResolver, DEFAULT_ASSETS_DIR, fontPreloadTag, } from "./assets.js";
1
+ export { assetInfoMap, assetsDirExists, assetUrl, collectAssets, createAssetResolver, DEFAULT_ASSETS_DIR, fontPreloadTag, } from "./assets.js";
2
2
  export { buildClient, readClientScript } from "./build.js";
3
3
  export { createDevServer } from "./dev.js";
4
4
  export { buildEdge } from "./edge.js";
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../packages/bundler/src/index.ts"],"names":[],"mappings":"AAKA,OAAO,EACL,eAAe,EACf,QAAQ,EACR,aAAa,EACb,mBAAmB,EACnB,kBAAkB,EAClB,cAAc,GACf,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE3D,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAE3C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAE5F,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAMxD,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC","sourcesContent":["export type {\n AssetUrlOptions,\n CollectAssetsResult,\n CreateAssetResolverOptions,\n} from \"./assets.ts\";\nexport {\n assetsDirExists,\n assetUrl,\n collectAssets,\n createAssetResolver,\n DEFAULT_ASSETS_DIR,\n fontPreloadTag,\n} from \"./assets.ts\";\nexport type { BuildClientOptions, BuildClientResult } from \"./build.ts\";\nexport { buildClient, readClientScript } from \"./build.ts\";\nexport type { DevServer, DevServerOptions } from \"./dev.ts\";\nexport { createDevServer } from \"./dev.ts\";\nexport type { BuildEdgeOptions, BuildEdgeResult } from \"./edge.ts\";\nexport { buildEdge } from \"./edge.ts\";\nexport type { AssetEntry, AssetManifest, AssetMap } from \"./manifest.ts\";\nexport { CLIENT_MANIFEST_FILE, readAssetManifest, writeAssetManifest } from \"./manifest.ts\";\nexport type { LoadedPlugins, PluginContext, RamonyxPlugin } from \"./plugin.ts\";\nexport { definePlugin, loadPlugins } from \"./plugin.ts\";\nexport type {\n PrerenderOptions,\n PrerenderResult,\n PrerenderRouteInfo,\n} from \"./prerender.ts\";\nexport { prerenderRoutes } from \"./prerender.ts\";\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../packages/bundler/src/index.ts"],"names":[],"mappings":"AAUA,OAAO,EACL,YAAY,EACZ,eAAe,EACf,QAAQ,EACR,aAAa,EACb,mBAAmB,EACnB,kBAAkB,EAClB,cAAc,GACf,MAAM,aAAa,CAAC;AAErB,OAAO,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE3D,OAAO,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAE3C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,OAAO,EAAE,oBAAoB,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAE5F,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAMxD,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC","sourcesContent":["export type {\n AssetProcessor,\n AssetProcessorInput,\n AssetProcessorOutput,\n AssetProcessorResult,\n AssetUrlOptions,\n CollectAssetsOptions,\n CollectAssetsResult,\n CreateAssetResolverOptions,\n} from \"./assets.ts\";\nexport {\n assetInfoMap,\n assetsDirExists,\n assetUrl,\n collectAssets,\n createAssetResolver,\n DEFAULT_ASSETS_DIR,\n fontPreloadTag,\n} from \"./assets.ts\";\nexport type { BuildClientOptions, BuildClientResult } from \"./build.ts\";\nexport { buildClient, readClientScript } from \"./build.ts\";\nexport type { DevServer, DevServerOptions } from \"./dev.ts\";\nexport { createDevServer } from \"./dev.ts\";\nexport type { BuildEdgeOptions, BuildEdgeResult } from \"./edge.ts\";\nexport { buildEdge } from \"./edge.ts\";\nexport type { AssetEntry, AssetManifest, AssetMap } from \"./manifest.ts\";\nexport { CLIENT_MANIFEST_FILE, readAssetManifest, writeAssetManifest } from \"./manifest.ts\";\nexport type { LoadedPlugins, PluginContext, RamonyxPlugin } from \"./plugin.ts\";\nexport { definePlugin, loadPlugins } from \"./plugin.ts\";\nexport type {\n PrerenderOptions,\n PrerenderResult,\n PrerenderRouteInfo,\n} from \"./prerender.ts\";\nexport { prerenderRoutes } from \"./prerender.ts\";\n"]}
@@ -1,9 +1,24 @@
1
1
  /** Nama berkas manifest aset di direktori keluaran klien (ADR-0055). */
2
2
  export declare const CLIENT_MANIFEST_FILE = "ramonyx.assets.json";
3
+ /** Varian satu aset (hasil prosesor plugin — ADR-0079). */
4
+ export interface AssetVariantEntry {
5
+ /** Path relatif terhadap direktori keluaran klien, mis. `assets/img/hero-640-ab12.webp`. */
6
+ readonly file: string;
7
+ readonly bytes: number;
8
+ readonly width: number;
9
+ readonly height?: number;
10
+ /** Format: `webp`, `avif`, atau ekstensi asli. */
11
+ readonly format: string;
12
+ }
3
13
  export interface AssetEntry {
4
14
  /** Path relatif terhadap direktori keluaran klien, mis. `assets/img/logo-abc123.png`. */
5
15
  readonly file: string;
6
16
  readonly bytes: number;
17
+ /** Dimensi intrinsik (dari prosesor — ADR-0079); dipakai `Image` anti-CLS. */
18
+ readonly width?: number;
19
+ readonly height?: number;
20
+ /** Varian resize/format (ADR-0079). */
21
+ readonly variants?: readonly AssetVariantEntry[];
7
22
  }
8
23
  export type AssetMap = Readonly<Record<string, AssetEntry>>;
9
24
  /** Manifest aset hasil build (`dist/client/ramonyx.assets.json`). */
@@ -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;AAmB1D,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\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}\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;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"]}
package/dist/plugin.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { type AppHooks, type Logger, type Middleware, type RamonyxConfig } from "@ramonyx/core";
2
2
  import type { PluginOption } from "vite";
3
+ import type { AssetProcessor, AssetProcessorInput, AssetProcessorResult } from "./assets.js";
3
4
  export interface PluginContext {
4
5
  readonly appDir: string;
5
6
  /** Config hasil `resolveConfig` (termasuk key pass-through untuk plugin). */
@@ -15,6 +16,11 @@ export interface RamonyxPlugin {
15
16
  middleware?(context: PluginContext): Middleware | readonly Middleware[] | Promise<Middleware | readonly Middleware[]>;
16
17
  /** Hook aplikasi (timing/invalidasi) — digabung dengan hook lain (ADR-0065). */
17
18
  hooks?(context: PluginContext): AppHooks | Promise<AppHooks>;
19
+ /**
20
+ * Transformasi aset build (ADR-0079): dipanggil per berkas `src/assets/**` saat
21
+ * `ramonyx build`; hasilnya menambah metadata (dimensi) + varian (mis. resize/format gambar).
22
+ */
23
+ assets?(context: PluginContext, asset: AssetProcessorInput): AssetProcessorResult | undefined | Promise<AssetProcessorResult | undefined>;
18
24
  }
19
25
  /** Identitas + tipe untuk plugin; mengembalikan nilai apa adanya. */
20
26
  export declare function definePlugin(plugin: RamonyxPlugin): RamonyxPlugin;
@@ -22,6 +28,8 @@ export interface LoadedPlugins {
22
28
  readonly vite: PluginOption[];
23
29
  readonly middleware: Middleware[];
24
30
  readonly hooks: AppHooks | undefined;
31
+ /** Prosesor aset (dari hook `assets` plugin — ADR-0079). */
32
+ readonly assets: AssetProcessor[];
25
33
  }
26
34
  /** Resolusi plugin → daftar konkret (vite/middleware/hooks) untuk dipakai build/dev/start. */
27
35
  export declare function loadPlugins(plugins: readonly RamonyxPlugin[] | undefined, context: PluginContext): Promise<LoadedPlugins>;
package/dist/plugin.js CHANGED
@@ -12,6 +12,7 @@ export async function loadPlugins(plugins, context) {
12
12
  const vite = [];
13
13
  const middleware = [];
14
14
  const hooks = [];
15
+ const assets = [];
15
16
  for (const plugin of plugins ?? []) {
16
17
  const viteResult = await plugin.vite?.(context);
17
18
  if (viteResult !== undefined) {
@@ -35,7 +36,11 @@ export async function loadPlugins(plugins, context) {
35
36
  if (hooksResult !== undefined) {
36
37
  hooks.push(hooksResult);
37
38
  }
39
+ if (plugin.assets !== undefined) {
40
+ const hook = plugin.assets.bind(plugin);
41
+ assets.push((asset) => hook(context, asset));
42
+ }
38
43
  }
39
- return { vite, middleware, hooks: mergeHooks(...hooks) };
44
+ return { vite, middleware, hooks: mergeHooks(...hooks), assets };
40
45
  }
41
46
  //# sourceMappingURL=plugin.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../../packages/bundler/src/plugin.ts"],"names":[],"mappings":"AAAA,mFAAmF;AACnF,EAAE;AACF,6FAA6F;AAC7F,kEAAkE;AAElE,OAAO,EAIL,UAAU,GAEX,MAAM,eAAe,CAAC;AAyBvB,qEAAqE;AACrE,MAAM,UAAU,YAAY,CAAC,MAAqB;IAChD,OAAO,MAAM,CAAC;AAChB,CAAC;AAQD,8FAA8F;AAC9F,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,OAA6C,EAC7C,OAAsB;IAEtB,MAAM,IAAI,GAAmB,EAAE,CAAC;IAChC,MAAM,UAAU,GAAiB,EAAE,CAAC;IACpC,MAAM,KAAK,GAAe,EAAE,CAAC;IAE7B,KAAK,MAAM,MAAM,IAAI,OAAO,IAAI,EAAE,EAAE,CAAC;QACnC,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,CAAC;QAChD,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;YAC7B,IAAI,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;gBAC9B,IAAI,CAAC,IAAI,CAAC,GAAI,UAA6B,CAAC,CAAC;YAC/C,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,IAAI,CAAC,UAA0B,CAAC,CAAC;YACxC,CAAC;QACH,CAAC;QAED,MAAM,gBAAgB,GAAG,MAAM,MAAM,CAAC,UAAU,EAAE,CAAC,OAAO,CAAC,CAAC;QAC5D,IAAI,gBAAgB,KAAK,SAAS,EAAE,CAAC;YACnC,IAAI,KAAK,CAAC,OAAO,CAAC,gBAAgB,CAAC,EAAE,CAAC;gBACpC,UAAU,CAAC,IAAI,CAAC,GAAI,gBAAiC,CAAC,CAAC;YACzD,CAAC;iBAAM,CAAC;gBACN,UAAU,CAAC,IAAI,CAAC,gBAA8B,CAAC,CAAC;YAClD,CAAC;QACH,CAAC;QAED,MAAM,WAAW,GAAG,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC,OAAO,CAAC,CAAC;QAClD,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;YAC9B,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QAC1B,CAAC;IACH,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,CAAC,GAAG,KAAK,CAAC,EAAE,CAAC;AAC3D,CAAC","sourcesContent":["// Kontrak plugin (ADR-0065/0067) — passthrough Vite + middleware + hooks aplikasi.\n//\n// Kontrak ini **experimental** sampai ada ≥2 plugin nyata (kebijakan ADR-0065): stabil untuk\n// dipakai, tetapi dapat bertambah (aditif) tanpa dijanjikan beku.\n\nimport {\n type AppHooks,\n type Logger,\n type Middleware,\n mergeHooks,\n type RamonyxConfig,\n} from \"@ramonyx/core\";\nimport type { PluginOption } from \"vite\";\n\nexport interface PluginContext {\n readonly appDir: string;\n /** Config hasil `resolveConfig` (termasuk key pass-through untuk plugin). */\n readonly config: RamonyxConfig;\n readonly logger: Logger;\n readonly mode: \"dev\" | \"build\" | \"start\";\n}\n\nexport interface RamonyxPlugin {\n readonly name: string;\n /** Plugin Vite (klien, dev, edge) — diteruskan apa adanya; tunduk guard `node:*` (ADR-0058/0063). */\n vite?(\n context: PluginContext,\n ): PluginOption | readonly PluginOption[] | Promise<PluginOption | readonly PluginOption[]>;\n /** Middleware global pre-routing (harus edge-safe bila artefak edge dipakai). */\n middleware?(\n context: PluginContext,\n ): Middleware | readonly Middleware[] | Promise<Middleware | readonly Middleware[]>;\n /** Hook aplikasi (timing/invalidasi) — digabung dengan hook lain (ADR-0065). */\n hooks?(context: PluginContext): AppHooks | Promise<AppHooks>;\n}\n\n/** Identitas + tipe untuk plugin; mengembalikan nilai apa adanya. */\nexport function definePlugin(plugin: RamonyxPlugin): RamonyxPlugin {\n return plugin;\n}\n\nexport interface LoadedPlugins {\n readonly vite: PluginOption[];\n readonly middleware: Middleware[];\n readonly hooks: AppHooks | undefined;\n}\n\n/** Resolusi plugin → daftar konkret (vite/middleware/hooks) untuk dipakai build/dev/start. */\nexport async function loadPlugins(\n plugins: readonly RamonyxPlugin[] | undefined,\n context: PluginContext,\n): Promise<LoadedPlugins> {\n const vite: PluginOption[] = [];\n const middleware: Middleware[] = [];\n const hooks: AppHooks[] = [];\n\n for (const plugin of plugins ?? []) {\n const viteResult = await plugin.vite?.(context);\n if (viteResult !== undefined) {\n if (Array.isArray(viteResult)) {\n vite.push(...(viteResult as PluginOption[]));\n } else {\n vite.push(viteResult as PluginOption);\n }\n }\n\n const middlewareResult = await plugin.middleware?.(context);\n if (middlewareResult !== undefined) {\n if (Array.isArray(middlewareResult)) {\n middleware.push(...(middlewareResult as Middleware[]));\n } else {\n middleware.push(middlewareResult as Middleware);\n }\n }\n\n const hooksResult = await plugin.hooks?.(context);\n if (hooksResult !== undefined) {\n hooks.push(hooksResult);\n }\n }\n\n return { vite, middleware, hooks: mergeHooks(...hooks) };\n}\n"]}
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../../../packages/bundler/src/plugin.ts"],"names":[],"mappings":"AAAA,mFAAmF;AACnF,EAAE;AACF,6FAA6F;AAC7F,kEAAkE;AAElE,OAAO,EAIL,UAAU,GAEX,MAAM,eAAe,CAAC;AAkCvB,qEAAqE;AACrE,MAAM,UAAU,YAAY,CAAC,MAAqB;IAChD,OAAO,MAAM,CAAC;AAChB,CAAC;AAUD,8FAA8F;AAC9F,MAAM,CAAC,KAAK,UAAU,WAAW,CAC/B,OAA6C,EAC7C,OAAsB;IAEtB,MAAM,IAAI,GAAmB,EAAE,CAAC;IAChC,MAAM,UAAU,GAAiB,EAAE,CAAC;IACpC,MAAM,KAAK,GAAe,EAAE,CAAC;IAC7B,MAAM,MAAM,GAAqB,EAAE,CAAC;IAEpC,KAAK,MAAM,MAAM,IAAI,OAAO,IAAI,EAAE,EAAE,CAAC;QACnC,MAAM,UAAU,GAAG,MAAM,MAAM,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,CAAC;QAChD,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;YAC7B,IAAI,KAAK,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;gBAC9B,IAAI,CAAC,IAAI,CAAC,GAAI,UAA6B,CAAC,CAAC;YAC/C,CAAC;iBAAM,CAAC;gBACN,IAAI,CAAC,IAAI,CAAC,UAA0B,CAAC,CAAC;YACxC,CAAC;QACH,CAAC;QAED,MAAM,gBAAgB,GAAG,MAAM,MAAM,CAAC,UAAU,EAAE,CAAC,OAAO,CAAC,CAAC;QAC5D,IAAI,gBAAgB,KAAK,SAAS,EAAE,CAAC;YACnC,IAAI,KAAK,CAAC,OAAO,CAAC,gBAAgB,CAAC,EAAE,CAAC;gBACpC,UAAU,CAAC,IAAI,CAAC,GAAI,gBAAiC,CAAC,CAAC;YACzD,CAAC;iBAAM,CAAC;gBACN,UAAU,CAAC,IAAI,CAAC,gBAA8B,CAAC,CAAC;YAClD,CAAC;QACH,CAAC;QAED,MAAM,WAAW,GAAG,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC,OAAO,CAAC,CAAC;QAClD,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;YAC9B,KAAK,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;QAC1B,CAAC;QAED,IAAI,MAAM,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YAChC,MAAM,IAAI,GAAG,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;YACxC,MAAM,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC;QAC/C,CAAC;IACH,CAAC;IAED,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,UAAU,CAAC,GAAG,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;AACnE,CAAC","sourcesContent":["// Kontrak plugin (ADR-0065/0067) — passthrough Vite + middleware + hooks aplikasi.\n//\n// Kontrak ini **experimental** sampai ada ≥2 plugin nyata (kebijakan ADR-0065): stabil untuk\n// dipakai, tetapi dapat bertambah (aditif) tanpa dijanjikan beku.\n\nimport {\n type AppHooks,\n type Logger,\n type Middleware,\n mergeHooks,\n type RamonyxConfig,\n} from \"@ramonyx/core\";\nimport type { PluginOption } from \"vite\";\nimport type { AssetProcessor, AssetProcessorInput, AssetProcessorResult } from \"./assets.ts\";\n\nexport interface PluginContext {\n readonly appDir: string;\n /** Config hasil `resolveConfig` (termasuk key pass-through untuk plugin). */\n readonly config: RamonyxConfig;\n readonly logger: Logger;\n readonly mode: \"dev\" | \"build\" | \"start\";\n}\n\nexport interface RamonyxPlugin {\n readonly name: string;\n /** Plugin Vite (klien, dev, edge) — diteruskan apa adanya; tunduk guard `node:*` (ADR-0058/0063). */\n vite?(\n context: PluginContext,\n ): PluginOption | readonly PluginOption[] | Promise<PluginOption | readonly PluginOption[]>;\n /** Middleware global pre-routing (harus edge-safe bila artefak edge dipakai). */\n middleware?(\n context: PluginContext,\n ): Middleware | readonly Middleware[] | Promise<Middleware | readonly Middleware[]>;\n /** Hook aplikasi (timing/invalidasi) — digabung dengan hook lain (ADR-0065). */\n hooks?(context: PluginContext): AppHooks | Promise<AppHooks>;\n /**\n * Transformasi aset build (ADR-0079): dipanggil per berkas `src/assets/**` saat\n * `ramonyx build`; hasilnya menambah metadata (dimensi) + varian (mis. resize/format gambar).\n */\n assets?(\n context: PluginContext,\n asset: AssetProcessorInput,\n ): AssetProcessorResult | undefined | Promise<AssetProcessorResult | undefined>;\n}\n\n/** Identitas + tipe untuk plugin; mengembalikan nilai apa adanya. */\nexport function definePlugin(plugin: RamonyxPlugin): RamonyxPlugin {\n return plugin;\n}\n\nexport interface LoadedPlugins {\n readonly vite: PluginOption[];\n readonly middleware: Middleware[];\n readonly hooks: AppHooks | undefined;\n /** Prosesor aset (dari hook `assets` plugin — ADR-0079). */\n readonly assets: AssetProcessor[];\n}\n\n/** Resolusi plugin → daftar konkret (vite/middleware/hooks) untuk dipakai build/dev/start. */\nexport async function loadPlugins(\n plugins: readonly RamonyxPlugin[] | undefined,\n context: PluginContext,\n): Promise<LoadedPlugins> {\n const vite: PluginOption[] = [];\n const middleware: Middleware[] = [];\n const hooks: AppHooks[] = [];\n const assets: AssetProcessor[] = [];\n\n for (const plugin of plugins ?? []) {\n const viteResult = await plugin.vite?.(context);\n if (viteResult !== undefined) {\n if (Array.isArray(viteResult)) {\n vite.push(...(viteResult as PluginOption[]));\n } else {\n vite.push(viteResult as PluginOption);\n }\n }\n\n const middlewareResult = await plugin.middleware?.(context);\n if (middlewareResult !== undefined) {\n if (Array.isArray(middlewareResult)) {\n middleware.push(...(middlewareResult as Middleware[]));\n } else {\n middleware.push(middlewareResult as Middleware);\n }\n }\n\n const hooksResult = await plugin.hooks?.(context);\n if (hooksResult !== undefined) {\n hooks.push(hooksResult);\n }\n\n if (plugin.assets !== undefined) {\n const hook = plugin.assets.bind(plugin);\n assets.push((asset) => hook(context, asset));\n }\n }\n\n return { vite, middleware, hooks: mergeHooks(...hooks), assets };\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ramonyx/bundler",
3
- "version": "0.2.0",
3
+ "version": "0.4.0",
4
4
  "description": "Paket framework Ramonyx.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -19,8 +19,8 @@
19
19
  "node": ">=24"
20
20
  },
21
21
  "dependencies": {
22
- "@ramonyx/compiler": "^0.2.0",
23
- "@ramonyx/core": "^0.2.0",
22
+ "@ramonyx/compiler": "^0.4.0",
23
+ "@ramonyx/core": "^0.4.0",
24
24
  "vite": "8.3.4"
25
25
  },
26
26
  "publishConfig": {