@aquiles-ai/renderize 2.1.0 → 2.2.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/index.cjs +450 -0
- package/dist/index.d.cts +20 -0
- package/dist/index.d.ts +20 -0
- package/dist/index.js +423 -0
- package/package.json +1 -1
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,450 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name in all)
|
|
8
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var index_exports = {};
|
|
22
|
+
__export(index_exports, {
|
|
23
|
+
Renderize: () => Renderize
|
|
24
|
+
});
|
|
25
|
+
module.exports = __toCommonJS(index_exports);
|
|
26
|
+
|
|
27
|
+
// src/Renderize.tsx
|
|
28
|
+
var import_react = require("react");
|
|
29
|
+
|
|
30
|
+
// src/template.ts
|
|
31
|
+
function buildTemplate(code) {
|
|
32
|
+
return `<!DOCTYPE html>
|
|
33
|
+
<html lang="en">
|
|
34
|
+
<head>
|
|
35
|
+
<meta charset="UTF-8" />
|
|
36
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
37
|
+
|
|
38
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
39
|
+
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
|
|
40
|
+
|
|
41
|
+
<script type="importmap">
|
|
42
|
+
{
|
|
43
|
+
"imports": {
|
|
44
|
+
"react": "https://esm.sh/react@18",
|
|
45
|
+
"react/jsx-runtime": "https://esm.sh/react@18/jsx-runtime",
|
|
46
|
+
"react-dom": "https://esm.sh/react-dom@18",
|
|
47
|
+
"react-dom/client": "https://esm.sh/react-dom@18/client",
|
|
48
|
+
"lucide-react": "https://esm.sh/lucide-react?external=react",
|
|
49
|
+
"clsx": "https://esm.sh/clsx",
|
|
50
|
+
"class-variance-authority": "https://esm.sh/class-variance-authority",
|
|
51
|
+
"tailwind-merge": "https://esm.sh/tailwind-merge",
|
|
52
|
+
"@radix-ui/react-accordion": "https://esm.sh/@radix-ui/react-accordion?external=react,react-dom",
|
|
53
|
+
"@radix-ui/react-alert-dialog": "https://esm.sh/@radix-ui/react-alert-dialog?external=react,react-dom",
|
|
54
|
+
"@radix-ui/react-avatar": "https://esm.sh/@radix-ui/react-avatar?external=react,react-dom",
|
|
55
|
+
"@radix-ui/react-checkbox": "https://esm.sh/@radix-ui/react-checkbox?external=react,react-dom",
|
|
56
|
+
"@radix-ui/react-collapsible": "https://esm.sh/@radix-ui/react-collapsible?external=react,react-dom",
|
|
57
|
+
"@radix-ui/react-context-menu": "https://esm.sh/@radix-ui/react-context-menu?external=react,react-dom",
|
|
58
|
+
"@radix-ui/react-dialog": "https://esm.sh/@radix-ui/react-dialog?external=react,react-dom",
|
|
59
|
+
"@radix-ui/react-dropdown-menu": "https://esm.sh/@radix-ui/react-dropdown-menu?external=react,react-dom",
|
|
60
|
+
"@radix-ui/react-hover-card": "https://esm.sh/@radix-ui/react-hover-card?external=react,react-dom",
|
|
61
|
+
"@radix-ui/react-label": "https://esm.sh/@radix-ui/react-label?external=react,react-dom",
|
|
62
|
+
"@radix-ui/react-menubar": "https://esm.sh/@radix-ui/react-menubar?external=react,react-dom",
|
|
63
|
+
"@radix-ui/react-navigation-menu": "https://esm.sh/@radix-ui/react-navigation-menu?external=react,react-dom",
|
|
64
|
+
"@radix-ui/react-popover": "https://esm.sh/@radix-ui/react-popover?external=react,react-dom",
|
|
65
|
+
"@radix-ui/react-progress": "https://esm.sh/@radix-ui/react-progress?external=react,react-dom",
|
|
66
|
+
"@radix-ui/react-radio-group": "https://esm.sh/@radix-ui/react-radio-group?external=react,react-dom",
|
|
67
|
+
"@radix-ui/react-scroll-area": "https://esm.sh/@radix-ui/react-scroll-area?external=react,react-dom",
|
|
68
|
+
"@radix-ui/react-select": "https://esm.sh/@radix-ui/react-select?external=react,react-dom",
|
|
69
|
+
"@radix-ui/react-separator": "https://esm.sh/@radix-ui/react-separator?external=react,react-dom",
|
|
70
|
+
"@radix-ui/react-slider": "https://esm.sh/@radix-ui/react-slider?external=react,react-dom",
|
|
71
|
+
"@radix-ui/react-slot": "https://esm.sh/@radix-ui/react-slot?external=react,react-dom",
|
|
72
|
+
"@radix-ui/react-switch": "https://esm.sh/@radix-ui/react-switch?external=react,react-dom",
|
|
73
|
+
"@radix-ui/react-tabs": "https://esm.sh/@radix-ui/react-tabs?external=react,react-dom",
|
|
74
|
+
"@radix-ui/react-toast": "https://esm.sh/@radix-ui/react-toast?external=react,react-dom",
|
|
75
|
+
"@radix-ui/react-toggle": "https://esm.sh/@radix-ui/react-toggle?external=react,react-dom",
|
|
76
|
+
"@radix-ui/react-toggle-group": "https://esm.sh/@radix-ui/react-toggle-group?external=react,react-dom",
|
|
77
|
+
"@radix-ui/react-toolbar": "https://esm.sh/@radix-ui/react-toolbar?external=react,react-dom",
|
|
78
|
+
"@radix-ui/react-tooltip": "https://esm.sh/@radix-ui/react-tooltip?external=react,react-dom"
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
</script>
|
|
82
|
+
|
|
83
|
+
<style>
|
|
84
|
+
* { box-sizing: border-box; }
|
|
85
|
+
body { margin: 0; padding: 0; }
|
|
86
|
+
|
|
87
|
+
/* Scrollbar personalizada: fina y semi-transparente */
|
|
88
|
+
::-webkit-scrollbar {
|
|
89
|
+
width: 6px;
|
|
90
|
+
height: 6px;
|
|
91
|
+
}
|
|
92
|
+
::-webkit-scrollbar-track {
|
|
93
|
+
background: transparent;
|
|
94
|
+
}
|
|
95
|
+
::-webkit-scrollbar-thumb {
|
|
96
|
+
background: rgba(255, 255, 255, 0.35);
|
|
97
|
+
border-radius: 999px;
|
|
98
|
+
transition: background 0.2s ease;
|
|
99
|
+
}
|
|
100
|
+
::-webkit-scrollbar-thumb:hover {
|
|
101
|
+
background: rgba(255, 255, 255, 0.55);
|
|
102
|
+
}
|
|
103
|
+
/* Firefox */
|
|
104
|
+
* {
|
|
105
|
+
scrollbar-width: thin;
|
|
106
|
+
scrollbar-color: rgba(255, 255, 255, 0.35) transparent;
|
|
107
|
+
}
|
|
108
|
+
</style>
|
|
109
|
+
</head>
|
|
110
|
+
<body>
|
|
111
|
+
<div id="root"></div>
|
|
112
|
+
|
|
113
|
+
<script>
|
|
114
|
+
// \u2500\u2500 FETCH PROXY \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
115
|
+
// Override window.fetch to proxy requests through the parent window.
|
|
116
|
+
// This solves the CORS/null-origin issue with srcdoc iframes:
|
|
117
|
+
// the parent has a real origin and can make fetch calls freely.
|
|
118
|
+
window.fetch = function(url, options = {}) {
|
|
119
|
+
return new Promise((resolve, reject) => {
|
|
120
|
+
const id = Math.random().toString(36).slice(2) + Date.now().toString(36);
|
|
121
|
+
|
|
122
|
+
// Serialize body \u2014 postMessage can't transfer Request objects
|
|
123
|
+
const serializedOptions = {
|
|
124
|
+
method: options.method || "GET",
|
|
125
|
+
headers: options.headers || {},
|
|
126
|
+
body: options.body || null,
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
// Listen for the response from the parent
|
|
130
|
+
function handleMessage(event) {
|
|
131
|
+
if (
|
|
132
|
+
event.data?.source !== "renderize" ||
|
|
133
|
+
event.data?.type !== "fetch-response" ||
|
|
134
|
+
event.data?.id !== id
|
|
135
|
+
) return;
|
|
136
|
+
|
|
137
|
+
window.removeEventListener("message", handleMessage);
|
|
138
|
+
|
|
139
|
+
if (event.data.error) {
|
|
140
|
+
reject(new Error(event.data.error));
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Reconstruct a real Response object from the serialized data
|
|
145
|
+
const { status, statusText, headers, body } = event.data;
|
|
146
|
+
const responseBody = typeof body === "string" ? body : JSON.stringify(body);
|
|
147
|
+
|
|
148
|
+
const response = new Response(responseBody, {
|
|
149
|
+
status,
|
|
150
|
+
statusText,
|
|
151
|
+
headers: new Headers(headers),
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
resolve(response);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
window.addEventListener("message", handleMessage);
|
|
158
|
+
|
|
159
|
+
// Ask the parent to perform the fetch on our behalf
|
|
160
|
+
window.parent.postMessage({
|
|
161
|
+
source: "renderize",
|
|
162
|
+
type: "fetch-request",
|
|
163
|
+
id,
|
|
164
|
+
url,
|
|
165
|
+
options: serializedOptions,
|
|
166
|
+
}, "*");
|
|
167
|
+
});
|
|
168
|
+
};
|
|
169
|
+
// \u2500\u2500 END FETCH PROXY \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
170
|
+
</script>
|
|
171
|
+
|
|
172
|
+
<script type="text/babel" data-type="module">
|
|
173
|
+
import React, {
|
|
174
|
+
useState, useEffect, useRef, useCallback,
|
|
175
|
+
useMemo, useReducer, useContext, createContext,
|
|
176
|
+
forwardRef, Fragment
|
|
177
|
+
} from "react";
|
|
178
|
+
import { createRoot } from "react-dom/client";
|
|
179
|
+
|
|
180
|
+
// \u2500\u2500 USER CODE START \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
181
|
+
${code}
|
|
182
|
+
// \u2500\u2500 USER CODE END \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
183
|
+
|
|
184
|
+
const container = document.getElementById("root");
|
|
185
|
+
createRoot(container).render(React.createElement(App));
|
|
186
|
+
</script>
|
|
187
|
+
</body>
|
|
188
|
+
</html>`;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// src/sanitize.ts
|
|
192
|
+
var TEMPLATE_PROVIDED_MODULES = /* @__PURE__ */ new Set([
|
|
193
|
+
"react",
|
|
194
|
+
"react-dom",
|
|
195
|
+
"react-dom/client",
|
|
196
|
+
"react/jsx-runtime"
|
|
197
|
+
]);
|
|
198
|
+
var TEMPLATE_PROVIDED_NAMES = /* @__PURE__ */ new Set([
|
|
199
|
+
"React",
|
|
200
|
+
"useState",
|
|
201
|
+
"useEffect",
|
|
202
|
+
"useRef",
|
|
203
|
+
"useCallback",
|
|
204
|
+
"useMemo",
|
|
205
|
+
"useReducer",
|
|
206
|
+
"useContext",
|
|
207
|
+
"createContext",
|
|
208
|
+
"forwardRef",
|
|
209
|
+
"Fragment",
|
|
210
|
+
"createRoot"
|
|
211
|
+
]);
|
|
212
|
+
var IMPORTMAP_MODULES = /* @__PURE__ */ new Set([
|
|
213
|
+
"react",
|
|
214
|
+
"react/jsx-runtime",
|
|
215
|
+
"react-dom",
|
|
216
|
+
"react-dom/client",
|
|
217
|
+
"lucide-react",
|
|
218
|
+
"clsx",
|
|
219
|
+
"class-variance-authority",
|
|
220
|
+
"tailwind-merge",
|
|
221
|
+
"@radix-ui/react-accordion",
|
|
222
|
+
"@radix-ui/react-alert-dialog",
|
|
223
|
+
"@radix-ui/react-avatar",
|
|
224
|
+
"@radix-ui/react-checkbox",
|
|
225
|
+
"@radix-ui/react-collapsible",
|
|
226
|
+
"@radix-ui/react-context-menu",
|
|
227
|
+
"@radix-ui/react-dialog",
|
|
228
|
+
"@radix-ui/react-dropdown-menu",
|
|
229
|
+
"@radix-ui/react-hover-card",
|
|
230
|
+
"@radix-ui/react-label",
|
|
231
|
+
"@radix-ui/react-menubar",
|
|
232
|
+
"@radix-ui/react-navigation-menu",
|
|
233
|
+
"@radix-ui/react-popover",
|
|
234
|
+
"@radix-ui/react-progress",
|
|
235
|
+
"@radix-ui/react-radio-group",
|
|
236
|
+
"@radix-ui/react-scroll-area",
|
|
237
|
+
"@radix-ui/react-select",
|
|
238
|
+
"@radix-ui/react-separator",
|
|
239
|
+
"@radix-ui/react-slider",
|
|
240
|
+
"@radix-ui/react-slot",
|
|
241
|
+
"@radix-ui/react-switch",
|
|
242
|
+
"@radix-ui/react-tabs",
|
|
243
|
+
"@radix-ui/react-toast",
|
|
244
|
+
"@radix-ui/react-toggle",
|
|
245
|
+
"@radix-ui/react-toggle-group",
|
|
246
|
+
"@radix-ui/react-toolbar",
|
|
247
|
+
"@radix-ui/react-tooltip"
|
|
248
|
+
]);
|
|
249
|
+
function findMatchingBrace(code, openIndex) {
|
|
250
|
+
let depth = 0;
|
|
251
|
+
let inSingle = false;
|
|
252
|
+
let inDouble = false;
|
|
253
|
+
let inTemplate = 0;
|
|
254
|
+
for (let i = openIndex; i < code.length; i++) {
|
|
255
|
+
const ch = code[i];
|
|
256
|
+
const prev = i > 0 ? code[i - 1] : "";
|
|
257
|
+
if (prev === "\\") continue;
|
|
258
|
+
if (!inDouble && !inTemplate && ch === "'") {
|
|
259
|
+
inSingle = !inSingle;
|
|
260
|
+
continue;
|
|
261
|
+
}
|
|
262
|
+
if (!inSingle && !inTemplate && ch === '"') {
|
|
263
|
+
inDouble = !inDouble;
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
if (!inSingle && !inDouble && ch === "`") {
|
|
267
|
+
inTemplate = inTemplate ? inTemplate - 1 : inTemplate + 1;
|
|
268
|
+
continue;
|
|
269
|
+
}
|
|
270
|
+
if (inSingle || inDouble || inTemplate) continue;
|
|
271
|
+
if (ch === "{") depth++;
|
|
272
|
+
else if (ch === "}") {
|
|
273
|
+
depth--;
|
|
274
|
+
if (depth === 0) return i + 1;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return -1;
|
|
278
|
+
}
|
|
279
|
+
function stripTypeScriptDeclarations(code) {
|
|
280
|
+
const blockKeywords = /(?:export\s+)?(?:interface|enum)\s+\w[\w<,\s>]*\s*\{/g;
|
|
281
|
+
let match;
|
|
282
|
+
while ((match = blockKeywords.exec(code)) !== null) {
|
|
283
|
+
const openBrace = code.indexOf("{", match.index + match[0].length - 1);
|
|
284
|
+
if (openBrace === -1) continue;
|
|
285
|
+
const end = findMatchingBrace(code, openBrace);
|
|
286
|
+
if (end === -1) continue;
|
|
287
|
+
const tail = code[end] === ";" ? end + 1 : end;
|
|
288
|
+
code = code.slice(0, match.index) + code.slice(tail);
|
|
289
|
+
blockKeywords.lastIndex = match.index;
|
|
290
|
+
}
|
|
291
|
+
code = code.replace(
|
|
292
|
+
/^(?:export\s+)?type\s+\w[\w<,\s>]*\s*=\s*/gm,
|
|
293
|
+
(_header, offset, fullCode) => {
|
|
294
|
+
const rest = fullCode.slice(offset + _header.length);
|
|
295
|
+
const trimmed = rest.trimStart();
|
|
296
|
+
if (trimmed.startsWith("{")) {
|
|
297
|
+
const relOpen = rest.indexOf("{");
|
|
298
|
+
const end = findMatchingBrace(rest, relOpen);
|
|
299
|
+
if (end !== -1) {
|
|
300
|
+
code = fullCode.slice(0, offset) + fullCode.slice(offset + _header.length + end);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return "";
|
|
304
|
+
}
|
|
305
|
+
);
|
|
306
|
+
return code;
|
|
307
|
+
}
|
|
308
|
+
function stripAsAssertions(code) {
|
|
309
|
+
return code.replace(
|
|
310
|
+
/(?<![{,]\s*\w+)\s+as\s+[A-Z]\w*(?:<[^>]*>)?(?:\[\])?(?=\s*[),;}\n])/g,
|
|
311
|
+
""
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
function detectMainComponentName(code) {
|
|
315
|
+
const fnExport = code.match(/export\s+default\s+function\s+([A-Z]\w*)/);
|
|
316
|
+
if (fnExport && fnExport[1] !== "App") return fnExport[1];
|
|
317
|
+
const constExport = code.match(/export\s+default\s+(?:const|let)\s+([A-Z]\w*)/);
|
|
318
|
+
if (constExport && constExport[1] !== "App") return constExport[1];
|
|
319
|
+
const allDefs = [
|
|
320
|
+
...code.matchAll(
|
|
321
|
+
/^(?:function|const|let)\s+([A-Z]\w*)\s*(?:=\s*(?:\(|React\.memo\()|[\(<(])/gm
|
|
322
|
+
)
|
|
323
|
+
].map((m) => m[1]).filter((n) => n !== "App");
|
|
324
|
+
if (allDefs.length === 1) return allDefs[0];
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
327
|
+
function sanitizeCode(raw) {
|
|
328
|
+
let code = raw;
|
|
329
|
+
code = code.replace(/^```[a-zA-Z]*\r?\n?/, "").replace(/\r?\n?```\s*$/, "");
|
|
330
|
+
code = code.replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\r/g, "\r");
|
|
331
|
+
code = code.replace(/^\s*['"]use (client|server)['"]\s*;?\s*\n?/gm, "");
|
|
332
|
+
code = stripTypeScriptDeclarations(code);
|
|
333
|
+
code = stripAsAssertions(code);
|
|
334
|
+
const originalName = detectMainComponentName(code);
|
|
335
|
+
if (originalName) {
|
|
336
|
+
code = code.replace(new RegExp(`\\b${originalName}\\b`, "g"), "App");
|
|
337
|
+
}
|
|
338
|
+
code = code.replace(/\bexport\s+default\s+(function\s+App\b)/, "$1");
|
|
339
|
+
code = code.replace(/\bexport\s+default\s+((?:const|let)\s+App\b)/, "$1");
|
|
340
|
+
code = code.replace(/\bexport\s+(function\s+App\b)/, "$1");
|
|
341
|
+
code = code.replace(/\bexport\s+((?:const|let)\s+App\b)/, "$1");
|
|
342
|
+
const importLineRegex = /^import\s+(?:type\s+)?(?:[^"'\n]+\s+from\s+)?["']([^"']+)["'];?\s*$/gm;
|
|
343
|
+
code = code.replace(importLineRegex, (line, modulePath) => {
|
|
344
|
+
if (TEMPLATE_PROVIDED_MODULES.has(modulePath)) return "";
|
|
345
|
+
if (!IMPORTMAP_MODULES.has(modulePath)) return "";
|
|
346
|
+
const namedMatch = line.match(/\{([^}]+)\}/);
|
|
347
|
+
if (namedMatch) {
|
|
348
|
+
const names = namedMatch[1].split(",").map((n) => n.trim().split(/\s+as\s+/)[0].trim()).filter(Boolean);
|
|
349
|
+
if (names.length > 0 && names.every((n) => TEMPLATE_PROVIDED_NAMES.has(n))) {
|
|
350
|
+
return "";
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
return line;
|
|
354
|
+
});
|
|
355
|
+
code = code.replace(
|
|
356
|
+
/^(?:const|let|var)\s+[\w\s,{}]+\s*=\s*require\s*\(["'][^"']+["']\)\s*;?\s*$/gm,
|
|
357
|
+
""
|
|
358
|
+
);
|
|
359
|
+
code = code.replace(/\n{3,}/g, "\n\n");
|
|
360
|
+
return code.trim();
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// src/Renderize.tsx
|
|
364
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
365
|
+
function Renderize({
|
|
366
|
+
code,
|
|
367
|
+
height = "100%",
|
|
368
|
+
width = "100%",
|
|
369
|
+
className,
|
|
370
|
+
style,
|
|
371
|
+
onError
|
|
372
|
+
}) {
|
|
373
|
+
const iframeRef = (0, import_react.useRef)(null);
|
|
374
|
+
const [srcDoc, setSrcDoc] = (0, import_react.useState)(null);
|
|
375
|
+
(0, import_react.useEffect)(() => {
|
|
376
|
+
if (!code?.trim()) return;
|
|
377
|
+
setSrcDoc(buildTemplate(sanitizeCode(code)));
|
|
378
|
+
}, [code]);
|
|
379
|
+
(0, import_react.useEffect)(() => {
|
|
380
|
+
const handler = async (event) => {
|
|
381
|
+
if (event.data?.source !== "renderize") return;
|
|
382
|
+
if (event.data.type === "fetch-request") {
|
|
383
|
+
const { id, url, options } = event.data;
|
|
384
|
+
try {
|
|
385
|
+
const res = await fetch(url, {
|
|
386
|
+
method: options.method,
|
|
387
|
+
headers: options.headers,
|
|
388
|
+
body: options.body ?? void 0
|
|
389
|
+
});
|
|
390
|
+
const body = await res.text();
|
|
391
|
+
iframeRef.current?.contentWindow?.postMessage(
|
|
392
|
+
{
|
|
393
|
+
source: "renderize",
|
|
394
|
+
type: "fetch-response",
|
|
395
|
+
id,
|
|
396
|
+
status: res.status,
|
|
397
|
+
statusText: res.statusText,
|
|
398
|
+
// Convert Headers to a plain object for structured clone
|
|
399
|
+
headers: Object.fromEntries(res.headers.entries()),
|
|
400
|
+
body
|
|
401
|
+
},
|
|
402
|
+
"*"
|
|
403
|
+
);
|
|
404
|
+
} catch (err) {
|
|
405
|
+
iframeRef.current?.contentWindow?.postMessage(
|
|
406
|
+
{
|
|
407
|
+
source: "renderize",
|
|
408
|
+
type: "fetch-response",
|
|
409
|
+
id,
|
|
410
|
+
error: err instanceof Error ? err.message : String(err)
|
|
411
|
+
},
|
|
412
|
+
"*"
|
|
413
|
+
);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
if (event.data.type === "error" && onError) {
|
|
417
|
+
onError(event.data.message);
|
|
418
|
+
}
|
|
419
|
+
};
|
|
420
|
+
window.addEventListener("message", handler);
|
|
421
|
+
return () => window.removeEventListener("message", handler);
|
|
422
|
+
}, [onError]);
|
|
423
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
424
|
+
"div",
|
|
425
|
+
{
|
|
426
|
+
className,
|
|
427
|
+
style: { width, height, overflow: "hidden", ...style },
|
|
428
|
+
children: srcDoc && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
429
|
+
"iframe",
|
|
430
|
+
{
|
|
431
|
+
ref: iframeRef,
|
|
432
|
+
srcDoc,
|
|
433
|
+
title: "Renderize Sandbox",
|
|
434
|
+
sandbox: "allow-scripts allow-forms allow-modals allow-popups allow-downloads",
|
|
435
|
+
style: {
|
|
436
|
+
width: "100%",
|
|
437
|
+
height: "100%",
|
|
438
|
+
border: "none",
|
|
439
|
+
display: "block"
|
|
440
|
+
}
|
|
441
|
+
},
|
|
442
|
+
srcDoc
|
|
443
|
+
)
|
|
444
|
+
}
|
|
445
|
+
);
|
|
446
|
+
}
|
|
447
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
448
|
+
0 && (module.exports = {
|
|
449
|
+
Renderize
|
|
450
|
+
});
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
interface RenderizeProps {
|
|
5
|
+
/** React code generated by the LLM. Must define a function component named App. */
|
|
6
|
+
code: string;
|
|
7
|
+
/** Height of the sandbox iframe. Defaults to "100%" */
|
|
8
|
+
height?: string | number;
|
|
9
|
+
/** Width of the sandbox iframe. Defaults to "100%" */
|
|
10
|
+
width?: string | number;
|
|
11
|
+
/** Custom class name for the wrapper element */
|
|
12
|
+
className?: string;
|
|
13
|
+
/** Custom inline styles for the wrapper element */
|
|
14
|
+
style?: React.CSSProperties;
|
|
15
|
+
/** Called when the sandbox encounters a runtime error */
|
|
16
|
+
onError?: (error: string) => void;
|
|
17
|
+
}
|
|
18
|
+
declare function Renderize({ code, height, width, className, style, onError, }: RenderizeProps): react_jsx_runtime.JSX.Element;
|
|
19
|
+
|
|
20
|
+
export { Renderize, type RenderizeProps };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
interface RenderizeProps {
|
|
5
|
+
/** React code generated by the LLM. Must define a function component named App. */
|
|
6
|
+
code: string;
|
|
7
|
+
/** Height of the sandbox iframe. Defaults to "100%" */
|
|
8
|
+
height?: string | number;
|
|
9
|
+
/** Width of the sandbox iframe. Defaults to "100%" */
|
|
10
|
+
width?: string | number;
|
|
11
|
+
/** Custom class name for the wrapper element */
|
|
12
|
+
className?: string;
|
|
13
|
+
/** Custom inline styles for the wrapper element */
|
|
14
|
+
style?: React.CSSProperties;
|
|
15
|
+
/** Called when the sandbox encounters a runtime error */
|
|
16
|
+
onError?: (error: string) => void;
|
|
17
|
+
}
|
|
18
|
+
declare function Renderize({ code, height, width, className, style, onError, }: RenderizeProps): react_jsx_runtime.JSX.Element;
|
|
19
|
+
|
|
20
|
+
export { Renderize, type RenderizeProps };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
// src/Renderize.tsx
|
|
2
|
+
import { useEffect, useRef, useState } from "react";
|
|
3
|
+
|
|
4
|
+
// src/template.ts
|
|
5
|
+
function buildTemplate(code) {
|
|
6
|
+
return `<!DOCTYPE html>
|
|
7
|
+
<html lang="en">
|
|
8
|
+
<head>
|
|
9
|
+
<meta charset="UTF-8" />
|
|
10
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
11
|
+
|
|
12
|
+
<script src="https://cdn.tailwindcss.com"></script>
|
|
13
|
+
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
|
|
14
|
+
|
|
15
|
+
<script type="importmap">
|
|
16
|
+
{
|
|
17
|
+
"imports": {
|
|
18
|
+
"react": "https://esm.sh/react@18",
|
|
19
|
+
"react/jsx-runtime": "https://esm.sh/react@18/jsx-runtime",
|
|
20
|
+
"react-dom": "https://esm.sh/react-dom@18",
|
|
21
|
+
"react-dom/client": "https://esm.sh/react-dom@18/client",
|
|
22
|
+
"lucide-react": "https://esm.sh/lucide-react?external=react",
|
|
23
|
+
"clsx": "https://esm.sh/clsx",
|
|
24
|
+
"class-variance-authority": "https://esm.sh/class-variance-authority",
|
|
25
|
+
"tailwind-merge": "https://esm.sh/tailwind-merge",
|
|
26
|
+
"@radix-ui/react-accordion": "https://esm.sh/@radix-ui/react-accordion?external=react,react-dom",
|
|
27
|
+
"@radix-ui/react-alert-dialog": "https://esm.sh/@radix-ui/react-alert-dialog?external=react,react-dom",
|
|
28
|
+
"@radix-ui/react-avatar": "https://esm.sh/@radix-ui/react-avatar?external=react,react-dom",
|
|
29
|
+
"@radix-ui/react-checkbox": "https://esm.sh/@radix-ui/react-checkbox?external=react,react-dom",
|
|
30
|
+
"@radix-ui/react-collapsible": "https://esm.sh/@radix-ui/react-collapsible?external=react,react-dom",
|
|
31
|
+
"@radix-ui/react-context-menu": "https://esm.sh/@radix-ui/react-context-menu?external=react,react-dom",
|
|
32
|
+
"@radix-ui/react-dialog": "https://esm.sh/@radix-ui/react-dialog?external=react,react-dom",
|
|
33
|
+
"@radix-ui/react-dropdown-menu": "https://esm.sh/@radix-ui/react-dropdown-menu?external=react,react-dom",
|
|
34
|
+
"@radix-ui/react-hover-card": "https://esm.sh/@radix-ui/react-hover-card?external=react,react-dom",
|
|
35
|
+
"@radix-ui/react-label": "https://esm.sh/@radix-ui/react-label?external=react,react-dom",
|
|
36
|
+
"@radix-ui/react-menubar": "https://esm.sh/@radix-ui/react-menubar?external=react,react-dom",
|
|
37
|
+
"@radix-ui/react-navigation-menu": "https://esm.sh/@radix-ui/react-navigation-menu?external=react,react-dom",
|
|
38
|
+
"@radix-ui/react-popover": "https://esm.sh/@radix-ui/react-popover?external=react,react-dom",
|
|
39
|
+
"@radix-ui/react-progress": "https://esm.sh/@radix-ui/react-progress?external=react,react-dom",
|
|
40
|
+
"@radix-ui/react-radio-group": "https://esm.sh/@radix-ui/react-radio-group?external=react,react-dom",
|
|
41
|
+
"@radix-ui/react-scroll-area": "https://esm.sh/@radix-ui/react-scroll-area?external=react,react-dom",
|
|
42
|
+
"@radix-ui/react-select": "https://esm.sh/@radix-ui/react-select?external=react,react-dom",
|
|
43
|
+
"@radix-ui/react-separator": "https://esm.sh/@radix-ui/react-separator?external=react,react-dom",
|
|
44
|
+
"@radix-ui/react-slider": "https://esm.sh/@radix-ui/react-slider?external=react,react-dom",
|
|
45
|
+
"@radix-ui/react-slot": "https://esm.sh/@radix-ui/react-slot?external=react,react-dom",
|
|
46
|
+
"@radix-ui/react-switch": "https://esm.sh/@radix-ui/react-switch?external=react,react-dom",
|
|
47
|
+
"@radix-ui/react-tabs": "https://esm.sh/@radix-ui/react-tabs?external=react,react-dom",
|
|
48
|
+
"@radix-ui/react-toast": "https://esm.sh/@radix-ui/react-toast?external=react,react-dom",
|
|
49
|
+
"@radix-ui/react-toggle": "https://esm.sh/@radix-ui/react-toggle?external=react,react-dom",
|
|
50
|
+
"@radix-ui/react-toggle-group": "https://esm.sh/@radix-ui/react-toggle-group?external=react,react-dom",
|
|
51
|
+
"@radix-ui/react-toolbar": "https://esm.sh/@radix-ui/react-toolbar?external=react,react-dom",
|
|
52
|
+
"@radix-ui/react-tooltip": "https://esm.sh/@radix-ui/react-tooltip?external=react,react-dom"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
</script>
|
|
56
|
+
|
|
57
|
+
<style>
|
|
58
|
+
* { box-sizing: border-box; }
|
|
59
|
+
body { margin: 0; padding: 0; }
|
|
60
|
+
|
|
61
|
+
/* Scrollbar personalizada: fina y semi-transparente */
|
|
62
|
+
::-webkit-scrollbar {
|
|
63
|
+
width: 6px;
|
|
64
|
+
height: 6px;
|
|
65
|
+
}
|
|
66
|
+
::-webkit-scrollbar-track {
|
|
67
|
+
background: transparent;
|
|
68
|
+
}
|
|
69
|
+
::-webkit-scrollbar-thumb {
|
|
70
|
+
background: rgba(255, 255, 255, 0.35);
|
|
71
|
+
border-radius: 999px;
|
|
72
|
+
transition: background 0.2s ease;
|
|
73
|
+
}
|
|
74
|
+
::-webkit-scrollbar-thumb:hover {
|
|
75
|
+
background: rgba(255, 255, 255, 0.55);
|
|
76
|
+
}
|
|
77
|
+
/* Firefox */
|
|
78
|
+
* {
|
|
79
|
+
scrollbar-width: thin;
|
|
80
|
+
scrollbar-color: rgba(255, 255, 255, 0.35) transparent;
|
|
81
|
+
}
|
|
82
|
+
</style>
|
|
83
|
+
</head>
|
|
84
|
+
<body>
|
|
85
|
+
<div id="root"></div>
|
|
86
|
+
|
|
87
|
+
<script>
|
|
88
|
+
// \u2500\u2500 FETCH PROXY \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
89
|
+
// Override window.fetch to proxy requests through the parent window.
|
|
90
|
+
// This solves the CORS/null-origin issue with srcdoc iframes:
|
|
91
|
+
// the parent has a real origin and can make fetch calls freely.
|
|
92
|
+
window.fetch = function(url, options = {}) {
|
|
93
|
+
return new Promise((resolve, reject) => {
|
|
94
|
+
const id = Math.random().toString(36).slice(2) + Date.now().toString(36);
|
|
95
|
+
|
|
96
|
+
// Serialize body \u2014 postMessage can't transfer Request objects
|
|
97
|
+
const serializedOptions = {
|
|
98
|
+
method: options.method || "GET",
|
|
99
|
+
headers: options.headers || {},
|
|
100
|
+
body: options.body || null,
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// Listen for the response from the parent
|
|
104
|
+
function handleMessage(event) {
|
|
105
|
+
if (
|
|
106
|
+
event.data?.source !== "renderize" ||
|
|
107
|
+
event.data?.type !== "fetch-response" ||
|
|
108
|
+
event.data?.id !== id
|
|
109
|
+
) return;
|
|
110
|
+
|
|
111
|
+
window.removeEventListener("message", handleMessage);
|
|
112
|
+
|
|
113
|
+
if (event.data.error) {
|
|
114
|
+
reject(new Error(event.data.error));
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Reconstruct a real Response object from the serialized data
|
|
119
|
+
const { status, statusText, headers, body } = event.data;
|
|
120
|
+
const responseBody = typeof body === "string" ? body : JSON.stringify(body);
|
|
121
|
+
|
|
122
|
+
const response = new Response(responseBody, {
|
|
123
|
+
status,
|
|
124
|
+
statusText,
|
|
125
|
+
headers: new Headers(headers),
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
resolve(response);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
window.addEventListener("message", handleMessage);
|
|
132
|
+
|
|
133
|
+
// Ask the parent to perform the fetch on our behalf
|
|
134
|
+
window.parent.postMessage({
|
|
135
|
+
source: "renderize",
|
|
136
|
+
type: "fetch-request",
|
|
137
|
+
id,
|
|
138
|
+
url,
|
|
139
|
+
options: serializedOptions,
|
|
140
|
+
}, "*");
|
|
141
|
+
});
|
|
142
|
+
};
|
|
143
|
+
// \u2500\u2500 END FETCH PROXY \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
144
|
+
</script>
|
|
145
|
+
|
|
146
|
+
<script type="text/babel" data-type="module">
|
|
147
|
+
import React, {
|
|
148
|
+
useState, useEffect, useRef, useCallback,
|
|
149
|
+
useMemo, useReducer, useContext, createContext,
|
|
150
|
+
forwardRef, Fragment
|
|
151
|
+
} from "react";
|
|
152
|
+
import { createRoot } from "react-dom/client";
|
|
153
|
+
|
|
154
|
+
// \u2500\u2500 USER CODE START \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
155
|
+
${code}
|
|
156
|
+
// \u2500\u2500 USER CODE END \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
157
|
+
|
|
158
|
+
const container = document.getElementById("root");
|
|
159
|
+
createRoot(container).render(React.createElement(App));
|
|
160
|
+
</script>
|
|
161
|
+
</body>
|
|
162
|
+
</html>`;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// src/sanitize.ts
|
|
166
|
+
var TEMPLATE_PROVIDED_MODULES = /* @__PURE__ */ new Set([
|
|
167
|
+
"react",
|
|
168
|
+
"react-dom",
|
|
169
|
+
"react-dom/client",
|
|
170
|
+
"react/jsx-runtime"
|
|
171
|
+
]);
|
|
172
|
+
var TEMPLATE_PROVIDED_NAMES = /* @__PURE__ */ new Set([
|
|
173
|
+
"React",
|
|
174
|
+
"useState",
|
|
175
|
+
"useEffect",
|
|
176
|
+
"useRef",
|
|
177
|
+
"useCallback",
|
|
178
|
+
"useMemo",
|
|
179
|
+
"useReducer",
|
|
180
|
+
"useContext",
|
|
181
|
+
"createContext",
|
|
182
|
+
"forwardRef",
|
|
183
|
+
"Fragment",
|
|
184
|
+
"createRoot"
|
|
185
|
+
]);
|
|
186
|
+
var IMPORTMAP_MODULES = /* @__PURE__ */ new Set([
|
|
187
|
+
"react",
|
|
188
|
+
"react/jsx-runtime",
|
|
189
|
+
"react-dom",
|
|
190
|
+
"react-dom/client",
|
|
191
|
+
"lucide-react",
|
|
192
|
+
"clsx",
|
|
193
|
+
"class-variance-authority",
|
|
194
|
+
"tailwind-merge",
|
|
195
|
+
"@radix-ui/react-accordion",
|
|
196
|
+
"@radix-ui/react-alert-dialog",
|
|
197
|
+
"@radix-ui/react-avatar",
|
|
198
|
+
"@radix-ui/react-checkbox",
|
|
199
|
+
"@radix-ui/react-collapsible",
|
|
200
|
+
"@radix-ui/react-context-menu",
|
|
201
|
+
"@radix-ui/react-dialog",
|
|
202
|
+
"@radix-ui/react-dropdown-menu",
|
|
203
|
+
"@radix-ui/react-hover-card",
|
|
204
|
+
"@radix-ui/react-label",
|
|
205
|
+
"@radix-ui/react-menubar",
|
|
206
|
+
"@radix-ui/react-navigation-menu",
|
|
207
|
+
"@radix-ui/react-popover",
|
|
208
|
+
"@radix-ui/react-progress",
|
|
209
|
+
"@radix-ui/react-radio-group",
|
|
210
|
+
"@radix-ui/react-scroll-area",
|
|
211
|
+
"@radix-ui/react-select",
|
|
212
|
+
"@radix-ui/react-separator",
|
|
213
|
+
"@radix-ui/react-slider",
|
|
214
|
+
"@radix-ui/react-slot",
|
|
215
|
+
"@radix-ui/react-switch",
|
|
216
|
+
"@radix-ui/react-tabs",
|
|
217
|
+
"@radix-ui/react-toast",
|
|
218
|
+
"@radix-ui/react-toggle",
|
|
219
|
+
"@radix-ui/react-toggle-group",
|
|
220
|
+
"@radix-ui/react-toolbar",
|
|
221
|
+
"@radix-ui/react-tooltip"
|
|
222
|
+
]);
|
|
223
|
+
function findMatchingBrace(code, openIndex) {
|
|
224
|
+
let depth = 0;
|
|
225
|
+
let inSingle = false;
|
|
226
|
+
let inDouble = false;
|
|
227
|
+
let inTemplate = 0;
|
|
228
|
+
for (let i = openIndex; i < code.length; i++) {
|
|
229
|
+
const ch = code[i];
|
|
230
|
+
const prev = i > 0 ? code[i - 1] : "";
|
|
231
|
+
if (prev === "\\") continue;
|
|
232
|
+
if (!inDouble && !inTemplate && ch === "'") {
|
|
233
|
+
inSingle = !inSingle;
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
if (!inSingle && !inTemplate && ch === '"') {
|
|
237
|
+
inDouble = !inDouble;
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
if (!inSingle && !inDouble && ch === "`") {
|
|
241
|
+
inTemplate = inTemplate ? inTemplate - 1 : inTemplate + 1;
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
if (inSingle || inDouble || inTemplate) continue;
|
|
245
|
+
if (ch === "{") depth++;
|
|
246
|
+
else if (ch === "}") {
|
|
247
|
+
depth--;
|
|
248
|
+
if (depth === 0) return i + 1;
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return -1;
|
|
252
|
+
}
|
|
253
|
+
function stripTypeScriptDeclarations(code) {
|
|
254
|
+
const blockKeywords = /(?:export\s+)?(?:interface|enum)\s+\w[\w<,\s>]*\s*\{/g;
|
|
255
|
+
let match;
|
|
256
|
+
while ((match = blockKeywords.exec(code)) !== null) {
|
|
257
|
+
const openBrace = code.indexOf("{", match.index + match[0].length - 1);
|
|
258
|
+
if (openBrace === -1) continue;
|
|
259
|
+
const end = findMatchingBrace(code, openBrace);
|
|
260
|
+
if (end === -1) continue;
|
|
261
|
+
const tail = code[end] === ";" ? end + 1 : end;
|
|
262
|
+
code = code.slice(0, match.index) + code.slice(tail);
|
|
263
|
+
blockKeywords.lastIndex = match.index;
|
|
264
|
+
}
|
|
265
|
+
code = code.replace(
|
|
266
|
+
/^(?:export\s+)?type\s+\w[\w<,\s>]*\s*=\s*/gm,
|
|
267
|
+
(_header, offset, fullCode) => {
|
|
268
|
+
const rest = fullCode.slice(offset + _header.length);
|
|
269
|
+
const trimmed = rest.trimStart();
|
|
270
|
+
if (trimmed.startsWith("{")) {
|
|
271
|
+
const relOpen = rest.indexOf("{");
|
|
272
|
+
const end = findMatchingBrace(rest, relOpen);
|
|
273
|
+
if (end !== -1) {
|
|
274
|
+
code = fullCode.slice(0, offset) + fullCode.slice(offset + _header.length + end);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
return "";
|
|
278
|
+
}
|
|
279
|
+
);
|
|
280
|
+
return code;
|
|
281
|
+
}
|
|
282
|
+
function stripAsAssertions(code) {
|
|
283
|
+
return code.replace(
|
|
284
|
+
/(?<![{,]\s*\w+)\s+as\s+[A-Z]\w*(?:<[^>]*>)?(?:\[\])?(?=\s*[),;}\n])/g,
|
|
285
|
+
""
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
function detectMainComponentName(code) {
|
|
289
|
+
const fnExport = code.match(/export\s+default\s+function\s+([A-Z]\w*)/);
|
|
290
|
+
if (fnExport && fnExport[1] !== "App") return fnExport[1];
|
|
291
|
+
const constExport = code.match(/export\s+default\s+(?:const|let)\s+([A-Z]\w*)/);
|
|
292
|
+
if (constExport && constExport[1] !== "App") return constExport[1];
|
|
293
|
+
const allDefs = [
|
|
294
|
+
...code.matchAll(
|
|
295
|
+
/^(?:function|const|let)\s+([A-Z]\w*)\s*(?:=\s*(?:\(|React\.memo\()|[\(<(])/gm
|
|
296
|
+
)
|
|
297
|
+
].map((m) => m[1]).filter((n) => n !== "App");
|
|
298
|
+
if (allDefs.length === 1) return allDefs[0];
|
|
299
|
+
return null;
|
|
300
|
+
}
|
|
301
|
+
function sanitizeCode(raw) {
|
|
302
|
+
let code = raw;
|
|
303
|
+
code = code.replace(/^```[a-zA-Z]*\r?\n?/, "").replace(/\r?\n?```\s*$/, "");
|
|
304
|
+
code = code.replace(/\\n/g, "\n").replace(/\\t/g, " ").replace(/\\r/g, "\r");
|
|
305
|
+
code = code.replace(/^\s*['"]use (client|server)['"]\s*;?\s*\n?/gm, "");
|
|
306
|
+
code = stripTypeScriptDeclarations(code);
|
|
307
|
+
code = stripAsAssertions(code);
|
|
308
|
+
const originalName = detectMainComponentName(code);
|
|
309
|
+
if (originalName) {
|
|
310
|
+
code = code.replace(new RegExp(`\\b${originalName}\\b`, "g"), "App");
|
|
311
|
+
}
|
|
312
|
+
code = code.replace(/\bexport\s+default\s+(function\s+App\b)/, "$1");
|
|
313
|
+
code = code.replace(/\bexport\s+default\s+((?:const|let)\s+App\b)/, "$1");
|
|
314
|
+
code = code.replace(/\bexport\s+(function\s+App\b)/, "$1");
|
|
315
|
+
code = code.replace(/\bexport\s+((?:const|let)\s+App\b)/, "$1");
|
|
316
|
+
const importLineRegex = /^import\s+(?:type\s+)?(?:[^"'\n]+\s+from\s+)?["']([^"']+)["'];?\s*$/gm;
|
|
317
|
+
code = code.replace(importLineRegex, (line, modulePath) => {
|
|
318
|
+
if (TEMPLATE_PROVIDED_MODULES.has(modulePath)) return "";
|
|
319
|
+
if (!IMPORTMAP_MODULES.has(modulePath)) return "";
|
|
320
|
+
const namedMatch = line.match(/\{([^}]+)\}/);
|
|
321
|
+
if (namedMatch) {
|
|
322
|
+
const names = namedMatch[1].split(",").map((n) => n.trim().split(/\s+as\s+/)[0].trim()).filter(Boolean);
|
|
323
|
+
if (names.length > 0 && names.every((n) => TEMPLATE_PROVIDED_NAMES.has(n))) {
|
|
324
|
+
return "";
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return line;
|
|
328
|
+
});
|
|
329
|
+
code = code.replace(
|
|
330
|
+
/^(?:const|let|var)\s+[\w\s,{}]+\s*=\s*require\s*\(["'][^"']+["']\)\s*;?\s*$/gm,
|
|
331
|
+
""
|
|
332
|
+
);
|
|
333
|
+
code = code.replace(/\n{3,}/g, "\n\n");
|
|
334
|
+
return code.trim();
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// src/Renderize.tsx
|
|
338
|
+
import { jsx } from "react/jsx-runtime";
|
|
339
|
+
function Renderize({
|
|
340
|
+
code,
|
|
341
|
+
height = "100%",
|
|
342
|
+
width = "100%",
|
|
343
|
+
className,
|
|
344
|
+
style,
|
|
345
|
+
onError
|
|
346
|
+
}) {
|
|
347
|
+
const iframeRef = useRef(null);
|
|
348
|
+
const [srcDoc, setSrcDoc] = useState(null);
|
|
349
|
+
useEffect(() => {
|
|
350
|
+
if (!code?.trim()) return;
|
|
351
|
+
setSrcDoc(buildTemplate(sanitizeCode(code)));
|
|
352
|
+
}, [code]);
|
|
353
|
+
useEffect(() => {
|
|
354
|
+
const handler = async (event) => {
|
|
355
|
+
if (event.data?.source !== "renderize") return;
|
|
356
|
+
if (event.data.type === "fetch-request") {
|
|
357
|
+
const { id, url, options } = event.data;
|
|
358
|
+
try {
|
|
359
|
+
const res = await fetch(url, {
|
|
360
|
+
method: options.method,
|
|
361
|
+
headers: options.headers,
|
|
362
|
+
body: options.body ?? void 0
|
|
363
|
+
});
|
|
364
|
+
const body = await res.text();
|
|
365
|
+
iframeRef.current?.contentWindow?.postMessage(
|
|
366
|
+
{
|
|
367
|
+
source: "renderize",
|
|
368
|
+
type: "fetch-response",
|
|
369
|
+
id,
|
|
370
|
+
status: res.status,
|
|
371
|
+
statusText: res.statusText,
|
|
372
|
+
// Convert Headers to a plain object for structured clone
|
|
373
|
+
headers: Object.fromEntries(res.headers.entries()),
|
|
374
|
+
body
|
|
375
|
+
},
|
|
376
|
+
"*"
|
|
377
|
+
);
|
|
378
|
+
} catch (err) {
|
|
379
|
+
iframeRef.current?.contentWindow?.postMessage(
|
|
380
|
+
{
|
|
381
|
+
source: "renderize",
|
|
382
|
+
type: "fetch-response",
|
|
383
|
+
id,
|
|
384
|
+
error: err instanceof Error ? err.message : String(err)
|
|
385
|
+
},
|
|
386
|
+
"*"
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
if (event.data.type === "error" && onError) {
|
|
391
|
+
onError(event.data.message);
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
window.addEventListener("message", handler);
|
|
395
|
+
return () => window.removeEventListener("message", handler);
|
|
396
|
+
}, [onError]);
|
|
397
|
+
return /* @__PURE__ */ jsx(
|
|
398
|
+
"div",
|
|
399
|
+
{
|
|
400
|
+
className,
|
|
401
|
+
style: { width, height, overflow: "hidden", ...style },
|
|
402
|
+
children: srcDoc && /* @__PURE__ */ jsx(
|
|
403
|
+
"iframe",
|
|
404
|
+
{
|
|
405
|
+
ref: iframeRef,
|
|
406
|
+
srcDoc,
|
|
407
|
+
title: "Renderize Sandbox",
|
|
408
|
+
sandbox: "allow-scripts allow-forms allow-modals allow-popups allow-downloads",
|
|
409
|
+
style: {
|
|
410
|
+
width: "100%",
|
|
411
|
+
height: "100%",
|
|
412
|
+
border: "none",
|
|
413
|
+
display: "block"
|
|
414
|
+
}
|
|
415
|
+
},
|
|
416
|
+
srcDoc
|
|
417
|
+
)
|
|
418
|
+
}
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
export {
|
|
422
|
+
Renderize
|
|
423
|
+
};
|
package/package.json
CHANGED