@ramonyx/bundler 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/assets.d.ts +39 -0
- package/dist/assets.js +105 -0
- package/dist/assets.js.map +1 -0
- package/dist/build.d.ts +23 -6
- package/dist/build.js +99 -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 +91 -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 +20 -0
- package/dist/manifest.js +29 -0
- package/dist/manifest.js.map +1 -0
- package/dist/plugin.d.ts +27 -0
- package/dist/plugin.js +41 -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,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.2.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.2.0",
|
|
23
|
+
"@ramonyx/core": "^0.2.0",
|
|
24
24
|
"vite": "8.3.4"
|
|
25
25
|
},
|
|
26
26
|
"publishConfig": {
|