better-svelte-email 1.0.0-beta.2 → 1.0.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/README.md CHANGED
@@ -22,10 +22,13 @@
22
22
  </p>
23
23
  </p>
24
24
 
25
+ ## Usage
26
+
27
+ See the [documentation](https://better-svelte-email.konixy.fr/docs) for a complete guide on how to use Better Svelte Email.
28
+
25
29
  ## Features
26
30
 
27
- - **Stable & Future-Proof** - Uses Svelte's public preprocessor API
28
- - **Tailwind CSS Support** - Transforms Tailwind classes to inline styles for email clients
31
+ - **Tailwind v4 Support** - Transforms Tailwind classes to inline styles for email clients
29
32
  - **Built-in Email Preview** - Visual email preview and test sending
30
33
  - **TypeScript First** - Fully typed with comprehensive type definitions
31
34
  - **Well Tested** - Extensive test coverage with unit and integration tests
@@ -39,191 +42,7 @@ Existing Svelte email solutions have significant limitations:
39
42
  - **svelte-email** hasn't been updated in over 2 years
40
43
  - **svelte-email-tailwind** suffers from stability issues and maintaining it is not sustainable anymore
41
44
 
42
- Better Svelte Email is a complete rewrite built on Svelte's official preprocessor API, providing the rock-solid foundation your email infrastructure needs. It brings the simplicity, reliability, and feature richness of [React Email](https://react.email/) to the Svelte ecosystem.
43
-
44
- ## Quick Start
45
-
46
- ### 1. Install the package
47
-
48
- ```bash
49
- npm i -D better-svelte-email
50
- # or
51
- bun add -D better-svelte-email
52
- # or
53
- pnpm add -D better-svelte-email
54
- ```
55
-
56
- ### 2. Configure the Preprocessor
57
-
58
- Add the preprocessor to your `svelte.config.js`:
59
-
60
- ```javascript
61
- import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
62
- import { betterSvelteEmailPreprocessor } from 'better-svelte-email';
63
-
64
- /** @type {import('@sveltejs/kit').Config} */
65
- const config = {
66
- preprocess: [vitePreprocess(), betterSvelteEmailPreprocessor()],
67
- kit: {
68
- adapter: adapter()
69
- }
70
- };
71
-
72
- export default config;
73
- ```
74
-
75
- ### 3. Create Email Components
76
-
77
- Create your email templates in `src/lib/emails/`:
78
-
79
- ```svelte
80
- <!-- src/lib/emails/welcome.svelte -->
81
- <script>
82
- import { Html, Head, Body, Preview, Container, Text, Button } from 'better-svelte-email';
83
-
84
- let { name = 'User' } = $props();
85
- </script>
86
-
87
- <Html>
88
- <Head />
89
- <Body class="bg-gray-100">
90
- <Preview preview="Welcome Email" />
91
- <Container class="mx-auto p-8">
92
- <Text class="mb-4 text-2xl font-bold">
93
- Welcome, {name}!
94
- </Text>
95
-
96
- <Button
97
- href="https://example.com"
98
- class="rounded bg-orange-600 px-6 py-3 text-white sm:text-sm"
99
- >
100
- Get Started
101
- </Button>
102
- </Container>
103
- </Body>
104
- </Html>
105
- ```
106
-
107
- ### 4. Render and Send
108
-
109
- ```typescript
110
- // src/routes/api/send-email/+server.ts
111
- import { render } from 'svelte/server';
112
- import WelcomeEmail from '$lib/emails/welcome.svelte';
113
-
114
- export async function POST({ request }) {
115
- const { name, email } = await request.json();
116
-
117
- // Render email (preprocessor already ran at build time!)
118
- const result = render(WelcomeEmail, { props: { name } });
119
-
120
- // Send email using your preferred service (Resend, SendGrid, etc.)
121
- // await resend.emails.send({
122
- // from: 'noreply@example.com',
123
- // to: email,
124
- // subject: 'Welcome!',
125
- // html: result.body
126
- // });
127
-
128
- return new Response('Sent');
129
- }
130
- ```
131
-
132
- ## Email Preview Component
133
-
134
- Better Svelte Email includes a built-in preview component for visually developing and testing your email templates during development.
135
-
136
- ### Setup
137
-
138
- Create a preview route in your SvelteKit app:
139
-
140
- ```svelte
141
- <!-- src/routes/preview/+page.svelte -->
142
- <script lang="ts">
143
- import { EmailPreview } from 'better-svelte-email/preview';
144
-
145
- let { data } = $props();
146
- </script>
147
-
148
- <EmailPreview emailList={data.emails} />
149
- ```
150
-
151
- ```typescript
152
- // src/routes/preview/+page.server.ts
153
- import { emailList, createEmail, sendEmail } from 'better-svelte-email/preview';
154
- import { env } from '$env/dynamic/private';
155
-
156
- export function load() {
157
- const emails = emailList({
158
- path: '/src/lib/emails' // optional, defaults to '/src/lib/emails'
159
- });
160
-
161
- return { emails };
162
- }
163
-
164
- export const actions = {
165
- ...createEmail,
166
- ...sendEmail({ resendApiKey: env.RESEND_API_KEY })
167
- };
168
- ```
169
-
170
- ### Features
171
-
172
- - **HTML Source View** - Inspect the generated HTML with syntax highlighting
173
- - **Copy to Clipboard** - Quickly copy the rendered HTML
174
- - **Test Email Sending** - Send test emails directly from the preview UI using Resend
175
- - **Template List** - Browse all your email templates in one place
176
-
177
- ### Environment Variables
178
-
179
- To enable test email sending, add your Resend API key to your `.env` file:
180
-
181
- ```env
182
- RESEND_API_KEY=re_your_api_key_here
183
- ```
184
-
185
- Get your API key from [Resend](https://resend.com/).
186
-
187
- ### Custom Email Provider
188
-
189
- If you prefer to use a different email provider, you can pass a custom send function:
190
-
191
- ```typescript
192
- export const actions = {
193
- ...createEmail,
194
- ...sendEmail({
195
- customSendEmailFunction: async ({ from, to, subject, html }) => {
196
- // Use your preferred email service (SendGrid, Mailgun, etc.)
197
- try {
198
- await yourEmailService.send({ from, to, subject, html });
199
- return { success: true };
200
- } catch (error) {
201
- return { success: false, error };
202
- }
203
- }
204
- })
205
- };
206
- ```
207
-
208
- ## Configuration
209
-
210
- Here are the available options:
211
-
212
- ```javascript
213
- betterSvelteEmailPreprocessor({
214
- pathToEmailFolder: '/src/lib/emails',
215
- debug: false,
216
- tailwindConfig: {
217
- theme: {
218
- extend: {
219
- colors: {
220
- brand: '#FF3E00'
221
- }
222
- }
223
- }
224
- }
225
- });
226
- ```
45
+ Better Svelte Email is a complete rewrite of [svelte-email-tailwind](https://github.com/steveninety/svelte-email-tailwind) inspired by [React Email](https://react.email/), providing the rock-solid foundation your email infrastructure needs. It brings the simplicity, reliability, and feature richness of [React Email](https://react.email/) to the Svelte ecosystem.
227
46
 
228
47
  ## Minimum Svelte Version
229
48
 
@@ -234,28 +53,29 @@ For older versions, you can use [`svelte-email-tailwind`](https://github.com/ste
234
53
 
235
54
  ### ✅ Supported
236
55
 
237
- - Static Tailwind classes
238
- - Custom Tailwind classes (`bg-[#fff]`, `my:[40px]`, ...)
239
- - All standard Tailwind (v3) utilities (colors, spacing, typography, etc.)
240
- - Responsive breakpoints (`sm:`, `md:`, `lg:`, `xl:`, `2xl:`)
241
- - HTML elements and Svelte components
242
- - Nested components
243
- - Conditional blocks (`{#if}`)
244
- - Each blocks (`{#each}`)
245
- - ✅ Custom Tailwind configurations
246
-
247
- ### ❌ Not Supported (Yet) (See [Roadmap](./ROADMAP.md))
248
-
249
- - ❌ Tailwind v4
250
- - ❌ CSS Object (`style={{ color: 'red' }}`)
251
- - ❌ Dynamic class expressions (`class={someVar}`)
252
- - ❌ Arbitrary values in responsive classes (`sm:[color:red]`)
253
- - ❌ Container queries
56
+ - All tailwindcss v4 utilities
57
+ - Custom Tailwind classes (`bg-[#fff]`, `my:[40px]`, ...)
58
+ - Dynamic Tailwind classes (`class={someVar}`)
59
+ - Responsive breakpoints (`sm:`, `md:`, `lg:`, `xl:`, `2xl:`)
60
+ - HTML elements and Svelte components
61
+ - Nested components
62
+ - All svelte features such as each blocks (`{#each}`) and if blocks (`{#if}`), and more
63
+ - Custom Tailwind configurations
254
64
 
255
65
  ## Author
256
66
 
257
67
  Anatole Dufour ([@Konixy](https://github.com/Konixy))
258
68
 
69
+ ### Author of `svelte-email-tailwind`
70
+
71
+ Steven Polak ([@steveninety](https://github.com/steveninety))
72
+
73
+ ### Authors of `react-email`
74
+
75
+ Bu Kinoshita ([@bukinoshita](https://github.com/bukinoshita))
76
+
77
+ Zeno Rocha ([@zenorocha](https://github.com/zenorocha))
78
+
259
79
  ## Development
260
80
 
261
81
  ### Running Tests
@@ -1,5 +1,4 @@
1
- // Email Components for better-svelte-email
2
- // These components work with the preprocessor's styleString prop
1
+ // Email components for better-svelte-email
3
2
  export { default as Body } from './Body.svelte';
4
3
  export { default as Button } from './Button.svelte';
5
4
  export { default as Column } from './Column.svelte';
package/dist/index.d.ts CHANGED
@@ -1,5 +1,2 @@
1
1
  export * from './components/index.js';
2
- export { betterSvelteEmailPreprocessor } from './preprocessor/index.js';
3
- export type { PreprocessorOptions, ComponentTransform } from './preprocessor/index.js';
4
- export { default as Renderer, type TailwindConfig, type RenderOptions } from './render/index.js';
5
- export * from './utils/index.js';
2
+ export { default as Renderer, toPlainText, type TailwindConfig, type RenderOptions } from './render/index.js';
package/dist/index.js CHANGED
@@ -1,8 +1,4 @@
1
1
  // Export email components
2
2
  export * from './components/index.js';
3
- // Export the preprocessor
4
- export { betterSvelteEmailPreprocessor } from './preprocessor/index.js';
5
3
  // Export renderer
6
- export { default as Renderer } from './render/index.js';
7
- // Export utilities
8
- export * from './utils/index.js';
4
+ export { default as Renderer, toPlainText } from './render/index.js';
@@ -142,26 +142,41 @@
142
142
  .email-directory {
143
143
  display: flex;
144
144
  align-items: center;
145
- gap: 0.5rem;
145
+ gap: 0.75rem;
146
146
  width: 100%;
147
147
  padding: 0.375rem 0.75rem;
148
148
  padding-left: calc(0.75rem + var(--node-depth, 0) * 1rem);
149
149
  border-radius: 0.5rem;
150
150
  border: 0;
151
151
  background-color: transparent;
152
- font-size: 0.75rem;
153
- text-transform: uppercase;
154
- letter-spacing: 0.04em;
155
- color: var(--muted-foreground);
152
+ font-size: 0.875rem;
153
+ font-weight: 500;
154
+ color: var(--secondary-foreground);
156
155
  cursor: pointer;
157
156
  transition: all 0.15s;
158
157
  justify-content: flex-start;
159
158
  }
160
159
 
160
+ .email-directory .email-icon.folder {
161
+ display: block;
162
+ }
163
+
164
+ .email-directory .email-icon.caret {
165
+ display: none;
166
+ }
167
+
161
168
  .email-directory:hover {
162
169
  background-color: var(--muted);
163
170
  }
164
171
 
172
+ .email-directory:not(.active):hover .email-icon.folder {
173
+ display: none;
174
+ }
175
+
176
+ .email-directory:not(.active):hover .email-icon.caret {
177
+ display: block;
178
+ }
179
+
165
180
  .email-button {
166
181
  display: flex;
167
182
  width: 100%;
@@ -206,13 +221,13 @@
206
221
  background-color: color-mix(in srgb, var(--muted) 80%, transparent);
207
222
  }
208
223
 
209
- .email-directory.collapsed .folder {
224
+ /* .email-directory.collapsed .folder {
210
225
  opacity: 0.7;
211
- }
226
+ } */
212
227
 
213
- .email-directory.collapsed .email-directory-name {
228
+ /* .email-directory.collapsed .email-directory-name {
214
229
  color: var(--muted-foreground);
215
- }
230
+ } */
216
231
 
217
232
  .email-children {
218
233
  margin: 0.125rem 0 0;
package/package.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "name": "better-svelte-email",
3
- "version": "1.0.0-beta.2",
3
+ "description": "Svelte email renderer with Tailwind support",
4
+ "version": "1.0.1",
4
5
  "author": "Konixy",
5
6
  "repository": {
6
7
  "type": "git",
@@ -19,9 +20,7 @@
19
20
  "dependencies": {
20
21
  "css-tree": "^3.1.0",
21
22
  "html-to-text": "^9.0.5",
22
- "magic-string": "^0.30.21",
23
- "parse5": "^8.0.0",
24
- "tw-to-css": "^0.0.12"
23
+ "parse5": "^8.0.0"
25
24
  },
26
25
  "optionalDependencies": {
27
26
  "prettier": "^3.6.2",
@@ -50,7 +49,7 @@
50
49
  "publint": "^0.3.15",
51
50
  "rehype-autolink-headings": "^7.1.0",
52
51
  "rehype-slug": "^6.0.0",
53
- "svelte": "5.43.9",
52
+ "svelte": "5.43.14",
54
53
  "svelte-check": "^4.3.4",
55
54
  "tailwindcss": "^4.1.17",
56
55
  "tailwindcss-motion": "^1.1.1",
@@ -63,13 +62,8 @@
63
62
  ".": {
64
63
  "types": "./dist/index.d.ts",
65
64
  "svelte": "./dist/index.js",
66
- "import": "./dist/preprocessor/index.js",
67
- "default": "./dist/preprocessor/index.js"
68
- },
69
- "./preprocessor": {
70
- "types": "./dist/preprocessor/index.d.ts",
71
- "import": "./dist/preprocessor/index.js",
72
- "default": "./dist/preprocessor/index.js"
65
+ "import": "./dist/index.js",
66
+ "default": "./dist/index.js"
73
67
  },
74
68
  "./components": {
75
69
  "types": "./dist/components/index.d.ts",
@@ -89,9 +83,6 @@
89
83
  "types": "./dist/preview/EmailPreview.svelte.d.ts",
90
84
  "svelte": "./dist/preview/EmailPreview.svelte"
91
85
  },
92
- "./preview/theme.css": {
93
- "default": "./dist/preview/theme.css"
94
- },
95
86
  "./utils": {
96
87
  "types": "./dist/utils/index.d.ts",
97
88
  "import": "./dist/utils/index.js",
@@ -104,7 +95,6 @@
104
95
  },
105
96
  "./package.json": "./package.json"
106
97
  },
107
- "description": "Svelte email renderer with Tailwind support",
108
98
  "files": [
109
99
  "dist",
110
100
  "!dist/**/*.test.*",
@@ -118,11 +108,9 @@
118
108
  "email",
119
109
  "tailwind",
120
110
  "tailwindcss",
121
- "preprocessor",
122
111
  "inline-styles",
123
112
  "responsive-email",
124
- "email-templates",
125
- "svelte-preprocessor"
113
+ "email-templates"
126
114
  ],
127
115
  "license": "MIT",
128
116
  "scripts": {
@@ -1,9 +0,0 @@
1
- import type { MediaQueryStyle } from './types.js';
2
- /**
3
- * Inject media query styles into the <Head> component
4
- */
5
- export declare function injectMediaQueries(source: string, mediaQueries: MediaQueryStyle[]): {
6
- code: string;
7
- success: boolean;
8
- error?: string;
9
- };
@@ -1,57 +0,0 @@
1
- import MagicString from 'magic-string';
2
- import { findHeadComponent } from './parser.js';
3
- /**
4
- * Inject media query styles into the <Head> component
5
- */
6
- export function injectMediaQueries(source, mediaQueries) {
7
- if (mediaQueries.length === 0) {
8
- // No media queries to inject
9
- return { code: source, success: true };
10
- }
11
- // Find the Head component
12
- const headInfo = findHeadComponent(source);
13
- if (!headInfo.found || headInfo.insertPosition === null) {
14
- return {
15
- code: source,
16
- success: false,
17
- error: 'No <Head> component found. Media queries cannot be injected.'
18
- };
19
- }
20
- // Generate the style tag content
21
- const styleContent = generateStyleTag(mediaQueries);
22
- // Use MagicString for surgical insertion
23
- const s = new MagicString(source);
24
- // Check if Head is self-closing and convert it
25
- const headStart = source.lastIndexOf('<Head', headInfo.insertPosition);
26
- const headSegment = source.substring(headStart, headInfo.insertPosition + 10);
27
- if (headSegment.includes('/>')) {
28
- // Self-closing: convert to non-self-closing
29
- // Check if there's a space before />
30
- const spaceBeforeSelfClose = source[headInfo.insertPosition - 1] === ' ';
31
- const replaceStart = spaceBeforeSelfClose
32
- ? headInfo.insertPosition - 1
33
- : headInfo.insertPosition;
34
- // Replace [space]?/> with >
35
- s.overwrite(replaceStart, headInfo.insertPosition + 2, '>');
36
- // Insert style content
37
- s.appendLeft(headInfo.insertPosition + 2, styleContent);
38
- // Add closing tag
39
- s.appendLeft(headInfo.insertPosition + 2, '</Head>');
40
- }
41
- else {
42
- // Already has closing tag, just insert content
43
- s.appendLeft(headInfo.insertPosition, styleContent);
44
- }
45
- return {
46
- code: s.toString(),
47
- success: true
48
- };
49
- }
50
- /**
51
- * Generate <style> tag with all media queries
52
- */
53
- function generateStyleTag(mediaQueries) {
54
- // Combine all media queries
55
- const allQueries = mediaQueries.map((mq) => mq.rules).join('\n');
56
- return `\n\t<style>\n\t\t${allQueries}\n\t</style>\n`;
57
- }
@@ -1,44 +0,0 @@
1
- import type { PreprocessorGroup } from 'svelte/compiler';
2
- import type { PreprocessorOptions, ComponentTransform } from './types.js';
3
- /**
4
- * Svelte 5 preprocessor for transforming Tailwind classes in email components
5
- *
6
- * @deprecated The preprocessor approach is deprecated. Use the `Renderer` class instead for better performance and flexibility.
7
- *
8
- * @example
9
- * ```javascript
10
- * // Old (deprecated):
11
- * // svelte.config.js
12
- * import { betterSvelteEmailPreprocessor } from 'better-svelte-email/preprocessor';
13
- *
14
- * export default {
15
- * preprocess: [
16
- * vitePreprocess(),
17
- * betterSvelteEmailPreprocessor({
18
- * pathToEmailFolder: '/src/lib/emails',
19
- * tailwindConfig: { ... }
20
- * })
21
- * ]
22
- * };
23
- *
24
- * // New (recommended):
25
- * import Renderer from 'better-svelte-email/renderer';
26
- * import EmailComponent from './email.svelte';
27
- *
28
- * const renderer = new Renderer({
29
- * theme: {
30
- * extend: {
31
- * colors: { brand: '#FF3E00' }
32
- * }
33
- * }
34
- * });
35
- *
36
- * const html = await renderer.render(EmailComponent, {
37
- * props: { name: 'John' }
38
- * });
39
- * ```
40
- *
41
- * Reference: https://svelte.dev/docs/svelte/svelte-compiler#preprocess
42
- */
43
- export declare function betterSvelteEmailPreprocessor(options?: PreprocessorOptions): PreprocessorGroup;
44
- export type { PreprocessorOptions, ComponentTransform };