maizzle / framework

Quickly build HTML emails with Tailwind CSS.
https://maizzle.com
MIT License
1.21k stars 48 forks source link

`tailwindcss/typography` with Maizzle produces invalid HTML & CSS #1241

Closed cowboycodr closed 5 months ago

cowboycodr commented 5 months ago

As brought up in the conversation attached: Maizzle doesn't handle @tailwindcss/typography plugin well, and mostly produces invalid HTML/CSS.

Here's an example:

<!DOCTYPE html>
<html lang="en" xmlns:v="urn:schemas-microsoft-com:vml" class="bg-white text-primary" style="background-color: rgb(255 255 255 / 1); color: hsl(222.2 47.4% 11.2% / 1)">
<head>
  <meta charset="utf-8">
  <meta name="x-apple-disable-message-reformatting">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <meta name="format-detection" content="telephone=no, date=no, address=no, email=no, url=no">
  <meta name="color-scheme" content="light dark">
  <meta name="supported-color-schemes" content="light dark">
  <!--[if mso]>
  <noscript>
    <xml>
      <o:OfficeDocumentSettings xmlns:o="urn:schemas-microsoft-com:office:office">
        <o:PixelsPerInch>96</o:PixelsPerInch>
      </o:OfficeDocumentSettings>
    </xml>
  </noscript>
  <style>
    td,th,div,p,a,h1,h2,h3,h4,h5,h6 {font-family: "Segoe UI", sans-serif; mso-line-height-rule: exactly;}
  </style>
  <![endif]-->
  <style>
    img {
      max-width: 100%;
      vertical-align: middle;
      line-height: 1;
    }
    [class ~ ="not-prose"] *)) {
      margin-top: 1.25em;
      margin-bottom: 1.25em;
    }
    [class ~ ="not-prose"] *)) {
      color: var(--tw-prose-lead);
      font-size: 1.25em;
      line-height: 1.6;
      margin-top: 1.2em;
      margin-bottom: 1.2em;
    }
    [class ~ ="not-prose"] *)) {
      color: var(--tw-prose-links);
      text-decoration: underline;
      font-weight: 500;
    }
    [class ~ ="not-prose"] *)) {
      color: var(--tw-prose-bold);
      font-weight: 600;
    }
    [class ~ ="not-prose"] *)) {
      color: inherit;
    }
    [class ~ ="not-prose"] *)) {
      color: inherit;
    }
    [class ~ ="not-prose"] *)) {
      color: inherit;
    }
    [class ~ ="not-prose"] *)) {
      list-style-type: decimal;
      margin-top: 1.25em;
      margin-bottom: 1.25em;
      padding-inline-start: 1.625em;
    }
    [class ~ ="not-prose"] *)) {
      list-style-type: upper-alpha;
    }
    [class ~ ="not-prose"] *)) {
      list-style-type: lower-alpha;
    }
    [class ~ ="not-prose"] *)) {
      list-style-type: upper-alpha;
    }
    [class ~ ="not-prose"] *)) {
      list-style-type: lower-alpha;
    }
    [class ~ ="not-prose"] *)) {
      list-style-type: upper-roman;
    }
    [class ~ ="not-prose"] *)) {
      list-style-type: lower-roman;
    }
    [class ~ ="not-prose"] *)) {
      list-style-type: upper-roman;
    }
    [class ~ ="not-prose"] *)) {
      list-style-type: lower-roman;
    }
    [class ~ ="not-prose"] *)) {
      list-style-type: decimal;
    }
    [class ~ ="not-prose"] *)) {
      list-style-type: disc;
      margin-top: 1.25em;
      margin-bottom: 1.25em;
      padding-inline-start: 1.625em;
    }
    [class ~ ="not-prose"] *))::marker {
      font-weight: 400;
      color: var(--tw-prose-counters);
    }
    [class ~ ="not-prose"] *))::marker {
      color: var(--tw-prose-bullets);
    }
    [class ~ ="not-prose"] *)) {
      color: var(--tw-prose-headings);
      font-weight: 600;
      margin-top: 1.25em;
    }
    [class ~ ="not-prose"] *)) {
      border-color: var(--tw-prose-hr);
      border-top-width: 1px;
      margin-top: 3em;
      margin-bottom: 3em;
    }
    [class ~ ="not-prose"] *)) {
      font-weight: 500;
      font-style: italic;
      color: var(--tw-prose-quotes);
      border-inline-start-width: 0.25rem;
      border-inline-start-color: var(--tw-prose-quote-borders);
      quotes: "\201C" "\201D" "\2018" "\2019";
      margin-top: 1.6em;
      margin-bottom: 1.6em;
      padding-inline-start: 1em;
    }
    [class ~ ="not-prose"] *))::before {
      content: open-quote;
    }
    [class ~ ="not-prose"] *))::after {
      content: close-quote;
    }
    [class ~ ="not-prose"] *)) {
      color: var(--tw-prose-headings);
      font-weight: 800;
      font-size: 2.25em;
      margin-top: 0;
      margin-bottom: 0.8888889em;
      line-height: 1.1111111;
    }
    [class ~ ="not-prose"] *)) {
      font-weight: 900;
      color: inherit;
    }
    [class ~ ="not-prose"] *)) {
      color: var(--tw-prose-headings);
      font-weight: 700;
      font-size: 1.5em;
      margin-top: 2em;
      margin-bottom: 1em;
      line-height: 1.3333333;
    }
    [class ~ ="not-prose"] *)) {
      font-weight: 800;
      color: inherit;
    }
    [class ~ ="not-prose"] *)) {
      color: var(--tw-prose-headings);
      font-weight: 600;
      font-size: 1.25em;
      margin-top: 1.6em;
      margin-bottom: 0.6em;
      line-height: 1.6;
    }
    [class ~ ="not-prose"] *)) {
      font-weight: 700;
      color: inherit;
    }
    [class ~ ="not-prose"] *)) {
      color: var(--tw-prose-headings);
      font-weight: 600;
      margin-top: 1.5em;
      margin-bottom: 0.5em;
      line-height: 1.5;
    }
    [class ~ ="not-prose"] *)) {
      font-weight: 700;
      color: inherit;
    }
    [class ~ ="not-prose"] *)) {
      margin-top: 2em;
      margin-bottom: 2em;
    }
    [class ~ ="not-prose"] *)) {
      display: block;
      margin-top: 2em;
      margin-bottom: 2em;
    }
    [class ~ ="not-prose"] *)) {
      margin-top: 2em;
      margin-bottom: 2em;
    }
    [class ~ ="not-prose"] *)) {
      font-weight: 500;
      font-family: inherit;
      color: var(--tw-prose-kbd);
      box-shadow: 0 0 0 1px rgb(var(--tw-prose-kbd-shadows) / 10%), 0 3px 0 rgb(var(--tw-prose-kbd-shadows) / 10%);
      font-size: 0.875em;
      border-radius: 0.3125rem;
      padding-top: 0.1875em;
      padding-inline-end: 0.375em;
      padding-bottom: 0.1875em;
      padding-inline-start: 0.375em;
    }
    [class ~ ="not-prose"] *)) {
      color: var(--tw-prose-code);
      font-weight: 600;
      font-size: 0.875em;
    }
    [class ~ ="not-prose"] *))::before {
      content: "`";
    }
    [class ~ ="not-prose"] *))::after {
      content: "`";
    }
    [class ~ ="not-prose"] *)) {
      color: inherit;
    }
    [class ~ ="not-prose"] *)) {
      color: inherit;
    }
    [class ~ ="not-prose"] *)) {
      color: inherit;
      font-size: 0.875em;
    }
    [class ~ ="not-prose"] *)) {
      color: inherit;
      font-size: 0.9em;
    }
    [class ~ ="not-prose"] *)) {
      color: inherit;
    }
    [class ~ ="not-prose"] *)) {
      color: inherit;
    }
    [class ~ ="not-prose"] *)) {
      color: inherit;
    }
    [class ~ ="not-prose"] *)) {
      color: var(--tw-prose-pre-code);
      background-color: var(--tw-prose-pre-bg);
      overflow-x: auto;
      font-weight: 400;
      font-size: 0.875em;
      line-height: 1.7142857;
      margin-top: 1.7142857em;
      margin-bottom: 1.7142857em;
      border-radius: 0.375rem;
      padding-top: 0.8571429em;
      padding-inline-end: 1.1428571em;
      padding-bottom: 0.8571429em;
      padding-inline-start: 1.1428571em;
    }
    [class ~ ="not-prose"] *)) {
      background-color: transparent;
      border-width: 0;
      border-radius: 0;
      padding: 0;
      font-weight: inherit;
      color: inherit;
      font-size: inherit;
      font-family: inherit;
      line-height: inherit;
    }
    [class ~ ="not-prose"] *))::before {
      content: none;
    }
    [class ~ ="not-prose"] *))::after {
      content: none;
    }
    [class ~ ="not-prose"] *)) {
      width: 100%;
      table-layout: auto;
      text-align: start;
      margin-top: 2em;
      margin-bottom: 2em;
      font-size: 0.875em;
      line-height: 1.7142857;
    }
    [class ~ ="not-prose"] *)) {
      border-bottom-width: 1px;
      border-bottom-color: var(--tw-prose-th-borders);
    }
    [class ~ ="not-prose"] *)) {
      color: var(--tw-prose-headings);
      font-weight: 600;
      vertical-align: bottom;
      padding-inline-end: 0.5714286em;
      padding-bottom: 0.5714286em;
      padding-inline-start: 0.5714286em;
    }
    .prose :where(tbody tr):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
      border-bottom-width: 1px;
      border-bottom-color: var(--tw-prose-td-borders);
    }
    .prose :where(tbody tr:last-child):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
      border-bottom-width: 0;
    }
    .prose :where(tbody td):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
      vertical-align: baseline;
    }
    [class ~ ="not-prose"] *)) {
      border-top-width: 1px;
      border-top-color: var(--tw-prose-th-borders);
    }
    [class ~ ="not-prose"] *)) {
      vertical-align: top;
    }
    [class ~ ="not-prose"] *)) {
      margin-top: 0;
      margin-bottom: 0;
    }
    [class ~ ="not-prose"] *)) {
      color: var(--tw-prose-captions);
      font-size: 0.875em;
      line-height: 1.4285714;
      margin-top: 0.8571429em;
    }
    [class ~ ="not-prose"] *)) {
      margin-top: 0;
      margin-bottom: 0;
    }
    [class ~ ="not-prose"] *)) {
      margin-top: 0.5em;
      margin-bottom: 0.5em;
    }
    [class ~ ="not-prose"] *)) {
      padding-inline-start: 0.375em;
    }
    [class ~ ="not-prose"] *)) {
      padding-inline-start: 0.375em;
    }
    [class ~ ="not-prose"] *)) {
      margin-top: 0.75em;
      margin-bottom: 0.75em;
    }
    [class ~ ="not-prose"] *)) {
      margin-top: 1.25em;
    }
    [class ~ ="not-prose"] *)) {
      margin-bottom: 1.25em;
    }
    [class ~ ="not-prose"] *)) {
      margin-top: 1.25em;
    }
    [class ~ ="not-prose"] *)) {
      margin-bottom: 1.25em;
    }
    ul ol,
    ol ul,
    ol ol):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
      margin-top: 0.75em;
      margin-bottom: 0.75em;
    }
    [class ~ ="not-prose"] *)) {
      margin-top: 1.25em;
      margin-bottom: 1.25em;
    }
    [class ~ ="not-prose"] *)) {
      margin-top: 0.5em;
      padding-inline-start: 1.625em;
    }
    [class ~ ="not-prose"] *)) {
      margin-top: 0;
    }
    [class ~ ="not-prose"] *)) {
      margin-top: 0;
    }
    [class ~ ="not-prose"] *)) {
      margin-top: 0;
    }
    [class ~ ="not-prose"] *)) {
      margin-top: 0;
    }
    [class ~ ="not-prose"] *)) {
      padding-inline-start: 0;
    }
    [class ~ ="not-prose"] *)) {
      padding-inline-end: 0;
    }
    .prose :where(tbody td, tfoot td):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
      padding-top: 0.5714286em;
      padding-inline-end: 0.5714286em;
      padding-bottom: 0.5714286em;
      padding-inline-start: 0.5714286em;
    }
    .prose :where(tbody td:first-child, tfoot td:first-child):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
      padding-inline-start: 0;
    }
    .prose :where(tbody td:last-child, tfoot td:last-child):not(:where([class~="not-prose"], [class~="not-prose"] *)) {
      padding-inline-end: 0;
    }
    [class ~ ="not-prose"] *)) {
      margin-top: 2em;
      margin-bottom: 2em;
    }
    [class ~ ="not-prose"] *)) {
      margin-top: 0;
    }
    [class ~ ="not-prose"] *)) {
      margin-bottom: 0;
    }
    .bg-white {
      --tw-bg-opacity: 1;
      background-color: rgb(255 255 255 / var(--tw-bg-opacity));
    }
  </style>
</head>
<body style="margin: 0px; width: 100%; padding: 0px; -webkit-font-smoothing: antialiased; word-break: break-word">
  <div role="article" aria-roledescription="email" aria-label lang="en">
    <div class="bg-white" style="color: #374151; max-width: 65ch; font-size: 1rem; line-height: 1.75; margin-left: auto; margin-right: auto; border-width: 1px; background-color: rgb(255 255 255 / 1); padding: 2.5rem 0.5rem; font-family: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'">
      {{{ content }}}
      <div style="text-align: center">
        Powered by <a href="https://blueq.app">BlueQ</a>
      </div>
    </div>
  </div>
</body>
</html>

As you can see, there are many invalid CSS selectors, as well not all of the styles are inlined which wouldn't work for popular email clients such as Gmail.

For more context here is my config.production.js:

/** @type {import('@maizzle/framework').Config} */

/*
|-------------------------------------------------------------------------------
| Production config                       https://maizzle.com/docs/environments
|-------------------------------------------------------------------------------
|
| This is where you define settings that optimize your emails for production.
| These will be merged on top of the base config.js, so you only need to
| specify the options that are changing.
|
*/

module.exports = {
  build: {
    templates: {
      source: "src/templates",
      destination: {
        path: 'build_production',
      },
    },
  },
  inlineCSS: true,
  removeUnusedCSS: true,
  shorthandCSS: true,
  prettify: true,
}

Discussed in https://github.com/orgs/maizzle/discussions/1240

Originally posted by **cowboycodr** April 9, 2024 The documentation previously states that it is possible (but not recommended) except it doesn't explain how. Could someone please show me how? https://maizzle.com/guides/markdown-emails#tailwindcsstypography
cowboycodr commented 5 months ago

Additionally I just realized that in some instances it is even producing the same (invalid) styles multiple times:

    [class ~ ="not-prose"] *)) {
      margin-top: 1.25em;
    }
    [class ~ ="not-prose"] *)) {
      margin-bottom: 1.25em;
    }
    [class ~ ="not-prose"] *)) {
      margin-top: 1.25em;
    }
    [class ~ ="not-prose"] *)) {
      margin-bottom: 1.25em;
    }
cowboycodr commented 5 months ago
image

I apologize. I spoke too soon and just read the part where it says that this isn't really possible for the exact reasons stated in this issue.

Nevertheless, it would be really cool/useful to add support for @tailwindcss/typography (if it is at all possible).