@emailens/engine 0.5.1 → 0.6.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/README.md +113 -1
- package/dist/{chunk-W4SPWESS.js → chunk-3NDQOTPM.js} +2 -2
- package/dist/{chunk-SZ5O5PDZ.js → chunk-OY3DGZVU.js} +2 -2
- package/dist/{chunk-PX25W7YG.js → chunk-URZ4XPST.js} +48 -20
- package/dist/chunk-URZ4XPST.js.map +1 -0
- package/dist/{chunk-PFONR3YC.js → chunk-ZQF2XUIJ.js} +1 -8
- package/dist/{chunk-PFONR3YC.js.map → chunk-ZQF2XUIJ.js.map} +1 -1
- package/dist/compile/index.cjs +45 -16
- package/dist/compile/index.cjs.map +1 -1
- package/dist/compile/index.js +7 -7
- package/dist/index.cjs +152 -57
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +106 -1
- package/dist/index.d.ts +106 -1
- package/dist/index.js +152 -58
- package/dist/index.js.map +1 -1
- package/dist/maizzle-Y2CLJ7GB.js +8 -0
- package/dist/mjml-D7IOYXXK.js +8 -0
- package/dist/react-email-4TW6IDAA.js +8 -0
- package/package.json +25 -9
- package/dist/chunk-PX25W7YG.js.map +0 -1
- package/dist/maizzle-YDSYDVSM.js +0 -8
- package/dist/mjml-IYGC6AOM.js +0 -8
- package/dist/react-email-QRL5KZ4Y.js +0 -8
- /package/dist/{chunk-W4SPWESS.js.map → chunk-3NDQOTPM.js.map} +0 -0
- /package/dist/{chunk-SZ5O5PDZ.js.map → chunk-OY3DGZVU.js.map} +0 -0
- /package/dist/{maizzle-YDSYDVSM.js.map → maizzle-Y2CLJ7GB.js.map} +0 -0
- /package/dist/{mjml-IYGC6AOM.js.map → mjml-D7IOYXXK.js.map} +0 -0
- /package/dist/{react-email-QRL5KZ4Y.js.map → react-email-4TW6IDAA.js.map} +0 -0
package/README.md
CHANGED
|
@@ -57,6 +57,8 @@ console.log(report.images.total);
|
|
|
57
57
|
|
|
58
58
|
**Unified API** — runs all email analysis checks in a single call. Returns compatibility warnings + scores, spam analysis, link validation, accessibility audit, and image analysis.
|
|
59
59
|
|
|
60
|
+
Internally parses the HTML once and shares the DOM across all analyzers.
|
|
61
|
+
|
|
60
62
|
```typescript
|
|
61
63
|
import { auditEmail } from "@emailens/engine";
|
|
62
64
|
|
|
@@ -81,6 +83,57 @@ const report = auditEmail(html, {
|
|
|
81
83
|
|
|
82
84
|
---
|
|
83
85
|
|
|
86
|
+
### `createSession(html: string, options?: CreateSessionOptions): EmailSession`
|
|
87
|
+
|
|
88
|
+
**Session API** — pre-parses the HTML once and exposes all analysis methods on the shared DOM. Use this when you need to call multiple analysis functions on the same HTML to avoid redundant parsing.
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
import { createSession } from "@emailens/engine";
|
|
92
|
+
|
|
93
|
+
const session = createSession(html, { framework: "jsx" });
|
|
94
|
+
|
|
95
|
+
// All analysis methods share a single DOM parse:
|
|
96
|
+
const warnings = session.analyze();
|
|
97
|
+
const scores = session.score(warnings);
|
|
98
|
+
const spam = session.analyzeSpam();
|
|
99
|
+
const links = session.validateLinks();
|
|
100
|
+
const a11y = session.checkAccessibility();
|
|
101
|
+
const images = session.analyzeImages();
|
|
102
|
+
|
|
103
|
+
// Or run everything at once:
|
|
104
|
+
const report = session.audit();
|
|
105
|
+
|
|
106
|
+
// Transforms and dark mode still work (parse internally per client):
|
|
107
|
+
const transforms = session.transformForAllClients();
|
|
108
|
+
const darkMode = session.simulateDarkMode("gmail-web");
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
**`CreateSessionOptions`:**
|
|
112
|
+
- `framework?: "jsx" | "mjml" | "maizzle"` — framework for fix snippets (applies to all session methods)
|
|
113
|
+
|
|
114
|
+
**`EmailSession` methods:**
|
|
115
|
+
|
|
116
|
+
| Method | Shares DOM | Description |
|
|
117
|
+
|---|---|---|
|
|
118
|
+
| `audit(options?)` | Yes | Run all checks (equivalent to `auditEmail`) |
|
|
119
|
+
| `analyze()` | Yes | CSS compatibility warnings |
|
|
120
|
+
| `score(warnings)` | — | Generate per-client scores |
|
|
121
|
+
| `analyzeSpam(options?)` | Yes | Spam indicator analysis |
|
|
122
|
+
| `validateLinks()` | Yes | Link validation |
|
|
123
|
+
| `checkAccessibility()` | Yes | Accessibility audit |
|
|
124
|
+
| `analyzeImages()` | Yes | Image analysis |
|
|
125
|
+
| `transformForClient(clientId)` | No | Transform for one client |
|
|
126
|
+
| `transformForAllClients()` | No | Transform for all 12 clients |
|
|
127
|
+
| `simulateDarkMode(clientId)` | No | Dark mode simulation |
|
|
128
|
+
|
|
129
|
+
**When to use sessions vs standalone functions:**
|
|
130
|
+
|
|
131
|
+
- **Multiple analysis calls on the same HTML** → use `createSession()` to avoid redundant parsing
|
|
132
|
+
- **Single analysis call** → use standalone functions (`auditEmail`, `analyzeEmail`, etc.)
|
|
133
|
+
- **Server-side batch processing** → use `createSession()` per email for best throughput
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
84
137
|
### `analyzeEmail(html: string, framework?: Framework): CSSWarning[]`
|
|
85
138
|
|
|
86
139
|
Analyzes an HTML email and returns CSS compatibility warnings for all 12 email clients. Detects `<style>`, `<link>`, `<svg>`, `<video>`, `<form>`, inline CSS properties, `@font-face`, `@media` queries, gradients, flexbox/grid, and more.
|
|
@@ -270,6 +323,50 @@ try {
|
|
|
270
323
|
|
|
271
324
|
---
|
|
272
325
|
|
|
326
|
+
## Performance
|
|
327
|
+
|
|
328
|
+
### Shared DOM parsing
|
|
329
|
+
|
|
330
|
+
The engine internally parses HTML using [Cheerio](https://cheerio.js.org/). For a typical 50–100KB email, each `cheerio.load()` call takes 5–15ms. Without optimization, calling multiple analysis functions on the same HTML would parse it repeatedly.
|
|
331
|
+
|
|
332
|
+
**`auditEmail()`** parses the HTML once and shares the DOM across all 5 analyzers (compatibility, spam, links, accessibility, images). Previously each analyzer parsed independently — this eliminates ~80% of parsing overhead in the audit path.
|
|
333
|
+
|
|
334
|
+
**`createSession()`** extends this optimization to any combination of calls. When you need to call `analyzeEmail()` + `analyzeSpam()` + `validateLinks()` + other checks on the same HTML, a session shares a single parse across all of them.
|
|
335
|
+
|
|
336
|
+
### Typical performance characteristics
|
|
337
|
+
|
|
338
|
+
| Operation | Complexity | Notes |
|
|
339
|
+
|---|---|---|
|
|
340
|
+
| `auditEmail()` | 1 parse + 5 analyses | Shared DOM, most efficient for full reports |
|
|
341
|
+
| `createSession()` | 1 parse upfront | Amortized across all subsequent analysis calls |
|
|
342
|
+
| `analyzeEmail()` | 1 parse + CSS property scan | Scans `<style>` blocks + inline styles × 12 clients |
|
|
343
|
+
| `transformForAllClients()` | 12 parses (1 per client) | Each client mutates its own DOM copy |
|
|
344
|
+
| `simulateDarkMode()` | 1 parse per call | Mutates DOM for color inversion |
|
|
345
|
+
|
|
346
|
+
### Optimization tips for consumers
|
|
347
|
+
|
|
348
|
+
```typescript
|
|
349
|
+
// Instead of this (6 separate HTML parses):
|
|
350
|
+
const warnings = analyzeEmail(html, "jsx");
|
|
351
|
+
const scores = generateCompatibilityScore(warnings);
|
|
352
|
+
const spam = analyzeSpam(html);
|
|
353
|
+
const links = validateLinks(html);
|
|
354
|
+
const a11y = checkAccessibility(html);
|
|
355
|
+
const images = analyzeImages(html);
|
|
356
|
+
|
|
357
|
+
// Do this (1 HTML parse):
|
|
358
|
+
const report = auditEmail(html, { framework: "jsx" });
|
|
359
|
+
|
|
360
|
+
// Or for selective analysis (1 HTML parse):
|
|
361
|
+
const session = createSession(html, { framework: "jsx" });
|
|
362
|
+
const warnings = session.analyze();
|
|
363
|
+
const scores = session.score(warnings);
|
|
364
|
+
const spam = session.analyzeSpam();
|
|
365
|
+
// ... pick only what you need
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
---
|
|
369
|
+
|
|
273
370
|
## Security Considerations
|
|
274
371
|
|
|
275
372
|
### Input Size Limits
|
|
@@ -376,6 +473,21 @@ interface AuditReport {
|
|
|
376
473
|
images: ImageReport;
|
|
377
474
|
}
|
|
378
475
|
|
|
476
|
+
interface EmailSession {
|
|
477
|
+
readonly html: string;
|
|
478
|
+
readonly framework: Framework | undefined;
|
|
479
|
+
audit(options?): AuditReport;
|
|
480
|
+
analyze(): CSSWarning[];
|
|
481
|
+
score(warnings): Record<string, ClientScore>;
|
|
482
|
+
analyzeSpam(options?): SpamReport;
|
|
483
|
+
validateLinks(): LinkReport;
|
|
484
|
+
checkAccessibility(): AccessibilityReport;
|
|
485
|
+
analyzeImages(): ImageReport;
|
|
486
|
+
transformForClient(clientId): TransformResult;
|
|
487
|
+
transformForAllClients(): TransformResult[];
|
|
488
|
+
simulateDarkMode(clientId): { html; warnings };
|
|
489
|
+
}
|
|
490
|
+
|
|
379
491
|
interface SpamReport {
|
|
380
492
|
score: number; // 0–100 (100 = clean)
|
|
381
493
|
level: "low" | "medium" | "high";
|
|
@@ -407,7 +519,7 @@ interface ImageReport {
|
|
|
407
519
|
bun test
|
|
408
520
|
```
|
|
409
521
|
|
|
410
|
-
|
|
522
|
+
467 tests covering analysis, transformation, dark mode simulation, framework-aware fixes, AI fix generation, token estimation, spam scoring, link validation, accessibility checking, image analysis, session API, security hardening, integration pipelines, and accuracy benchmarks.
|
|
411
523
|
|
|
412
524
|
## License
|
|
413
525
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
CompileError
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-ZQF2XUIJ.js";
|
|
4
4
|
|
|
5
5
|
// src/compile/mjml.ts
|
|
6
6
|
var MAX_SOURCE_SIZE = 512e3;
|
|
@@ -61,4 +61,4 @@ async function compileMjml(source) {
|
|
|
61
61
|
export {
|
|
62
62
|
compileMjml
|
|
63
63
|
};
|
|
64
|
-
//# sourceMappingURL=chunk-
|
|
64
|
+
//# sourceMappingURL=chunk-3NDQOTPM.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
CompileError
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-ZQF2XUIJ.js";
|
|
4
4
|
|
|
5
5
|
// src/compile/maizzle.ts
|
|
6
6
|
var MAX_SOURCE_SIZE = 512e3;
|
|
@@ -75,4 +75,4 @@ async function compileMaizzle(source) {
|
|
|
75
75
|
export {
|
|
76
76
|
compileMaizzle
|
|
77
77
|
};
|
|
78
|
-
//# sourceMappingURL=chunk-
|
|
78
|
+
//# sourceMappingURL=chunk-OY3DGZVU.js.map
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
|
-
CompileError
|
|
3
|
-
|
|
4
|
-
} from "./chunk-PFONR3YC.js";
|
|
2
|
+
CompileError
|
|
3
|
+
} from "./chunk-ZQF2XUIJ.js";
|
|
5
4
|
|
|
6
5
|
// src/compile/react-email.ts
|
|
7
6
|
var MAX_SOURCE_SIZE = 256e3;
|
|
@@ -80,7 +79,7 @@ async function compileReactEmail(source, options) {
|
|
|
80
79
|
moduleExports = await executeInQuickJs(transpiledCode, React, ReactEmailComponents);
|
|
81
80
|
break;
|
|
82
81
|
case "vm":
|
|
83
|
-
moduleExports = executeInVm(transpiledCode, React, ReactEmailComponents);
|
|
82
|
+
moduleExports = await executeInVm(transpiledCode, React, ReactEmailComponents);
|
|
84
83
|
break;
|
|
85
84
|
default:
|
|
86
85
|
throw new CompileError(
|
|
@@ -111,8 +110,8 @@ async function compileReactEmail(source, options) {
|
|
|
111
110
|
throw new CompileError(`React rendering error: ${message}`, "jsx", "render");
|
|
112
111
|
}
|
|
113
112
|
}
|
|
114
|
-
function executeInVm(code, React, ReactEmailComponents) {
|
|
115
|
-
const { createContext, Script } =
|
|
113
|
+
async function executeInVm(code, React, ReactEmailComponents) {
|
|
114
|
+
const { createContext, Script } = await import("vm");
|
|
116
115
|
const ALLOWED_MODULES = {
|
|
117
116
|
react: React,
|
|
118
117
|
"@react-email/components": ReactEmailComponents
|
|
@@ -199,10 +198,11 @@ function executeInVm(code, React, ReactEmailComponents) {
|
|
|
199
198
|
return moduleObj.exports;
|
|
200
199
|
}
|
|
201
200
|
async function executeInIsolatedVm(code, React, ReactEmailComponents) {
|
|
202
|
-
var _a;
|
|
201
|
+
var _a, _b;
|
|
203
202
|
let ivm;
|
|
204
203
|
try {
|
|
205
|
-
|
|
204
|
+
const ivmMod = await import("isolated-vm");
|
|
205
|
+
ivm = (_a = ivmMod.default) != null ? _a : ivmMod;
|
|
206
206
|
} catch (e) {
|
|
207
207
|
throw new CompileError(
|
|
208
208
|
'Sandbox strategy "isolated-vm" requires the "isolated-vm" package. Install it:\n npm install isolated-vm\nOr use sandbox: "vm" for a lighter (but less secure) alternative.',
|
|
@@ -217,15 +217,25 @@ async function executeInIsolatedVm(code, React, ReactEmailComponents) {
|
|
|
217
217
|
(function() {
|
|
218
218
|
var module = { exports: {} };
|
|
219
219
|
var exports = module.exports;
|
|
220
|
-
var
|
|
221
|
-
|
|
220
|
+
var noop = function() { return {}; };
|
|
221
|
+
var React = new Proxy({
|
|
222
|
+
createElement: noop,
|
|
222
223
|
forwardRef: function(fn) { return fn; },
|
|
223
224
|
Fragment: "Fragment",
|
|
224
|
-
|
|
225
|
+
createContext: function() { return { Provider: noop, Consumer: noop }; },
|
|
226
|
+
useState: function(v) { return [v, noop]; },
|
|
227
|
+
useRef: function() { return { current: null }; },
|
|
228
|
+
useEffect: noop,
|
|
229
|
+
useMemo: function(fn) { return fn(); },
|
|
230
|
+
useCallback: function(fn) { return fn; },
|
|
231
|
+
Children: { map: noop, forEach: noop, toArray: function() { return []; } },
|
|
232
|
+
}, { get: function(t, p) { return p in t ? t[p] : noop; } });
|
|
233
|
+
var componentsProxy = new Proxy({}, {
|
|
234
|
+
get: function() { return noop; }
|
|
235
|
+
});
|
|
225
236
|
function require(name) {
|
|
226
|
-
if (name === "react"
|
|
227
|
-
|
|
228
|
-
}
|
|
237
|
+
if (name === "react") return React;
|
|
238
|
+
if (name === "@react-email/components") return componentsProxy;
|
|
229
239
|
throw new Error('Import of "' + name + '" is not allowed. Only "react" and "@react-email/components" can be imported.');
|
|
230
240
|
}
|
|
231
241
|
try {
|
|
@@ -244,7 +254,7 @@ async function executeInIsolatedVm(code, React, ReactEmailComponents) {
|
|
|
244
254
|
const parsed = JSON.parse(result);
|
|
245
255
|
if (!parsed.ok) {
|
|
246
256
|
throw new CompileError(
|
|
247
|
-
`JSX execution error: ${(
|
|
257
|
+
`JSX execution error: ${(_b = parsed.error) != null ? _b : "Unknown error"}`,
|
|
248
258
|
"jsx",
|
|
249
259
|
"execution"
|
|
250
260
|
);
|
|
@@ -282,11 +292,29 @@ async function executeInQuickJs(code, React, ReactEmailComponents) {
|
|
|
282
292
|
(function() {
|
|
283
293
|
var module = { exports: {} };
|
|
284
294
|
var exports = module.exports;
|
|
285
|
-
var
|
|
295
|
+
var noop = function() { return {}; };
|
|
296
|
+
var React = {
|
|
297
|
+
createElement: noop,
|
|
298
|
+
forwardRef: function(fn) { return fn; },
|
|
299
|
+
Fragment: "Fragment",
|
|
300
|
+
createContext: function() { return { Provider: noop, Consumer: noop }; },
|
|
301
|
+
useState: function(v) { return [v, noop]; },
|
|
302
|
+
useRef: function() { return { current: null }; },
|
|
303
|
+
useEffect: noop,
|
|
304
|
+
useMemo: function(fn) { return fn(); },
|
|
305
|
+
useCallback: function(fn) { return fn; },
|
|
306
|
+
Children: { map: noop, forEach: noop, toArray: function() { return []; } },
|
|
307
|
+
};
|
|
308
|
+
var components = {};
|
|
309
|
+
var names = [
|
|
310
|
+
"Html","Head","Body","Container","Section","Row","Column","Text",
|
|
311
|
+
"Link","Button","Img","Hr","Preview","Heading","Font","Style",
|
|
312
|
+
"CodeBlock","CodeInline","Markdown","Tailwind","Responsive",
|
|
313
|
+
];
|
|
314
|
+
for (var i = 0; i < names.length; i++) components[names[i]] = noop;
|
|
286
315
|
function require(name) {
|
|
287
|
-
if (name === "react"
|
|
288
|
-
|
|
289
|
-
}
|
|
316
|
+
if (name === "react") return React;
|
|
317
|
+
if (name === "@react-email/components") return components;
|
|
290
318
|
throw new Error('Import of "' + name + '" is not allowed.');
|
|
291
319
|
}
|
|
292
320
|
try {
|
|
@@ -328,4 +356,4 @@ async function executeInQuickJs(code, React, ReactEmailComponents) {
|
|
|
328
356
|
export {
|
|
329
357
|
compileReactEmail
|
|
330
358
|
};
|
|
331
|
-
//# sourceMappingURL=chunk-
|
|
359
|
+
//# sourceMappingURL=chunk-URZ4XPST.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/compile/react-email.ts"],"sourcesContent":["import { CompileError } from \"./errors.js\";\r\n\r\n/** Maximum source code size: 256KB */\r\nconst MAX_SOURCE_SIZE = 256_000;\r\n\r\n/** Execution timeout: 5 seconds */\r\nconst EXECUTION_TIMEOUT_MS = 5_000;\r\n\r\n/**\r\n * Sandbox strategy for JSX execution.\r\n *\r\n * - `\"vm\"` — `node:vm` with hardened globals. Fast, zero-dependency,\r\n * but NOT a true security boundary (prototype-chain escapes are possible).\r\n * Suitable for CLI / local use where users run their own code.\r\n *\r\n * - `\"isolated-vm\"` (default) — Separate V8 isolate via the `isolated-vm`\r\n * npm package. True heap isolation; escapes require a V8 engine bug.\r\n * Requires `isolated-vm` to be installed (native addon).\r\n *\r\n * - `\"quickjs\"` — Validates code structure in a QuickJS WASM sandbox, then\r\n * executes in `node:vm` for React rendering. Security is equivalent to\r\n * `node:vm` — the QuickJS phase validates import restrictions only.\r\n * No native addons needed, but only supports ES2020 and is slower.\r\n * For true isolation on servers, use `isolated-vm`.\r\n */\r\nexport type SandboxStrategy = \"vm\" | \"isolated-vm\" | \"quickjs\";\r\n\r\nexport interface CompileReactEmailOptions {\r\n /**\r\n * Sandbox strategy to use for executing user JSX code.\r\n * @default \"isolated-vm\"\r\n */\r\n sandbox?: SandboxStrategy;\r\n}\r\n\r\n/**\r\n * Compile a React Email JSX/TSX source string into an HTML email string.\r\n *\r\n * Pipeline:\r\n * 1. Validate input (size, basic checks)\r\n * 2. Transpile JSX/TSX → CommonJS JS using sucrase\r\n * 3. Execute inside a sandbox (configurable strategy)\r\n * 4. Render the exported component to a full HTML email string\r\n *\r\n * Requires peer dependencies: sucrase, react, @react-email/components,\r\n * @react-email/render. Additionally:\r\n * - sandbox \"isolated-vm\" requires `isolated-vm`\r\n * - sandbox \"quickjs\" requires `quickjs-emscripten`\r\n */\r\nexport async function compileReactEmail(\r\n source: string,\r\n options?: CompileReactEmailOptions,\r\n): Promise<string> {\r\n const strategy = options?.sandbox ?? \"isolated-vm\";\r\n\r\n // ── 1. Validate ──────────────────────────────────────────────────────\r\n if (!source || !source.trim()) {\r\n throw new CompileError(\"JSX source must not be empty.\", \"jsx\", \"validation\");\r\n }\r\n\r\n if (source.length > MAX_SOURCE_SIZE) {\r\n throw new CompileError(\r\n `JSX source exceeds ${MAX_SOURCE_SIZE / 1000}KB limit.`,\r\n \"jsx\",\r\n \"validation\",\r\n );\r\n }\r\n\r\n // ── 2. Load peer dependencies ────────────────────────────────────────\r\n let transform: typeof import(\"sucrase\").transform;\r\n let React: typeof import(\"react\");\r\n let ReactEmailComponents: typeof import(\"@react-email/components\");\r\n let render: typeof import(\"@react-email/render\").render;\r\n\r\n try {\r\n ({ transform } = await import(\"sucrase\"));\r\n } catch {\r\n throw new CompileError(\r\n 'JSX compilation requires \"sucrase\". Install it:\\n npm install sucrase',\r\n \"jsx\",\r\n \"transpile\",\r\n );\r\n }\r\n\r\n try {\r\n React = await import(\"react\");\r\n } catch {\r\n throw new CompileError(\r\n 'JSX compilation requires \"react\". Install it:\\n npm install react',\r\n \"jsx\",\r\n \"transpile\",\r\n );\r\n }\r\n\r\n try {\r\n ReactEmailComponents = await import(\"@react-email/components\");\r\n } catch {\r\n throw new CompileError(\r\n 'JSX compilation requires \"@react-email/components\". Install it:\\n npm install @react-email/components',\r\n \"jsx\",\r\n \"transpile\",\r\n );\r\n }\r\n\r\n try {\r\n ({ render } = await import(\"@react-email/render\"));\r\n } catch {\r\n throw new CompileError(\r\n 'JSX compilation requires \"@react-email/render\". Install it:\\n npm install @react-email/render',\r\n \"jsx\",\r\n \"transpile\",\r\n );\r\n }\r\n\r\n // ── 3. Transpile JSX/TSX → CommonJS ──────────────────────────────────\r\n let transpiledCode: string;\r\n try {\r\n const result = transform(source, {\r\n transforms: [\"typescript\", \"jsx\", \"imports\"],\r\n jsxRuntime: \"classic\",\r\n production: true,\r\n });\r\n transpiledCode = result.code;\r\n } catch (err: unknown) {\r\n const message = err instanceof Error ? err.message : \"Unknown transpilation error\";\r\n throw new CompileError(`JSX syntax error: ${message}`, \"jsx\", \"transpile\");\r\n }\r\n\r\n // ── 4. Execute in sandbox ────────────────────────────────────────────\r\n let moduleExports: Record<string, unknown>;\r\n\r\n switch (strategy) {\r\n case \"isolated-vm\":\r\n moduleExports = await executeInIsolatedVm(transpiledCode, React, ReactEmailComponents);\r\n break;\r\n case \"quickjs\":\r\n moduleExports = await executeInQuickJs(transpiledCode, React, ReactEmailComponents);\r\n break;\r\n case \"vm\":\r\n moduleExports = await executeInVm(transpiledCode, React, ReactEmailComponents);\r\n break;\r\n default:\r\n throw new CompileError(\r\n `Unknown sandbox strategy: \"${strategy}\". Use \"vm\", \"isolated-vm\", or \"quickjs\".`,\r\n \"jsx\",\r\n \"execution\",\r\n );\r\n }\r\n\r\n // ── 5. Extract component and render ──────────────────────────────────\r\n let Component: unknown = moduleExports.default ?? moduleExports;\r\n\r\n if (typeof Component !== \"function\" && typeof Component === \"object\" && Component !== null) {\r\n const values = Object.values(Component as Record<string, unknown>);\r\n const fn = values.find((v) => typeof v === \"function\");\r\n if (fn) Component = fn;\r\n }\r\n\r\n if (typeof Component !== \"function\") {\r\n throw new CompileError(\r\n 'The JSX source must export a React component function. ' +\r\n 'Use \"export default function Email() { ... }\" or ' +\r\n '\"export function Email() { ... }\".',\r\n \"jsx\",\r\n \"execution\",\r\n );\r\n }\r\n\r\n try {\r\n const element = React.createElement(Component as React.FC);\r\n const html = await render(element);\r\n return html;\r\n } catch (err: unknown) {\r\n const message = err instanceof Error ? err.message : \"Unknown rendering error\";\r\n throw new CompileError(`React rendering error: ${message}`, \"jsx\", \"render\");\r\n }\r\n}\r\n\r\n// ─── Sandbox: node:vm ──────────────────────────────────────────────────────\r\n\r\n/**\r\n * Execute transpiled code in a node:vm context with hardened globals.\r\n *\r\n * NOT a security boundary — see node:vm documentation. Suitable for CLI\r\n * use where the user runs their own code. For server use, prefer\r\n * \"isolated-vm\" or \"quickjs\".\r\n */\r\nasync function executeInVm(\r\n code: string,\r\n React: typeof import(\"react\"),\r\n ReactEmailComponents: typeof import(\"@react-email/components\"),\r\n): Promise<Record<string, unknown>> {\r\n const { createContext, Script } = await import(\"node:vm\");\r\n\r\n const ALLOWED_MODULES: Record<string, unknown> = {\r\n react: React,\r\n \"@react-email/components\": ReactEmailComponents,\r\n };\r\n\r\n const moduleExports: Record<string, unknown> = {};\r\n const moduleObj = { exports: moduleExports };\r\n\r\n const mockRequire = (moduleName: string): unknown => {\r\n if (moduleName in ALLOWED_MODULES) {\r\n return ALLOWED_MODULES[moduleName];\r\n }\r\n throw new Error(\r\n `Import of \"${moduleName}\" is not allowed. ` +\r\n `Only \"react\" and \"@react-email/components\" can be imported.`,\r\n );\r\n };\r\n\r\n const sandbox: Record<string, unknown> = {\r\n module: moduleObj,\r\n exports: moduleExports,\r\n require: mockRequire,\r\n React,\r\n Object, Array, String, Number, Boolean,\r\n Map, Set, WeakMap, WeakSet,\r\n JSON, Math, Date, RegExp,\r\n Error, TypeError, RangeError, ReferenceError, SyntaxError, URIError,\r\n Promise, Symbol,\r\n Proxy: undefined, Reflect: undefined,\r\n parseInt, parseFloat, isNaN, isFinite,\r\n encodeURIComponent, decodeURIComponent, encodeURI, decodeURI,\r\n undefined, NaN, Infinity,\r\n console: { log: () => {}, warn: () => {}, error: () => {}, info: () => {}, debug: () => {} },\r\n setTimeout: undefined, setInterval: undefined, setImmediate: undefined, queueMicrotask: undefined,\r\n process: undefined, globalThis: undefined, global: undefined, Buffer: undefined,\r\n __dirname: undefined, __filename: undefined,\r\n };\r\n\r\n const context = createContext(sandbox, {\r\n codeGeneration: { strings: false, wasm: false },\r\n });\r\n\r\n try {\r\n const script = new Script(code, { filename: \"user-email-component.tsx\" });\r\n script.runInContext(context, { timeout: EXECUTION_TIMEOUT_MS, displayErrors: true });\r\n } catch (err: unknown) {\r\n const message = err instanceof Error ? err.message : \"Unknown execution error\";\r\n if (message.includes(\"Script execution timed out\")) {\r\n throw new CompileError(\"JSX execution timed out (possible infinite loop).\", \"jsx\", \"execution\");\r\n }\r\n throw new CompileError(`JSX execution error: ${message}`, \"jsx\", \"execution\");\r\n }\r\n\r\n return moduleObj.exports as Record<string, unknown>;\r\n}\r\n\r\n// ─── Sandbox: isolated-vm ──────────────────────────────────────────────────\r\n\r\n/**\r\n * Validate code in a separate V8 isolate, then execute in `node:vm`.\r\n *\r\n * Two-phase approach:\r\n * 1. **Validate** — run the transpiled code in a true V8 isolate with stub\r\n * React/component implementations. This catches disallowed imports and\r\n * structural errors inside a genuine security boundary (separate heap,\r\n * 128 MB memory cap, timeout). Escape requires a V8 engine bug.\r\n * 2. **Execute** — run the validated code in `node:vm` with real React\r\n * objects for actual rendering.\r\n *\r\n * Why two phases: React's internal type system uses Symbols\r\n * (`Symbol(react.forward_ref)`, `Symbol(react.element)`, etc.) which cannot\r\n * be transferred across V8 isolate boundaries — the structured clone\r\n * algorithm does not support Symbols. Running React code directly inside\r\n * `isolated-vm` is not possible.\r\n */\r\nasync function executeInIsolatedVm(\r\n code: string,\r\n React: typeof import(\"react\"),\r\n ReactEmailComponents: typeof import(\"@react-email/components\"),\r\n): Promise<Record<string, unknown>> {\r\n let ivm: typeof import(\"isolated-vm\");\r\n try {\r\n const ivmMod = await import(\"isolated-vm\");\r\n ivm = (ivmMod as any).default ?? ivmMod;\r\n } catch {\r\n throw new CompileError(\r\n 'Sandbox strategy \"isolated-vm\" requires the \"isolated-vm\" package. Install it:\\n' +\r\n \" npm install isolated-vm\\n\" +\r\n 'Or use sandbox: \"vm\" for a lighter (but less secure) alternative.',\r\n \"jsx\",\r\n \"execution\",\r\n );\r\n }\r\n\r\n // ── Phase 1: Validate in a true V8 isolate ─────────────────────────────\r\n const isolate = new ivm.Isolate({ memoryLimit: 128 });\r\n try {\r\n const ivmContext = await isolate.createContext();\r\n\r\n // Stub implementations let the code parse and execute structurally\r\n // without needing real React objects (which contain non-cloneable Symbols).\r\n // Stub React: createElement returns a plain object, forwardRef passes\r\n // through, and any unknown property returns a no-op function. The Proxy\r\n // on the components module ensures that any named import (Html, Head,\r\n // Button, etc.) resolves to a dummy component function so the transpiled\r\n // code can execute structurally without real React objects.\r\n const validationCode = `\r\n (function() {\r\n var module = { exports: {} };\r\n var exports = module.exports;\r\n var noop = function() { return {}; };\r\n var React = new Proxy({\r\n createElement: noop,\r\n forwardRef: function(fn) { return fn; },\r\n Fragment: \"Fragment\",\r\n createContext: function() { return { Provider: noop, Consumer: noop }; },\r\n useState: function(v) { return [v, noop]; },\r\n useRef: function() { return { current: null }; },\r\n useEffect: noop,\r\n useMemo: function(fn) { return fn(); },\r\n useCallback: function(fn) { return fn; },\r\n Children: { map: noop, forEach: noop, toArray: function() { return []; } },\r\n }, { get: function(t, p) { return p in t ? t[p] : noop; } });\r\n var componentsProxy = new Proxy({}, {\r\n get: function() { return noop; }\r\n });\r\n function require(name) {\r\n if (name === \"react\") return React;\r\n if (name === \"@react-email/components\") return componentsProxy;\r\n throw new Error('Import of \"' + name + '\" is not allowed. Only \"react\" and \"@react-email/components\" can be imported.');\r\n }\r\n try {\r\n ${code}\r\n return JSON.stringify({ ok: true });\r\n } catch(e) {\r\n return JSON.stringify({ ok: false, error: e.message || \"Unknown error\" });\r\n }\r\n })()\r\n `;\r\n\r\n try {\r\n const result = await ivmContext.eval(validationCode, {\r\n timeout: EXECUTION_TIMEOUT_MS,\r\n });\r\n\r\n if (typeof result === \"string\") {\r\n const parsed = JSON.parse(result) as { ok: boolean; error?: string };\r\n if (!parsed.ok) {\r\n throw new CompileError(\r\n `JSX execution error: ${parsed.error ?? \"Unknown error\"}`,\r\n \"jsx\",\r\n \"execution\",\r\n );\r\n }\r\n }\r\n } catch (err: unknown) {\r\n if (err instanceof CompileError) throw err;\r\n const message = err instanceof Error ? err.message : \"Unknown execution error\";\r\n if (message.includes(\"timed out\") || message.includes(\"Timeout\")) {\r\n throw new CompileError(\"JSX execution timed out (possible infinite loop).\", \"jsx\", \"execution\");\r\n }\r\n throw new CompileError(`JSX execution error: ${message}`, \"jsx\", \"execution\");\r\n }\r\n } finally {\r\n isolate.dispose();\r\n }\r\n\r\n // ── Phase 2: Execute validated code in node:vm with real React ──────────\r\n return executeInVm(code, React, ReactEmailComponents);\r\n}\r\n\r\n// ─── Sandbox: QuickJS (WASM) ──────────────────────────────────────────────\r\n\r\n/**\r\n * Validate code structure in QuickJS WASM, then execute in `node:vm`.\r\n *\r\n * Two-phase approach:\r\n * 1. Validate that the code doesn't access disallowed modules by running it\r\n * in a QuickJS WASM sandbox with stub implementations.\r\n * 2. Execute in `node:vm` for actual React rendering (React objects can't\r\n * cross the WASM boundary).\r\n *\r\n * **Security note:** The actual execution happens in `node:vm`, so runtime\r\n * security is equivalent to the `\"vm\"` strategy. The QuickJS phase only\r\n * validates import restrictions. For true isolation on servers, use\r\n * `\"isolated-vm\"`.\r\n */\r\nasync function executeInQuickJs(\r\n code: string,\r\n React: typeof import(\"react\"),\r\n ReactEmailComponents: typeof import(\"@react-email/components\"),\r\n): Promise<Record<string, unknown>> {\r\n let getQuickJS: typeof import(\"quickjs-emscripten\").getQuickJS;\r\n try {\r\n ({ getQuickJS } = await import(\"quickjs-emscripten\"));\r\n } catch {\r\n throw new CompileError(\r\n 'Sandbox strategy \"quickjs\" requires the \"quickjs-emscripten\" package. Install it:\\n' +\r\n \" npm install quickjs-emscripten\\n\" +\r\n 'Or use sandbox: \"vm\" for a lighter alternative.',\r\n \"jsx\",\r\n \"execution\",\r\n );\r\n }\r\n\r\n const QuickJS = await getQuickJS();\r\n const vm = QuickJS.newContext();\r\n\r\n try {\r\n // Phase 1: Validate code safety in the WASM sandbox.\r\n // We provide stub implementations of React and the module system so\r\n // the code can execute without errors, but we only care that it\r\n // doesn't try to access anything dangerous.\r\n // QuickJS (ES2020) does not support Proxy, so we enumerate known\r\n // React Email component names as stub functions instead.\r\n const validationCode = `\r\n (function() {\r\n var module = { exports: {} };\r\n var exports = module.exports;\r\n var noop = function() { return {}; };\r\n var React = {\r\n createElement: noop,\r\n forwardRef: function(fn) { return fn; },\r\n Fragment: \"Fragment\",\r\n createContext: function() { return { Provider: noop, Consumer: noop }; },\r\n useState: function(v) { return [v, noop]; },\r\n useRef: function() { return { current: null }; },\r\n useEffect: noop,\r\n useMemo: function(fn) { return fn(); },\r\n useCallback: function(fn) { return fn; },\r\n Children: { map: noop, forEach: noop, toArray: function() { return []; } },\r\n };\r\n var components = {};\r\n var names = [\r\n \"Html\",\"Head\",\"Body\",\"Container\",\"Section\",\"Row\",\"Column\",\"Text\",\r\n \"Link\",\"Button\",\"Img\",\"Hr\",\"Preview\",\"Heading\",\"Font\",\"Style\",\r\n \"CodeBlock\",\"CodeInline\",\"Markdown\",\"Tailwind\",\"Responsive\",\r\n ];\r\n for (var i = 0; i < names.length; i++) components[names[i]] = noop;\r\n function require(name) {\r\n if (name === \"react\") return React;\r\n if (name === \"@react-email/components\") return components;\r\n throw new Error('Import of \"' + name + '\" is not allowed.');\r\n }\r\n try {\r\n ${code}\r\n return JSON.stringify({ ok: true });\r\n } catch(e) {\r\n return JSON.stringify({ ok: false, error: e.message || \"Unknown error\" });\r\n }\r\n })()\r\n `;\r\n\r\n const result = vm.evalCode(validationCode);\r\n if (result.error) {\r\n const errorVal = vm.dump(result.error);\r\n result.error.dispose();\r\n throw new CompileError(\r\n `JSX execution error: ${typeof errorVal === \"string\" ? errorVal : \"QuickJS execution failed\"}`,\r\n \"jsx\",\r\n \"execution\",\r\n );\r\n }\r\n\r\n const resultStr = vm.dump(result.value);\r\n result.value.dispose();\r\n\r\n if (typeof resultStr === \"string\") {\r\n const parsed = JSON.parse(resultStr) as { ok: boolean; error?: string };\r\n if (!parsed.ok) {\r\n throw new CompileError(\r\n `JSX execution error: ${parsed.error ?? \"Unknown error\"}`,\r\n \"jsx\",\r\n \"execution\",\r\n );\r\n }\r\n }\r\n\r\n // Phase 2: Code validated as safe — execute in node:vm for actual\r\n // React rendering (React objects can't cross the WASM boundary)\r\n return executeInVm(code, React, ReactEmailComponents);\r\n } finally {\r\n vm.dispose();\r\n }\r\n}\r\n"],"mappings":"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","names":[]}
|
|
@@ -17,12 +17,6 @@ var __spreadValues = (a, b) => {
|
|
|
17
17
|
return a;
|
|
18
18
|
};
|
|
19
19
|
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
20
|
-
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
21
|
-
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
22
|
-
}) : x)(function(x) {
|
|
23
|
-
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
24
|
-
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
25
|
-
});
|
|
26
20
|
var __objRest = (source, exclude) => {
|
|
27
21
|
var target = {};
|
|
28
22
|
for (var prop in source)
|
|
@@ -49,8 +43,7 @@ var CompileError = class extends Error {
|
|
|
49
43
|
export {
|
|
50
44
|
__spreadValues,
|
|
51
45
|
__spreadProps,
|
|
52
|
-
__require,
|
|
53
46
|
__objRest,
|
|
54
47
|
CompileError
|
|
55
48
|
};
|
|
56
|
-
//# sourceMappingURL=chunk-
|
|
49
|
+
//# sourceMappingURL=chunk-ZQF2XUIJ.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/compile/errors.ts"],"sourcesContent":["import type { InputFormat } from \"../types.js\";\r\n\r\n/**\r\n * Unified error class for all email compilation failures.\r\n *\r\n * Replaces the per-format error classes (ReactEmailCompileError,\r\n * MjmlCompileError, MaizzleCompileError) with a single class that\r\n * carries the source format and failure phase.\r\n */\r\nexport class CompileError extends Error {\r\n override name = \"CompileError\";\r\n readonly format: Exclude<InputFormat, \"html\">;\r\n readonly phase: \"validation\" | \"transpile\" | \"execution\" | \"render\" | \"compile\";\r\n\r\n constructor(\r\n message: string,\r\n format: CompileError[\"format\"],\r\n phase: CompileError[\"phase\"],\r\n ) {\r\n super(message);\r\n this.format = format;\r\n this.phase = phase;\r\n }\r\n}\r\n"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../src/compile/errors.ts"],"sourcesContent":["import type { InputFormat } from \"../types.js\";\r\n\r\n/**\r\n * Unified error class for all email compilation failures.\r\n *\r\n * Replaces the per-format error classes (ReactEmailCompileError,\r\n * MjmlCompileError, MaizzleCompileError) with a single class that\r\n * carries the source format and failure phase.\r\n */\r\nexport class CompileError extends Error {\r\n override name = \"CompileError\";\r\n readonly format: Exclude<InputFormat, \"html\">;\r\n readonly phase: \"validation\" | \"transpile\" | \"execution\" | \"render\" | \"compile\";\r\n\r\n constructor(\r\n message: string,\r\n format: CompileError[\"format\"],\r\n phase: CompileError[\"phase\"],\r\n ) {\r\n super(message);\r\n this.format = format;\r\n this.phase = phase;\r\n }\r\n}\r\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AASO,IAAM,eAAN,cAA2B,MAAM;AAAA,EAKtC,YACE,SACA,QACA,OACA;AACA,UAAM,OAAO;AATf,SAAS,OAAO;AAUd,SAAK,SAAS;AACd,SAAK,QAAQ;AAAA,EACf;AACF;","names":[]}
|
package/dist/compile/index.cjs
CHANGED
|
@@ -125,7 +125,7 @@ async function compileReactEmail(source, options) {
|
|
|
125
125
|
moduleExports = await executeInQuickJs(transpiledCode, React, ReactEmailComponents);
|
|
126
126
|
break;
|
|
127
127
|
case "vm":
|
|
128
|
-
moduleExports = executeInVm(transpiledCode, React, ReactEmailComponents);
|
|
128
|
+
moduleExports = await executeInVm(transpiledCode, React, ReactEmailComponents);
|
|
129
129
|
break;
|
|
130
130
|
default:
|
|
131
131
|
throw new CompileError(
|
|
@@ -156,8 +156,8 @@ async function compileReactEmail(source, options) {
|
|
|
156
156
|
throw new CompileError(`React rendering error: ${message}`, "jsx", "render");
|
|
157
157
|
}
|
|
158
158
|
}
|
|
159
|
-
function executeInVm(code, React, ReactEmailComponents) {
|
|
160
|
-
const { createContext, Script } =
|
|
159
|
+
async function executeInVm(code, React, ReactEmailComponents) {
|
|
160
|
+
const { createContext, Script } = await import("vm");
|
|
161
161
|
const ALLOWED_MODULES = {
|
|
162
162
|
react: React,
|
|
163
163
|
"@react-email/components": ReactEmailComponents
|
|
@@ -244,10 +244,11 @@ function executeInVm(code, React, ReactEmailComponents) {
|
|
|
244
244
|
return moduleObj.exports;
|
|
245
245
|
}
|
|
246
246
|
async function executeInIsolatedVm(code, React, ReactEmailComponents) {
|
|
247
|
-
var _a;
|
|
247
|
+
var _a, _b;
|
|
248
248
|
let ivm;
|
|
249
249
|
try {
|
|
250
|
-
|
|
250
|
+
const ivmMod = await import("isolated-vm");
|
|
251
|
+
ivm = (_a = ivmMod.default) != null ? _a : ivmMod;
|
|
251
252
|
} catch (e) {
|
|
252
253
|
throw new CompileError(
|
|
253
254
|
'Sandbox strategy "isolated-vm" requires the "isolated-vm" package. Install it:\n npm install isolated-vm\nOr use sandbox: "vm" for a lighter (but less secure) alternative.',
|
|
@@ -262,15 +263,25 @@ async function executeInIsolatedVm(code, React, ReactEmailComponents) {
|
|
|
262
263
|
(function() {
|
|
263
264
|
var module = { exports: {} };
|
|
264
265
|
var exports = module.exports;
|
|
265
|
-
var
|
|
266
|
-
|
|
266
|
+
var noop = function() { return {}; };
|
|
267
|
+
var React = new Proxy({
|
|
268
|
+
createElement: noop,
|
|
267
269
|
forwardRef: function(fn) { return fn; },
|
|
268
270
|
Fragment: "Fragment",
|
|
269
|
-
|
|
271
|
+
createContext: function() { return { Provider: noop, Consumer: noop }; },
|
|
272
|
+
useState: function(v) { return [v, noop]; },
|
|
273
|
+
useRef: function() { return { current: null }; },
|
|
274
|
+
useEffect: noop,
|
|
275
|
+
useMemo: function(fn) { return fn(); },
|
|
276
|
+
useCallback: function(fn) { return fn; },
|
|
277
|
+
Children: { map: noop, forEach: noop, toArray: function() { return []; } },
|
|
278
|
+
}, { get: function(t, p) { return p in t ? t[p] : noop; } });
|
|
279
|
+
var componentsProxy = new Proxy({}, {
|
|
280
|
+
get: function() { return noop; }
|
|
281
|
+
});
|
|
270
282
|
function require(name) {
|
|
271
|
-
if (name === "react"
|
|
272
|
-
|
|
273
|
-
}
|
|
283
|
+
if (name === "react") return React;
|
|
284
|
+
if (name === "@react-email/components") return componentsProxy;
|
|
274
285
|
throw new Error('Import of "' + name + '" is not allowed. Only "react" and "@react-email/components" can be imported.');
|
|
275
286
|
}
|
|
276
287
|
try {
|
|
@@ -289,7 +300,7 @@ async function executeInIsolatedVm(code, React, ReactEmailComponents) {
|
|
|
289
300
|
const parsed = JSON.parse(result);
|
|
290
301
|
if (!parsed.ok) {
|
|
291
302
|
throw new CompileError(
|
|
292
|
-
`JSX execution error: ${(
|
|
303
|
+
`JSX execution error: ${(_b = parsed.error) != null ? _b : "Unknown error"}`,
|
|
293
304
|
"jsx",
|
|
294
305
|
"execution"
|
|
295
306
|
);
|
|
@@ -327,11 +338,29 @@ async function executeInQuickJs(code, React, ReactEmailComponents) {
|
|
|
327
338
|
(function() {
|
|
328
339
|
var module = { exports: {} };
|
|
329
340
|
var exports = module.exports;
|
|
330
|
-
var
|
|
341
|
+
var noop = function() { return {}; };
|
|
342
|
+
var React = {
|
|
343
|
+
createElement: noop,
|
|
344
|
+
forwardRef: function(fn) { return fn; },
|
|
345
|
+
Fragment: "Fragment",
|
|
346
|
+
createContext: function() { return { Provider: noop, Consumer: noop }; },
|
|
347
|
+
useState: function(v) { return [v, noop]; },
|
|
348
|
+
useRef: function() { return { current: null }; },
|
|
349
|
+
useEffect: noop,
|
|
350
|
+
useMemo: function(fn) { return fn(); },
|
|
351
|
+
useCallback: function(fn) { return fn; },
|
|
352
|
+
Children: { map: noop, forEach: noop, toArray: function() { return []; } },
|
|
353
|
+
};
|
|
354
|
+
var components = {};
|
|
355
|
+
var names = [
|
|
356
|
+
"Html","Head","Body","Container","Section","Row","Column","Text",
|
|
357
|
+
"Link","Button","Img","Hr","Preview","Heading","Font","Style",
|
|
358
|
+
"CodeBlock","CodeInline","Markdown","Tailwind","Responsive",
|
|
359
|
+
];
|
|
360
|
+
for (var i = 0; i < names.length; i++) components[names[i]] = noop;
|
|
331
361
|
function require(name) {
|
|
332
|
-
if (name === "react"
|
|
333
|
-
|
|
334
|
-
}
|
|
362
|
+
if (name === "react") return React;
|
|
363
|
+
if (name === "@react-email/components") return components;
|
|
335
364
|
throw new Error('Import of "' + name + '" is not allowed.');
|
|
336
365
|
}
|
|
337
366
|
try {
|