xplorajs 0.3.7 → 0.3.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,10 +1,15 @@
1
1
  #!/usr/bin/env bun
2
+ import { readFileSync } from "node:fs";
3
+ import { dirname, join } from "node:path";
4
+ import { fileURLToPath } from "node:url";
2
5
  import { Command } from "commander";
3
6
  import { build } from "./commands/build.js";
4
7
  import { dev } from "./commands/dev.js";
5
8
  import { start } from "./commands/start.js";
9
+ const __dirname = dirname(fileURLToPath(import.meta.url));
10
+ const pkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"));
6
11
  const program = new Command();
7
- program.name("xplorajs").description("Xplora.js CLI tool").version("0.0.0");
12
+ program.name("xplorajs").description("Xplora.js CLI tool").version(pkg.version);
8
13
  program.command("dev").description("Start development server").action(dev);
9
14
  program.command("build").description("Build for production").action(build);
10
15
  program.command("start").description("Start production server").action(start);
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { Command } from \"commander\";\nimport { build } from \"./commands/build.js\";\nimport { dev } from \"./commands/dev.js\";\nimport { start } from \"./commands/start.js\";\n\nconst program = new Command();\n\nprogram.name(\"xplorajs\").description(\"Xplora.js CLI tool\").version(\"0.0.0\");\n\nprogram.command(\"dev\").description(\"Start development server\").action(dev);\n\nprogram.command(\"build\").description(\"Build for production\").action(build);\n\nprogram.command(\"start\").description(\"Start production server\").action(start);\n\nprogram.parse();\n"],"names":["Command","build","dev","start","program","name","description","version","command","action","parse"],"mappings":";AACA,SAASA,OAAO,QAAQ,YAAY;AACpC,SAASC,KAAK,QAAQ,sBAAsB;AAC5C,SAASC,GAAG,QAAQ,oBAAoB;AACxC,SAASC,KAAK,QAAQ,sBAAsB;AAE5C,MAAMC,UAAU,IAAIJ;AAEpBI,QAAQC,IAAI,CAAC,YAAYC,WAAW,CAAC,sBAAsBC,OAAO,CAAC;AAEnEH,QAAQI,OAAO,CAAC,OAAOF,WAAW,CAAC,4BAA4BG,MAAM,CAACP;AAEtEE,QAAQI,OAAO,CAAC,SAASF,WAAW,CAAC,wBAAwBG,MAAM,CAACR;AAEpEG,QAAQI,OAAO,CAAC,SAASF,WAAW,CAAC,2BAA2BG,MAAM,CAACN;AAEvEC,QAAQM,KAAK"}
1
+ {"version":3,"sources":["../src/cli.ts"],"sourcesContent":["#!/usr/bin/env node\nimport { readFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { Command } from \"commander\";\nimport { build } from \"./commands/build.js\";\nimport { dev } from \"./commands/dev.js\";\nimport { start } from \"./commands/start.js\";\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst pkg = JSON.parse(\n\treadFileSync(join(__dirname, \"..\", \"package.json\"), \"utf-8\"),\n);\n\nconst program = new Command();\n\nprogram.name(\"xplorajs\").description(\"Xplora.js CLI tool\").version(pkg.version);\n\nprogram.command(\"dev\").description(\"Start development server\").action(dev);\n\nprogram.command(\"build\").description(\"Build for production\").action(build);\n\nprogram.command(\"start\").description(\"Start production server\").action(start);\n\nprogram.parse();\n"],"names":["readFileSync","dirname","join","fileURLToPath","Command","build","dev","start","__dirname","url","pkg","JSON","parse","program","name","description","version","command","action"],"mappings":";AACA,SAASA,YAAY,QAAQ,UAAU;AACvC,SAASC,OAAO,EAAEC,IAAI,QAAQ,YAAY;AAC1C,SAASC,aAAa,QAAQ,WAAW;AACzC,SAASC,OAAO,QAAQ,YAAY;AACpC,SAASC,KAAK,QAAQ,sBAAsB;AAC5C,SAASC,GAAG,QAAQ,oBAAoB;AACxC,SAASC,KAAK,QAAQ,sBAAsB;AAE5C,MAAMC,YAAYP,QAAQE,cAAc,YAAYM,GAAG;AACvD,MAAMC,MAAMC,KAAKC,KAAK,CACrBZ,aAAaE,KAAKM,WAAW,MAAM,iBAAiB;AAGrD,MAAMK,UAAU,IAAIT;AAEpBS,QAAQC,IAAI,CAAC,YAAYC,WAAW,CAAC,sBAAsBC,OAAO,CAACN,IAAIM,OAAO;AAE9EH,QAAQI,OAAO,CAAC,OAAOF,WAAW,CAAC,4BAA4BG,MAAM,CAACZ;AAEtEO,QAAQI,OAAO,CAAC,SAASF,WAAW,CAAC,wBAAwBG,MAAM,CAACb;AAEpEQ,QAAQI,OAAO,CAAC,SAASF,WAAW,CAAC,2BAA2BG,MAAM,CAACX;AAEvEM,QAAQD,KAAK"}
@@ -1,13 +1,24 @@
1
- const ws = new WebSocket("ws://localhost:3001");
1
+ const wsPort = window.__XPLORA_WS_PORT__ || 3001;
2
+ const ws = new WebSocket(`ws://localhost:${wsPort}`);
3
+ ws.onopen = ()=>{
4
+ console.log("[HMR] Connected to dev server");
5
+ };
6
+ ws.onclose = ()=>{
7
+ console.log("[HMR] Disconnected from dev server");
8
+ };
9
+ ws.onerror = (error)=>{
10
+ console.error("[HMR] WebSocket error:", error);
11
+ };
2
12
  ws.onmessage = async (e)=>{
3
13
  const data = JSON.parse(e.data);
4
14
  if (data.type === "css") {
15
+ console.log("[HMR] CSS updated, reloading styles...");
5
16
  document.querySelectorAll('link[rel="stylesheet"]').forEach((l)=>{
6
- l.href = "/assets/style.css?v=" + Date.now();
17
+ l.href = l.href.split("?")[0] + "?v=" + Date.now();
7
18
  });
8
19
  }
9
20
  if (data.type === "reload") {
10
- console.log("Reloading...");
21
+ console.log("[HMR] Files changed, reloading page...");
11
22
  location.reload();
12
23
  }
13
24
  };
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/client/hmr.js"],"sourcesContent":["const ws = new WebSocket(\"ws://localhost:3001\");\n\nws.onmessage = async (e) => {\n const data = JSON.parse(e.data);\n\n if (data.type === \"css\") {\n document.querySelectorAll('link[rel=\"stylesheet\"]').forEach((l) => {\n l.href = \"/assets/style.css?v=\" + Date.now();\n });\n }\n\n if (data.type === \"reload\") {\n console.log(\"Reloading...\");\n location.reload();\n }\n};\n"],"names":["ws","WebSocket","onmessage","e","data","JSON","parse","type","document","querySelectorAll","forEach","l","href","Date","now","console","log","location","reload"],"mappings":"AAAA,MAAMA,KAAK,IAAIC,UAAU;AAEzBD,GAAGE,SAAS,GAAG,OAAOC;IACpB,MAAMC,OAAOC,KAAKC,KAAK,CAACH,EAAEC,IAAI;IAE9B,IAAIA,KAAKG,IAAI,KAAK,OAAO;QACvBC,SAASC,gBAAgB,CAAC,0BAA0BC,OAAO,CAAC,CAACC;YAC3DA,EAAEC,IAAI,GAAG,yBAAyBC,KAAKC,GAAG;QAC5C;IACF;IAEA,IAAIV,KAAKG,IAAI,KAAK,UAAU;QAC1BQ,QAAQC,GAAG,CAAC;QACZC,SAASC,MAAM;IACjB;AACF"}
1
+ {"version":3,"sources":["../../src/client/hmr.js"],"sourcesContent":["const wsPort = window.__XPLORA_WS_PORT__ || 3001;\nconst ws = new WebSocket(`ws://localhost:${wsPort}`);\n\nws.onopen = () => {\n\tconsole.log(\"[HMR] Connected to dev server\");\n};\n\nws.onclose = () => {\n\tconsole.log(\"[HMR] Disconnected from dev server\");\n};\n\nws.onerror = (error) => {\n\tconsole.error(\"[HMR] WebSocket error:\", error);\n};\n\nws.onmessage = async (e) => {\n\tconst data = JSON.parse(e.data);\n\n\tif (data.type === \"css\") {\n\t\tconsole.log(\"[HMR] CSS updated, reloading styles...\");\n\t\tdocument.querySelectorAll('link[rel=\"stylesheet\"]').forEach((l) => {\n\t\t\tl.href = l.href.split(\"?\")[0] + \"?v=\" + Date.now();\n\t\t});\n\t}\n\n\tif (data.type === \"reload\") {\n\t\tconsole.log(\"[HMR] Files changed, reloading page...\");\n\t\tlocation.reload();\n\t}\n};\n"],"names":["wsPort","window","__XPLORA_WS_PORT__","ws","WebSocket","onopen","console","log","onclose","onerror","error","onmessage","e","data","JSON","parse","type","document","querySelectorAll","forEach","l","href","split","Date","now","location","reload"],"mappings":"AAAA,MAAMA,SAASC,OAAOC,kBAAkB,IAAI;AAC5C,MAAMC,KAAK,IAAIC,UAAU,CAAC,eAAe,EAAEJ,QAAQ;AAEnDG,GAAGE,MAAM,GAAG;IACXC,QAAQC,GAAG,CAAC;AACb;AAEAJ,GAAGK,OAAO,GAAG;IACZF,QAAQC,GAAG,CAAC;AACb;AAEAJ,GAAGM,OAAO,GAAG,CAACC;IACbJ,QAAQI,KAAK,CAAC,0BAA0BA;AACzC;AAEAP,GAAGQ,SAAS,GAAG,OAAOC;IACrB,MAAMC,OAAOC,KAAKC,KAAK,CAACH,EAAEC,IAAI;IAE9B,IAAIA,KAAKG,IAAI,KAAK,OAAO;QACxBV,QAAQC,GAAG,CAAC;QACZU,SAASC,gBAAgB,CAAC,0BAA0BC,OAAO,CAAC,CAACC;YAC5DA,EAAEC,IAAI,GAAGD,EAAEC,IAAI,CAACC,KAAK,CAAC,IAAI,CAAC,EAAE,GAAG,QAAQC,KAAKC,GAAG;QACjD;IACD;IAEA,IAAIX,KAAKG,IAAI,KAAK,UAAU;QAC3BV,QAAQC,GAAG,CAAC;QACZkB,SAASC,MAAM;IAChB;AACD"}
@@ -1 +1 @@
1
- {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/commands/build.ts"],"names":[],"mappings":"AA0DA,wBAAsB,KAAK,kBA8B1B"}
1
+ {"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/commands/build.ts"],"names":[],"mappings":"AAsFA,wBAAsB,KAAK,kBA4C1B"}
@@ -3,18 +3,35 @@ import { join } from "node:path";
3
3
  import { glob } from "fast-glob";
4
4
  import React from "react";
5
5
  import { generateStaticPage } from "xplorajs-react";
6
- function convertToRoute(filePath) {
7
- const relativePath = filePath.replace("src/app/", "");
8
- const path = relativePath.replace(/\.tsx$/, "").replace(/\/page$/, "").replace(/\[([^\]]+)\]/g, ":$1");
9
- const params = (relativePath.match(/\[([^\]]+)\]/g) || []).map((param)=>param.slice(1, -1));
10
- return {
11
- path: path === "page" ? "/" : `/${path}`,
12
- file: filePath,
13
- isDynamic: params.length > 0,
14
- params
15
- };
6
+ import { loadConfig } from "../config.js";
7
+ import { convertToRoute } from "../utils/routes.js";
8
+ async function loadLayout() {
9
+ const layoutPath = join(process.cwd(), "src/app/layout.tsx");
10
+ const file = Bun.file(layoutPath);
11
+ try {
12
+ if (await file.exists()) {
13
+ const module = await import(layoutPath);
14
+ return module.default;
15
+ }
16
+ } catch (error) {
17
+ console.error("Failed to load layout.tsx:", error);
18
+ }
19
+ return null;
20
+ }
21
+ function DefaultLayout({ children }) {
22
+ return React.createElement("html", {
23
+ lang: "en"
24
+ }, React.createElement("head", null, React.createElement("meta", {
25
+ charSet: "utf-8"
26
+ }), React.createElement("meta", {
27
+ name: "viewport",
28
+ content: "width=device-width, initial-scale=1"
29
+ }), React.createElement("link", {
30
+ rel: "stylesheet",
31
+ href: "/assets/style.css"
32
+ })), React.createElement("body", null, children));
16
33
  }
17
- async function processPage(page, route) {
34
+ async function processPage(page, route, outputDir, Layout) {
18
35
  try {
19
36
  const module = await import(join(process.cwd(), page));
20
37
  const PageComponent = module.default;
@@ -24,9 +41,14 @@ async function processPage(page, route) {
24
41
  const result = await getStaticProps();
25
42
  props = result.props;
26
43
  }
27
- const outputPath = join(process.cwd(), "dist", route.path, "index.html");
44
+ const pageElement = React.createElement(PageComponent, props);
45
+ const LayoutComponent = Layout || DefaultLayout;
46
+ const content = React.createElement(LayoutComponent, {
47
+ children: pageElement
48
+ });
49
+ const outputPath = join(process.cwd(), outputDir, route.path, "index.html");
28
50
  await generateStaticPage({
29
- component: React.createElement(PageComponent, props),
51
+ component: content,
30
52
  outputPath,
31
53
  props
32
54
  });
@@ -37,13 +59,22 @@ async function processPage(page, route) {
37
59
  }
38
60
  export async function build() {
39
61
  console.log("Building application...");
40
- await mkdir(join(process.cwd(), "dist"), {
62
+ const config = await loadConfig();
63
+ const outputDir = config.static.outputDir;
64
+ console.log(`Using output directory: ${outputDir}`);
65
+ await mkdir(join(process.cwd(), outputDir), {
41
66
  recursive: true
42
67
  });
43
68
  await mkdir(join(process.cwd(), ".xplora"), {
44
69
  recursive: true
45
70
  });
46
- const pages = await glob("src/app/**/*.tsx", {
71
+ const Layout = await loadLayout();
72
+ if (Layout) {
73
+ console.log("Using layout.tsx for HTML structure");
74
+ } else {
75
+ console.log("No layout.tsx found, using default HTML structure");
76
+ }
77
+ const pages = await glob("src/app/**/page.tsx", {
47
78
  ignore: [
48
79
  "**/node_modules/**"
49
80
  ]
@@ -52,7 +83,7 @@ export async function build() {
52
83
  for (const page of pages){
53
84
  const route = convertToRoute(page);
54
85
  routes.push(route);
55
- await processPage(page, route);
86
+ await processPage(page, route, outputDir, Layout);
56
87
  }
57
88
  const routesConfig = {
58
89
  routes,
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/commands/build.ts"],"sourcesContent":["import { mkdir, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { glob } from \"fast-glob\";\nimport React from \"react\";\nimport { generateStaticPage } from \"xplorajs-react\";\n\ninterface Route {\n\tpath: string;\n\tfile: string;\n\tisDynamic: boolean;\n\tparams: string[];\n}\n\nfunction convertToRoute(filePath: string): Route {\n\tconst relativePath = filePath.replace(\"src/app/\", \"\");\n\tconst path = relativePath\n\t\t.replace(/\\.tsx$/, \"\")\n\t\t.replace(/\\/page$/, \"\")\n\t\t.replace(/\\[([^\\]]+)\\]/g, \":$1\");\n\n\tconst params = (relativePath.match(/\\[([^\\]]+)\\]/g) || []).map((param) =>\n\t\tparam.slice(1, -1),\n\t);\n\n\treturn {\n\t\tpath: path === \"page\" ? \"/\" : `/${path}`,\n\t\tfile: filePath,\n\t\tisDynamic: params.length > 0,\n\t\tparams,\n\t};\n}\n\nasync function processPage(page: string, route: Route) {\n\ttry {\n\t\tconst module = await import(join(process.cwd(), page));\n\t\tconst PageComponent = module.default;\n\n\t\tconst getStaticProps = module.getStaticProps;\n\t\tlet props = {};\n\n\t\tif (getStaticProps) {\n\t\t\tconst result = await getStaticProps();\n\t\t\tprops = result.props;\n\t\t}\n\n\t\tconst outputPath = join(process.cwd(), \"dist\", route.path, \"index.html\");\n\t\tawait generateStaticPage({\n\t\t\tcomponent: React.createElement(PageComponent, props),\n\t\t\toutputPath,\n\t\t\tprops,\n\t\t});\n\n\t\tconsole.log(`Generated ${outputPath}`);\n\t} catch (error) {\n\t\tconsole.error(`Error processing ${page}:`, error);\n\t}\n}\n\nexport async function build() {\n\tconsole.log(\"Building application...\");\n\n\tawait mkdir(join(process.cwd(), \"dist\"), { recursive: true });\n\tawait mkdir(join(process.cwd(), \".xplora\"), { recursive: true });\n\n\tconst pages = await glob(\"src/app/**/*.tsx\", {\n\t\tignore: [\"**/node_modules/**\"],\n\t});\n\n\tconst routes: Route[] = [];\n\n\tfor (const page of pages) {\n\t\tconst route = convertToRoute(page);\n\t\troutes.push(route);\n\t\tawait processPage(page, route);\n\t}\n\n\tconst routesConfig = {\n\t\troutes,\n\t\tgeneratedAt: new Date().toISOString(),\n\t};\n\n\tawait writeFile(\n\t\tjoin(process.cwd(), \".xplora\", \"routes.json\"),\n\t\tJSON.stringify(routesConfig, null, 2),\n\t);\n\n\tconsole.log(\"Build completed!\");\n\tconsole.log(\"Routes:\", routes.map((r) => r.path).join(\"\\n\"));\n}\n"],"names":["mkdir","writeFile","join","glob","React","generateStaticPage","convertToRoute","filePath","relativePath","replace","path","params","match","map","param","slice","file","isDynamic","length","processPage","page","route","module","process","cwd","PageComponent","default","getStaticProps","props","result","outputPath","component","createElement","console","log","error","build","recursive","pages","ignore","routes","push","routesConfig","generatedAt","Date","toISOString","JSON","stringify","r"],"mappings":"AAAA,SAASA,KAAK,EAAEC,SAAS,QAAQ,mBAAmB;AACpD,SAASC,IAAI,QAAQ,YAAY;AACjC,SAASC,IAAI,QAAQ,YAAY;AACjC,OAAOC,WAAW,QAAQ;AAC1B,SAASC,kBAAkB,QAAQ,iBAAiB;AASpD,SAASC,eAAeC,QAAgB;IACvC,MAAMC,eAAeD,SAASE,OAAO,CAAC,YAAY;IAClD,MAAMC,OAAOF,aACXC,OAAO,CAAC,UAAU,IAClBA,OAAO,CAAC,WAAW,IACnBA,OAAO,CAAC,iBAAiB;IAE3B,MAAME,SAAS,AAACH,CAAAA,aAAaI,KAAK,CAAC,oBAAoB,EAAE,AAAD,EAAGC,GAAG,CAAC,CAACC,QAC/DA,MAAMC,KAAK,CAAC,GAAG,CAAC;IAGjB,OAAO;QACNL,MAAMA,SAAS,SAAS,MAAM,CAAC,CAAC,EAAEA,MAAM;QACxCM,MAAMT;QACNU,WAAWN,OAAOO,MAAM,GAAG;QAC3BP;IACD;AACD;AAEA,eAAeQ,YAAYC,IAAY,EAAEC,KAAY;IACpD,IAAI;QACH,MAAMC,SAAS,MAAM,MAAM,CAACpB,KAAKqB,QAAQC,GAAG,IAAIJ;QAChD,MAAMK,gBAAgBH,OAAOI,OAAO;QAEpC,MAAMC,iBAAiBL,OAAOK,cAAc;QAC5C,IAAIC,QAAQ,CAAC;QAEb,IAAID,gBAAgB;YACnB,MAAME,SAAS,MAAMF;YACrBC,QAAQC,OAAOD,KAAK;QACrB;QAEA,MAAME,aAAa5B,KAAKqB,QAAQC,GAAG,IAAI,QAAQH,MAAMX,IAAI,EAAE;QAC3D,MAAML,mBAAmB;YACxB0B,WAAW3B,MAAM4B,aAAa,CAACP,eAAeG;YAC9CE;YACAF;QACD;QAEAK,QAAQC,GAAG,CAAC,CAAC,UAAU,EAAEJ,YAAY;IACtC,EAAE,OAAOK,OAAO;QACfF,QAAQE,KAAK,CAAC,CAAC,iBAAiB,EAAEf,KAAK,CAAC,CAAC,EAAEe;IAC5C;AACD;AAEA,OAAO,eAAeC;IACrBH,QAAQC,GAAG,CAAC;IAEZ,MAAMlC,MAAME,KAAKqB,QAAQC,GAAG,IAAI,SAAS;QAAEa,WAAW;IAAK;IAC3D,MAAMrC,MAAME,KAAKqB,QAAQC,GAAG,IAAI,YAAY;QAAEa,WAAW;IAAK;IAE9D,MAAMC,QAAQ,MAAMnC,KAAK,oBAAoB;QAC5CoC,QAAQ;YAAC;SAAqB;IAC/B;IAEA,MAAMC,SAAkB,EAAE;IAE1B,KAAK,MAAMpB,QAAQkB,MAAO;QACzB,MAAMjB,QAAQf,eAAec;QAC7BoB,OAAOC,IAAI,CAACpB;QACZ,MAAMF,YAAYC,MAAMC;IACzB;IAEA,MAAMqB,eAAe;QACpBF;QACAG,aAAa,IAAIC,OAAOC,WAAW;IACpC;IAEA,MAAM5C,UACLC,KAAKqB,QAAQC,GAAG,IAAI,WAAW,gBAC/BsB,KAAKC,SAAS,CAACL,cAAc,MAAM;IAGpCT,QAAQC,GAAG,CAAC;IACZD,QAAQC,GAAG,CAAC,WAAWM,OAAO3B,GAAG,CAAC,CAACmC,IAAMA,EAAEtC,IAAI,EAAER,IAAI,CAAC;AACvD"}
1
+ {"version":3,"sources":["../../src/commands/build.ts"],"sourcesContent":["import { mkdir, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { glob } from \"fast-glob\";\nimport React from \"react\";\nimport type { ComponentType, ReactNode } from \"react\";\nimport { generateStaticPage } from \"xplorajs-react\";\nimport { loadConfig } from \"../config.js\";\nimport { convertToRoute, type Route } from \"../utils/routes.js\";\n\nasync function loadLayout(): Promise<ComponentType<{ children: ReactNode }> | null> {\n\tconst layoutPath = join(process.cwd(), \"src/app/layout.tsx\");\n\tconst file = Bun.file(layoutPath);\n\n\ttry {\n\t\tif (await file.exists()) {\n\t\t\tconst module = await import(layoutPath);\n\t\t\treturn module.default as ComponentType<{ children: ReactNode }>;\n\t\t}\n\t} catch (error) {\n\t\tconsole.error(\"Failed to load layout.tsx:\", error);\n\t}\n\n\treturn null;\n}\n\n/**\n * Create a default layout wrapper when no layout.tsx exists\n */\nfunction DefaultLayout({ children }: { children: ReactNode }) {\n\treturn React.createElement(\n\t\t\"html\",\n\t\t{ lang: \"en\" },\n\t\tReact.createElement(\n\t\t\t\"head\",\n\t\t\tnull,\n\t\t\tReact.createElement(\"meta\", { charSet: \"utf-8\" }),\n\t\t\tReact.createElement(\"meta\", {\n\t\t\t\tname: \"viewport\",\n\t\t\t\tcontent: \"width=device-width, initial-scale=1\",\n\t\t\t}),\n\t\t\tReact.createElement(\"link\", {\n\t\t\t\trel: \"stylesheet\",\n\t\t\t\thref: \"/assets/style.css\",\n\t\t\t}),\n\t\t),\n\t\tReact.createElement(\"body\", null, children),\n\t);\n}\n\nasync function processPage(\n\tpage: string,\n\troute: Route,\n\toutputDir: string,\n\tLayout: ComponentType<{ children: ReactNode }> | null,\n) {\n\ttry {\n\t\tconst module = await import(join(process.cwd(), page));\n\t\tconst PageComponent = module.default;\n\n\t\tconst getStaticProps = module.getStaticProps;\n\t\tlet props = {};\n\n\t\tif (getStaticProps) {\n\t\t\tconst result = await getStaticProps();\n\t\t\tprops = result.props;\n\t\t}\n\n\t\tconst pageElement = React.createElement(PageComponent, props);\n\n\t\t// Wrap with layout (user's layout or default)\n\t\tconst LayoutComponent = Layout || DefaultLayout;\n\t\tconst content = React.createElement(LayoutComponent, { children: pageElement });\n\n\t\tconst outputPath = join(process.cwd(), outputDir, route.path, \"index.html\");\n\t\tawait generateStaticPage({\n\t\t\tcomponent: content,\n\t\t\toutputPath,\n\t\t\tprops,\n\t\t});\n\n\t\tconsole.log(`Generated ${outputPath}`);\n\t} catch (error) {\n\t\tconsole.error(`Error processing ${page}:`, error);\n\t}\n}\n\nexport async function build() {\n\tconsole.log(\"Building application...\");\n\n\t// Load configuration\n\tconst config = await loadConfig();\n\tconst outputDir = config.static.outputDir;\n\n\tconsole.log(`Using output directory: ${outputDir}`);\n\n\tawait mkdir(join(process.cwd(), outputDir), { recursive: true });\n\tawait mkdir(join(process.cwd(), \".xplora\"), { recursive: true });\n\n\t// Load layout\n\tconst Layout = await loadLayout();\n\tif (Layout) {\n\t\tconsole.log(\"Using layout.tsx for HTML structure\");\n\t} else {\n\t\tconsole.log(\"No layout.tsx found, using default HTML structure\");\n\t}\n\n\tconst pages = await glob(\"src/app/**/page.tsx\", {\n\t\tignore: [\"**/node_modules/**\"],\n\t});\n\n\tconst routes: Route[] = [];\n\n\tfor (const page of pages) {\n\t\tconst route = convertToRoute(page);\n\t\troutes.push(route);\n\t\tawait processPage(page, route, outputDir, Layout);\n\t}\n\n\tconst routesConfig = {\n\t\troutes,\n\t\tgeneratedAt: new Date().toISOString(),\n\t};\n\n\tawait writeFile(\n\t\tjoin(process.cwd(), \".xplora\", \"routes.json\"),\n\t\tJSON.stringify(routesConfig, null, 2),\n\t);\n\n\tconsole.log(\"Build completed!\");\n\tconsole.log(\"Routes:\", routes.map((r) => r.path).join(\"\\n\"));\n}\n"],"names":["mkdir","writeFile","join","glob","React","generateStaticPage","loadConfig","convertToRoute","loadLayout","layoutPath","process","cwd","file","Bun","exists","module","default","error","console","DefaultLayout","children","createElement","lang","charSet","name","content","rel","href","processPage","page","route","outputDir","Layout","PageComponent","getStaticProps","props","result","pageElement","LayoutComponent","outputPath","path","component","log","build","config","static","recursive","pages","ignore","routes","push","routesConfig","generatedAt","Date","toISOString","JSON","stringify","map","r"],"mappings":"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"}
@@ -13,7 +13,7 @@ export async function buildCSS() {
13
13
  recursive: true
14
14
  });
15
15
  try {
16
- await $`bunx tailwindcss -i ${inputPath} -o ${outputPath} --minify`.quiet();
16
+ await $`bunx @tailwindcss/cli -i ${inputPath} -o ${outputPath} --minify`.quiet();
17
17
  console.log("CSS built successfully");
18
18
  } catch (error) {
19
19
  console.error("CSS build failed:", error);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/commands/css.ts"],"sourcesContent":["import { join } from \"node:path\";\nimport { mkdir } from \"node:fs/promises\";\nimport { $, file } from \"bun\";\n\n/**\n * Build CSS with Tailwind CSS v4.\n * Reads src/app/styles.css and outputs to dist/assets/style.css\n */\nexport async function buildCSS() {\n const inputPath = join(process.cwd(), \"src/app/styles.css\");\n const outputPath = join(process.cwd(), \"dist/assets/style.css\");\n\n const inputFile = file(inputPath);\n if (!(await inputFile.exists())) {\n console.log(\"No styles.css found, skipping CSS build\");\n return;\n }\n\n await mkdir(join(process.cwd(), \"dist/assets\"), { recursive: true });\n\n try {\n await $`bunx tailwindcss -i ${inputPath} -o ${outputPath} --minify`.quiet();\n console.log(\"CSS built successfully\");\n } catch (error) {\n console.error(\"CSS build failed:\", error);\n throw error;\n }\n}\n"],"names":["join","mkdir","$","file","buildCSS","inputPath","process","cwd","outputPath","inputFile","exists","console","log","recursive","quiet","error"],"mappings":"AAAA,SAASA,IAAI,QAAQ,YAAY;AACjC,SAASC,KAAK,QAAQ,mBAAmB;AACzC,SAASC,CAAC,EAAEC,IAAI,QAAQ,MAAM;AAM9B,OAAO,eAAeC;IACpB,MAAMC,YAAYL,KAAKM,QAAQC,GAAG,IAAI;IACtC,MAAMC,aAAaR,KAAKM,QAAQC,GAAG,IAAI;IAEvC,MAAME,YAAYN,KAAKE;IACvB,IAAI,CAAE,MAAMI,UAAUC,MAAM,IAAK;QAC/BC,QAAQC,GAAG,CAAC;QACZ;IACF;IAEA,MAAMX,MAAMD,KAAKM,QAAQC,GAAG,IAAI,gBAAgB;QAAEM,WAAW;IAAK;IAElE,IAAI;QACF,MAAMX,CAAC,CAAC,oBAAoB,EAAEG,UAAU,IAAI,EAAEG,WAAW,SAAS,CAAC,CAACM,KAAK;QACzEH,QAAQC,GAAG,CAAC;IACd,EAAE,OAAOG,OAAO;QACdJ,QAAQI,KAAK,CAAC,qBAAqBA;QACnC,MAAMA;IACR;AACF"}
1
+ {"version":3,"sources":["../../src/commands/css.ts"],"sourcesContent":["import { join } from \"node:path\";\nimport { mkdir } from \"node:fs/promises\";\nimport { $, file } from \"bun\";\n\n/**\n * Build CSS with Tailwind CSS v4.\n * Reads src/app/styles.css and outputs to dist/assets/style.css\n */\nexport async function buildCSS() {\n const inputPath = join(process.cwd(), \"src/app/styles.css\");\n const outputPath = join(process.cwd(), \"dist/assets/style.css\");\n\n const inputFile = file(inputPath);\n if (!(await inputFile.exists())) {\n console.log(\"No styles.css found, skipping CSS build\");\n return;\n }\n\n await mkdir(join(process.cwd(), \"dist/assets\"), { recursive: true });\n\n try {\n await $`bunx @tailwindcss/cli -i ${inputPath} -o ${outputPath} --minify`.quiet();\n console.log(\"CSS built successfully\");\n } catch (error) {\n console.error(\"CSS build failed:\", error);\n throw error;\n }\n}\n"],"names":["join","mkdir","$","file","buildCSS","inputPath","process","cwd","outputPath","inputFile","exists","console","log","recursive","quiet","error"],"mappings":"AAAA,SAASA,IAAI,QAAQ,YAAY;AACjC,SAASC,KAAK,QAAQ,mBAAmB;AACzC,SAASC,CAAC,EAAEC,IAAI,QAAQ,MAAM;AAM9B,OAAO,eAAeC;IACpB,MAAMC,YAAYL,KAAKM,QAAQC,GAAG,IAAI;IACtC,MAAMC,aAAaR,KAAKM,QAAQC,GAAG,IAAI;IAEvC,MAAME,YAAYN,KAAKE;IACvB,IAAI,CAAE,MAAMI,UAAUC,MAAM,IAAK;QAC/BC,QAAQC,GAAG,CAAC;QACZ;IACF;IAEA,MAAMX,MAAMD,KAAKM,QAAQC,GAAG,IAAI,gBAAgB;QAAEM,WAAW;IAAK;IAElE,IAAI;QACF,MAAMX,CAAC,CAAC,yBAAyB,EAAEG,UAAU,IAAI,EAAEG,WAAW,SAAS,CAAC,CAACM,KAAK;QAC9EH,QAAQC,GAAG,CAAC;IACd,EAAE,OAAOG,OAAO;QACdJ,QAAQI,KAAK,CAAC,qBAAqBA;QACnC,MAAMA;IACR;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/commands/dev.ts"],"names":[],"mappings":"AA8EA,wBAAsB,GAAG,kBAyGxB"}
1
+ {"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/commands/dev.ts"],"names":[],"mappings":"AAgJA,wBAAsB,GAAG,kBAoKxB"}
@@ -1,29 +1,21 @@
1
- import { join } from "node:path";
2
1
  import { mkdir, writeFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
3
  import { serve } from "bun";
4
4
  import { watch } from "chokidar";
5
5
  import { glob } from "fast-glob";
6
6
  import React from "react";
7
7
  import { WebSocketServer } from "ws";
8
8
  import { renderToStream } from "xplorajs-react";
9
- import { buildCSS } from "./css";
9
+ import { loadConfig } from "../config.js";
10
+ import { buildCSS } from "./css.js";
11
+ import { convertToRoute } from "../utils/routes.js";
10
12
  const pages = new Map();
11
- function convertToRoute(filePath) {
12
- const relativePath = filePath.replace("src/app/", "");
13
- const path = relativePath.replace(/\.tsx$/, "").replace(/\/page$/, "").replace(/\[([^\]]+)\]/g, ":$1");
14
- const params = (relativePath.match(/\[([^\]]+)\]/g) || []).map((param)=>param.slice(1, -1));
15
- return {
16
- path: path === "page" ? "/" : `/${path}`,
17
- file: filePath,
18
- isDynamic: params.length > 0,
19
- params
20
- };
21
- }
13
+ let Layout = null;
22
14
  async function generateRoutes() {
23
15
  await mkdir(join(process.cwd(), ".xplora"), {
24
16
  recursive: true
25
17
  });
26
- const pageFiles = await glob("src/app/**/*.tsx", {
18
+ const pageFiles = await glob("src/app/**/page.tsx", {
27
19
  ignore: [
28
20
  "**/node_modules/**"
29
21
  ]
@@ -36,19 +28,91 @@ async function generateRoutes() {
36
28
  await writeFile(join(process.cwd(), ".xplora", "routes.json"), JSON.stringify(routesConfig, null, 2));
37
29
  return routes;
38
30
  }
31
+ async function loadLayout() {
32
+ const layoutPath = join(process.cwd(), "src/app/layout.tsx");
33
+ const file = Bun.file(layoutPath);
34
+ try {
35
+ if (await file.exists()) {
36
+ delete require.cache[layoutPath];
37
+ const module = await import(layoutPath);
38
+ return module.default;
39
+ }
40
+ } catch (error) {
41
+ console.error("Failed to load layout.tsx:", error);
42
+ }
43
+ return null;
44
+ }
39
45
  async function loadPages() {
40
46
  pages.clear();
41
47
  const routes = await generateRoutes();
42
48
  for (const route of routes){
43
49
  const abs = join(process.cwd(), route.file);
44
- delete import.meta.require?.cache?.[abs];
50
+ delete require.cache[abs];
45
51
  pages.set(route.path, (await import(abs)).default);
46
52
  }
47
53
  }
54
+ function createHMRInjectionTransform(wsPort, hmrEnabled) {
55
+ const encoder = new TextEncoder();
56
+ const decoder = new TextDecoder();
57
+ let buffer = "";
58
+ const hmrScripts = hmrEnabled ? `<script>window.__XPLORA_WS_PORT__ = ${wsPort};</script>
59
+ <script type="module" src="/client/hmr.js"></script>
60
+ <script type="module" src="/client/refresh.js"></script>` : "";
61
+ const stylesheetLink = `<link rel="stylesheet" href="/assets/style.css"/>`;
62
+ return new TransformStream({
63
+ transform (chunk, controller) {
64
+ buffer += decoder.decode(chunk, {
65
+ stream: true
66
+ });
67
+ if (buffer.includes("</head>") && !buffer.includes("/assets/style.css")) {
68
+ buffer = buffer.replace("</head>", `${stylesheetLink}</head>`);
69
+ }
70
+ if (buffer.includes("</body>")) {
71
+ buffer = buffer.replace("</body>", `${hmrScripts}</body>`);
72
+ controller.enqueue(encoder.encode(buffer));
73
+ buffer = "";
74
+ }
75
+ },
76
+ flush (controller) {
77
+ if (buffer.length > 0) {
78
+ if (buffer.includes("</body>")) {
79
+ buffer = buffer.replace("</body>", `${hmrScripts}</body>`);
80
+ } else {
81
+ buffer += hmrScripts;
82
+ }
83
+ if (buffer.includes("</head>") && !buffer.includes("/assets/style.css")) {
84
+ buffer = buffer.replace("</head>", `${stylesheetLink}</head>`);
85
+ }
86
+ controller.enqueue(encoder.encode(buffer));
87
+ }
88
+ }
89
+ });
90
+ }
91
+ function createFallbackHtml(wsPort, hmrEnabled) {
92
+ const hmrScripts = hmrEnabled ? `<script>window.__XPLORA_WS_PORT__ = ${wsPort};</script>
93
+ <script type="module" src="/client/refresh.js"></script>
94
+ <script type="module" src="/client/hmr.js"></script>` : "";
95
+ const head = `<!DOCTYPE html><html><head>
96
+ <meta charset="utf-8"/>
97
+ <meta name="viewport" content="width=device-width, initial-scale=1"/>
98
+ <link rel="stylesheet" href="/assets/style.css"/>
99
+ <script>window.process={env:{NODE_ENV:"development"}};</script>
100
+ </head><body><div id="root">`;
101
+ const foot = `</div>
102
+ ${hmrScripts}
103
+ </body></html>`;
104
+ return {
105
+ head,
106
+ foot
107
+ };
108
+ }
48
109
  export async function dev() {
49
110
  console.log("Starting development server...");
111
+ const config = await loadConfig();
112
+ const { port, wsPort, hmr } = config.dev;
113
+ console.log(`Using config: port=${port}, wsPort=${wsPort}, hmr=${hmr}`);
50
114
  const wss = new WebSocketServer({
51
- port: 3001
115
+ port: wsPort
52
116
  });
53
117
  const watcher = watch([
54
118
  "src/**/*"
@@ -58,6 +122,9 @@ export async function dev() {
58
122
  });
59
123
  watcher.on("change", async (path)=>{
60
124
  console.log(`File ${path} has been changed`);
125
+ if (path.endsWith("layout.tsx")) {
126
+ Layout = await loadLayout();
127
+ }
61
128
  if (path.endsWith(".css")) {
62
129
  await buildCSS();
63
130
  for (const client of wss.clients){
@@ -80,8 +147,9 @@ export async function dev() {
80
147
  });
81
148
  await buildCSS();
82
149
  await loadPages();
150
+ Layout = await loadLayout();
83
151
  serve({
84
- port: 3000,
152
+ port,
85
153
  development: true,
86
154
  async fetch (req) {
87
155
  const url = new URL(req.url);
@@ -105,21 +173,64 @@ export async function dev() {
105
173
  if (!Page) return new Response("Not Found", {
106
174
  status: 404
107
175
  });
108
- const stream = await renderToStream(React.createElement(Page));
109
- const head = `<!DOCTYPE html><html><head>
110
- <meta charset="utf-8"/>
111
- <link rel="stylesheet" href="/assets/style.css"/>
112
- <script>window.process={env:{NODE_ENV:"development"}};</script>
113
- </head><body><div id="root">`;
114
- const foot = `</div>
115
- <script type="module" src="/client/refresh.js"></script>
116
- <script type="module" src="/client/hmr.js"></script>
117
- </body></html>`;
176
+ const pageElement = React.createElement(Page);
177
+ if (Layout) {
178
+ const content = React.createElement(Layout, {
179
+ children: pageElement
180
+ });
181
+ const stream = await renderToStream(content);
182
+ if (typeof stream === "string") {
183
+ let html = stream;
184
+ const hmrScripts = hmr ? `<script>window.__XPLORA_WS_PORT__ = ${wsPort};</script>
185
+ <script type="module" src="/client/hmr.js"></script>
186
+ <script type="module" src="/client/refresh.js"></script>` : "";
187
+ const stylesheetLink = `<link rel="stylesheet" href="/assets/style.css"/>`;
188
+ if (html.includes("</head>") && !html.includes("/assets/style.css")) {
189
+ html = html.replace("</head>", `${stylesheetLink}</head>`);
190
+ }
191
+ if (html.includes("</body>")) {
192
+ html = html.replace("</body>", `${hmrScripts}</body>`);
193
+ } else {
194
+ html += hmrScripts;
195
+ }
196
+ return new Response(`<!DOCTYPE html>${html}`, {
197
+ headers: {
198
+ "Content-Type": "text/html; charset=utf-8"
199
+ }
200
+ });
201
+ }
202
+ const transformedStream = stream.pipeThrough(createHMRInjectionTransform(wsPort, hmr));
203
+ return new Response(new ReadableStream({
204
+ async start (controller) {
205
+ const doctype = new TextEncoder().encode("<!DOCTYPE html>");
206
+ controller.enqueue(doctype);
207
+ const reader = transformedStream.getReader();
208
+ try {
209
+ while(true){
210
+ const { done, value } = await reader.read();
211
+ if (done) break;
212
+ controller.enqueue(value);
213
+ }
214
+ controller.close();
215
+ } catch (error) {
216
+ controller.error(error);
217
+ }
218
+ }
219
+ }), {
220
+ headers: {
221
+ "Content-Type": "text/html; charset=utf-8"
222
+ }
223
+ });
224
+ }
225
+ const stream = await renderToStream(pageElement);
226
+ const { head, foot } = createFallbackHtml(wsPort, hmr);
118
227
  return new Response(new ReadableStream({
119
228
  start (ctrl) {
120
229
  ctrl.enqueue(new TextEncoder().encode(head));
121
230
  if (typeof stream === "string") {
122
231
  ctrl.enqueue(new TextEncoder().encode(stream));
232
+ ctrl.enqueue(new TextEncoder().encode(foot));
233
+ ctrl.close();
123
234
  } else {
124
235
  stream.pipeTo(new WritableStream({
125
236
  write (c) {
@@ -139,7 +250,10 @@ export async function dev() {
139
250
  });
140
251
  }
141
252
  });
142
- console.log("Development server running at http://localhost:3000");
253
+ console.log(`Development server running at http://localhost:${port}`);
254
+ if (hmr) {
255
+ console.log(`HMR WebSocket server running on port ${wsPort}`);
256
+ }
143
257
  }
144
258
 
145
259
  //# sourceMappingURL=dev.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/commands/dev.ts"],"sourcesContent":["import { readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { mkdir, writeFile } from \"node:fs/promises\";\nimport { serve } from \"bun\";\nimport { watch } from \"chokidar\";\nimport { glob } from \"fast-glob\";\nimport React from \"react\";\nimport type { ComponentType } from \"react\";\nimport { WebSocketServer } from \"ws\";\nimport { renderToStream } from \"xplorajs-react\";\nimport { buildCSS } from \"./css\";\n\n// biome-ignore lint/suspicious/noExplicitAny: <intended>\nconst pages = new Map<string, ComponentType<Record<string, any>>>();\n\ninterface Route {\n path: string;\n file: string;\n isDynamic: boolean;\n params: string[];\n}\n\nfunction convertToRoute(filePath: string): Route {\n const relativePath = filePath.replace(\"src/app/\", \"\");\n const path = relativePath\n .replace(/\\.tsx$/, \"\")\n .replace(/\\/page$/, \"\")\n .replace(/\\[([^\\]]+)\\]/g, \":$1\");\n\n const params = (relativePath.match(/\\[([^\\]]+)\\]/g) || []).map((param) =>\n param.slice(1, -1),\n );\n\n return {\n path: path === \"page\" ? \"/\" : `/${path}`,\n file: filePath,\n isDynamic: params.length > 0,\n params,\n };\n}\n\nasync function generateRoutes() {\n await mkdir(join(process.cwd(), \".xplora\"), { recursive: true });\n\n const pageFiles = await glob(\"src/app/**/*.tsx\", {\n ignore: [\"**/node_modules/**\"],\n });\n\n const routes: Route[] = pageFiles.map(convertToRoute);\n\n const routesConfig = {\n routes,\n generatedAt: new Date().toISOString(),\n };\n\n await writeFile(\n join(process.cwd(), \".xplora\", \"routes.json\"),\n JSON.stringify(routesConfig, null, 2),\n );\n\n return routes;\n}\n\nasync function loadPages() {\n pages.clear();\n const routes = await generateRoutes();\n\n for (const route of routes) {\n const abs = join(process.cwd(), route.file);\n delete import.meta.require?.cache?.[abs];\n pages.set(\n route.path,\n // biome-ignore lint/suspicious/noExplicitAny: <intended>\n (await import(abs)).default as ComponentType<Record<string, any>>,\n );\n }\n}\n\nexport async function dev() {\n console.log(\"Starting development server...\");\n\n const wss = new WebSocketServer({ port: 3001 });\n\n const watcher = watch([\"src/**/*\"], {\n ignored: /(^|[\\/\\\\])\\../,\n persistent: true,\n });\n\n watcher.on(\"change\", async (path: string) => {\n console.log(`File ${path} has been changed`);\n\n if (path.endsWith(\".css\")) {\n // CSS changed - rebuild CSS and notify clients for CSS-only reload\n await buildCSS();\n for (const client of wss.clients) {\n if (client.readyState === WebSocket.OPEN) {\n client.send(JSON.stringify({ type: \"css\" }));\n }\n }\n } else {\n // TSX/other files changed - reload pages and trigger full reload\n await loadPages();\n for (const client of wss.clients) {\n if (client.readyState === WebSocket.OPEN) {\n client.send(JSON.stringify({ type: \"reload\" }));\n }\n }\n }\n });\n\n await buildCSS();\n await loadPages();\n\n serve({\n port: 3000,\n development: true,\n async fetch(req: Request) {\n const url = new URL(req.url);\n const path = url.pathname;\n\n if (path.startsWith(\"/assets/\")) {\n const f = Bun.file(join(process.cwd(), \"dist\", path));\n if (await f.exists()) return new Response(f);\n }\n\n if (path.startsWith(\"/client/\")) {\n const clientPath = join(\n import.meta.dir,\n \"..\",\n \"client\",\n path.replace(\"/client/\", \"\"),\n );\n const f = Bun.file(clientPath);\n if (await f.exists()) {\n return new Response(f, {\n headers: { \"Content-Type\": \"application/javascript\" },\n });\n }\n }\n\n const Page = pages.get(path);\n if (!Page) return new Response(\"Not Found\", { status: 404 });\n\n const stream = await renderToStream(React.createElement(Page));\n\n const head = `<!DOCTYPE html><html><head>\n\t\t\t\t<meta charset=\"utf-8\"/>\n\t\t\t\t<link rel=\"stylesheet\" href=\"/assets/style.css\"/>\n\t\t\t\t<script>window.process={env:{NODE_ENV:\"development\"}};</script>\n\t\t\t\t</head><body><div id=\"root\">`;\n\n const foot = `</div>\n\t\t\t\t<script type=\"module\" src=\"/client/refresh.js\"></script>\n\t\t\t\t<script type=\"module\" src=\"/client/hmr.js\"></script>\n\t\t\t</body></html>`;\n\n return new Response(\n new ReadableStream({\n start(ctrl) {\n ctrl.enqueue(new TextEncoder().encode(head));\n if (typeof stream === \"string\") {\n ctrl.enqueue(new TextEncoder().encode(stream));\n } else {\n stream.pipeTo(\n new WritableStream({\n write(c) {\n ctrl.enqueue(c);\n },\n close() {\n ctrl.enqueue(new TextEncoder().encode(foot));\n ctrl.close();\n },\n }),\n );\n }\n },\n }),\n { headers: { \"Content-Type\": \"text/html; charset=utf-8\" } },\n );\n },\n });\n\n console.log(\"Development server running at http://localhost:3000\");\n}\n"],"names":["join","mkdir","writeFile","serve","watch","glob","React","WebSocketServer","renderToStream","buildCSS","pages","Map","convertToRoute","filePath","relativePath","replace","path","params","match","map","param","slice","file","isDynamic","length","generateRoutes","process","cwd","recursive","pageFiles","ignore","routes","routesConfig","generatedAt","Date","toISOString","JSON","stringify","loadPages","clear","route","abs","require","cache","set","default","dev","console","log","wss","port","watcher","ignored","persistent","on","endsWith","client","clients","readyState","WebSocket","OPEN","send","type","development","fetch","req","url","URL","pathname","startsWith","f","Bun","exists","Response","clientPath","dir","headers","Page","get","status","stream","createElement","head","foot","ReadableStream","start","ctrl","enqueue","TextEncoder","encode","pipeTo","WritableStream","write","c","close"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/commands/dev.ts"],"sourcesContent":["import { mkdir, writeFile } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { serve } from \"bun\";\nimport { watch } from \"chokidar\";\nimport { glob } from \"fast-glob\";\nimport React from \"react\";\nimport type { ComponentType, ReactNode } from \"react\";\nimport { WebSocketServer } from \"ws\";\nimport { renderToStream } from \"xplorajs-react\";\nimport { loadConfig, type ResolvedConfig } from \"../config.js\";\nimport { buildCSS } from \"./css.js\";\nimport { convertToRoute, type Route } from \"../utils/routes.js\";\n\n// biome-ignore lint/suspicious/noExplicitAny: <intended>\nconst pages = new Map<string, ComponentType<Record<string, any>>>();\n\nlet Layout: ComponentType<{ children: ReactNode }> | null = null;\n\nasync function generateRoutes() {\n\tawait mkdir(join(process.cwd(), \".xplora\"), { recursive: true });\n\n\tconst pageFiles = await glob(\"src/app/**/page.tsx\", {\n\t\tignore: [\"**/node_modules/**\"],\n\t});\n\n\tconst routes: Route[] = pageFiles.map(convertToRoute);\n\n\tconst routesConfig = {\n\t\troutes,\n\t\tgeneratedAt: new Date().toISOString(),\n\t};\n\n\tawait writeFile(\n\t\tjoin(process.cwd(), \".xplora\", \"routes.json\"),\n\t\tJSON.stringify(routesConfig, null, 2),\n\t);\n\n\treturn routes;\n}\n\nasync function loadLayout(): Promise<ComponentType<{ children: ReactNode }> | null> {\n\tconst layoutPath = join(process.cwd(), \"src/app/layout.tsx\");\n\tconst file = Bun.file(layoutPath);\n\n\ttry {\n\t\tif (await file.exists()) {\n\t\t\tdelete require.cache[layoutPath];\n\t\t\tconst module = await import(layoutPath);\n\t\t\treturn module.default as ComponentType<{ children: ReactNode }>;\n\t\t}\n\t} catch (error) {\n\t\tconsole.error(\"Failed to load layout.tsx:\", error);\n\t}\n\n\treturn null;\n}\n\nasync function loadPages() {\n\tpages.clear();\n\tconst routes = await generateRoutes();\n\n\tfor (const route of routes) {\n\t\tconst abs = join(process.cwd(), route.file);\n\t\tdelete require.cache[abs];\n\t\tpages.set(\n\t\t\troute.path,\n\t\t\t// biome-ignore lint/suspicious/noExplicitAny: <intended>\n\t\t\t(await import(abs)).default as ComponentType<Record<string, any>>,\n\t\t);\n\t}\n}\n\n/**\n * Inject HMR scripts before </body> in the HTML stream\n */\nfunction createHMRInjectionTransform(wsPort: number, hmrEnabled: boolean): TransformStream<Uint8Array, Uint8Array> {\n\tconst encoder = new TextEncoder();\n\tconst decoder = new TextDecoder();\n\tlet buffer = \"\";\n\n\tconst hmrScripts = hmrEnabled\n\t\t? `<script>window.__XPLORA_WS_PORT__ = ${wsPort};</script>\n<script type=\"module\" src=\"/client/hmr.js\"></script>\n<script type=\"module\" src=\"/client/refresh.js\"></script>`\n\t\t: \"\";\n\n\tconst stylesheetLink = `<link rel=\"stylesheet\" href=\"/assets/style.css\"/>`;\n\n\treturn new TransformStream({\n\t\ttransform(chunk, controller) {\n\t\t\tbuffer += decoder.decode(chunk, { stream: true });\n\n\t\t\tif (buffer.includes(\"</head>\") && !buffer.includes(\"/assets/style.css\")) {\n\t\t\t\tbuffer = buffer.replace(\"</head>\", `${stylesheetLink}</head>`);\n\t\t\t}\n\n\t\t\tif (buffer.includes(\"</body>\")) {\n\t\t\t\tbuffer = buffer.replace(\"</body>\", `${hmrScripts}</body>`);\n\t\t\t\tcontroller.enqueue(encoder.encode(buffer));\n\t\t\t\tbuffer = \"\";\n\t\t\t}\n\t\t},\n\t\tflush(controller) {\n\t\t\tif (buffer.length > 0) {\n\t\t\t\tif (buffer.includes(\"</body>\")) {\n\t\t\t\t\tbuffer = buffer.replace(\"</body>\", `${hmrScripts}</body>`);\n\t\t\t\t} else {\n\t\t\t\t\tbuffer += hmrScripts;\n\t\t\t\t}\n\n\t\t\t\tif (buffer.includes(\"</head>\") && !buffer.includes(\"/assets/style.css\")) {\n\t\t\t\t\tbuffer = buffer.replace(\"</head>\", `${stylesheetLink}</head>`);\n\t\t\t\t}\n\n\t\t\t\tcontroller.enqueue(encoder.encode(buffer));\n\t\t\t}\n\t\t},\n\t});\n}\n\n/**\n * Create fallback HTML wrapper when no layout.tsx exists\n */\nfunction createFallbackHtml(wsPort: number, hmrEnabled: boolean): { head: string; foot: string } {\n\tconst hmrScripts = hmrEnabled\n\t\t? `<script>window.__XPLORA_WS_PORT__ = ${wsPort};</script>\n\t\t<script type=\"module\" src=\"/client/refresh.js\"></script>\n\t\t<script type=\"module\" src=\"/client/hmr.js\"></script>`\n\t\t: \"\";\n\n\tconst head = `<!DOCTYPE html><html><head>\n\t\t<meta charset=\"utf-8\"/>\n\t\t<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"/>\n\t\t<link rel=\"stylesheet\" href=\"/assets/style.css\"/>\n\t\t<script>window.process={env:{NODE_ENV:\"development\"}};</script>\n\t\t</head><body><div id=\"root\">`;\n\n\tconst foot = `</div>\n\t\t${hmrScripts}\n\t</body></html>`;\n\n\treturn { head, foot };\n}\n\nexport async function dev() {\n\tconsole.log(\"Starting development server...\");\n\n\tconst config = await loadConfig();\n\tconst { port, wsPort, hmr } = config.dev;\n\n\tconsole.log(`Using config: port=${port}, wsPort=${wsPort}, hmr=${hmr}`);\n\n\tconst wss = new WebSocketServer({ port: wsPort });\n\n\tconst watcher = watch([\"src/**/*\"], {\n\t\tignored: /(^|[\\/\\\\])\\../,\n\t\tpersistent: true,\n\t});\n\n\twatcher.on(\"change\", async (path: string) => {\n\t\tconsole.log(`File ${path} has been changed`);\n\n\t\tif (path.endsWith(\"layout.tsx\")) {\n\t\t\tLayout = await loadLayout();\n\t\t}\n\n\t\tif (path.endsWith(\".css\")) {\n\t\t\tawait buildCSS();\n\t\t\tfor (const client of wss.clients) {\n\t\t\t\tif (client.readyState === WebSocket.OPEN) {\n\t\t\t\t\tclient.send(JSON.stringify({ type: \"css\" }));\n\t\t\t\t}\n\t\t\t}\n\t\t} else {\n\t\t\tawait loadPages();\n\t\t\tfor (const client of wss.clients) {\n\t\t\t\tif (client.readyState === WebSocket.OPEN) {\n\t\t\t\t\tclient.send(JSON.stringify({ type: \"reload\" }));\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t});\n\n\tawait buildCSS();\n\tawait loadPages();\n\tLayout = await loadLayout();\n\n\tserve({\n\t\tport,\n\t\tdevelopment: true,\n\t\tasync fetch(req: Request) {\n\t\t\tconst url = new URL(req.url);\n\t\t\tconst path = url.pathname;\n\n\t\t\tif (path.startsWith(\"/assets/\")) {\n\t\t\t\tconst f = Bun.file(join(process.cwd(), \"dist\", path));\n\t\t\t\tif (await f.exists()) return new Response(f);\n\t\t\t}\n\n\t\t\tif (path.startsWith(\"/client/\")) {\n\t\t\t\tconst clientPath = join(\n\t\t\t\t\timport.meta.dir,\n\t\t\t\t\t\"..\",\n\t\t\t\t\t\"client\",\n\t\t\t\t\tpath.replace(\"/client/\", \"\"),\n\t\t\t\t);\n\t\t\t\tconst f = Bun.file(clientPath);\n\t\t\t\tif (await f.exists()) {\n\t\t\t\t\treturn new Response(f, {\n\t\t\t\t\t\theaders: { \"Content-Type\": \"application/javascript\" },\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst Page = pages.get(path);\n\t\t\tif (!Page) return new Response(\"Not Found\", { status: 404 });\n\n\t\t\tconst pageElement = React.createElement(Page);\n\n\t\t\tif (Layout) {\n\t\t\t\tconst content = React.createElement(Layout, { children: pageElement });\n\t\t\t\tconst stream = await renderToStream(content);\n\n\t\t\t\tif (typeof stream === \"string\") {\n\t\t\t\t\tlet html = stream;\n\t\t\t\t\tconst hmrScripts = hmr\n\t\t\t\t\t\t? `<script>window.__XPLORA_WS_PORT__ = ${wsPort};</script>\n<script type=\"module\" src=\"/client/hmr.js\"></script>\n<script type=\"module\" src=\"/client/refresh.js\"></script>`\n\t\t\t\t\t\t: \"\";\n\t\t\t\t\tconst stylesheetLink = `<link rel=\"stylesheet\" href=\"/assets/style.css\"/>`;\n\n\t\t\t\t\tif (html.includes(\"</head>\") && !html.includes(\"/assets/style.css\")) {\n\t\t\t\t\t\thtml = html.replace(\"</head>\", `${stylesheetLink}</head>`);\n\t\t\t\t\t}\n\t\t\t\t\tif (html.includes(\"</body>\")) {\n\t\t\t\t\t\thtml = html.replace(\"</body>\", `${hmrScripts}</body>`);\n\t\t\t\t\t} else {\n\t\t\t\t\t\thtml += hmrScripts;\n\t\t\t\t\t}\n\n\t\t\t\t\treturn new Response(`<!DOCTYPE html>${html}`, {\n\t\t\t\t\t\theaders: { \"Content-Type\": \"text/html; charset=utf-8\" },\n\t\t\t\t\t});\n\t\t\t\t}\n\n\t\t\t\tconst transformedStream = stream.pipeThrough(\n\t\t\t\t\tcreateHMRInjectionTransform(wsPort, hmr),\n\t\t\t\t);\n\n\t\t\t\treturn new Response(\n\t\t\t\t\tnew ReadableStream({\n\t\t\t\t\t\tasync start(controller) {\n\t\t\t\t\t\t\tconst doctype = new TextEncoder().encode(\"<!DOCTYPE html>\");\n\t\t\t\t\t\t\tcontroller.enqueue(doctype);\n\n\t\t\t\t\t\t\tconst reader = transformedStream.getReader();\n\t\t\t\t\t\t\ttry {\n\t\t\t\t\t\t\t\twhile (true) {\n\t\t\t\t\t\t\t\t\tconst { done, value } = await reader.read();\n\t\t\t\t\t\t\t\t\tif (done) break;\n\t\t\t\t\t\t\t\t\tcontroller.enqueue(value);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\tcontroller.close();\n\t\t\t\t\t\t\t} catch (error) {\n\t\t\t\t\t\t\t\tcontroller.error(error);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t},\n\t\t\t\t\t}),\n\t\t\t\t\t{ headers: { \"Content-Type\": \"text/html; charset=utf-8\" } },\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tconst stream = await renderToStream(pageElement);\n\t\t\tconst { head, foot } = createFallbackHtml(wsPort, hmr);\n\n\t\t\treturn new Response(\n\t\t\t\tnew ReadableStream({\n\t\t\t\t\tstart(ctrl) {\n\t\t\t\t\t\tctrl.enqueue(new TextEncoder().encode(head));\n\t\t\t\t\t\tif (typeof stream === \"string\") {\n\t\t\t\t\t\t\tctrl.enqueue(new TextEncoder().encode(stream));\n\t\t\t\t\t\t\tctrl.enqueue(new TextEncoder().encode(foot));\n\t\t\t\t\t\t\tctrl.close();\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tstream.pipeTo(\n\t\t\t\t\t\t\t\tnew WritableStream({\n\t\t\t\t\t\t\t\t\twrite(c) {\n\t\t\t\t\t\t\t\t\t\tctrl.enqueue(c);\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t\tclose() {\n\t\t\t\t\t\t\t\t\t\tctrl.enqueue(new TextEncoder().encode(foot));\n\t\t\t\t\t\t\t\t\t\tctrl.close();\n\t\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t\t{ headers: { \"Content-Type\": \"text/html; charset=utf-8\" } },\n\t\t\t);\n\t\t},\n\t});\n\n\tconsole.log(`Development server running at http://localhost:${port}`);\n\tif (hmr) {\n\t\tconsole.log(`HMR WebSocket server running on port ${wsPort}`);\n\t}\n}\n"],"names":["mkdir","writeFile","join","serve","watch","glob","React","WebSocketServer","renderToStream","loadConfig","buildCSS","convertToRoute","pages","Map","Layout","generateRoutes","process","cwd","recursive","pageFiles","ignore","routes","map","routesConfig","generatedAt","Date","toISOString","JSON","stringify","loadLayout","layoutPath","file","Bun","exists","require","cache","module","default","error","console","loadPages","clear","route","abs","set","path","createHMRInjectionTransform","wsPort","hmrEnabled","encoder","TextEncoder","decoder","TextDecoder","buffer","hmrScripts","stylesheetLink","TransformStream","transform","chunk","controller","decode","stream","includes","replace","enqueue","encode","flush","length","createFallbackHtml","head","foot","dev","log","config","port","hmr","wss","watcher","ignored","persistent","on","endsWith","client","clients","readyState","WebSocket","OPEN","send","type","development","fetch","req","url","URL","pathname","startsWith","f","Response","clientPath","dir","headers","Page","get","status","pageElement","createElement","content","children","html","transformedStream","pipeThrough","ReadableStream","start","doctype","reader","getReader","done","value","read","close","ctrl","pipeTo","WritableStream","write","c"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"start.d.ts","sourceRoot":"","sources":["../../src/commands/start.ts"],"names":[],"mappings":"AAGA,wBAAsB,KAAK,kBAkC1B"}
1
+ {"version":3,"file":"start.d.ts","sourceRoot":"","sources":["../../src/commands/start.ts"],"names":[],"mappings":"AAIA,wBAAsB,KAAK,kBAmC1B"}
@@ -1,16 +1,17 @@
1
1
  import { join } from "node:path";
2
2
  import { serve } from "bun";
3
+ import { loadConfig } from "../config.js";
3
4
  export async function start() {
4
5
  console.log("Starting production server...");
5
- const config = {
6
- port: 3000
7
- };
6
+ const config = await loadConfig();
7
+ const { port } = config.dev;
8
+ const { outputDir } = config.static;
9
+ const distDir = join(process.cwd(), outputDir);
8
10
  serve({
9
- port: config.port,
11
+ port,
10
12
  async fetch (req) {
11
13
  const url = new URL(req.url);
12
14
  const pathname = url.pathname;
13
- const distDir = join(process.cwd(), "dist");
14
15
  let filePath;
15
16
  if (pathname.includes(".")) {
16
17
  filePath = join(distDir, pathname);
@@ -26,7 +27,8 @@ export async function start() {
26
27
  });
27
28
  }
28
29
  });
29
- console.log("Production server running at http://localhost:3000");
30
+ console.log(`Production server running at http://localhost:${port}`);
31
+ console.log(`Serving files from: ${distDir}`);
30
32
  }
31
33
 
32
34
  //# sourceMappingURL=start.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/commands/start.ts"],"sourcesContent":["import { join } from \"node:path\";\nimport { serve } from \"bun\";\n\nexport async function start() {\n console.log(\"Starting production server...\");\n\n const config = {\n port: 3000,\n };\n\n serve({\n port: config.port,\n async fetch(req: Request) {\n const url = new URL(req.url);\n const pathname = url.pathname;\n\n const distDir = join(process.cwd(), \"dist\");\n\n let filePath: string;\n\n if (pathname.includes(\".\")) {\n filePath = join(distDir, pathname);\n } else {\n filePath = join(distDir, pathname, \"index.html\");\n }\n\n const file = Bun.file(filePath);\n\n if (await file.exists()) {\n return new Response(file);\n }\n\n return new Response(\"Not found\", { status: 404 });\n },\n });\n\n console.log(\"Production server running at http://localhost:3000\");\n}\n"],"names":["join","serve","start","console","log","config","port","fetch","req","url","URL","pathname","distDir","process","cwd","filePath","includes","file","Bun","exists","Response","status"],"mappings":"AAAA,SAASA,IAAI,QAAQ,YAAY;AACjC,SAASC,KAAK,QAAQ,MAAM;AAE5B,OAAO,eAAeC;IACpBC,QAAQC,GAAG,CAAC;IAEZ,MAAMC,SAAS;QACbC,MAAM;IACR;IAEAL,MAAM;QACJK,MAAMD,OAAOC,IAAI;QACjB,MAAMC,OAAMC,GAAY;YACtB,MAAMC,MAAM,IAAIC,IAAIF,IAAIC,GAAG;YAC3B,MAAME,WAAWF,IAAIE,QAAQ;YAE7B,MAAMC,UAAUZ,KAAKa,QAAQC,GAAG,IAAI;YAEpC,IAAIC;YAEJ,IAAIJ,SAASK,QAAQ,CAAC,MAAM;gBAC1BD,WAAWf,KAAKY,SAASD;YAC3B,OAAO;gBACLI,WAAWf,KAAKY,SAASD,UAAU;YACrC;YAEA,MAAMM,OAAOC,IAAID,IAAI,CAACF;YAEtB,IAAI,MAAME,KAAKE,MAAM,IAAI;gBACvB,OAAO,IAAIC,SAASH;YACtB;YAEA,OAAO,IAAIG,SAAS,aAAa;gBAAEC,QAAQ;YAAI;QACjD;IACF;IAEAlB,QAAQC,GAAG,CAAC;AACd"}
1
+ {"version":3,"sources":["../../src/commands/start.ts"],"sourcesContent":["import { join } from \"node:path\";\nimport { serve } from \"bun\";\nimport { loadConfig } from \"../config.js\";\n\nexport async function start() {\n\tconsole.log(\"Starting production server...\");\n\n\tconst config = await loadConfig();\n\tconst { port } = config.dev;\n\tconst { outputDir } = config.static;\n\n\tconst distDir = join(process.cwd(), outputDir);\n\n\tserve({\n\t\tport,\n\t\tasync fetch(req: Request) {\n\t\t\tconst url = new URL(req.url);\n\t\t\tconst pathname = url.pathname;\n\n\t\t\tlet filePath: string;\n\n\t\t\tif (pathname.includes(\".\")) {\n\t\t\t\tfilePath = join(distDir, pathname);\n\t\t\t} else {\n\t\t\t\tfilePath = join(distDir, pathname, \"index.html\");\n\t\t\t}\n\n\t\t\tconst file = Bun.file(filePath);\n\n\t\t\tif (await file.exists()) {\n\t\t\t\treturn new Response(file);\n\t\t\t}\n\n\t\t\treturn new Response(\"Not found\", { status: 404 });\n\t\t},\n\t});\n\n\tconsole.log(`Production server running at http://localhost:${port}`);\n\tconsole.log(`Serving files from: ${distDir}`);\n}\n"],"names":["join","serve","loadConfig","start","console","log","config","port","dev","outputDir","static","distDir","process","cwd","fetch","req","url","URL","pathname","filePath","includes","file","Bun","exists","Response","status"],"mappings":"AAAA,SAASA,IAAI,QAAQ,YAAY;AACjC,SAASC,KAAK,QAAQ,MAAM;AAC5B,SAASC,UAAU,QAAQ,eAAe;AAE1C,OAAO,eAAeC;IACrBC,QAAQC,GAAG,CAAC;IAEZ,MAAMC,SAAS,MAAMJ;IACrB,MAAM,EAAEK,IAAI,EAAE,GAAGD,OAAOE,GAAG;IAC3B,MAAM,EAAEC,SAAS,EAAE,GAAGH,OAAOI,MAAM;IAEnC,MAAMC,UAAUX,KAAKY,QAAQC,GAAG,IAAIJ;IAEpCR,MAAM;QACLM;QACA,MAAMO,OAAMC,GAAY;YACvB,MAAMC,MAAM,IAAIC,IAAIF,IAAIC,GAAG;YAC3B,MAAME,WAAWF,IAAIE,QAAQ;YAE7B,IAAIC;YAEJ,IAAID,SAASE,QAAQ,CAAC,MAAM;gBAC3BD,WAAWnB,KAAKW,SAASO;YAC1B,OAAO;gBACNC,WAAWnB,KAAKW,SAASO,UAAU;YACpC;YAEA,MAAMG,OAAOC,IAAID,IAAI,CAACF;YAEtB,IAAI,MAAME,KAAKE,MAAM,IAAI;gBACxB,OAAO,IAAIC,SAASH;YACrB;YAEA,OAAO,IAAIG,SAAS,aAAa;gBAAEC,QAAQ;YAAI;QAChD;IACD;IAEArB,QAAQC,GAAG,CAAC,CAAC,8CAA8C,EAAEE,MAAM;IACnEH,QAAQC,GAAG,CAAC,CAAC,oBAAoB,EAAEM,SAAS;AAC7C"}
package/dist/config.d.ts CHANGED
@@ -1,6 +1,7 @@
1
- interface XploraConfig {
1
+ export interface XploraConfig {
2
2
  dev?: {
3
3
  port?: number;
4
+ wsPort?: number;
4
5
  hmr?: boolean;
5
6
  };
6
7
  static?: {
@@ -13,7 +14,31 @@ interface XploraConfig {
13
14
  sourcemap?: boolean;
14
15
  };
15
16
  }
16
- declare function defineConfig(config: XploraConfig): XploraConfig;
17
- export type { XploraConfig };
18
- export { defineConfig };
17
+ export interface ResolvedConfig {
18
+ dev: {
19
+ port: number;
20
+ wsPort: number;
21
+ hmr: boolean;
22
+ };
23
+ static: {
24
+ outputDir: string;
25
+ revalidate: number;
26
+ fallback: boolean;
27
+ };
28
+ build: {
29
+ minify: boolean;
30
+ sourcemap: boolean;
31
+ };
32
+ }
33
+ declare const DEFAULT_CONFIG: ResolvedConfig;
34
+ /**
35
+ * Define a configuration object with defaults merged in
36
+ */
37
+ declare function defineConfig(config: XploraConfig): ResolvedConfig;
38
+ /**
39
+ * Load configuration from xplora.config.ts in the current working directory.
40
+ * Falls back to default configuration if no config file is found.
41
+ */
42
+ declare function loadConfig(): Promise<ResolvedConfig>;
43
+ export { defineConfig, loadConfig, DEFAULT_CONFIG };
19
44
  //# sourceMappingURL=config.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,UAAU,YAAY;IACpB,GAAG,CAAC,EAAE;QACJ,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,GAAG,CAAC,EAAE,OAAO,CAAC;KACf,CAAC;IACF,MAAM,CAAC,EAAE;QACP,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;KACpB,CAAC;IACF,KAAK,CAAC,EAAE;QACN,MAAM,CAAC,EAAE,OAAO,CAAC;QACjB,SAAS,CAAC,EAAE,OAAO,CAAC;KACrB,CAAC;CACH;AAED,iBAAS,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,YAAY,CAmBxD;AAED,YAAY,EAAE,YAAY,EAAE,CAAC;AAC7B,OAAO,EAAE,YAAY,EAAE,CAAC"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,YAAY;IAC5B,GAAG,CAAC,EAAE;QACL,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,GAAG,CAAC,EAAE,OAAO,CAAC;KACd,CAAC;IACF,MAAM,CAAC,EAAE;QACR,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,QAAQ,CAAC,EAAE,OAAO,CAAC;KACnB,CAAC;IACF,KAAK,CAAC,EAAE;QACP,MAAM,CAAC,EAAE,OAAO,CAAC;QACjB,SAAS,CAAC,EAAE,OAAO,CAAC;KACpB,CAAC;CACF;AAED,MAAM,WAAW,cAAc;IAC9B,GAAG,EAAE;QACJ,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,GAAG,EAAE,OAAO,CAAC;KACb,CAAC;IACF,MAAM,EAAE;QACP,SAAS,EAAE,MAAM,CAAC;QAClB,UAAU,EAAE,MAAM,CAAC;QACnB,QAAQ,EAAE,OAAO,CAAC;KAClB,CAAC;IACF,KAAK,EAAE;QACN,MAAM,EAAE,OAAO,CAAC;QAChB,SAAS,EAAE,OAAO,CAAC;KACnB,CAAC;CACF;AAED,QAAA,MAAM,cAAc,EAAE,cAerB,CAAC;AAEF;;GAEG;AACH,iBAAS,YAAY,CAAC,MAAM,EAAE,YAAY,GAAG,cAAc,CAiB1D;AAED;;;GAGG;AACH,iBAAe,UAAU,IAAI,OAAO,CAAC,cAAc,CAAC,CAkBnD;AAED,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,cAAc,EAAE,CAAC"}
package/dist/config.js CHANGED
@@ -1,23 +1,55 @@
1
+ import { existsSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ const DEFAULT_CONFIG = {
4
+ dev: {
5
+ port: 3000,
6
+ wsPort: 3001,
7
+ hmr: true
8
+ },
9
+ static: {
10
+ outputDir: "./dist",
11
+ revalidate: 3600,
12
+ fallback: false
13
+ },
14
+ build: {
15
+ minify: true,
16
+ sourcemap: true
17
+ }
18
+ };
1
19
  function defineConfig(config) {
2
20
  return {
3
21
  dev: {
4
- port: 3000,
5
- hmr: true,
6
- ...config.dev
22
+ port: config.dev?.port ?? DEFAULT_CONFIG.dev.port,
23
+ wsPort: config.dev?.wsPort ?? (config.dev?.port ? config.dev.port + 1 : DEFAULT_CONFIG.dev.wsPort),
24
+ hmr: config.dev?.hmr ?? DEFAULT_CONFIG.dev.hmr
7
25
  },
8
26
  static: {
9
- outputDir: "./dist",
10
- revalidate: 3600,
11
- fallback: false,
12
- ...config.static
27
+ outputDir: config.static?.outputDir ?? DEFAULT_CONFIG.static.outputDir,
28
+ revalidate: config.static?.revalidate ?? DEFAULT_CONFIG.static.revalidate,
29
+ fallback: config.static?.fallback ?? DEFAULT_CONFIG.static.fallback
13
30
  },
14
31
  build: {
15
- minify: true,
16
- sourcemap: true,
17
- ...config.build
32
+ minify: config.build?.minify ?? DEFAULT_CONFIG.build.minify,
33
+ sourcemap: config.build?.sourcemap ?? DEFAULT_CONFIG.build.sourcemap
18
34
  }
19
35
  };
20
36
  }
21
- export { defineConfig };
37
+ async function loadConfig() {
38
+ const configPath = join(process.cwd(), "xplora.config.ts");
39
+ try {
40
+ if (existsSync(configPath)) {
41
+ delete require.cache[configPath];
42
+ const userConfig = (await import(configPath)).default;
43
+ return defineConfig(userConfig);
44
+ }
45
+ } catch (error) {
46
+ console.warn("Warning: Failed to load xplora.config.ts, using defaults");
47
+ if (error instanceof Error) {
48
+ console.warn(` Reason: ${error.message}`);
49
+ }
50
+ }
51
+ return defineConfig({});
52
+ }
53
+ export { defineConfig, loadConfig, DEFAULT_CONFIG };
22
54
 
23
55
  //# sourceMappingURL=config.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/config.ts"],"sourcesContent":["interface XploraConfig {\n dev?: {\n port?: number;\n hmr?: boolean;\n };\n static?: {\n outputDir?: string;\n revalidate?: number;\n fallback?: boolean;\n };\n build?: {\n minify?: boolean;\n sourcemap?: boolean;\n };\n}\n\nfunction defineConfig(config: XploraConfig): XploraConfig {\n return {\n dev: {\n port: 3000,\n hmr: true,\n ...config.dev,\n },\n static: {\n outputDir: \"./dist\",\n revalidate: 3600,\n fallback: false,\n ...config.static,\n },\n build: {\n minify: true,\n sourcemap: true,\n ...config.build,\n },\n };\n}\n\nexport type { XploraConfig };\nexport { defineConfig };\n"],"names":["defineConfig","config","dev","port","hmr","static","outputDir","revalidate","fallback","build","minify","sourcemap"],"mappings":"AAgBA,SAASA,aAAaC,MAAoB;IACxC,OAAO;QACLC,KAAK;YACHC,MAAM;YACNC,KAAK;YACL,GAAGH,OAAOC,GAAG;QACf;QACAG,QAAQ;YACNC,WAAW;YACXC,YAAY;YACZC,UAAU;YACV,GAAGP,OAAOI,MAAM;QAClB;QACAI,OAAO;YACLC,QAAQ;YACRC,WAAW;YACX,GAAGV,OAAOQ,KAAK;QACjB;IACF;AACF;AAGA,SAAST,YAAY,GAAG"}
1
+ {"version":3,"sources":["../src/config.ts"],"sourcesContent":["import { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nexport interface XploraConfig {\n\tdev?: {\n\t\tport?: number;\n\t\twsPort?: number;\n\t\thmr?: boolean;\n\t};\n\tstatic?: {\n\t\toutputDir?: string;\n\t\trevalidate?: number;\n\t\tfallback?: boolean;\n\t};\n\tbuild?: {\n\t\tminify?: boolean;\n\t\tsourcemap?: boolean;\n\t};\n}\n\nexport interface ResolvedConfig {\n\tdev: {\n\t\tport: number;\n\t\twsPort: number;\n\t\thmr: boolean;\n\t};\n\tstatic: {\n\t\toutputDir: string;\n\t\trevalidate: number;\n\t\tfallback: boolean;\n\t};\n\tbuild: {\n\t\tminify: boolean;\n\t\tsourcemap: boolean;\n\t};\n}\n\nconst DEFAULT_CONFIG: ResolvedConfig = {\n\tdev: {\n\t\tport: 3000,\n\t\twsPort: 3001,\n\t\thmr: true,\n\t},\n\tstatic: {\n\t\toutputDir: \"./dist\",\n\t\trevalidate: 3600,\n\t\tfallback: false,\n\t},\n\tbuild: {\n\t\tminify: true,\n\t\tsourcemap: true,\n\t},\n};\n\n/**\n * Define a configuration object with defaults merged in\n */\nfunction defineConfig(config: XploraConfig): ResolvedConfig {\n\treturn {\n\t\tdev: {\n\t\t\tport: config.dev?.port ?? DEFAULT_CONFIG.dev.port,\n\t\t\twsPort: config.dev?.wsPort ?? (config.dev?.port ? config.dev.port + 1 : DEFAULT_CONFIG.dev.wsPort),\n\t\t\thmr: config.dev?.hmr ?? DEFAULT_CONFIG.dev.hmr,\n\t\t},\n\t\tstatic: {\n\t\t\toutputDir: config.static?.outputDir ?? DEFAULT_CONFIG.static.outputDir,\n\t\t\trevalidate: config.static?.revalidate ?? DEFAULT_CONFIG.static.revalidate,\n\t\t\tfallback: config.static?.fallback ?? DEFAULT_CONFIG.static.fallback,\n\t\t},\n\t\tbuild: {\n\t\t\tminify: config.build?.minify ?? DEFAULT_CONFIG.build.minify,\n\t\t\tsourcemap: config.build?.sourcemap ?? DEFAULT_CONFIG.build.sourcemap,\n\t\t},\n\t};\n}\n\n/**\n * Load configuration from xplora.config.ts in the current working directory.\n * Falls back to default configuration if no config file is found.\n */\nasync function loadConfig(): Promise<ResolvedConfig> {\n\tconst configPath = join(process.cwd(), \"xplora.config.ts\");\n\n\ttry {\n\t\tif (existsSync(configPath)) {\n\t\t\t// Clear cache to support HMR of config\n\t\t\tdelete require.cache[configPath];\n\t\t\tconst userConfig = (await import(configPath)).default as XploraConfig;\n\t\t\treturn defineConfig(userConfig);\n\t\t}\n\t} catch (error) {\n\t\tconsole.warn(\"Warning: Failed to load xplora.config.ts, using defaults\");\n\t\tif (error instanceof Error) {\n\t\t\tconsole.warn(` Reason: ${error.message}`);\n\t\t}\n\t}\n\n\treturn defineConfig({});\n}\n\nexport { defineConfig, loadConfig, DEFAULT_CONFIG };\n"],"names":["existsSync","join","DEFAULT_CONFIG","dev","port","wsPort","hmr","static","outputDir","revalidate","fallback","build","minify","sourcemap","defineConfig","config","loadConfig","configPath","process","cwd","require","cache","userConfig","default","error","console","warn","Error","message"],"mappings":"AAAA,SAASA,UAAU,QAAQ,UAAU;AACrC,SAASC,IAAI,QAAQ,YAAY;AAoCjC,MAAMC,iBAAiC;IACtCC,KAAK;QACJC,MAAM;QACNC,QAAQ;QACRC,KAAK;IACN;IACAC,QAAQ;QACPC,WAAW;QACXC,YAAY;QACZC,UAAU;IACX;IACAC,OAAO;QACNC,QAAQ;QACRC,WAAW;IACZ;AACD;AAKA,SAASC,aAAaC,MAAoB;IACzC,OAAO;QACNZ,KAAK;YACJC,MAAMW,OAAOZ,GAAG,EAAEC,QAAQF,eAAeC,GAAG,CAACC,IAAI;YACjDC,QAAQU,OAAOZ,GAAG,EAAEE,UAAWU,CAAAA,OAAOZ,GAAG,EAAEC,OAAOW,OAAOZ,GAAG,CAACC,IAAI,GAAG,IAAIF,eAAeC,GAAG,CAACE,MAAM,AAAD;YAChGC,KAAKS,OAAOZ,GAAG,EAAEG,OAAOJ,eAAeC,GAAG,CAACG,GAAG;QAC/C;QACAC,QAAQ;YACPC,WAAWO,OAAOR,MAAM,EAAEC,aAAaN,eAAeK,MAAM,CAACC,SAAS;YACtEC,YAAYM,OAAOR,MAAM,EAAEE,cAAcP,eAAeK,MAAM,CAACE,UAAU;YACzEC,UAAUK,OAAOR,MAAM,EAAEG,YAAYR,eAAeK,MAAM,CAACG,QAAQ;QACpE;QACAC,OAAO;YACNC,QAAQG,OAAOJ,KAAK,EAAEC,UAAUV,eAAeS,KAAK,CAACC,MAAM;YAC3DC,WAAWE,OAAOJ,KAAK,EAAEE,aAAaX,eAAeS,KAAK,CAACE,SAAS;QACrE;IACD;AACD;AAMA,eAAeG;IACd,MAAMC,aAAahB,KAAKiB,QAAQC,GAAG,IAAI;IAEvC,IAAI;QACH,IAAInB,WAAWiB,aAAa;YAE3B,OAAOG,QAAQC,KAAK,CAACJ,WAAW;YAChC,MAAMK,aAAa,AAAC,CAAA,MAAM,MAAM,CAACL,WAAU,EAAGM,OAAO;YACrD,OAAOT,aAAaQ;QACrB;IACD,EAAE,OAAOE,OAAO;QACfC,QAAQC,IAAI,CAAC;QACb,IAAIF,iBAAiBG,OAAO;YAC3BF,QAAQC,IAAI,CAAC,CAAC,UAAU,EAAEF,MAAMI,OAAO,EAAE;QAC1C;IACD;IAEA,OAAOd,aAAa,CAAC;AACtB;AAEA,SAASA,YAAY,EAAEE,UAAU,EAAEd,cAAc,GAAG"}
package/dist/index.d.ts CHANGED
@@ -1,3 +1,5 @@
1
- export type { XploraConfig } from "./config";
2
- export { defineConfig } from "./config";
1
+ export type { XploraConfig, ResolvedConfig } from "./config";
2
+ export { defineConfig, loadConfig } from "./config";
3
+ export type { Route } from "./utils/routes";
4
+ export { convertToRoute } from "./utils/routes";
3
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,YAAY,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,UAAU,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AACpD,YAAY,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAC5C,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC"}
package/dist/index.js CHANGED
@@ -1,3 +1,4 @@
1
- export { defineConfig } from "./config";
1
+ export { defineConfig, loadConfig } from "./config";
2
+ export { convertToRoute } from "./utils/routes";
2
3
 
3
4
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export type { XploraConfig } from \"./config\";\nexport { defineConfig } from \"./config\";\n"],"names":["defineConfig"],"mappings":"AACA,SAASA,YAAY,QAAQ,WAAW"}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export type { XploraConfig, ResolvedConfig } from \"./config\";\nexport { defineConfig, loadConfig } from \"./config\";\nexport type { Route } from \"./utils/routes\";\nexport { convertToRoute } from \"./utils/routes\";\n"],"names":["defineConfig","loadConfig","convertToRoute"],"mappings":"AACA,SAASA,YAAY,EAAEC,UAAU,QAAQ,WAAW;AAEpD,SAASC,cAAc,QAAQ,iBAAiB"}
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Shared route utilities for XploraJS
3
+ */
4
+ export interface Route {
5
+ path: string;
6
+ file: string;
7
+ isDynamic: boolean;
8
+ params: string[];
9
+ }
10
+ /**
11
+ * Convert a file path to a Route object
12
+ * @param filePath - The file path relative to the project root (e.g., "src/app/page.tsx")
13
+ * @returns Route object with path, file, isDynamic, and params
14
+ */
15
+ export declare function convertToRoute(filePath: string): Route;
16
+ //# sourceMappingURL=routes.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/utils/routes.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,MAAM,WAAW,KAAK;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,OAAO,CAAC;IACnB,MAAM,EAAE,MAAM,EAAE,CAAC;CACjB;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,KAAK,CAiBtD"}
@@ -0,0 +1,13 @@
1
+ export function convertToRoute(filePath) {
2
+ const relativePath = filePath.replace("src/app/", "");
3
+ const path = relativePath.replace(/\.tsx$/, "").replace(/\/page$/, "").replace(/\[([^\]]+)\]/g, ":$1");
4
+ const params = (relativePath.match(/\[([^\]]+)\]/g) || []).map((param)=>param.slice(1, -1));
5
+ return {
6
+ path: path === "page" ? "/" : `/${path}`,
7
+ file: filePath,
8
+ isDynamic: params.length > 0,
9
+ params
10
+ };
11
+ }
12
+
13
+ //# sourceMappingURL=routes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/utils/routes.ts"],"sourcesContent":["/**\n * Shared route utilities for XploraJS\n */\n\nexport interface Route {\n\tpath: string;\n\tfile: string;\n\tisDynamic: boolean;\n\tparams: string[];\n}\n\n/**\n * Convert a file path to a Route object\n * @param filePath - The file path relative to the project root (e.g., \"src/app/page.tsx\")\n * @returns Route object with path, file, isDynamic, and params\n */\nexport function convertToRoute(filePath: string): Route {\n\tconst relativePath = filePath.replace(\"src/app/\", \"\");\n\tconst path = relativePath\n\t\t.replace(/\\.tsx$/, \"\")\n\t\t.replace(/\\/page$/, \"\")\n\t\t.replace(/\\[([^\\]]+)\\]/g, \":$1\");\n\n\tconst params = (relativePath.match(/\\[([^\\]]+)\\]/g) || []).map((param) =>\n\t\tparam.slice(1, -1),\n\t);\n\n\treturn {\n\t\tpath: path === \"page\" ? \"/\" : `/${path}`,\n\t\tfile: filePath,\n\t\tisDynamic: params.length > 0,\n\t\tparams,\n\t};\n}\n"],"names":["convertToRoute","filePath","relativePath","replace","path","params","match","map","param","slice","file","isDynamic","length"],"mappings":"AAgBA,OAAO,SAASA,eAAeC,QAAgB;IAC9C,MAAMC,eAAeD,SAASE,OAAO,CAAC,YAAY;IAClD,MAAMC,OAAOF,aACXC,OAAO,CAAC,UAAU,IAClBA,OAAO,CAAC,WAAW,IACnBA,OAAO,CAAC,iBAAiB;IAE3B,MAAME,SAAS,AAACH,CAAAA,aAAaI,KAAK,CAAC,oBAAoB,EAAE,AAAD,EAAGC,GAAG,CAAC,CAACC,QAC/DA,MAAMC,KAAK,CAAC,GAAG,CAAC;IAGjB,OAAO;QACNL,MAAMA,SAAS,SAAS,MAAM,CAAC,CAAC,EAAEA,MAAM;QACxCM,MAAMT;QACNU,WAAWN,OAAOO,MAAM,GAAG;QAC3BP;IACD;AACD"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "xplorajs",
3
- "version": "0.3.7",
3
+ "version": "0.3.9",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",