Skip to content

Commit 8d22fb3

Browse files
authored
Merge pull request #16 from Konixy/tailwind-v4
2 parents b942c01 + 0df3d9e commit 8d22fb3

File tree

76 files changed

+5167
-1920
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

76 files changed

+5167
-1920
lines changed

.github/workflows/release.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ jobs:
6060
uses: actions/setup-node@v4
6161
with:
6262
node-version: lts/*
63-
registry-url: "https://registry.npmjs.org"
63+
registry-url: 'https://registry.npmjs.org'
6464

6565
- name: Get current version
6666
id: current_version

bun.lock

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@
44
"": {
55
"name": "better-svelte-email",
66
"dependencies": {
7+
"css-tree": "^3.1.0",
78
"html-to-text": "^9.0.5",
89
"magic-string": "^0.30.21",
10+
"parse5": "^8.0.0",
911
"tw-to-css": "^0.0.12",
1012
},
1113
"devDependencies": {
@@ -16,6 +18,7 @@
1618
"@sveltejs/package": "^2.5.4",
1719
"@sveltejs/vite-plugin-svelte": "^6.2.1",
1820
"@tailwindcss/vite": "^4.1.16",
21+
"@types/css-tree": "^2.3.11",
1922
"@types/html-to-text": "^9.0.4",
2023
"@types/node": "^24.9.1",
2124
"eslint": "^9.38.0",
@@ -29,7 +32,7 @@
2932
"publint": "^0.3.15",
3033
"rehype-autolink-headings": "^7.1.0",
3134
"rehype-slug": "^6.0.0",
32-
"svelte": "5.42.2",
35+
"svelte": "5.43.3",
3336
"svelte-check": "^4.3.3",
3437
"tailwindcss": "^4.1.16",
3538
"typescript": "^5.9.3",
@@ -274,6 +277,8 @@
274277

275278
"@types/cookie": ["@types/cookie@0.6.0", "", {}, "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="],
276279

280+
"@types/css-tree": ["@types/css-tree@2.3.11", "", {}, "sha512-aEokibJOI77uIlqoBOkVbaQGC9zII0A+JH1kcTNKW2CwyYWD8KM6qdo+4c77wD3wZOQfJuNWAr9M4hdk+YhDIg=="],
281+
277282
"@types/deep-eql": ["@types/deep-eql@4.0.2", "", {}, "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw=="],
278283

279284
"@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="],
@@ -406,6 +411,8 @@
406411

407412
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
408413

414+
"css-tree": ["css-tree@3.1.0", "", { "dependencies": { "mdn-data": "2.12.2", "source-map-js": "^1.0.1" } }, "sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w=="],
415+
409416
"cssesc": ["cssesc@3.0.0", "", { "bin": { "cssesc": "bin/cssesc" } }, "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg=="],
410417

411418
"debug": ["debug@4.4.3", "", { "dependencies": { "ms": "^2.1.3" } }, "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA=="],
@@ -442,7 +449,7 @@
442449

443450
"enhanced-resolve": ["enhanced-resolve@5.18.3", "", { "dependencies": { "graceful-fs": "^4.2.4", "tapable": "^2.2.0" } }, "sha512-d4lC8xfavMeBjzGr2vECC3fsGXziXZQyJxD868h2M/mBI3PwAuODxAkLkq5HYuvrPYcUtiLzsTo8U3PgX3Ocww=="],
444451

445-
"entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
452+
"entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="],
446453

447454
"es-module-lexer": ["es-module-lexer@1.7.0", "", {}, "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA=="],
448455

@@ -636,6 +643,8 @@
636643

637644
"mdast-util-to-hast": ["mdast-util-to-hast@13.2.0", "", { "dependencies": { "@types/hast": "^3.0.0", "@types/mdast": "^4.0.0", "@ungap/structured-clone": "^1.0.0", "devlop": "^1.0.0", "micromark-util-sanitize-uri": "^2.0.0", "trim-lines": "^3.0.0", "unist-util-position": "^5.0.0", "unist-util-visit": "^5.0.0", "vfile": "^6.0.0" } }, "sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA=="],
638645

646+
"mdn-data": ["mdn-data@2.12.2", "", {}, "sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA=="],
647+
639648
"mdsvex": ["mdsvex@0.12.6", "", { "dependencies": { "@types/mdast": "^4.0.4", "@types/unist": "^2.0.3", "prism-svelte": "^0.4.7", "prismjs": "^1.17.1", "unist-util-visit": "^2.0.1", "vfile-message": "^2.0.4" }, "peerDependencies": { "svelte": "^3.56.0 || ^4.0.0 || ^5.0.0-next.120" } }, "sha512-pupx2gzWh3hDtm/iDW4WuCpljmyHbHi34r7ktOqpPGvyiM4MyfNgdJ3qMizXdgCErmvYC9Nn/qyjePy+4ss9Wg=="],
640649

641650
"merge2": ["merge2@1.4.1", "", {}, "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="],
@@ -698,6 +707,8 @@
698707

699708
"parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="],
700709

710+
"parse5": ["parse5@8.0.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA=="],
711+
701712
"parseley": ["parseley@0.12.1", "", { "dependencies": { "leac": "^0.6.0", "peberminta": "^0.9.0" } }, "sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw=="],
702713

703714
"path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="],
@@ -838,7 +849,7 @@
838849

839850
"supports-preserve-symlinks-flag": ["supports-preserve-symlinks-flag@1.0.0", "", {}, "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w=="],
840851

841-
"svelte": ["svelte@5.42.2", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "acorn": "^8.12.1", "aria-query": "^5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "esm-env": "^1.2.1", "esrap": "^2.1.0", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-iSry5jsBHispVczyt9UrBX/1qu3HQ/UyKPAIjqlvlu3o/eUvc+kpyMyRS2O4HLLx4MvLurLGIUOyyP11pyD59g=="],
852+
"svelte": ["svelte@5.43.3", "", { "dependencies": { "@jridgewell/remapping": "^2.3.4", "@jridgewell/sourcemap-codec": "^1.5.0", "@sveltejs/acorn-typescript": "^1.0.5", "@types/estree": "^1.0.5", "acorn": "^8.12.1", "aria-query": "^5.3.1", "axobject-query": "^4.1.0", "clsx": "^2.1.1", "esm-env": "^1.2.1", "esrap": "^2.1.0", "is-reference": "^3.0.3", "locate-character": "^3.0.0", "magic-string": "^0.30.11", "zimmerframe": "^1.1.2" } }, "sha512-kjkAjCk41mJfvJZG56XcJNOdJSke94JxtcX8zFzzz2vrt47E0LnoBzU6azIZ1aBxJgUep8qegAkguSf1GjxLXQ=="],
842853

843854
"svelte-check": ["svelte-check@4.3.3", "", { "dependencies": { "@jridgewell/trace-mapping": "^0.3.25", "chokidar": "^4.0.1", "fdir": "^6.2.0", "picocolors": "^1.0.0", "sade": "^1.7.4" }, "peerDependencies": { "svelte": "^4.0.0 || ^5.0.0-next.0", "typescript": ">=5.0.0" }, "bin": { "svelte-check": "bin/svelte-check" } }, "sha512-RYP0bEwenDXzfv0P1sKAwjZSlaRyqBn0Fz1TVni58lqyEiqgwztTpmodJrGzP6ZT2aHl4MbTvWP6gbmQ3FOnBg=="],
844855

@@ -976,12 +987,16 @@
976987

977988
"anymatch/picomatch": ["picomatch@2.3.1", "", {}, "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA=="],
978989

990+
"dom-serializer/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
991+
979992
"fast-glob/glob-parent": ["glob-parent@5.1.2", "", { "dependencies": { "is-glob": "^4.0.1" } }, "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow=="],
980993

981994
"glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="],
982995

983996
"hast-util-to-html/@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="],
984997

998+
"htmlparser2/entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="],
999+
9851000
"import-fresh/resolve-from": ["resolve-from@4.0.0", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],
9861001

9871002
"mdast-util-to-hast/unist-util-visit": ["unist-util-visit@5.0.0", "", { "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", "unist-util-visit-parents": "^6.0.0" } }, "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg=="],

package.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@
1717
}
1818
},
1919
"dependencies": {
20+
"css-tree": "^3.1.0",
2021
"html-to-text": "^9.0.5",
2122
"magic-string": "^0.30.21",
23+
"parse5": "^8.0.0",
2224
"tw-to-css": "^0.0.12"
2325
},
2426
"optionalDependencies": {
@@ -34,6 +36,7 @@
3436
"@sveltejs/package": "^2.5.4",
3537
"@sveltejs/vite-plugin-svelte": "^6.2.1",
3638
"@tailwindcss/vite": "^4.1.16",
39+
"@types/css-tree": "^2.3.11",
3740
"@types/html-to-text": "^9.0.4",
3841
"@types/node": "^24.9.1",
3942
"eslint": "^9.38.0",
@@ -85,13 +88,19 @@
8588
"import": "./dist/utils/index.js",
8689
"default": "./dist/utils/index.js"
8790
},
91+
"./render": {
92+
"types": "./dist/render/index.d.ts",
93+
"import": "./dist/render/index.js",
94+
"default": "./dist/render/index.js"
95+
},
8896
"./package.json": "./package.json"
8997
},
9098
"description": "Svelte email renderer with Tailwind support",
9199
"files": [
92100
"dist",
93101
"!dist/**/*.test.*",
94102
"!dist/**/*.spec.*",
103+
"!dist/**/__fixtures__",
95104
"!dist/emails"
96105
],
97106
"keywords": [
@@ -112,6 +121,7 @@
112121
"build": "bun run prepack && vite build",
113122
"preview": "vite preview",
114123
"package": "svelte-package",
124+
"package:watch": "nodemon -x \"bun run package\" -i dist -e ts,svelte",
115125
"prepare": "svelte-kit sync || echo ''",
116126
"prepack": "svelte-kit sync && svelte-package && publint",
117127
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",

src/lib/components/Body.svelte

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
<script lang="ts">
22
import type { HTMLAttributes } from 'svelte/elements';
33
4-
let { children, style, ...restProps }: { children?: any } & HTMLAttributes<HTMLBodyElement> =
5-
$props();
4+
let {
5+
children,
6+
style,
7+
class: className,
8+
...restProps
9+
}: { children?: any } & HTMLAttributes<HTMLBodyElement> = $props();
610
</script>
711

812
<body {...restProps}>
913
<table align="center" width="100%" border="0" cellspacing="0" cellpadding="0" role="presentation">
1014
<tbody>
1115
<tr>
12-
<td {style}>
16+
<td {style} class={className}>
1317
{@render children?.()}
1418
</td>
1519
</tr>

src/lib/components/Button.svelte

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<script lang="ts">
22
import { styleToString, pxToPt, combineStyles } from '$lib/utils/index.js';
3-
import type { HTMLAttributes } from 'svelte/elements';
3+
import type { HTMLAnchorAttributes } from 'svelte/elements';
44
55
let {
66
href = '#',
@@ -16,7 +16,7 @@
1616
pX?: number;
1717
pY?: number;
1818
children: any;
19-
} & HTMLAttributes<HTMLAnchorElement> = $props();
19+
} & HTMLAnchorAttributes = $props();
2020
2121
const y = pY * 2;
2222
const textRaise = pxToPt(y.toString());
@@ -43,17 +43,15 @@
4343
</script>
4444

4545
<a {...restProps} {href} {target} style={combineStyles(buttonStyle, style)}>
46-
{#if pX}
47-
<span>
48-
{@html `<!--[if mso]><i style="letter-spacing: ${pX}px;mso-font-width:-100%;mso-text-raise:${textRaise}" hidden>&nbsp;</i><![endif]-->`}
49-
</span>
50-
{/if}
46+
<span>
47+
{@html `<!--[if mso]><i style="letter-spacing: ${pX}px;mso-font-width:-100%;mso-text-raise:${textRaise}" hidden>&nbsp;</i><![endif]-->`}
48+
</span>
49+
5150
<span style={buttonTextStyle}>
5251
{@render children?.()}
5352
</span>
54-
{#if pX}
55-
<span>
56-
{@html `<!--[if mso]><i style="letter-spacing: ${pX}px;mso-font-width:-100%" hidden>&nbsp;</i><![endif]-->`}
57-
</span>
58-
{/if}
53+
54+
<span style="display: none;">
55+
{@html `<!--[if mso]><i style="letter-spacing: ${pX}px;mso-font-width:-100%" hidden>&nbsp;</i><![endif]-->`}
56+
</span>
5957
</a>
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
<script>
2+
import { Container, Heading } from '$lib/components/index.js';
3+
</script>
4+
5+
<Container>
6+
<Heading as="h1" m="10" style="font-weight: bold;" class="text-blue-600">Template title</Heading>
7+
</Container>
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
<script>
2+
import {
3+
Html,
4+
Head,
5+
Body,
6+
Text,
7+
Button,
8+
Container,
9+
Heading,
10+
Link,
11+
Hr,
12+
Preview,
13+
Section,
14+
Row,
15+
Column,
16+
Img
17+
} from '../../index.js';
18+
import Nested from './nested/nested.svelte';
19+
20+
const padding = 'px-4 py-2';
21+
const fontSemibold = 'font-semibold';
22+
const paddingStyle = 'padding: 2rem;';
23+
</script>
24+
25+
<Html>
26+
<Head />
27+
<Body>
28+
<!-- Preview text (edge case: long text truncation) -->
29+
<Preview preview="This is a preview text that should be visible in email clients" />
30+
31+
<Container class="bg-gray-100" style={paddingStyle}>
32+
<!-- Nested component with margin props + Tailwind classes -->
33+
<Nested />
34+
35+
<!-- Edge case: Responsive classes (should be kept as sanitized classes) -->
36+
<Heading as="h2" class="font-semibold md:text-3xl lg:text-4xl">Responsive Heading</Heading>
37+
38+
<!-- Edge case: Multiple utility combinations -->
39+
<Text class="mb-4 text-center text-lg text-gray-700">Multiple utilities text</Text>
40+
41+
<!-- Edge case: Mixed inline styles + Tailwind -->
42+
<Text style="font-weight: bold;" class="mt-2 text-red-600">Bold red text with margin</Text>
43+
44+
<!-- Horizontal rule -->
45+
<Hr />
46+
47+
<!-- Edge case: Section with Row and Column layout -->
48+
<Section class="rounded-lg bg-white p-6">
49+
<Row>
50+
<Column class="w-1/2 p-4">
51+
<Text class="font-bold">Column 1</Text>
52+
<Text class="text-sm text-gray-600">Left column content</Text>
53+
</Column>
54+
<Column class="w-1/2 p-4">
55+
<Text class="font-bold">Column 2</Text>
56+
<Text class="text-sm text-gray-600">Right column content</Text>
57+
</Column>
58+
</Row>
59+
</Section>
60+
61+
<Hr />
62+
63+
<!-- Edge case: Image with Tailwind classes -->
64+
<Img
65+
src="https://example.com/logo.png"
66+
alt="Logo"
67+
width="200"
68+
height="100"
69+
class="mx-auto rounded-md"
70+
/>
71+
72+
<!-- Edge case: Link with Tailwind + inline styles -->
73+
<Link href="https://example.com" class="font-semibold text-blue-600 underline">
74+
Visit our website
75+
</Link>
76+
77+
<!-- Edge case: Button with custom brand color + multiple utilities -->
78+
<Button
79+
class="bg-brand rounded {padding} text-white hover:bg-red-600"
80+
style="width: 33.33%"
81+
href="https://example.com"
82+
>
83+
Click Me
84+
</Button>
85+
86+
<!-- Edge case: Button with gradient-like classes -->
87+
<Button
88+
class="rounded-full bg-linear-to-r from-blue-500 to-purple-600 px-6 py-3 text-white"
89+
href="#"
90+
>
91+
Gradient Button
92+
</Button>
93+
94+
<!-- Edge case: Complex spacing utilities -->
95+
<Container class="mt-8 mb-4 border border-blue-200 bg-blue-50 px-6 py-8 shadow-sm">
96+
<Heading as="h3" class="mb-2 text-xl font-bold text-blue-900">Nested Container</Heading>
97+
<Text class="leading-relaxed text-blue-700">
98+
This tests complex spacing, borders, and shadows
99+
</Text>
100+
</Container>
101+
102+
<!-- Edge case: Arbitrary values (if supported) -->
103+
<Text class="p-[12px] text-[#123456]">Custom arbitrary values</Text>
104+
105+
<!-- Edge case: Empty style attribute -->
106+
<Text style="" class="text-green-600">Text with empty style attribute</Text>
107+
108+
<!-- Edge case: No classes, only inline styles -->
109+
<Text style="color: purple; font-size: 18px;">Pure inline styles</Text>
110+
</Container>
111+
</Body>
112+
</Html>

0 commit comments

Comments
 (0)