@lupinum/nuxt-email 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +49 -0
- package/LICENSE +21 -0
- package/README.md +172 -0
- package/THIRD_PARTY_NOTICES +75 -0
- package/dist/chunks/setup.mjs +149 -0
- package/dist/module.d.mts +15 -0
- package/dist/module.json +12 -0
- package/dist/module.mjs +300 -0
- package/dist/runtime/code-block/create-component.d.ts +16 -0
- package/dist/runtime/code-block/create-component.js +106 -0
- package/dist/runtime/components/EBody.d.ts +4 -0
- package/dist/runtime/components/EBody.js +75 -0
- package/dist/runtime/components/EButton.d.ts +11 -0
- package/dist/runtime/components/EButton.js +89 -0
- package/dist/runtime/components/EButton.padding.d.ts +12 -0
- package/dist/runtime/components/EButton.padding.js +118 -0
- package/dist/runtime/components/ECodeInline.d.ts +13 -0
- package/dist/runtime/components/ECodeInline.js +43 -0
- package/dist/runtime/components/EColumn.d.ts +4 -0
- package/dist/runtime/components/EColumn.js +12 -0
- package/dist/runtime/components/EContainer.d.ts +4 -0
- package/dist/runtime/components/EContainer.js +35 -0
- package/dist/runtime/components/EFont.d.ts +24 -0
- package/dist/runtime/components/EFont.js +124 -0
- package/dist/runtime/components/EHead.d.ts +4 -0
- package/dist/runtime/components/EHead.js +17 -0
- package/dist/runtime/components/EHeading.d.ts +7 -0
- package/dist/runtime/components/EHeading.js +23 -0
- package/dist/runtime/components/EHr.d.ts +4 -0
- package/dist/runtime/components/EHr.js +26 -0
- package/dist/runtime/components/EHtml.d.ts +4 -0
- package/dist/runtime/components/EHtml.js +17 -0
- package/dist/runtime/components/EImg.d.ts +7 -0
- package/dist/runtime/components/EImg.js +44 -0
- package/dist/runtime/components/ELink.d.ts +6 -0
- package/dist/runtime/components/ELink.js +35 -0
- package/dist/runtime/components/EMarkdown.d.ts +8 -0
- package/dist/runtime/components/EMarkdown.js +64 -0
- package/dist/runtime/components/EPreview.d.ts +8 -0
- package/dist/runtime/components/EPreview.js +85 -0
- package/dist/runtime/components/ERow.d.ts +4 -0
- package/dist/runtime/components/ERow.js +53 -0
- package/dist/runtime/components/ESection.d.ts +4 -0
- package/dist/runtime/components/ESection.js +35 -0
- package/dist/runtime/components/ETailwind.d.ts +24 -0
- package/dist/runtime/components/ETailwind.js +49 -0
- package/dist/runtime/components/EText.d.ts +4 -0
- package/dist/runtime/components/EText.js +20 -0
- package/dist/runtime/components/EText.margins.d.ts +18 -0
- package/dist/runtime/components/EText.margins.js +81 -0
- package/dist/runtime/components/attributes.d.ts +4 -0
- package/dist/runtime/components/attributes.js +11 -0
- package/dist/runtime/components/email-component-names.d.ts +2 -0
- package/dist/runtime/components/email-component-names.js +20 -0
- package/dist/runtime/components/email-component-registry.d.ts +8 -0
- package/dist/runtime/components/email-component-registry.js +2 -0
- package/dist/runtime/components/index.d.ts +23 -0
- package/dist/runtime/components/index.js +18 -0
- package/dist/runtime/components/markdown/parse-css-in-js-to-inline-css.d.ts +2 -0
- package/dist/runtime/components/markdown/parse-css-in-js-to-inline-css.js +60 -0
- package/dist/runtime/components/markdown/render-markdown.d.ts +2 -0
- package/dist/runtime/components/markdown/render-markdown.js +137 -0
- package/dist/runtime/components/markdown/styles.d.ts +30 -0
- package/dist/runtime/components/markdown/styles.js +91 -0
- package/dist/runtime/components/presentation-table.d.ts +7 -0
- package/dist/runtime/components/presentation-table.js +11 -0
- package/dist/runtime/components/style.d.ts +5 -0
- package/dist/runtime/components/style.js +32 -0
- package/dist/runtime/components/table-padding.d.ts +6 -0
- package/dist/runtime/components/table-padding.js +20 -0
- package/dist/runtime/define-email.d.ts +2 -0
- package/dist/runtime/define-email.js +5 -0
- package/dist/runtime/dev-preview/page-script.d.ts +1 -0
- package/dist/runtime/dev-preview/page-script.js +403 -0
- package/dist/runtime/dev-preview/page.css.d.ts +1 -0
- package/dist/runtime/dev-preview/page.css.js +602 -0
- package/dist/runtime/dev-preview/page.get.d.ts +4 -0
- package/dist/runtime/dev-preview/page.get.js +119 -0
- package/dist/runtime/dev-preview/render.get.d.ts +3 -0
- package/dist/runtime/dev-preview/render.get.js +123 -0
- package/dist/runtime/dev-preview/templates.get.d.ts +7 -0
- package/dist/runtime/dev-preview/templates.get.js +14 -0
- package/dist/runtime/errors.d.ts +4 -0
- package/dist/runtime/errors.js +7 -0
- package/dist/runtime/render/define-email.d.ts +56 -0
- package/dist/runtime/render/define-email.js +40 -0
- package/dist/runtime/render/document.d.ts +4 -0
- package/dist/runtime/render/document.js +75 -0
- package/dist/runtime/render/errors.d.ts +4 -0
- package/dist/runtime/render/errors.js +8 -0
- package/dist/runtime/render/plain-text.d.ts +1 -0
- package/dist/runtime/render/plain-text.js +15 -0
- package/dist/runtime/render/render-component.d.ts +4 -0
- package/dist/runtime/render/render-component.js +46 -0
- package/dist/runtime/render/render-email-component.d.ts +16 -0
- package/dist/runtime/render/render-email-component.js +46 -0
- package/dist/runtime/render/types.d.ts +9 -0
- package/dist/runtime/render/types.js +0 -0
- package/dist/runtime/tailwind/engine/compatibility/escape-class-name.d.ts +8 -0
- package/dist/runtime/tailwind/engine/compatibility/escape-class-name.js +10 -0
- package/dist/runtime/tailwind/engine/compatibility/sanitize-class-name.d.ts +7 -0
- package/dist/runtime/tailwind/engine/compatibility/sanitize-class-name.js +17 -0
- package/dist/runtime/tailwind/engine/compatibility/unescape-class.d.ts +1 -0
- package/dist/runtime/tailwind/engine/compatibility/unescape-class.js +3 -0
- package/dist/runtime/tailwind/engine/css/downlevel-for-email-clients.d.ts +63 -0
- package/dist/runtime/tailwind/engine/css/downlevel-for-email-clients.js +107 -0
- package/dist/runtime/tailwind/engine/css/extract-rules-per-class.d.ts +6 -0
- package/dist/runtime/tailwind/engine/css/extract-rules-per-class.js +54 -0
- package/dist/runtime/tailwind/engine/css/get-css-property.d.ts +17 -0
- package/dist/runtime/tailwind/engine/css/get-css-property.js +3 -0
- package/dist/runtime/tailwind/engine/css/get-custom-properties.d.ts +8 -0
- package/dist/runtime/tailwind/engine/css/get-custom-properties.js +37 -0
- package/dist/runtime/tailwind/engine/css/is-part-inlinable.d.ts +2 -0
- package/dist/runtime/tailwind/engine/css/is-part-inlinable.js +9 -0
- package/dist/runtime/tailwind/engine/css/is-rule-inlinable.d.ts +2 -0
- package/dist/runtime/tailwind/engine/css/is-rule-inlinable.js +9 -0
- package/dist/runtime/tailwind/engine/css/make-inline-styles-for.d.ts +21 -0
- package/dist/runtime/tailwind/engine/css/make-inline-styles-for.js +62 -0
- package/dist/runtime/tailwind/engine/css/resolve-all-css-variables.d.ts +8 -0
- package/dist/runtime/tailwind/engine/css/resolve-all-css-variables.js +138 -0
- package/dist/runtime/tailwind/engine/css/resolve-calc-expressions.d.ts +5 -0
- package/dist/runtime/tailwind/engine/css/resolve-calc-expressions.js +101 -0
- package/dist/runtime/tailwind/engine/css/sanitize-declarations.d.ts +15 -0
- package/dist/runtime/tailwind/engine/css/sanitize-declarations.js +358 -0
- package/dist/runtime/tailwind/engine/css/sanitize-non-inlinable-rules.d.ts +17 -0
- package/dist/runtime/tailwind/engine/css/sanitize-non-inlinable-rules.js +30 -0
- package/dist/runtime/tailwind/engine/css/split-mixed-rule.d.ts +12 -0
- package/dist/runtime/tailwind/engine/css/split-mixed-rule.js +37 -0
- package/dist/runtime/tailwind/engine/css/strip-empty-tailwind-vars.d.ts +19 -0
- package/dist/runtime/tailwind/engine/css/strip-empty-tailwind-vars.js +44 -0
- package/dist/runtime/tailwind/engine/css/unwrap-value.d.ts +2 -0
- package/dist/runtime/tailwind/engine/css/unwrap-value.js +6 -0
- package/dist/runtime/tailwind/engine/index.d.ts +63 -0
- package/dist/runtime/tailwind/engine/index.js +53 -0
- package/dist/runtime/tailwind/engine/pixel-based-preset.d.ts +9 -0
- package/dist/runtime/tailwind/engine/pixel-based-preset.js +58 -0
- package/dist/runtime/tailwind/engine/sanitize-stylesheet.d.ts +10 -0
- package/dist/runtime/tailwind/engine/sanitize-stylesheet.js +8 -0
- package/dist/runtime/tailwind/engine/setup-tailwind.d.ts +23 -0
- package/dist/runtime/tailwind/engine/setup-tailwind.js +98 -0
- package/dist/runtime/tailwind/engine/stylesheets.d.ts +4 -0
- package/dist/runtime/tailwind/engine/stylesheets.js +1756 -0
- package/dist/runtime/tailwind/engine/types.d.ts +25 -0
- package/dist/runtime/tailwind/engine/types.js +0 -0
- package/dist/runtime/tailwind/errors.d.ts +7 -0
- package/dist/runtime/tailwind/errors.js +9 -0
- package/dist/runtime/tailwind/inline-utils.d.ts +19 -0
- package/dist/runtime/tailwind/inline-utils.js +24 -0
- package/dist/runtime/tailwind/nested.d.ts +59 -0
- package/dist/runtime/tailwind/nested.js +47 -0
- package/dist/runtime/tailwind/post-render.d.ts +7 -0
- package/dist/runtime/tailwind/post-render.js +156 -0
- package/dist/runtime/template-registry/errors.d.ts +5 -0
- package/dist/runtime/template-registry/errors.js +11 -0
- package/dist/runtime/template-registry/registry.d.ts +13 -0
- package/dist/runtime/testing/index.d.ts +13 -0
- package/dist/runtime/testing/index.js +2 -0
- package/dist/types.d.mts +5 -0
- package/package.json +122 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## v0.1.0
|
|
4
|
+
|
|
5
|
+
Status: pre-1.0 candidate. The package identity is `@lupinum/nuxt-email`; the unscoped npm package is unrelated. Manual email-client QA and external beta evidence remain release gates.
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Ordinary Vue SFC email authoring under recursive `app/emails/` discovery.
|
|
10
|
+
- Eighteen built-in auto-registered components: the document and layout primitives `EHtml`, `EHead`, `EBody`, `EContainer`, `ESection`, `ERow`, `EColumn`, `EHr`; the content primitives `EHeading`, `EText`, `ELink`, `EImg`, the Outlook-safe `EButton`, and the hidden `EPreview` preheader; plus `EFont`, `ECodeInline`, `EMarkdown`, and `ETailwind`.
|
|
11
|
+
- Opt-in `ECodeBlock` syntax highlighting with one configured Shiki theme, a closed language allowlist, inline token colors, email-safe wrapping, optional line numbers, and a Nuxt-config-aware `#nuxt-email/testing` binding.
|
|
12
|
+
- Email-safe Tailwind v4 through `ETailwind`: render-time class inlining, including utilities emitted inside nested components, with non-inlinable rules downleveled and injected into a `<head>` `<style>`.
|
|
13
|
+
- `EMarkdown` conversion of Markdown source to email-safe HTML with react-dom-compatible inline styles, raw-HTML rejection, safe URL-scheme validation, and escaped code content.
|
|
14
|
+
- Optional `defineEmail({ subject: () => string })`, imported from `@lupinum/nuxt-email/define-email`, that captures the template's real props in a closure; the subject is returned as `subject` on the render result and shown in the preview.
|
|
15
|
+
- Deterministic complete-document HTML rendering and plain-text fallback generation.
|
|
16
|
+
- A generated Nitro-only `renderEmail(name, props)` API with typed template names and Vue props, returning `{ html, text, subject? }`.
|
|
17
|
+
- One canonical server registry shared by runtime rendering and development preview.
|
|
18
|
+
- First-class testing utilities on the `@lupinum/nuxt-email/testing` subpath: `renderEmailComponent`, `EmailRenderError`, and the `RenderedEmail` type.
|
|
19
|
+
- Stable focused entry points for `@lupinum/nuxt-email/define-email` and `@lupinum/nuxt-email/errors`.
|
|
20
|
+
- Exact sibling `.fixtures.ts` preview data and a development-only `/__email` application with a sandboxed preview, HTML, plain-text, copy/open actions, and automatic refresh.
|
|
21
|
+
- Preview enhancements: a computed subject bar, a 600px/375px/full viewport toggle, and an exact UTF-8 byte-size badge with an approximate Gmail clipping-budget warning.
|
|
22
|
+
- An email-client proof kit (`proofs:generate`, `proofs:send`) that renders a representative batch to `.html`, `.txt`, and valid `multipart/alternative` `.eml` messages with an optional Resend send path, plus a client QA checklist.
|
|
23
|
+
- Actionable unknown-template, duplicate-template, discovery, document, prop, and render errors with preserved causes where applicable.
|
|
24
|
+
- React Email `6.9.0` and `@react-email/render` `2.1.0` behavioral oracle, generated conformance report, deterministic renderer measurements, production boundary tests, package inspection, and an isolated fresh-install verification workflow.
|
|
25
|
+
- A cross-platform CI matrix (Linux, macOS, Windows on Node 22, 24, and 26) running lint, type checks, and the full test suite, with the runtime-heavy oracle, conformance, and package/fresh-install checks run once on Linux, and a documentation-site build job.
|
|
26
|
+
- MIT project license, third-party notices, provenance record, email-client QA checklist, and external beta record.
|
|
27
|
+
|
|
28
|
+
### Security and production boundaries
|
|
29
|
+
|
|
30
|
+
- Vue interpolation and attributes remain escaped by default.
|
|
31
|
+
- The component set has no raw-HTML primitive and rejects content-replacement and event-handler attributes.
|
|
32
|
+
- The canonical generated rendering path excludes the renderer and templates from application client bundles.
|
|
33
|
+
- The canonical discovery path excludes preview UI, preview endpoints, and fixture modules from production builds.
|
|
34
|
+
- The preview iframe is sandboxed and raw preview output uses a restrictive content security policy.
|
|
35
|
+
- No public render endpoint or send endpoint is generated for production.
|
|
36
|
+
|
|
37
|
+
### Intentional scope
|
|
38
|
+
|
|
39
|
+
- Supported behavior is reported per case in the generated [conformance report](https://github.com/lupinum-dev/nuxt-email/blob/main/docs/conformance/report.md); this release does not claim full React Email compatibility.
|
|
40
|
+
- Provider adapters, send endpoints, JSX, React streaming, edge runtimes, raw HTML, configurable discovery paths, and a public registry protocol are not part of v1. `codeBlock` is the sole module option.
|
|
41
|
+
- Sending remains application-owned: callers pass `{ html, text, subject? }` to their selected provider SDK.
|
|
42
|
+
|
|
43
|
+
### Supported environment
|
|
44
|
+
|
|
45
|
+
- Node.js `^22.18.0 || ^24.11.0 || ^26.0.0`.
|
|
46
|
+
- Nuxt `>=4.5.1 <5`.
|
|
47
|
+
- Vue `^3.5.35`.
|
|
48
|
+
|
|
49
|
+
The repository's v1.0 readiness gate records the current contract and remaining approval steps; the live release ledger is intentionally excluded from the npm tarball.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026-present Lupinum OG and contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="docs/public/favicon.svg" width="128" alt="Nuxt Email icon">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">Nuxt Email</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center">Write typed transactional email as Vue components and render it directly in Nuxt.</p>
|
|
8
|
+
|
|
9
|
+
<p align="center">
|
|
10
|
+
<a href="https://www.npmjs.com/package/@lupinum/nuxt-email"><img src="https://img.shields.io/npm/v/@lupinum/nuxt-email?label=npm" alt="npm version"></a>
|
|
11
|
+
<a href="https://github.com/lupinum-dev/nuxt-email/actions/workflows/ci.yml"><img src="https://github.com/lupinum-dev/nuxt-email/actions/workflows/ci.yml/badge.svg" alt="CI status"></a>
|
|
12
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="MIT license"></a>
|
|
13
|
+
</p>
|
|
14
|
+
|
|
15
|
+
> [!WARNING]
|
|
16
|
+
> The first scoped release is in preparation. Public APIs can change before version 1.0. The unscoped `nuxt-email` package on npm is unrelated to this project.
|
|
17
|
+
|
|
18
|
+
## Why use Nuxt Email?
|
|
19
|
+
|
|
20
|
+
Nuxt Email turns `app/emails/` into one typed template registry. You author normal Vue single-file components. Nitro renders deterministic HTML, plain text, and an optional subject line.
|
|
21
|
+
|
|
22
|
+
The module uses the same rendering core for production, development preview, and tests. Your application keeps control of delivery, recipients, and provider credentials.
|
|
23
|
+
|
|
24
|
+
## When to use it
|
|
25
|
+
|
|
26
|
+
Use Nuxt Email for account messages, receipts, alerts, and other transactional email that belongs to a Nuxt application.
|
|
27
|
+
|
|
28
|
+
Do not use it for marketing campaigns or a workflow shared by non-Nuxt applications. Use a dedicated email framework such as Maizzle for those cases. Nuxt Email does not send messages or manage recipients.
|
|
29
|
+
|
|
30
|
+
## Requirements
|
|
31
|
+
|
|
32
|
+
- Node.js `^22.18.0 || ^24.11.0 || ^26.0.0`.
|
|
33
|
+
- Nuxt `>=4.5.1 <5`.
|
|
34
|
+
- Vue `^3.5.35`.
|
|
35
|
+
|
|
36
|
+
CI tests every supported Node major. The release consumer installs Nuxt `4.5.2` without a dependency shim.
|
|
37
|
+
|
|
38
|
+
## Installation
|
|
39
|
+
|
|
40
|
+
Install and register the module:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
pnpm add @lupinum/nuxt-email
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
export default defineNuxtConfig({
|
|
48
|
+
modules: ['@lupinum/nuxt-email'],
|
|
49
|
+
})
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Quick start
|
|
53
|
+
|
|
54
|
+
Create `app/emails/welcome.vue`:
|
|
55
|
+
|
|
56
|
+
```vue
|
|
57
|
+
<script setup lang="ts">
|
|
58
|
+
import { defineEmail } from '@lupinum/nuxt-email/define-email'
|
|
59
|
+
|
|
60
|
+
const props = defineProps<{ firstName: string; activationUrl: string }>()
|
|
61
|
+
|
|
62
|
+
defineEmail({ subject: () => `Welcome, ${props.firstName}` })
|
|
63
|
+
</script>
|
|
64
|
+
|
|
65
|
+
<template>
|
|
66
|
+
<ETailwind>
|
|
67
|
+
<EHtml lang="en">
|
|
68
|
+
<EBody class="bg-slate-100 p-6">
|
|
69
|
+
<EPreview>Your account is ready.</EPreview>
|
|
70
|
+
<EContainer class="bg-white p-6">
|
|
71
|
+
<EHeading>Welcome, {{ firstName }}</EHeading>
|
|
72
|
+
<EButton :href="activationUrl">Activate account</EButton>
|
|
73
|
+
</EContainer>
|
|
74
|
+
</EBody>
|
|
75
|
+
</EHtml>
|
|
76
|
+
</ETailwind>
|
|
77
|
+
</template>
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Render the template from Nitro:
|
|
81
|
+
|
|
82
|
+
```ts
|
|
83
|
+
export default defineEventHandler(async () => {
|
|
84
|
+
return await renderEmail('welcome', {
|
|
85
|
+
firstName: 'Ada',
|
|
86
|
+
activationUrl: 'https://example.com/activate',
|
|
87
|
+
})
|
|
88
|
+
})
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
The template name and props are generated from the Vue file. Invalid names and props fail during type checking.
|
|
92
|
+
|
|
93
|
+
## Email components
|
|
94
|
+
|
|
95
|
+
Nuxt Email registers 18 server components for document structure, table layout, text, images, links, Outlook-safe buttons, Markdown, inline code, fonts, previews, and Tailwind.
|
|
96
|
+
|
|
97
|
+
`ETailwind` converts compatible Tailwind v4 utilities to inline styles. It keeps media-query and pseudo-class rules in the document head. Configure `ECodeBlock` only when you need syntax highlighting:
|
|
98
|
+
|
|
99
|
+
```ts
|
|
100
|
+
export default defineNuxtConfig({
|
|
101
|
+
modules: ['@lupinum/nuxt-email'],
|
|
102
|
+
nuxtEmail: {
|
|
103
|
+
codeBlock: {
|
|
104
|
+
languages: ['typescript', 'vue'],
|
|
105
|
+
theme: 'github-dark',
|
|
106
|
+
},
|
|
107
|
+
},
|
|
108
|
+
})
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Development preview
|
|
112
|
+
|
|
113
|
+
Run the Nuxt application and open `/__email`. The development-only page shows the rendered message, HTML, plain text, subject, viewport sizes, and exact byte count.
|
|
114
|
+
|
|
115
|
+
Add one sibling fixture file for deterministic sample props. Preview routes and fixtures are excluded from production builds.
|
|
116
|
+
|
|
117
|
+
## Testing
|
|
118
|
+
|
|
119
|
+
Render a Vue email without starting Nuxt:
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
import { renderEmailComponent } from '@lupinum/nuxt-email/testing'
|
|
123
|
+
import Welcome from './app/emails/welcome.vue'
|
|
124
|
+
|
|
125
|
+
const result = await renderEmailComponent(Welcome, {
|
|
126
|
+
firstName: 'Ada',
|
|
127
|
+
activationUrl: 'https://example.com/activate',
|
|
128
|
+
})
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Use `#nuxt-email/testing` after `nuxt prepare` when the template uses configured components such as `ECodeBlock`.
|
|
132
|
+
|
|
133
|
+
## Compatibility evidence
|
|
134
|
+
|
|
135
|
+
Nuxt Email does not claim universal email-client or React Email compatibility. The generated [conformance report](https://github.com/lupinum-dev/nuxt-email/blob/main/docs/conformance/report.md) records each compared behavior and each intentional difference.
|
|
136
|
+
|
|
137
|
+
Complete the [manual client QA checklist](https://github.com/lupinum-dev/nuxt-email/blob/main/docs/release/client-qa-checklist.md) before a rendering release. Unit tests cannot prove how every email client displays a message.
|
|
138
|
+
|
|
139
|
+
## Package exports
|
|
140
|
+
|
|
141
|
+
- `@lupinum/nuxt-email` provides the Nuxt module and the `RenderedEmail` type.
|
|
142
|
+
- `@lupinum/nuxt-email/define-email` provides typed template metadata.
|
|
143
|
+
- `@lupinum/nuxt-email/testing` provides standalone component rendering.
|
|
144
|
+
- `@lupinum/nuxt-email/errors` provides supported runtime error classes.
|
|
145
|
+
|
|
146
|
+
## Documentation
|
|
147
|
+
|
|
148
|
+
Read the [Nuxt Email documentation](https://nuxt-email.lupinum.com/docs). It contains the [installation guide](https://nuxt-email.lupinum.com/docs/getting-started/installation), [component reference](https://nuxt-email.lupinum.com/docs/components), [testing guide](https://nuxt-email.lupinum.com/docs/guides/testing-your-emails), and [preview guide](https://nuxt-email.lupinum.com/docs/guides/preview-workflow).
|
|
149
|
+
|
|
150
|
+
See the [changelog](CHANGELOG.md) for released changes.
|
|
151
|
+
|
|
152
|
+
## Contributing and development
|
|
153
|
+
|
|
154
|
+
Read the [contribution guide](https://github.com/lupinum-dev/nuxt-email/blob/main/CONTRIBUTING.md) before you open a pull request. Run the normal handoff gate before you submit a change:
|
|
155
|
+
|
|
156
|
+
```bash
|
|
157
|
+
corepack enable
|
|
158
|
+
pnpm install --frozen-lockfile
|
|
159
|
+
pnpm verify
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Maintainers use the protected workflow in [MAINTAINING.md](https://github.com/lupinum-dev/nuxt-email/blob/main/MAINTAINING.md) for releases.
|
|
163
|
+
|
|
164
|
+
## Support and security
|
|
165
|
+
|
|
166
|
+
Open a [GitHub issue](https://github.com/lupinum-dev/nuxt-email/issues) for bugs and focused proposals. Join the [Lupinum OSS Discord](https://discord.gg/RPH6SeA36N) for project discussion.
|
|
167
|
+
|
|
168
|
+
Use the [private security process](https://github.com/lupinum-dev/nuxt-email/security/policy) to report a vulnerability. Do not report a vulnerability in a public issue.
|
|
169
|
+
|
|
170
|
+
## License
|
|
171
|
+
|
|
172
|
+
Nuxt Email is available under the [MIT License](LICENSE). Copyright belongs to [Lupinum OG](https://lupinum.com).
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
React Email
|
|
2
|
+
===========
|
|
3
|
+
|
|
4
|
+
Nuxt Email contains Vue adaptations of selected compatibility algorithms and
|
|
5
|
+
markup strategies from React Email 6.9.0, pinned at source commit
|
|
6
|
+
6eb428924c4c2774228a07cbec1977ad8898f143. The adapted surface covers button
|
|
7
|
+
padding and Outlook spacing, text margin expansion, table padding placement,
|
|
8
|
+
hidden preview text, the font-face declaration, inline code rendering, Markdown
|
|
9
|
+
rendering, and Tailwind class inlining. The pinned upstream source is available at
|
|
10
|
+
https://github.com/resend/react-email/tree/6eb428924c4c2774228a07cbec1977ad8898f143.
|
|
11
|
+
|
|
12
|
+
Copyright 2024 Plus Five Five, Inc
|
|
13
|
+
|
|
14
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
15
|
+
this software and associated documentation files (the "Software"), to deal in
|
|
16
|
+
the Software without restriction, including without limitation the rights to
|
|
17
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
18
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
|
19
|
+
subject to the following conditions:
|
|
20
|
+
|
|
21
|
+
The above copyright notice and this permission notice shall be included in all
|
|
22
|
+
copies or substantial portions of the Software.
|
|
23
|
+
|
|
24
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
25
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
26
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
27
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
28
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
29
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
30
|
+
SOFTWARE.
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
Runtime libraries
|
|
34
|
+
=================
|
|
35
|
+
|
|
36
|
+
The following third-party libraries are used by the runtime of the Markdown,
|
|
37
|
+
Tailwind, and opt-in syntax-highlighting components. Each is distributed under the MIT License,
|
|
38
|
+
whose text is reproduced once below.
|
|
39
|
+
|
|
40
|
+
- marked 15.0.12 — Markdown-to-HTML rendering in EMarkdown.
|
|
41
|
+
Copyright (c) 2018+, MarkedJS (https://github.com/markedjs/)
|
|
42
|
+
Copyright (c) 2011-2018, Christopher Jeffrey (https://github.com/chjj/)
|
|
43
|
+
|
|
44
|
+
- css-tree 3.2.1 — CSS parsing and generation in the Tailwind inlining engine.
|
|
45
|
+
Copyright (C) 2016-2026 by Roman Dvornov
|
|
46
|
+
|
|
47
|
+
- tailwindcss 4.1.18 — utility compilation used by ETailwind; its stylesheet CSS
|
|
48
|
+
is also vendored byte-for-byte into the engine.
|
|
49
|
+
Copyright (c) Tailwind Labs, Inc.
|
|
50
|
+
|
|
51
|
+
- @shikijs/core, @shikijs/engine-javascript, @shikijs/langs, and
|
|
52
|
+
@shikijs/themes 4.3.1 — fine-grained TextMate syntax highlighting used only
|
|
53
|
+
when ECodeBlock is configured.
|
|
54
|
+
Copyright (c) 2021 Pine Wu
|
|
55
|
+
Copyright (c) 2023-present Anthony Fu
|
|
56
|
+
|
|
57
|
+
MIT License
|
|
58
|
+
|
|
59
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
60
|
+
this software and associated documentation files (the "Software"), to deal in
|
|
61
|
+
the Software without restriction, including without limitation the rights to
|
|
62
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
63
|
+
the Software, and to permit persons to whom the Software is furnished to do so,
|
|
64
|
+
subject to the following conditions:
|
|
65
|
+
|
|
66
|
+
The above copyright notice and this permission notice shall be included in all
|
|
67
|
+
copies or substantial portions of the Software.
|
|
68
|
+
|
|
69
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
70
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
71
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
72
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
73
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
74
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
75
|
+
SOFTWARE.
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { addTemplate, addComponent, addTypeTemplate } from '@nuxt/kit';
|
|
2
|
+
import { languageNames, languageAliasNames } from '@shikijs/langs';
|
|
3
|
+
import { themeNames } from '@shikijs/themes';
|
|
4
|
+
|
|
5
|
+
function importPath$1(path) {
|
|
6
|
+
return JSON.stringify(path.replaceAll("\\", "/"));
|
|
7
|
+
}
|
|
8
|
+
function quotedList(values) {
|
|
9
|
+
return values.map((value) => JSON.stringify(value)).join(", ");
|
|
10
|
+
}
|
|
11
|
+
function generateCodeBlockComponent(options, paths) {
|
|
12
|
+
if (paths.languages.length !== options.languages.length) {
|
|
13
|
+
throw new TypeError("nuxt-email internal error: each configured code-block language needs one resolved module");
|
|
14
|
+
}
|
|
15
|
+
const languageImports = paths.languages.map((path, index) => `import language${index} from ${importPath$1(path)}`).join("\n");
|
|
16
|
+
const languageValues = paths.languages.map((_path, index) => `language${index}`).join(", ");
|
|
17
|
+
return `import { createHighlighterCore } from ${importPath$1(paths.core)}
|
|
18
|
+
import { createJavaScriptRegexEngine } from ${importPath$1(paths.engineJavaScript)}
|
|
19
|
+
${languageImports}
|
|
20
|
+
import codeBlockTheme from ${importPath$1(paths.theme)}
|
|
21
|
+
import { createCodeBlockComponent } from ${importPath$1(paths.createCodeBlockComponent)}
|
|
22
|
+
|
|
23
|
+
const enabledLanguages = Object.freeze([${quotedList(options.languages)}])
|
|
24
|
+
let highlighterPromise
|
|
25
|
+
|
|
26
|
+
function getHighlighter() {
|
|
27
|
+
highlighterPromise ??= createHighlighterCore({
|
|
28
|
+
langs: [${languageValues}],
|
|
29
|
+
themes: [codeBlockTheme],
|
|
30
|
+
engine: createJavaScriptRegexEngine(),
|
|
31
|
+
})
|
|
32
|
+
return highlighterPromise
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const ECodeBlock = createCodeBlockComponent({
|
|
36
|
+
enabledLanguages,
|
|
37
|
+
theme: ${JSON.stringify(options.theme)},
|
|
38
|
+
async highlight(code, language) {
|
|
39
|
+
const highlighter = await getHighlighter()
|
|
40
|
+
return highlighter.codeToTokens(code, {
|
|
41
|
+
lang: language,
|
|
42
|
+
theme: ${JSON.stringify(options.theme)},
|
|
43
|
+
})
|
|
44
|
+
},
|
|
45
|
+
})
|
|
46
|
+
`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function importPath(path) {
|
|
50
|
+
return JSON.stringify(path.replaceAll("\\", "/"));
|
|
51
|
+
}
|
|
52
|
+
function generateConfiguredRenderer(runtimePaths) {
|
|
53
|
+
return `import { emailComponentRegistry } from ${importPath(runtimePaths.emailComponentRegistry)}
|
|
54
|
+
import { createRenderEmailComponent } from ${importPath(runtimePaths.createRenderEmailComponent)}
|
|
55
|
+
import { ECodeBlock } from ${importPath(runtimePaths.codeBlockComponent.replace(/\.ts$/, ""))}
|
|
56
|
+
export { EmailRenderError } from ${importPath(runtimePaths.emailRenderError)}
|
|
57
|
+
|
|
58
|
+
const configuredEmailComponents = Object.freeze({ ...emailComponentRegistry, ECodeBlock })
|
|
59
|
+
|
|
60
|
+
export const renderEmailComponent = createRenderEmailComponent(configuredEmailComponents)
|
|
61
|
+
`;
|
|
62
|
+
}
|
|
63
|
+
function generateConfiguredRendererTypes(testingPath) {
|
|
64
|
+
return `export { EmailRenderError, renderEmailComponent } from ${importPath(testingPath)}
|
|
65
|
+
export type { RenderedEmail } from ${importPath(testingPath)}
|
|
66
|
+
`;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const availableLanguages = /* @__PURE__ */ new Set([...languageNames, ...languageAliasNames]);
|
|
70
|
+
const availableThemes = new Set(themeNames);
|
|
71
|
+
function assertPlainObject(value, label) {
|
|
72
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
73
|
+
throw new TypeError(`${label} must be an object`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function normalizeCodeBlockOptions(value) {
|
|
77
|
+
assertPlainObject(value, "nuxt-email codeBlock");
|
|
78
|
+
const unknownOptions = Object.keys(value).filter((name) => name !== "languages" && name !== "theme").sort();
|
|
79
|
+
if (unknownOptions.length > 0) {
|
|
80
|
+
throw new TypeError(`nuxt-email codeBlock received unknown option${unknownOptions.length === 1 ? "" : "s"}: ${unknownOptions.join(", ")}`);
|
|
81
|
+
}
|
|
82
|
+
if (!Array.isArray(value.languages) || value.languages.length === 0) {
|
|
83
|
+
throw new TypeError("nuxt-email codeBlock.languages must be a non-empty array");
|
|
84
|
+
}
|
|
85
|
+
const languages = value.languages.map((language, index) => {
|
|
86
|
+
if (typeof language !== "string" || language.length === 0) {
|
|
87
|
+
throw new TypeError(`nuxt-email codeBlock.languages[${index}] must be a non-empty string`);
|
|
88
|
+
}
|
|
89
|
+
if (!availableLanguages.has(language)) {
|
|
90
|
+
throw new TypeError(`nuxt-email codeBlock language ${JSON.stringify(language)} is not available in Shiki`);
|
|
91
|
+
}
|
|
92
|
+
return language;
|
|
93
|
+
});
|
|
94
|
+
const duplicateLanguage = languages.find((language, index) => languages.indexOf(language) !== index);
|
|
95
|
+
if (duplicateLanguage !== void 0) {
|
|
96
|
+
throw new TypeError(`nuxt-email codeBlock language ${JSON.stringify(duplicateLanguage)} is configured more than once`);
|
|
97
|
+
}
|
|
98
|
+
if (typeof value.theme !== "string" || value.theme.length === 0) {
|
|
99
|
+
throw new TypeError("nuxt-email codeBlock.theme must be a non-empty string");
|
|
100
|
+
}
|
|
101
|
+
if (!availableThemes.has(value.theme)) {
|
|
102
|
+
throw new TypeError(`nuxt-email codeBlock theme ${JSON.stringify(value.theme)} is not available in Shiki`);
|
|
103
|
+
}
|
|
104
|
+
return Object.freeze({
|
|
105
|
+
languages: Object.freeze(languages),
|
|
106
|
+
theme: value.theme
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function setupCodeBlock(options, resolver) {
|
|
111
|
+
const normalizedOptions = normalizeCodeBlockOptions(options);
|
|
112
|
+
const componentTemplate = addTemplate({
|
|
113
|
+
filename: "nuxt-email/ECodeBlock.ts",
|
|
114
|
+
write: true,
|
|
115
|
+
getContents: async () => generateCodeBlockComponent(normalizedOptions, {
|
|
116
|
+
core: await resolver.resolvePath("@shikijs/core"),
|
|
117
|
+
createCodeBlockComponent: (await resolver.resolvePath("./runtime/code-block/create-component")).replace(/\.ts$/, ""),
|
|
118
|
+
engineJavaScript: await resolver.resolvePath("@shikijs/engine-javascript"),
|
|
119
|
+
languages: await Promise.all(normalizedOptions.languages.map((language) => {
|
|
120
|
+
return resolver.resolvePath(`@shikijs/langs/${language}`);
|
|
121
|
+
})),
|
|
122
|
+
theme: await resolver.resolvePath(`@shikijs/themes/${normalizedOptions.theme}`)
|
|
123
|
+
})
|
|
124
|
+
});
|
|
125
|
+
addComponent({
|
|
126
|
+
name: "ECodeBlock",
|
|
127
|
+
export: "ECodeBlock",
|
|
128
|
+
filePath: componentTemplate.dst,
|
|
129
|
+
mode: "server"
|
|
130
|
+
});
|
|
131
|
+
const configuredRendererPaths = {
|
|
132
|
+
codeBlockComponent: componentTemplate.dst,
|
|
133
|
+
createRenderEmailComponent: resolver.resolve("./runtime/render/render-email-component"),
|
|
134
|
+
emailComponentRegistry: resolver.resolve("./runtime/components/email-component-registry"),
|
|
135
|
+
emailRenderError: resolver.resolve("./runtime/render/errors")
|
|
136
|
+
};
|
|
137
|
+
const configuredRendererTemplate = addTemplate({
|
|
138
|
+
filename: "nuxt-email/configured-renderer.js",
|
|
139
|
+
write: true,
|
|
140
|
+
getContents: () => generateConfiguredRenderer(configuredRendererPaths)
|
|
141
|
+
});
|
|
142
|
+
addTypeTemplate({
|
|
143
|
+
filename: "nuxt-email/configured-renderer.d.ts",
|
|
144
|
+
getContents: () => generateConfiguredRendererTypes(resolver.resolve("./runtime/testing"))
|
|
145
|
+
}, { nitro: true });
|
|
146
|
+
return configuredRendererTemplate.dst;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export { setupCodeBlock };
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import * as _nuxt_schema from '@nuxt/schema';
|
|
2
|
+
export { RenderedEmail } from '../dist/runtime/render/types.js';
|
|
3
|
+
|
|
4
|
+
interface CodeBlockOptions {
|
|
5
|
+
languages: readonly string[];
|
|
6
|
+
theme: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
interface ModuleOptions {
|
|
10
|
+
codeBlock?: CodeBlockOptions;
|
|
11
|
+
}
|
|
12
|
+
declare const _default: _nuxt_schema.NuxtModule<ModuleOptions, ModuleOptions, false>;
|
|
13
|
+
|
|
14
|
+
export { _default as default };
|
|
15
|
+
export type { ModuleOptions };
|