diff --git a/README.md b/README.md index f0bdba1..7374f06 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,18 @@ Measured with `pnpm benchmark:rendering` on the repository marketing email fixtu **Cached** means the template is compiled once and only the render step is measured. This is the expected production usage — compile at module load, render per request. The "one-time" compile+render cost is comparable to calling `render()` directly. +Plain-text benchmarks measured with `pnpm benchmark:html-to-text` on the repository HTML-to-text fixtures. Lower mean time is better. + +| Operation | Fixture | Mean | Throughput | Comparison | +| --- | --- | ---: | ---: | --- | +| `@solid-email/render` `toPlainText` | HTML fixtures | 2.4369ms | 410.36 hz | 3.40x faster than React Email `toPlainText` | +| `@solid-email/render` compiled text template | Solid JSX | 1.4434ms | 692.83 hz | 8.61x faster than React Email plain-text render | +| `@solid-email/render` uncompiled `renderSync` plain text | Solid JSX | 2.8895ms | 346.09 hz | 4.30x faster than React Email plain-text render | +| `@solid-email/html-to-text` `convert` | HTML fixtures | 3.9657ms | 252.16 hz | Direct package converter | +| `html-to-text` `convert` | HTML fixtures | 3.8166ms | 262.01 hz | Direct converter baseline | +| React Email `toPlainText` | HTML fixtures | 8.2867ms | 120.67 hz | React text conversion baseline | +| React Email `render` plain text | React JSX | 12.4310ms | 80.44 hz | React plain-text render baseline | + Bundle size compares built ESM entry files after `pnpm build`; gzip uses Node's `zlib.gzipSync`. | Package entry | Raw size | Gzip size | Comparison | @@ -129,6 +141,55 @@ const html2 = await compiled.render({ name: 'Bob', url: 'https://other.com' }); Use `compileSync()` for the synchronous equivalent (rejects `pretty` output). +### Compile plain-text output + +For repeated plain-text bodies, compile the template with `withPlainText: true`. The compiled template keeps a reusable text representation, so each render only substitutes slot values. + +```tsx +import { Body, Button, Container, Html, Text } from '@akin01/solid-email'; +import { compile, Slot, slot } from '@solid-email/render'; + +const compiled = await compile( + + + + + Hello ! + + + + + , + { withPlainText: true }, +); + +const text = await compiled.render( + { name: 'Alice', url: 'https://example.com/dashboard' }, + { plainText: true }, +); +``` + +For one-off Solid JSX to plain-text output, render the template with `plainText: true`. + +```tsx +import { Body, Button, Container, Html, Text } from '@akin01/solid-email'; +import { render } from '@solid-email/render'; + +const text = await render( + () => ( + + + + Hello Alice + + + + + ), + { plainText: true }, +); +``` + ### Slots Slots mark the dynamic parts of a compiled template. diff --git a/packages/html-to-text/LICENSE b/packages/html-to-text/LICENSE new file mode 100644 index 0000000..83678ea --- /dev/null +++ b/packages/html-to-text/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Ainul Yaqin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/html-to-text/README.md b/packages/html-to-text/README.md new file mode 100644 index 0000000..d77e185 --- /dev/null +++ b/packages/html-to-text/README.md @@ -0,0 +1,38 @@ +# @solid-email/html-to-text + +HTML-to-plain-text converter used by Solid Email rendering. + +## Install + +```sh +pnpm add @solid-email/html-to-text +``` + +Most Solid Email users should call `render(..., { plainText: true })` from `@solid-email/render`. Use this package directly when converting raw HTML strings or batch-processing HTML outside the renderer. + +## Convert HTML + +```ts +import { convert } from '@solid-email/html-to-text'; + +const text = convert('

Hello Alice

', { + wordwrap: false, +}); +``` + +## Compile converter options + +Compile options once when converting many HTML strings with the same settings. + +```ts +import { compile } from '@solid-email/html-to-text'; + +const toText = compile({ + wordwrap: false, + selectors: [{ selector: 'img', format: 'skip' }], +}); + +const text = toText('

Hello Alice

'); +``` + +See the repository README for Solid JSX plain-text rendering examples and benchmarks: https://github.com/Akin01/solid-email diff --git a/packages/html-to-text/package.json b/packages/html-to-text/package.json index 41fab05..e571500 100644 --- a/packages/html-to-text/package.json +++ b/packages/html-to-text/package.json @@ -17,6 +17,8 @@ "./package.json": "./package.json" }, "files": [ + "LICENSE", + "README.md", "dist/**", "src/**" ], diff --git a/packages/render/LICENSE b/packages/render/LICENSE new file mode 100644 index 0000000..83678ea --- /dev/null +++ b/packages/render/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Ainul Yaqin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/render/README.md b/packages/render/README.md new file mode 100644 index 0000000..2992caa --- /dev/null +++ b/packages/render/README.md @@ -0,0 +1,82 @@ +# @solid-email/render + +Render SolidJS email templates to HTML or plain text, and compile templates for repeated renders with slots. + +## Install + +```sh +pnpm add @solid-email/render solid-js +``` + +Install `@akin01/solid-email` when using the Solid Email component set. + +```sh +pnpm add @akin01/solid-email +``` + +## Render HTML + +```tsx +import { Body, Button, Container, Html, Text } from '@akin01/solid-email'; +import { render } from '@solid-email/render'; + +const html = await render(() => ( + + + + Welcome to Solid Email. + + + + +)); +``` + +## Render plain text + +```tsx +const text = await render( + () => ( + + + + Hello Alice + + + + + ), + { plainText: true }, +); +``` + +## Compile repeated renders + +```tsx +import { Body, Button, Container, Html, Text } from '@akin01/solid-email'; +import { compile, Slot, slot } from '@solid-email/render'; + +const compiled = await compile( + + + + + Hello ! + + + + + , + { withPlainText: true }, +); + +const html = await compiled.render({ name: 'Alice', url: 'https://example.com/dashboard' }); +const text = await compiled.render( + { name: 'Alice', url: 'https://example.com/dashboard' }, + { plainText: true }, +); +``` + +Use `renderSync()` and `compileSync()` only for static templates that do not need async Solid rendering or `pretty` output. + +See the repository README for benchmarks and advanced usage: https://github.com/Akin01/solid-email diff --git a/packages/render/package.json b/packages/render/package.json index c16352f..d76d8b4 100644 --- a/packages/render/package.json +++ b/packages/render/package.json @@ -12,6 +12,8 @@ "module": "./dist/node/index.mjs", "types": "./dist/node/index.d.mts", "files": [ + "LICENSE", + "README.md", "dist/**" ], "exports": { diff --git a/packages/solid-email/LICENSE b/packages/solid-email/LICENSE new file mode 100644 index 0000000..83678ea --- /dev/null +++ b/packages/solid-email/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Ainul Yaqin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/solid-email/README.md b/packages/solid-email/README.md new file mode 100644 index 0000000..37facda --- /dev/null +++ b/packages/solid-email/README.md @@ -0,0 +1,37 @@ +# @akin01/solid-email + +High-quality SolidJS components for building HTML email templates. + +## Install + +```sh +pnpm add @akin01/solid-email @solid-email/render solid-js +``` + +## Example + +```tsx +import { Body, Button, Container, Html, Text } from '@akin01/solid-email'; +import { render } from '@solid-email/render'; + +function WelcomeEmail() { + return ( + + + + Welcome to Solid Email. + + + + + ); +} + +const html = await render(() => ); +``` + +## Components + +Includes email-safe primitives such as `Html`, `Head`, `Preview`, `Body`, `Container`, `Section`, `Row`, `Column`, `Text`, `Heading`, `Button`, `Link`, `Img`, `Hr`, `Markdown`, `CodeInline`, `CodeBlock`, and `Tailwind`. + +See the repository README for benchmarks, compiled template examples, and delivery guidance: https://github.com/Akin01/solid-email diff --git a/packages/solid-email/package.json b/packages/solid-email/package.json index 8be4096..d4e0950 100644 --- a/packages/solid-email/package.json +++ b/packages/solid-email/package.json @@ -9,6 +9,8 @@ "type": "module", "types": "./dist/index.d.mts", "files": [ + "LICENSE", + "README.md", "dist/**" ], "exports": { diff --git a/packages/tsconfig/LICENSE b/packages/tsconfig/LICENSE new file mode 100644 index 0000000..83678ea --- /dev/null +++ b/packages/tsconfig/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Ainul Yaqin + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/tsconfig/package.json b/packages/tsconfig/package.json index 76d6f86..54810b1 100644 --- a/packages/tsconfig/package.json +++ b/packages/tsconfig/package.json @@ -3,6 +3,7 @@ "version": "0.1.3", "private": true, "files": [ + "LICENSE", "base.json", "node-vitest.json", "solid-library.json", diff --git a/skills/solid-email/SKILL.md b/skills/solid-email/SKILL.md index 8c13c7c..f77888f 100644 --- a/skills/solid-email/SKILL.md +++ b/skills/solid-email/SKILL.md @@ -3,8 +3,8 @@ name: solid-email description: Use when building, reviewing, testing, or documenting HTML email templates with Solid Email. Covers SolidJS email components, server rendering to HTML or plain text, compile-based template caching with slots, Tailwind inlining, markdown, code highlighting, Vite/TanStack integration, and email-client-safe styling. license: MIT metadata: - author: Solid Email contributors - version: "0.1.0" + author: Ainul Yaqin + version: "0.1.3" homepage: https://github.com/akin01/solid-email source: https://github.com/akin01/solid-email --- @@ -180,6 +180,33 @@ const text = await render(() => ! Start +

, + { withPlainText: true }, +); + +const text = await compiled.render( + { name: 'Alice', actionUrl: 'https://example.com/start' }, + { plainText: true }, +); +``` + +For batch raw HTML conversion, compile `@solid-email/html-to-text` options once and reuse the returned converter. + +```ts +import { compile } from '@solid-email/html-to-text'; + +const toText = compile({ wordwrap: false }); +const text = toText('

Hello Alice

'); +``` + ## Component rules - Use `Html`, `Head`, `Preview`, and `Body` for every complete email. diff --git a/skills/solid-email/references/RENDERING.md b/skills/solid-email/references/RENDERING.md index 18b7c87..57b3879 100644 --- a/skills/solid-email/references/RENDERING.md +++ b/skills/solid-email/references/RENDERING.md @@ -76,6 +76,38 @@ const html2 = await compiled.render({ name: 'Bob', actionUrl: 'https://other.com Use `compileSync()` for the synchronous equivalent. It rejects `pretty` output. +Compile plain-text output for repeated sends by passing `withPlainText: true`, then request text during render. + +```tsx +import { compile, Slot, slot } from '@solid-email/render'; + +const compiled = await compile( +

+ Hello ! + Start +

, + { withPlainText: true }, +); + +const text = await compiled.render( + { name: 'Alice', actionUrl: 'https://example.com/start' }, + { plainText: true }, +); +``` + +For raw HTML-to-text batch conversion, compile converter options once with `@solid-email/html-to-text`. + +```ts +import { compile } from '@solid-email/html-to-text'; + +const toText = compile({ + wordwrap: false, + selectors: [{ selector: 'img', format: 'skip' }], +}); + +const text = toText('

Hello Alice

'); +``` + Slots mark dynamic parts of a compiled template: ```tsx