In the world of email development, ensuring consistent styling across various email clients is a significant challenge. Email clients are notoriously inconsistent in their CSS support. Many strip out tags or ignore external stylesheets. So ensuring consistent appearance across various email clients often requires inline CSS styles. This requirement can conflict with modern development practices, especially when using utility-first CSS frameworks like Tailwind.</p> <p>Recently, while working on a project that required sending various emails to users, I faced this exact dilemma. We were using Tailwind for our main application, and I wanted to maintain this efficient workflow for our email templates as well. However, the need for inline styles in emails posed a significant hurdle.</p> <p>I considered several approaches:</p> <ol> <li> <p>Creating email templates with inline styles from the start.</p> <ul> <li>Pros: Guaranteed compatibility with email clients.</li> <li>Cons: Time-consuming to write and maintain, prone to errors, and incompatible with Tailwind.</li> </ul> </li> <li> <p>Using online converters to transform Tailwind-styled templates into inline styles. Then, save the HTML with inline styles as a separate file to be sent via email.</p> <ul> <li>Pros: Allows use of familiar Tailwind syntax during development.</li> <li>Cons: Requires keeping two versions of each template (original and converted).</li> </ul> </li> <li> <p>Utilizing an npm package to convert templates to inline styles at runtime.</p> <ul> <li>Pros: It’s time-efficient and allows me to maintain only one version of each email template using Tailwind.</li> <li> <p>Cons: I did come across some packages like tailwind-to-css or tw-to-css, but while they have their merits, I encountered the following issues:</p> <p>❌ tailwind-to-css generates random class names in the result HTML<br> ❌ tw-to-css requires calling a converter function for every Tailwind class usage.</p> </li> </ul> </li> </ol> <p>After evaluating these options and finding existing tools lacking, I decided to implement a custom solution. In this article, I’ll guide you through the development of a TypeScript module that automates the process of inlining Tailwind classes for email-ready HTML.</p> <p>I want to highlight that the code actually runs on the Node.js server, so I aimed to use the bare minimum from the frontend world necessary to make the logic work.</p> <p><strong>Actual implementation</strong></p> <p>The main logic is resided in makeStylesInline function:<br> </p> <pre>export const makeStylesInline: TMakeStylesInline = async ( templatePath, data, ) => { const templateSource = fs.readFileSync(templatePath, 'utf8'); const template = Handlebars.compile(templateSource); const html = template(data); return inlineStyles(html); }; </pre> <p>This function takes a template path and optional data, compiles the template with Handlebars, and processes the resulting HTML to inline styles.</p> <p><strong>Harnessing the Power of Tailwind CSS</strong></p> <p>A key component is the processTailwindCSS function, which applies Tailwind CSS to our HTML:<br> </p> <pre>const processTailwindCSS = async (html: string): Promise<string> => { const tailwindConfig = { content: [{ raw: html, extension: 'html' }], corePlugins: { preflight: false, }, }; const result = await postcss([ tailwindcss(tailwindConfig), autoprefixer, ]).process('@tailwind components; @tailwind utilities;', { from: undefined, }); return result.css; }; </pre> <p>This function sets up a minimal Tailwind configuration and uses PostCSS to generate the necessary CSS.</p> <p><strong>Simplifying and Optimizing CSS for Emails</strong></p> <p>Given the limited CSS support in email clients, the simplifyColors function optimizes the generated CSS:<br> </p> <pre>const simplifyColors = (css: string): string => { const generalSimplifications = css .replace(/rgb\(([^)]+)\) \/ var\(--tw-[^)]+\)/g, 'rgb($1)') .replace( /rgba\(([^,]+),([^,]+),([^,]+),var\(--tw-[^)]+\)\)/g, 'rgba($1,$2,$3,1)' ) .replace(/var\(--tw-[^)]+\)/g, '1') .replace(/--tw-[^:]+:[^;]+;/g, ''); return generalSimplifications.replaceAll( /(rgba?\(\d+\s+\d+\s+\d+\s*\/.*\))/g, (match) => rgbToHex(match) ); }; </pre> <p>This function removes Tailwind-specific CSS variables and converts RGB colors to hexadecimal for better email client support.</p> <p><strong>The Art of Style Inlining</strong></p> <p>For the actual inlining process, we use the juice library:<br> </p> <pre>const inlineStyles = async (html: string): Promise<string> => { const tailwindCss = await processTailwindCSS(html); const simplifiedCss = simplifyColors(tailwindCss); return juice(html, { extraCss: simplifiedCss, applyStyleTags: true, removeStyleTags: true, preserveMediaQueries: true, preserveFontFaces: true, preserveImportant: true, inlinePseudoElements: true, }); }; </pre> <p>This function processes the HTML with Tailwind, simplifies the resulting CSS, and then uses juice to inline the styles. It preserves important structures like media queries and font faces.</p> <p><strong>Handling Dynamic Content with Handlebars</strong></p> <p>The solution integrates Handlebars for template compilation, allowing for dynamic content insertion:<br> </p> <pre>const templateSource = fs.readFileSync(templatePath, 'utf8'); const template = Handlebars.compile(templateSource); const html = template(data); </pre> <p>This approach enables the use of placeholder values in email templates, enhancing flexibility and reusability.</p> <p><strong>Conclusion</strong></p> <p>This TypeScript code offers an automated solution for generating email-ready HTML with inlined styles. By leveraging Tailwind CSS, PostCSS, and careful CSS optimization, it significantly streamlines the email development process. The result is consistently styled, cross-client compatible email HTML, generated with minimal manual intervention.</p> <p>For developers looking to improve their email development workflow, this solution provides a robust starting point. It combines the utility-first approach of Tailwind CSS with the practicality of automated style inlining, all while maintaining the flexibility needed for dynamic email content.</p> <p>For the full source code and more detailed implementation, visit the GitHub repository.</p> <p>需要注意的是,這是該解決方案的版本 1,因此,它可能無法涵蓋所有可能的用例或具有所有所需的功能。總是有改進和擴展的空間,我歡迎在專案中嘗試此解決方案的開發人員提供回饋。 </p> <p>感謝您的閱讀!如果您有任何疑問或需要澄清,請隨時聯繫。 </p> <p><em>隨時了解最新的 JavaScript 和軟體開發新聞!加入我的 Telegram 頻道以獲取更多見解和討論</em>:TechSavvy:前端和後端。 </p>