almostnode 0.2.5 → 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
@@ -12068,6 +12068,125 @@ export default css;
12068
12068
  }
12069
12069
  }
12070
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
+
12071
12190
  const isBrowser = typeof window !== "undefined" && typeof window.navigator !== "undefined" && "serviceWorker" in window.navigator;
12072
12191
  async function initEsbuild() {
12073
12192
  if (!isBrowser) return;
@@ -12292,25 +12411,31 @@ const applyVirtualBase = (url) => {
12292
12411
 
12293
12412
  export default function Link({ href, children, ...props }) {
12294
12413
  const handleClick = (e) => {
12414
+ console.log('[Link] Click handler called, href:', href);
12415
+
12295
12416
  if (props.onClick) {
12296
12417
  props.onClick(e);
12297
12418
  }
12298
12419
 
12299
12420
  // Allow cmd/ctrl click to open in new tab
12300
12421
  if (e.metaKey || e.ctrlKey) {
12422
+ console.log('[Link] Meta/Ctrl key pressed, allowing default behavior');
12301
12423
  return;
12302
12424
  }
12303
12425
 
12304
12426
  if (typeof href !== 'string' || !href || href.startsWith('#') || href.startsWith('?')) {
12427
+ console.log('[Link] Skipping navigation for href:', href);
12305
12428
  return;
12306
12429
  }
12307
12430
 
12308
12431
  if (/^(https?:)?\\/\\//.test(href)) {
12432
+ console.log('[Link] External URL, allowing default behavior:', href);
12309
12433
  return;
12310
12434
  }
12311
12435
 
12312
12436
  e.preventDefault();
12313
12437
  const resolvedHref = applyVirtualBase(href);
12438
+ console.log('[Link] Navigating to:', resolvedHref);
12314
12439
  window.history.pushState({}, '', resolvedHref);
12315
12440
  window.dispatchEvent(new PopStateEvent('popstate'));
12316
12441
  };
@@ -12649,6 +12774,305 @@ export default function Head({ children }) {
12649
12774
  return null;
12650
12775
  }
12651
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
+ `;
12652
13076
  class NextDevServer extends DevServer {
12653
13077
  /** Pages Router directory (default: '/pages') */
12654
13078
  pagesDir;
@@ -12666,6 +13090,14 @@ class NextDevServer extends DevServer {
12666
13090
  options;
12667
13091
  /** Transform result cache for performance */
12668
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 = "";
12669
13101
  constructor(vfs, options) {
12670
13102
  super(vfs, options);
12671
13103
  this.options = options;
@@ -12677,6 +13109,93 @@ class NextDevServer extends DevServer {
12677
13109
  } else {
12678
13110
  this.useAppRouter = this.hasAppRouter();
12679
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;
12680
13199
  }
12681
13200
  /**
12682
13201
  * Set an environment variable at runtime
@@ -12702,20 +13221,46 @@ class NextDevServer extends DevServer {
12702
13221
  /**
12703
13222
  * Generate a script tag that defines process.env with NEXT_PUBLIC_* variables
12704
13223
  * This makes environment variables available to browser code via process.env.NEXT_PUBLIC_*
13224
+ * Also includes all env variables for Server Component compatibility
12705
13225
  */
12706
13226
  generateEnvScript() {
12707
13227
  const env = this.options.env || {};
12708
- const publicEnvVars = Object.entries(env).filter(([key]) => key.startsWith("NEXT_PUBLIC_")).reduce((acc, [k, v]) => ({ ...acc, [k]: v }), {});
12709
- if (Object.keys(publicEnvVars).length === 0) {
12710
- return "";
13228
+ const publicEnvVars = {};
13229
+ for (const [key, value] of Object.entries(env)) {
13230
+ if (key.startsWith("NEXT_PUBLIC_")) {
13231
+ publicEnvVars[key] = value;
13232
+ }
12711
13233
  }
12712
13234
  return `<script>
12713
- // NEXT_PUBLIC_* environment variables (injected by NextDevServer)
13235
+ // Environment variables (injected by NextDevServer)
12714
13236
  window.process = window.process || {};
12715
13237
  window.process.env = window.process.env || {};
12716
13238
  Object.assign(window.process.env, ${JSON.stringify(publicEnvVars)});
12717
13239
  </script>`;
12718
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
+ }
12719
13264
  /**
12720
13265
  * Check if App Router is available
12721
13266
  */
@@ -12736,10 +13281,26 @@ class NextDevServer extends DevServer {
12736
13281
  */
12737
13282
  async handleRequest(method, url, headers, body) {
12738
13283
  const urlObj = new URL(url, "http://localhost");
12739
- 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
+ }
12740
13298
  if (pathname.startsWith("/_next/shims/")) {
12741
13299
  return this.serveNextShim(pathname);
12742
13300
  }
13301
+ if (pathname === "/_next/route-info") {
13302
+ return this.serveRouteInfo(urlObj.searchParams.get("pathname") || "/");
13303
+ }
12743
13304
  if (pathname.startsWith("/_next/pages/")) {
12744
13305
  return this.servePageComponent(pathname);
12745
13306
  }
@@ -12759,6 +13320,13 @@ class NextDevServer extends DevServer {
12759
13320
  if (this.needsTransform(pathname) && this.exists(pathname)) {
12760
13321
  return this.transformAndServe(pathname, pathname);
12761
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
+ }
12762
13330
  if (this.exists(pathname) && !this.isDirectory(pathname)) {
12763
13331
  return this.serveFile(pathname);
12764
13332
  }
@@ -12783,6 +13351,18 @@ class NextDevServer extends DevServer {
12783
13351
  case "navigation":
12784
13352
  code = NEXT_NAVIGATION_SHIM;
12785
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;
12786
13366
  default:
12787
13367
  return this.notFound(pathname);
12788
13368
  }
@@ -12798,6 +13378,26 @@ class NextDevServer extends DevServer {
12798
13378
  body: buffer
12799
13379
  };
12800
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
+ }
12801
13401
  /**
12802
13402
  * Serve static assets from /_next/static/
12803
13403
  */
@@ -13304,17 +13904,18 @@ class NextDevServer extends DevServer {
13304
13904
  for (const ext of extensions) {
13305
13905
  const pagePath = `${dirPath}/page${ext}`;
13306
13906
  if (this.exists(pagePath)) {
13307
- return { page: pagePath, layouts };
13907
+ return { page: pagePath, layouts, params: {} };
13308
13908
  }
13309
13909
  }
13310
13910
  return this.resolveAppDynamicRoute(pathname, segments);
13311
13911
  }
13312
13912
  /**
13313
13913
  * Resolve dynamic App Router routes like /app/[id]/page.jsx
13914
+ * Also extracts route params from dynamic segments
13314
13915
  */
13315
13916
  resolveAppDynamicRoute(pathname, segments) {
13316
13917
  const extensions = [".jsx", ".tsx", ".js", ".ts"];
13317
- const tryPath = (dirPath, remainingSegments, layouts2) => {
13918
+ const tryPath = (dirPath, remainingSegments, layouts2, params) => {
13318
13919
  for (const ext of extensions) {
13319
13920
  const layoutPath = `${dirPath}/layout${ext}`;
13320
13921
  if (this.exists(layoutPath) && !layouts2.includes(layoutPath)) {
@@ -13325,7 +13926,7 @@ class NextDevServer extends DevServer {
13325
13926
  for (const ext of extensions) {
13326
13927
  const pagePath = `${dirPath}/page${ext}`;
13327
13928
  if (this.exists(pagePath)) {
13328
- return { page: pagePath, layouts: layouts2 };
13929
+ return { page: pagePath, layouts: layouts2, params };
13329
13930
  }
13330
13931
  }
13331
13932
  return null;
@@ -13333,16 +13934,26 @@ class NextDevServer extends DevServer {
13333
13934
  const [current, ...rest] = remainingSegments;
13334
13935
  const exactPath = `${dirPath}/${current}`;
13335
13936
  if (this.isDirectory(exactPath)) {
13336
- const result = tryPath(exactPath, rest, layouts2);
13937
+ const result = tryPath(exactPath, rest, layouts2, params);
13337
13938
  if (result) return result;
13338
13939
  }
13339
13940
  try {
13340
13941
  const entries = this.vfs.readdirSync(dirPath);
13341
13942
  for (const entry of entries) {
13342
- if (entry.startsWith("[") && entry.endsWith("]") && !entry.includes(".")) {
13943
+ if (entry.startsWith("[...") && entry.endsWith("]")) {
13343
13944
  const dynamicPath = `${dirPath}/${entry}`;
13344
13945
  if (this.isDirectory(dynamicPath)) {
13345
- 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);
13346
13957
  if (result) return result;
13347
13958
  }
13348
13959
  }
@@ -13359,7 +13970,7 @@ class NextDevServer extends DevServer {
13359
13970
  break;
13360
13971
  }
13361
13972
  }
13362
- return tryPath(this.appDir, segments, layouts);
13973
+ return tryPath(this.appDir, segments, layouts, {});
13363
13974
  }
13364
13975
  /**
13365
13976
  * Generate HTML for App Router with nested layouts
@@ -13378,6 +13989,7 @@ class NextDevServer extends DevServer {
13378
13989
  for (let i = route.layouts.length - 1; i >= 0; i--) {
13379
13990
  }
13380
13991
  const envScript = this.generateEnvScript();
13992
+ const tailwindConfigScript = await this.loadTailwindConfigIfNeeded();
13381
13993
  return `<!DOCTYPE html>
13382
13994
  <html lang="en">
13383
13995
  <head>
@@ -13387,6 +13999,7 @@ class NextDevServer extends DevServer {
13387
13999
  <title>Next.js App</title>
13388
14000
  ${envScript}
13389
14001
  ${TAILWIND_CDN_SCRIPT}
14002
+ ${tailwindConfigScript}
13390
14003
  ${CORS_PROXY_SCRIPT}
13391
14004
  ${globalCssLinks.join("\n ")}
13392
14005
  ${REACT_REFRESH_PREAMBLE}
@@ -13408,7 +14021,11 @@ class NextDevServer extends DevServer {
13408
14021
  "next/link": "${virtualPrefix}/_next/shims/link.js",
13409
14022
  "next/router": "${virtualPrefix}/_next/shims/router.js",
13410
14023
  "next/head": "${virtualPrefix}/_next/shims/head.js",
13411
- "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"
13412
14029
  }
13413
14030
  }
13414
14031
  </script>
@@ -13422,6 +14039,39 @@ class NextDevServer extends DevServer {
13422
14039
 
13423
14040
  const virtualBase = '${virtualPrefix}';
13424
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
+
13425
14075
  // Convert URL path to app router page module path
13426
14076
  function getAppPageModulePath(pathname) {
13427
14077
  let route = pathname;
@@ -13488,11 +14138,60 @@ class NextDevServer extends DevServer {
13488
14138
  return layouts;
13489
14139
  }
13490
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
+
13491
14189
  // Router component
13492
14190
  function Router() {
13493
14191
  const [Page, setPage] = React.useState(null);
13494
14192
  const [layouts, setLayouts] = React.useState([]);
13495
14193
  const [path, setPath] = React.useState(window.location.pathname);
14194
+ const [search, setSearch] = React.useState(window.location.search);
13496
14195
 
13497
14196
  React.useEffect(() => {
13498
14197
  Promise.all([loadPage(path), loadLayouts(path)]).then(([P, L]) => {
@@ -13504,21 +14203,35 @@ class NextDevServer extends DevServer {
13504
14203
  React.useEffect(() => {
13505
14204
  const handleNavigation = async () => {
13506
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
+
13507
14214
  if (newPath !== path) {
14215
+ console.log('[Router] Path changed, loading new page...');
13508
14216
  setPath(newPath);
13509
14217
  const [P, L] = await Promise.all([loadPage(newPath), loadLayouts(newPath)]);
14218
+ console.log('[Router] Page loaded:', !!P, 'Layouts:', L.length);
13510
14219
  if (P) setPage(() => P);
13511
14220
  setLayouts(L);
14221
+ } else {
14222
+ console.log('[Router] Path unchanged, skipping navigation');
13512
14223
  }
13513
14224
  };
13514
14225
  window.addEventListener('popstate', handleNavigation);
14226
+ console.log('[Router] Added popstate listener for path:', path);
13515
14227
  return () => window.removeEventListener('popstate', handleNavigation);
13516
- }, [path]);
14228
+ }, [path, search]);
13517
14229
 
13518
14230
  if (!Page) return null;
13519
14231
 
13520
- // Build nested layout structure
13521
- 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 });
13522
14235
  for (let i = layouts.length - 1; i >= 0; i--) {
13523
14236
  content = React.createElement(layouts[i], null, content);
13524
14237
  }
@@ -13635,6 +14348,7 @@ class NextDevServer extends DevServer {
13635
14348
  }
13636
14349
  }
13637
14350
  const envScript = this.generateEnvScript();
14351
+ const tailwindConfigScript = await this.loadTailwindConfigIfNeeded();
13638
14352
  return `<!DOCTYPE html>
13639
14353
  <html lang="en">
13640
14354
  <head>
@@ -13644,6 +14358,7 @@ class NextDevServer extends DevServer {
13644
14358
  <title>Next.js App</title>
13645
14359
  ${envScript}
13646
14360
  ${TAILWIND_CDN_SCRIPT}
14361
+ ${tailwindConfigScript}
13647
14362
  ${CORS_PROXY_SCRIPT}
13648
14363
  ${globalCssLinks.join("\n ")}
13649
14364
  ${REACT_REFRESH_PREAMBLE}
@@ -13657,7 +14372,12 @@ class NextDevServer extends DevServer {
13657
14372
  "react-dom/client": "https://esm.sh/react-dom@18.2.0/client?dev",
13658
14373
  "next/link": "${virtualPrefix}/_next/shims/link.js",
13659
14374
  "next/router": "${virtualPrefix}/_next/shims/router.js",
13660
- "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"
13661
14381
  }
13662
14382
  }
13663
14383
  </script>
@@ -13776,6 +14496,30 @@ class NextDevServer extends DevServer {
13776
14496
  body: buffer
13777
14497
  };
13778
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
+ }
13779
14523
  /**
13780
14524
  * Check if a file needs transformation
13781
14525
  */
@@ -13805,7 +14549,7 @@ class NextDevServer extends DevServer {
13805
14549
  body: buffer2
13806
14550
  };
13807
14551
  }
13808
- const transformed = await this.transformCode(content, urlPath);
14552
+ const transformed = await this.transformCode(content, filePath);
13809
14553
  this.transformCache.set(filePath, { code: transformed, hash });
13810
14554
  const buffer = BufferPolyfill.from(transformed);
13811
14555
  return {
@@ -13848,11 +14592,12 @@ console.error(${JSON.stringify(message)});`;
13848
14592
  throw new Error("esbuild not available");
13849
14593
  }
13850
14594
  const codeWithoutCssImports = this.stripCssImports(code);
14595
+ const codeWithResolvedAliases = this.resolvePathAliases(codeWithoutCssImports, filename);
13851
14596
  let loader = "js";
13852
14597
  if (filename.endsWith(".jsx")) loader = "jsx";
13853
14598
  else if (filename.endsWith(".tsx")) loader = "tsx";
13854
14599
  else if (filename.endsWith(".ts")) loader = "ts";
13855
- const result = await esbuild.transform(codeWithoutCssImports, {
14600
+ const result = await esbuild.transform(codeWithResolvedAliases, {
13856
14601
  loader,
13857
14602
  format: "esm",
13858
14603
  target: "esnext",
@@ -13861,22 +14606,71 @@ console.error(${JSON.stringify(message)});`;
13861
14606
  sourcemap: "inline",
13862
14607
  sourcefile: filename
13863
14608
  });
14609
+ const codeWithCdnImports = this.redirectNpmImports(result.code);
13864
14610
  if (/\.(jsx|tsx)$/.test(filename)) {
13865
- return this.addReactRefresh(result.code, filename);
14611
+ return this.addReactRefresh(codeWithCdnImports, filename);
13866
14612
  }
13867
- 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
+ });
13868
14661
  }
13869
14662
  /**
13870
14663
  * Strip CSS imports from code (they are loaded via <link> tags instead)
13871
14664
  * Handles: import './styles.css', import '../globals.css', etc.
13872
14665
  */
13873
14666
  stripCssImports(code) {
13874
- return code.replace(/import\s+['"][^'"]+\.css['"]\s*;?/g, "// CSS import removed (loaded via <link>)");
14667
+ return code.replace(/import\s+['"][^'"]+\.css['"]\s*;?/g, "");
13875
14668
  }
13876
14669
  /**
13877
14670
  * Transform API handler code to CommonJS for eval execution
13878
14671
  */
13879
14672
  async transformApiHandler(code, filename) {
14673
+ const codeWithResolvedAliases = this.resolvePathAliases(code, filename);
13880
14674
  if (isBrowser) {
13881
14675
  await initEsbuild();
13882
14676
  const esbuild = getEsbuild();
@@ -13887,7 +14681,7 @@ console.error(${JSON.stringify(message)});`;
13887
14681
  if (filename.endsWith(".jsx")) loader = "jsx";
13888
14682
  else if (filename.endsWith(".tsx")) loader = "tsx";
13889
14683
  else if (filename.endsWith(".ts")) loader = "ts";
13890
- const result = await esbuild.transform(code, {
14684
+ const result = await esbuild.transform(codeWithResolvedAliases, {
13891
14685
  loader,
13892
14686
  format: "cjs",
13893
14687
  // CommonJS for eval execution
@@ -13897,7 +14691,7 @@ console.error(${JSON.stringify(message)});`;
13897
14691
  });
13898
14692
  return result.code;
13899
14693
  }
13900
- let transformed = code;
14694
+ let transformed = codeWithResolvedAliases;
13901
14695
  transformed = transformed.replace(
13902
14696
  /import\s+(\w+)\s+from\s+['"]([^'"]+)['"]/g,
13903
14697
  'const $1 = require("$2")'
@@ -14028,6 +14822,57 @@ ${registrations}
14028
14822
  }
14029
14823
  }
14030
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
+ }
14031
14876
  /**
14032
14877
  * Stop the server
14033
14878
  */