Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions packages/tailwind/src/__snapshots__/tailwind.spec.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ exports[`Tailwind component > with non-inlinable styles > throws an error when u
For the media queries to work properly on rendering, they need to be added into a <style> tag inside of a <head> tag,
the Tailwind component tried finding a <head> element but just wasn't able to find it.

Make sure that you have a <head> element at some point inside of the <Tailwind> component at any depth.
Make sure that you have a <head> element at some point inside of the <Tailwind> component at any depth.
This can also be our <Head> component.

If you do already have a <head> element at some depth,
If you do already have a <head> element at some depth,
please file a bug https://github.com/resend/react-email/issues/new?assignees=&labels=Type%3A+Bug&projects=&template=1.bug_report.yml.]
`;

Expand All @@ -17,9 +17,9 @@ exports[`Tailwind component > with non-inlinable styles > throws error when used
For the media queries to work properly on rendering, they need to be added into a <style> tag inside of a <head> tag,
the Tailwind component tried finding a <head> element but just wasn't able to find it.

Make sure that you have a <head> element at some point inside of the <Tailwind> component at any depth.
Make sure that you have a <head> element at some point inside of the <Tailwind> component at any depth.
This can also be our <Head> component.

If you do already have a <head> element at some depth,
If you do already have a <head> element at some depth,
please file a bug https://github.com/resend/react-email/issues/new?assignees=&labels=Type%3A+Bug&projects=&template=1.bug_report.yml.]
`;
11 changes: 11 additions & 0 deletions packages/tailwind/src/hooks/use-suspended-promise.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,18 @@ interface PromiseState {
}

const promiseStates = new Map<string, PromiseState>();
const MAX_CACHE_SIZE = 50;

export function useSuspensedPromise<Result>(
promiseFn: () => Promise<Result>,
key: string,
) {
const previousState = promiseStates.get(key);
if (previousState) {
// LRU: move to end of insertion order
promiseStates.delete(key);
promiseStates.set(key, previousState);

if ('error' in previousState) {
throw previousState.error;
}
Expand All @@ -23,6 +28,12 @@ export function useSuspensedPromise<Result>(
throw previousState.promise;
}

// Evict oldest entry when at capacity
if (promiseStates.size >= MAX_CACHE_SIZE) {
const oldestKey = promiseStates.keys().next().value;
if (oldestKey !== undefined) promiseStates.delete(oldestKey);
}

const state: PromiseState = {
promise: promiseFn()
.then((result) => (state.result = result))
Expand Down
14 changes: 8 additions & 6 deletions packages/tailwind/src/tailwind.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -89,20 +89,22 @@ export function Tailwind({ children, config }: TailwindProps) {
typeof value === 'function' ? value.toString() : value,
),
);
let classesUsed: string[] = [];
const classesUsed: string[] = [];

let mappedChildren: React.ReactNode = mapReactTree(children, (node) => {
if (React.isValidElement<EmailElementProps>(node)) {
if (node.props.className) {
const classes = node.props.className?.split(/\s+/);
classesUsed = [...classesUsed, ...classes];
tailwindSetup.addUtilities(classes);
const classes = node.props.className.split(/\s+/);
classesUsed.push(...classes);
}
}

return node;
});

const uniqueClasses = [...new Set(classesUsed)];
tailwindSetup.addUtilities(uniqueClasses);

const styleSheet = tailwindSetup.getStyleSheet();
sanitizeStyleSheet(styleSheet);

Expand Down Expand Up @@ -162,10 +164,10 @@ export function Tailwind({ children, config }: TailwindProps) {
For the media queries to work properly on rendering, they need to be added into a <style> tag inside of a <head> tag,
the Tailwind component tried finding a <head> element but just wasn't able to find it.

Make sure that you have a <head> element at some point inside of the <Tailwind> component at any depth.
Make sure that you have a <head> element at some point inside of the <Tailwind> component at any depth.
This can also be our <Head> component.

If you do already have a <head> element at some depth,
If you do already have a <head> element at some depth,
please file a bug https://github.com/resend/react-email/issues/new?assignees=&labels=Type%3A+Bug&projects=&template=1.bug_report.yml.`,
);
}
Expand Down
27 changes: 15 additions & 12 deletions packages/tailwind/src/utils/css/extract-rules-per-class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,22 +11,25 @@ export function extractRulesPerClass(root: CssNode, classes: string[]) {
visit: 'Rule',
enter(rule) {
const selectorClasses: string[] = [];
walk(rule, {
visit: 'ClassSelector',
enter(classSelector) {
selectorClasses.push(string.decode(classSelector.name));
},
});
if (rule.prelude) {
walk(rule.prelude, {
visit: 'ClassSelector',
enter(classSelector) {
selectorClasses.push(string.decode(classSelector.name));
},
});
}

const matchingClasses = selectorClasses.filter((c) => classSet.has(c));
if (matchingClasses.length === 0) return;

if (isRuleInlinable(rule)) {
for (const className of selectorClasses) {
if (classSet.has(className)) {
inlinableRules.set(className, rule);
}
for (const className of matchingClasses) {
inlinableRules.set(className, rule);
}
} else {
const { inlinablePart, nonInlinablePart } = splitMixedRule(rule);
for (const className of selectorClasses) {
if (!classSet.has(className)) continue;
for (const className of matchingClasses) {
if (inlinablePart) {
inlinableRules.set(className, inlinablePart);
}
Expand Down
10 changes: 8 additions & 2 deletions packages/tailwind/src/utils/tailwindcss/setup-tailwind.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { parse, type StyleSheet } from 'css-tree';
import { clone, parse, type StyleSheet } from 'css-tree';
import { compile } from 'tailwindcss';
import type { TailwindConfig } from '../../tailwind';
import indexCss from './tailwind-stylesheets/index';
Expand Down Expand Up @@ -70,13 +70,19 @@ export async function setupTailwind(config: TailwindConfig) {
});

let css: string = baseCss;
let cachedCss: string | undefined;
let cachedAst: StyleSheet | undefined;

return {
addUtilities: function addUtilities(candidates: string[]): void {
css = compiler.build(candidates);
},
getStyleSheet: function getCss() {
return parse(css) as StyleSheet;
if (css !== cachedCss) {
cachedCss = css;
cachedAst = parse(css) as StyleSheet;
}
return clone(cachedAst!) as StyleSheet;
},
};
}
Loading