@run0/jiki 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser-bundle.d.ts +40 -0
- package/dist/builtins.d.ts +22 -0
- package/dist/code-transform.d.ts +7 -0
- package/dist/config/cdn.d.ts +13 -0
- package/dist/container.d.ts +101 -0
- package/dist/dev-server.d.ts +69 -0
- package/dist/errors.d.ts +19 -0
- package/dist/frameworks/code-transforms.d.ts +32 -0
- package/dist/frameworks/next-api-handler.d.ts +72 -0
- package/dist/frameworks/next-dev-server.d.ts +141 -0
- package/dist/frameworks/next-html-generator.d.ts +36 -0
- package/dist/frameworks/next-route-resolver.d.ts +19 -0
- package/dist/frameworks/next-shims.d.ts +78 -0
- package/dist/frameworks/remix-dev-server.d.ts +47 -0
- package/dist/frameworks/sveltekit-dev-server.d.ts +43 -0
- package/dist/frameworks/vite-dev-server.d.ts +50 -0
- package/dist/fs-errors.d.ts +36 -0
- package/dist/index.cjs +14916 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +61 -0
- package/dist/index.mjs +14898 -0
- package/dist/index.mjs.map +1 -0
- package/dist/kernel.d.ts +48 -0
- package/dist/memfs.d.ts +144 -0
- package/dist/metrics.d.ts +78 -0
- package/dist/module-resolver.d.ts +60 -0
- package/dist/network-interceptor.d.ts +71 -0
- package/dist/npm/cache.d.ts +76 -0
- package/dist/npm/index.d.ts +60 -0
- package/dist/npm/lockfile-reader.d.ts +32 -0
- package/dist/npm/pnpm.d.ts +18 -0
- package/dist/npm/registry.d.ts +45 -0
- package/dist/npm/resolver.d.ts +39 -0
- package/dist/npm/sync-installer.d.ts +18 -0
- package/dist/npm/tarball.d.ts +4 -0
- package/dist/npm/workspaces.d.ts +46 -0
- package/dist/persistence.d.ts +94 -0
- package/dist/plugin.d.ts +156 -0
- package/dist/polyfills/assert.d.ts +30 -0
- package/dist/polyfills/child_process.d.ts +116 -0
- package/dist/polyfills/chokidar.d.ts +18 -0
- package/dist/polyfills/crypto.d.ts +49 -0
- package/dist/polyfills/events.d.ts +28 -0
- package/dist/polyfills/fs.d.ts +82 -0
- package/dist/polyfills/http.d.ts +147 -0
- package/dist/polyfills/module.d.ts +29 -0
- package/dist/polyfills/net.d.ts +53 -0
- package/dist/polyfills/os.d.ts +91 -0
- package/dist/polyfills/path.d.ts +96 -0
- package/dist/polyfills/perf_hooks.d.ts +21 -0
- package/dist/polyfills/process.d.ts +99 -0
- package/dist/polyfills/querystring.d.ts +15 -0
- package/dist/polyfills/readdirp.d.ts +18 -0
- package/dist/polyfills/readline.d.ts +32 -0
- package/dist/polyfills/stream.d.ts +106 -0
- package/dist/polyfills/stubs.d.ts +737 -0
- package/dist/polyfills/tty.d.ts +25 -0
- package/dist/polyfills/url.d.ts +41 -0
- package/dist/polyfills/util.d.ts +61 -0
- package/dist/polyfills/v8.d.ts +43 -0
- package/dist/polyfills/vm.d.ts +76 -0
- package/dist/polyfills/worker-threads.d.ts +77 -0
- package/dist/polyfills/ws.d.ts +32 -0
- package/dist/polyfills/zlib.d.ts +87 -0
- package/dist/runtime-helpers.d.ts +4 -0
- package/dist/runtime-interface.d.ts +39 -0
- package/dist/sandbox.d.ts +69 -0
- package/dist/server-bridge.d.ts +55 -0
- package/dist/shell-commands.d.ts +2 -0
- package/dist/shell.d.ts +101 -0
- package/dist/transpiler.d.ts +47 -0
- package/dist/type-checker.d.ts +57 -0
- package/dist/types/package-json.d.ts +17 -0
- package/dist/utils/binary-encoding.d.ts +4 -0
- package/dist/utils/hash.d.ts +6 -0
- package/dist/utils/safe-path.d.ts +6 -0
- package/dist/worker-runtime.d.ts +34 -0
- package/package.json +59 -0
- package/src/browser-bundle.ts +498 -0
- package/src/builtins.ts +222 -0
- package/src/code-transform.ts +183 -0
- package/src/config/cdn.ts +17 -0
- package/src/container.ts +343 -0
- package/src/dev-server.ts +322 -0
- package/src/errors.ts +604 -0
- package/src/frameworks/code-transforms.ts +667 -0
- package/src/frameworks/next-api-handler.ts +366 -0
- package/src/frameworks/next-dev-server.ts +1252 -0
- package/src/frameworks/next-html-generator.ts +585 -0
- package/src/frameworks/next-route-resolver.ts +521 -0
- package/src/frameworks/next-shims.ts +1084 -0
- package/src/frameworks/remix-dev-server.ts +163 -0
- package/src/frameworks/sveltekit-dev-server.ts +197 -0
- package/src/frameworks/vite-dev-server.ts +370 -0
- package/src/fs-errors.ts +118 -0
- package/src/index.ts +188 -0
- package/src/kernel.ts +381 -0
- package/src/memfs.ts +1006 -0
- package/src/metrics.ts +140 -0
- package/src/module-resolver.ts +511 -0
- package/src/network-interceptor.ts +143 -0
- package/src/npm/cache.ts +172 -0
- package/src/npm/index.ts +377 -0
- package/src/npm/lockfile-reader.ts +105 -0
- package/src/npm/pnpm.ts +108 -0
- package/src/npm/registry.ts +120 -0
- package/src/npm/resolver.ts +339 -0
- package/src/npm/sync-installer.ts +217 -0
- package/src/npm/tarball.ts +136 -0
- package/src/npm/workspaces.ts +255 -0
- package/src/persistence.ts +235 -0
- package/src/plugin.ts +293 -0
- package/src/polyfills/assert.ts +164 -0
- package/src/polyfills/child_process.ts +535 -0
- package/src/polyfills/chokidar.ts +52 -0
- package/src/polyfills/crypto.ts +433 -0
- package/src/polyfills/events.ts +178 -0
- package/src/polyfills/fs.ts +297 -0
- package/src/polyfills/http.ts +478 -0
- package/src/polyfills/module.ts +97 -0
- package/src/polyfills/net.ts +123 -0
- package/src/polyfills/os.ts +108 -0
- package/src/polyfills/path.ts +169 -0
- package/src/polyfills/perf_hooks.ts +30 -0
- package/src/polyfills/process.ts +349 -0
- package/src/polyfills/querystring.ts +66 -0
- package/src/polyfills/readdirp.ts +72 -0
- package/src/polyfills/readline.ts +80 -0
- package/src/polyfills/stream.ts +610 -0
- package/src/polyfills/stubs.ts +600 -0
- package/src/polyfills/tty.ts +43 -0
- package/src/polyfills/url.ts +97 -0
- package/src/polyfills/util.ts +173 -0
- package/src/polyfills/v8.ts +62 -0
- package/src/polyfills/vm.ts +111 -0
- package/src/polyfills/worker-threads.ts +189 -0
- package/src/polyfills/ws.ts +73 -0
- package/src/polyfills/zlib.ts +244 -0
- package/src/runtime-helpers.ts +83 -0
- package/src/runtime-interface.ts +46 -0
- package/src/sandbox.ts +178 -0
- package/src/server-bridge.ts +473 -0
- package/src/service-worker.ts +153 -0
- package/src/shell-commands.ts +708 -0
- package/src/shell.ts +795 -0
- package/src/transpiler.ts +282 -0
- package/src/type-checker.ts +241 -0
- package/src/types/package-json.ts +17 -0
- package/src/utils/binary-encoding.ts +38 -0
- package/src/utils/hash.ts +24 -0
- package/src/utils/safe-path.ts +38 -0
- package/src/worker-runtime.ts +42 -0
|
@@ -0,0 +1,667 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Code transformation utilities for ESM/CJS conversion, CSS imports,
|
|
3
|
+
* npm import redirection, and React Refresh registration.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import * as acorn from "acorn";
|
|
7
|
+
import acornJsx from "acorn-jsx";
|
|
8
|
+
import { simpleHash } from "../utils/hash";
|
|
9
|
+
import { REACT_CDN, REACT_DOM_CDN } from "../config/cdn";
|
|
10
|
+
|
|
11
|
+
const jsxParser = acorn.Parser.extend(acornJsx());
|
|
12
|
+
|
|
13
|
+
let _varCounter = 0;
|
|
14
|
+
|
|
15
|
+
// JS identifiers can contain $, so we use [\w$]+ instead of \w+
|
|
16
|
+
const ID = "[\\w$]+";
|
|
17
|
+
|
|
18
|
+
export interface CssModuleContext {
|
|
19
|
+
readFile: (path: string) => string;
|
|
20
|
+
exists: (path: string) => boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function transformEsmToCjsSimple(code: string): string {
|
|
24
|
+
let result = code;
|
|
25
|
+
|
|
26
|
+
// Transform: import defaultExport, { named } from 'module' (combined)
|
|
27
|
+
result = result.replace(
|
|
28
|
+
new RegExp(
|
|
29
|
+
`^([ \\t]*)import\\s+(${ID})\\s*,\\s*\\{([\\s\\S]+?)\\}\\s+from\\s+['\"]([^'\"]+)['\"]\\s*;?`,
|
|
30
|
+
"gm",
|
|
31
|
+
),
|
|
32
|
+
(_, indent, defaultName, imports, mod) => {
|
|
33
|
+
const parts = imports
|
|
34
|
+
.split(",")
|
|
35
|
+
.map((s: string) => s.trim())
|
|
36
|
+
.filter(Boolean);
|
|
37
|
+
const bindings = parts.map((p: string) => {
|
|
38
|
+
const [name, alias] = p.split(/\s+as\s+/).map((s: string) => s.trim());
|
|
39
|
+
return alias ? `${name}: ${alias}` : name;
|
|
40
|
+
});
|
|
41
|
+
const tmpVar = `_mod_${(++_varCounter).toString(36)}`;
|
|
42
|
+
return `${indent}const ${tmpVar} = require("${mod}");\n${indent}const ${defaultName} = ${tmpVar}.default || ${tmpVar};\n${indent}const { ${bindings.join(
|
|
43
|
+
", ",
|
|
44
|
+
)} } = ${tmpVar};`;
|
|
45
|
+
},
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
// Transform: import defaultExport, * as name from 'module' (combined)
|
|
49
|
+
result = result.replace(
|
|
50
|
+
new RegExp(
|
|
51
|
+
`^([ \\t]*)import\\s+(${ID})\\s*,\\s*\\*\\s+as\\s+(${ID})\\s+from\\s+['\"]([^'\"]+)['\"]\\s*;?`,
|
|
52
|
+
"gm",
|
|
53
|
+
),
|
|
54
|
+
(_, indent, defaultName, nsName, mod) => {
|
|
55
|
+
return `${indent}const ${nsName} = require("${mod}");\n${indent}const ${defaultName} = ${nsName}.default || ${nsName};`;
|
|
56
|
+
},
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
// Transform: import defaultExport from 'module'
|
|
60
|
+
result = result.replace(
|
|
61
|
+
new RegExp(
|
|
62
|
+
`^([ \\t]*)import\\s+(${ID})\\s+from\\s+['\"]([^'\"]+)['\"]\\s*;?`,
|
|
63
|
+
"gm",
|
|
64
|
+
),
|
|
65
|
+
(_, indent, name, mod) => `${indent}const ${name} = require("${mod}");`,
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
// Transform: import { a, b as c } from 'module'
|
|
69
|
+
result = result.replace(
|
|
70
|
+
/^([ \t]*)import\s+\{([\s\S]+?)\}\s+from\s+['"]([^'"]+)['"]\s*;?/gm,
|
|
71
|
+
(_, indent, imports, mod) => {
|
|
72
|
+
const parts = imports
|
|
73
|
+
.split(",")
|
|
74
|
+
.map((s: string) => s.trim())
|
|
75
|
+
.filter(Boolean);
|
|
76
|
+
const bindings = parts.map((p: string) => {
|
|
77
|
+
const [name, alias] = p.split(/\s+as\s+/).map((s: string) => s.trim());
|
|
78
|
+
return alias ? `${name}: ${alias}` : name;
|
|
79
|
+
});
|
|
80
|
+
return `${indent}const { ${bindings.join(", ")} } = require("${mod}");`;
|
|
81
|
+
},
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
// Transform: import * as name from 'module'
|
|
85
|
+
result = result.replace(
|
|
86
|
+
new RegExp(
|
|
87
|
+
`^([ \\t]*)import\\s+\\*\\s+as\\s+(${ID})\\s+from\\s+['\"]([^'\"]+)['\"]\\s*;?`,
|
|
88
|
+
"gm",
|
|
89
|
+
),
|
|
90
|
+
(_, indent, name, mod) => `${indent}const ${name} = require("${mod}");`,
|
|
91
|
+
);
|
|
92
|
+
|
|
93
|
+
// Transform: import 'module' (side-effect only)
|
|
94
|
+
result = result.replace(
|
|
95
|
+
/^([ \t]*)import\s+['"]([^'"]+)['"]\s*;?/gm,
|
|
96
|
+
(_, indent, mod) => `${indent}require("${mod}");`,
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
// Transform: export default expr
|
|
100
|
+
result = result.replace(
|
|
101
|
+
/^([ \t]*)export\s+default\s+(?=class\s|function\s)/gm,
|
|
102
|
+
"$1module.exports = exports.default = ",
|
|
103
|
+
);
|
|
104
|
+
result = result.replace(
|
|
105
|
+
/^([ \t]*)export\s+default\s+/gm,
|
|
106
|
+
"$1module.exports = exports.default = ",
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
// Transform: export { a, b as c }
|
|
110
|
+
result = result.replace(
|
|
111
|
+
/^([ \t]*)export\s+\{([\s\S]+?)\}\s*(?:from\s+['"]([^'"]+)['"])?\s*;?/gm,
|
|
112
|
+
(_, indent, exportsList, mod) => {
|
|
113
|
+
const parts = exportsList
|
|
114
|
+
.split(",")
|
|
115
|
+
.map((s: string) => s.trim())
|
|
116
|
+
.filter(Boolean);
|
|
117
|
+
if (mod) {
|
|
118
|
+
const tmpVar = `_re_export_${(++_varCounter).toString(36)}`;
|
|
119
|
+
const lines = [`${indent}const ${tmpVar} = require("${mod}");`];
|
|
120
|
+
for (const part of parts) {
|
|
121
|
+
const [name, alias] = part
|
|
122
|
+
.split(/\s+as\s+/)
|
|
123
|
+
.map((s: string) => s.trim());
|
|
124
|
+
lines.push(
|
|
125
|
+
`${indent}Object.defineProperty(exports, "${
|
|
126
|
+
alias || name
|
|
127
|
+
}", { enumerable: true, get: function() { return ${tmpVar}.${name}; } });`,
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
return lines.join("\n");
|
|
131
|
+
}
|
|
132
|
+
return parts
|
|
133
|
+
.map((part: string) => {
|
|
134
|
+
const [name, alias] = part
|
|
135
|
+
.split(/\s+as\s+/)
|
|
136
|
+
.map((s: string) => s.trim());
|
|
137
|
+
return `${indent}Object.defineProperty(exports, "${
|
|
138
|
+
alias || name
|
|
139
|
+
}", { enumerable: true, get: function() { return ${name}; } });`;
|
|
140
|
+
})
|
|
141
|
+
.join("\n");
|
|
142
|
+
},
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
// Transform: export const/let/var name = ...
|
|
146
|
+
result = result.replace(/^([ \t]*)export\s+(const|let|var)\s+/gm, "$1$2 ");
|
|
147
|
+
const declMatch = code.matchAll(
|
|
148
|
+
new RegExp(`^[ \\t]*export\\s+(?:const|let|var)\\s+(${ID})`, "gm"),
|
|
149
|
+
);
|
|
150
|
+
for (const m of declMatch) {
|
|
151
|
+
const name = m[1];
|
|
152
|
+
result += `\nObject.defineProperty(exports, "${name}", { enumerable: true, get: function() { return ${name}; } });`;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Transform: export function name() / export class name / export async function
|
|
156
|
+
result = result.replace(
|
|
157
|
+
new RegExp(
|
|
158
|
+
`^([ \\t]*)export\\s+(async\\s+)?(function|class)\\s+(${ID})`,
|
|
159
|
+
"gm",
|
|
160
|
+
),
|
|
161
|
+
(_, indent, async_, type, name) =>
|
|
162
|
+
`${indent}${async_ || ""}${type} ${name}`,
|
|
163
|
+
);
|
|
164
|
+
const funcClassMatch = code.matchAll(
|
|
165
|
+
new RegExp(
|
|
166
|
+
`^[ \\t]*export\\s+(?:async\\s+)?(?:function|class)\\s+(${ID})`,
|
|
167
|
+
"gm",
|
|
168
|
+
),
|
|
169
|
+
);
|
|
170
|
+
for (const m of funcClassMatch) {
|
|
171
|
+
const name = m[1];
|
|
172
|
+
result += `\nObject.defineProperty(exports, "${name}", { enumerable: true, get: function() { return ${name}; } });`;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// Transform: export * as name from 'module' (must come before export * from)
|
|
176
|
+
result = result.replace(
|
|
177
|
+
new RegExp(
|
|
178
|
+
`^([ \\t]*)export\\s+\\*\\s+as\\s+(${ID})\\s+from\\s+['\"]([^'\"]+)['\"]\\s*;?`,
|
|
179
|
+
"gm",
|
|
180
|
+
),
|
|
181
|
+
(_, indent, name, mod) => `${indent}exports.${name} = require("${mod}");`,
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
// Transform: export * from 'module' — filter dangerous keys and default
|
|
185
|
+
result = result.replace(
|
|
186
|
+
/^([ \t]*)export\s+\*\s+from\s+['"]([^'"]+)['"]\s*;?/gm,
|
|
187
|
+
(_, indent, mod) =>
|
|
188
|
+
`${indent}(function(m) { for (var k in m) if (k !== "default" && k !== "__proto__" && k !== "constructor" && k !== "prototype" && Object.prototype.hasOwnProperty.call(m, k)) exports[k] = m[k]; })(require("${mod}"));`,
|
|
189
|
+
);
|
|
190
|
+
|
|
191
|
+
return result;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// ── CSS Import Handling ──────────────────────────────────────────────
|
|
195
|
+
|
|
196
|
+
export function resolveRelativePath(dir: string, relativePath: string): string {
|
|
197
|
+
const parts = dir.split("/").filter(Boolean);
|
|
198
|
+
const relParts = relativePath.split("/");
|
|
199
|
+
|
|
200
|
+
for (const part of relParts) {
|
|
201
|
+
if (part === "..") {
|
|
202
|
+
parts.pop();
|
|
203
|
+
} else if (part !== "." && part !== "") {
|
|
204
|
+
parts.push(part);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return "/" + parts.join("/");
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export function resolveCssModulePath(
|
|
212
|
+
cssPath: string,
|
|
213
|
+
currentFile: string | undefined,
|
|
214
|
+
ctx: CssModuleContext,
|
|
215
|
+
): string | null {
|
|
216
|
+
if (currentFile && (cssPath.startsWith("./") || cssPath.startsWith("../"))) {
|
|
217
|
+
const dir = currentFile.replace(/\/[^/]+$/, "");
|
|
218
|
+
const resolved = resolveRelativePath(dir, cssPath);
|
|
219
|
+
if (ctx.exists(resolved)) return resolved;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (ctx.exists(cssPath)) return cssPath;
|
|
223
|
+
|
|
224
|
+
const withSlash = "/" + cssPath.replace(/^\.\//, "");
|
|
225
|
+
if (ctx.exists(withSlash)) return withSlash;
|
|
226
|
+
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Generate replacement code for a CSS Module import.
|
|
232
|
+
* Parses the CSS file, extracts class names via regex (no css-tree dependency),
|
|
233
|
+
* generates scoped names, and injects the scoped CSS via a style tag.
|
|
234
|
+
*/
|
|
235
|
+
export function generateCssModuleReplacement(
|
|
236
|
+
varName: string,
|
|
237
|
+
cssPath: string,
|
|
238
|
+
currentFile: string | undefined,
|
|
239
|
+
ctx: CssModuleContext,
|
|
240
|
+
): string {
|
|
241
|
+
try {
|
|
242
|
+
const resolvedPath = resolveCssModulePath(cssPath, currentFile, ctx);
|
|
243
|
+
if (!resolvedPath) return `const ${varName} = {};`;
|
|
244
|
+
|
|
245
|
+
const cssContent = ctx.readFile(resolvedPath);
|
|
246
|
+
const fileHash = simpleHash(resolvedPath + cssContent).slice(0, 6);
|
|
247
|
+
|
|
248
|
+
const classMap: Record<string, string> = {};
|
|
249
|
+
|
|
250
|
+
// Extract class names via regex and build scoped names
|
|
251
|
+
const classRegex = /\.([a-zA-Z_][\w-]*)/g;
|
|
252
|
+
let m;
|
|
253
|
+
while ((m = classRegex.exec(cssContent)) !== null) {
|
|
254
|
+
if (!classMap[m[1]]) {
|
|
255
|
+
classMap[m[1]] = `${m[1]}_${fileHash}`;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// Replace class names in CSS to create scoped version
|
|
260
|
+
let scopedCss = cssContent;
|
|
261
|
+
for (const [original, scoped] of Object.entries(classMap)) {
|
|
262
|
+
scopedCss = scopedCss.replace(
|
|
263
|
+
new RegExp(
|
|
264
|
+
`\\.${original.replace(
|
|
265
|
+
/[-/\\^$*+?.()|[\]{}]/g,
|
|
266
|
+
"\\$&",
|
|
267
|
+
)}(?=[\\s{:,>+~])`,
|
|
268
|
+
"g",
|
|
269
|
+
),
|
|
270
|
+
`.${scoped}`,
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
const escapedCss = scopedCss
|
|
275
|
+
.replace(/\\/g, "\\\\")
|
|
276
|
+
.replace(/`/g, "\\`")
|
|
277
|
+
.replace(/\$/g, "\\$");
|
|
278
|
+
|
|
279
|
+
const mapEntries = Object.entries(classMap)
|
|
280
|
+
.map(([k, v]) => `${JSON.stringify(k)}: ${JSON.stringify(v)}`)
|
|
281
|
+
.join(", ");
|
|
282
|
+
|
|
283
|
+
return `const ${varName} = {${mapEntries}};
|
|
284
|
+
(function() {
|
|
285
|
+
if (typeof document !== 'undefined') {
|
|
286
|
+
var id = ${JSON.stringify("cssmod-" + fileHash)};
|
|
287
|
+
if (!document.getElementById(id)) {
|
|
288
|
+
var s = document.createElement('style');
|
|
289
|
+
s.id = id;
|
|
290
|
+
s.textContent = \`${escapedCss}\`;
|
|
291
|
+
document.head.appendChild(s);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
})();`;
|
|
295
|
+
} catch {
|
|
296
|
+
return `const ${varName} = {};`;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/**
|
|
301
|
+
* Strip CSS imports from code. CSS Module imports (*.module.css) are converted
|
|
302
|
+
* to inline objects with class name mappings. Regular CSS imports are removed.
|
|
303
|
+
*/
|
|
304
|
+
export function stripCssImports(
|
|
305
|
+
code: string,
|
|
306
|
+
currentFile: string | undefined,
|
|
307
|
+
ctx: CssModuleContext,
|
|
308
|
+
): string {
|
|
309
|
+
// CSS Module default imports
|
|
310
|
+
code = code.replace(
|
|
311
|
+
/import\s+(\w+)\s+from\s+['"]([^'"]+\.module\.css)['"]\s*;?/g,
|
|
312
|
+
(_match, varName, cssPath) => {
|
|
313
|
+
return generateCssModuleReplacement(varName, cssPath, currentFile, ctx);
|
|
314
|
+
},
|
|
315
|
+
);
|
|
316
|
+
|
|
317
|
+
// Destructured CSS Module imports
|
|
318
|
+
code = code.replace(
|
|
319
|
+
/import\s+\{([\s\S]+?)\}\s+from\s+['"]([^'"]+\.module\.css)['"]\s*;?/g,
|
|
320
|
+
(_match, names, cssPath) => {
|
|
321
|
+
const varName = "__cssModule_" + simpleHash(cssPath);
|
|
322
|
+
const replacement = generateCssModuleReplacement(
|
|
323
|
+
varName,
|
|
324
|
+
cssPath,
|
|
325
|
+
currentFile,
|
|
326
|
+
ctx,
|
|
327
|
+
);
|
|
328
|
+
const namedExports = (names as string)
|
|
329
|
+
.split(",")
|
|
330
|
+
.map((n: string) => {
|
|
331
|
+
const trimmed = n.trim();
|
|
332
|
+
const parts = trimmed.split(/\s+as\s+/);
|
|
333
|
+
const key = parts[0].trim();
|
|
334
|
+
const alias = parts[1]?.trim() || key;
|
|
335
|
+
return `const ${alias} = ${varName}[${JSON.stringify(key)}];`;
|
|
336
|
+
})
|
|
337
|
+
.join("\n");
|
|
338
|
+
return `${replacement}\n${namedExports}`;
|
|
339
|
+
},
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
// Strip remaining plain CSS imports
|
|
343
|
+
return code.replace(/import\s+['"][^'"]+\.css['"]\s*;?/g, "");
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// ── NPM Import Redirection ──────────────────────────────────────────
|
|
347
|
+
|
|
348
|
+
const EXPLICIT_MAPPINGS: Record<string, string> = {
|
|
349
|
+
react: `${REACT_CDN}?dev`,
|
|
350
|
+
"react/jsx-runtime": `${REACT_CDN}&dev/jsx-runtime`,
|
|
351
|
+
"react/jsx-dev-runtime": `${REACT_CDN}&dev/jsx-dev-runtime`,
|
|
352
|
+
"react-dom": `${REACT_DOM_CDN}?dev`,
|
|
353
|
+
"react-dom/client": `${REACT_DOM_CDN}/client?dev`,
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
const LOCAL_PACKAGES = new Set([
|
|
357
|
+
"next/link",
|
|
358
|
+
"next/router",
|
|
359
|
+
"next/head",
|
|
360
|
+
"next/navigation",
|
|
361
|
+
"next/dynamic",
|
|
362
|
+
"next/image",
|
|
363
|
+
"next/script",
|
|
364
|
+
"next/font/google",
|
|
365
|
+
"next/font/local",
|
|
366
|
+
]);
|
|
367
|
+
|
|
368
|
+
function extractMajorVersion(range: string): string | null {
|
|
369
|
+
const match = range.match(/(\d+)\.\d+/);
|
|
370
|
+
return match ? match[1] : null;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function resolveNpmPackage(
|
|
374
|
+
packageName: string,
|
|
375
|
+
extraLocalPackages?: Set<string>,
|
|
376
|
+
dependencies?: Record<string, string>,
|
|
377
|
+
esmShDeps?: string,
|
|
378
|
+
installedPackages?: Set<string>,
|
|
379
|
+
): string | null {
|
|
380
|
+
if (
|
|
381
|
+
packageName.startsWith(".") ||
|
|
382
|
+
packageName.startsWith("/") ||
|
|
383
|
+
packageName.startsWith("http://") ||
|
|
384
|
+
packageName.startsWith("https://") ||
|
|
385
|
+
packageName.startsWith("/__virtual__")
|
|
386
|
+
) {
|
|
387
|
+
return null;
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
if (EXPLICIT_MAPPINGS[packageName]) return EXPLICIT_MAPPINGS[packageName];
|
|
391
|
+
if (LOCAL_PACKAGES.has(packageName)) return null;
|
|
392
|
+
if (extraLocalPackages?.has(packageName)) return null;
|
|
393
|
+
|
|
394
|
+
const basePkg = packageName.includes("/")
|
|
395
|
+
? packageName.split("/")[0]
|
|
396
|
+
: packageName;
|
|
397
|
+
const isScoped = basePkg.startsWith("@");
|
|
398
|
+
const scopedBasePkg =
|
|
399
|
+
isScoped && packageName.includes("/")
|
|
400
|
+
? packageName.split("/").slice(0, 2).join("/")
|
|
401
|
+
: basePkg;
|
|
402
|
+
|
|
403
|
+
if (LOCAL_PACKAGES.has(scopedBasePkg)) return null;
|
|
404
|
+
if (extraLocalPackages?.has(scopedBasePkg)) return null;
|
|
405
|
+
|
|
406
|
+
if (installedPackages?.has(scopedBasePkg)) return `/_npm/${packageName}`;
|
|
407
|
+
|
|
408
|
+
let esmPkg = packageName;
|
|
409
|
+
if (dependencies) {
|
|
410
|
+
const depVersion = dependencies[scopedBasePkg];
|
|
411
|
+
if (depVersion) {
|
|
412
|
+
const major = extractMajorVersion(depVersion);
|
|
413
|
+
if (major) {
|
|
414
|
+
const subpath = packageName.slice(scopedBasePkg.length);
|
|
415
|
+
esmPkg = `${scopedBasePkg}@${major}${subpath}`;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const depsParam = esmShDeps ? `&deps=${esmShDeps}` : "";
|
|
421
|
+
return `https://esm.sh/${esmPkg}?external=react${depsParam}`;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Redirect bare npm package imports to esm.sh CDN URLs.
|
|
426
|
+
* Uses acorn AST for precision, falls back to regex.
|
|
427
|
+
*/
|
|
428
|
+
export function redirectNpmImports(
|
|
429
|
+
code: string,
|
|
430
|
+
additionalLocalPackages?: string[],
|
|
431
|
+
dependencies?: Record<string, string>,
|
|
432
|
+
esmShDeps?: string,
|
|
433
|
+
installedPackages?: Set<string>,
|
|
434
|
+
): string {
|
|
435
|
+
const extraSet = additionalLocalPackages?.length
|
|
436
|
+
? new Set(additionalLocalPackages)
|
|
437
|
+
: undefined;
|
|
438
|
+
try {
|
|
439
|
+
return redirectNpmImportsAst(
|
|
440
|
+
code,
|
|
441
|
+
extraSet,
|
|
442
|
+
dependencies,
|
|
443
|
+
esmShDeps,
|
|
444
|
+
installedPackages,
|
|
445
|
+
);
|
|
446
|
+
} catch {
|
|
447
|
+
return redirectNpmImportsRegex(
|
|
448
|
+
code,
|
|
449
|
+
extraSet,
|
|
450
|
+
dependencies,
|
|
451
|
+
esmShDeps,
|
|
452
|
+
installedPackages,
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
function redirectNpmImportsAst(
|
|
458
|
+
code: string,
|
|
459
|
+
extraLocalPackages?: Set<string>,
|
|
460
|
+
dependencies?: Record<string, string>,
|
|
461
|
+
esmShDeps?: string,
|
|
462
|
+
installedPackages?: Set<string>,
|
|
463
|
+
): string {
|
|
464
|
+
const ast = jsxParser.parse(code, {
|
|
465
|
+
ecmaVersion: "latest",
|
|
466
|
+
sourceType: "module",
|
|
467
|
+
});
|
|
468
|
+
const replacements: Array<[number, number, string]> = [];
|
|
469
|
+
|
|
470
|
+
function processSource(sourceNode: any) {
|
|
471
|
+
if (!sourceNode || sourceNode.type !== "Literal") return;
|
|
472
|
+
const resolved = resolveNpmPackage(
|
|
473
|
+
sourceNode.value,
|
|
474
|
+
extraLocalPackages,
|
|
475
|
+
dependencies,
|
|
476
|
+
esmShDeps,
|
|
477
|
+
installedPackages,
|
|
478
|
+
);
|
|
479
|
+
if (resolved) {
|
|
480
|
+
replacements.push([
|
|
481
|
+
sourceNode.start,
|
|
482
|
+
sourceNode.end,
|
|
483
|
+
JSON.stringify(resolved),
|
|
484
|
+
]);
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
for (const node of (ast as any).body) {
|
|
489
|
+
if (node.type === "ImportDeclaration") processSource(node.source);
|
|
490
|
+
else if (node.type === "ExportNamedDeclaration" && node.source)
|
|
491
|
+
processSource(node.source);
|
|
492
|
+
else if (node.type === "ExportAllDeclaration") processSource(node.source);
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
if (replacements.length === 0) return code;
|
|
496
|
+
|
|
497
|
+
let result = code;
|
|
498
|
+
replacements.sort((a, b) => b[0] - a[0]);
|
|
499
|
+
for (const [start, end, replacement] of replacements) {
|
|
500
|
+
result = result.slice(0, start) + replacement + result.slice(end);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
return result;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function redirectNpmImportsRegex(
|
|
507
|
+
code: string,
|
|
508
|
+
extraLocalPackages?: Set<string>,
|
|
509
|
+
dependencies?: Record<string, string>,
|
|
510
|
+
esmShDeps?: string,
|
|
511
|
+
installedPackages?: Set<string>,
|
|
512
|
+
): string {
|
|
513
|
+
const importPattern = /(from\s*['"])([^'"./][^'"]*?)(['"])/g;
|
|
514
|
+
return code.replace(importPattern, (match, prefix, packageName, suffix) => {
|
|
515
|
+
const resolved = resolveNpmPackage(
|
|
516
|
+
packageName,
|
|
517
|
+
extraLocalPackages,
|
|
518
|
+
dependencies,
|
|
519
|
+
esmShDeps,
|
|
520
|
+
installedPackages,
|
|
521
|
+
);
|
|
522
|
+
if (!resolved) return match;
|
|
523
|
+
return `${prefix}${resolved}${suffix}`;
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
// ── React Refresh Registration ───────────────────────────────────────
|
|
528
|
+
|
|
529
|
+
/**
|
|
530
|
+
* Add React Refresh registration to transformed code.
|
|
531
|
+
* Enables true HMR (state-preserving) for React components.
|
|
532
|
+
*/
|
|
533
|
+
export function addReactRefresh(code: string, filename: string): string {
|
|
534
|
+
const components = detectReactComponents(code);
|
|
535
|
+
|
|
536
|
+
if (components.length === 0) {
|
|
537
|
+
return `// HMR Setup
|
|
538
|
+
import.meta.hot = window.__vite_hot_context__("${filename}");
|
|
539
|
+
|
|
540
|
+
${code}
|
|
541
|
+
|
|
542
|
+
// HMR Accept
|
|
543
|
+
if (import.meta.hot) {
|
|
544
|
+
import.meta.hot.accept();
|
|
545
|
+
}
|
|
546
|
+
`;
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
const registrations = components
|
|
550
|
+
.map(name => ` $RefreshReg$(${name}, "${filename} ${name}");`)
|
|
551
|
+
.join("\n");
|
|
552
|
+
|
|
553
|
+
return `// HMR Setup
|
|
554
|
+
import.meta.hot = window.__vite_hot_context__("${filename}");
|
|
555
|
+
|
|
556
|
+
${code}
|
|
557
|
+
|
|
558
|
+
// React Refresh Registration
|
|
559
|
+
if (import.meta.hot) {
|
|
560
|
+
${registrations}
|
|
561
|
+
import.meta.hot.accept(() => {
|
|
562
|
+
if (window.$RefreshRuntime$) {
|
|
563
|
+
window.$RefreshRuntime$.performReactRefresh();
|
|
564
|
+
}
|
|
565
|
+
});
|
|
566
|
+
}
|
|
567
|
+
`;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
function isUppercaseStart(name: string): boolean {
|
|
571
|
+
return name.length > 0 && name[0] >= "A" && name[0] <= "Z";
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
function detectReactComponents(code: string): string[] {
|
|
575
|
+
try {
|
|
576
|
+
return detectReactComponentsAst(code);
|
|
577
|
+
} catch {
|
|
578
|
+
return detectReactComponentsRegex(code);
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
function detectReactComponentsAst(code: string): string[] {
|
|
583
|
+
const ast = jsxParser.parse(code, {
|
|
584
|
+
ecmaVersion: "latest",
|
|
585
|
+
sourceType: "module",
|
|
586
|
+
});
|
|
587
|
+
const components: string[] = [];
|
|
588
|
+
|
|
589
|
+
for (const node of (ast as any).body) {
|
|
590
|
+
if (
|
|
591
|
+
node.type === "FunctionDeclaration" &&
|
|
592
|
+
node.id &&
|
|
593
|
+
isUppercaseStart(node.id.name)
|
|
594
|
+
) {
|
|
595
|
+
if (!components.includes(node.id.name)) components.push(node.id.name);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
if (
|
|
599
|
+
node.type === "ExportDefaultDeclaration" &&
|
|
600
|
+
node.declaration?.type === "FunctionDeclaration" &&
|
|
601
|
+
node.declaration.id &&
|
|
602
|
+
isUppercaseStart(node.declaration.id.name)
|
|
603
|
+
) {
|
|
604
|
+
if (!components.includes(node.declaration.id.name))
|
|
605
|
+
components.push(node.declaration.id.name);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
if (
|
|
609
|
+
node.type === "ExportNamedDeclaration" &&
|
|
610
|
+
node.declaration?.type === "FunctionDeclaration" &&
|
|
611
|
+
node.declaration.id &&
|
|
612
|
+
isUppercaseStart(node.declaration.id.name)
|
|
613
|
+
) {
|
|
614
|
+
if (!components.includes(node.declaration.id.name))
|
|
615
|
+
components.push(node.declaration.id.name);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
const varDecl =
|
|
619
|
+
node.type === "VariableDeclaration"
|
|
620
|
+
? node
|
|
621
|
+
: node.type === "ExportNamedDeclaration" &&
|
|
622
|
+
node.declaration?.type === "VariableDeclaration"
|
|
623
|
+
? node.declaration
|
|
624
|
+
: null;
|
|
625
|
+
|
|
626
|
+
if (varDecl) {
|
|
627
|
+
for (const declarator of varDecl.declarations) {
|
|
628
|
+
if (
|
|
629
|
+
declarator.id?.name &&
|
|
630
|
+
isUppercaseStart(declarator.id.name) &&
|
|
631
|
+
declarator.init
|
|
632
|
+
) {
|
|
633
|
+
const initType = declarator.init.type;
|
|
634
|
+
if (
|
|
635
|
+
initType === "ArrowFunctionExpression" ||
|
|
636
|
+
initType === "FunctionExpression" ||
|
|
637
|
+
initType === "CallExpression"
|
|
638
|
+
) {
|
|
639
|
+
if (!components.includes(declarator.id.name))
|
|
640
|
+
components.push(declarator.id.name);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
return components;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
function detectReactComponentsRegex(code: string): string[] {
|
|
651
|
+
const components: string[] = [];
|
|
652
|
+
|
|
653
|
+
const funcDeclRegex =
|
|
654
|
+
/(?:^|\n)(?:export\s+)?(?:async\s+)?function\s+([A-Z][a-zA-Z0-9]*)\s*\(/g;
|
|
655
|
+
let match;
|
|
656
|
+
while ((match = funcDeclRegex.exec(code)) !== null) {
|
|
657
|
+
if (!components.includes(match[1])) components.push(match[1]);
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
const arrowRegex =
|
|
661
|
+
/(?:^|\n)(?:export\s+)?(?:const|let|var)\s+([A-Z][a-zA-Z0-9]*)\s*=/g;
|
|
662
|
+
while ((match = arrowRegex.exec(code)) !== null) {
|
|
663
|
+
if (!components.includes(match[1])) components.push(match[1]);
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
return components;
|
|
667
|
+
}
|