-
Notifications
You must be signed in to change notification settings - Fork 28
/
tailwind.tsx
112 lines (95 loc) · 3.54 KB
/
tailwind.tsx
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
// eslint-disable-next-line import/no-extraneous-dependencies
import { createGenerator, type ConfigBase } from '@unocss/core';
import { presetTypography } from '@unocss/preset-typography';
import { presetWind } from '@unocss/preset-wind';
import { presetUno } from '@unocss/preset-uno';
import { presetRemToPx } from '@unocss/preset-rem-to-px';
import transformerCompileClass from '@unocss/transformer-compile-class';
import transformerVariantGroup from '@unocss/transformer-variant-group';
import MagicString from 'magic-string';
import postcss from 'postcss';
// @ts-ignore
// Note: https://github.com/csstools/postcss-plugins/issues/1217
import { postcssVarReplace } from 'postcss-var-replace';
import { Suspense } from 'react';
import { debug } from '../../debug';
import { jsxToString, useData } from '../../render/jsx-to-string';
import { plugin as colorFunctions } from './color-functions';
const { warn } = console;
export interface TailwindProps {
config?: Pick<
ConfigBase,
'layers' | 'presets' | 'rules' | 'separators' | 'shortcuts' | 'theme' | 'variants'
>;
production?: boolean;
}
const debugProps = debug.elements.enabled ? { dataType: 'jsx-email/tailwind' } : {};
const getUno = (config: ConfigBase, production: boolean) => {
const transformers = [transformerVariantGroup()];
if (production)
transformers.push(
transformerCompileClass({
classPrefix: 'je-',
trigger: ':jsx:'
})
);
if ((config?.theme as any)?.extend) {
warn(
'jsx-email → Tailwind: Use of `theme.extend` is not necessary. `theme.extend` has been merged into `theme`'
);
const { extend } = config.theme as any;
delete (config.theme as any).extend;
config.theme = { ...config.theme, ...extend };
}
const presets = [
...(config.presets || []),
// Convert all `rem` values to `px`
presetRemToPx(),
presetTypography(),
presetUno({ dark: 'media' }),
presetWind()
];
const uno = createGenerator({
...(config as any),
presets,
transformers
});
return uno;
};
const render = async ({
children,
config = {},
production = false
}: React.PropsWithChildren<TailwindProps>) => {
const uno = getUno(config, production);
const html = await jsxToString(<>{children}</>);
const code = production ? html.replace(/class="/g, 'class=":jsx: ') : html;
const s = new MagicString(code);
const invalidate = () => 0;
for (const transformer of uno.config.transformers || []) {
// eslint-disable-next-line no-await-in-loop
await transformer.transform(s, 'Tailwind', { invalidate, tokens: new Set(), uno } as any);
}
const finalHtml = s.toString();
const result = await uno.generate(finalHtml);
// Note: Remove css variables, replace them with static values. It's not ideal to run PostCSS
// after using Uno, but it's pretty quick. Uno doesn't have a transformer that can match this,
// and it's crucial for email client support (e.g. Gmail)
const { css } = postcss([
postcssVarReplace({ preserveAtRulesOrder: true }),
colorFunctions()
]).process(result.css);
const styleTag = `<style tailwind>${css}</style>`;
return `${finalHtml}${styleTag}`;
};
const Renderer = (props: React.PropsWithChildren<TailwindProps>) => {
const html = useData(props, () => render(props));
return <head {...debugProps} dangerouslySetInnerHTML={{ __html: html }} />;
};
export const Tailwind = ({ children, ...props }: React.PropsWithChildren<TailwindProps>) => (
<>
<Suspense fallback={<div>waiting</div>}>
<Renderer {...props}>{children}</Renderer>
</Suspense>
</>
);