@raypx-kit/email-preview 0.0.0-alpha.0 → 0.0.0-alpha.1
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 +6 -0
- package/dist/index.mjs +23 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @raypx-kit/email-preview
|
|
2
2
|
|
|
3
|
+
## 0.0.0-alpha.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- f4006e4: Recover a template whose repair lands within 50 ms of the broken save. Vite's watcher drops that second change on Linux, which left the failed render cached and the preview stuck on the old compile error; the runtime now re-checks each changed file after the watcher's throttle window and refreshes the preview when it changed.
|
|
8
|
+
|
|
3
9
|
## 0.0.0-alpha.0
|
|
4
10
|
|
|
5
11
|
Initial prerelease.
|
package/dist/index.mjs
CHANGED
|
@@ -2,6 +2,7 @@ import { fileURLToPath } from "node:url";
|
|
|
2
2
|
import { createRunnableDevEnvironment, isRunnableDevEnvironment } from "vite";
|
|
3
3
|
import { readFile, readdir, realpath, stat } from "node:fs/promises";
|
|
4
4
|
import { basename, dirname, extname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
5
|
+
import { statSync } from "node:fs";
|
|
5
6
|
import * as html from "prettier/plugins/html";
|
|
6
7
|
import { format } from "prettier/standalone";
|
|
7
8
|
// src/server/errors.ts
|
|
@@ -273,6 +274,10 @@ async function readStaticFile(directory, name) {
|
|
|
273
274
|
};
|
|
274
275
|
}
|
|
275
276
|
// src/server/runtime.ts
|
|
277
|
+
function fileSignature(path) {
|
|
278
|
+
const stats = statSync(path, { throwIfNoEntry: false });
|
|
279
|
+
return stats ? `${stats.mtimeMs}:${stats.size}` : "missing";
|
|
280
|
+
}
|
|
276
281
|
function isRenderedEmail(value) {
|
|
277
282
|
return typeof value === "object" && value !== null && "html" in value && typeof value.html === "string" && "text" in value && typeof value.text === "string";
|
|
278
283
|
}
|
|
@@ -280,20 +285,23 @@ var PreviewRuntime = class PreviewRuntime {
|
|
|
280
285
|
server;
|
|
281
286
|
directory;
|
|
282
287
|
timeoutMs;
|
|
288
|
+
recheckMs;
|
|
283
289
|
listeners = /* @__PURE__ */ new Set();
|
|
284
290
|
streams = /* @__PURE__ */ new Set();
|
|
291
|
+
rechecks = /* @__PURE__ */ new Map();
|
|
285
292
|
notification;
|
|
286
293
|
closed = false;
|
|
287
294
|
currentRevision = 0;
|
|
288
|
-
constructor(server, directory, timeoutMs) {
|
|
295
|
+
constructor(server, directory, timeoutMs, recheckMs) {
|
|
289
296
|
this.server = server;
|
|
290
297
|
this.directory = directory;
|
|
291
298
|
this.timeoutMs = timeoutMs;
|
|
299
|
+
this.recheckMs = recheckMs;
|
|
292
300
|
}
|
|
293
301
|
static async create(server, options) {
|
|
294
302
|
if (!Number.isFinite(options.renderTimeoutMs) || options.renderTimeoutMs < 1) throw new PreviewRuntimeError("INVALID_OPTIONS", "renderTimeoutMs must be a positive number.", 400);
|
|
295
303
|
const directory = await resolveDirectory(server.config.root, options.directory);
|
|
296
|
-
const runtime = new PreviewRuntime(server, directory, options.renderTimeoutMs);
|
|
304
|
+
const runtime = new PreviewRuntime(server, directory, options.renderTimeoutMs, options.missedChangeWindowMs ?? 75);
|
|
297
305
|
server.watcher.add(directory);
|
|
298
306
|
server.watcher.on("all", runtime.onFileEvent);
|
|
299
307
|
return runtime;
|
|
@@ -307,12 +315,22 @@ var PreviewRuntime = class PreviewRuntime {
|
|
|
307
315
|
const path = resolve(file);
|
|
308
316
|
const environment = this.server.environments[environmentName];
|
|
309
317
|
if (!isContained(this.directory, path) && basename(path) !== "tsconfig.json" && !environment?.moduleGraph.getModulesByFile(path)?.size) return;
|
|
318
|
+
this.refresh();
|
|
319
|
+
const seen = fileSignature(path);
|
|
320
|
+
clearTimeout(this.rechecks.get(path));
|
|
321
|
+
this.rechecks.set(path, setTimeout(() => {
|
|
322
|
+
this.rechecks.delete(path);
|
|
323
|
+
if (!this.closed && fileSignature(path) !== seen) this.refresh();
|
|
324
|
+
}, this.recheckMs));
|
|
325
|
+
};
|
|
326
|
+
refresh() {
|
|
327
|
+
const environment = this.server.environments[environmentName];
|
|
310
328
|
if (environment && isRunnableDevEnvironment(environment)) {
|
|
311
329
|
environment.moduleGraph.invalidateAll();
|
|
312
330
|
environment.runner.clearCache();
|
|
313
331
|
}
|
|
314
332
|
this.invalidate();
|
|
315
|
-
}
|
|
333
|
+
}
|
|
316
334
|
get revision() {
|
|
317
335
|
return this.currentRevision;
|
|
318
336
|
}
|
|
@@ -410,6 +428,8 @@ var PreviewRuntime = class PreviewRuntime {
|
|
|
410
428
|
if (this.closed) return;
|
|
411
429
|
this.closed = true;
|
|
412
430
|
if (this.notification) clearTimeout(this.notification);
|
|
431
|
+
for (const timer of this.rechecks.values()) clearTimeout(timer);
|
|
432
|
+
this.rechecks.clear();
|
|
413
433
|
this.server.watcher.off("all", this.onFileEvent);
|
|
414
434
|
for (const close of this.streams) close();
|
|
415
435
|
this.streams.clear();
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/server/errors.ts","../src/server/discovery.ts","../src/server/middleware.ts","../src/server/render.ts","../src/server/format.ts","../src/server/static.ts","../src/server/runtime.ts","../src/server/plugin.ts"],"sourcesContent":["export class PreviewRuntimeError extends Error {\n readonly code: string;\n readonly status: number;\n\n constructor(code: string, message: string, status = 500) {\n super(message);\n this.name = \"PreviewRuntimeError\";\n this.code = code;\n this.status = status;\n }\n}\n\nexport function errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n","import { readdir, realpath, stat } from \"node:fs/promises\";\nimport { basename, dirname, isAbsolute, relative, resolve, sep } from \"node:path\";\nimport type { Template } from \"../types.js\";\nimport { PreviewRuntimeError } from \"./errors.js\";\n\nexport function isContained(root: string, path: string): boolean {\n const name = relative(root, path);\n return name === \"\" || (!isAbsolute(name) && name !== \"..\" && !name.startsWith(`..${sep}`));\n}\n\nexport async function resolveDirectory(cwd: string, directory: string): Promise<string> {\n const requested = resolve(cwd, directory);\n try {\n const path = await realpath(requested);\n if (!(await stat(path)).isDirectory()) throw new Error(\"Not a directory\");\n return path;\n } catch (error) {\n if (error instanceof Error && \"code\" in error && error.code === \"ENOENT\") {\n // A new project can start the preview before its emails folder exists.\n // Resolve existing ancestors so future path checks use one canonical root.\n const parent = dirname(requested);\n if (parent !== requested)\n return resolve(await resolveDirectory(parent, \".\"), basename(requested));\n }\n throw new PreviewRuntimeError(\n \"DIRECTORY_NOT_FOUND\",\n `Email directory not found: ${directory}`,\n 400,\n );\n }\n}\n\n/** Never use an HTTP-supplied path as a filesystem path without this boundary. */\nexport async function resolveContainedFile(root: string, name: string): Promise<string> {\n if (!name || name.includes(\"\\\\\") || name.includes(\"\\0\") || isAbsolute(name)) {\n throw new PreviewRuntimeError(\"NOT_FOUND\", \"File not found\", 404);\n }\n const path = resolve(root, name);\n if (!isContained(root, path)) throw new PreviewRuntimeError(\"NOT_FOUND\", \"File not found\", 404);\n try {\n const canonical = await realpath(path);\n if (!isContained(root, canonical) || !(await stat(canonical)).isFile()) {\n throw new Error(\"Outside directory or not a file\");\n }\n return canonical;\n } catch {\n throw new PreviewRuntimeError(\"NOT_FOUND\", \"File not found\", 404);\n }\n}\n\nexport async function discoverTemplates(root: string): Promise<Template[]> {\n const templates: Template[] = [];\n async function visit(directory: string): Promise<void> {\n const entries = await readdir(directory, { withFileTypes: true });\n for (const entry of entries) {\n if (entry.name.startsWith(\".\") || entry.name.startsWith(\"_\") || entry.name === \"static\")\n continue;\n const path = resolve(directory, entry.name);\n if (entry.isDirectory()) {\n await visit(path);\n } else if (entry.isFile() && /\\.(tsx|jsx)$/.test(entry.name)) {\n const id = relative(root, path).split(sep).join(\"/\");\n const slash = id.lastIndexOf(\"/\");\n templates.push({\n id,\n name: entry.name.replace(/\\.(tsx|jsx)$/, \"\"),\n directory: slash < 0 ? \"\" : id.slice(0, slash),\n });\n }\n }\n }\n try {\n await visit(root);\n } catch (error) {\n if (!(error instanceof Error) || !(\"code\" in error) || error.code !== \"ENOENT\") throw error;\n }\n return templates.sort((left, right) => left.id.localeCompare(right.id));\n}\n","import { readFile } from \"node:fs/promises\";\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { extname } from \"node:path\";\nimport { resolveContainedFile } from \"./discovery.js\";\nimport { PreviewRuntimeError } from \"./errors.js\";\nimport type { PreviewRuntime } from \"./runtime.js\";\n\ntype Next = (error?: unknown) => void;\n\nconst noCache = { \"Cache-Control\": \"no-store\", \"X-Content-Type-Options\": \"nosniff\" };\n\nconst interfaceTypes: Readonly<Record<string, string>> = {\n \".css\": \"text/css; charset=utf-8\",\n \".html\": \"text/html; charset=utf-8\",\n \".js\": \"text/javascript; charset=utf-8\",\n \".svg\": \"image/svg+xml\",\n \".woff2\": \"font/woff2\",\n};\n\nfunction send(\n response: ServerResponse,\n status: number,\n headers: Record<string, string>,\n body?: string | Uint8Array,\n) {\n response.writeHead(status, headers);\n response.end(body);\n}\n\nfunction sendJson(response: ServerResponse, status: number, value: unknown) {\n send(\n response,\n status,\n { ...noCache, \"Content-Type\": \"application/json; charset=utf-8\" },\n JSON.stringify(value),\n );\n}\n\nfunction sendError(response: ServerResponse, error: unknown) {\n const expected = error instanceof PreviewRuntimeError;\n sendJson(response, expected ? error.status : 500, {\n error: {\n code: expected ? error.code : \"INTERNAL_ERROR\",\n message: expected\n ? error.message\n : \"The preview request failed. Try again after checking the template.\",\n },\n });\n}\n\nfunction openEvents(runtime: PreviewRuntime, request: IncomingMessage, response: ServerResponse) {\n response.writeHead(200, {\n ...noCache,\n \"Content-Type\": \"text/event-stream\",\n Connection: \"keep-alive\",\n \"X-Accel-Buffering\": \"no\",\n });\n const send = (event: string, revision: number) =>\n response.write(`event: ${event}\\ndata: ${JSON.stringify({ revision })}\\n\\n`);\n const unsubscribe = runtime.subscribe((revision) => send(\"change\", revision));\n const heartbeat = setInterval(() => response.write(\": heartbeat\\n\\n\"), 15000);\n heartbeat.unref();\n let untrack = () => {};\n const close = () => {\n clearInterval(heartbeat);\n unsubscribe();\n untrack();\n if (!response.writableEnded) response.end();\n };\n untrack = runtime.trackStream(close);\n request.once(\"close\", close);\n send(\"ready\", runtime.revision);\n}\n\n/**\n * Serves the prebuilt interface and its API below `base`, which ends with a slash.\n * Requests outside `base` continue to the host server's own middleware.\n */\nexport function previewMiddleware(\n base: string,\n interfaceDirectory: string,\n getRuntime: () => Promise<PreviewRuntime>,\n) {\n let indexHtml: Promise<string> | undefined;\n const readIndex = () =>\n (indexHtml ??= readFile(`${interfaceDirectory}/index.html`, \"utf8\").then(\n // Relative asset and API URLs resolve against the mount point.\n (html) => html.replace(\"<head>\", `<head><base href=\"${base}\" />`),\n () => {\n indexHtml = undefined;\n throw new PreviewRuntimeError(\n \"NOT_FOUND\",\n \"The preview interface has not been built.\",\n 404,\n );\n },\n ));\n\n return async (request: IncomingMessage, response: ServerResponse, next: Next) => {\n const url = new URL(request.url ?? \"/\", \"http://localhost\");\n const mount = base.slice(0, -1);\n if (mount && url.pathname === mount) {\n send(response, 302, { Location: `${base}${url.search}` });\n return;\n }\n if (!url.pathname.startsWith(base)) return next();\n if (request.method !== \"GET\" && request.method !== \"HEAD\") return next();\n const path = url.pathname.slice(base.length);\n try {\n if (path === \"api/templates\") {\n sendJson(response, 200, await (await getRuntime()).listTemplates());\n } else if (path === \"api/preview\") {\n const template = url.searchParams.get(\"template\");\n if (!template) {\n throw new PreviewRuntimeError(\"INVALID_TEMPLATE\", \"Select an email template.\", 400);\n }\n sendJson(response, 200, await (await getRuntime()).renderTemplate(template));\n } else if (path === \"api/events\") {\n openEvents(await getRuntime(), request, response);\n } else if (path.startsWith(\"static/\")) {\n const file = await (await getRuntime()).getStaticFile(decodeURIComponent(path.slice(7)));\n send(\n response,\n 200,\n { ...noCache, \"Content-Type\": file.contentType, \"Content-Security-Policy\": \"sandbox\" },\n file.data,\n );\n } else if (path === \"\" || path === \"index.html\") {\n send(\n response,\n 200,\n { ...noCache, \"Content-Type\": interfaceTypes[\".html\"]! },\n await readIndex(),\n );\n } else if (path.startsWith(\"assets/\")) {\n const file = await resolveContainedFile(interfaceDirectory, path).catch(() => undefined);\n // Unknown paths belong to the host server, which matters when `base` is `/`.\n if (!file) return next();\n send(\n response,\n 200,\n {\n \"Cache-Control\": \"public, max-age=31536000, immutable\",\n \"Content-Type\": interfaceTypes[extname(file)] ?? \"application/octet-stream\",\n \"X-Content-Type-Options\": \"nosniff\",\n },\n await readFile(file),\n );\n } else {\n return next();\n }\n } catch (error) {\n if (response.headersSent) response.destroy();\n else sendError(response, error);\n }\n };\n}\n","import { readFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\n/** The Vite environment that evaluates templates, separate from the application's `ssr`. */\nexport const environmentName = \"email_preview\";\n\nconst entryPrefix = \"virtual:email-preview-render:\";\nconst resolvedEntryPrefix = `\\0${entryPrefix}`;\n// Resolved from this package, which depends on the renderer; the project may not.\n// The generated entry imports it by path inside the project's Vite environment, so\n// its `react` import resolves to the same React as the template.\nconst rendererPath = fileURLToPath(import.meta.resolve(\"@react-email/render\"));\n\nexport function renderEntryId(template: string): string {\n return `${entryPrefix}${template}`;\n}\n\nexport function resolveRenderEntry(id: string): string | undefined {\n return id.startsWith(entryPrefix) ? `\\0${id}` : undefined;\n}\n\n/** Renders one template with its `PreviewProps`, importing React through the project. */\nexport function loadRenderEntry(id: string): string | undefined {\n if (!id.startsWith(resolvedEntryPrefix)) return;\n const template = id.slice(resolvedEntryPrefix.length);\n return `import { createElement } from \"react\";\nimport { render, toPlainText } from ${JSON.stringify(rendererPath)};\nimport * as email from ${JSON.stringify(template)};\nexport async function renderEmail() {\n const Component = email.default;\n if (!Component || (typeof Component !== \"function\" && typeof Component !== \"object\")) {\n throw new Error(\"The email template must default-export a React component.\");\n }\n const html = await render(createElement(Component, Component.PreviewProps ?? {}));\n return { html, text: toPlainText(html) };\n}`;\n}\n\n/**\n * Reads the project's manifest straight from disk. Bun caches failed module\n * lookups in-process, so `require.resolve` would miss a later install.\n */\nasync function readProjectManifest(name: string, from: string): Promise<{ version?: string }> {\n for (let directory = dirname(from); ; directory = dirname(directory)) {\n try {\n return JSON.parse(\n await readFile(join(directory, \"node_modules\", name, \"package.json\"), \"utf8\"),\n );\n } catch (error) {\n if (!(error instanceof Error && \"code\" in error && error.code === \"ENOENT\")) throw error;\n }\n if (dirname(directory) === directory) return {};\n }\n}\n\nexport async function assertReact(template: string): Promise<void> {\n for (const name of [\"react\", \"react-dom\"]) {\n const manifest = await readProjectManifest(name, template);\n if (!manifest.version?.startsWith(\"19.\")) {\n throw new Error(`Install ${name} 19 in the project containing your email templates.`);\n }\n }\n}\n","import * as html from \"prettier/plugins/html\";\nimport { format } from \"prettier/standalone\";\n\n/**\n * Indents rendered HTML for the source view. The preview, clipboard and downloads\n * keep the original output, so markup Prettier cannot parse is shown unchanged.\n */\nexport async function formatHtml(source: string): Promise<string> {\n try {\n return await format(source, {\n parser: \"html\",\n plugins: [html],\n endOfLine: \"lf\",\n tabWidth: 2,\n bracketSameLine: true,\n });\n } catch {\n return source;\n }\n}\n","import { readFile, realpath } from \"node:fs/promises\";\nimport { extname, resolve } from \"node:path\";\nimport { isContained, resolveContainedFile } from \"./discovery.js\";\nimport { PreviewRuntimeError } from \"./errors.js\";\n\nconst contentTypes: Readonly<Record<string, string>> = {\n \".avif\": \"image/avif\",\n \".css\": \"text/css; charset=utf-8\",\n \".gif\": \"image/gif\",\n \".ico\": \"image/x-icon\",\n \".jpeg\": \"image/jpeg\",\n \".jpg\": \"image/jpeg\",\n \".png\": \"image/png\",\n \".svg\": \"image/svg+xml\",\n \".ttf\": \"font/ttf\",\n \".webp\": \"image/webp\",\n \".woff\": \"font/woff\",\n \".woff2\": \"font/woff2\",\n};\n\nexport async function readStaticFile(\n directory: string,\n name: string,\n): Promise<{ data: Uint8Array<ArrayBuffer>; contentType: string }> {\n let root: string;\n try {\n root = await realpath(resolve(directory, \"static\"));\n } catch {\n throw new PreviewRuntimeError(\"NOT_FOUND\", \"Static file not found\", 404);\n }\n if (!isContained(directory, root)) {\n throw new PreviewRuntimeError(\"NOT_FOUND\", \"Static file not found\", 404);\n }\n const path = await resolveContainedFile(root, name);\n return {\n data: await readFile(path),\n contentType: contentTypes[extname(path).toLowerCase()] ?? \"application/octet-stream\",\n };\n}\n","import { basename, resolve } from \"node:path\";\nimport { isRunnableDevEnvironment } from \"vite\";\nimport type { RunnableDevEnvironment, ViteDevServer } from \"vite\";\nimport type { PreviewResult, TemplateList } from \"../types.js\";\nimport {\n discoverTemplates,\n isContained,\n resolveContainedFile,\n resolveDirectory,\n} from \"./discovery.js\";\nimport { errorMessage, PreviewRuntimeError } from \"./errors.js\";\nimport { formatHtml } from \"./format.js\";\nimport { assertReact, environmentName, renderEntryId } from \"./render.js\";\nimport { readStaticFile } from \"./static.js\";\n\nexport interface RuntimeOptions {\n /** Template directory, relative to the Vite root unless absolute. */\n directory: string;\n renderTimeoutMs: number;\n}\n\ntype RenderedEmail = Pick<PreviewResult, \"html\" | \"text\">;\n\nfunction isRenderedEmail(value: unknown): value is RenderedEmail {\n return (\n typeof value === \"object\" &&\n value !== null &&\n \"html\" in value &&\n typeof value.html === \"string\" &&\n \"text\" in value &&\n typeof value.text === \"string\"\n );\n}\n\nexport class PreviewRuntime {\n private readonly listeners = new Set<(revision: number) => void>();\n private readonly streams = new Set<() => void>();\n private notification: ReturnType<typeof setTimeout> | undefined;\n private closed = false;\n private currentRevision = 0;\n\n private constructor(\n private readonly server: ViteDevServer,\n readonly directory: string,\n private readonly timeoutMs: number,\n ) {}\n\n static async create(server: ViteDevServer, options: RuntimeOptions): Promise<PreviewRuntime> {\n if (!Number.isFinite(options.renderTimeoutMs) || options.renderTimeoutMs < 1) {\n throw new PreviewRuntimeError(\n \"INVALID_OPTIONS\",\n \"renderTimeoutMs must be a positive number.\",\n 400,\n );\n }\n const directory = await resolveDirectory(server.config.root, options.directory);\n const runtime = new PreviewRuntime(server, directory, options.renderTimeoutMs);\n // The template directory may sit outside the Vite root, for example in a sibling package.\n server.watcher.add(directory);\n server.watcher.on(\"all\", runtime.onFileEvent);\n return runtime;\n }\n\n private get environment(): RunnableDevEnvironment {\n const environment = this.server.environments[environmentName];\n if (!environment || !isRunnableDevEnvironment(environment)) {\n throw new PreviewRuntimeError(\n \"NOT_INITIALIZED\",\n \"The email preview environment is not available.\",\n 503,\n );\n }\n return environment;\n }\n\n private readonly onFileEvent = (_event: string, file: string): void => {\n const path = resolve(file);\n const environment = this.server.environments[environmentName];\n if (\n !isContained(this.directory, path) &&\n basename(path) !== \"tsconfig.json\" &&\n !environment?.moduleGraph.getModulesByFile(path)?.size\n ) {\n return;\n }\n // Vite invalidates changed modules asynchronously. Template graphs are small,\n // so drop them now and the next render can never observe stale code.\n if (environment && isRunnableDevEnvironment(environment)) {\n environment.moduleGraph.invalidateAll();\n environment.runner.clearCache();\n }\n this.invalidate();\n };\n\n get revision(): number {\n return this.currentRevision;\n }\n\n private invalidate(): void {\n if (this.closed) return;\n this.currentRevision += 1;\n if (this.notification) clearTimeout(this.notification);\n this.notification = setTimeout(() => {\n this.notification = undefined;\n for (const listener of this.listeners) {\n try {\n listener(this.currentRevision);\n } catch {\n this.listeners.delete(listener);\n }\n }\n }, 80);\n }\n\n private assertOpen(): void {\n if (this.closed) throw new PreviewRuntimeError(\"CLOSED\", \"The preview server is closing.\", 503);\n }\n\n async listTemplates(): Promise<TemplateList> {\n this.assertOpen();\n return {\n directory: this.directory,\n templates: await discoverTemplates(this.directory),\n revision: this.revision,\n };\n }\n\n async renderTemplate(id: string): Promise<PreviewResult> {\n this.assertOpen();\n const { templates } = await this.listTemplates();\n if (!templates.some((template) => template.id === id)) {\n throw new PreviewRuntimeError(\"TEMPLATE_NOT_FOUND\", \"Email template not found.\", 404);\n }\n const path = await resolveContainedFile(this.directory, id);\n try {\n await assertReact(path);\n } catch (error) {\n throw new PreviewRuntimeError(\"DEPENDENCY_ERROR\", errorMessage(error), 422);\n }\n for (let attempt = 0; attempt < 3; attempt += 1) {\n this.assertOpen();\n const revision = this.revision;\n const result = await this.render(path);\n if (this.revision === revision) {\n return { id, revision, ...result, formattedHtml: await formatHtml(result.html) };\n }\n }\n throw new PreviewRuntimeError(\n \"TEMPLATE_CHANGED\",\n \"The template changed while rendering. Please try again.\",\n 409,\n );\n }\n\n private async render(path: string): Promise<RenderedEmail> {\n let entry: { renderEmail?: unknown };\n try {\n entry = await this.withTimeout(this.environment.runner.import(renderEntryId(path)));\n } catch (error) {\n if (error instanceof PreviewRuntimeError) throw error;\n throw new PreviewRuntimeError(\"COMPILE_ERROR\", errorMessage(error), 422);\n }\n try {\n if (typeof entry.renderEmail !== \"function\") throw new Error(\"Rendering failed to load.\");\n const value: unknown = await this.withTimeout(Promise.resolve(entry.renderEmail()));\n if (!isRenderedEmail(value)) throw new Error(\"The template returned an invalid result.\");\n return value;\n } catch (error) {\n if (error instanceof PreviewRuntimeError) throw error;\n throw new PreviewRuntimeError(\"RENDER_ERROR\", errorMessage(error), 422);\n }\n }\n\n /**\n * Templates run in the dev server process. A timeout reports work that never\n * settles; it cannot interrupt a synchronous loop.\n */\n private withTimeout<T>(work: Promise<T>): Promise<T> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timeout = new Promise<never>((_, reject) => {\n timer = setTimeout(\n () =>\n reject(\n new PreviewRuntimeError(\n \"RENDER_TIMEOUT\",\n `Template rendering exceeded ${this.timeoutMs} ms.`,\n 422,\n ),\n ),\n this.timeoutMs,\n );\n });\n return Promise.race([work, timeout]).finally(() => clearTimeout(timer));\n }\n\n subscribe(listener: (revision: number) => void): () => void {\n this.assertOpen();\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n /** Registers an open event stream so shutdown can end it. */\n trackStream(close: () => void): () => void {\n this.streams.add(close);\n return () => this.streams.delete(close);\n }\n\n async getStaticFile(\n path: string,\n ): Promise<{ data: Uint8Array<ArrayBuffer>; contentType: string }> {\n this.assertOpen();\n return readStaticFile(this.directory, path);\n }\n\n close(): void {\n if (this.closed) return;\n this.closed = true;\n if (this.notification) clearTimeout(this.notification);\n this.server.watcher.off(\"all\", this.onFileEvent);\n for (const close of this.streams) close();\n this.streams.clear();\n this.listeners.clear();\n }\n}\n","import { fileURLToPath } from \"node:url\";\nimport { createRunnableDevEnvironment } from \"vite\";\nimport type { Plugin } from \"vite\";\nimport { previewMiddleware } from \"./middleware.js\";\nimport { environmentName, loadRenderEntry, resolveRenderEntry } from \"./render.js\";\nimport { PreviewRuntime } from \"./runtime.js\";\n\nexport interface EmailPreviewOptions {\n /** Template directory relative to the Vite root. Defaults to `emails`. */\n dir?: string;\n /** URL prefix for the interface and its API. Defaults to `/__email`. */\n base?: string;\n /** Rendering timeout in milliseconds. Defaults to 10000. */\n renderTimeoutMs?: number;\n}\n\n/** @internal Lets source tests and the package's own development serve another build. */\nexport interface InternalOptions extends EmailPreviewOptions {\n interfaceDirectory?: string;\n}\n\nfunction normalizeBase(base: string): string {\n const value = `/${base.replace(/^\\/+|\\/+$/g, \"\")}/`.replace(/^\\/\\/$/, \"/\");\n if (!/^\\/(?:[\\w.-]+\\/)*$/.test(value)) {\n throw new Error(`Invalid email preview base: ${base}`);\n }\n return value;\n}\n\n/** Adds the React Email preview to a Vite dev server. Builds are unaffected. */\nexport function emailPreview(options: EmailPreviewOptions = {}): Plugin {\n const {\n dir = \"emails\",\n renderTimeoutMs = 10_000,\n interfaceDirectory = fileURLToPath(new URL(\"./ui\", import.meta.url)),\n } = options as InternalOptions;\n const base = normalizeBase(options.base ?? \"/__email\");\n let runtime: Promise<PreviewRuntime> | undefined;\n\n return {\n name: \"raypx-email-preview\",\n apply: \"serve\",\n // Lets tests and cooperating plugins reach the running preview.\n api: { runtime: () => runtime },\n config() {\n return {\n environments: {\n // Templates get their own module graph, so rendering never loads into or\n // invalidates the application's SSR environment.\n [environmentName]: {\n consumer: \"server\",\n resolve: {\n dedupe: [\"react\", \"react-dom\"],\n external: true,\n // Bundled through Vite so the renderer resolves the project's React.\n noExternal: [\"@react-email/render\"],\n },\n dev: {\n createEnvironment: (name, config) => createRunnableDevEnvironment(name, config),\n },\n },\n },\n };\n },\n resolveId(id) {\n return resolveRenderEntry(id);\n },\n load(id) {\n return loadRenderEntry(id);\n },\n configureServer(server) {\n runtime = PreviewRuntime.create(server, { directory: dir, renderTimeoutMs });\n // Keep a failed start observable through each request instead of crashing.\n runtime.catch(() => {});\n server.middlewares.use(previewMiddleware(base, interfaceDirectory, () => runtime!));\n server.httpServer?.once(\"close\", () => void runtime?.then((value) => value.close()));\n },\n async buildEnd() {\n (await runtime?.catch(() => undefined))?.close();\n },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/server/errors.ts","../src/server/discovery.ts","../src/server/middleware.ts","../src/server/render.ts","../src/server/format.ts","../src/server/static.ts","../src/server/runtime.ts","../src/server/plugin.ts"],"sourcesContent":["export class PreviewRuntimeError extends Error {\n readonly code: string;\n readonly status: number;\n\n constructor(code: string, message: string, status = 500) {\n super(message);\n this.name = \"PreviewRuntimeError\";\n this.code = code;\n this.status = status;\n }\n}\n\nexport function errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n","import { readdir, realpath, stat } from \"node:fs/promises\";\nimport { basename, dirname, isAbsolute, relative, resolve, sep } from \"node:path\";\nimport type { Template } from \"../types.js\";\nimport { PreviewRuntimeError } from \"./errors.js\";\n\nexport function isContained(root: string, path: string): boolean {\n const name = relative(root, path);\n return name === \"\" || (!isAbsolute(name) && name !== \"..\" && !name.startsWith(`..${sep}`));\n}\n\nexport async function resolveDirectory(cwd: string, directory: string): Promise<string> {\n const requested = resolve(cwd, directory);\n try {\n const path = await realpath(requested);\n if (!(await stat(path)).isDirectory()) throw new Error(\"Not a directory\");\n return path;\n } catch (error) {\n if (error instanceof Error && \"code\" in error && error.code === \"ENOENT\") {\n // A new project can start the preview before its emails folder exists.\n // Resolve existing ancestors so future path checks use one canonical root.\n const parent = dirname(requested);\n if (parent !== requested)\n return resolve(await resolveDirectory(parent, \".\"), basename(requested));\n }\n throw new PreviewRuntimeError(\n \"DIRECTORY_NOT_FOUND\",\n `Email directory not found: ${directory}`,\n 400,\n );\n }\n}\n\n/** Never use an HTTP-supplied path as a filesystem path without this boundary. */\nexport async function resolveContainedFile(root: string, name: string): Promise<string> {\n if (!name || name.includes(\"\\\\\") || name.includes(\"\\0\") || isAbsolute(name)) {\n throw new PreviewRuntimeError(\"NOT_FOUND\", \"File not found\", 404);\n }\n const path = resolve(root, name);\n if (!isContained(root, path)) throw new PreviewRuntimeError(\"NOT_FOUND\", \"File not found\", 404);\n try {\n const canonical = await realpath(path);\n if (!isContained(root, canonical) || !(await stat(canonical)).isFile()) {\n throw new Error(\"Outside directory or not a file\");\n }\n return canonical;\n } catch {\n throw new PreviewRuntimeError(\"NOT_FOUND\", \"File not found\", 404);\n }\n}\n\nexport async function discoverTemplates(root: string): Promise<Template[]> {\n const templates: Template[] = [];\n async function visit(directory: string): Promise<void> {\n const entries = await readdir(directory, { withFileTypes: true });\n for (const entry of entries) {\n if (entry.name.startsWith(\".\") || entry.name.startsWith(\"_\") || entry.name === \"static\")\n continue;\n const path = resolve(directory, entry.name);\n if (entry.isDirectory()) {\n await visit(path);\n } else if (entry.isFile() && /\\.(tsx|jsx)$/.test(entry.name)) {\n const id = relative(root, path).split(sep).join(\"/\");\n const slash = id.lastIndexOf(\"/\");\n templates.push({\n id,\n name: entry.name.replace(/\\.(tsx|jsx)$/, \"\"),\n directory: slash < 0 ? \"\" : id.slice(0, slash),\n });\n }\n }\n }\n try {\n await visit(root);\n } catch (error) {\n if (!(error instanceof Error) || !(\"code\" in error) || error.code !== \"ENOENT\") throw error;\n }\n return templates.sort((left, right) => left.id.localeCompare(right.id));\n}\n","import { readFile } from \"node:fs/promises\";\nimport type { IncomingMessage, ServerResponse } from \"node:http\";\nimport { extname } from \"node:path\";\nimport { resolveContainedFile } from \"./discovery.js\";\nimport { PreviewRuntimeError } from \"./errors.js\";\nimport type { PreviewRuntime } from \"./runtime.js\";\n\ntype Next = (error?: unknown) => void;\n\nconst noCache = { \"Cache-Control\": \"no-store\", \"X-Content-Type-Options\": \"nosniff\" };\n\nconst interfaceTypes: Readonly<Record<string, string>> = {\n \".css\": \"text/css; charset=utf-8\",\n \".html\": \"text/html; charset=utf-8\",\n \".js\": \"text/javascript; charset=utf-8\",\n \".svg\": \"image/svg+xml\",\n \".woff2\": \"font/woff2\",\n};\n\nfunction send(\n response: ServerResponse,\n status: number,\n headers: Record<string, string>,\n body?: string | Uint8Array,\n) {\n response.writeHead(status, headers);\n response.end(body);\n}\n\nfunction sendJson(response: ServerResponse, status: number, value: unknown) {\n send(\n response,\n status,\n { ...noCache, \"Content-Type\": \"application/json; charset=utf-8\" },\n JSON.stringify(value),\n );\n}\n\nfunction sendError(response: ServerResponse, error: unknown) {\n const expected = error instanceof PreviewRuntimeError;\n sendJson(response, expected ? error.status : 500, {\n error: {\n code: expected ? error.code : \"INTERNAL_ERROR\",\n message: expected\n ? error.message\n : \"The preview request failed. Try again after checking the template.\",\n },\n });\n}\n\nfunction openEvents(runtime: PreviewRuntime, request: IncomingMessage, response: ServerResponse) {\n response.writeHead(200, {\n ...noCache,\n \"Content-Type\": \"text/event-stream\",\n Connection: \"keep-alive\",\n \"X-Accel-Buffering\": \"no\",\n });\n const send = (event: string, revision: number) =>\n response.write(`event: ${event}\\ndata: ${JSON.stringify({ revision })}\\n\\n`);\n const unsubscribe = runtime.subscribe((revision) => send(\"change\", revision));\n const heartbeat = setInterval(() => response.write(\": heartbeat\\n\\n\"), 15000);\n heartbeat.unref();\n let untrack = () => {};\n const close = () => {\n clearInterval(heartbeat);\n unsubscribe();\n untrack();\n if (!response.writableEnded) response.end();\n };\n untrack = runtime.trackStream(close);\n request.once(\"close\", close);\n send(\"ready\", runtime.revision);\n}\n\n/**\n * Serves the prebuilt interface and its API below `base`, which ends with a slash.\n * Requests outside `base` continue to the host server's own middleware.\n */\nexport function previewMiddleware(\n base: string,\n interfaceDirectory: string,\n getRuntime: () => Promise<PreviewRuntime>,\n) {\n let indexHtml: Promise<string> | undefined;\n const readIndex = () =>\n (indexHtml ??= readFile(`${interfaceDirectory}/index.html`, \"utf8\").then(\n // Relative asset and API URLs resolve against the mount point.\n (html) => html.replace(\"<head>\", `<head><base href=\"${base}\" />`),\n () => {\n indexHtml = undefined;\n throw new PreviewRuntimeError(\n \"NOT_FOUND\",\n \"The preview interface has not been built.\",\n 404,\n );\n },\n ));\n\n return async (request: IncomingMessage, response: ServerResponse, next: Next) => {\n const url = new URL(request.url ?? \"/\", \"http://localhost\");\n const mount = base.slice(0, -1);\n if (mount && url.pathname === mount) {\n send(response, 302, { Location: `${base}${url.search}` });\n return;\n }\n if (!url.pathname.startsWith(base)) return next();\n if (request.method !== \"GET\" && request.method !== \"HEAD\") return next();\n const path = url.pathname.slice(base.length);\n try {\n if (path === \"api/templates\") {\n sendJson(response, 200, await (await getRuntime()).listTemplates());\n } else if (path === \"api/preview\") {\n const template = url.searchParams.get(\"template\");\n if (!template) {\n throw new PreviewRuntimeError(\"INVALID_TEMPLATE\", \"Select an email template.\", 400);\n }\n sendJson(response, 200, await (await getRuntime()).renderTemplate(template));\n } else if (path === \"api/events\") {\n openEvents(await getRuntime(), request, response);\n } else if (path.startsWith(\"static/\")) {\n const file = await (await getRuntime()).getStaticFile(decodeURIComponent(path.slice(7)));\n send(\n response,\n 200,\n { ...noCache, \"Content-Type\": file.contentType, \"Content-Security-Policy\": \"sandbox\" },\n file.data,\n );\n } else if (path === \"\" || path === \"index.html\") {\n send(\n response,\n 200,\n { ...noCache, \"Content-Type\": interfaceTypes[\".html\"]! },\n await readIndex(),\n );\n } else if (path.startsWith(\"assets/\")) {\n const file = await resolveContainedFile(interfaceDirectory, path).catch(() => undefined);\n // Unknown paths belong to the host server, which matters when `base` is `/`.\n if (!file) return next();\n send(\n response,\n 200,\n {\n \"Cache-Control\": \"public, max-age=31536000, immutable\",\n \"Content-Type\": interfaceTypes[extname(file)] ?? \"application/octet-stream\",\n \"X-Content-Type-Options\": \"nosniff\",\n },\n await readFile(file),\n );\n } else {\n return next();\n }\n } catch (error) {\n if (response.headersSent) response.destroy();\n else sendError(response, error);\n }\n };\n}\n","import { readFile } from \"node:fs/promises\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\n/** The Vite environment that evaluates templates, separate from the application's `ssr`. */\nexport const environmentName = \"email_preview\";\n\nconst entryPrefix = \"virtual:email-preview-render:\";\nconst resolvedEntryPrefix = `\\0${entryPrefix}`;\n// Resolved from this package, which depends on the renderer; the project may not.\n// The generated entry imports it by path inside the project's Vite environment, so\n// its `react` import resolves to the same React as the template.\nconst rendererPath = fileURLToPath(import.meta.resolve(\"@react-email/render\"));\n\nexport function renderEntryId(template: string): string {\n return `${entryPrefix}${template}`;\n}\n\nexport function resolveRenderEntry(id: string): string | undefined {\n return id.startsWith(entryPrefix) ? `\\0${id}` : undefined;\n}\n\n/** Renders one template with its `PreviewProps`, importing React through the project. */\nexport function loadRenderEntry(id: string): string | undefined {\n if (!id.startsWith(resolvedEntryPrefix)) return;\n const template = id.slice(resolvedEntryPrefix.length);\n return `import { createElement } from \"react\";\nimport { render, toPlainText } from ${JSON.stringify(rendererPath)};\nimport * as email from ${JSON.stringify(template)};\nexport async function renderEmail() {\n const Component = email.default;\n if (!Component || (typeof Component !== \"function\" && typeof Component !== \"object\")) {\n throw new Error(\"The email template must default-export a React component.\");\n }\n const html = await render(createElement(Component, Component.PreviewProps ?? {}));\n return { html, text: toPlainText(html) };\n}`;\n}\n\n/**\n * Reads the project's manifest straight from disk. Bun caches failed module\n * lookups in-process, so `require.resolve` would miss a later install.\n */\nasync function readProjectManifest(name: string, from: string): Promise<{ version?: string }> {\n for (let directory = dirname(from); ; directory = dirname(directory)) {\n try {\n return JSON.parse(\n await readFile(join(directory, \"node_modules\", name, \"package.json\"), \"utf8\"),\n );\n } catch (error) {\n if (!(error instanceof Error && \"code\" in error && error.code === \"ENOENT\")) throw error;\n }\n if (dirname(directory) === directory) return {};\n }\n}\n\nexport async function assertReact(template: string): Promise<void> {\n for (const name of [\"react\", \"react-dom\"]) {\n const manifest = await readProjectManifest(name, template);\n if (!manifest.version?.startsWith(\"19.\")) {\n throw new Error(`Install ${name} 19 in the project containing your email templates.`);\n }\n }\n}\n","import * as html from \"prettier/plugins/html\";\nimport { format } from \"prettier/standalone\";\n\n/**\n * Indents rendered HTML for the source view. The preview, clipboard and downloads\n * keep the original output, so markup Prettier cannot parse is shown unchanged.\n */\nexport async function formatHtml(source: string): Promise<string> {\n try {\n return await format(source, {\n parser: \"html\",\n plugins: [html],\n endOfLine: \"lf\",\n tabWidth: 2,\n bracketSameLine: true,\n });\n } catch {\n return source;\n }\n}\n","import { readFile, realpath } from \"node:fs/promises\";\nimport { extname, resolve } from \"node:path\";\nimport { isContained, resolveContainedFile } from \"./discovery.js\";\nimport { PreviewRuntimeError } from \"./errors.js\";\n\nconst contentTypes: Readonly<Record<string, string>> = {\n \".avif\": \"image/avif\",\n \".css\": \"text/css; charset=utf-8\",\n \".gif\": \"image/gif\",\n \".ico\": \"image/x-icon\",\n \".jpeg\": \"image/jpeg\",\n \".jpg\": \"image/jpeg\",\n \".png\": \"image/png\",\n \".svg\": \"image/svg+xml\",\n \".ttf\": \"font/ttf\",\n \".webp\": \"image/webp\",\n \".woff\": \"font/woff\",\n \".woff2\": \"font/woff2\",\n};\n\nexport async function readStaticFile(\n directory: string,\n name: string,\n): Promise<{ data: Uint8Array<ArrayBuffer>; contentType: string }> {\n let root: string;\n try {\n root = await realpath(resolve(directory, \"static\"));\n } catch {\n throw new PreviewRuntimeError(\"NOT_FOUND\", \"Static file not found\", 404);\n }\n if (!isContained(directory, root)) {\n throw new PreviewRuntimeError(\"NOT_FOUND\", \"Static file not found\", 404);\n }\n const path = await resolveContainedFile(root, name);\n return {\n data: await readFile(path),\n contentType: contentTypes[extname(path).toLowerCase()] ?? \"application/octet-stream\",\n };\n}\n","import { statSync } from \"node:fs\";\nimport { basename, resolve } from \"node:path\";\nimport { isRunnableDevEnvironment } from \"vite\";\nimport type { RunnableDevEnvironment, ViteDevServer } from \"vite\";\nimport type { PreviewResult, TemplateList } from \"../types.js\";\nimport {\n discoverTemplates,\n isContained,\n resolveContainedFile,\n resolveDirectory,\n} from \"./discovery.js\";\nimport { errorMessage, PreviewRuntimeError } from \"./errors.js\";\nimport { formatHtml } from \"./format.js\";\nimport { assertReact, environmentName, renderEntryId } from \"./render.js\";\nimport { readStaticFile } from \"./static.js\";\n\nexport interface RuntimeOptions {\n /** Template directory, relative to the Vite root unless absolute. */\n directory: string;\n renderTimeoutMs: number;\n /**\n * Vite's watcher drops a second change to the same file within 50 ms, which would\n * leave a failed render cached until an unrelated edit. Each changed file is compared\n * again after this window. Defaults to 75 ms; tests widen it.\n */\n missedChangeWindowMs?: number;\n}\n\ntype RenderedEmail = Pick<PreviewResult, \"html\" | \"text\">;\n\nfunction fileSignature(path: string): string {\n const stats = statSync(path, { throwIfNoEntry: false });\n return stats ? `${stats.mtimeMs}:${stats.size}` : \"missing\";\n}\n\nfunction isRenderedEmail(value: unknown): value is RenderedEmail {\n return (\n typeof value === \"object\" &&\n value !== null &&\n \"html\" in value &&\n typeof value.html === \"string\" &&\n \"text\" in value &&\n typeof value.text === \"string\"\n );\n}\n\nexport class PreviewRuntime {\n private readonly listeners = new Set<(revision: number) => void>();\n private readonly streams = new Set<() => void>();\n private readonly rechecks = new Map<string, ReturnType<typeof setTimeout>>();\n private notification: ReturnType<typeof setTimeout> | undefined;\n private closed = false;\n private currentRevision = 0;\n\n private constructor(\n private readonly server: ViteDevServer,\n readonly directory: string,\n private readonly timeoutMs: number,\n private readonly recheckMs: number,\n ) {}\n\n static async create(server: ViteDevServer, options: RuntimeOptions): Promise<PreviewRuntime> {\n if (!Number.isFinite(options.renderTimeoutMs) || options.renderTimeoutMs < 1) {\n throw new PreviewRuntimeError(\n \"INVALID_OPTIONS\",\n \"renderTimeoutMs must be a positive number.\",\n 400,\n );\n }\n const directory = await resolveDirectory(server.config.root, options.directory);\n const runtime = new PreviewRuntime(\n server,\n directory,\n options.renderTimeoutMs,\n options.missedChangeWindowMs ?? 75,\n );\n // The template directory may sit outside the Vite root, for example in a sibling package.\n server.watcher.add(directory);\n server.watcher.on(\"all\", runtime.onFileEvent);\n return runtime;\n }\n\n private get environment(): RunnableDevEnvironment {\n const environment = this.server.environments[environmentName];\n if (!environment || !isRunnableDevEnvironment(environment)) {\n throw new PreviewRuntimeError(\n \"NOT_INITIALIZED\",\n \"The email preview environment is not available.\",\n 503,\n );\n }\n return environment;\n }\n\n private readonly onFileEvent = (_event: string, file: string): void => {\n const path = resolve(file);\n const environment = this.server.environments[environmentName];\n if (\n !isContained(this.directory, path) &&\n basename(path) !== \"tsconfig.json\" &&\n !environment?.moduleGraph.getModulesByFile(path)?.size\n ) {\n return;\n }\n this.refresh();\n const seen = fileSignature(path);\n clearTimeout(this.rechecks.get(path));\n this.rechecks.set(\n path,\n setTimeout(() => {\n this.rechecks.delete(path);\n if (!this.closed && fileSignature(path) !== seen) this.refresh();\n }, this.recheckMs),\n );\n };\n\n private refresh(): void {\n // Vite invalidates changed modules asynchronously. Template graphs are small,\n // so drop them now and the next render can never observe stale code.\n const environment = this.server.environments[environmentName];\n if (environment && isRunnableDevEnvironment(environment)) {\n environment.moduleGraph.invalidateAll();\n environment.runner.clearCache();\n }\n this.invalidate();\n }\n\n get revision(): number {\n return this.currentRevision;\n }\n\n private invalidate(): void {\n if (this.closed) return;\n this.currentRevision += 1;\n if (this.notification) clearTimeout(this.notification);\n this.notification = setTimeout(() => {\n this.notification = undefined;\n for (const listener of this.listeners) {\n try {\n listener(this.currentRevision);\n } catch {\n this.listeners.delete(listener);\n }\n }\n }, 80);\n }\n\n private assertOpen(): void {\n if (this.closed) throw new PreviewRuntimeError(\"CLOSED\", \"The preview server is closing.\", 503);\n }\n\n async listTemplates(): Promise<TemplateList> {\n this.assertOpen();\n return {\n directory: this.directory,\n templates: await discoverTemplates(this.directory),\n revision: this.revision,\n };\n }\n\n async renderTemplate(id: string): Promise<PreviewResult> {\n this.assertOpen();\n const { templates } = await this.listTemplates();\n if (!templates.some((template) => template.id === id)) {\n throw new PreviewRuntimeError(\"TEMPLATE_NOT_FOUND\", \"Email template not found.\", 404);\n }\n const path = await resolveContainedFile(this.directory, id);\n try {\n await assertReact(path);\n } catch (error) {\n throw new PreviewRuntimeError(\"DEPENDENCY_ERROR\", errorMessage(error), 422);\n }\n for (let attempt = 0; attempt < 3; attempt += 1) {\n this.assertOpen();\n const revision = this.revision;\n const result = await this.render(path);\n if (this.revision === revision) {\n return { id, revision, ...result, formattedHtml: await formatHtml(result.html) };\n }\n }\n throw new PreviewRuntimeError(\n \"TEMPLATE_CHANGED\",\n \"The template changed while rendering. Please try again.\",\n 409,\n );\n }\n\n private async render(path: string): Promise<RenderedEmail> {\n let entry: { renderEmail?: unknown };\n try {\n entry = await this.withTimeout(this.environment.runner.import(renderEntryId(path)));\n } catch (error) {\n if (error instanceof PreviewRuntimeError) throw error;\n throw new PreviewRuntimeError(\"COMPILE_ERROR\", errorMessage(error), 422);\n }\n try {\n if (typeof entry.renderEmail !== \"function\") throw new Error(\"Rendering failed to load.\");\n const value: unknown = await this.withTimeout(Promise.resolve(entry.renderEmail()));\n if (!isRenderedEmail(value)) throw new Error(\"The template returned an invalid result.\");\n return value;\n } catch (error) {\n if (error instanceof PreviewRuntimeError) throw error;\n throw new PreviewRuntimeError(\"RENDER_ERROR\", errorMessage(error), 422);\n }\n }\n\n /**\n * Templates run in the dev server process. A timeout reports work that never\n * settles; it cannot interrupt a synchronous loop.\n */\n private withTimeout<T>(work: Promise<T>): Promise<T> {\n let timer: ReturnType<typeof setTimeout> | undefined;\n const timeout = new Promise<never>((_, reject) => {\n timer = setTimeout(\n () =>\n reject(\n new PreviewRuntimeError(\n \"RENDER_TIMEOUT\",\n `Template rendering exceeded ${this.timeoutMs} ms.`,\n 422,\n ),\n ),\n this.timeoutMs,\n );\n });\n return Promise.race([work, timeout]).finally(() => clearTimeout(timer));\n }\n\n subscribe(listener: (revision: number) => void): () => void {\n this.assertOpen();\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n /** Registers an open event stream so shutdown can end it. */\n trackStream(close: () => void): () => void {\n this.streams.add(close);\n return () => this.streams.delete(close);\n }\n\n async getStaticFile(\n path: string,\n ): Promise<{ data: Uint8Array<ArrayBuffer>; contentType: string }> {\n this.assertOpen();\n return readStaticFile(this.directory, path);\n }\n\n close(): void {\n if (this.closed) return;\n this.closed = true;\n if (this.notification) clearTimeout(this.notification);\n for (const timer of this.rechecks.values()) clearTimeout(timer);\n this.rechecks.clear();\n this.server.watcher.off(\"all\", this.onFileEvent);\n for (const close of this.streams) close();\n this.streams.clear();\n this.listeners.clear();\n }\n}\n","import { fileURLToPath } from \"node:url\";\nimport { createRunnableDevEnvironment } from \"vite\";\nimport type { Plugin } from \"vite\";\nimport { previewMiddleware } from \"./middleware.js\";\nimport { environmentName, loadRenderEntry, resolveRenderEntry } from \"./render.js\";\nimport { PreviewRuntime } from \"./runtime.js\";\n\nexport interface EmailPreviewOptions {\n /** Template directory relative to the Vite root. Defaults to `emails`. */\n dir?: string;\n /** URL prefix for the interface and its API. Defaults to `/__email`. */\n base?: string;\n /** Rendering timeout in milliseconds. Defaults to 10000. */\n renderTimeoutMs?: number;\n}\n\n/** @internal Lets source tests and the package's own development serve another build. */\nexport interface InternalOptions extends EmailPreviewOptions {\n interfaceDirectory?: string;\n}\n\nfunction normalizeBase(base: string): string {\n const value = `/${base.replace(/^\\/+|\\/+$/g, \"\")}/`.replace(/^\\/\\/$/, \"/\");\n if (!/^\\/(?:[\\w.-]+\\/)*$/.test(value)) {\n throw new Error(`Invalid email preview base: ${base}`);\n }\n return value;\n}\n\n/** Adds the React Email preview to a Vite dev server. Builds are unaffected. */\nexport function emailPreview(options: EmailPreviewOptions = {}): Plugin {\n const {\n dir = \"emails\",\n renderTimeoutMs = 10_000,\n interfaceDirectory = fileURLToPath(new URL(\"./ui\", import.meta.url)),\n } = options as InternalOptions;\n const base = normalizeBase(options.base ?? \"/__email\");\n let runtime: Promise<PreviewRuntime> | undefined;\n\n return {\n name: \"raypx-email-preview\",\n apply: \"serve\",\n // Lets tests and cooperating plugins reach the running preview.\n api: { runtime: () => runtime },\n config() {\n return {\n environments: {\n // Templates get their own module graph, so rendering never loads into or\n // invalidates the application's SSR environment.\n [environmentName]: {\n consumer: \"server\",\n resolve: {\n dedupe: [\"react\", \"react-dom\"],\n external: true,\n // Bundled through Vite so the renderer resolves the project's React.\n noExternal: [\"@react-email/render\"],\n },\n dev: {\n createEnvironment: (name, config) => createRunnableDevEnvironment(name, config),\n },\n },\n },\n };\n },\n resolveId(id) {\n return resolveRenderEntry(id);\n },\n load(id) {\n return loadRenderEntry(id);\n },\n configureServer(server) {\n runtime = PreviewRuntime.create(server, { directory: dir, renderTimeoutMs });\n // Keep a failed start observable through each request instead of crashing.\n runtime.catch(() => {});\n server.middlewares.use(previewMiddleware(base, interfaceDirectory, () => runtime!));\n server.httpServer?.once(\"close\", () => void runtime?.then((value) => value.close()));\n },\n async buildEnd() {\n (await runtime?.catch(() => undefined))?.close();\n },\n };\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@raypx-kit/email-preview",
|
|
3
|
-
"version": "0.0.0-alpha.
|
|
3
|
+
"version": "0.0.0-alpha.1",
|
|
4
4
|
"description": "Preview React Email templates in a Vite dev server or a standalone CLI.",
|
|
5
5
|
"homepage": "https://github.com/raypx/raypx-kit/tree/canary/packages/email-preview",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -51,9 +51,9 @@
|
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@playwright/test": "^1.64.1",
|
|
54
|
-
"@raypx-kit/themes": "
|
|
55
|
-
"@raypx-kit/tsconfig": "
|
|
56
|
-
"@raypx-kit/ui": "
|
|
54
|
+
"@raypx-kit/themes": "0.0.0-alpha.0",
|
|
55
|
+
"@raypx-kit/tsconfig": "0.0.0-alpha.1",
|
|
56
|
+
"@raypx-kit/ui": "0.0.0-alpha.0",
|
|
57
57
|
"@react-email/components": "^1.0.12",
|
|
58
58
|
"@tailwindcss/vite": "^4.3.3",
|
|
59
59
|
"@tanstack/react-router": "^1.170.45",
|