@jay-framework/dev-server 0.16.1 → 0.16.3

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.
Files changed (2) hide show
  1. package/dist/index.js +98 -36
  2. package/package.json +14 -14
package/dist/index.js CHANGED
@@ -13,15 +13,15 @@ import path__default from "node:path";
13
13
  import { JAY_IMPORT_RESOLVER, injectHeadfullFSTemplates, parseContract, slowRenderTransform, discoverHeadlessInstances, assignCoordinatesToJayHtml, resolveHeadlessInstances } from "@jay-framework/compiler-jay-html";
14
14
  import { getLogger, getDevLogger } from "@jay-framework/logger";
15
15
  import { createRequire as createRequire$1 } from "node:module";
16
- import * as fs from "node:fs";
17
- import fs__default$1 from "node:fs";
16
+ import * as fsSync from "node:fs";
17
+ import fsSync__default from "node:fs";
18
18
  import { scanRoutes, createRoute, routeToExpressRoute } from "@jay-framework/stack-route-scanner";
19
19
  import { discoverPluginsWithInit, sortPluginsByDependencies, executePluginServerInits, runInitCallbacks, actionRegistry, discoverAndRegisterActions, discoverAllPluginActions, runShutdownCallbacks, clearLifecycleCallbacks, clearServiceRegistry, clearClientInitData, loadPageParts, runLoadParams, DevSlowlyChangingPhase, SlowRenderCache, preparePluginClientInits, registerService, clearServerElementCache, scanPlugins, getServiceRegistry, materializeContracts, renderFastChangingData, mergeHeadTags, generateClientScript, getClientInitData, generateSSRPageHtml, generateFrozenPageHtml, validateForEachInstances, slowRenderInstances } from "@jay-framework/stack-server-runtime";
20
- import * as fs$1 from "node:fs/promises";
20
+ import * as fs from "node:fs/promises";
21
21
  import fs__default from "node:fs/promises";
22
22
  import { pathToFileURL } from "node:url";
23
23
  import Busboy from "busboy";
24
- import fs$2 from "fs";
24
+ import fs$1 from "fs";
25
25
  import path$1 from "path";
26
26
  import crypto from "crypto";
27
27
  import { randomUUID } from "node:crypto";
@@ -1318,16 +1318,16 @@ function jayStackCompiler(options = {}) {
1318
1318
  const importerDir = path.dirname(importer);
1319
1319
  let resolvedPath = path.resolve(importerDir, source);
1320
1320
  if (!resolvedPath.endsWith(".ts") && !resolvedPath.endsWith(".js")) {
1321
- if (fs.existsSync(resolvedPath + ".ts")) {
1321
+ if (fsSync.existsSync(resolvedPath + ".ts")) {
1322
1322
  resolvedPath += ".ts";
1323
- } else if (fs.existsSync(resolvedPath + ".js")) {
1323
+ } else if (fsSync.existsSync(resolvedPath + ".js")) {
1324
1324
  resolvedPath += ".js";
1325
1325
  } else {
1326
1326
  return null;
1327
1327
  }
1328
- } else if (resolvedPath.endsWith(".js") && !fs.existsSync(resolvedPath)) {
1328
+ } else if (resolvedPath.endsWith(".js") && !fsSync.existsSync(resolvedPath)) {
1329
1329
  const tsPath = resolvedPath.slice(0, -3) + ".ts";
1330
- if (fs.existsSync(tsPath)) {
1330
+ if (fsSync.existsSync(tsPath)) {
1331
1331
  resolvedPath = tsPath;
1332
1332
  } else {
1333
1333
  return null;
@@ -1342,7 +1342,7 @@ function jayStackCompiler(options = {}) {
1342
1342
  const actualPath = id.slice("\0jay-action:".length);
1343
1343
  let code;
1344
1344
  try {
1345
- code = await fs.promises.readFile(actualPath, "utf-8");
1345
+ code = await fsSync.promises.readFile(actualPath, "utf-8");
1346
1346
  } catch (err) {
1347
1347
  getLogger().error(
1348
1348
  `[action-transform] Could not read ${actualPath}: ${err}`
@@ -1417,6 +1417,22 @@ async function createViteServer(options) {
1417
1417
  base,
1418
1418
  // Root directory for module resolution
1419
1419
  root: pagesRoot,
1420
+ // Force singleton resolution for all runtime packages.
1421
+ // Without this, pre-bundled deps (in .vite/deps/) inline their own copy
1422
+ // of these packages, while plugin client bundles (served as raw ESM via /@fs/)
1423
+ // resolve to a separate instance → duplicate createJayContext() → runtime crash.
1424
+ resolve: {
1425
+ dedupe: [
1426
+ "@jay-framework/runtime",
1427
+ "@jay-framework/reactive",
1428
+ "@jay-framework/component",
1429
+ "@jay-framework/fullstack-component",
1430
+ "@jay-framework/stack-client-runtime",
1431
+ "@jay-framework/runtime-automation",
1432
+ "@jay-framework/view-state-merge",
1433
+ "@jay-framework/json-patch"
1434
+ ]
1435
+ },
1420
1436
  // SSR configuration
1421
1437
  ssr: {
1422
1438
  // Mark jay-framework packages as external so Vite uses Node's require
@@ -1424,7 +1440,10 @@ async function createViteServer(options) {
1424
1440
  external: ["@jay-framework/stack-server-runtime", "@jay-framework/fullstack-component"]
1425
1441
  },
1426
1442
  // Disable automatic entry point discovery for pre-bundling —
1427
- // we run in middleware mode with no HTML files, so Vite can't auto-detect entries
1443
+ // we run in middleware mode with no HTML files, so Vite can't auto-detect entries.
1444
+ // Explicitly include singleton packages so Vite pre-bundles them as separate entries.
1445
+ // Without this, stack-client-runtime inlines its own copy of component/reactive/runtime
1446
+ // into the .vite/deps/ chunk, while plugin client bundles import the raw ESM copy → duplicates.
1428
1447
  optimizeDeps: {
1429
1448
  entries: []
1430
1449
  },
@@ -1482,7 +1501,7 @@ class ServiceLifecycleManager {
1482
1501
  const extensions = [".ts", ".js"];
1483
1502
  for (const ext of extensions) {
1484
1503
  const filePath = path.join(this.projectRoot, this.sourceBase, "init" + ext);
1485
- if (fs.existsSync(filePath)) {
1504
+ if (fsSync.existsSync(filePath)) {
1486
1505
  return filePath;
1487
1506
  }
1488
1507
  }
@@ -1863,7 +1882,7 @@ const DEFAULT_MAX_FILE_SIZE = 10 * 1024 * 1024;
1863
1882
  const DEFAULT_MAX_FILES = 10;
1864
1883
  function parseMultipart(req, tempDir, maxFileSize, maxFiles) {
1865
1884
  return new Promise((resolve, reject) => {
1866
- fs$2.mkdirSync(tempDir, { recursive: true });
1885
+ fs$1.mkdirSync(tempDir, { recursive: true });
1867
1886
  const files = {};
1868
1887
  let jsonData = {};
1869
1888
  let fileCount = 0;
@@ -1884,7 +1903,7 @@ function parseMultipart(req, tempDir, maxFileSize, maxFiles) {
1884
1903
  const tempPath = path$1.join(tempDir, `${fileCount}-${filename}`);
1885
1904
  let size = 0;
1886
1905
  let truncated = false;
1887
- const writeStream = fs$2.createWriteStream(tempPath);
1906
+ const writeStream = fs$1.createWriteStream(tempPath);
1888
1907
  stream.pipe(writeStream);
1889
1908
  stream.on("data", (data) => {
1890
1909
  size += data.length;
@@ -1952,7 +1971,7 @@ function parseMultipart(req, tempDir, maxFileSize, maxFiles) {
1952
1971
  }
1953
1972
  function cleanupTempDir(dir) {
1954
1973
  try {
1955
- fs$2.rmSync(dir, { recursive: true, force: true });
1974
+ fs$1.rmSync(dir, { recursive: true, force: true });
1956
1975
  } catch {
1957
1976
  }
1958
1977
  }
@@ -2032,7 +2051,7 @@ class FreezeStore {
2032
2051
  this.dir = path.join(buildFolder, "freezes");
2033
2052
  }
2034
2053
  async save(route, viewState, routePattern) {
2035
- await fs$1.mkdir(this.dir, { recursive: true });
2054
+ await fs.mkdir(this.dir, { recursive: true });
2036
2055
  const entry = {
2037
2056
  id: randomUUID().slice(0, 8),
2038
2057
  route,
@@ -2040,7 +2059,7 @@ class FreezeStore {
2040
2059
  viewState,
2041
2060
  createdAt: (/* @__PURE__ */ new Date()).toISOString()
2042
2061
  };
2043
- await fs$1.writeFile(
2062
+ await fs.writeFile(
2044
2063
  path.join(this.dir, `${entry.id}.json`),
2045
2064
  JSON.stringify(entry, null, 2),
2046
2065
  "utf-8"
@@ -2049,7 +2068,7 @@ class FreezeStore {
2049
2068
  }
2050
2069
  async get(id) {
2051
2070
  try {
2052
- const content = await fs$1.readFile(path.join(this.dir, `${id}.json`), "utf-8");
2071
+ const content = await fs.readFile(path.join(this.dir, `${id}.json`), "utf-8");
2053
2072
  return JSON.parse(content);
2054
2073
  } catch {
2055
2074
  return void 0;
@@ -2057,13 +2076,13 @@ class FreezeStore {
2057
2076
  }
2058
2077
  async list(route) {
2059
2078
  try {
2060
- const files = await fs$1.readdir(this.dir);
2079
+ const files = await fs.readdir(this.dir);
2061
2080
  const entries = [];
2062
2081
  for (const file of files) {
2063
2082
  if (!file.endsWith(".json"))
2064
2083
  continue;
2065
2084
  try {
2066
- const content = await fs$1.readFile(path.join(this.dir, file), "utf-8");
2085
+ const content = await fs.readFile(path.join(this.dir, file), "utf-8");
2067
2086
  const entry = JSON.parse(content);
2068
2087
  if (!route || entry.routePattern === route || entry.route === route) {
2069
2088
  entries.push(entry);
@@ -2083,7 +2102,7 @@ class FreezeStore {
2083
2102
  if (!entry)
2084
2103
  return false;
2085
2104
  entry.name = name;
2086
- await fs$1.writeFile(
2105
+ await fs.writeFile(
2087
2106
  path.join(this.dir, `${id}.json`),
2088
2107
  JSON.stringify(entry, null, 2),
2089
2108
  "utf-8"
@@ -2092,7 +2111,7 @@ class FreezeStore {
2092
2111
  }
2093
2112
  async delete(id) {
2094
2113
  try {
2095
- await fs$1.unlink(path.join(this.dir, `${id}.json`));
2114
+ await fs.unlink(path.join(this.dir, `${id}.json`));
2096
2115
  return true;
2097
2116
  } catch {
2098
2117
  return false;
@@ -2173,8 +2192,10 @@ async function scanPluginRoutes(projectRoot, projectRoutes) {
2173
2192
  );
2174
2193
  continue;
2175
2194
  }
2176
- const compPath = route.component.startsWith(".") ? path__default.resolve(plugin.pluginPath, route.component) : resolvePluginModule(plugin);
2177
- pluginRoutes.push(createRoute(route.path, jayHtmlPath, compPath));
2195
+ const isLocalComponent = route.component.startsWith(".");
2196
+ const compPath = isLocalComponent ? path__default.resolve(plugin.pluginPath, route.component) : resolvePluginModule(plugin);
2197
+ const componentExport = isLocalComponent ? void 0 : route.component;
2198
+ pluginRoutes.push(createRoute(route.path, jayHtmlPath, compPath, componentExport));
2178
2199
  getLogger().info(`[Routes] Plugin "${plugin.name}" provides route ${route.path}`);
2179
2200
  }
2180
2201
  }
@@ -2184,7 +2205,7 @@ function resolvePluginExport(pluginPath, exportSubpath) {
2184
2205
  const normalized = exportSubpath.replace(/^\.\//, "");
2185
2206
  const packageJsonPath = path__default.join(pluginPath, "package.json");
2186
2207
  try {
2187
- const packageJson = JSON.parse(fs__default$1.readFileSync(packageJsonPath, "utf-8"));
2208
+ const packageJson = JSON.parse(fsSync__default.readFileSync(packageJsonPath, "utf-8"));
2188
2209
  if (packageJson.exports) {
2189
2210
  const exportKey = "./" + normalized;
2190
2211
  const exportValue = packageJson.exports[exportKey];
@@ -2201,7 +2222,7 @@ function resolvePluginExport(pluginPath, exportSubpath) {
2201
2222
  for (const dir of ["dist", "lib", ""]) {
2202
2223
  const candidate = path__default.join(pluginPath, dir, normalized);
2203
2224
  try {
2204
- fs__default$1.accessSync(candidate);
2225
+ fsSync__default.accessSync(candidate);
2205
2226
  return candidate;
2206
2227
  } catch {
2207
2228
  }
@@ -2209,11 +2230,25 @@ function resolvePluginExport(pluginPath, exportSubpath) {
2209
2230
  return void 0;
2210
2231
  }
2211
2232
  function resolvePluginModule(plugin) {
2233
+ const pkgJsonPath = path__default.join(plugin.pluginPath, "package.json");
2234
+ if (fsSync__default.existsSync(pkgJsonPath)) {
2235
+ try {
2236
+ const pkg = JSON.parse(fsSync__default.readFileSync(pkgJsonPath, "utf-8"));
2237
+ const mainExport = pkg.exports?.["."];
2238
+ const mainPath = typeof mainExport === "string" ? mainExport : mainExport?.default || mainExport?.import || pkg.main;
2239
+ if (mainPath) {
2240
+ const resolved = path__default.join(plugin.pluginPath, mainPath);
2241
+ if (fsSync__default.existsSync(resolved))
2242
+ return resolved;
2243
+ }
2244
+ } catch {
2245
+ }
2246
+ }
2212
2247
  const modulePath = plugin.manifest.module || "index";
2213
2248
  for (const ext of [".ts", ".js", "/index.ts", "/index.js"]) {
2214
2249
  const candidate = path__default.join(plugin.pluginPath, modulePath + ext);
2215
2250
  try {
2216
- fs__default$1.accessSync(candidate);
2251
+ fsSync__default.accessSync(candidate);
2217
2252
  return candidate;
2218
2253
  } catch {
2219
2254
  }
@@ -2221,7 +2256,7 @@ function resolvePluginModule(plugin) {
2221
2256
  for (const ext of [".ts", ".js"]) {
2222
2257
  const candidate = path__default.join(plugin.pluginPath, "lib", path__default.basename(modulePath) + ext);
2223
2258
  try {
2224
- fs__default$1.accessSync(candidate);
2259
+ fsSync__default.accessSync(candidate);
2225
2260
  return candidate;
2226
2261
  } catch {
2227
2262
  }
@@ -2339,7 +2374,11 @@ function mkRoute(route, vite, slowlyPhase, options, slowRenderCache, freezeStore
2339
2374
  query
2340
2375
  );
2341
2376
  } else {
2342
- const cachedEntry = await slowRenderCache.get(route.jayHtmlPath, pageParams);
2377
+ const cachedEntry = await slowRenderCache.get(
2378
+ route.jayHtmlPath,
2379
+ pageParams,
2380
+ getRouteDir(route)
2381
+ );
2343
2382
  if (cachedEntry) {
2344
2383
  await handleCachedRequest(
2345
2384
  vite,
@@ -2455,6 +2494,7 @@ async function handleCachedRequest(vite, route, options, cachedEntry, pageParams
2455
2494
  pluginsForPage,
2456
2495
  options,
2457
2496
  routeToExpressRoute(route),
2497
+ getRouteDir(route),
2458
2498
  cachedEntry.slowViewState,
2459
2499
  timing,
2460
2500
  cachedEntry.preRenderedContent,
@@ -2529,7 +2569,8 @@ async function handlePreRenderRequest(vite, route, options, slowlyPhase, slowRen
2529
2569
  pageParams,
2530
2570
  preRenderResult.preRenderedJayHtml,
2531
2571
  renderedSlowly.rendered,
2532
- carryForward
2572
+ carryForward,
2573
+ getRouteDir(route)
2533
2574
  );
2534
2575
  getLogger().info(`[SlowRender] Cached pre-rendered jay-html at ${cachedEntry.preRenderedPath}`);
2535
2576
  await handleCachedRequest(
@@ -2650,9 +2691,8 @@ async function handleClientOnlyRequest(vite, route, options, slowlyPhase, pagePa
2650
2691
  res.status(200).set({ "Content-Type": "text/html" }).send(compiledPageHtml);
2651
2692
  timing?.end();
2652
2693
  }
2653
- async function sendResponse(vite, res, url, jayHtmlPath, sourceJayHtmlPath, pageParts, viewState, carryForward, clientTrackByMap, projectInit, pluginsForPage, options, routePattern, slowViewState, timing, preLoadedContent, headTags) {
2694
+ async function sendResponse(vite, res, url, jayHtmlPath, sourceJayHtmlPath, pageParts, viewState, carryForward, clientTrackByMap, projectInit, pluginsForPage, options, routePattern, routeDir, slowViewState, timing, preLoadedContent, headTags) {
2654
2695
  let pageHtml;
2655
- const routeDir = path__default.dirname(path__default.relative(options.pagesRootFolder, sourceJayHtmlPath));
2656
2696
  try {
2657
2697
  let jayHtmlContent = preLoadedContent ?? await fs__default.readFile(jayHtmlPath, "utf-8");
2658
2698
  const jayHtmlFilename = path__default.basename(jayHtmlPath);
@@ -2726,13 +2766,13 @@ async function handleFrozenRequest(vite, route, options, freezeStore, slowRender
2726
2766
  const label = entry.name ? `"${entry.name}" (${freezeId})` : freezeId;
2727
2767
  getLogger().info(`[Freeze] Serving frozen page ${label} for ${route.rawRoute} [${format}]`);
2728
2768
  try {
2729
- const cachedEntry = await slowRenderCache.get(route.jayHtmlPath, pageParams);
2769
+ const routeDir = getRouteDir(route);
2770
+ const cachedEntry = await slowRenderCache.get(route.jayHtmlPath, pageParams, routeDir);
2730
2771
  const jayHtmlPath = cachedEntry?.preRenderedPath ?? route.jayHtmlPath;
2731
2772
  const jayHtmlContent = cachedEntry?.preRenderedContent ?? await fs__default.readFile(jayHtmlPath, "utf-8");
2732
2773
  const jayHtmlFilename = path__default.basename(jayHtmlPath);
2733
2774
  const jayHtmlDir = path__default.dirname(jayHtmlPath);
2734
2775
  const sourceDir = path__default.dirname(route.jayHtmlPath);
2735
- const routeDir = path__default.dirname(path__default.relative(options.pagesRootFolder, route.jayHtmlPath));
2736
2776
  const { injectHeadfullFSTemplates: injectHeadfullFSTemplates2 } = await import("@jay-framework/compiler-jay-html");
2737
2777
  const { JAY_IMPORT_RESOLVER: JAY_IMPORT_RESOLVER2 } = await import("@jay-framework/compiler-jay-html");
2738
2778
  const fullJayHtml = injectHeadfullFSTemplates2(
@@ -3053,6 +3093,28 @@ function setupFreezeEndpoint(vite, freezeStore) {
3053
3093
  });
3054
3094
  getLogger().info("[Freeze] Freeze endpoint mounted at /_jay/freeze");
3055
3095
  }
3096
+ function getRouteDir(route) {
3097
+ return route.rawRoute.replace(/^\//, "") || "index";
3098
+ }
3099
+ function getRoutePrefix(jayHtmlPath, pagesRootFolder) {
3100
+ const rel = path__default.relative(pagesRootFolder, path__default.dirname(jayHtmlPath));
3101
+ const segments = rel.split(path__default.sep).filter(Boolean);
3102
+ const staticSegments = [];
3103
+ for (const seg of segments) {
3104
+ if (seg.startsWith("["))
3105
+ break;
3106
+ staticSegments.push(seg);
3107
+ }
3108
+ return "/" + staticSegments.join("/");
3109
+ }
3110
+ function sendPageReload(vite, jayHtmlPath, pagesRootFolder) {
3111
+ const routePrefix = getRoutePrefix(jayHtmlPath, pagesRootFolder);
3112
+ vite.ws.send({
3113
+ type: "custom",
3114
+ event: "jay:page-reload",
3115
+ data: { routePrefix }
3116
+ });
3117
+ }
3056
3118
  function setupSlowRenderCacheInvalidation(vite, cache, pagesRootFolder, projectRootFolder) {
3057
3119
  const watchedFiles = /* @__PURE__ */ new Set();
3058
3120
  const watchLinkedFiles = (files) => {
@@ -3079,7 +3141,7 @@ function setupSlowRenderCacheInvalidation(vite, cache, pagesRootFolder, projectR
3079
3141
  clearServerElementCache();
3080
3142
  cache.clear().then(() => {
3081
3143
  getLogger().info(`[SlowRender] Cache cleared (jay-html changed: ${changedPath})`);
3082
- vite.ws.send({ type: "full-reload" });
3144
+ sendPageReload(vite, changedPath, pagesRootFolder);
3083
3145
  });
3084
3146
  return;
3085
3147
  }
@@ -3094,7 +3156,7 @@ function setupSlowRenderCacheInvalidation(vite, cache, pagesRootFolder, projectR
3094
3156
  getLogger().info(
3095
3157
  `[SlowRender] Cache invalidated for ${jayHtmlPath} (page.ts changed)`
3096
3158
  );
3097
- vite.ws.send({ type: "full-reload" });
3159
+ sendPageReload(vite, jayHtmlPath, pagesRootFolder);
3098
3160
  });
3099
3161
  return;
3100
3162
  }
@@ -3105,7 +3167,7 @@ function setupSlowRenderCacheInvalidation(vite, cache, pagesRootFolder, projectR
3105
3167
  getLogger().info(
3106
3168
  `[SlowRender] Cache invalidated for ${jayHtmlPath} (contract changed)`
3107
3169
  );
3108
- vite.ws.send({ type: "full-reload" });
3170
+ sendPageReload(vite, jayHtmlPath, pagesRootFolder);
3109
3171
  });
3110
3172
  return;
3111
3173
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jay-framework/dev-server",
3
- "version": "0.16.1",
3
+ "version": "0.16.3",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "main": "dist/index.js",
@@ -23,23 +23,23 @@
23
23
  "test:watch": "vitest"
24
24
  },
25
25
  "dependencies": {
26
- "@jay-framework/compiler-jay-stack": "^0.16.1",
27
- "@jay-framework/compiler-shared": "^0.16.1",
28
- "@jay-framework/component": "^0.16.1",
29
- "@jay-framework/fullstack-component": "^0.16.1",
30
- "@jay-framework/logger": "^0.16.1",
31
- "@jay-framework/runtime": "^0.16.1",
32
- "@jay-framework/stack-client-runtime": "^0.16.1",
33
- "@jay-framework/stack-route-scanner": "^0.16.1",
34
- "@jay-framework/stack-server-runtime": "^0.16.1",
35
- "@jay-framework/view-state-merge": "^0.16.1",
26
+ "@jay-framework/compiler-jay-stack": "^0.16.3",
27
+ "@jay-framework/compiler-shared": "^0.16.3",
28
+ "@jay-framework/component": "^0.16.3",
29
+ "@jay-framework/fullstack-component": "^0.16.3",
30
+ "@jay-framework/logger": "^0.16.3",
31
+ "@jay-framework/runtime": "^0.16.3",
32
+ "@jay-framework/stack-client-runtime": "^0.16.3",
33
+ "@jay-framework/stack-route-scanner": "^0.16.3",
34
+ "@jay-framework/stack-server-runtime": "^0.16.3",
35
+ "@jay-framework/view-state-merge": "^0.16.3",
36
36
  "busboy": "^1.6.0",
37
37
  "vite": "^5.0.11"
38
38
  },
39
39
  "devDependencies": {
40
- "@jay-framework/dev-environment": "^0.16.1",
41
- "@jay-framework/jay-cli": "^0.16.1",
42
- "@jay-framework/stack-client-runtime": "^0.16.1",
40
+ "@jay-framework/dev-environment": "^0.16.3",
41
+ "@jay-framework/jay-cli": "^0.16.3",
42
+ "@jay-framework/stack-client-runtime": "^0.16.3",
43
43
  "@playwright/test": "^1.58.2",
44
44
  "@types/busboy": "^1.5.4",
45
45
  "@types/express": "^5.0.2",