next-live 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/CHANGELOG.md +33 -0
- package/LICENSE +21 -0
- package/README.md +142 -0
- package/dist/editor.cjs +247 -0
- package/dist/editor.cjs.map +1 -0
- package/dist/editor.d.cts +80 -0
- package/dist/editor.d.ts +80 -0
- package/dist/editor.js +225 -0
- package/dist/editor.js.map +1 -0
- package/dist/index.cjs +1265 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +580 -0
- package/dist/index.d.ts +580 -0
- package/dist/index.js +1192 -0
- package/dist/index.js.map +1 -0
- package/dist/server.cjs +287 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.d.cts +142 -0
- package/dist/server.d.ts +142 -0
- package/dist/server.js +282 -0
- package/dist/server.js.map +1 -0
- package/dist/shared.js +30 -0
- package/dist/shared.js.map +1 -0
- package/docs/01-getting-started.md +244 -0
- package/docs/02-module-registry.md +487 -0
- package/docs/03-sharing-your-app-libraries.md +206 -0
- package/docs/04-scaling.md +234 -0
- package/docs/05-security.md +204 -0
- package/docs/06-api-reference.md +337 -0
- package/docs/07-troubleshooting.md +289 -0
- package/docs/08-integration-guide.md +340 -0
- package/docs/09-non-ui-snippets.md +124 -0
- package/docs/10-validating-in-ci.md +192 -0
- package/docs/README.md +76 -0
- package/package.json +105 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1265 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
var React = require('react');
|
|
5
|
+
var JsxRuntime = require('react/jsx-runtime');
|
|
6
|
+
var JsxDevRuntime = require('react/jsx-dev-runtime');
|
|
7
|
+
|
|
8
|
+
function _interopNamespace(e) {
|
|
9
|
+
if (e && e.__esModule) return e;
|
|
10
|
+
var n = Object.create(null);
|
|
11
|
+
if (e) {
|
|
12
|
+
Object.keys(e).forEach(function (k) {
|
|
13
|
+
if (k !== 'default') {
|
|
14
|
+
var d = Object.getOwnPropertyDescriptor(e, k);
|
|
15
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
16
|
+
enumerable: true,
|
|
17
|
+
get: function () { return e[k]; }
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
n.default = e;
|
|
23
|
+
return Object.freeze(n);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
var React__namespace = /*#__PURE__*/_interopNamespace(React);
|
|
27
|
+
var JsxRuntime__namespace = /*#__PURE__*/_interopNamespace(JsxRuntime);
|
|
28
|
+
var JsxDevRuntime__namespace = /*#__PURE__*/_interopNamespace(JsxDevRuntime);
|
|
29
|
+
|
|
30
|
+
var CONTEXT_KEY = /* @__PURE__ */ Symbol.for("next-live.LiveContext");
|
|
31
|
+
function createLiveContext() {
|
|
32
|
+
const store = globalThis;
|
|
33
|
+
const existing = store[CONTEXT_KEY];
|
|
34
|
+
if (existing) return existing;
|
|
35
|
+
const context = React.createContext(null);
|
|
36
|
+
context.displayName = "LiveContext";
|
|
37
|
+
store[CONTEXT_KEY] = context;
|
|
38
|
+
return context;
|
|
39
|
+
}
|
|
40
|
+
var LiveContext = createLiveContext();
|
|
41
|
+
|
|
42
|
+
// src/core/builtin-specifiers.ts
|
|
43
|
+
var BUILTIN_SPECIFIERS = [
|
|
44
|
+
"react",
|
|
45
|
+
"react/jsx-runtime",
|
|
46
|
+
"react/jsx-dev-runtime"
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
// src/core/builtins.ts
|
|
50
|
+
var builtinModules = {
|
|
51
|
+
[BUILTIN_SPECIFIERS[0]]: React__namespace,
|
|
52
|
+
[BUILTIN_SPECIFIERS[1]]: JsxRuntime__namespace,
|
|
53
|
+
[BUILTIN_SPECIFIERS[2]]: JsxDevRuntime__namespace
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// src/core/errors.ts
|
|
57
|
+
var LiveError = class extends Error {
|
|
58
|
+
constructor(message, options) {
|
|
59
|
+
super(message);
|
|
60
|
+
this.name = new.target.name;
|
|
61
|
+
if (options?.cause !== void 0) this.cause = options.cause;
|
|
62
|
+
Object.setPrototypeOf(this, new.target.prototype);
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
var LiveCompileError = class extends LiveError {
|
|
66
|
+
line;
|
|
67
|
+
column;
|
|
68
|
+
constructor(message, position, cause) {
|
|
69
|
+
super(message, { cause });
|
|
70
|
+
this.line = position?.line;
|
|
71
|
+
this.column = position?.column;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
var LiveRuntimeError = class extends LiveError {
|
|
75
|
+
};
|
|
76
|
+
var RenderLoopError = class extends LiveRuntimeError {
|
|
77
|
+
};
|
|
78
|
+
var ModuleNotFoundError = class extends LiveError {
|
|
79
|
+
constructor(specifier, available) {
|
|
80
|
+
super(buildModuleNotFoundMessage(specifier, available));
|
|
81
|
+
this.specifier = specifier;
|
|
82
|
+
this.available = available;
|
|
83
|
+
}
|
|
84
|
+
specifier;
|
|
85
|
+
available;
|
|
86
|
+
};
|
|
87
|
+
var NoComponentError = class extends LiveError {
|
|
88
|
+
};
|
|
89
|
+
var TranspilerLoadError = class extends LiveError {
|
|
90
|
+
constructor(cause) {
|
|
91
|
+
super(
|
|
92
|
+
"next-live could not load its transpiler (sucrase). This is usually a network or code-splitting failure - check that the chunk is reachable.",
|
|
93
|
+
{ cause }
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
var MAX_LISTED = 12;
|
|
98
|
+
function buildModuleNotFoundMessage(specifier, available) {
|
|
99
|
+
const lines = [`Module '${specifier}' is not registered in the next-live scope.`];
|
|
100
|
+
const suggestion = nearestSpecifier(specifier, available);
|
|
101
|
+
if (suggestion) lines.push("", `Did you mean '${suggestion}'?`);
|
|
102
|
+
if (available.length > 0) {
|
|
103
|
+
const sorted = [...available].sort();
|
|
104
|
+
const shown = sorted.slice(0, MAX_LISTED);
|
|
105
|
+
const rest = sorted.length - shown.length;
|
|
106
|
+
lines.push(
|
|
107
|
+
"",
|
|
108
|
+
`Registered modules (${sorted.length}): ${shown.join(", ")}` + (rest > 0 ? `, \u2026and ${rest} more` : "")
|
|
109
|
+
);
|
|
110
|
+
} else {
|
|
111
|
+
lines.push("", "No modules are registered.");
|
|
112
|
+
}
|
|
113
|
+
if (!specifier.startsWith(".") && !specifier.startsWith("/")) {
|
|
114
|
+
lines.push(
|
|
115
|
+
"",
|
|
116
|
+
"next-live does not bundle npm packages - pass them in explicitly:",
|
|
117
|
+
` <LiveProvider modules={{ '${specifier}': theModule }} />`
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
return lines.join("\n");
|
|
121
|
+
}
|
|
122
|
+
function nearestSpecifier(specifier, available) {
|
|
123
|
+
const lower = specifier.toLowerCase();
|
|
124
|
+
const caseMatch = available.find((key) => key.toLowerCase() === lower);
|
|
125
|
+
if (caseMatch) return caseMatch;
|
|
126
|
+
const threshold = Math.max(1, Math.min(3, Math.floor(specifier.length / 3)));
|
|
127
|
+
let best;
|
|
128
|
+
let bestDistance = threshold + 1;
|
|
129
|
+
for (const key of available) {
|
|
130
|
+
if (Math.abs(key.length - specifier.length) > threshold) continue;
|
|
131
|
+
const distance = editDistance(lower, key.toLowerCase(), bestDistance);
|
|
132
|
+
if (distance < bestDistance) {
|
|
133
|
+
bestDistance = distance;
|
|
134
|
+
best = key;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return bestDistance <= threshold ? best : void 0;
|
|
138
|
+
}
|
|
139
|
+
function editDistance(a, b, limit) {
|
|
140
|
+
if (a === b) return 0;
|
|
141
|
+
let previous = new Array(b.length + 1);
|
|
142
|
+
let current = new Array(b.length + 1);
|
|
143
|
+
for (let j = 0; j <= b.length; j++) previous[j] = j;
|
|
144
|
+
for (let i = 1; i <= a.length; i++) {
|
|
145
|
+
current[0] = i;
|
|
146
|
+
let rowMin = i;
|
|
147
|
+
for (let j = 1; j <= b.length; j++) {
|
|
148
|
+
const cost = a.charCodeAt(i - 1) === b.charCodeAt(j - 1) ? 0 : 1;
|
|
149
|
+
const value = Math.min(
|
|
150
|
+
current[j - 1] + 1,
|
|
151
|
+
previous[j] + 1,
|
|
152
|
+
previous[j - 1] + cost
|
|
153
|
+
);
|
|
154
|
+
current[j] = value;
|
|
155
|
+
if (value < rowMin) rowMin = value;
|
|
156
|
+
}
|
|
157
|
+
if (rowMin > limit) return limit + 1;
|
|
158
|
+
const swap = previous;
|
|
159
|
+
previous = current;
|
|
160
|
+
current = swap;
|
|
161
|
+
}
|
|
162
|
+
return previous[b.length];
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// src/core/transpile.ts
|
|
166
|
+
var transpilerPromise = null;
|
|
167
|
+
function loadTranspiler() {
|
|
168
|
+
if (transpilerPromise === null) {
|
|
169
|
+
transpilerPromise = import('sucrase').catch((cause) => {
|
|
170
|
+
transpilerPromise = null;
|
|
171
|
+
throw new TranspilerLoadError(cause);
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
return transpilerPromise;
|
|
175
|
+
}
|
|
176
|
+
function preloadTranspiler() {
|
|
177
|
+
void loadTranspiler().catch(() => {
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
function setTranspiler(module) {
|
|
181
|
+
transpilerPromise = module === null ? null : Promise.resolve(module);
|
|
182
|
+
}
|
|
183
|
+
var defaultTranspileOptions = {
|
|
184
|
+
filePath: "LiveCode.tsx",
|
|
185
|
+
production: true,
|
|
186
|
+
jsxRuntime: "automatic",
|
|
187
|
+
jsxImportSource: "react"
|
|
188
|
+
};
|
|
189
|
+
var MODULE_SYNTAX_RE = /^[ \t]*(?:export\b|import\s*[({'"*]|import\s+[A-Za-z_$])/m;
|
|
190
|
+
var RENDER_CALL_RE = /(^|[^.\w$])render\s*\(/m;
|
|
191
|
+
function isModuleSource(source) {
|
|
192
|
+
return MODULE_SYNTAX_RE.test(source) || RENDER_CALL_RE.test(source);
|
|
193
|
+
}
|
|
194
|
+
function isBlankSource(source) {
|
|
195
|
+
const withoutComments = source.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/^[ \t]*\/\/.*$/gm, " ");
|
|
196
|
+
return withoutComments.trim() === "";
|
|
197
|
+
}
|
|
198
|
+
function sucraseOptions(options) {
|
|
199
|
+
return {
|
|
200
|
+
transforms: ["jsx", "typescript", "imports"],
|
|
201
|
+
jsxRuntime: options.jsxRuntime,
|
|
202
|
+
jsxImportSource: options.jsxImportSource,
|
|
203
|
+
production: options.production,
|
|
204
|
+
filePath: options.filePath,
|
|
205
|
+
// Leaving native `import()` intact would resolve against the *page* URL
|
|
206
|
+
// and 404 on './utils'; routing it through our shim is the only sane
|
|
207
|
+
// behaviour inside an evaluated snippet.
|
|
208
|
+
preserveDynamicImport: false
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
function runTranspile(transformFn, source, options) {
|
|
212
|
+
const opts = sucraseOptions(options);
|
|
213
|
+
if (!isModuleSource(source) && !isBlankSource(source)) {
|
|
214
|
+
try {
|
|
215
|
+
return {
|
|
216
|
+
code: transformFn(`export default (
|
|
217
|
+
${source}
|
|
218
|
+
)`, opts).code,
|
|
219
|
+
linePrefixOffset: 1,
|
|
220
|
+
expression: true
|
|
221
|
+
};
|
|
222
|
+
} catch {
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return { code: transformFn(source, opts).code, linePrefixOffset: 0, expression: false };
|
|
226
|
+
}
|
|
227
|
+
async function transpile(source, options = {}, transform) {
|
|
228
|
+
const resolved = { ...defaultTranspileOptions, ...options };
|
|
229
|
+
if (transform) return transform(source, resolved);
|
|
230
|
+
const { transform: sucraseTransform } = await loadTranspiler();
|
|
231
|
+
try {
|
|
232
|
+
return runTranspile(sucraseTransform, source, resolved);
|
|
233
|
+
} catch (cause) {
|
|
234
|
+
throw toCompileError(cause);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
function toCompileError(cause) {
|
|
238
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
239
|
+
const match = /\((\d+):(\d+)\)\s*$/.exec(message);
|
|
240
|
+
if (!match) return new LiveCompileError(message, void 0, cause);
|
|
241
|
+
return new LiveCompileError(
|
|
242
|
+
message.slice(0, match.index).trim(),
|
|
243
|
+
{ line: Number(match[1]), column: Number(match[2]) },
|
|
244
|
+
cause
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
function scanTopLevelDeclarations(code) {
|
|
248
|
+
const names = [];
|
|
249
|
+
const patterns = [
|
|
250
|
+
/(?:^|;)\s*(?:async\s+)?function\s*\*?\s*([A-Za-z_$][\w$]*)/gm,
|
|
251
|
+
/(?:^|;)\s*class\s+([A-Za-z_$][\w$]*)/gm,
|
|
252
|
+
/(?:^|;)\s*(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=/gm
|
|
253
|
+
];
|
|
254
|
+
for (const pattern of patterns) {
|
|
255
|
+
let match;
|
|
256
|
+
while ((match = pattern.exec(code)) !== null) {
|
|
257
|
+
const name = match[1];
|
|
258
|
+
if (name !== void 0 && !names.includes(name)) names.push(name);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
return names;
|
|
262
|
+
}
|
|
263
|
+
function precompiledTransform(result) {
|
|
264
|
+
return () => result;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// src/core/evaluate.ts
|
|
268
|
+
var RESERVED = /* @__PURE__ */ new Set(["module", "exports", "require", "React", "render", "__liveTick"]);
|
|
269
|
+
var JS_RESERVED_WORDS = /* @__PURE__ */ new Set([
|
|
270
|
+
"break",
|
|
271
|
+
"case",
|
|
272
|
+
"catch",
|
|
273
|
+
"class",
|
|
274
|
+
"const",
|
|
275
|
+
"continue",
|
|
276
|
+
"debugger",
|
|
277
|
+
"default",
|
|
278
|
+
"delete",
|
|
279
|
+
"do",
|
|
280
|
+
"else",
|
|
281
|
+
"enum",
|
|
282
|
+
"export",
|
|
283
|
+
"extends",
|
|
284
|
+
"false",
|
|
285
|
+
"finally",
|
|
286
|
+
"for",
|
|
287
|
+
"function",
|
|
288
|
+
"if",
|
|
289
|
+
"import",
|
|
290
|
+
"in",
|
|
291
|
+
"instanceof",
|
|
292
|
+
"new",
|
|
293
|
+
"null",
|
|
294
|
+
"return",
|
|
295
|
+
"super",
|
|
296
|
+
"switch",
|
|
297
|
+
"this",
|
|
298
|
+
"throw",
|
|
299
|
+
"true",
|
|
300
|
+
"try",
|
|
301
|
+
"typeof",
|
|
302
|
+
"var",
|
|
303
|
+
"void",
|
|
304
|
+
"while",
|
|
305
|
+
"with",
|
|
306
|
+
"yield",
|
|
307
|
+
"let",
|
|
308
|
+
"static",
|
|
309
|
+
"implements",
|
|
310
|
+
"interface",
|
|
311
|
+
"package",
|
|
312
|
+
"private",
|
|
313
|
+
"protected",
|
|
314
|
+
"public",
|
|
315
|
+
"arguments",
|
|
316
|
+
"eval"
|
|
317
|
+
]);
|
|
318
|
+
var IDENTIFIER_RE = /^[A-Za-z_$][\w$]*$/;
|
|
319
|
+
function usableScopeKeys(scope) {
|
|
320
|
+
const keys = [];
|
|
321
|
+
for (const key of Object.keys(scope)) {
|
|
322
|
+
if (!IDENTIFIER_RE.test(key) || JS_RESERVED_WORDS.has(key)) {
|
|
323
|
+
warn(`next-live: scope key ${JSON.stringify(key)} is not a valid identifier and was skipped.`);
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
if (RESERVED.has(key)) {
|
|
327
|
+
warn(`next-live: scope key "${key}" is reserved by next-live and was skipped.`);
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
keys.push(key);
|
|
331
|
+
}
|
|
332
|
+
return keys.sort();
|
|
333
|
+
}
|
|
334
|
+
function warn(message) {
|
|
335
|
+
if (typeof console !== "undefined") console.warn(message);
|
|
336
|
+
}
|
|
337
|
+
function evaluate(options) {
|
|
338
|
+
const { exports, rendered, recovered } = runModule(options);
|
|
339
|
+
return pickRenderable(exports, rendered, recovered);
|
|
340
|
+
}
|
|
341
|
+
function runModule(options) {
|
|
342
|
+
const { code, filePath, require: requireFn, scope, liveTick } = options;
|
|
343
|
+
const tick = liveTick ?? (code.includes("__liveTick") ? () => {
|
|
344
|
+
} : void 0);
|
|
345
|
+
const scopeKeys = usableScopeKeys(scope);
|
|
346
|
+
const epilogue = buildEpilogue(code);
|
|
347
|
+
const body = `${code}
|
|
348
|
+
${epilogue}
|
|
349
|
+
//# sourceURL=next-live:///${filePath}`;
|
|
350
|
+
const moduleObject = {
|
|
351
|
+
exports: /* @__PURE__ */ Object.create(null)
|
|
352
|
+
};
|
|
353
|
+
let rendered;
|
|
354
|
+
let didRender = false;
|
|
355
|
+
const render = (node) => {
|
|
356
|
+
rendered = node;
|
|
357
|
+
didRender = true;
|
|
358
|
+
};
|
|
359
|
+
const paramNames = ["module", "exports", "require", "React", "render"];
|
|
360
|
+
const paramValues = [
|
|
361
|
+
moduleObject,
|
|
362
|
+
moduleObject.exports,
|
|
363
|
+
requireFn,
|
|
364
|
+
React__namespace,
|
|
365
|
+
render
|
|
366
|
+
];
|
|
367
|
+
if (tick) {
|
|
368
|
+
paramNames.push("__liveTick");
|
|
369
|
+
paramValues.push(tick);
|
|
370
|
+
}
|
|
371
|
+
paramNames.push(...scopeKeys);
|
|
372
|
+
paramValues.push(...scopeKeys.map((key) => scope[key]));
|
|
373
|
+
let factory;
|
|
374
|
+
try {
|
|
375
|
+
factory = new Function(...paramNames, body);
|
|
376
|
+
} catch (cause) {
|
|
377
|
+
if (isCspEvalBlock(cause)) throw cspError(cause);
|
|
378
|
+
throw new LiveRuntimeError(
|
|
379
|
+
cause instanceof Error ? cause.message : String(cause),
|
|
380
|
+
{ cause }
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
factory(...paramValues);
|
|
384
|
+
return {
|
|
385
|
+
exports: moduleObject.exports,
|
|
386
|
+
...didRender ? { rendered: { rendered } } : {},
|
|
387
|
+
...moduleObject.__nextLiveRecovered === true ? { recovered: true } : {}
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
function buildEpilogue(code) {
|
|
391
|
+
if (/\bexports\./.test(code)) return "";
|
|
392
|
+
const declarations = scanTopLevelDeclarations(code);
|
|
393
|
+
if (declarations.length === 0) return "";
|
|
394
|
+
const ranked = rankCandidates(declarations);
|
|
395
|
+
return ranked.map(
|
|
396
|
+
(name) => `;if(!('default' in exports)){try{if(typeof ${name}!=='undefined'){exports.default=${name};module.__nextLiveRecovered=true}}catch(e){}}`
|
|
397
|
+
).join("");
|
|
398
|
+
}
|
|
399
|
+
var PREFERRED_NAMES = ["App", "Component", "Main", "Demo", "Example", "Page"];
|
|
400
|
+
function rankCandidates(names) {
|
|
401
|
+
const preferred = names.filter((name) => PREFERRED_NAMES.includes(name));
|
|
402
|
+
const pascal = names.filter(
|
|
403
|
+
(name) => !preferred.includes(name) && /^[A-Z]/.test(name)
|
|
404
|
+
);
|
|
405
|
+
const rest = names.filter(
|
|
406
|
+
(name) => !preferred.includes(name) && !pascal.includes(name)
|
|
407
|
+
);
|
|
408
|
+
return [...preferred, ...pascal.reverse(), ...rest.reverse()];
|
|
409
|
+
}
|
|
410
|
+
function pickRenderable(exports, renderCall, recovered) {
|
|
411
|
+
if (renderCall) {
|
|
412
|
+
const found = asRenderable(renderCall.rendered);
|
|
413
|
+
if (found) return { renderable: found, via: "render()" };
|
|
414
|
+
throw new NoComponentError(
|
|
415
|
+
"render() was called with something React cannot render."
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
const asCommonJs = asRenderable(exports);
|
|
419
|
+
if (asCommonJs) return { renderable: asCommonJs, via: "module.exports" };
|
|
420
|
+
const defaultExport = exports["default"];
|
|
421
|
+
if (defaultExport !== void 0) {
|
|
422
|
+
const found = asRenderable(defaultExport);
|
|
423
|
+
if (found) return { renderable: found, via: recovered ? "declaration" : "export default" };
|
|
424
|
+
throw new NoComponentError(
|
|
425
|
+
`The default export is ${describe(defaultExport)}, which React cannot render. Export a component or an element instead.`
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
const named = Object.keys(exports).filter((key) => key !== "__esModule");
|
|
429
|
+
const renderableNames = named.filter((key) => asRenderable(exports[key]) !== null);
|
|
430
|
+
if (renderableNames.length === 1) {
|
|
431
|
+
const only = renderableNames[0];
|
|
432
|
+
return { renderable: asRenderable(exports[only]), via: "named export" };
|
|
433
|
+
}
|
|
434
|
+
if (renderableNames.length > 1) {
|
|
435
|
+
const preferred = PREFERRED_NAMES.find((name) => renderableNames.includes(name));
|
|
436
|
+
if (preferred) {
|
|
437
|
+
return {
|
|
438
|
+
renderable: asRenderable(exports[preferred]),
|
|
439
|
+
via: "named export"
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
throw new NoComponentError(
|
|
443
|
+
`Several components were exported (${renderableNames.join(", ")}) and none is the default. Add \`export default\` to the one you want rendered.`
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
throw new NoComponentError(
|
|
447
|
+
named.length > 0 ? `Nothing renderable was exported. Found: ${named.join(", ")}. Add \`export default YourComponent\`.` : "The snippet did not produce a component. Add `export default YourComponent`, or end the snippet with a single JSX expression."
|
|
448
|
+
);
|
|
449
|
+
}
|
|
450
|
+
function describe(value) {
|
|
451
|
+
if (value === null) return "null";
|
|
452
|
+
if (Array.isArray(value)) return "an array";
|
|
453
|
+
return `a ${typeof value}`;
|
|
454
|
+
}
|
|
455
|
+
function asRenderable(value) {
|
|
456
|
+
if (isReactElement(value)) return { kind: "element", element: value };
|
|
457
|
+
if (isRenderableComponent(value)) {
|
|
458
|
+
return { kind: "component", component: value };
|
|
459
|
+
}
|
|
460
|
+
return null;
|
|
461
|
+
}
|
|
462
|
+
var symbolFor = (name) => typeof Symbol === "function" && Symbol.for ? Symbol.for(name) : name;
|
|
463
|
+
var COMPONENT_TYPES = /* @__PURE__ */ new Set([
|
|
464
|
+
symbolFor("react.memo"),
|
|
465
|
+
symbolFor("react.forward_ref"),
|
|
466
|
+
symbolFor("react.lazy"),
|
|
467
|
+
symbolFor("react.provider"),
|
|
468
|
+
symbolFor("react.context"),
|
|
469
|
+
symbolFor("react.suspense"),
|
|
470
|
+
symbolFor("react.suspense_list"),
|
|
471
|
+
symbolFor("react.fragment"),
|
|
472
|
+
symbolFor("react.profiler"),
|
|
473
|
+
symbolFor("react.client.reference")
|
|
474
|
+
]);
|
|
475
|
+
var ELEMENT_TYPES = /* @__PURE__ */ new Set([
|
|
476
|
+
symbolFor("react.transitional.element"),
|
|
477
|
+
// React 19
|
|
478
|
+
symbolFor("react.element"),
|
|
479
|
+
// React 18 and interop
|
|
480
|
+
symbolFor("react.portal")
|
|
481
|
+
]);
|
|
482
|
+
function isRenderableComponent(value) {
|
|
483
|
+
if (typeof value === "function") return true;
|
|
484
|
+
if (typeof value !== "object" || value === null) return false;
|
|
485
|
+
return COMPONENT_TYPES.has(value.$$typeof);
|
|
486
|
+
}
|
|
487
|
+
function isReactElement(value) {
|
|
488
|
+
if (typeof value !== "object" || value === null) return false;
|
|
489
|
+
return ELEMENT_TYPES.has(value.$$typeof);
|
|
490
|
+
}
|
|
491
|
+
function isCspEvalBlock(cause) {
|
|
492
|
+
if (cause instanceof EvalError) return true;
|
|
493
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
494
|
+
return /unsafe-eval|Refused to evaluate|call to eval/i.test(message);
|
|
495
|
+
}
|
|
496
|
+
function cspError(cause) {
|
|
497
|
+
return new LiveCompileError(
|
|
498
|
+
"next-live could not evaluate this snippet: the page's Content Security Policy blocks eval.\n\nAdd 'unsafe-eval' to script-src for the routes that run snippets (in proxy.ts). This is expected - next-live compiles code at runtime, so there is no way around the directive.\n\nNote that 'unsafe-eval' does not allow loading external scripts, and it does not need to apply to your whole application.",
|
|
499
|
+
void 0,
|
|
500
|
+
cause
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// src/core/inject-render-budget.ts
|
|
505
|
+
function injectRenderBudgetTick(source) {
|
|
506
|
+
let out = source;
|
|
507
|
+
out = out.replace(
|
|
508
|
+
/export\s+default\s+function\s+(\w*)\s*\([^)]*\)\s*\{/g,
|
|
509
|
+
(match) => `${match}
|
|
510
|
+
__liveTick();`
|
|
511
|
+
);
|
|
512
|
+
out = out.replace(
|
|
513
|
+
/export\s+default\s*(?:async\s+)?(?:function\s*)?\([^)]*\)\s*=>\s*\{/g,
|
|
514
|
+
(match) => `${match}
|
|
515
|
+
__liveTick();`
|
|
516
|
+
);
|
|
517
|
+
out = out.replace(
|
|
518
|
+
/^export\s+function\s+([A-Z]\w*)\s*\([^)]*\)\s*\{/gm,
|
|
519
|
+
(match) => `${match}
|
|
520
|
+
__liveTick();`
|
|
521
|
+
);
|
|
522
|
+
return out;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// src/core/resolver.ts
|
|
526
|
+
var NORMALIZED = /* @__PURE__ */ Symbol.for("next-live.normalized");
|
|
527
|
+
var LOADER = /* @__PURE__ */ Symbol.for("next-live.loader");
|
|
528
|
+
var ASSET_RE = /\.(css|scss|sass|less|styl|svg|png|jpe?g|gif|webp|avif|woff2?)$/i;
|
|
529
|
+
function defineLoader(load) {
|
|
530
|
+
return Object.defineProperty(load, LOADER, { value: true });
|
|
531
|
+
}
|
|
532
|
+
function isLoader(value) {
|
|
533
|
+
return typeof value === "function" && value[LOADER] === true;
|
|
534
|
+
}
|
|
535
|
+
function defineModule(shape) {
|
|
536
|
+
const record = /* @__PURE__ */ Object.create(null);
|
|
537
|
+
Object.defineProperty(record, "__esModule", { value: true });
|
|
538
|
+
Object.defineProperty(record, NORMALIZED, { value: true });
|
|
539
|
+
const defaultExport = "default" in shape ? shape.default : shape.exports?.["default"];
|
|
540
|
+
Object.defineProperty(record, "default", { value: defaultExport, enumerable: true });
|
|
541
|
+
for (const [key, value] of Object.entries(shape.exports ?? {})) {
|
|
542
|
+
if (key === "default") continue;
|
|
543
|
+
Object.defineProperty(record, key, { value, enumerable: true });
|
|
544
|
+
}
|
|
545
|
+
return record;
|
|
546
|
+
}
|
|
547
|
+
function normalizeModule(value) {
|
|
548
|
+
if (isNormalized(value)) return value;
|
|
549
|
+
if (value === null || value === void 0) {
|
|
550
|
+
return defineModule({ default: value });
|
|
551
|
+
}
|
|
552
|
+
if (typeof value !== "object" && typeof value !== "function") {
|
|
553
|
+
return defineModule({ default: value });
|
|
554
|
+
}
|
|
555
|
+
const source = value;
|
|
556
|
+
const record = /* @__PURE__ */ Object.create(null);
|
|
557
|
+
Object.defineProperty(record, "__esModule", { value: true });
|
|
558
|
+
Object.defineProperty(record, NORMALIZED, { value: true });
|
|
559
|
+
const defaultExport = "default" in source ? source["default"] : source;
|
|
560
|
+
Object.defineProperty(record, "default", {
|
|
561
|
+
get: () => defaultExport,
|
|
562
|
+
enumerable: true,
|
|
563
|
+
configurable: true
|
|
564
|
+
});
|
|
565
|
+
for (const key of ownEnumerableKeys(source)) {
|
|
566
|
+
if (key === "default" || key === "__esModule") continue;
|
|
567
|
+
Object.defineProperty(record, key, {
|
|
568
|
+
get: () => source[key],
|
|
569
|
+
enumerable: true,
|
|
570
|
+
configurable: true
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
return record;
|
|
574
|
+
}
|
|
575
|
+
function isNormalized(value) {
|
|
576
|
+
return typeof value === "object" && value !== null && value[NORMALIZED] === true;
|
|
577
|
+
}
|
|
578
|
+
function ownEnumerableKeys(source) {
|
|
579
|
+
const keys = /* @__PURE__ */ new Set();
|
|
580
|
+
for (const key of Object.keys(source)) keys.add(key);
|
|
581
|
+
for (const key in source) keys.add(key);
|
|
582
|
+
return [...keys];
|
|
583
|
+
}
|
|
584
|
+
async function resolveModules(options) {
|
|
585
|
+
const { registry, specifiers, resolveSubpaths = false, signal } = options;
|
|
586
|
+
const resolved = /* @__PURE__ */ new Map();
|
|
587
|
+
const pending = [];
|
|
588
|
+
for (const specifier of specifiers) {
|
|
589
|
+
if (resolved.has(specifier)) continue;
|
|
590
|
+
const found = lookup(registry, specifier, resolveSubpaths);
|
|
591
|
+
if (found === MISSING) {
|
|
592
|
+
if (ASSET_RE.test(specifier)) resolved.set(specifier, defineModule({}));
|
|
593
|
+
continue;
|
|
594
|
+
}
|
|
595
|
+
if (isLoader(found)) {
|
|
596
|
+
pending.push(
|
|
597
|
+
Promise.resolve(found(specifier)).then((value) => {
|
|
598
|
+
resolved.set(specifier, normalizeModule(value));
|
|
599
|
+
})
|
|
600
|
+
);
|
|
601
|
+
} else {
|
|
602
|
+
resolved.set(specifier, normalizeModule(found));
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
if (pending.length > 0) await Promise.all(pending);
|
|
606
|
+
signal?.throwIfAborted();
|
|
607
|
+
const keys = Object.keys(registry);
|
|
608
|
+
return {
|
|
609
|
+
get: (specifier) => resolved.get(specifier),
|
|
610
|
+
keys
|
|
611
|
+
};
|
|
612
|
+
}
|
|
613
|
+
var MISSING = /* @__PURE__ */ Symbol("missing");
|
|
614
|
+
function lookup(registry, specifier, resolveSubpaths) {
|
|
615
|
+
if (Object.prototype.hasOwnProperty.call(registry, specifier)) {
|
|
616
|
+
return registry[specifier];
|
|
617
|
+
}
|
|
618
|
+
let bestPrefix;
|
|
619
|
+
for (const key of Object.keys(registry)) {
|
|
620
|
+
if (!key.endsWith("/")) continue;
|
|
621
|
+
if (!specifier.startsWith(key)) continue;
|
|
622
|
+
if (bestPrefix === void 0 || key.length > bestPrefix.length) bestPrefix = key;
|
|
623
|
+
}
|
|
624
|
+
if (bestPrefix !== void 0) {
|
|
625
|
+
return registry[bestPrefix];
|
|
626
|
+
}
|
|
627
|
+
if (resolveSubpaths) {
|
|
628
|
+
const walked = walkSubpath(registry, specifier);
|
|
629
|
+
if (walked !== MISSING) return walked;
|
|
630
|
+
}
|
|
631
|
+
return MISSING;
|
|
632
|
+
}
|
|
633
|
+
function walkSubpath(registry, specifier) {
|
|
634
|
+
let base;
|
|
635
|
+
for (const key of Object.keys(registry)) {
|
|
636
|
+
if (!specifier.startsWith(key + "/")) continue;
|
|
637
|
+
if (base === void 0 || key.length > base.length) base = key;
|
|
638
|
+
}
|
|
639
|
+
if (base === void 0) return MISSING;
|
|
640
|
+
let current = registry[base];
|
|
641
|
+
if (isLoader(current)) return MISSING;
|
|
642
|
+
for (const segment of specifier.slice(base.length + 1).split("/")) {
|
|
643
|
+
if (current === null || current === void 0) return MISSING;
|
|
644
|
+
if (typeof current !== "object" && typeof current !== "function") return MISSING;
|
|
645
|
+
const next = current[segment];
|
|
646
|
+
if (next === void 0) return MISSING;
|
|
647
|
+
current = next;
|
|
648
|
+
}
|
|
649
|
+
return current;
|
|
650
|
+
}
|
|
651
|
+
function createRequire(resolved) {
|
|
652
|
+
return function require2(specifier) {
|
|
653
|
+
const found = resolved.get(specifier);
|
|
654
|
+
if (found === void 0) {
|
|
655
|
+
throw new ModuleNotFoundError(specifier, resolved.keys);
|
|
656
|
+
}
|
|
657
|
+
return found;
|
|
658
|
+
};
|
|
659
|
+
}
|
|
660
|
+
function scanRequires(code) {
|
|
661
|
+
const found = /* @__PURE__ */ new Set();
|
|
662
|
+
const re = /\brequire\s*\(\s*(['"])((?:(?!\1)[^\\]|\\.)*)\1\s*\)/g;
|
|
663
|
+
let match;
|
|
664
|
+
while ((match = re.exec(code)) !== null) {
|
|
665
|
+
const specifier = match[2];
|
|
666
|
+
if (specifier) found.add(specifier);
|
|
667
|
+
}
|
|
668
|
+
return found;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
// src/core/stacks.ts
|
|
672
|
+
var cachedOffset = null;
|
|
673
|
+
function getFunctionLineOffset() {
|
|
674
|
+
if (cachedOffset !== null) return cachedOffset;
|
|
675
|
+
cachedOffset = 2;
|
|
676
|
+
try {
|
|
677
|
+
new Function('throw new Error("next-live-probe")')();
|
|
678
|
+
} catch (error) {
|
|
679
|
+
const line = firstFrameLine(error);
|
|
680
|
+
if (line !== null) cachedOffset = line - 1;
|
|
681
|
+
}
|
|
682
|
+
return cachedOffset;
|
|
683
|
+
}
|
|
684
|
+
var FRAME_PATTERNS = [
|
|
685
|
+
/(?::|@)(\d+):(\d+)\)?\s*$/
|
|
686
|
+
// V8 "at x (file:LINE:COL)" and SpiderMonkey "x@file:LINE:COL"
|
|
687
|
+
];
|
|
688
|
+
function firstFrameLine(error) {
|
|
689
|
+
const stack = error instanceof Error ? error.stack : void 0;
|
|
690
|
+
if (!stack) return null;
|
|
691
|
+
for (const raw of stack.split("\n").slice(1)) {
|
|
692
|
+
for (const pattern of FRAME_PATTERNS) {
|
|
693
|
+
const match = pattern.exec(raw.trim());
|
|
694
|
+
if (match?.[1]) return Number(match[1]);
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
return null;
|
|
698
|
+
}
|
|
699
|
+
function mapPosition(position, meta) {
|
|
700
|
+
if (meta.generatedLineCount !== meta.sourceLineCount + meta.linePrefixOffset * 2) {
|
|
701
|
+
return null;
|
|
702
|
+
}
|
|
703
|
+
const line = position.line - getFunctionLineOffset() - meta.linePrefixOffset;
|
|
704
|
+
if (line < 1 || line > meta.sourceLineCount) return null;
|
|
705
|
+
return line === 1 ? { line } : { line, column: position.column };
|
|
706
|
+
}
|
|
707
|
+
var SOURCE_URL_PREFIX = "next-live:///";
|
|
708
|
+
function filterUserFrames(stack) {
|
|
709
|
+
if (!stack) return void 0;
|
|
710
|
+
const frames = stack.split("\n").filter((line) => line.includes(SOURCE_URL_PREFIX));
|
|
711
|
+
return frames.length > 0 ? frames.join("\n") : void 0;
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// src/core/compile.ts
|
|
715
|
+
async function compile(input) {
|
|
716
|
+
const prepared = await prepare(input);
|
|
717
|
+
try {
|
|
718
|
+
const { renderable, via } = evaluate(prepared.evaluateOptions);
|
|
719
|
+
return { renderable, via, code: prepared.code, imports: prepared.imports };
|
|
720
|
+
} catch (cause) {
|
|
721
|
+
throw enrichRuntimeError(cause, prepared.meta);
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
async function prepare(input) {
|
|
725
|
+
const {
|
|
726
|
+
code: source,
|
|
727
|
+
modules,
|
|
728
|
+
scope = {},
|
|
729
|
+
transform,
|
|
730
|
+
signal,
|
|
731
|
+
onRender,
|
|
732
|
+
resolveSubpaths = false,
|
|
733
|
+
...transpileOptions
|
|
734
|
+
} = input;
|
|
735
|
+
const options = { ...defaultTranspileOptions, ...transpileOptions };
|
|
736
|
+
const budgetedSource = onRender ? injectRenderBudgetTick(source) : source;
|
|
737
|
+
const transformed = await transpile(budgetedSource, options, transform);
|
|
738
|
+
signal?.throwIfAborted();
|
|
739
|
+
const registry = { ...builtinModules, ...modules };
|
|
740
|
+
const imports = [...scanRequires(transformed.code)].sort();
|
|
741
|
+
const resolved = await resolveModules({
|
|
742
|
+
registry,
|
|
743
|
+
specifiers: imports,
|
|
744
|
+
resolveSubpaths,
|
|
745
|
+
...signal ? { signal } : {}
|
|
746
|
+
});
|
|
747
|
+
signal?.throwIfAborted();
|
|
748
|
+
return {
|
|
749
|
+
code: transformed.code,
|
|
750
|
+
imports,
|
|
751
|
+
evaluateOptions: {
|
|
752
|
+
code: transformed.code,
|
|
753
|
+
filePath: options.filePath,
|
|
754
|
+
require: createRequire(resolved),
|
|
755
|
+
scope,
|
|
756
|
+
...onRender ? { liveTick: onRender } : {}
|
|
757
|
+
},
|
|
758
|
+
meta: {
|
|
759
|
+
linePrefixOffset: transformed.linePrefixOffset,
|
|
760
|
+
generatedLineCount: countLines(transformed.code),
|
|
761
|
+
sourceLineCount: countLines(source)
|
|
762
|
+
}
|
|
763
|
+
};
|
|
764
|
+
}
|
|
765
|
+
async function compileModule(input) {
|
|
766
|
+
const prepared = await prepare(input);
|
|
767
|
+
try {
|
|
768
|
+
const { exports } = runModule(prepared.evaluateOptions);
|
|
769
|
+
return { exports, code: prepared.code, imports: prepared.imports };
|
|
770
|
+
} catch (cause) {
|
|
771
|
+
throw enrichRuntimeError(cause, prepared.meta);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
function countLines(text) {
|
|
775
|
+
let count = 1;
|
|
776
|
+
for (let i = 0; i < text.length; i++) {
|
|
777
|
+
if (text.charCodeAt(i) === 10) count++;
|
|
778
|
+
}
|
|
779
|
+
return count;
|
|
780
|
+
}
|
|
781
|
+
function enrichRuntimeError(cause, meta) {
|
|
782
|
+
if (!(cause instanceof Error)) {
|
|
783
|
+
return new LiveRuntimeError(String(cause), { cause });
|
|
784
|
+
}
|
|
785
|
+
if (cause instanceof LiveError) return cause;
|
|
786
|
+
const position = positionFromStack(cause.stack);
|
|
787
|
+
const mapped = position ? mapPosition(position, meta) : null;
|
|
788
|
+
const error = new LiveRuntimeError(cause.message, { cause });
|
|
789
|
+
error.stack = filterUserFrames(cause.stack) ?? cause.stack;
|
|
790
|
+
if (mapped) {
|
|
791
|
+
Object.defineProperty(error, "line", { value: mapped.line, enumerable: true });
|
|
792
|
+
if (mapped.column !== void 0) {
|
|
793
|
+
Object.defineProperty(error, "column", { value: mapped.column, enumerable: true });
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
return error;
|
|
797
|
+
}
|
|
798
|
+
function positionFromStack(stack) {
|
|
799
|
+
if (!stack) return null;
|
|
800
|
+
for (const raw of stack.split("\n")) {
|
|
801
|
+
if (!raw.includes("next-live:///")) continue;
|
|
802
|
+
const match = /:(\d+):(\d+)\)?\s*$/.exec(raw.trim());
|
|
803
|
+
if (match?.[1]) {
|
|
804
|
+
return { line: Number(match[1]), column: match[2] ? Number(match[2]) : void 0 };
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
return null;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
// src/core/guards.ts
|
|
811
|
+
function createRenderBudget(options = {}) {
|
|
812
|
+
const maxRenders = options.maxRenders ?? 1e3;
|
|
813
|
+
const windowMs = options.windowMs ?? 1e3;
|
|
814
|
+
const message = `This component rendered more than ${maxRenders} times in ${windowMs}ms, so next-live stopped it to keep the page responsive.
|
|
815
|
+
|
|
816
|
+
The usual causes are calling a state setter during render, or a useEffect that updates state without a correct dependency array.`;
|
|
817
|
+
let windowStart = 0;
|
|
818
|
+
let count = 0;
|
|
819
|
+
let tripped = false;
|
|
820
|
+
return function tick() {
|
|
821
|
+
if (tripped) throw new RenderLoopError(message);
|
|
822
|
+
const now = Date.now();
|
|
823
|
+
if (now - windowStart > windowMs) {
|
|
824
|
+
windowStart = now;
|
|
825
|
+
count = 0;
|
|
826
|
+
}
|
|
827
|
+
count += 1;
|
|
828
|
+
if (count > maxRenders) {
|
|
829
|
+
tripped = true;
|
|
830
|
+
throw new RenderLoopError(message);
|
|
831
|
+
}
|
|
832
|
+
};
|
|
833
|
+
}
|
|
834
|
+
var KEY_SEP = "\0";
|
|
835
|
+
function compileSuccessInfo(result, compileId, durationMs) {
|
|
836
|
+
if (!result || typeof result !== "object" || !("imports" in result)) return null;
|
|
837
|
+
const typed = result;
|
|
838
|
+
return {
|
|
839
|
+
compileId,
|
|
840
|
+
imports: typed.imports,
|
|
841
|
+
..."via" in typed && typed.via !== void 0 ? { via: typed.via } : {},
|
|
842
|
+
durationMs
|
|
843
|
+
};
|
|
844
|
+
}
|
|
845
|
+
function useCompileTask(options, run) {
|
|
846
|
+
const {
|
|
847
|
+
code: initialCode,
|
|
848
|
+
debounce = 150,
|
|
849
|
+
keepLastGood = true,
|
|
850
|
+
onCodeChange,
|
|
851
|
+
onCompileSuccess,
|
|
852
|
+
modules,
|
|
853
|
+
scope,
|
|
854
|
+
transform,
|
|
855
|
+
...transpileOptions
|
|
856
|
+
} = options;
|
|
857
|
+
const [code, setCode] = React.useState(initialCode);
|
|
858
|
+
const [state, setState] = React.useState({
|
|
859
|
+
result: null,
|
|
860
|
+
error: null,
|
|
861
|
+
compileId: 0
|
|
862
|
+
});
|
|
863
|
+
const [isCompiling, setIsCompiling] = React.useState(false);
|
|
864
|
+
const compileIdRef = React.useRef(0);
|
|
865
|
+
const mountedRef = React.useRef(true);
|
|
866
|
+
React.useEffect(() => {
|
|
867
|
+
mountedRef.current = true;
|
|
868
|
+
return () => {
|
|
869
|
+
mountedRef.current = false;
|
|
870
|
+
};
|
|
871
|
+
}, []);
|
|
872
|
+
React.useEffect(() => {
|
|
873
|
+
setCode(initialCode);
|
|
874
|
+
}, [initialCode]);
|
|
875
|
+
const modulesKey = React.useMemo(
|
|
876
|
+
() => Object.keys(modules ?? {}).sort().join(KEY_SEP),
|
|
877
|
+
[modules]
|
|
878
|
+
);
|
|
879
|
+
const scopeKey = React.useMemo(
|
|
880
|
+
() => Object.keys(scope ?? {}).sort().join(KEY_SEP),
|
|
881
|
+
[scope]
|
|
882
|
+
);
|
|
883
|
+
const latest = React.useRef({
|
|
884
|
+
modules,
|
|
885
|
+
scope,
|
|
886
|
+
transform,
|
|
887
|
+
transpileOptions,
|
|
888
|
+
run,
|
|
889
|
+
onCodeChange,
|
|
890
|
+
onCompileSuccess
|
|
891
|
+
});
|
|
892
|
+
latest.current = {
|
|
893
|
+
modules,
|
|
894
|
+
scope,
|
|
895
|
+
transform,
|
|
896
|
+
transpileOptions,
|
|
897
|
+
run,
|
|
898
|
+
onCodeChange,
|
|
899
|
+
onCompileSuccess
|
|
900
|
+
};
|
|
901
|
+
const { filePath, production, jsxRuntime, jsxImportSource } = transpileOptions;
|
|
902
|
+
const prevFilePath = React.useRef(filePath);
|
|
903
|
+
React.useEffect(() => {
|
|
904
|
+
if (filePath === prevFilePath.current) return;
|
|
905
|
+
prevFilePath.current = filePath;
|
|
906
|
+
compileIdRef.current += 1;
|
|
907
|
+
if (!mountedRef.current) return;
|
|
908
|
+
setState({ result: null, error: null, compileId: compileIdRef.current });
|
|
909
|
+
}, [filePath]);
|
|
910
|
+
React.useEffect(() => {
|
|
911
|
+
const controller = new AbortController();
|
|
912
|
+
let cancelled = false;
|
|
913
|
+
const spinnerTimer = setTimeout(() => {
|
|
914
|
+
if (!cancelled && mountedRef.current) setIsCompiling(true);
|
|
915
|
+
}, 200);
|
|
916
|
+
const timer = setTimeout(() => {
|
|
917
|
+
const current = latest.current;
|
|
918
|
+
const startedAt = performance.now();
|
|
919
|
+
current.run({
|
|
920
|
+
code,
|
|
921
|
+
signal: controller.signal,
|
|
922
|
+
...current.transpileOptions,
|
|
923
|
+
...current.modules ? { modules: current.modules } : {},
|
|
924
|
+
...current.scope ? { scope: current.scope } : {},
|
|
925
|
+
...current.transform ? { transform: current.transform } : {}
|
|
926
|
+
}).then((result) => {
|
|
927
|
+
if (cancelled || !mountedRef.current) return;
|
|
928
|
+
const nextCompileId = compileIdRef.current + 1;
|
|
929
|
+
compileIdRef.current = nextCompileId;
|
|
930
|
+
setState({
|
|
931
|
+
result,
|
|
932
|
+
error: null,
|
|
933
|
+
compileId: nextCompileId
|
|
934
|
+
});
|
|
935
|
+
const info = compileSuccessInfo(result, nextCompileId, performance.now() - startedAt);
|
|
936
|
+
if (info && current.onCompileSuccess) {
|
|
937
|
+
try {
|
|
938
|
+
current.onCompileSuccess(info);
|
|
939
|
+
} catch {
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
}).catch((error) => {
|
|
943
|
+
if (cancelled || !mountedRef.current) return;
|
|
944
|
+
const asError = error instanceof Error ? error : new Error(String(error));
|
|
945
|
+
if (keepLastGood) {
|
|
946
|
+
setState((previous) => ({ ...previous, error: asError }));
|
|
947
|
+
} else {
|
|
948
|
+
const nextCompileId = compileIdRef.current + 1;
|
|
949
|
+
compileIdRef.current = nextCompileId;
|
|
950
|
+
setState({ result: null, error: asError, compileId: nextCompileId });
|
|
951
|
+
}
|
|
952
|
+
}).finally(() => {
|
|
953
|
+
if (cancelled || !mountedRef.current) return;
|
|
954
|
+
clearTimeout(spinnerTimer);
|
|
955
|
+
setIsCompiling(false);
|
|
956
|
+
});
|
|
957
|
+
}, debounce);
|
|
958
|
+
return () => {
|
|
959
|
+
cancelled = true;
|
|
960
|
+
controller.abort();
|
|
961
|
+
clearTimeout(timer);
|
|
962
|
+
clearTimeout(spinnerTimer);
|
|
963
|
+
};
|
|
964
|
+
}, [
|
|
965
|
+
code,
|
|
966
|
+
debounce,
|
|
967
|
+
keepLastGood,
|
|
968
|
+
modulesKey,
|
|
969
|
+
scopeKey,
|
|
970
|
+
filePath,
|
|
971
|
+
production,
|
|
972
|
+
jsxRuntime,
|
|
973
|
+
jsxImportSource,
|
|
974
|
+
transform
|
|
975
|
+
]);
|
|
976
|
+
const setCodeStable = React.useCallback((next) => {
|
|
977
|
+
setCode(next);
|
|
978
|
+
latest.current.onCodeChange?.(next);
|
|
979
|
+
}, []);
|
|
980
|
+
return {
|
|
981
|
+
code,
|
|
982
|
+
setCode: setCodeStable,
|
|
983
|
+
result: state.result,
|
|
984
|
+
error: state.error,
|
|
985
|
+
isCompiling,
|
|
986
|
+
compileId: state.compileId
|
|
987
|
+
};
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
// src/hooks/useLiveRunner.ts
|
|
991
|
+
function useLiveRunner(options) {
|
|
992
|
+
const { maxRendersPerSecond = 1e3, ...taskOptions } = options;
|
|
993
|
+
const run = React.useCallback(
|
|
994
|
+
(input) => (
|
|
995
|
+
// A fresh budget per compile, so fixing a snippet clears a tripped
|
|
996
|
+
// breaker without the user having to reload the page.
|
|
997
|
+
compile({ ...input, onRender: createRenderBudget({ maxRenders: maxRendersPerSecond }) })
|
|
998
|
+
),
|
|
999
|
+
[maxRendersPerSecond]
|
|
1000
|
+
);
|
|
1001
|
+
const task = useCompileTask(taskOptions, run);
|
|
1002
|
+
const renderable = task.result?.renderable ?? null;
|
|
1003
|
+
return {
|
|
1004
|
+
code: task.code,
|
|
1005
|
+
setCode: task.setCode,
|
|
1006
|
+
Component: renderable?.kind === "component" ? renderable.component : null,
|
|
1007
|
+
element: renderable?.kind === "element" ? renderable.element : null,
|
|
1008
|
+
error: task.error,
|
|
1009
|
+
isCompiling: task.isCompiling,
|
|
1010
|
+
compileId: task.compileId
|
|
1011
|
+
};
|
|
1012
|
+
}
|
|
1013
|
+
var EMPTY_PROPS = {};
|
|
1014
|
+
function LiveProvider(props) {
|
|
1015
|
+
const {
|
|
1016
|
+
code,
|
|
1017
|
+
props: componentProps,
|
|
1018
|
+
language = "tsx",
|
|
1019
|
+
onError,
|
|
1020
|
+
fallback = null,
|
|
1021
|
+
children,
|
|
1022
|
+
...runnerOptions
|
|
1023
|
+
} = props;
|
|
1024
|
+
const runner = useLiveRunner({ code, ...runnerOptions });
|
|
1025
|
+
const [runtimeError, setRuntimeError] = React.useState(null);
|
|
1026
|
+
const compileIdRef = React.useRef(runner.compileId);
|
|
1027
|
+
compileIdRef.current = runner.compileId;
|
|
1028
|
+
const mountedRef = React.useRef(true);
|
|
1029
|
+
React.useEffect(() => {
|
|
1030
|
+
mountedRef.current = true;
|
|
1031
|
+
return () => {
|
|
1032
|
+
mountedRef.current = false;
|
|
1033
|
+
};
|
|
1034
|
+
}, []);
|
|
1035
|
+
const lastRuntimeReport = React.useRef(null);
|
|
1036
|
+
const reportRuntimeError = React.useCallback(
|
|
1037
|
+
(error) => {
|
|
1038
|
+
if (!mountedRef.current) return;
|
|
1039
|
+
const compileId = compileIdRef.current;
|
|
1040
|
+
const last = lastRuntimeReport.current;
|
|
1041
|
+
if (last !== null && last.compileId === compileId && last.message === error.message) {
|
|
1042
|
+
return;
|
|
1043
|
+
}
|
|
1044
|
+
lastRuntimeReport.current = { compileId, message: error.message };
|
|
1045
|
+
setRuntimeError({ error, compileId });
|
|
1046
|
+
onError?.(error);
|
|
1047
|
+
},
|
|
1048
|
+
[onError]
|
|
1049
|
+
);
|
|
1050
|
+
const activeRuntimeError = runtimeError !== null && runtimeError.compileId === runner.compileId ? runtimeError.error : null;
|
|
1051
|
+
const compileError = runner.error;
|
|
1052
|
+
React.useEffect(() => {
|
|
1053
|
+
if (compileError) onError?.(compileError);
|
|
1054
|
+
}, [compileError, onError]);
|
|
1055
|
+
const forwardedProps = componentProps ?? EMPTY_PROPS;
|
|
1056
|
+
const value = React.useMemo(
|
|
1057
|
+
() => ({
|
|
1058
|
+
...runner,
|
|
1059
|
+
error: runner.error ?? activeRuntimeError,
|
|
1060
|
+
props: forwardedProps,
|
|
1061
|
+
language,
|
|
1062
|
+
fallback,
|
|
1063
|
+
reportRuntimeError
|
|
1064
|
+
}),
|
|
1065
|
+
[runner, activeRuntimeError, forwardedProps, language, fallback, reportRuntimeError]
|
|
1066
|
+
);
|
|
1067
|
+
return /* @__PURE__ */ JsxRuntime.jsx(LiveContext.Provider, { value, children });
|
|
1068
|
+
}
|
|
1069
|
+
var LiveErrorBoundary = class extends React.Component {
|
|
1070
|
+
mounted = true;
|
|
1071
|
+
constructor(props) {
|
|
1072
|
+
super(props);
|
|
1073
|
+
this.state = { error: null, resetKey: props.resetKey, recovering: false };
|
|
1074
|
+
}
|
|
1075
|
+
static getDerivedStateFromError(error) {
|
|
1076
|
+
return { error, recovering: false };
|
|
1077
|
+
}
|
|
1078
|
+
static getDerivedStateFromProps(props, state) {
|
|
1079
|
+
if (props.resetKey !== state.resetKey) {
|
|
1080
|
+
return { resetKey: props.resetKey, recovering: true };
|
|
1081
|
+
}
|
|
1082
|
+
return null;
|
|
1083
|
+
}
|
|
1084
|
+
componentDidCatch(error, info) {
|
|
1085
|
+
if (!this.mounted) return;
|
|
1086
|
+
this.props.onError(error);
|
|
1087
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1088
|
+
console.error("next-live: error in evaluated code\n", error, info.componentStack);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
componentDidUpdate(_prevProps, prevState) {
|
|
1092
|
+
if (prevState.recovering && this.state.recovering && this.state.error !== null) {
|
|
1093
|
+
this.setState({ error: null, recovering: false });
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
componentWillUnmount() {
|
|
1097
|
+
this.mounted = false;
|
|
1098
|
+
}
|
|
1099
|
+
render() {
|
|
1100
|
+
if (this.state.error !== null && !this.state.recovering) {
|
|
1101
|
+
return this.props.fallback ?? null;
|
|
1102
|
+
}
|
|
1103
|
+
return this.props.children;
|
|
1104
|
+
}
|
|
1105
|
+
};
|
|
1106
|
+
function useLiveContext() {
|
|
1107
|
+
const value = React.useContext(LiveContext);
|
|
1108
|
+
if (value === null) {
|
|
1109
|
+
throw new Error(
|
|
1110
|
+
"useLiveContext must be called inside a <LiveProvider>. If you want to run code without the provider, use the useLiveRunner hook directly."
|
|
1111
|
+
);
|
|
1112
|
+
}
|
|
1113
|
+
return value;
|
|
1114
|
+
}
|
|
1115
|
+
function LivePreview(props) {
|
|
1116
|
+
const { as: Wrapper = "div", className, style, props: extraProps, fallback } = props;
|
|
1117
|
+
const live = useLiveContext();
|
|
1118
|
+
const merged = extraProps ? { ...live.props, ...extraProps } : live.props;
|
|
1119
|
+
const placeholder = fallback !== void 0 ? fallback : live.fallback;
|
|
1120
|
+
let content;
|
|
1121
|
+
if (live.Component && isRenderableComponent(live.Component)) {
|
|
1122
|
+
content = React.createElement(live.Component, merged);
|
|
1123
|
+
} else if (live.element && React.isValidElement(live.element)) {
|
|
1124
|
+
content = live.element;
|
|
1125
|
+
} else {
|
|
1126
|
+
content = placeholder;
|
|
1127
|
+
}
|
|
1128
|
+
return /* @__PURE__ */ JsxRuntime.jsx(Wrapper, { className, style, children: /* @__PURE__ */ JsxRuntime.jsx(
|
|
1129
|
+
LiveErrorBoundary,
|
|
1130
|
+
{
|
|
1131
|
+
resetKey: live.compileId,
|
|
1132
|
+
onError: live.reportRuntimeError,
|
|
1133
|
+
fallback: null,
|
|
1134
|
+
children: content
|
|
1135
|
+
}
|
|
1136
|
+
) });
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
// src/core/positions.ts
|
|
1140
|
+
function errorPosition(error) {
|
|
1141
|
+
if (error === null || error === void 0) return null;
|
|
1142
|
+
const positioned = error;
|
|
1143
|
+
if (positioned.line === void 0 || positioned.line < 1) return null;
|
|
1144
|
+
return {
|
|
1145
|
+
line: positioned.line,
|
|
1146
|
+
...positioned.column !== void 0 ? { column: positioned.column } : {}
|
|
1147
|
+
};
|
|
1148
|
+
}
|
|
1149
|
+
function LiveError2(props) {
|
|
1150
|
+
const { as: Wrapper = "pre", className, style, children } = props;
|
|
1151
|
+
const live = useLiveContext();
|
|
1152
|
+
if (!live.error) return null;
|
|
1153
|
+
if (children) return /* @__PURE__ */ JsxRuntime.jsx(JsxRuntime.Fragment, { children: children(live.error) });
|
|
1154
|
+
const position = errorPosition(live.error);
|
|
1155
|
+
const location = position !== null ? `Line ${position.line}${position.column !== void 0 ? `:${position.column}` : ""} - ` : "";
|
|
1156
|
+
return /* @__PURE__ */ JsxRuntime.jsxs(
|
|
1157
|
+
Wrapper,
|
|
1158
|
+
{
|
|
1159
|
+
className,
|
|
1160
|
+
role: "alert",
|
|
1161
|
+
style: {
|
|
1162
|
+
margin: 0,
|
|
1163
|
+
padding: "0.75rem 1rem",
|
|
1164
|
+
whiteSpace: "pre-wrap",
|
|
1165
|
+
wordBreak: "break-word",
|
|
1166
|
+
fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace',
|
|
1167
|
+
fontSize: "0.8125rem",
|
|
1168
|
+
lineHeight: 1.5,
|
|
1169
|
+
color: "#fff",
|
|
1170
|
+
background: "#b3261e",
|
|
1171
|
+
...style
|
|
1172
|
+
},
|
|
1173
|
+
children: [
|
|
1174
|
+
location,
|
|
1175
|
+
live.error.message
|
|
1176
|
+
]
|
|
1177
|
+
}
|
|
1178
|
+
);
|
|
1179
|
+
}
|
|
1180
|
+
function useLiveModule(options) {
|
|
1181
|
+
const run = React.useCallback(
|
|
1182
|
+
(input) => compileModule(input),
|
|
1183
|
+
[]
|
|
1184
|
+
);
|
|
1185
|
+
const task = useCompileTask(options, run);
|
|
1186
|
+
const exports = task.result?.exports ?? null;
|
|
1187
|
+
return {
|
|
1188
|
+
code: task.code,
|
|
1189
|
+
setCode: task.setCode,
|
|
1190
|
+
exports,
|
|
1191
|
+
value: exports?.["default"],
|
|
1192
|
+
error: task.error,
|
|
1193
|
+
isCompiling: task.isCompiling,
|
|
1194
|
+
compileId: task.compileId
|
|
1195
|
+
};
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
// src/core/registry.ts
|
|
1199
|
+
function createRegistry(...groups) {
|
|
1200
|
+
const merged = {};
|
|
1201
|
+
if (process.env.NODE_ENV !== "production") {
|
|
1202
|
+
const seen = /* @__PURE__ */ new Map();
|
|
1203
|
+
groups.forEach((group, index) => {
|
|
1204
|
+
for (const key of Object.keys(group)) {
|
|
1205
|
+
const previous = seen.get(key);
|
|
1206
|
+
if (previous !== void 0) {
|
|
1207
|
+
console.warn(
|
|
1208
|
+
`next-live: module '${key}' is defined in more than one registry group (group ${previous} and group ${index}). The later one wins.`
|
|
1209
|
+
);
|
|
1210
|
+
}
|
|
1211
|
+
seen.set(key, index);
|
|
1212
|
+
}
|
|
1213
|
+
});
|
|
1214
|
+
}
|
|
1215
|
+
for (const group of groups) Object.assign(merged, group);
|
|
1216
|
+
return merged;
|
|
1217
|
+
}
|
|
1218
|
+
function registryFromGlob(glob, toSpecifier) {
|
|
1219
|
+
const registry = {};
|
|
1220
|
+
for (const [path, load] of Object.entries(glob)) {
|
|
1221
|
+
const specifier = toSpecifier(path);
|
|
1222
|
+
if (specifier === null) continue;
|
|
1223
|
+
if (process.env.NODE_ENV !== "production" && specifier in registry) {
|
|
1224
|
+
console.warn(
|
|
1225
|
+
`next-live: two files map to the module specifier '${specifier}'. Check the toSpecifier function for collisions.`
|
|
1226
|
+
);
|
|
1227
|
+
}
|
|
1228
|
+
registry[specifier] = defineLoader(() => load());
|
|
1229
|
+
}
|
|
1230
|
+
return registry;
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
exports.LiveCompileError = LiveCompileError;
|
|
1234
|
+
exports.LiveContext = LiveContext;
|
|
1235
|
+
exports.LiveError = LiveError2;
|
|
1236
|
+
exports.LiveErrorBase = LiveError;
|
|
1237
|
+
exports.LiveErrorBoundary = LiveErrorBoundary;
|
|
1238
|
+
exports.LivePreview = LivePreview;
|
|
1239
|
+
exports.LiveProvider = LiveProvider;
|
|
1240
|
+
exports.LiveRuntimeError = LiveRuntimeError;
|
|
1241
|
+
exports.ModuleNotFoundError = ModuleNotFoundError;
|
|
1242
|
+
exports.NoComponentError = NoComponentError;
|
|
1243
|
+
exports.RenderLoopError = RenderLoopError;
|
|
1244
|
+
exports.TranspilerLoadError = TranspilerLoadError;
|
|
1245
|
+
exports.builtinModules = builtinModules;
|
|
1246
|
+
exports.compile = compile;
|
|
1247
|
+
exports.compileModule = compileModule;
|
|
1248
|
+
exports.createRegistry = createRegistry;
|
|
1249
|
+
exports.createRenderBudget = createRenderBudget;
|
|
1250
|
+
exports.createRequire = createRequire;
|
|
1251
|
+
exports.defineLoader = defineLoader;
|
|
1252
|
+
exports.defineModule = defineModule;
|
|
1253
|
+
exports.errorPosition = errorPosition;
|
|
1254
|
+
exports.normalizeModule = normalizeModule;
|
|
1255
|
+
exports.precompiledTransform = precompiledTransform;
|
|
1256
|
+
exports.preloadTranspiler = preloadTranspiler;
|
|
1257
|
+
exports.registryFromGlob = registryFromGlob;
|
|
1258
|
+
exports.resolveModules = resolveModules;
|
|
1259
|
+
exports.setTranspiler = setTranspiler;
|
|
1260
|
+
exports.transpile = transpile;
|
|
1261
|
+
exports.useLiveContext = useLiveContext;
|
|
1262
|
+
exports.useLiveModule = useLiveModule;
|
|
1263
|
+
exports.useLiveRunner = useLiveRunner;
|
|
1264
|
+
//# sourceMappingURL=index.cjs.map
|
|
1265
|
+
//# sourceMappingURL=index.cjs.map
|