Skip to content

Commit

Permalink
fix(tailwind): Media query major issues (#1758)
Browse files Browse the repository at this point in the history
  • Loading branch information
gabrielmfern authored Nov 4, 2024
1 parent 076039c commit 6bd37a7
Show file tree
Hide file tree
Showing 12 changed files with 66 additions and 34 deletions.
5 changes: 5 additions & 0 deletions .changeset/five-pants-boil.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@react-email/tailwind": patch
---

Fix duplicate media query styles
5 changes: 5 additions & 0 deletions .changeset/moody-steaks-invent.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@react-email/tailwind": patch
---

Fix Promise being returned as a React Node
2 changes: 1 addition & 1 deletion packages/tailwind/integrations/nextjs/next-env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
/// <reference types="next/image-types/global" />

// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.
// see https://nextjs.org/docs/app/building-your-application/configuring/typescript for more information.
2 changes: 1 addition & 1 deletion packages/tailwind/integrations/nextjs/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/tailwind/integrations/vite/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 5 additions & 3 deletions packages/tailwind/src/__snapshots__/tailwind.spec.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ exports[`Custom theme config > should be able to use custom text alignment 1`] =

exports[`Responsive styles > should add css to <head/> and keep responsive class names 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html lang="en"><head><!--$--><style>@media(min-width:640px){.sm_bg-red-300{background-color:rgb(252,165,165) !important}}@media(min-width:768px){.md_bg-red-400{background-color:rgb(248,113,113) !important}}@media(min-width:1024px){.lg_bg-red-500{background-color:rgb(239,68,68) !important}}</style></head><body><div class="sm_bg-red-300 md_bg-red-400 lg_bg-red-500" style="background-color:rgb(254,202,202)"></div><!--/$--></body></html>"`;

exports[`Responsive styles > should not have duplicate media queries 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><head><meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/><meta name="x-apple-disable-message-reformatting"/><!--$--><style>@media(min-width:768px){.md_px-64px{padding-left:64px !important;padding-right:64px !important}}</style></head><body class="md_px-64px" style="background-color:rgb(255,255,255);margin-top:auto;margin-bottom:auto;margin-left:auto;margin-right:auto;font-family:ui-sans-serif, system-ui, sans-serif, &quot;Apple Color Emoji&quot;, &quot;Segoe UI Emoji&quot;, &quot;Segoe UI Symbol&quot;, &quot;Noto Color Emoji&quot;"><div class="md_px-64px"></div><!--/$--></body>"`;

exports[`Responsive styles > should persist existing <head/> elements 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html lang="en"><head><!--$--><style></style><link/><style>@media(min-width:640px){.sm_bg-red-500{background-color:rgb(239,68,68) !important}}</style></head><body><div class="sm_bg-red-500" style="background-color:rgb(254,202,202)"></div><!--/$--></body></html>"`;

exports[`Responsive styles > should throw an error when used without a <head/> 1`] = `
Expand Down Expand Up @@ -46,7 +48,7 @@ exports[`Responsive styles > should work with arbitrarily deep (in the React tre
exports[`Responsive styles > should work with arbitrarily deep (in the React tree) <head> elements 2`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html lang="en"><head><!--$--><style>@media(min-width:640px){.sm_bg-red-300{background-color:rgb(252,165,165) !important}}@media(min-width:768px){.md_bg-red-400{background-color:rgb(248,113,113) !important}}@media(min-width:1024px){.lg_bg-red-500{background-color:rgb(239,68,68) !important}}</style></head><body><div class="sm_bg-red-300 md_bg-red-400 lg_bg-red-500" style="background-color:rgb(254,202,202)"></div><!--/$--></body></html>"`;
exports[`Responsive styles > should work with relatively complex media query utilities 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><head><meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/><meta name="x-apple-disable-message-reformatting"/><!--$--><style>@media not all and(min-width:640px){.max-sm_text-red-600{color:rgb(220,38,38) !important}}</style></head><p class="max-sm_text-red-600" style="color:rgb(29,78,216)">I am some text<!-- --></p><!--/$-->"`;
exports[`Responsive styles > should work with relatively complex media query utilities 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><head><meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/><meta name="x-apple-disable-message-reformatting"/><!--$--><style>@media not all and(min-width:640px){.max-sm_text-red-600{color:rgb(220,38,38) !important}}</style></head><p class="max-sm_text-red-600" style="color:rgb(29,78,216)">I am some text</p><!--/$-->"`;
exports[`Tailwind component > <Button className="px-3 py-2 mt-8 text-sm text-gray-200 bg-blue-600 rounded-md"> 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><!--$--><a style="padding-left:0.75rem;padding-right:0.75rem;padding-top:0.5rem;padding-bottom:0.5rem;margin-top:2rem;font-size:0.875rem;line-height:1.25rem;color:rgb(229,231,235);background-color:rgb(37,99,235);border-radius:0.375rem;text-decoration:none;display:inline-block;max-width:100%;mso-padding-alt:0px;padding:8px 12px 8px 12px" target="_blank"><span><!--[if mso]><i style="mso-font-width:300%;mso-text-raise:12" hidden>&#8202;&#8202;</i><![endif]--></span><span style="max-width:100%;display:inline-block;line-height:120%;mso-padding-alt:0px;mso-text-raise:6px">Testing button</span><span><!--[if mso]><i style="mso-font-width:300%" hidden>&#8202;&#8202;&#8203;</i><![endif]--></span></a>Testing<!--/$-->"`;
Expand All @@ -60,13 +62,13 @@ exports[`Tailwind component > should not override inline styles with Tailwind st
exports[`Tailwind component > should preserve mso styles 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html dir="ltr" lang="en"><head><meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/><meta name="x-apple-disable-message-reformatting"/><!--$--><style>@media(min-width:640px){.sm_bg-red-50{background-color:rgb(254,242,242) !important}.sm_text-sm{font-size:0.875rem !important;line-height:1.25rem !important}}@media(min-width:768px){.md_text-lg{font-size:1.125rem !important;line-height:1.75rem !important}}</style></head><span><!--[if mso]><i style="letter-spacing: 10px;mso-font-width:-100%;" hidden>&nbsp;</i><![endif]--></span><div class="sm_bg-red-50 sm_text-sm md_text-lg custom-class" style="background-color:rgb(255,255,255)"></div><!--/$--></html>"`;
exports[`Tailwind component > should recognize custom responsive screen 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html dir="ltr" lang="en"><head><meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/><meta name="x-apple-disable-message-reformatting"/><!--$--><style>@media(min-width:1280px){.xl_bg-green-500{background-color:rgb(34,197,94) !important}}@media(min-width:1536px){.twoxl_bg-blue-500{background-color:rgb(59,130,246) !important}}</style></head><div class="xl_bg-green-500" style="background-color:rgb(254,226,226)">Test<!-- --></div><div class="twoxl_bg-blue-500">Test<!-- --></div><!--/$--></html>"`;
exports[`Tailwind component > should recognize custom responsive screen 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html dir="ltr" lang="en"><head><meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/><meta name="x-apple-disable-message-reformatting"/><!--$--><style>@media(min-width:1280px){.xl_bg-green-500{background-color:rgb(34,197,94) !important}}@media(min-width:1536px){.twoxl_bg-blue-500{background-color:rgb(59,130,246) !important}}</style></head><div class="xl_bg-green-500" style="background-color:rgb(254,226,226)">Test</div><div class="twoxl_bg-blue-500">Test</div><!--/$--></html>"`;
exports[`Tailwind component > should work properly with 'no-underline' 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html dir="ltr" lang="en"><head></head><!--$--><body><p style="color:rgb(0,0,0);font-size:14px;line-height:24px">or copy and paste this URL into your browser:<!-- --> <a class="other" href="https://react.email" style="color:rgb(37,99,235);text-decoration-line:none" target="_blank">https://react.email</a></p><p style="color:rgb(0,0,0);font-size:14px;line-height:24px">or copy and paste this URL into your browser:<!-- --> <a href="https://react.email" style="color:rgb(37,99,235);text-decoration-line:none" target="_blank">https://react.email</a></p><!--/$--></body></html>"`;
exports[`Tailwind component > should work with Heading component 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><!--$-->Hello<h1>My testing heading</h1>friends<!--/$-->"`;
exports[`Tailwind component > should work with calc() with + sign 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><head><!--$--><style>@media(min-width:1024px){.lg_max-h-calc50pxplus5rem{max-height:calc(50px + 5rem) !important}}</style></head><div class="lg_max-h-calc50pxplus5rem" style="max-height:calc(50px + 3rem);background-color:rgb(254,226,226)"><div style="height:200px">something tall<!-- --></div></div><!--/$-->"`;
exports[`Tailwind component > should work with calc() with + sign 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><head><!--$--><style>@media(min-width:1024px){.lg_max-h-calc50pxplus5rem{max-height:calc(50px + 5rem) !important}}</style></head><div class="lg_max-h-calc50pxplus5rem" style="max-height:calc(50px + 3rem);background-color:rgb(254,226,226)"><div style="height:200px">something tall</div></div><!--/$-->"`;
exports[`Tailwind component > should work with class manipulation done on components 1`] = `"<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><!--$--><div style="color:rgb(96,165,250);padding:4px;background-color:rgb(239,68,68)"></div><!--/$-->"`;
Expand Down
16 changes: 16 additions & 0 deletions packages/tailwind/src/tailwind.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -382,6 +382,22 @@ describe("Responsive styles", () => {
).toMatchSnapshot();
});

it("should not have duplicate media queries", async () => {
const Body = (props: { className: string; children: React.ReactNode }) => {
return <body className={props.className}>{props.children}</body>;
};
const output = await render(
<Tailwind>
<Head />
<Body className="bg-white my-auto mx-auto font-sans md:px-[64px]">
<div className="md:px-[64px]"></div>
</Body>
</Tailwind>,
);

expect(output).toMatchSnapshot();
});

it("should add css to <head/> and keep responsive class names", async () => {
const actualOutput = await render(
<html lang="en">
Expand Down
5 changes: 4 additions & 1 deletion packages/tailwind/src/tailwind.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { minifyCss } from "./utils/css/minify-css";
import { setupTailwind } from "./utils/tailwindcss/setup-tailwind";
import { mapReactTree } from "./utils/react/map-react-tree";
import { cloneElementWithInlinedStyles } from "./utils/tailwindcss/clone-element-with-inlined-styles";
import { removeRuleDuplicatesFromRoot } from "./utils/css/remove-rule-duplicates-from-root";

export type TailwindConfig = Pick<
TailwindOriginalConfig,
Expand Down Expand Up @@ -62,10 +63,12 @@ export const Tailwind: React.FC<TailwindProps> = ({ children, config }) => {
return node;
});

removeRuleDuplicatesFromRoot(nonInlineStylesRootToApply);

if (hasNonInlineStylesToApply) {
let hasAppliedNonInlineStyles = false as boolean;

mappedChildren = mapReactTree(mappedChildren, async (node) => {
mappedChildren = mapReactTree(mappedChildren, (node) => {
if (hasAppliedNonInlineStyles) {
return node;
}
Expand Down
11 changes: 11 additions & 0 deletions packages/tailwind/src/utils/css/remove-if-empty-recursively.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { Container, Document } from "postcss";

export const removeIfEmptyRecursively = (node: Container | Document) => {
if (node.first === undefined) {
const parent = node.parent;
if (parent) {
node.remove();
removeIfEmptyRecursively(parent);
}
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { Root } from "postcss";
import { removeIfEmptyRecursively } from "./remove-if-empty-recursively";

export const removeRuleDuplicatesFromRoot = (root: Root) => {
root.walkRules((rule) => {
root.walkRules(rule.selector, (duplicateRule) => {
if (duplicateRule === rule) return;

const parent = duplicateRule.parent;
duplicateRule.remove();
if (parent) {
removeIfEmptyRecursively(parent);
}
});
});
};
13 changes: 1 addition & 12 deletions packages/tailwind/src/utils/css/resolve-all-css-variables.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,13 @@
import {
type Container,
type Root,
type Document,
type Node,
type Declaration,
Rule,
rule as createRule,
decl as createDeclaration,
AtRule,
} from "postcss";

const removeIfEmptyRecursively = (node: Container | Document) => {
if (node.first === undefined) {
const parent = node.parent;
if (parent) {
node.remove();
removeIfEmptyRecursively(parent);
}
}
};
import { removeIfEmptyRecursively } from "./remove-if-empty-recursively";

const doNodesMatch = (first: Node | undefined, second: Node | undefined) => {
if (first instanceof Rule && second instanceof Rule) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,5 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html

exports[`setupTailwind() 1`] = `
".text-red-500 {
color: rgb(239 68 68 / 1)
}
@media (min-width: 640px) {
.sm\\:bg-blue-300 {
background-color: rgb(147 197 253 / 1)
}
}
.bg-slate-900 {
background-color: rgb(15 23 42 / 1)
}
"
`;

exports[`tailwind's generateRootForClasses() 1`] = `
".text-red-500 {
color: rgb(239 68 68 / 1)
Expand Down

1 comment on commit 6bd37a7

@vercel
Copy link

@vercel vercel bot commented on 6bd37a7 Nov 4, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

react-email-demo – ./apps/demo

demo.react.email
react-email-demo-git-main-resend.vercel.app
react-email-demo-resend.vercel.app
react-email-demo.vercel.app

Please sign in to comment.