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/LICENSE +1 -1
- package/dist/__sw__.js +25 -16
- package/dist/frameworks/next-dev-server.d.ts +61 -0
- package/dist/frameworks/next-dev-server.d.ts.map +1 -1
- package/dist/frameworks/tailwind-config-loader.d.ts +32 -0
- package/dist/frameworks/tailwind-config-loader.d.ts.map +1 -0
- package/dist/index.cjs +869 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.mjs +845 -27
- package/dist/index.mjs.map +1 -1
- package/dist/macaly-demo.d.ts +42 -0
- package/dist/macaly-demo.d.ts.map +1 -0
- package/package.json +2 -1
- package/src/frameworks/next-dev-server.ts +913 -34
- package/src/frameworks/tailwind-config-loader.ts +206 -0
- package/src/macaly-demo.ts +172 -0
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 =
|
|
12709
|
-
|
|
12710
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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("]")
|
|
13943
|
+
if (entry.startsWith("[...") && entry.endsWith("]")) {
|
|
13343
13944
|
const dynamicPath = `${dirPath}/${entry}`;
|
|
13344
13945
|
if (this.isDirectory(dynamicPath)) {
|
|
13345
|
-
const
|
|
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
|
-
//
|
|
13521
|
-
|
|
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,
|
|
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(
|
|
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(
|
|
14611
|
+
return this.addReactRefresh(codeWithCdnImports, filename);
|
|
13866
14612
|
}
|
|
13867
|
-
return
|
|
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, "
|
|
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(
|
|
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 =
|
|
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
|
*/
|