@lupinum/nuxt-email 0.1.1 → 1.0.0-beta.2
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 +47 -0
- package/README.md +63 -18
- package/dist/build.d.mts +13 -0
- package/dist/build.mjs +218 -0
- package/dist/module.d.mts +1 -1
- package/dist/module.json +1 -1
- package/dist/module.mjs +54 -145
- package/dist/runtime/components/EHtml.d.ts +3 -1
- package/dist/runtime/components/EHtml.js +12 -2
- package/dist/runtime/components/ELink.js +1 -6
- package/dist/runtime/dev-preview/render.get.d.ts +26 -1
- package/dist/runtime/dev-preview/render.get.js +2 -2
- package/dist/runtime/render/define-email.d.ts +19 -10
- package/dist/runtime/render/define-email.js +21 -4
- package/dist/runtime/render/errors.d.ts +2 -2
- package/dist/runtime/render/errors.js +4 -4
- package/dist/runtime/render/index.d.ts +6 -0
- package/dist/runtime/render/index.js +3 -0
- package/dist/runtime/render/render-email-component.d.ts +3 -6
- package/dist/runtime/render/render-email-component.js +16 -16
- package/dist/runtime/render/types.d.ts +7 -0
- package/dist/runtime/tailwind/css-tree.d.ts +10 -0
- package/dist/runtime/tailwind/css-tree.js +11 -0
- package/dist/runtime/tailwind/engine/css/downlevel-for-email-clients.d.ts +8 -38
- package/dist/runtime/tailwind/engine/css/downlevel-for-email-clients.js +186 -72
- package/dist/runtime/tailwind/engine/css/extract-rules-per-class.d.ts +3 -1
- package/dist/runtime/tailwind/engine/css/extract-rules-per-class.js +9 -2
- package/dist/runtime/tailwind/engine/css/get-custom-properties.d.ts +1 -1
- package/dist/runtime/tailwind/engine/css/get-custom-properties.js +1 -1
- package/dist/runtime/tailwind/engine/css/is-part-inlinable.d.ts +1 -1
- package/dist/runtime/tailwind/engine/css/is-part-inlinable.js +1 -1
- package/dist/runtime/tailwind/engine/css/is-rule-inlinable.d.ts +1 -1
- package/dist/runtime/tailwind/engine/css/is-rule-inlinable.js +1 -1
- package/dist/runtime/tailwind/engine/css/make-inline-styles-for.d.ts +1 -1
- package/dist/runtime/tailwind/engine/css/make-inline-styles-for.js +1 -1
- package/dist/runtime/tailwind/engine/css/resolve-all-css-variables.d.ts +1 -1
- package/dist/runtime/tailwind/engine/css/resolve-all-css-variables.js +1 -1
- package/dist/runtime/tailwind/engine/css/resolve-calc-expressions.d.ts +1 -1
- package/dist/runtime/tailwind/engine/css/resolve-calc-expressions.js +1 -1
- package/dist/runtime/tailwind/engine/css/sanitize-declarations.d.ts +1 -1
- package/dist/runtime/tailwind/engine/css/sanitize-declarations.js +1 -1
- package/dist/runtime/tailwind/engine/css/sanitize-non-inlinable-rules.d.ts +1 -1
- package/dist/runtime/tailwind/engine/css/sanitize-non-inlinable-rules.js +1 -1
- package/dist/runtime/tailwind/engine/css/split-mixed-rule.d.ts +1 -1
- package/dist/runtime/tailwind/engine/css/split-mixed-rule.js +1 -1
- package/dist/runtime/tailwind/engine/css/strip-empty-tailwind-vars.d.ts +1 -1
- package/dist/runtime/tailwind/engine/css/strip-empty-tailwind-vars.js +1 -1
- package/dist/runtime/tailwind/engine/index.d.ts +6 -6
- package/dist/runtime/tailwind/engine/index.js +70 -7
- package/dist/runtime/tailwind/engine/setup-tailwind.d.ts +1 -1
- package/dist/runtime/tailwind/engine/setup-tailwind.js +1 -1
- package/dist/runtime/tailwind/post-render.js +7 -8
- package/dist/shared/nuxt-email.B5ux0eBv.mjs +133 -0
- package/dist/types.d.mts +1 -1
- package/package.json +18 -7
package/dist/module.mjs
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { resolve, isAbsolute, relative } from 'node:path';
|
|
2
|
+
import { defineNuxtModule, createResolver, addServerTemplate, addTypeTemplate, addServerImports, addComponent, addServerHandler, useNitro, updateTemplates } from '@nuxt/kit';
|
|
3
3
|
import vueRollup from 'unplugin-vue/rollup';
|
|
4
4
|
import { stat } from 'node:fs/promises';
|
|
5
5
|
import { EMAIL_COMPONENT_NAMES } from '../dist/runtime/components/email-component-names.js';
|
|
6
|
+
import { g as generateEmailRegistry, a as generateEmailTypes, d as discoverEmailTemplates } from './shared/nuxt-email.B5ux0eBv.mjs';
|
|
6
7
|
|
|
7
8
|
function previewFixturePath(sourcePath) {
|
|
8
9
|
return `${sourcePath.slice(0, -".vue".length)}.fixtures.ts`;
|
|
@@ -26,140 +27,34 @@ async function attachPreviewFixtures(templates) {
|
|
|
26
27
|
}));
|
|
27
28
|
}
|
|
28
29
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
this.name = "DuplicateEmailTemplateError";
|
|
36
|
-
this.templateName = templateName;
|
|
37
|
-
this.sourcePaths = sortedSourcePaths;
|
|
30
|
+
function configureEmailTypeProject(tsConfig, emailDirectory, registryId, registryPath) {
|
|
31
|
+
const normalizedDirectory = emailDirectory.replaceAll("\\", "/");
|
|
32
|
+
const include = tsConfig.include ??= [];
|
|
33
|
+
const emailTypeGlob = `${normalizedDirectory}/**/*.vue`;
|
|
34
|
+
if (!include.includes(emailTypeGlob)) {
|
|
35
|
+
include.push(emailTypeGlob);
|
|
38
36
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
super(`Failed to discover email templates under ${sourcePath}`, { cause });
|
|
44
|
-
this.name = "EmailTemplateDiscoveryError";
|
|
45
|
-
this.sourcePath = sourcePath;
|
|
37
|
+
const exclude = tsConfig.exclude ??= [];
|
|
38
|
+
const emailComponentTypeGlob = `${normalizedDirectory}/components/**/*.vue`;
|
|
39
|
+
if (!exclude.includes(emailComponentTypeGlob)) {
|
|
40
|
+
exclude.push(emailComponentTypeGlob);
|
|
46
41
|
}
|
|
42
|
+
const paths = (tsConfig.compilerOptions ??= {}).paths ??= {};
|
|
43
|
+
paths[registryId] = [registryPath];
|
|
44
|
+
return paths;
|
|
47
45
|
}
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
return left < right ? -1 : left > right ? 1 : 0;
|
|
51
|
-
}
|
|
52
|
-
function normalizeSeparators(value) {
|
|
53
|
-
return value.split(sep).join("/").replaceAll("\\", "/");
|
|
46
|
+
function previewURL(baseURL) {
|
|
47
|
+
return `${baseURL.endsWith("/") ? baseURL : `${baseURL}/`}__email`;
|
|
54
48
|
}
|
|
55
|
-
function
|
|
56
|
-
const
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
49
|
+
function relativeEmailPath(emailDirectory, path, roots) {
|
|
50
|
+
const candidates = isAbsolute(path) ? [path] : roots.map((root) => resolve(root, path));
|
|
51
|
+
for (const candidate of candidates) {
|
|
52
|
+
const relativePath = relative(emailDirectory, candidate).replaceAll("\\", "/");
|
|
53
|
+
if (relativePath !== ".." && !relativePath.startsWith("../") && !isAbsolute(relativePath)) {
|
|
54
|
+
return relativePath;
|
|
60
55
|
}
|
|
61
|
-
return {
|
|
62
|
-
name: relativePath.slice(0, -".vue".length),
|
|
63
|
-
sourcePath: normalizeSeparators(sourcePath)
|
|
64
|
-
};
|
|
65
|
-
}).sort((left, right) => {
|
|
66
|
-
return compareCodePoints(left.name, right.name) || compareCodePoints(left.sourcePath, right.sourcePath);
|
|
67
|
-
});
|
|
68
|
-
const pathsByName = /* @__PURE__ */ new Map();
|
|
69
|
-
for (const template of templates) {
|
|
70
|
-
const paths = pathsByName.get(template.name) ?? [];
|
|
71
|
-
paths.push(template.sourcePath);
|
|
72
|
-
pathsByName.set(template.name, paths);
|
|
73
56
|
}
|
|
74
|
-
const duplicate = [...pathsByName.entries()].filter(([, paths]) => paths.length > 1).sort(([left], [right]) => compareCodePoints(left, right))[0];
|
|
75
|
-
if (duplicate) {
|
|
76
|
-
throw new DuplicateEmailTemplateError(duplicate[0], duplicate[1]);
|
|
77
|
-
}
|
|
78
|
-
return templates;
|
|
79
57
|
}
|
|
80
|
-
async function discoverEmailTemplates(emailDirectory) {
|
|
81
|
-
let sourcePaths;
|
|
82
|
-
try {
|
|
83
|
-
sourcePaths = await resolveFiles(emailDirectory, "**/*.vue", {
|
|
84
|
-
followSymbolicLinks: false,
|
|
85
|
-
ignore: ["components/**"]
|
|
86
|
-
});
|
|
87
|
-
} catch (error) {
|
|
88
|
-
throw new EmailTemplateDiscoveryError(emailDirectory, error);
|
|
89
|
-
}
|
|
90
|
-
return templatesFromSourcePaths(emailDirectory, sourcePaths);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function importPath(path) {
|
|
94
|
-
return JSON.stringify(path.replaceAll("\\", "/"));
|
|
95
|
-
}
|
|
96
|
-
function generateEmailRegistry(templates, runtimePaths) {
|
|
97
|
-
const entries = templates.map((template) => {
|
|
98
|
-
const fixture = template.fixturePath ? `
|
|
99
|
-
fixture: () => import(${importPath(template.fixturePath)}),` : "";
|
|
100
|
-
return ` [${JSON.stringify(template.name)}]: {
|
|
101
|
-
component: () => import(${importPath(template.sourcePath)}),${fixture}
|
|
102
|
-
},`;
|
|
103
|
-
});
|
|
104
|
-
return `import { EmailRenderError } from ${importPath(runtimePaths.emailRenderError)}
|
|
105
|
-
import { renderEmailComponent } from ${importPath(runtimePaths.renderEmailComponent)}
|
|
106
|
-
import { UnknownEmailTemplateError } from ${importPath(runtimePaths.templateRegistryErrors)}
|
|
107
|
-
|
|
108
|
-
export const emailTemplates = Object.freeze({
|
|
109
|
-
${entries.join("\n")}
|
|
110
|
-
})
|
|
111
|
-
|
|
112
|
-
export async function renderEmail(name, props) {
|
|
113
|
-
const loader = typeof name === 'string' && Object.hasOwn(emailTemplates, name)
|
|
114
|
-
? emailTemplates[name]
|
|
115
|
-
: undefined
|
|
116
|
-
if (!loader) {
|
|
117
|
-
throw new UnknownEmailTemplateError(String(name), Object.keys(emailTemplates))
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
try {
|
|
121
|
-
const templateModule = await loader.component()
|
|
122
|
-
return await renderEmailComponent(templateModule.default, props)
|
|
123
|
-
}
|
|
124
|
-
catch (error) {
|
|
125
|
-
throw new EmailRenderError(name, error instanceof EmailRenderError ? error.cause : error)
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
`;
|
|
129
|
-
}
|
|
130
|
-
function generateEmailTypes(templates, typePaths) {
|
|
131
|
-
const entries = templates.map((template) => {
|
|
132
|
-
const fixture = template.fixturePath ? `
|
|
133
|
-
fixture: () => Promise<typeof import(${importPath(template.fixturePath)})>` : "";
|
|
134
|
-
return ` ${JSON.stringify(template.name)}: {
|
|
135
|
-
component: () => Promise<typeof import(${importPath(template.sourcePath)})>${fixture}
|
|
136
|
-
}`;
|
|
137
|
-
});
|
|
138
|
-
return `export const emailTemplates: Readonly<{
|
|
139
|
-
${entries.join("\n")}
|
|
140
|
-
}>
|
|
141
|
-
|
|
142
|
-
export type EmailTemplateName = keyof typeof emailTemplates
|
|
143
|
-
type _EmailComponent = abstract new (...args: never[]) => { $props: object }
|
|
144
|
-
type _DeclaredEmailProps<Component extends _EmailComponent> = Omit<
|
|
145
|
-
InstanceType<Component>['$props'],
|
|
146
|
-
keyof import('vue').PublicProps
|
|
147
|
-
>
|
|
148
|
-
type _EmailProps<Component extends _EmailComponent> = keyof _DeclaredEmailProps<Component> extends never
|
|
149
|
-
? Record<string, never>
|
|
150
|
-
: _DeclaredEmailProps<Component>
|
|
151
|
-
|
|
152
|
-
export type EmailTemplateProps = {
|
|
153
|
-
[Name in EmailTemplateName]: _EmailProps<Awaited<ReturnType<(typeof emailTemplates)[Name]['component']>>['default']>
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
export function renderEmail<Name extends EmailTemplateName>(
|
|
157
|
-
name: Name,
|
|
158
|
-
props: NoInfer<EmailTemplateProps[Name]>,
|
|
159
|
-
): Promise<import(${importPath(typePaths.renderedEmail)}).RenderedEmail>
|
|
160
|
-
`;
|
|
161
|
-
}
|
|
162
|
-
|
|
163
58
|
const module$1 = defineNuxtModule({
|
|
164
59
|
meta: {
|
|
165
60
|
name: "@lupinum/nuxt-email",
|
|
@@ -176,10 +71,12 @@ const module$1 = defineNuxtModule({
|
|
|
176
71
|
const resolver = createResolver(import.meta.url);
|
|
177
72
|
const defineEmailPublicPath = await resolver.resolvePath("./runtime/define-email");
|
|
178
73
|
const errorsPublicPath = await resolver.resolvePath("./runtime/errors");
|
|
179
|
-
let renderEmailComponentPath = await resolver.resolvePath("./runtime/
|
|
74
|
+
let renderEmailComponentPath = await resolver.resolvePath("./runtime/render/index");
|
|
75
|
+
let testingRendererPath = await resolver.resolvePath("./runtime/testing");
|
|
180
76
|
if (options.codeBlock !== void 0) {
|
|
181
77
|
const { setupCodeBlock } = await import('./chunks/setup.mjs');
|
|
182
78
|
renderEmailComponentPath = setupCodeBlock(options.codeBlock, resolver);
|
|
79
|
+
testingRendererPath = renderEmailComponentPath;
|
|
183
80
|
}
|
|
184
81
|
const emailDirectory = resolve(nuxt.options.srcDir, "emails");
|
|
185
82
|
const loadTemplates = async () => {
|
|
@@ -205,6 +102,9 @@ const module$1 = defineNuxtModule({
|
|
|
205
102
|
write: true,
|
|
206
103
|
getContents: () => generateEmailTypes(templates, registryTypePaths)
|
|
207
104
|
}, { nitro: true });
|
|
105
|
+
nuxt.hook("prepare:types", ({ tsConfig: tsConfig2 }) => {
|
|
106
|
+
configureEmailTypeProject(tsConfig2, emailDirectory, registryId, typeTemplate.dst);
|
|
107
|
+
});
|
|
208
108
|
addServerImports([
|
|
209
109
|
{
|
|
210
110
|
name: "renderEmail",
|
|
@@ -220,6 +120,17 @@ const module$1 = defineNuxtModule({
|
|
|
220
120
|
});
|
|
221
121
|
}
|
|
222
122
|
if (nuxt.options.dev) {
|
|
123
|
+
nuxt.hook("devtools:customTabs", (tabs) => {
|
|
124
|
+
tabs.push({
|
|
125
|
+
name: "nuxt-email",
|
|
126
|
+
title: "Nuxt Email",
|
|
127
|
+
icon: "i-lucide-mail",
|
|
128
|
+
view: {
|
|
129
|
+
type: "iframe",
|
|
130
|
+
src: previewURL(nuxt.options.app.baseURL)
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
});
|
|
223
134
|
addServerHandler({
|
|
224
135
|
route: "/__email",
|
|
225
136
|
method: "get",
|
|
@@ -250,21 +161,21 @@ const module$1 = defineNuxtModule({
|
|
|
250
161
|
let registryUpdates = Promise.resolve();
|
|
251
162
|
nuxt.hook("builder:watch", (event, path) => {
|
|
252
163
|
const structureChanged = ["add", "unlink", "addDir", "unlinkDir"].includes(event);
|
|
253
|
-
if (
|
|
164
|
+
if (!structureChanged) {
|
|
254
165
|
return;
|
|
255
166
|
}
|
|
256
|
-
const
|
|
257
|
-
|
|
258
|
-
|
|
167
|
+
const relativePath = relativeEmailPath(emailDirectory, path, [
|
|
168
|
+
nuxt.options.rootDir,
|
|
169
|
+
nuxt.options.srcDir
|
|
170
|
+
]);
|
|
171
|
+
if (relativePath === void 0) {
|
|
259
172
|
return;
|
|
260
173
|
}
|
|
261
174
|
const update = async () => {
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
});
|
|
267
|
-
}
|
|
175
|
+
templates = await loadTemplates();
|
|
176
|
+
await updateTemplates({
|
|
177
|
+
filter: (template) => template.filename === typeTemplate.filename
|
|
178
|
+
});
|
|
268
179
|
await reloadRegistry();
|
|
269
180
|
};
|
|
270
181
|
const queuedUpdate = registryUpdates.then(update, update);
|
|
@@ -275,7 +186,7 @@ const module$1 = defineNuxtModule({
|
|
|
275
186
|
const nuxtOptions = nuxt.options;
|
|
276
187
|
const nitro = nuxtOptions.nitro ??= {};
|
|
277
188
|
const alias = nitro.alias ??= {};
|
|
278
|
-
alias["#nuxt-email/testing"] =
|
|
189
|
+
alias["#nuxt-email/testing"] = testingRendererPath;
|
|
279
190
|
alias["@lupinum/nuxt-email/define-email"] = defineEmailPublicPath;
|
|
280
191
|
alias["@lupinum/nuxt-email/errors"] = errorsPublicPath;
|
|
281
192
|
const externals = nitro.externals ??= {};
|
|
@@ -287,10 +198,8 @@ const module$1 = defineNuxtModule({
|
|
|
287
198
|
}
|
|
288
199
|
const typescript = nitro.typescript ??= {};
|
|
289
200
|
const tsConfig = typescript.tsConfig ??= {};
|
|
290
|
-
const
|
|
291
|
-
|
|
292
|
-
paths[registryId] = [typeTemplate.dst];
|
|
293
|
-
paths["#nuxt-email/testing"] = [renderEmailComponentPath];
|
|
201
|
+
const paths = configureEmailTypeProject(tsConfig, emailDirectory, registryId, typeTemplate.dst);
|
|
202
|
+
paths["#nuxt-email/testing"] = [testingRendererPath];
|
|
294
203
|
nitro.rollupConfig ??= {};
|
|
295
204
|
const existingPlugins = nitro.rollupConfig.plugins;
|
|
296
205
|
nitro.rollupConfig.plugins = existingPlugins ? [existingPlugins, vueRollup()] : [vueRollup()];
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { DefineComponent, HTMLAttributes } from 'vue';
|
|
2
2
|
import type { SafeEmailAttributes } from './attributes.js';
|
|
3
|
-
export type EHtmlProps = SafeEmailAttributes<HTMLAttributes
|
|
3
|
+
export type EHtmlProps = Omit<SafeEmailAttributes<HTMLAttributes>, 'lang'> & {
|
|
4
|
+
lang: string;
|
|
5
|
+
};
|
|
4
6
|
export declare const EHtml: DefineComponent<EHtmlProps>;
|
|
@@ -3,10 +3,20 @@ import { assertSafeEmailAttributes } from "./attributes.js";
|
|
|
3
3
|
export const EHtml = defineComponent({
|
|
4
4
|
name: "EHtml",
|
|
5
5
|
inheritAttrs: false,
|
|
6
|
-
|
|
6
|
+
props: {
|
|
7
|
+
lang: {
|
|
8
|
+
type: String,
|
|
9
|
+
required: true
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
setup(props, { attrs, slots }) {
|
|
7
13
|
return () => {
|
|
8
14
|
assertSafeEmailAttributes("EHtml", attrs);
|
|
9
|
-
const { dir = "ltr",
|
|
15
|
+
const { dir = "ltr", ...attributes } = attrs;
|
|
16
|
+
const { lang } = props;
|
|
17
|
+
if (typeof lang !== "string" || lang.trim().length === 0) {
|
|
18
|
+
throw new TypeError("EHtml lang must be a non-empty string");
|
|
19
|
+
}
|
|
10
20
|
return h("html", {
|
|
11
21
|
...attributes,
|
|
12
22
|
dir,
|
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
import { defineComponent, h, inject } from "vue";
|
|
2
2
|
import { resolveNestedTailwindStyle, TAILWIND_NESTED_KEY } from "../tailwind/nested.js";
|
|
3
3
|
import { assertSafeEmailAttributes } from "./attributes.js";
|
|
4
|
-
import { mergeEmailStyles } from "./style.js";
|
|
5
|
-
const DEFAULT_LINK_STYLE = {
|
|
6
|
-
color: "#067df7",
|
|
7
|
-
textDecorationLine: "none"
|
|
8
|
-
};
|
|
9
4
|
export const ELink = defineComponent({
|
|
10
5
|
name: "ELink",
|
|
11
6
|
inheritAttrs: false,
|
|
@@ -27,7 +22,7 @@ export const ELink = defineComponent({
|
|
|
27
22
|
return h("a", {
|
|
28
23
|
...attributes,
|
|
29
24
|
href: props.href,
|
|
30
|
-
style:
|
|
25
|
+
...effectiveStyle === void 0 || effectiveStyle === null ? {} : { style: effectiveStyle },
|
|
31
26
|
target: attrs.target ?? "_blank"
|
|
32
27
|
}, slots.default?.());
|
|
33
28
|
};
|
|
@@ -1,3 +1,28 @@
|
|
|
1
1
|
export declare const PREVIEW_RENDER_CSP: string;
|
|
2
|
-
|
|
2
|
+
interface SerializedPreviewError {
|
|
3
|
+
name: string;
|
|
4
|
+
message: string;
|
|
5
|
+
stack?: string;
|
|
6
|
+
templateName?: string;
|
|
7
|
+
requestedName?: string;
|
|
8
|
+
knownNames?: readonly string[];
|
|
9
|
+
cause?: SerializedPreviewError;
|
|
10
|
+
}
|
|
11
|
+
declare const _default: import("h3").EventHandler<import("h3").EventHandlerRequest, Promise<string | {
|
|
12
|
+
bytes: any;
|
|
13
|
+
html: string;
|
|
14
|
+
text: string;
|
|
15
|
+
subject?: string;
|
|
16
|
+
name: string;
|
|
17
|
+
statusCode?: undefined;
|
|
18
|
+
statusMessage?: undefined;
|
|
19
|
+
data?: undefined;
|
|
20
|
+
} | {
|
|
21
|
+
statusCode: number;
|
|
22
|
+
statusMessage: string;
|
|
23
|
+
data: {
|
|
24
|
+
templateName: string | undefined;
|
|
25
|
+
error: SerializedPreviewError;
|
|
26
|
+
};
|
|
27
|
+
}>>;
|
|
3
28
|
export default _default;
|
|
@@ -45,8 +45,8 @@ function serializeError(error, seen = /* @__PURE__ */ new Set()) {
|
|
|
45
45
|
if (error.stack) {
|
|
46
46
|
serialized.stack = error.stack;
|
|
47
47
|
}
|
|
48
|
-
if ("
|
|
49
|
-
serialized.
|
|
48
|
+
if ("templateName" in error && typeof error.templateName === "string") {
|
|
49
|
+
serialized.templateName = error.templateName;
|
|
50
50
|
}
|
|
51
51
|
if ("requestedName" in error && typeof error.requestedName === "string") {
|
|
52
52
|
serialized.requestedName = error.requestedName;
|
|
@@ -2,12 +2,18 @@ import type { TailwindRegion } from '../tailwind/nested.js';
|
|
|
2
2
|
/**
|
|
3
3
|
* Options declared by an email template via {@link defineEmail}.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
* value and
|
|
5
|
+
* String values are constant metadata. Functions are zero-argument closures
|
|
6
|
+
* over the template's real `defineProps()` value and run synchronously after
|
|
7
|
+
* the HTML render completes.
|
|
7
8
|
*/
|
|
8
|
-
export
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
export type EmailMetadataValue = string | (() => string);
|
|
10
|
+
export type DefineEmailOptions = {
|
|
11
|
+
subject: EmailMetadataValue;
|
|
12
|
+
text?: EmailMetadataValue;
|
|
13
|
+
} | {
|
|
14
|
+
subject?: EmailMetadataValue;
|
|
15
|
+
text: EmailMetadataValue;
|
|
16
|
+
};
|
|
11
17
|
declare const EMAIL_RENDER_BRAND: unique symbol;
|
|
12
18
|
/**
|
|
13
19
|
* Per-render registry created by the renderer. A fresh instance is created for
|
|
@@ -16,7 +22,10 @@ declare const EMAIL_RENDER_BRAND: unique symbol;
|
|
|
16
22
|
*/
|
|
17
23
|
export interface EmailRenderContext {
|
|
18
24
|
readonly [EMAIL_RENDER_BRAND]: true;
|
|
19
|
-
|
|
25
|
+
metadata?: {
|
|
26
|
+
subject?: () => string;
|
|
27
|
+
text?: () => string;
|
|
28
|
+
};
|
|
20
29
|
/**
|
|
21
30
|
* Tailwind regions registered by `<ETailwind>` boundaries during render, in
|
|
22
31
|
* registration order. Consumed by the post-render pass to complete each head
|
|
@@ -44,10 +53,10 @@ export declare function getEmailRenderContext(): EmailRenderContext | undefined;
|
|
|
44
53
|
*/
|
|
45
54
|
export declare function runWithEmailRenderContext<T>(context: EmailRenderContext, fn: () => T): T;
|
|
46
55
|
/**
|
|
47
|
-
* Declare
|
|
48
|
-
* `<script setup>`.
|
|
49
|
-
*
|
|
50
|
-
*
|
|
56
|
+
* Declare subject and/or authored plain text from an email template's
|
|
57
|
+
* `<script setup>`. A zero-argument closure captures the template's real
|
|
58
|
+
* `defineProps()` value, so there is no separate generic that can lie about the
|
|
59
|
+
* template contract.
|
|
51
60
|
*
|
|
52
61
|
* Must run during an email render; calling it elsewhere throws
|
|
53
62
|
* {@link DefineEmailOutsideRenderError}.
|
|
@@ -30,11 +30,28 @@ export function defineEmail(options) {
|
|
|
30
30
|
if (!isEmailRenderContext(context)) {
|
|
31
31
|
throw new DefineEmailOutsideRenderError();
|
|
32
32
|
}
|
|
33
|
-
if (context.
|
|
33
|
+
if (context.metadata) {
|
|
34
34
|
throw new DuplicateEmailDefinitionError();
|
|
35
35
|
}
|
|
36
|
-
if (!options || typeof options
|
|
37
|
-
throw new TypeError("defineEmail() subject
|
|
36
|
+
if (!options || typeof options !== "object") {
|
|
37
|
+
throw new TypeError("defineEmail() requires subject or text metadata.");
|
|
38
38
|
}
|
|
39
|
-
|
|
39
|
+
const keys = Object.keys(options);
|
|
40
|
+
const unsupportedKeys = keys.filter((key) => key !== "subject" && key !== "text");
|
|
41
|
+
if (unsupportedKeys.length > 0) {
|
|
42
|
+
throw new TypeError(`defineEmail() received unsupported metadata: ${unsupportedKeys.sort().join(", ")}.`);
|
|
43
|
+
}
|
|
44
|
+
if (options.subject === void 0 && options.text === void 0) {
|
|
45
|
+
throw new TypeError("defineEmail() requires subject or text metadata.");
|
|
46
|
+
}
|
|
47
|
+
const toFactory = (name, value) => {
|
|
48
|
+
if (value === void 0) return void 0;
|
|
49
|
+
if (typeof value === "string") return () => value;
|
|
50
|
+
if (typeof value === "function") return value;
|
|
51
|
+
throw new TypeError(`defineEmail() ${name} must be a string or a function returning a string.`);
|
|
52
|
+
};
|
|
53
|
+
context.metadata = {
|
|
54
|
+
subject: toFactory("subject", options.subject),
|
|
55
|
+
text: toFactory("text", options.text)
|
|
56
|
+
};
|
|
40
57
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
export class EmailRenderError extends Error {
|
|
2
|
-
|
|
3
|
-
constructor(
|
|
4
|
-
super(`Failed to render email
|
|
2
|
+
templateName;
|
|
3
|
+
constructor(templateName, cause) {
|
|
4
|
+
super(`Failed to render email template ${templateName}`, { cause });
|
|
5
5
|
this.name = "EmailRenderError";
|
|
6
|
-
this.
|
|
6
|
+
this.templateName = templateName;
|
|
7
7
|
}
|
|
8
8
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Server-only production rendering of already compiled Vue components. */
|
|
2
|
+
export { renderEmailComponent } from './render-email-component.js';
|
|
3
|
+
export { EmailRenderError } from './errors.js';
|
|
4
|
+
export { UnknownEmailTemplateError } from '../template-registry/errors.js';
|
|
5
|
+
export type { EmailComponentProps, RenderedEmail } from './types.js';
|
|
6
|
+
export type EmailComponents = typeof import('../components/email-component-registry').emailComponentRegistry;
|
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
import type { Component
|
|
1
|
+
import type { Component } from 'vue';
|
|
2
2
|
import type { EmailComponentRegistry } from './render-component.js';
|
|
3
|
-
import type { RenderedEmail } from './types.js';
|
|
4
|
-
type DeclaredComponentProps<ComponentType extends Component> = ComponentType extends abstract new (...args: infer _Arguments) => infer Instance ? Instance extends {
|
|
5
|
-
$props: infer Props;
|
|
6
|
-
} ? Omit<Props, keyof PublicProps> : Record<string, unknown> : ComponentType extends FunctionalComponent<infer Props> ? Props : Record<string, unknown>;
|
|
3
|
+
import type { EmailComponentProps, RenderedEmail } from './types.js';
|
|
7
4
|
type RequiredPropKeys<Props> = {
|
|
8
5
|
[Key in keyof Props]-?: Record<never, never> extends Pick<Props, Key> ? never : Key;
|
|
9
6
|
}[keyof Props];
|
|
10
|
-
type RenderEmailComponentArguments<ComponentType extends Component> = keyof
|
|
7
|
+
type RenderEmailComponentArguments<ComponentType extends Component> = keyof EmailComponentProps<ComponentType> extends never ? [props?: Record<string, never>] : RequiredPropKeys<EmailComponentProps<ComponentType>> extends never ? [props?: EmailComponentProps<ComponentType>] : [props: EmailComponentProps<ComponentType>];
|
|
11
8
|
export interface RenderEmailComponent {
|
|
12
9
|
<ComponentType extends Component>(component: ComponentType, ...args: RenderEmailComponentArguments<ComponentType>): Promise<RenderedEmail>;
|
|
13
10
|
}
|
|
@@ -4,7 +4,7 @@ import { assertCompleteEmailDocument } from "./document.js";
|
|
|
4
4
|
import { EmailRenderError } from "./errors.js";
|
|
5
5
|
import { renderPlainText } from "./plain-text.js";
|
|
6
6
|
import { renderComponentToHtml } from "./render-component.js";
|
|
7
|
-
function
|
|
7
|
+
function templateName(component) {
|
|
8
8
|
if (typeof component === "object" && component !== null || typeof component === "function") {
|
|
9
9
|
if ("name" in component && typeof component.name === "string" && component.name.length > 0) {
|
|
10
10
|
return component.name;
|
|
@@ -22,24 +22,24 @@ export function createRenderEmailComponent(componentRegistry) {
|
|
|
22
22
|
const context = createEmailRenderContext();
|
|
23
23
|
const html = await renderComponentToHtml(component, props, context, componentRegistry);
|
|
24
24
|
assertCompleteEmailDocument(html);
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
return {
|
|
25
|
+
const resolveMetadata = (name, factory) => {
|
|
26
|
+
if (!factory) return void 0;
|
|
27
|
+
const value = factory();
|
|
28
|
+
if (typeof value !== "string") {
|
|
29
|
+
throw new TypeError(`defineEmail() ${name} must return a string; received ${typeof value}`);
|
|
30
|
+
}
|
|
31
|
+
return value;
|
|
32
|
+
};
|
|
33
|
+
const subject = resolveMetadata("subject", context.metadata?.subject);
|
|
34
|
+
const text = resolveMetadata("text", context.metadata?.text) ?? renderPlainText(html);
|
|
35
|
+
const result = {
|
|
37
36
|
html,
|
|
38
|
-
text
|
|
39
|
-
subject
|
|
37
|
+
text
|
|
40
38
|
};
|
|
39
|
+
if (subject !== void 0) result.subject = subject;
|
|
40
|
+
return result;
|
|
41
41
|
} catch (error) {
|
|
42
|
-
throw new EmailRenderError(
|
|
42
|
+
throw new EmailRenderError(templateName(component), error);
|
|
43
43
|
}
|
|
44
44
|
};
|
|
45
45
|
}
|
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import type { FunctionalComponent, PublicProps } from 'vue';
|
|
2
|
+
type VueListenerProp = `on${Capitalize<string>}`;
|
|
3
|
+
type EmailAuthoringProps<Props> = string extends keyof Props ? Props : Omit<Props, keyof PublicProps | VueListenerProp>;
|
|
4
|
+
export type EmailComponentProps<ComponentType> = ComponentType extends abstract new (...args: never[]) => {
|
|
5
|
+
$props: infer Props;
|
|
6
|
+
} ? EmailAuthoringProps<Props> : ComponentType extends FunctionalComponent<infer Props> ? EmailAuthoringProps<Props> : never;
|
|
1
7
|
export interface RenderedEmail {
|
|
2
8
|
html: string;
|
|
3
9
|
text: string;
|
|
@@ -7,3 +13,4 @@ export interface RenderedEmail {
|
|
|
7
13
|
*/
|
|
8
14
|
subject?: string;
|
|
9
15
|
}
|
|
16
|
+
export {};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type * as CssTree from 'css-tree';
|
|
2
|
+
export declare const parse: typeof CssTree.parse;
|
|
3
|
+
export declare const generate: typeof CssTree.generate;
|
|
4
|
+
export declare const walk: typeof CssTree.walk;
|
|
5
|
+
export declare const find: typeof CssTree.find;
|
|
6
|
+
export declare const clone: typeof CssTree.clone;
|
|
7
|
+
export declare const List: typeof CssTree.List;
|
|
8
|
+
export type List<T> = CssTree.List<T>;
|
|
9
|
+
export declare const string: typeof CssTree.string;
|
|
10
|
+
export type * from 'css-tree';
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import parser from "css-tree/parser";
|
|
2
|
+
import generator from "css-tree/generator";
|
|
3
|
+
import walker from "css-tree/walker";
|
|
4
|
+
import { clone as cloneNode, List as NodeList, string as cssString } from "css-tree/utils";
|
|
5
|
+
export const parse = parser;
|
|
6
|
+
export const generate = generator;
|
|
7
|
+
export const walk = walker;
|
|
8
|
+
export const find = walker.find;
|
|
9
|
+
export const clone = cloneNode;
|
|
10
|
+
export const List = NodeList;
|
|
11
|
+
export const string = cssString;
|