@ramonyx/bundler 0.1.1 → 0.3.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 +1 -1
- package/dist/assets.d.ts +76 -0
- package/dist/assets.js +197 -0
- package/dist/assets.js.map +1 -0
- package/dist/build.d.ts +26 -6
- package/dist/build.js +101 -8
- package/dist/build.js.map +1 -1
- package/dist/dev.d.ts +6 -0
- package/dist/dev.js +122 -2
- package/dist/dev.js.map +1 -1
- package/dist/devtools.d.ts +43 -0
- package/dist/devtools.js +146 -0
- package/dist/devtools.js.map +1 -0
- package/dist/edge.d.ts +28 -0
- package/dist/edge.js +85 -0
- package/dist/edge.js.map +1 -0
- package/dist/index.d.ts +11 -1
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/manifest.d.ts +35 -0
- package/dist/manifest.js +29 -0
- package/dist/manifest.js.map +1 -0
- package/dist/plugin.d.ts +35 -0
- package/dist/plugin.js +46 -0
- package/dist/plugin.js.map +1 -0
- package/dist/prerender.d.ts +49 -0
- package/dist/prerender.js +104 -0
- package/dist/prerender.js.map +1 -0
- package/dist/server-only.d.ts +6 -0
- package/dist/server-only.js +69 -0
- package/dist/server-only.js.map +1 -0
- package/package.json +3 -3
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/** Nama berkas manifest aset di direktori keluaran klien (ADR-0055). */
|
|
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
|
+
}
|
|
13
|
+
export interface AssetEntry {
|
|
14
|
+
/** Path relatif terhadap direktori keluaran klien, mis. `assets/img/logo-abc123.png`. */
|
|
15
|
+
readonly file: string;
|
|
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[];
|
|
22
|
+
}
|
|
23
|
+
export type AssetMap = Readonly<Record<string, AssetEntry>>;
|
|
24
|
+
/** Manifest aset hasil build (`dist/client/ramonyx.assets.json`). */
|
|
25
|
+
export interface AssetManifest {
|
|
26
|
+
/** Nama berkas bundle klien (relatif terhadap direktori keluaran), mis. `client-abc.js`. */
|
|
27
|
+
readonly client: string;
|
|
28
|
+
readonly files: readonly string[];
|
|
29
|
+
/** Aset dari `src/assets/**` (key = path relatif posix) — ADR-0069. */
|
|
30
|
+
readonly assets?: AssetMap;
|
|
31
|
+
}
|
|
32
|
+
/** Tulis manifest ke direktori keluaran klien. */
|
|
33
|
+
export declare function writeAssetManifest(clientOutDir: string, manifest: AssetManifest): Promise<void>;
|
|
34
|
+
/** Baca manifest aset; `null` bila belum pernah dibangun. */
|
|
35
|
+
export declare function readAssetManifest(clientDir: string): Promise<AssetManifest | null>;
|
package/dist/manifest.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// Manifest aset klien (`dist/client/ramonyx.assets.json`) — dipakai build, resolver aset
|
|
2
|
+
// (ADR-0069), dan pembaca runtime (`readClientScript`). Dipisah agar tanpa circular import.
|
|
3
|
+
import { readFile, writeFile } from "node:fs/promises";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
/** Nama berkas manifest aset di direktori keluaran klien (ADR-0055). */
|
|
6
|
+
export const CLIENT_MANIFEST_FILE = "ramonyx.assets.json";
|
|
7
|
+
/** Tulis manifest ke direktori keluaran klien. */
|
|
8
|
+
export async function writeAssetManifest(clientOutDir, manifest) {
|
|
9
|
+
await writeFile(join(clientOutDir, CLIENT_MANIFEST_FILE), `${JSON.stringify(manifest, null, 2)}\n`);
|
|
10
|
+
}
|
|
11
|
+
/** Baca manifest aset; `null` bila belum pernah dibangun. */
|
|
12
|
+
export async function readAssetManifest(clientDir) {
|
|
13
|
+
try {
|
|
14
|
+
const raw = await readFile(join(clientDir, CLIENT_MANIFEST_FILE), "utf8");
|
|
15
|
+
const parsed = JSON.parse(raw);
|
|
16
|
+
if (typeof parsed.client !== "string" || parsed.client.length === 0) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
return {
|
|
20
|
+
client: parsed.client,
|
|
21
|
+
files: Array.isArray(parsed.files) ? parsed.files : [],
|
|
22
|
+
...(parsed.assets !== undefined && parsed.assets !== null ? { assets: parsed.assets } : {}),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=manifest.js.map
|
|
@@ -0,0 +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"]}
|
package/dist/plugin.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type AppHooks, type Logger, type Middleware, type RamonyxConfig } from "@ramonyx/core";
|
|
2
|
+
import type { PluginOption } from "vite";
|
|
3
|
+
import type { AssetProcessor, AssetProcessorInput, AssetProcessorResult } from "./assets.js";
|
|
4
|
+
export interface PluginContext {
|
|
5
|
+
readonly appDir: string;
|
|
6
|
+
/** Config hasil `resolveConfig` (termasuk key pass-through untuk plugin). */
|
|
7
|
+
readonly config: RamonyxConfig;
|
|
8
|
+
readonly logger: Logger;
|
|
9
|
+
readonly mode: "dev" | "build" | "start";
|
|
10
|
+
}
|
|
11
|
+
export interface RamonyxPlugin {
|
|
12
|
+
readonly name: string;
|
|
13
|
+
/** Plugin Vite (klien, dev, edge) — diteruskan apa adanya; tunduk guard `node:*` (ADR-0058/0063). */
|
|
14
|
+
vite?(context: PluginContext): PluginOption | readonly PluginOption[] | Promise<PluginOption | readonly PluginOption[]>;
|
|
15
|
+
/** Middleware global pre-routing (harus edge-safe bila artefak edge dipakai). */
|
|
16
|
+
middleware?(context: PluginContext): Middleware | readonly Middleware[] | Promise<Middleware | readonly Middleware[]>;
|
|
17
|
+
/** Hook aplikasi (timing/invalidasi) — digabung dengan hook lain (ADR-0065). */
|
|
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>;
|
|
24
|
+
}
|
|
25
|
+
/** Identitas + tipe untuk plugin; mengembalikan nilai apa adanya. */
|
|
26
|
+
export declare function definePlugin(plugin: RamonyxPlugin): RamonyxPlugin;
|
|
27
|
+
export interface LoadedPlugins {
|
|
28
|
+
readonly vite: PluginOption[];
|
|
29
|
+
readonly middleware: Middleware[];
|
|
30
|
+
readonly hooks: AppHooks | undefined;
|
|
31
|
+
/** Prosesor aset (dari hook `assets` plugin — ADR-0079). */
|
|
32
|
+
readonly assets: AssetProcessor[];
|
|
33
|
+
}
|
|
34
|
+
/** Resolusi plugin → daftar konkret (vite/middleware/hooks) untuk dipakai build/dev/start. */
|
|
35
|
+
export declare function loadPlugins(plugins: readonly RamonyxPlugin[] | undefined, context: PluginContext): Promise<LoadedPlugins>;
|
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// Kontrak plugin (ADR-0065/0067) — passthrough Vite + middleware + hooks aplikasi.
|
|
2
|
+
//
|
|
3
|
+
// Kontrak ini **experimental** sampai ada ≥2 plugin nyata (kebijakan ADR-0065): stabil untuk
|
|
4
|
+
// dipakai, tetapi dapat bertambah (aditif) tanpa dijanjikan beku.
|
|
5
|
+
import { mergeHooks, } from "@ramonyx/core";
|
|
6
|
+
/** Identitas + tipe untuk plugin; mengembalikan nilai apa adanya. */
|
|
7
|
+
export function definePlugin(plugin) {
|
|
8
|
+
return plugin;
|
|
9
|
+
}
|
|
10
|
+
/** Resolusi plugin → daftar konkret (vite/middleware/hooks) untuk dipakai build/dev/start. */
|
|
11
|
+
export async function loadPlugins(plugins, context) {
|
|
12
|
+
const vite = [];
|
|
13
|
+
const middleware = [];
|
|
14
|
+
const hooks = [];
|
|
15
|
+
const assets = [];
|
|
16
|
+
for (const plugin of plugins ?? []) {
|
|
17
|
+
const viteResult = await plugin.vite?.(context);
|
|
18
|
+
if (viteResult !== undefined) {
|
|
19
|
+
if (Array.isArray(viteResult)) {
|
|
20
|
+
vite.push(...viteResult);
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
vite.push(viteResult);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
const middlewareResult = await plugin.middleware?.(context);
|
|
27
|
+
if (middlewareResult !== undefined) {
|
|
28
|
+
if (Array.isArray(middlewareResult)) {
|
|
29
|
+
middleware.push(...middlewareResult);
|
|
30
|
+
}
|
|
31
|
+
else {
|
|
32
|
+
middleware.push(middlewareResult);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
const hooksResult = await plugin.hooks?.(context);
|
|
36
|
+
if (hooksResult !== undefined) {
|
|
37
|
+
hooks.push(hooksResult);
|
|
38
|
+
}
|
|
39
|
+
if (plugin.assets !== undefined) {
|
|
40
|
+
const hook = plugin.assets.bind(plugin);
|
|
41
|
+
assets.push((asset) => hook(context, asset));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return { vite, middleware, hooks: mergeHooks(...hooks), assets };
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=plugin.js.map
|
|
@@ -0,0 +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;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"]}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { type FetchHandler, type LoadedPage } from "@ramonyx/core";
|
|
2
|
+
export interface PrerenderOptions {
|
|
3
|
+
/** Handler aplikasi (hasil `createApp()` dengan `clientScript` produksi). */
|
|
4
|
+
readonly handler: FetchHandler;
|
|
5
|
+
/** Halaman hasil `loadPageRoutes` (untuk enumerasi rute statis + `getStaticPaths`). */
|
|
6
|
+
readonly pages: readonly LoadedPage[];
|
|
7
|
+
/** Direktori keluaran (mis. `dist/static`). */
|
|
8
|
+
readonly outDir: string;
|
|
9
|
+
/** Rute tambahan eksplisit (mis. dari enumerasi aplikasi sendiri). */
|
|
10
|
+
readonly paths?: readonly string[];
|
|
11
|
+
/** Sertakan rute statis (tanpa param) dari manifest; default true. */
|
|
12
|
+
readonly includeStaticPages?: boolean;
|
|
13
|
+
/** Path sintetis untuk halaman 404; default `/__ramonyx-404__`. */
|
|
14
|
+
readonly notFoundPath?: string;
|
|
15
|
+
/** Salin direktori aset klien ke `<outDir>/assets`. */
|
|
16
|
+
readonly clientDir?: string;
|
|
17
|
+
/** Tulis `.nojekyll` (hosting GitHub Pages). */
|
|
18
|
+
readonly nojekyll?: boolean;
|
|
19
|
+
/** Origin sintetis untuk request prerender. */
|
|
20
|
+
readonly origin?: string;
|
|
21
|
+
/** Transformasi HTML per rute (mis. menyuntik link discovery agen). */
|
|
22
|
+
readonly transformHtml?: (html: string, routePath: string) => string;
|
|
23
|
+
/** Tulis `<rute>/index.html` (default true) atau `<rute>.html`. */
|
|
24
|
+
readonly directoryIndex?: boolean;
|
|
25
|
+
}
|
|
26
|
+
export interface PrerenderRouteInfo {
|
|
27
|
+
readonly path: string;
|
|
28
|
+
readonly file: string;
|
|
29
|
+
readonly status: number;
|
|
30
|
+
readonly bytes: number;
|
|
31
|
+
}
|
|
32
|
+
export interface PrerenderResult {
|
|
33
|
+
readonly routes: readonly PrerenderRouteInfo[];
|
|
34
|
+
readonly skipped: readonly {
|
|
35
|
+
readonly path: string;
|
|
36
|
+
readonly reason: string;
|
|
37
|
+
}[];
|
|
38
|
+
readonly notFoundFile: string | null;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Render rute → HTML statis di `outDir`.
|
|
42
|
+
*
|
|
43
|
+
* - Rute statis (tanpa param) dari manifest ikut kecuali `page.prerender === false`.
|
|
44
|
+
* - Rute dinamis dienumerasi lewat `getStaticPaths` (bila ada) + `paths` eksplisit.
|
|
45
|
+
* - Halaman 404 ditulis dari `notFoundPath`; aset klien disalin bila `clientDir` diisi.
|
|
46
|
+
* - Status non-2xx pada rute = error build (kecuali halaman 404 yang memang non-2xx?
|
|
47
|
+
* tidak: 404 harus 404; kegagalan lain menggagalkan build dengan pesan jelas).
|
|
48
|
+
*/
|
|
49
|
+
export declare function prerenderRoutes(options: PrerenderOptions): Promise<PrerenderResult>;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
// Static generation (SSG) — ADR-0056: render rute menjadi HTML saat build.
|
|
2
|
+
//
|
|
3
|
+
// Dipakai `ramonyx build --static` (CLI) dan aplikasi yang mengekspor sendiri
|
|
4
|
+
// (mis. situs docs: `apps/docs/export.ts`). Handler aplikasi dijalankan dengan request
|
|
5
|
+
// sintetis sehingga paritas SSR ↔ statis terjaga (satu pipeline, bukan renderer kedua).
|
|
6
|
+
import { cp, mkdir, writeFile } from "node:fs/promises";
|
|
7
|
+
import { dirname, join } from "node:path";
|
|
8
|
+
import { fillRouteParams, hasDynamicSegment, } from "@ramonyx/core";
|
|
9
|
+
function routeToFile(outDir, route, directoryIndex) {
|
|
10
|
+
if (route === "" || route === "/") {
|
|
11
|
+
return join(outDir, "index.html");
|
|
12
|
+
}
|
|
13
|
+
const clean = route.replace(/^\//, "").replace(/\/$/, "");
|
|
14
|
+
return directoryIndex ? join(outDir, clean, "index.html") : join(outDir, `${clean}.html`);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Render rute → HTML statis di `outDir`.
|
|
18
|
+
*
|
|
19
|
+
* - Rute statis (tanpa param) dari manifest ikut kecuali `page.prerender === false`.
|
|
20
|
+
* - Rute dinamis dienumerasi lewat `getStaticPaths` (bila ada) + `paths` eksplisit.
|
|
21
|
+
* - Halaman 404 ditulis dari `notFoundPath`; aset klien disalin bila `clientDir` diisi.
|
|
22
|
+
* - Status non-2xx pada rute = error build (kecuali halaman 404 yang memang non-2xx?
|
|
23
|
+
* tidak: 404 harus 404; kegagalan lain menggagalkan build dengan pesan jelas).
|
|
24
|
+
*/
|
|
25
|
+
export async function prerenderRoutes(options) {
|
|
26
|
+
const origin = options.origin ?? "http://prerender.local";
|
|
27
|
+
const directoryIndex = options.directoryIndex ?? true;
|
|
28
|
+
const includeStatic = options.includeStaticPages ?? true;
|
|
29
|
+
const routes = new Set();
|
|
30
|
+
const skipped = [];
|
|
31
|
+
for (const page of options.pages) {
|
|
32
|
+
const prerender = page.page.prerender;
|
|
33
|
+
if (prerender === false) {
|
|
34
|
+
skipped.push({ path: page.path, reason: "prerender: false" });
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (!hasDynamicSegment(page.path)) {
|
|
38
|
+
if (includeStatic) {
|
|
39
|
+
routes.add(page.path);
|
|
40
|
+
}
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
const enumerate = page.page.getStaticPaths;
|
|
44
|
+
if (enumerate === undefined) {
|
|
45
|
+
skipped.push({ path: page.path, reason: "rute dinamis tanpa getStaticPaths" });
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
for (const params of await enumerate()) {
|
|
49
|
+
const filled = fillRouteParams(page.path, params);
|
|
50
|
+
if (filled === null) {
|
|
51
|
+
skipped.push({ path: page.path, reason: `param kurang: ${JSON.stringify(params)}` });
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
routes.add(filled);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
for (const path of options.paths ?? []) {
|
|
58
|
+
routes.add(path);
|
|
59
|
+
}
|
|
60
|
+
const rendered = [];
|
|
61
|
+
for (const route of [...routes].sort()) {
|
|
62
|
+
const response = await options.handler(new Request(new URL(route, origin)));
|
|
63
|
+
const contentType = response.headers.get("content-type") ?? "";
|
|
64
|
+
if (!contentType.includes("text/html")) {
|
|
65
|
+
throw new Error(`prerender ${route}: respons bukan HTML (content-type: ${contentType || "kosong"}, status ${response.status})`);
|
|
66
|
+
}
|
|
67
|
+
const html = await response.text();
|
|
68
|
+
if (response.status >= 400) {
|
|
69
|
+
const cuplikan = html.replace(/\s+/g, " ").slice(0, 200);
|
|
70
|
+
throw new Error(`prerender ${route}: status ${response.status} (halaman harus 2xx) — ${cuplikan}`);
|
|
71
|
+
}
|
|
72
|
+
const file = routeToFile(options.outDir, route, directoryIndex);
|
|
73
|
+
await mkdir(dirname(file), { recursive: true });
|
|
74
|
+
const output = options.transformHtml === undefined ? html : options.transformHtml(html, route);
|
|
75
|
+
await writeFile(file, output);
|
|
76
|
+
rendered.push({
|
|
77
|
+
path: route,
|
|
78
|
+
file,
|
|
79
|
+
status: response.status,
|
|
80
|
+
bytes: Buffer.byteLength(output),
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
let notFoundFile = null;
|
|
84
|
+
if (options.notFoundPath !== undefined) {
|
|
85
|
+
const response = await options.handler(new Request(new URL(options.notFoundPath, origin)));
|
|
86
|
+
if (response.status !== 404) {
|
|
87
|
+
throw new Error(`prerender 404: path "${options.notFoundPath}" menghasilkan status ${response.status} (harus 404)`);
|
|
88
|
+
}
|
|
89
|
+
notFoundFile = join(options.outDir, "404.html");
|
|
90
|
+
const html = await response.text();
|
|
91
|
+
const output = options.transformHtml === undefined
|
|
92
|
+
? html
|
|
93
|
+
: options.transformHtml(html, options.notFoundPath);
|
|
94
|
+
await writeFile(notFoundFile, output);
|
|
95
|
+
}
|
|
96
|
+
if (options.clientDir !== undefined) {
|
|
97
|
+
await cp(options.clientDir, join(options.outDir, "assets"), { recursive: true });
|
|
98
|
+
}
|
|
99
|
+
if (options.nojekyll === true) {
|
|
100
|
+
await writeFile(join(options.outDir, ".nojekyll"), "");
|
|
101
|
+
}
|
|
102
|
+
return { routes: rendered, skipped, notFoundFile };
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=prerender.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"prerender.js","sourceRoot":"","sources":["../../../packages/bundler/src/prerender.ts"],"names":[],"mappings":"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","sourcesContent":["// Static generation (SSG) — ADR-0056: render rute menjadi HTML saat build.\n//\n// Dipakai `ramonyx build --static` (CLI) dan aplikasi yang mengekspor sendiri\n// (mis. situs docs: `apps/docs/export.ts`). Handler aplikasi dijalankan dengan request\n// sintetis sehingga paritas SSR ↔ statis terjaga (satu pipeline, bukan renderer kedua).\n\nimport { cp, mkdir, writeFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport {\n type FetchHandler,\n fillRouteParams,\n hasDynamicSegment,\n type LoadedPage,\n} from \"@ramonyx/core\";\n\nexport interface PrerenderOptions {\n /** Handler aplikasi (hasil `createApp()` dengan `clientScript` produksi). */\n readonly handler: FetchHandler;\n /** Halaman hasil `loadPageRoutes` (untuk enumerasi rute statis + `getStaticPaths`). */\n readonly pages: readonly LoadedPage[];\n /** Direktori keluaran (mis. `dist/static`). */\n readonly outDir: string;\n /** Rute tambahan eksplisit (mis. dari enumerasi aplikasi sendiri). */\n readonly paths?: readonly string[];\n /** Sertakan rute statis (tanpa param) dari manifest; default true. */\n readonly includeStaticPages?: boolean;\n /** Path sintetis untuk halaman 404; default `/__ramonyx-404__`. */\n readonly notFoundPath?: string;\n /** Salin direktori aset klien ke `<outDir>/assets`. */\n readonly clientDir?: string;\n /** Tulis `.nojekyll` (hosting GitHub Pages). */\n readonly nojekyll?: boolean;\n /** Origin sintetis untuk request prerender. */\n readonly origin?: string;\n /** Transformasi HTML per rute (mis. menyuntik link discovery agen). */\n readonly transformHtml?: (html: string, routePath: string) => string;\n /** Tulis `<rute>/index.html` (default true) atau `<rute>.html`. */\n readonly directoryIndex?: boolean;\n}\n\nexport interface PrerenderRouteInfo {\n readonly path: string;\n readonly file: string;\n readonly status: number;\n readonly bytes: number;\n}\n\nexport interface PrerenderResult {\n readonly routes: readonly PrerenderRouteInfo[];\n readonly skipped: readonly { readonly path: string; readonly reason: string }[];\n readonly notFoundFile: string | null;\n}\n\nfunction routeToFile(outDir: string, route: string, directoryIndex: boolean): string {\n if (route === \"\" || route === \"/\") {\n return join(outDir, \"index.html\");\n }\n const clean = route.replace(/^\\//, \"\").replace(/\\/$/, \"\");\n return directoryIndex ? join(outDir, clean, \"index.html\") : join(outDir, `${clean}.html`);\n}\n\n/**\n * Render rute → HTML statis di `outDir`.\n *\n * - Rute statis (tanpa param) dari manifest ikut kecuali `page.prerender === false`.\n * - Rute dinamis dienumerasi lewat `getStaticPaths` (bila ada) + `paths` eksplisit.\n * - Halaman 404 ditulis dari `notFoundPath`; aset klien disalin bila `clientDir` diisi.\n * - Status non-2xx pada rute = error build (kecuali halaman 404 yang memang non-2xx?\n * tidak: 404 harus 404; kegagalan lain menggagalkan build dengan pesan jelas).\n */\nexport async function prerenderRoutes(options: PrerenderOptions): Promise<PrerenderResult> {\n const origin = options.origin ?? \"http://prerender.local\";\n const directoryIndex = options.directoryIndex ?? true;\n const includeStatic = options.includeStaticPages ?? true;\n\n const routes = new Set<string>();\n const skipped: Array<{ path: string; reason: string }> = [];\n\n for (const page of options.pages) {\n const prerender = page.page.prerender;\n if (prerender === false) {\n skipped.push({ path: page.path, reason: \"prerender: false\" });\n continue;\n }\n if (!hasDynamicSegment(page.path)) {\n if (includeStatic) {\n routes.add(page.path);\n }\n continue;\n }\n const enumerate = page.page.getStaticPaths;\n if (enumerate === undefined) {\n skipped.push({ path: page.path, reason: \"rute dinamis tanpa getStaticPaths\" });\n continue;\n }\n for (const params of await enumerate()) {\n const filled = fillRouteParams(page.path, params);\n if (filled === null) {\n skipped.push({ path: page.path, reason: `param kurang: ${JSON.stringify(params)}` });\n continue;\n }\n routes.add(filled);\n }\n }\n for (const path of options.paths ?? []) {\n routes.add(path);\n }\n\n const rendered: PrerenderRouteInfo[] = [];\n for (const route of [...routes].sort()) {\n const response = await options.handler(new Request(new URL(route, origin)));\n const contentType = response.headers.get(\"content-type\") ?? \"\";\n if (!contentType.includes(\"text/html\")) {\n throw new Error(\n `prerender ${route}: respons bukan HTML (content-type: ${contentType || \"kosong\"}, status ${response.status})`,\n );\n }\n const html = await response.text();\n if (response.status >= 400) {\n const cuplikan = html.replace(/\\s+/g, \" \").slice(0, 200);\n throw new Error(\n `prerender ${route}: status ${response.status} (halaman harus 2xx) — ${cuplikan}`,\n );\n }\n const file = routeToFile(options.outDir, route, directoryIndex);\n await mkdir(dirname(file), { recursive: true });\n const output = options.transformHtml === undefined ? html : options.transformHtml(html, route);\n await writeFile(file, output);\n rendered.push({\n path: route,\n file,\n status: response.status,\n bytes: Buffer.byteLength(output),\n });\n }\n\n let notFoundFile: string | null = null;\n if (options.notFoundPath !== undefined) {\n const response = await options.handler(new Request(new URL(options.notFoundPath, origin)));\n if (response.status !== 404) {\n throw new Error(\n `prerender 404: path \"${options.notFoundPath}\" menghasilkan status ${response.status} (harus 404)`,\n );\n }\n notFoundFile = join(options.outDir, \"404.html\");\n const html = await response.text();\n const output =\n options.transformHtml === undefined\n ? html\n : options.transformHtml(html, options.notFoundPath);\n await writeFile(notFoundFile, output);\n }\n\n if (options.clientDir !== undefined) {\n await cp(options.clientDir, join(options.outDir, \"assets\"), { recursive: true });\n }\n if (options.nojekyll === true) {\n await writeFile(join(options.outDir, \".nojekyll\"), \"\");\n }\n\n return { routes: rendered, skipped, notFoundFile };\n}\n"]}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// Plugin Vite: modul `*.server.ts` diganti **stub** di bundle klien (ADR-0063).
|
|
2
|
+
//
|
|
3
|
+
// Pola "server-only": halaman boleh mengimpor modul server (dipakai `loader`/`action` yang
|
|
4
|
+
// berjalan di server), tetapi bundle klien menerima stub yang melempar bila dipanggil dari
|
|
5
|
+
// kode klien — bukan kebocoran implementasi/API server.
|
|
6
|
+
import { readFile } from "node:fs/promises";
|
|
7
|
+
const SERVER_MODULE = /\.server\.(ts|tsx|js|mjs)$/;
|
|
8
|
+
const VIRTUAL_PREFIX = "\0ramonyx-server-stub:";
|
|
9
|
+
/** Nama ekspor dari sumber server (regex ringan — tipe/interface diabaikan). */
|
|
10
|
+
function exportedNames(source) {
|
|
11
|
+
const names = new Set();
|
|
12
|
+
for (const match of source.matchAll(/export\s+(?:async\s+)?(?:function|const|let|var|class)\s+([A-Za-z_$][\w$]*)/g)) {
|
|
13
|
+
if (match[1] !== undefined) {
|
|
14
|
+
names.add(match[1]);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
for (const match of source.matchAll(/export\s*\{([^}]*)\}/g)) {
|
|
18
|
+
for (const part of (match[1] ?? "").split(",")) {
|
|
19
|
+
const cleaned = part.trim().replace(/^type\s+/, "");
|
|
20
|
+
if (cleaned.length === 0) {
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
const alias = cleaned.split(/\s+as\s+/);
|
|
24
|
+
const name = (alias[1] ?? alias[0] ?? "").trim();
|
|
25
|
+
if (name.length > 0 && name !== "default") {
|
|
26
|
+
names.add(name);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return [...names].sort();
|
|
31
|
+
}
|
|
32
|
+
function stubSource(file, names) {
|
|
33
|
+
const poison = (name) => `(() => { throw new Error(${JSON.stringify(`"${name}" dari ${file} hanya tersedia di server — impor modul ini dari loader/action/route API, bukan kode klien (ADR-0063).`)}); })`;
|
|
34
|
+
const lines = [
|
|
35
|
+
"// Stub server-only (digenerate @ramonyx/bundler — ADR-0063).",
|
|
36
|
+
...names.map((name) => `export const ${name} = ${poison(name)};`),
|
|
37
|
+
`export default ${poison("default")};`,
|
|
38
|
+
];
|
|
39
|
+
return `${lines.join("\n")}\n`;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Plugin untuk `buildClient`: setiap impor `*.server.ts` diresolusi ke stub yang melempar
|
|
43
|
+
* bila dipanggil di browser.
|
|
44
|
+
*/
|
|
45
|
+
export function serverOnlyPlugin() {
|
|
46
|
+
return {
|
|
47
|
+
name: "ramonyx-server-only",
|
|
48
|
+
enforce: "pre",
|
|
49
|
+
async resolveId(source, importer) {
|
|
50
|
+
if (!SERVER_MODULE.test(source) || importer === undefined) {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
const resolved = await this.resolve(source, importer, { skipSelf: true });
|
|
54
|
+
if (resolved === null) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
return `${VIRTUAL_PREFIX}${resolved.id}`;
|
|
58
|
+
},
|
|
59
|
+
async load(id) {
|
|
60
|
+
if (!id.startsWith(VIRTUAL_PREFIX)) {
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
const file = id.slice(VIRTUAL_PREFIX.length);
|
|
64
|
+
const source = await readFile(file, "utf8");
|
|
65
|
+
return stubSource(file, exportedNames(source));
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=server-only.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-only.js","sourceRoot":"","sources":["../../../packages/bundler/src/server-only.ts"],"names":[],"mappings":"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","sourcesContent":["// Plugin Vite: modul `*.server.ts` diganti **stub** di bundle klien (ADR-0063).\n//\n// Pola \"server-only\": halaman boleh mengimpor modul server (dipakai `loader`/`action` yang\n// berjalan di server), tetapi bundle klien menerima stub yang melempar bila dipanggil dari\n// kode klien — bukan kebocoran implementasi/API server.\n\nimport { readFile } from \"node:fs/promises\";\nimport type { Plugin } from \"vite\";\n\nconst SERVER_MODULE = /\\.server\\.(ts|tsx|js|mjs)$/;\nconst VIRTUAL_PREFIX = \"\\0ramonyx-server-stub:\";\n\n/** Nama ekspor dari sumber server (regex ringan — tipe/interface diabaikan). */\nfunction exportedNames(source: string): string[] {\n const names = new Set<string>();\n for (const match of source.matchAll(\n /export\\s+(?:async\\s+)?(?:function|const|let|var|class)\\s+([A-Za-z_$][\\w$]*)/g,\n )) {\n if (match[1] !== undefined) {\n names.add(match[1]);\n }\n }\n for (const match of source.matchAll(/export\\s*\\{([^}]*)\\}/g)) {\n for (const part of (match[1] ?? \"\").split(\",\")) {\n const cleaned = part.trim().replace(/^type\\s+/, \"\");\n if (cleaned.length === 0) {\n continue;\n }\n const alias = cleaned.split(/\\s+as\\s+/);\n const name = (alias[1] ?? alias[0] ?? \"\").trim();\n if (name.length > 0 && name !== \"default\") {\n names.add(name);\n }\n }\n }\n return [...names].sort();\n}\n\nfunction stubSource(file: string, names: readonly string[]): string {\n const poison = (name: string): string =>\n `(() => { throw new Error(${JSON.stringify(\n `\"${name}\" dari ${file} hanya tersedia di server — impor modul ini dari loader/action/route API, bukan kode klien (ADR-0063).`,\n )}); })`;\n const lines = [\n \"// Stub server-only (digenerate @ramonyx/bundler — ADR-0063).\",\n ...names.map((name) => `export const ${name} = ${poison(name)};`),\n `export default ${poison(\"default\")};`,\n ];\n return `${lines.join(\"\\n\")}\\n`;\n}\n\n/**\n * Plugin untuk `buildClient`: setiap impor `*.server.ts` diresolusi ke stub yang melempar\n * bila dipanggil di browser.\n */\nexport function serverOnlyPlugin(): Plugin {\n return {\n name: \"ramonyx-server-only\",\n enforce: \"pre\",\n async resolveId(source, importer) {\n if (!SERVER_MODULE.test(source) || importer === undefined) {\n return null;\n }\n const resolved = await this.resolve(source, importer, { skipSelf: true });\n if (resolved === null) {\n return null;\n }\n return `${VIRTUAL_PREFIX}${resolved.id}`;\n },\n async load(id) {\n if (!id.startsWith(VIRTUAL_PREFIX)) {\n return null;\n }\n const file = id.slice(VIRTUAL_PREFIX.length);\n const source = await readFile(file, \"utf8\");\n return stubSource(file, exportedNames(source));\n },\n };\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ramonyx/bundler",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.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.
|
|
23
|
-
"@ramonyx/core": "^0.
|
|
22
|
+
"@ramonyx/compiler": "^0.3.0",
|
|
23
|
+
"@ramonyx/core": "^0.3.0",
|
|
24
24
|
"vite": "8.3.4"
|
|
25
25
|
},
|
|
26
26
|
"publishConfig": {
|