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