@jxsuite/compiler 0.13.0 → 0.14.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jxsuite/compiler",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "description": "Jx static HTML compiler, island detector, and site builder",
5
5
  "license": "MIT",
6
6
  "repository": {
package/src/compiler.js CHANGED
@@ -22,7 +22,11 @@ import {
22
22
  DEFAULT_REACTIVITY_SRC,
23
23
  DEFAULT_LIT_HTML_SRC,
24
24
  } from "./shared.js";
25
- import { compileServer, compileSiteServer } from "./targets/compile-server.js";
25
+ import {
26
+ compileServer,
27
+ compileSiteServer,
28
+ compilePagesFunctions,
29
+ } from "./targets/compile-server.js";
26
30
  import {
27
31
  compileElement,
28
32
  compileElementPage,
@@ -36,6 +40,7 @@ export {
36
40
  isDynamic,
37
41
  compileServer,
38
42
  compileSiteServer,
43
+ compilePagesFunctions,
39
44
  compileElement,
40
45
  compileElementPage,
41
46
  compileClient,
@@ -25,7 +25,7 @@ import { discoverPages, expandDynamicRoutes } from "./pages-discovery.js";
25
25
  import { resolveLayout } from "./layout-resolver.js";
26
26
  import { mergeHead, renderHead } from "./head-merger.js";
27
27
  import { injectContext } from "./context-injection.js";
28
- import { compile, compileServer, compileSiteServer } from "../compiler.js";
28
+ import { compile, compileServer, compileSiteServer, compilePagesFunctions } from "../compiler.js";
29
29
  import { compileElement } from "../targets/compile-element.js";
30
30
  import {
31
31
  buildInitialScope,
@@ -177,7 +177,7 @@ export async function buildSite(projectRoot, options = {}) {
177
177
  for (const [, doc] of componentDefs) {
178
178
  const entries = collectServerEntries(doc);
179
179
  for (const entry of entries) {
180
- const resolvedSrc = "./" + join("components", entry.src.replace(/^\.\//, ""));
180
+ const resolvedSrc = "./components/" + entry.src.replace(/^\.\//, "");
181
181
  siteServerEntries.push({ exportName: entry.exportName, src: resolvedSrc });
182
182
  }
183
183
  }
@@ -269,6 +269,7 @@ export async function buildSite(projectRoot, options = {}) {
269
269
 
270
270
  // ── 6c. Generate site-wide server worker ────────────────────────────────
271
271
  if (projectConfig.build.adapter) {
272
+ const adapter = projectConfig.build.adapter;
272
273
  log("Generating site-wide server worker...");
273
274
 
274
275
  const deduped = new Map();
@@ -276,18 +277,19 @@ export async function buildSite(projectRoot, options = {}) {
276
277
  if (!deduped.has(entry.exportName)) deduped.set(entry.exportName, entry);
277
278
  }
278
279
 
279
- const workerSource = compileSiteServer([...deduped.values()], {
280
- adapter: projectConfig.build.adapter,
281
- });
280
+ if (adapter === "cloudflare-pages") {
281
+ const functions = compilePagesFunctions([...deduped.values()]);
282
282
 
283
- if (workerSource) {
284
- const workerPath = resolve(outDir, "worker.js");
285
- writeFileSync(workerPath, workerSource, "utf8");
286
- fileCount++;
287
- log(` Generated dist/worker.js (${deduped.size} server function(s))`);
283
+ for (const [filePath, source] of functions) {
284
+ const fullPath = resolve(outDir, filePath);
285
+ mkdirSync(resolve(fullPath, ".."), { recursive: true });
286
+ writeFileSync(fullPath, source, "utf8");
287
+ fileCount++;
288
+ }
288
289
 
289
- // Copy server source files into dist/components/ so worker imports resolve
290
+ // Copy server source files so function imports resolve
290
291
  const distComponentsDir = resolve(outDir, "components");
292
+ mkdirSync(distComponentsDir, { recursive: true });
291
293
  for (const { src } of deduped.values()) {
292
294
  const srcFile = resolve(projectRoot, src.replace(/^\.\//, ""));
293
295
  const destFile = resolve(distComponentsDir, src.replace(/^\.\/components\//, ""));
@@ -295,6 +297,29 @@ export async function buildSite(projectRoot, options = {}) {
295
297
  copyFileSync(srcFile, destFile);
296
298
  }
297
299
  }
300
+
301
+ if (functions.size > 0) {
302
+ log(` Generated ${functions.size} Pages function(s) in dist/functions/`);
303
+ }
304
+ } else {
305
+ const workerSource = compileSiteServer([...deduped.values()], { adapter });
306
+
307
+ if (workerSource) {
308
+ const workerPath = resolve(outDir, "worker.js");
309
+ writeFileSync(workerPath, workerSource, "utf8");
310
+ fileCount++;
311
+ log(` Generated dist/worker.js (${deduped.size} server function(s))`);
312
+
313
+ // Copy server source files into dist/components/ so worker imports resolve
314
+ const distComponentsDir = resolve(outDir, "components");
315
+ for (const { src } of deduped.values()) {
316
+ const srcFile = resolve(projectRoot, src.replace(/^\.\//, ""));
317
+ const destFile = resolve(distComponentsDir, src.replace(/^\.\/components\//, ""));
318
+ if (existsSync(srcFile)) {
319
+ copyFileSync(srcFile, destFile);
320
+ }
321
+ }
322
+ }
298
323
  }
299
324
  }
300
325
 
@@ -87,6 +87,15 @@ export function loadProjectConfig(projectRoot) {
87
87
  if (raw.imports) config.imports = raw.imports;
88
88
  if (raw.contentTypes) config.contentTypes = raw.contentTypes;
89
89
 
90
+ // Validate adapter
91
+ const VALID_ADAPTERS = ["cloudflare-workers", "cloudflare-pages", "node", "bun"];
92
+ if (config.build.adapter && !VALID_ADAPTERS.includes(config.build.adapter)) {
93
+ throw new Error(
94
+ `Unknown build adapter "${config.build.adapter}" in project.json. ` +
95
+ `Valid adapters: ${VALID_ADAPTERS.join(", ")}`,
96
+ );
97
+ }
98
+
90
99
  return {
91
100
  config,
92
101
  configPath,
@@ -69,7 +69,9 @@ export function compileSiteServer(entries, opts = {}) {
69
69
  const routes = entries.map(({ exportName }) => buildRoute(exportName, baseUrl)).join("\n");
70
70
 
71
71
  const adapterBlock =
72
- adapter === "cloudflare" ? "\napp.all('*', (c) => c.env.ASSETS.fetch(c.req.raw))\n" : "\n";
72
+ adapter === "cloudflare-workers"
73
+ ? "\napp.all('*', (c) => c.env.ASSETS.fetch(c.req.raw))\n"
74
+ : "\n";
73
75
 
74
76
  return `// Generated by @jxsuite/compiler — do not edit manually
75
77
  import { Hono } from 'hono'
@@ -94,3 +96,42 @@ app.post('${baseUrl}/${exportName}', async (c) => {
94
96
  }
95
97
  })`;
96
98
  }
99
+
100
+ /**
101
+ * Generate Cloudflare Pages Functions from pre-collected server entries. Returns a Map of relative
102
+ * file paths (under `functions/`) to source strings.
103
+ *
104
+ * @param {{ exportName: string; src: string }[]} entries - Resolved server entries
105
+ * @param {object} [opts]
106
+ * @param {string} [opts.baseUrl] - Base path prefix. Default is `'/_jx/server'`
107
+ * @returns {Map<string, string>} Map of output path → source (e.g.
108
+ * `"functions/_jx/server/sendEmail.js"` → source)
109
+ */
110
+ export function compilePagesFunctions(entries, opts = {}) {
111
+ const { baseUrl = "/_jx/server" } = opts;
112
+ const files = new Map();
113
+
114
+ for (const { exportName, src } of entries) {
115
+ const relativeSrc = src.startsWith("./") ? src : `./${src}`;
116
+ const funcPath = `functions${baseUrl}/${exportName}.js`;
117
+ const depth = funcPath.split("/").length - 1;
118
+ const importPath = "../".repeat(depth) + relativeSrc.replace(/^\.\//, "");
119
+
120
+ const source = `// Generated by @jxsuite/compiler — do not edit manually
121
+ import { ${exportName} } from '${importPath}'
122
+
123
+ export async function onRequestPost(context) {
124
+ const args = await context.request.json().catch(() => ({}))
125
+ try {
126
+ const result = await ${exportName}(args, context.env)
127
+ return Response.json(result)
128
+ } catch (e) {
129
+ return Response.json({ ok: false, error: e?.message ?? 'Server error' }, { status: 500 })
130
+ }
131
+ }
132
+ `;
133
+ files.set(funcPath, source);
134
+ }
135
+
136
+ return files;
137
+ }