almostnode 0.2.4 → 0.2.6

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/index.cjs CHANGED
@@ -41,7 +41,8 @@ class VirtualFS {
41
41
  constructor() {
42
42
  this.root = {
43
43
  type: "directory",
44
- children: /* @__PURE__ */ new Map()
44
+ children: /* @__PURE__ */ new Map(),
45
+ mtime: Date.now()
45
46
  };
46
47
  }
47
48
  on(event, listener) {
@@ -148,7 +149,8 @@ class VirtualFS {
148
149
  const content = typeof data === "string" ? this.encoder.encode(data) : data;
149
150
  parent.children.set(basename, {
150
151
  type: "file",
151
- content
152
+ content,
153
+ mtime: Date.now()
152
154
  });
153
155
  if (emitEvent) {
154
156
  this.notifyWatchers(normalized, existed ? "change" : "rename");
@@ -225,7 +227,7 @@ class VirtualFS {
225
227
  }
226
228
  let child = current.children.get(segment);
227
229
  if (!child) {
228
- child = { type: "directory", children: /* @__PURE__ */ new Map() };
230
+ child = { type: "directory", children: /* @__PURE__ */ new Map(), mtime: Date.now() };
229
231
  current.children.set(segment, child);
230
232
  } else if (child.type !== "directory") {
231
233
  throw new Error(`ENOTDIR: not a directory, '${path}'`);
@@ -248,8 +250,8 @@ class VirtualFS {
248
250
  if (!node) {
249
251
  throw createNodeError("ENOENT", "stat", path);
250
252
  }
251
- const now = Date.now();
252
253
  const size = node.type === "file" ? node.content?.length || 0 : 0;
254
+ const mtime = node.mtime;
253
255
  return {
254
256
  isFile: () => node.type === "file",
255
257
  isDirectory: () => node.type === "directory",
@@ -260,14 +262,14 @@ class VirtualFS {
260
262
  isSocket: () => false,
261
263
  size,
262
264
  mode: node.type === "directory" ? 493 : 420,
263
- mtime: new Date(now),
264
- atime: new Date(now),
265
- ctime: new Date(now),
266
- birthtime: new Date(now),
267
- mtimeMs: now,
268
- atimeMs: now,
269
- ctimeMs: now,
270
- birthtimeMs: now,
265
+ mtime: new Date(mtime),
266
+ atime: new Date(mtime),
267
+ ctime: new Date(mtime),
268
+ birthtime: new Date(mtime),
269
+ mtimeMs: mtime,
270
+ atimeMs: mtime,
271
+ ctimeMs: mtime,
272
+ birthtimeMs: mtime,
271
273
  nlink: 1,
272
274
  uid: 1e3,
273
275
  gid: 1e3,
@@ -330,7 +332,8 @@ class VirtualFS {
330
332
  }
331
333
  parent.children.set(basename, {
332
334
  type: "directory",
333
- children: /* @__PURE__ */ new Map()
335
+ children: /* @__PURE__ */ new Map(),
336
+ mtime: Date.now()
334
337
  });
335
338
  }
336
339
  /**
@@ -664,6 +667,15 @@ class VirtualFS {
664
667
  }
665
668
  }
666
669
 
670
+ function simpleHash(str) {
671
+ let hash = 0;
672
+ for (let i = 0; i < str.length; i++) {
673
+ hash = (hash << 5) - hash + str.charCodeAt(i);
674
+ hash |= 0;
675
+ }
676
+ return hash.toString(36);
677
+ }
678
+
667
679
  class Dirent {
668
680
  name;
669
681
  _isDirectory;
@@ -9070,7 +9082,23 @@ const builtinModules = {
9070
9082
  "@sentry/node": sentryShim,
9071
9083
  "@sentry/core": sentryShim
9072
9084
  };
9073
- function createRequire(vfs, fsShim, process, currentDir, moduleCache, options) {
9085
+ function createRequire(vfs, fsShim, process, currentDir, moduleCache, options, processedCodeCache) {
9086
+ const resolutionCache = /* @__PURE__ */ new Map();
9087
+ const packageJsonCache = /* @__PURE__ */ new Map();
9088
+ const getParsedPackageJson = (pkgPath) => {
9089
+ if (packageJsonCache.has(pkgPath)) {
9090
+ return packageJsonCache.get(pkgPath);
9091
+ }
9092
+ try {
9093
+ const content = vfs.readFileSync(pkgPath, "utf8");
9094
+ const parsed = JSON.parse(content);
9095
+ packageJsonCache.set(pkgPath, parsed);
9096
+ return parsed;
9097
+ } catch {
9098
+ packageJsonCache.set(pkgPath, null);
9099
+ return null;
9100
+ }
9101
+ };
9074
9102
  const resolveModule = (id, fromDir) => {
9075
9103
  if (id.startsWith("node:")) {
9076
9104
  id = id.slice(5);
@@ -9078,15 +9106,25 @@ function createRequire(vfs, fsShim, process, currentDir, moduleCache, options) {
9078
9106
  if (builtinModules[id] || id === "fs" || id === "process" || id === "url" || id === "querystring" || id === "util") {
9079
9107
  return id;
9080
9108
  }
9109
+ const cacheKey = `${fromDir}|${id}`;
9110
+ const cached = resolutionCache.get(cacheKey);
9111
+ if (cached !== void 0) {
9112
+ if (cached === null) {
9113
+ throw new Error(`Cannot find module '${id}'`);
9114
+ }
9115
+ return cached;
9116
+ }
9081
9117
  if (id.startsWith("./") || id.startsWith("../") || id.startsWith("/")) {
9082
9118
  const resolved = id.startsWith("/") ? id : resolve$2(fromDir, id);
9083
9119
  if (vfs.existsSync(resolved)) {
9084
9120
  const stats = vfs.statSync(resolved);
9085
9121
  if (stats.isFile()) {
9122
+ resolutionCache.set(cacheKey, resolved);
9086
9123
  return resolved;
9087
9124
  }
9088
9125
  const indexPath = join(resolved, "index.js");
9089
9126
  if (vfs.existsSync(indexPath)) {
9127
+ resolutionCache.set(cacheKey, indexPath);
9090
9128
  return indexPath;
9091
9129
  }
9092
9130
  }
@@ -9094,9 +9132,11 @@ function createRequire(vfs, fsShim, process, currentDir, moduleCache, options) {
9094
9132
  for (const ext of extensions) {
9095
9133
  const withExt = resolved + ext;
9096
9134
  if (vfs.existsSync(withExt)) {
9135
+ resolutionCache.set(cacheKey, withExt);
9097
9136
  return withExt;
9098
9137
  }
9099
9138
  }
9139
+ resolutionCache.set(cacheKey, null);
9100
9140
  throw new Error(`Cannot find module '${id}' from '${fromDir}'`);
9101
9141
  }
9102
9142
  const tryResolveFile = (basePath) => {
@@ -9127,9 +9167,8 @@ function createRequire(vfs, fsShim, process, currentDir, moduleCache, options) {
9127
9167
  const pkgName = parts[0].startsWith("@") && parts.length > 1 ? `${parts[0]}/${parts[1]}` : parts[0];
9128
9168
  const pkgRoot = join(nodeModulesDir, pkgName);
9129
9169
  const pkgPath = join(pkgRoot, "package.json");
9130
- if (vfs.existsSync(pkgPath)) {
9131
- const pkgContent = vfs.readFileSync(pkgPath, "utf8");
9132
- const pkg = JSON.parse(pkgContent);
9170
+ const pkg = getParsedPackageJson(pkgPath);
9171
+ if (pkg) {
9133
9172
  if (pkg.exports) {
9134
9173
  try {
9135
9174
  const resolved2 = resolve_exports.resolve(pkg, moduleId, { require: true });
@@ -9155,11 +9194,18 @@ function createRequire(vfs, fsShim, process, currentDir, moduleCache, options) {
9155
9194
  while (searchDir !== "/") {
9156
9195
  const nodeModulesDir = join(searchDir, "node_modules");
9157
9196
  const resolved = tryResolveFromNodeModules(nodeModulesDir, id);
9158
- if (resolved) return resolved;
9197
+ if (resolved) {
9198
+ resolutionCache.set(cacheKey, resolved);
9199
+ return resolved;
9200
+ }
9159
9201
  searchDir = dirname(searchDir);
9160
9202
  }
9161
9203
  const rootResolved = tryResolveFromNodeModules("/node_modules", id);
9162
- if (rootResolved) return rootResolved;
9204
+ if (rootResolved) {
9205
+ resolutionCache.set(cacheKey, rootResolved);
9206
+ return rootResolved;
9207
+ }
9208
+ resolutionCache.set(cacheKey, null);
9163
9209
  throw new Error(`Cannot find module '${id}'`);
9164
9210
  };
9165
9211
  const loadModule = (resolvedPath) => {
@@ -9181,25 +9227,32 @@ function createRequire(vfs, fsShim, process, currentDir, moduleCache, options) {
9181
9227
  module.loaded = true;
9182
9228
  return module;
9183
9229
  }
9184
- let code = vfs.readFileSync(resolvedPath, "utf8");
9230
+ const rawCode = vfs.readFileSync(resolvedPath, "utf8");
9185
9231
  const dirname$1 = dirname(resolvedPath);
9186
- const isCjsFile = resolvedPath.endsWith(".cjs");
9187
- const isAlreadyBundledCjs = code.startsWith('"use strict";\nvar __') || code.startsWith("'use strict';\nvar __");
9188
- const hasEsmImport = /\bimport\s+[\w{*'"]/m.test(code);
9189
- const hasEsmExport = /\bexport\s+(?:default|const|let|var|function|class|{|\*)/m.test(code);
9190
- if (!isCjsFile && !isAlreadyBundledCjs) {
9191
- if (resolvedPath.endsWith(".mjs") || resolvedPath.includes("/esm/") || hasEsmImport || hasEsmExport) {
9192
- code = transformEsmToCjs(code, resolvedPath);
9232
+ const codeCacheKey = `${resolvedPath}|${simpleHash(rawCode)}`;
9233
+ let code = processedCodeCache?.get(codeCacheKey);
9234
+ if (!code) {
9235
+ code = rawCode;
9236
+ const isCjsFile = resolvedPath.endsWith(".cjs");
9237
+ const isAlreadyBundledCjs = code.startsWith('"use strict";\nvar __') || code.startsWith("'use strict';\nvar __");
9238
+ const hasEsmImport = /\bimport\s+[\w{*'"]/m.test(code);
9239
+ const hasEsmExport = /\bexport\s+(?:default|const|let|var|function|class|{|\*)/m.test(code);
9240
+ if (!isCjsFile && !isAlreadyBundledCjs) {
9241
+ if (resolvedPath.endsWith(".mjs") || resolvedPath.includes("/esm/") || hasEsmImport || hasEsmExport) {
9242
+ code = transformEsmToCjs(code, resolvedPath);
9243
+ }
9193
9244
  }
9245
+ code = transformDynamicImports(code);
9246
+ processedCodeCache?.set(codeCacheKey, code);
9194
9247
  }
9195
- code = transformDynamicImports(code);
9196
9248
  const moduleRequire = createRequire(
9197
9249
  vfs,
9198
9250
  fsShim,
9199
9251
  process,
9200
9252
  dirname$1,
9201
9253
  moduleCache,
9202
- options
9254
+ options,
9255
+ processedCodeCache
9203
9256
  );
9204
9257
  moduleRequire.cache = moduleCache;
9205
9258
  const consoleWrapper = createConsoleWrapper(options.onConsole);
@@ -9382,6 +9435,8 @@ class Runtime {
9382
9435
  process;
9383
9436
  moduleCache = {};
9384
9437
  options;
9438
+ /** Cache for pre-processed code (after ESM transform) before eval */
9439
+ processedCodeCache = /* @__PURE__ */ new Map();
9385
9440
  constructor(vfs2, options2 = {}) {
9386
9441
  this.vfs = vfs2;
9387
9442
  this.process = createProcess({
@@ -9475,7 +9530,8 @@ class Runtime {
9475
9530
  this.process,
9476
9531
  dirname$1,
9477
9532
  this.moduleCache,
9478
- this.options
9533
+ this.options,
9534
+ this.processedCodeCache
9479
9535
  );
9480
9536
  const module = {
9481
9537
  id: filename,
@@ -9589,7 +9645,7 @@ class WorkerRuntime {
9589
9645
  this.vfs = vfs;
9590
9646
  this.options = options;
9591
9647
  this.worker = new Worker(
9592
- new URL(/* @vite-ignore */ "/assets/runtime-worker-D9x_Ddwz.js", (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))),
9648
+ new URL(/* @vite-ignore */ "/assets/runtime-worker-B8_LZkBX.js", (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))),
9593
9649
  { type: "module" }
9594
9650
  );
9595
9651
  this.workerApi = comlink.wrap(this.worker);
@@ -11677,6 +11733,7 @@ class ViteDevServer extends DevServer {
11677
11733
  watcherCleanup = null;
11678
11734
  options;
11679
11735
  hmrTargetWindow = null;
11736
+ transformCache = /* @__PURE__ */ new Map();
11680
11737
  constructor(vfs, options) {
11681
11738
  super(vfs, options);
11682
11739
  this.options = {
@@ -11810,7 +11867,25 @@ class ViteDevServer extends DevServer {
11810
11867
  async transformAndServe(filePath, urlPath) {
11811
11868
  try {
11812
11869
  const content = this.vfs.readFileSync(filePath, "utf8");
11870
+ const hash = simpleHash(content);
11871
+ const cached = this.transformCache.get(filePath);
11872
+ if (cached && cached.hash === hash) {
11873
+ const buffer2 = BufferPolyfill.from(cached.code);
11874
+ return {
11875
+ statusCode: 200,
11876
+ statusMessage: "OK",
11877
+ headers: {
11878
+ "Content-Type": "application/javascript; charset=utf-8",
11879
+ "Content-Length": String(buffer2.length),
11880
+ "Cache-Control": "no-cache",
11881
+ "X-Transformed": "true",
11882
+ "X-Cache": "hit"
11883
+ },
11884
+ body: buffer2
11885
+ };
11886
+ }
11813
11887
  const transformed = await this.transformCode(content, urlPath);
11888
+ this.transformCache.set(filePath, { code: transformed, hash });
11814
11889
  const buffer = BufferPolyfill.from(transformed);
11815
11890
  return {
11816
11891
  statusCode: 200,
@@ -11993,6 +12068,125 @@ export default css;
11993
12068
  }
11994
12069
  }
11995
12070
 
12071
+ const CONFIG_FILE_NAMES = [
12072
+ "/tailwind.config.ts",
12073
+ "/tailwind.config.js",
12074
+ "/tailwind.config.mjs"
12075
+ ];
12076
+ async function loadTailwindConfig(vfs, root = "/") {
12077
+ let configPath = null;
12078
+ let configContent = null;
12079
+ for (const fileName of CONFIG_FILE_NAMES) {
12080
+ const fullPath = root === "/" ? fileName : `${root}${fileName}`;
12081
+ try {
12082
+ const content = vfs.readFileSync(fullPath);
12083
+ configContent = typeof content === "string" ? content : content instanceof Uint8Array ? new TextDecoder("utf-8").decode(content) : Buffer.from(content).toString("utf-8");
12084
+ configPath = fullPath;
12085
+ break;
12086
+ } catch {
12087
+ continue;
12088
+ }
12089
+ }
12090
+ if (!configPath || configContent === null) {
12091
+ return {
12092
+ configScript: "",
12093
+ success: true
12094
+ // Not an error, just no config
12095
+ };
12096
+ }
12097
+ try {
12098
+ const jsConfig = stripTypescriptSyntax(configContent);
12099
+ const configObject = extractConfigObject(jsConfig);
12100
+ if (!configObject) {
12101
+ return {
12102
+ configScript: "",
12103
+ success: false,
12104
+ error: "Could not extract config object from tailwind.config"
12105
+ };
12106
+ }
12107
+ const configScript = generateConfigScript(configObject);
12108
+ return {
12109
+ configScript,
12110
+ success: true
12111
+ };
12112
+ } catch (error) {
12113
+ return {
12114
+ configScript: "",
12115
+ success: false,
12116
+ error: `Failed to parse tailwind.config: ${error instanceof Error ? error.message : String(error)}`
12117
+ };
12118
+ }
12119
+ }
12120
+ function stripTypescriptSyntax(content) {
12121
+ let result = content;
12122
+ result = result.replace(/import\s+type\s+\{[^}]*\}\s+from\s+['"][^'"]*['"]\s*;?\s*/g, "");
12123
+ result = result.replace(/import\s+\{[^}]*\}\s+from\s+['"][^'"]*['"]\s*;?\s*/g, "");
12124
+ result = result.replace(/\s+satisfies\s+\w+\s*$/gm, "");
12125
+ result = result.replace(/\s+satisfies\s+\w+\s*;?\s*$/gm, "");
12126
+ result = result.replace(/:\s*Config\s*=/g, " =");
12127
+ result = result.replace(/\s+as\s+const\s*/g, " ");
12128
+ return result;
12129
+ }
12130
+ function extractConfigObject(content) {
12131
+ const exportDefaultMatch = content.match(/export\s+default\s*/);
12132
+ if (!exportDefaultMatch || exportDefaultMatch.index === void 0) {
12133
+ return null;
12134
+ }
12135
+ const startIndex = exportDefaultMatch.index + exportDefaultMatch[0].length;
12136
+ const remaining = content.substring(startIndex);
12137
+ const trimmedRemaining = remaining.trimStart();
12138
+ if (!trimmedRemaining.startsWith("{")) {
12139
+ return null;
12140
+ }
12141
+ const objectStart = startIndex + (remaining.length - trimmedRemaining.length);
12142
+ const objectContent = content.substring(objectStart);
12143
+ let braceCount = 0;
12144
+ let inString = false;
12145
+ let stringChar = "";
12146
+ let escaped = false;
12147
+ let endIndex = -1;
12148
+ for (let i = 0; i < objectContent.length; i++) {
12149
+ const char = objectContent[i];
12150
+ if (escaped) {
12151
+ escaped = false;
12152
+ continue;
12153
+ }
12154
+ if (char === "\\") {
12155
+ escaped = true;
12156
+ continue;
12157
+ }
12158
+ if (inString) {
12159
+ if (char === stringChar) {
12160
+ inString = false;
12161
+ }
12162
+ continue;
12163
+ }
12164
+ if (char === '"' || char === "'" || char === "`") {
12165
+ inString = true;
12166
+ stringChar = char;
12167
+ continue;
12168
+ }
12169
+ if (char === "{") {
12170
+ braceCount++;
12171
+ } else if (char === "}") {
12172
+ braceCount--;
12173
+ if (braceCount === 0) {
12174
+ endIndex = i + 1;
12175
+ break;
12176
+ }
12177
+ }
12178
+ }
12179
+ if (endIndex === -1) {
12180
+ return null;
12181
+ }
12182
+ return objectContent.substring(0, endIndex);
12183
+ }
12184
+ function generateConfigScript(configObject) {
12185
+ return `<script>
12186
+ tailwind.config = ${configObject};
12187
+ <\/script>`;
12188
+ }
12189
+
11996
12190
  const isBrowser = typeof window !== "undefined" && typeof window.navigator !== "undefined" && "serviceWorker" in window.navigator;
11997
12191
  async function initEsbuild() {
11998
12192
  if (!isBrowser) return;
@@ -12217,25 +12411,31 @@ const applyVirtualBase = (url) => {
12217
12411
 
12218
12412
  export default function Link({ href, children, ...props }) {
12219
12413
  const handleClick = (e) => {
12414
+ console.log('[Link] Click handler called, href:', href);
12415
+
12220
12416
  if (props.onClick) {
12221
12417
  props.onClick(e);
12222
12418
  }
12223
12419
 
12224
12420
  // Allow cmd/ctrl click to open in new tab
12225
12421
  if (e.metaKey || e.ctrlKey) {
12422
+ console.log('[Link] Meta/Ctrl key pressed, allowing default behavior');
12226
12423
  return;
12227
12424
  }
12228
12425
 
12229
12426
  if (typeof href !== 'string' || !href || href.startsWith('#') || href.startsWith('?')) {
12427
+ console.log('[Link] Skipping navigation for href:', href);
12230
12428
  return;
12231
12429
  }
12232
12430
 
12233
12431
  if (/^(https?:)?\\/\\//.test(href)) {
12432
+ console.log('[Link] External URL, allowing default behavior:', href);
12234
12433
  return;
12235
12434
  }
12236
12435
 
12237
12436
  e.preventDefault();
12238
12437
  const resolvedHref = applyVirtualBase(href);
12438
+ console.log('[Link] Navigating to:', resolvedHref);
12239
12439
  window.history.pushState({}, '', resolvedHref);
12240
12440
  window.dispatchEvent(new PopStateEvent('popstate'));
12241
12441
  };
@@ -12574,6 +12774,305 @@ export default function Head({ children }) {
12574
12774
  return null;
12575
12775
  }
12576
12776
  `;
12777
+ const NEXT_IMAGE_SHIM = `
12778
+ import React from 'react';
12779
+
12780
+ function Image({
12781
+ src,
12782
+ alt = '',
12783
+ width,
12784
+ height,
12785
+ fill,
12786
+ loader,
12787
+ quality = 75,
12788
+ priority,
12789
+ loading,
12790
+ placeholder,
12791
+ blurDataURL,
12792
+ unoptimized,
12793
+ onLoad,
12794
+ onError,
12795
+ style,
12796
+ className,
12797
+ sizes,
12798
+ ...rest
12799
+ }) {
12800
+ // Handle src - could be string or StaticImageData object
12801
+ const imageSrc = typeof src === 'object' ? src.src : src;
12802
+
12803
+ // Build style object
12804
+ const imgStyle = { ...style };
12805
+ if (fill) {
12806
+ imgStyle.position = 'absolute';
12807
+ imgStyle.width = '100%';
12808
+ imgStyle.height = '100%';
12809
+ imgStyle.objectFit = imgStyle.objectFit || 'cover';
12810
+ imgStyle.inset = '0';
12811
+ }
12812
+
12813
+ return React.createElement('img', {
12814
+ src: imageSrc,
12815
+ alt,
12816
+ width: fill ? undefined : width,
12817
+ height: fill ? undefined : height,
12818
+ loading: priority ? 'eager' : (loading || 'lazy'),
12819
+ decoding: 'async',
12820
+ style: imgStyle,
12821
+ className,
12822
+ onLoad,
12823
+ onError,
12824
+ ...rest
12825
+ });
12826
+ }
12827
+
12828
+ export default Image;
12829
+ export { Image };
12830
+ `;
12831
+ const NEXT_DYNAMIC_SHIM = `
12832
+ import React from 'react';
12833
+
12834
+ function dynamic(importFn, options = {}) {
12835
+ const {
12836
+ loading: LoadingComponent,
12837
+ ssr = true,
12838
+ } = options;
12839
+
12840
+ // Create a lazy component
12841
+ const LazyComponent = React.lazy(importFn);
12842
+
12843
+ // Wrapper component that handles loading state
12844
+ function DynamicComponent(props) {
12845
+ const fallback = LoadingComponent
12846
+ ? React.createElement(LoadingComponent, { isLoading: true })
12847
+ : null;
12848
+
12849
+ return React.createElement(
12850
+ React.Suspense,
12851
+ { fallback },
12852
+ React.createElement(LazyComponent, props)
12853
+ );
12854
+ }
12855
+
12856
+ return DynamicComponent;
12857
+ }
12858
+
12859
+ export default dynamic;
12860
+ export { dynamic };
12861
+ `;
12862
+ const NEXT_SCRIPT_SHIM = `
12863
+ import React from 'react';
12864
+
12865
+ function Script({
12866
+ src,
12867
+ strategy = 'afterInteractive',
12868
+ onLoad,
12869
+ onReady,
12870
+ onError,
12871
+ children,
12872
+ dangerouslySetInnerHTML,
12873
+ ...rest
12874
+ }) {
12875
+ React.useEffect(function() {
12876
+ if (!src && !children && !dangerouslySetInnerHTML) return;
12877
+
12878
+ var script = document.createElement('script');
12879
+
12880
+ if (src) {
12881
+ script.src = src;
12882
+ script.async = strategy !== 'beforeInteractive';
12883
+ }
12884
+
12885
+ Object.keys(rest).forEach(function(key) {
12886
+ script.setAttribute(key, rest[key]);
12887
+ });
12888
+
12889
+ if (children) {
12890
+ script.textContent = children;
12891
+ } else if (dangerouslySetInnerHTML && dangerouslySetInnerHTML.__html) {
12892
+ script.textContent = dangerouslySetInnerHTML.__html;
12893
+ }
12894
+
12895
+ script.onload = function() {
12896
+ if (onLoad) onLoad();
12897
+ if (onReady) onReady();
12898
+ };
12899
+ script.onerror = onError;
12900
+
12901
+ document.head.appendChild(script);
12902
+
12903
+ return function() {
12904
+ if (script.parentNode) {
12905
+ script.parentNode.removeChild(script);
12906
+ }
12907
+ };
12908
+ }, [src]);
12909
+
12910
+ return null;
12911
+ }
12912
+
12913
+ export default Script;
12914
+ export { Script };
12915
+ `;
12916
+ const NEXT_FONT_GOOGLE_SHIM = `
12917
+ // Track loaded fonts to avoid duplicate style injections
12918
+ const loadedFonts = new Set();
12919
+
12920
+ /**
12921
+ * Convert font function name to Google Fonts family name
12922
+ * Examples:
12923
+ * DM_Sans -> DM Sans
12924
+ * Open_Sans -> Open Sans
12925
+ * Fraunces -> Fraunces
12926
+ */
12927
+ function toFontFamily(fontName) {
12928
+ return fontName.replace(/_/g, ' ');
12929
+ }
12930
+
12931
+ /**
12932
+ * Inject font CSS into document
12933
+ * - Adds preconnect links for faster font loading
12934
+ * - Loads the font from Google Fonts CDN
12935
+ * - Creates a CSS class that sets the CSS variable
12936
+ */
12937
+ function injectFontCSS(fontFamily, variableName, weight, style) {
12938
+ const fontKey = fontFamily + '-' + (variableName || 'default');
12939
+ if (loadedFonts.has(fontKey)) {
12940
+ return;
12941
+ }
12942
+ loadedFonts.add(fontKey);
12943
+
12944
+ if (typeof document === 'undefined') {
12945
+ return;
12946
+ }
12947
+
12948
+ // Add preconnect links for faster loading (only once)
12949
+ if (!document.querySelector('link[href="https://fonts.googleapis.com"]')) {
12950
+ const preconnect1 = document.createElement('link');
12951
+ preconnect1.rel = 'preconnect';
12952
+ preconnect1.href = 'https://fonts.googleapis.com';
12953
+ document.head.appendChild(preconnect1);
12954
+
12955
+ const preconnect2 = document.createElement('link');
12956
+ preconnect2.rel = 'preconnect';
12957
+ preconnect2.href = 'https://fonts.gstatic.com';
12958
+ preconnect2.crossOrigin = 'anonymous';
12959
+ document.head.appendChild(preconnect2);
12960
+ }
12961
+
12962
+ // Build Google Fonts URL
12963
+ const escapedFamily = fontFamily.replace(/ /g, '+');
12964
+
12965
+ // Build axis list based on options
12966
+ let axisList = '';
12967
+ const axes = [];
12968
+
12969
+ // Handle italic style
12970
+ if (style === 'italic') {
12971
+ axes.push('ital');
12972
+ }
12973
+
12974
+ // Handle weight - use specific weight or variable range
12975
+ if (weight && weight !== '400' && !Array.isArray(weight)) {
12976
+ // Specific weight requested
12977
+ axes.push('wght');
12978
+ if (style === 'italic') {
12979
+ axisList = ':ital,wght@1,' + weight;
12980
+ } else {
12981
+ axisList = ':wght@' + weight;
12982
+ }
12983
+ } else if (Array.isArray(weight)) {
12984
+ // Multiple weights
12985
+ axes.push('wght');
12986
+ axisList = ':wght@' + weight.join(';');
12987
+ } else {
12988
+ // Default: request common weights for flexibility
12989
+ axisList = ':wght@400;500;600;700';
12990
+ }
12991
+
12992
+ const fontUrl = 'https://fonts.googleapis.com/css2?family=' +
12993
+ escapedFamily + axisList + '&display=swap';
12994
+
12995
+ // Add link element for Google Fonts (if not already present)
12996
+ if (!document.querySelector('link[href*="family=' + escapedFamily + '"]')) {
12997
+ const link = document.createElement('link');
12998
+ link.rel = 'stylesheet';
12999
+ link.href = fontUrl;
13000
+ document.head.appendChild(link);
13001
+ }
13002
+
13003
+ // Create style element for CSS variable at :root level (globally available)
13004
+ // This makes the variable work without needing to apply the class to body
13005
+ if (variableName) {
13006
+ const styleEl = document.createElement('style');
13007
+ styleEl.setAttribute('data-font-var', variableName);
13008
+ styleEl.textContent = ':root { ' + variableName + ': "' + fontFamily + '", ' + (fontFamily.includes('Serif') ? 'serif' : 'sans-serif') + '; }';
13009
+ document.head.appendChild(styleEl);
13010
+ }
13011
+ }
13012
+
13013
+ /**
13014
+ * Create a font loader function for a specific font
13015
+ */
13016
+ function createFontLoader(fontName) {
13017
+ const fontFamily = toFontFamily(fontName);
13018
+
13019
+ return function(options = {}) {
13020
+ const {
13021
+ weight,
13022
+ style = 'normal',
13023
+ subsets = ['latin'],
13024
+ variable,
13025
+ display = 'swap',
13026
+ preload = true,
13027
+ fallback = ['sans-serif'],
13028
+ adjustFontFallback = true
13029
+ } = options;
13030
+
13031
+ // Inject the font CSS
13032
+ injectFontCSS(fontFamily, variable, weight, style);
13033
+
13034
+ // Generate class name from variable (--font-inter -> __font-inter)
13035
+ const className = variable
13036
+ ? variable.replace('--', '__')
13037
+ : '__font-' + fontName.toLowerCase().replace(/_/g, '-');
13038
+
13039
+ return {
13040
+ className,
13041
+ variable: className,
13042
+ style: {
13043
+ fontFamily: '"' + fontFamily + '", ' + fallback.join(', ')
13044
+ }
13045
+ };
13046
+ };
13047
+ }
13048
+
13049
+ /**
13050
+ * Use a Proxy to dynamically create font loaders for ANY font name
13051
+ * This allows: import { AnyGoogleFont } from "next/font/google"
13052
+ */
13053
+ const fontProxy = new Proxy({}, {
13054
+ get(target, prop) {
13055
+ // Handle special properties
13056
+ if (prop === '__esModule') return true;
13057
+ if (prop === 'default') return fontProxy;
13058
+ if (typeof prop !== 'string') return undefined;
13059
+
13060
+ // Create a font loader for this font name
13061
+ return createFontLoader(prop);
13062
+ }
13063
+ });
13064
+
13065
+ // Export the proxy as both default and named exports
13066
+ export default fontProxy;
13067
+
13068
+ // Re-export through proxy for named imports
13069
+ export const {
13070
+ Fraunces, Inter, DM_Sans, DM_Serif_Text, Roboto, Open_Sans, Lato,
13071
+ Montserrat, Poppins, Playfair_Display, Merriweather, Raleway, Nunito,
13072
+ Ubuntu, Oswald, Quicksand, Work_Sans, Fira_Sans, Barlow, Mulish, Rubik,
13073
+ Noto_Sans, Manrope, Space_Grotesk, Geist, Geist_Mono
13074
+ } = fontProxy;
13075
+ `;
12577
13076
  class NextDevServer extends DevServer {
12578
13077
  /** Pages Router directory (default: '/pages') */
12579
13078
  pagesDir;
@@ -12589,6 +13088,16 @@ class NextDevServer extends DevServer {
12589
13088
  hmrTargetWindow = null;
12590
13089
  /** Store options for later access (e.g., env vars) */
12591
13090
  options;
13091
+ /** Transform result cache for performance */
13092
+ transformCache = /* @__PURE__ */ new Map();
13093
+ /** Path aliases from tsconfig.json (e.g., @/* -> ./*) */
13094
+ pathAliases = /* @__PURE__ */ new Map();
13095
+ /** Cached Tailwind config script (injected before CDN) */
13096
+ tailwindConfigScript = "";
13097
+ /** Whether Tailwind config has been loaded */
13098
+ tailwindConfigLoaded = false;
13099
+ /** Asset prefix for static files (e.g., '/marketing') */
13100
+ assetPrefix = "";
12592
13101
  constructor(vfs, options) {
12593
13102
  super(vfs, options);
12594
13103
  this.options = options;
@@ -12600,6 +13109,93 @@ class NextDevServer extends DevServer {
12600
13109
  } else {
12601
13110
  this.useAppRouter = this.hasAppRouter();
12602
13111
  }
13112
+ this.loadPathAliases();
13113
+ this.loadAssetPrefix(options.assetPrefix);
13114
+ }
13115
+ /**
13116
+ * Load path aliases from tsconfig.json
13117
+ * Supports common patterns like @/* -> ./*
13118
+ */
13119
+ loadPathAliases() {
13120
+ try {
13121
+ const tsconfigPath = "/tsconfig.json";
13122
+ if (!this.vfs.existsSync(tsconfigPath)) {
13123
+ return;
13124
+ }
13125
+ const content = this.vfs.readFileSync(tsconfigPath, "utf-8");
13126
+ const tsconfig = JSON.parse(content);
13127
+ const paths = tsconfig?.compilerOptions?.paths;
13128
+ if (!paths) {
13129
+ return;
13130
+ }
13131
+ for (const [alias, targets] of Object.entries(paths)) {
13132
+ if (Array.isArray(targets) && targets.length > 0) {
13133
+ const aliasPrefix = alias.replace(/\*$/, "");
13134
+ const targetPrefix = targets[0].replace(/\*$/, "").replace(/^\./, "");
13135
+ this.pathAliases.set(aliasPrefix, targetPrefix);
13136
+ }
13137
+ }
13138
+ } catch (e) {
13139
+ }
13140
+ }
13141
+ /**
13142
+ * Load assetPrefix from options or auto-detect from next.config.ts/js
13143
+ * The assetPrefix is used to prefix static asset URLs (e.g., '/marketing')
13144
+ */
13145
+ loadAssetPrefix(optionValue) {
13146
+ if (optionValue !== void 0) {
13147
+ this.assetPrefix = optionValue.startsWith("/") ? optionValue : `/${optionValue}`;
13148
+ if (this.assetPrefix.endsWith("/")) {
13149
+ this.assetPrefix = this.assetPrefix.slice(0, -1);
13150
+ }
13151
+ return;
13152
+ }
13153
+ try {
13154
+ const configFiles = ["/next.config.ts", "/next.config.js", "/next.config.mjs"];
13155
+ for (const configPath of configFiles) {
13156
+ if (!this.vfs.existsSync(configPath)) {
13157
+ continue;
13158
+ }
13159
+ const content = this.vfs.readFileSync(configPath, "utf-8");
13160
+ const match = content.match(/assetPrefix\s*:\s*["']([^"']+)["']/);
13161
+ if (match) {
13162
+ let prefix = match[1];
13163
+ if (!prefix.startsWith("/")) {
13164
+ prefix = `/${prefix}`;
13165
+ }
13166
+ if (prefix.endsWith("/")) {
13167
+ prefix = prefix.slice(0, -1);
13168
+ }
13169
+ this.assetPrefix = prefix;
13170
+ return;
13171
+ }
13172
+ }
13173
+ } catch (e) {
13174
+ }
13175
+ }
13176
+ /**
13177
+ * Resolve path aliases in transformed code
13178
+ * Converts imports like "@/components/foo" to "/__virtual__/PORT/components/foo"
13179
+ * This ensures imports go through the virtual server instead of the main server
13180
+ */
13181
+ resolvePathAliases(code, currentFile) {
13182
+ if (this.pathAliases.size === 0) {
13183
+ return code;
13184
+ }
13185
+ const virtualBase = `/__virtual__/${this.port}`;
13186
+ let result = code;
13187
+ for (const [alias, target] of this.pathAliases) {
13188
+ const aliasEscaped = alias.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
13189
+ const pattern = new RegExp(
13190
+ `(from\\s*['"]|import\\s*\\(\\s*['"])${aliasEscaped}([^'"]+)(['"])`,
13191
+ "g"
13192
+ );
13193
+ result = result.replace(pattern, (match, prefix, path, quote) => {
13194
+ const resolvedPath = `${virtualBase}${target}${path}`;
13195
+ return `${prefix}${resolvedPath}${quote}`;
13196
+ });
13197
+ }
13198
+ return result;
12603
13199
  }
12604
13200
  /**
12605
13201
  * Set an environment variable at runtime
@@ -12625,20 +13221,46 @@ class NextDevServer extends DevServer {
12625
13221
  /**
12626
13222
  * Generate a script tag that defines process.env with NEXT_PUBLIC_* variables
12627
13223
  * This makes environment variables available to browser code via process.env.NEXT_PUBLIC_*
13224
+ * Also includes all env variables for Server Component compatibility
12628
13225
  */
12629
13226
  generateEnvScript() {
12630
13227
  const env = this.options.env || {};
12631
- const publicEnvVars = Object.entries(env).filter(([key]) => key.startsWith("NEXT_PUBLIC_")).reduce((acc, [k, v]) => ({ ...acc, [k]: v }), {});
12632
- if (Object.keys(publicEnvVars).length === 0) {
12633
- return "";
13228
+ const publicEnvVars = {};
13229
+ for (const [key, value] of Object.entries(env)) {
13230
+ if (key.startsWith("NEXT_PUBLIC_")) {
13231
+ publicEnvVars[key] = value;
13232
+ }
12634
13233
  }
12635
13234
  return `<script>
12636
- // NEXT_PUBLIC_* environment variables (injected by NextDevServer)
13235
+ // Environment variables (injected by NextDevServer)
12637
13236
  window.process = window.process || {};
12638
13237
  window.process.env = window.process.env || {};
12639
13238
  Object.assign(window.process.env, ${JSON.stringify(publicEnvVars)});
12640
13239
  </script>`;
12641
13240
  }
13241
+ /**
13242
+ * Load Tailwind config from tailwind.config.ts and generate a script
13243
+ * that configures the Tailwind CDN at runtime
13244
+ */
13245
+ async loadTailwindConfigIfNeeded() {
13246
+ if (this.tailwindConfigLoaded) {
13247
+ return this.tailwindConfigScript;
13248
+ }
13249
+ try {
13250
+ const result = await loadTailwindConfig(this.vfs, this.root);
13251
+ if (result.success) {
13252
+ this.tailwindConfigScript = result.configScript;
13253
+ } else if (result.error) {
13254
+ console.warn("[NextDevServer] Tailwind config warning:", result.error);
13255
+ this.tailwindConfigScript = "";
13256
+ }
13257
+ } catch (error) {
13258
+ console.warn("[NextDevServer] Failed to load tailwind.config:", error);
13259
+ this.tailwindConfigScript = "";
13260
+ }
13261
+ this.tailwindConfigLoaded = true;
13262
+ return this.tailwindConfigScript;
13263
+ }
12642
13264
  /**
12643
13265
  * Check if App Router is available
12644
13266
  */
@@ -12659,10 +13281,26 @@ class NextDevServer extends DevServer {
12659
13281
  */
12660
13282
  async handleRequest(method, url, headers, body) {
12661
13283
  const urlObj = new URL(url, "http://localhost");
12662
- const pathname = urlObj.pathname;
13284
+ let pathname = urlObj.pathname;
13285
+ const virtualPrefixMatch = pathname.match(/^\/__virtual__\/\d+/);
13286
+ if (virtualPrefixMatch) {
13287
+ pathname = pathname.slice(virtualPrefixMatch[0].length) || "/";
13288
+ }
13289
+ if (this.assetPrefix && pathname.startsWith(this.assetPrefix)) {
13290
+ const rest = pathname.slice(this.assetPrefix.length);
13291
+ if (rest === "" || rest.startsWith("/")) {
13292
+ pathname = rest || "/";
13293
+ if (pathname.startsWith("//")) {
13294
+ pathname = pathname.slice(1);
13295
+ }
13296
+ }
13297
+ }
12663
13298
  if (pathname.startsWith("/_next/shims/")) {
12664
13299
  return this.serveNextShim(pathname);
12665
13300
  }
13301
+ if (pathname === "/_next/route-info") {
13302
+ return this.serveRouteInfo(urlObj.searchParams.get("pathname") || "/");
13303
+ }
12666
13304
  if (pathname.startsWith("/_next/pages/")) {
12667
13305
  return this.servePageComponent(pathname);
12668
13306
  }
@@ -12682,6 +13320,13 @@ class NextDevServer extends DevServer {
12682
13320
  if (this.needsTransform(pathname) && this.exists(pathname)) {
12683
13321
  return this.transformAndServe(pathname, pathname);
12684
13322
  }
13323
+ const resolvedFile = this.resolveFileWithExtension(pathname);
13324
+ if (resolvedFile) {
13325
+ if (this.needsTransform(resolvedFile)) {
13326
+ return this.transformAndServe(resolvedFile, pathname);
13327
+ }
13328
+ return this.serveFile(resolvedFile);
13329
+ }
12685
13330
  if (this.exists(pathname) && !this.isDirectory(pathname)) {
12686
13331
  return this.serveFile(pathname);
12687
13332
  }
@@ -12706,6 +13351,18 @@ class NextDevServer extends DevServer {
12706
13351
  case "navigation":
12707
13352
  code = NEXT_NAVIGATION_SHIM;
12708
13353
  break;
13354
+ case "image":
13355
+ code = NEXT_IMAGE_SHIM;
13356
+ break;
13357
+ case "dynamic":
13358
+ code = NEXT_DYNAMIC_SHIM;
13359
+ break;
13360
+ case "script":
13361
+ code = NEXT_SCRIPT_SHIM;
13362
+ break;
13363
+ case "font/google":
13364
+ code = NEXT_FONT_GOOGLE_SHIM;
13365
+ break;
12709
13366
  default:
12710
13367
  return this.notFound(pathname);
12711
13368
  }
@@ -12721,6 +13378,26 @@ class NextDevServer extends DevServer {
12721
13378
  body: buffer
12722
13379
  };
12723
13380
  }
13381
+ /**
13382
+ * Serve route info for client-side navigation
13383
+ * Returns params extracted from dynamic route segments
13384
+ */
13385
+ serveRouteInfo(pathname) {
13386
+ const route = this.resolveAppRoute(pathname);
13387
+ const info = route ? { params: route.params, found: true } : { params: {}, found: false };
13388
+ const json = JSON.stringify(info);
13389
+ const buffer = BufferPolyfill.from(json);
13390
+ return {
13391
+ statusCode: 200,
13392
+ statusMessage: "OK",
13393
+ headers: {
13394
+ "Content-Type": "application/json; charset=utf-8",
13395
+ "Content-Length": String(buffer.length),
13396
+ "Cache-Control": "no-cache"
13397
+ },
13398
+ body: buffer
13399
+ };
13400
+ }
12724
13401
  /**
12725
13402
  * Serve static assets from /_next/static/
12726
13403
  */
@@ -13227,17 +13904,18 @@ class NextDevServer extends DevServer {
13227
13904
  for (const ext of extensions) {
13228
13905
  const pagePath = `${dirPath}/page${ext}`;
13229
13906
  if (this.exists(pagePath)) {
13230
- return { page: pagePath, layouts };
13907
+ return { page: pagePath, layouts, params: {} };
13231
13908
  }
13232
13909
  }
13233
13910
  return this.resolveAppDynamicRoute(pathname, segments);
13234
13911
  }
13235
13912
  /**
13236
13913
  * Resolve dynamic App Router routes like /app/[id]/page.jsx
13914
+ * Also extracts route params from dynamic segments
13237
13915
  */
13238
13916
  resolveAppDynamicRoute(pathname, segments) {
13239
13917
  const extensions = [".jsx", ".tsx", ".js", ".ts"];
13240
- const tryPath = (dirPath, remainingSegments, layouts2) => {
13918
+ const tryPath = (dirPath, remainingSegments, layouts2, params) => {
13241
13919
  for (const ext of extensions) {
13242
13920
  const layoutPath = `${dirPath}/layout${ext}`;
13243
13921
  if (this.exists(layoutPath) && !layouts2.includes(layoutPath)) {
@@ -13248,7 +13926,7 @@ class NextDevServer extends DevServer {
13248
13926
  for (const ext of extensions) {
13249
13927
  const pagePath = `${dirPath}/page${ext}`;
13250
13928
  if (this.exists(pagePath)) {
13251
- return { page: pagePath, layouts: layouts2 };
13929
+ return { page: pagePath, layouts: layouts2, params };
13252
13930
  }
13253
13931
  }
13254
13932
  return null;
@@ -13256,16 +13934,26 @@ class NextDevServer extends DevServer {
13256
13934
  const [current, ...rest] = remainingSegments;
13257
13935
  const exactPath = `${dirPath}/${current}`;
13258
13936
  if (this.isDirectory(exactPath)) {
13259
- const result = tryPath(exactPath, rest, layouts2);
13937
+ const result = tryPath(exactPath, rest, layouts2, params);
13260
13938
  if (result) return result;
13261
13939
  }
13262
13940
  try {
13263
13941
  const entries = this.vfs.readdirSync(dirPath);
13264
13942
  for (const entry of entries) {
13265
- if (entry.startsWith("[") && entry.endsWith("]") && !entry.includes(".")) {
13943
+ if (entry.startsWith("[...") && entry.endsWith("]")) {
13266
13944
  const dynamicPath = `${dirPath}/${entry}`;
13267
13945
  if (this.isDirectory(dynamicPath)) {
13268
- const result = tryPath(dynamicPath, rest, layouts2);
13946
+ const paramName = entry.slice(4, -1);
13947
+ const newParams = { ...params, [paramName]: [current, ...rest] };
13948
+ const result = tryPath(dynamicPath, [], layouts2, newParams);
13949
+ if (result) return result;
13950
+ }
13951
+ } else if (entry.startsWith("[") && entry.endsWith("]") && !entry.includes(".")) {
13952
+ const dynamicPath = `${dirPath}/${entry}`;
13953
+ if (this.isDirectory(dynamicPath)) {
13954
+ const paramName = entry.slice(1, -1);
13955
+ const newParams = { ...params, [paramName]: current };
13956
+ const result = tryPath(dynamicPath, rest, layouts2, newParams);
13269
13957
  if (result) return result;
13270
13958
  }
13271
13959
  }
@@ -13282,7 +13970,7 @@ class NextDevServer extends DevServer {
13282
13970
  break;
13283
13971
  }
13284
13972
  }
13285
- return tryPath(this.appDir, segments, layouts);
13973
+ return tryPath(this.appDir, segments, layouts, {});
13286
13974
  }
13287
13975
  /**
13288
13976
  * Generate HTML for App Router with nested layouts
@@ -13301,6 +13989,7 @@ class NextDevServer extends DevServer {
13301
13989
  for (let i = route.layouts.length - 1; i >= 0; i--) {
13302
13990
  }
13303
13991
  const envScript = this.generateEnvScript();
13992
+ const tailwindConfigScript = await this.loadTailwindConfigIfNeeded();
13304
13993
  return `<!DOCTYPE html>
13305
13994
  <html lang="en">
13306
13995
  <head>
@@ -13310,6 +13999,7 @@ class NextDevServer extends DevServer {
13310
13999
  <title>Next.js App</title>
13311
14000
  ${envScript}
13312
14001
  ${TAILWIND_CDN_SCRIPT}
14002
+ ${tailwindConfigScript}
13313
14003
  ${CORS_PROXY_SCRIPT}
13314
14004
  ${globalCssLinks.join("\n ")}
13315
14005
  ${REACT_REFRESH_PREAMBLE}
@@ -13331,7 +14021,11 @@ class NextDevServer extends DevServer {
13331
14021
  "next/link": "${virtualPrefix}/_next/shims/link.js",
13332
14022
  "next/router": "${virtualPrefix}/_next/shims/router.js",
13333
14023
  "next/head": "${virtualPrefix}/_next/shims/head.js",
13334
- "next/navigation": "${virtualPrefix}/_next/shims/navigation.js"
14024
+ "next/navigation": "${virtualPrefix}/_next/shims/navigation.js",
14025
+ "next/image": "${virtualPrefix}/_next/shims/image.js",
14026
+ "next/dynamic": "${virtualPrefix}/_next/shims/dynamic.js",
14027
+ "next/script": "${virtualPrefix}/_next/shims/script.js",
14028
+ "next/font/google": "${virtualPrefix}/_next/shims/font/google.js"
13335
14029
  }
13336
14030
  }
13337
14031
  </script>
@@ -13345,6 +14039,39 @@ class NextDevServer extends DevServer {
13345
14039
 
13346
14040
  const virtualBase = '${virtualPrefix}';
13347
14041
 
14042
+ // Initial route params (embedded by server for initial page load)
14043
+ const initialRouteParams = ${JSON.stringify(route.params)};
14044
+ const initialPathname = '${pathname}';
14045
+
14046
+ // Route params cache for client-side navigation
14047
+ const routeParamsCache = new Map();
14048
+ routeParamsCache.set(initialPathname, initialRouteParams);
14049
+
14050
+ // Extract route params from server for client-side navigation
14051
+ async function extractRouteParams(pathname) {
14052
+ // Strip virtual base if present
14053
+ let route = pathname;
14054
+ if (route.startsWith(virtualBase)) {
14055
+ route = route.slice(virtualBase.length);
14056
+ }
14057
+ route = route.replace(/^\\/+/, '/') || '/';
14058
+
14059
+ // Check cache first
14060
+ if (routeParamsCache.has(route)) {
14061
+ return routeParamsCache.get(route);
14062
+ }
14063
+
14064
+ try {
14065
+ const response = await fetch(virtualBase + '/_next/route-info?pathname=' + encodeURIComponent(route));
14066
+ const info = await response.json();
14067
+ routeParamsCache.set(route, info.params || {});
14068
+ return info.params || {};
14069
+ } catch (e) {
14070
+ console.error('[Router] Failed to extract route params:', e);
14071
+ return {};
14072
+ }
14073
+ }
14074
+
13348
14075
  // Convert URL path to app router page module path
13349
14076
  function getAppPageModulePath(pathname) {
13350
14077
  let route = pathname;
@@ -13411,11 +14138,60 @@ class NextDevServer extends DevServer {
13411
14138
  return layouts;
13412
14139
  }
13413
14140
 
14141
+ // Wrapper for async Server Components
14142
+ function AsyncComponent({ component: Component, pathname, search }) {
14143
+ const [content, setContent] = React.useState(null);
14144
+ const [error, setError] = React.useState(null);
14145
+
14146
+ React.useEffect(() => {
14147
+ let cancelled = false;
14148
+ async function render() {
14149
+ try {
14150
+ // Create searchParams as a Promise (Next.js 15 pattern)
14151
+ const url = new URL(window.location.href);
14152
+ const searchParamsObj = Object.fromEntries(url.searchParams);
14153
+ const searchParams = Promise.resolve(searchParamsObj);
14154
+
14155
+ // Extract route params from pathname (fetches from server for dynamic routes)
14156
+ const routeParams = await extractRouteParams(pathname);
14157
+ const params = Promise.resolve(routeParams);
14158
+
14159
+ // Call component with props like Next.js does for page components
14160
+ const result = Component({ searchParams, params });
14161
+ if (result && typeof result.then === 'function') {
14162
+ // It's a Promise (async component)
14163
+ const resolved = await result;
14164
+ if (!cancelled) setContent(resolved);
14165
+ } else {
14166
+ // Synchronous component - result is already JSX
14167
+ if (!cancelled) setContent(result);
14168
+ }
14169
+ } catch (e) {
14170
+ console.error('[AsyncComponent] Error rendering:', e);
14171
+ if (!cancelled) setError(e);
14172
+ }
14173
+ }
14174
+ render();
14175
+ return () => { cancelled = true; };
14176
+ }, [Component, pathname, search]);
14177
+
14178
+ if (error) {
14179
+ return React.createElement('div', { style: { color: 'red', padding: '20px' } },
14180
+ 'Error: ' + error.message
14181
+ );
14182
+ }
14183
+ if (!content) {
14184
+ return React.createElement('div', { style: { padding: '20px' } }, 'Loading...');
14185
+ }
14186
+ return content;
14187
+ }
14188
+
13414
14189
  // Router component
13415
14190
  function Router() {
13416
14191
  const [Page, setPage] = React.useState(null);
13417
14192
  const [layouts, setLayouts] = React.useState([]);
13418
14193
  const [path, setPath] = React.useState(window.location.pathname);
14194
+ const [search, setSearch] = React.useState(window.location.search);
13419
14195
 
13420
14196
  React.useEffect(() => {
13421
14197
  Promise.all([loadPage(path), loadLayouts(path)]).then(([P, L]) => {
@@ -13427,21 +14203,35 @@ class NextDevServer extends DevServer {
13427
14203
  React.useEffect(() => {
13428
14204
  const handleNavigation = async () => {
13429
14205
  const newPath = window.location.pathname;
14206
+ const newSearch = window.location.search;
14207
+ console.log('[Router] handleNavigation called, newPath:', newPath, 'current path:', path);
14208
+
14209
+ // Always update search params
14210
+ if (newSearch !== search) {
14211
+ setSearch(newSearch);
14212
+ }
14213
+
13430
14214
  if (newPath !== path) {
14215
+ console.log('[Router] Path changed, loading new page...');
13431
14216
  setPath(newPath);
13432
14217
  const [P, L] = await Promise.all([loadPage(newPath), loadLayouts(newPath)]);
14218
+ console.log('[Router] Page loaded:', !!P, 'Layouts:', L.length);
13433
14219
  if (P) setPage(() => P);
13434
14220
  setLayouts(L);
14221
+ } else {
14222
+ console.log('[Router] Path unchanged, skipping navigation');
13435
14223
  }
13436
14224
  };
13437
14225
  window.addEventListener('popstate', handleNavigation);
14226
+ console.log('[Router] Added popstate listener for path:', path);
13438
14227
  return () => window.removeEventListener('popstate', handleNavigation);
13439
- }, [path]);
14228
+ }, [path, search]);
13440
14229
 
13441
14230
  if (!Page) return null;
13442
14231
 
13443
- // Build nested layout structure
13444
- let content = React.createElement(Page);
14232
+ // Use AsyncComponent wrapper to handle async Server Components
14233
+ // Pass search to force re-render when query params change
14234
+ let content = React.createElement(AsyncComponent, { component: Page, pathname: path, search: search });
13445
14235
  for (let i = layouts.length - 1; i >= 0; i--) {
13446
14236
  content = React.createElement(layouts[i], null, content);
13447
14237
  }
@@ -13558,6 +14348,7 @@ class NextDevServer extends DevServer {
13558
14348
  }
13559
14349
  }
13560
14350
  const envScript = this.generateEnvScript();
14351
+ const tailwindConfigScript = await this.loadTailwindConfigIfNeeded();
13561
14352
  return `<!DOCTYPE html>
13562
14353
  <html lang="en">
13563
14354
  <head>
@@ -13567,6 +14358,7 @@ class NextDevServer extends DevServer {
13567
14358
  <title>Next.js App</title>
13568
14359
  ${envScript}
13569
14360
  ${TAILWIND_CDN_SCRIPT}
14361
+ ${tailwindConfigScript}
13570
14362
  ${CORS_PROXY_SCRIPT}
13571
14363
  ${globalCssLinks.join("\n ")}
13572
14364
  ${REACT_REFRESH_PREAMBLE}
@@ -13580,7 +14372,12 @@ class NextDevServer extends DevServer {
13580
14372
  "react-dom/client": "https://esm.sh/react-dom@18.2.0/client?dev",
13581
14373
  "next/link": "${virtualPrefix}/_next/shims/link.js",
13582
14374
  "next/router": "${virtualPrefix}/_next/shims/router.js",
13583
- "next/head": "${virtualPrefix}/_next/shims/head.js"
14375
+ "next/head": "${virtualPrefix}/_next/shims/head.js",
14376
+ "next/navigation": "${virtualPrefix}/_next/shims/navigation.js",
14377
+ "next/image": "${virtualPrefix}/_next/shims/image.js",
14378
+ "next/dynamic": "${virtualPrefix}/_next/shims/dynamic.js",
14379
+ "next/script": "${virtualPrefix}/_next/shims/script.js",
14380
+ "next/font/google": "${virtualPrefix}/_next/shims/font/google.js"
13584
14381
  }
13585
14382
  }
13586
14383
  </script>
@@ -13699,6 +14496,30 @@ class NextDevServer extends DevServer {
13699
14496
  body: buffer
13700
14497
  };
13701
14498
  }
14499
+ /**
14500
+ * Try to resolve a file path by adding common extensions
14501
+ * e.g., /components/faq -> /components/faq.tsx
14502
+ * Also handles index files in directories
14503
+ */
14504
+ resolveFileWithExtension(pathname) {
14505
+ if (/\.\w+$/.test(pathname) && this.exists(pathname)) {
14506
+ return pathname;
14507
+ }
14508
+ const extensions = [".tsx", ".ts", ".jsx", ".js"];
14509
+ for (const ext of extensions) {
14510
+ const withExt = pathname + ext;
14511
+ if (this.exists(withExt)) {
14512
+ return withExt;
14513
+ }
14514
+ }
14515
+ for (const ext of extensions) {
14516
+ const indexPath = pathname + "/index" + ext;
14517
+ if (this.exists(indexPath)) {
14518
+ return indexPath;
14519
+ }
14520
+ }
14521
+ return null;
14522
+ }
13702
14523
  /**
13703
14524
  * Check if a file needs transformation
13704
14525
  */
@@ -13711,7 +14532,25 @@ class NextDevServer extends DevServer {
13711
14532
  async transformAndServe(filePath, urlPath) {
13712
14533
  try {
13713
14534
  const content = this.vfs.readFileSync(filePath, "utf8");
13714
- const transformed = await this.transformCode(content, urlPath);
14535
+ const hash = simpleHash(content);
14536
+ const cached = this.transformCache.get(filePath);
14537
+ if (cached && cached.hash === hash) {
14538
+ const buffer2 = BufferPolyfill.from(cached.code);
14539
+ return {
14540
+ statusCode: 200,
14541
+ statusMessage: "OK",
14542
+ headers: {
14543
+ "Content-Type": "application/javascript; charset=utf-8",
14544
+ "Content-Length": String(buffer2.length),
14545
+ "Cache-Control": "no-cache",
14546
+ "X-Transformed": "true",
14547
+ "X-Cache": "hit"
14548
+ },
14549
+ body: buffer2
14550
+ };
14551
+ }
14552
+ const transformed = await this.transformCode(content, filePath);
14553
+ this.transformCache.set(filePath, { code: transformed, hash });
13715
14554
  const buffer = BufferPolyfill.from(transformed);
13716
14555
  return {
13717
14556
  statusCode: 200,
@@ -13753,11 +14592,12 @@ console.error(${JSON.stringify(message)});`;
13753
14592
  throw new Error("esbuild not available");
13754
14593
  }
13755
14594
  const codeWithoutCssImports = this.stripCssImports(code);
14595
+ const codeWithResolvedAliases = this.resolvePathAliases(codeWithoutCssImports, filename);
13756
14596
  let loader = "js";
13757
14597
  if (filename.endsWith(".jsx")) loader = "jsx";
13758
14598
  else if (filename.endsWith(".tsx")) loader = "tsx";
13759
14599
  else if (filename.endsWith(".ts")) loader = "ts";
13760
- const result = await esbuild.transform(codeWithoutCssImports, {
14600
+ const result = await esbuild.transform(codeWithResolvedAliases, {
13761
14601
  loader,
13762
14602
  format: "esm",
13763
14603
  target: "esnext",
@@ -13766,22 +14606,71 @@ console.error(${JSON.stringify(message)});`;
13766
14606
  sourcemap: "inline",
13767
14607
  sourcefile: filename
13768
14608
  });
14609
+ const codeWithCdnImports = this.redirectNpmImports(result.code);
13769
14610
  if (/\.(jsx|tsx)$/.test(filename)) {
13770
- return this.addReactRefresh(result.code, filename);
14611
+ return this.addReactRefresh(codeWithCdnImports, filename);
13771
14612
  }
13772
- return result.code;
14613
+ return codeWithCdnImports;
14614
+ }
14615
+ /**
14616
+ * Redirect bare npm package imports to esm.sh CDN
14617
+ * e.g., import { Crisp } from "crisp-sdk-web" -> import { Crisp } from "https://esm.sh/crisp-sdk-web?external=react"
14618
+ *
14619
+ * IMPORTANT: We redirect ALL npm packages to esm.sh URLs (including React)
14620
+ * because import maps don't work reliably for dynamically imported modules.
14621
+ */
14622
+ redirectNpmImports(code) {
14623
+ const explicitMappings = {
14624
+ "react": "https://esm.sh/react@18.2.0?dev",
14625
+ "react/jsx-runtime": "https://esm.sh/react@18.2.0&dev/jsx-runtime",
14626
+ "react/jsx-dev-runtime": "https://esm.sh/react@18.2.0&dev/jsx-dev-runtime",
14627
+ "react-dom": "https://esm.sh/react-dom@18.2.0?dev",
14628
+ "react-dom/client": "https://esm.sh/react-dom@18.2.0/client?dev"
14629
+ };
14630
+ const localPackages = /* @__PURE__ */ new Set([
14631
+ "next/link",
14632
+ "next/router",
14633
+ "next/head",
14634
+ "next/navigation",
14635
+ "next/dynamic",
14636
+ "next/image",
14637
+ "next/script",
14638
+ "next/font/google",
14639
+ "convex/_generated/api"
14640
+ ]);
14641
+ const importPattern = /(from\s*['"])([^'"./][^'"]*?)(['"])/g;
14642
+ return code.replace(importPattern, (match, prefix, packageName, suffix) => {
14643
+ if (packageName.startsWith("http://") || packageName.startsWith("https://") || packageName.startsWith("/__virtual__")) {
14644
+ return match;
14645
+ }
14646
+ if (explicitMappings[packageName]) {
14647
+ return `${prefix}${explicitMappings[packageName]}${suffix}`;
14648
+ }
14649
+ if (localPackages.has(packageName)) {
14650
+ return match;
14651
+ }
14652
+ const basePkg = packageName.includes("/") ? packageName.split("/")[0] : packageName;
14653
+ const isScoped = basePkg.startsWith("@");
14654
+ const scopedBasePkg = isScoped && packageName.includes("/") ? packageName.split("/").slice(0, 2).join("/") : basePkg;
14655
+ if (localPackages.has(scopedBasePkg)) {
14656
+ return match;
14657
+ }
14658
+ const esmUrl = `https://esm.sh/${packageName}?external=react`;
14659
+ return `${prefix}${esmUrl}${suffix}`;
14660
+ });
13773
14661
  }
13774
14662
  /**
13775
14663
  * Strip CSS imports from code (they are loaded via <link> tags instead)
13776
14664
  * Handles: import './styles.css', import '../globals.css', etc.
13777
14665
  */
13778
14666
  stripCssImports(code) {
13779
- return code.replace(/import\s+['"][^'"]+\.css['"]\s*;?/g, "// CSS import removed (loaded via <link>)");
14667
+ return code.replace(/import\s+['"][^'"]+\.css['"]\s*;?/g, "");
13780
14668
  }
13781
14669
  /**
13782
14670
  * Transform API handler code to CommonJS for eval execution
13783
14671
  */
13784
14672
  async transformApiHandler(code, filename) {
14673
+ const codeWithResolvedAliases = this.resolvePathAliases(code, filename);
13785
14674
  if (isBrowser) {
13786
14675
  await initEsbuild();
13787
14676
  const esbuild = getEsbuild();
@@ -13792,7 +14681,7 @@ console.error(${JSON.stringify(message)});`;
13792
14681
  if (filename.endsWith(".jsx")) loader = "jsx";
13793
14682
  else if (filename.endsWith(".tsx")) loader = "tsx";
13794
14683
  else if (filename.endsWith(".ts")) loader = "ts";
13795
- const result = await esbuild.transform(code, {
14684
+ const result = await esbuild.transform(codeWithResolvedAliases, {
13796
14685
  loader,
13797
14686
  format: "cjs",
13798
14687
  // CommonJS for eval execution
@@ -13802,7 +14691,7 @@ console.error(${JSON.stringify(message)});`;
13802
14691
  });
13803
14692
  return result.code;
13804
14693
  }
13805
- let transformed = code;
14694
+ let transformed = codeWithResolvedAliases;
13806
14695
  transformed = transformed.replace(
13807
14696
  /import\s+(\w+)\s+from\s+['"]([^'"]+)['"]/g,
13808
14697
  'const $1 = require("$2")'
@@ -13933,6 +14822,57 @@ ${registrations}
13933
14822
  }
13934
14823
  }
13935
14824
  }
14825
+ /**
14826
+ * Override serveFile to wrap JSON files as ES modules
14827
+ * This is needed because browsers can't dynamically import raw JSON files
14828
+ */
14829
+ serveFile(filePath) {
14830
+ if (filePath.endsWith(".json")) {
14831
+ try {
14832
+ const normalizedPath = this.resolvePath(filePath);
14833
+ const content = this.vfs.readFileSync(normalizedPath);
14834
+ let jsonContent;
14835
+ if (typeof content === "string") {
14836
+ jsonContent = content;
14837
+ } else if (content instanceof Uint8Array) {
14838
+ jsonContent = new TextDecoder("utf-8").decode(content);
14839
+ } else {
14840
+ jsonContent = BufferPolyfill.from(content).toString("utf-8");
14841
+ }
14842
+ const esModuleContent = `export default ${jsonContent};`;
14843
+ const buffer = BufferPolyfill.from(esModuleContent);
14844
+ return {
14845
+ statusCode: 200,
14846
+ statusMessage: "OK",
14847
+ headers: {
14848
+ "Content-Type": "application/javascript; charset=utf-8",
14849
+ "Content-Length": String(buffer.length),
14850
+ "Cache-Control": "no-cache"
14851
+ },
14852
+ body: buffer
14853
+ };
14854
+ } catch (error) {
14855
+ if (error.code === "ENOENT") {
14856
+ return this.notFound(filePath);
14857
+ }
14858
+ return this.serverError(error);
14859
+ }
14860
+ }
14861
+ return super.serveFile(filePath);
14862
+ }
14863
+ /**
14864
+ * Resolve a path (helper to access protected method from parent)
14865
+ */
14866
+ resolvePath(urlPath) {
14867
+ let path = urlPath.split("?")[0].split("#")[0];
14868
+ if (!path.startsWith("/")) {
14869
+ path = "/" + path;
14870
+ }
14871
+ if (this.root !== "/") {
14872
+ path = this.root + path;
14873
+ }
14874
+ return path;
14875
+ }
13936
14876
  /**
13937
14877
  * Stop the server
13938
14878
  */