better-svelte-email 1.0.0-beta.2 → 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -204
- package/dist/components/index.js +1 -2
- package/dist/index.d.ts +1 -4
- package/dist/index.js +1 -5
- package/dist/preview/EmailTreeNode.svelte +24 -9
- package/package.json +7 -19
- package/dist/preprocessor/head-injector.d.ts +0 -9
- package/dist/preprocessor/head-injector.js +0 -57
- package/dist/preprocessor/index.d.ts +0 -44
- package/dist/preprocessor/index.js +0 -227
- package/dist/preprocessor/parser.d.ts +0 -17
- package/dist/preprocessor/parser.js +0 -315
- package/dist/preprocessor/transformer.d.ts +0 -18
- package/dist/preprocessor/transformer.js +0 -158
- package/dist/preprocessor/types.d.ts +0 -125
- package/dist/preprocessor/types.js +0 -1
|
@@ -1,158 +0,0 @@
|
|
|
1
|
-
import { tailwindToCSS } from 'tw-to-css';
|
|
2
|
-
/**
|
|
3
|
-
* Initialize Tailwind converter with config
|
|
4
|
-
*/
|
|
5
|
-
export function createTailwindConverter(config) {
|
|
6
|
-
const { twi } = tailwindToCSS({ config });
|
|
7
|
-
return twi;
|
|
8
|
-
}
|
|
9
|
-
/**
|
|
10
|
-
* Transform Tailwind classes to inline styles and responsive classes
|
|
11
|
-
*/
|
|
12
|
-
export function transformTailwindClasses(classString, tailwindConverter) {
|
|
13
|
-
// Split classes
|
|
14
|
-
const classes = classString.trim().split(/\s+/).filter(Boolean);
|
|
15
|
-
// Separate responsive from non-responsive classes
|
|
16
|
-
const responsiveClasses = [];
|
|
17
|
-
const nonResponsiveClasses = [];
|
|
18
|
-
for (const cls of classes) {
|
|
19
|
-
// Responsive classes have format: sm:, md:, lg:, xl:, 2xl:
|
|
20
|
-
if (/^(sm|md|lg|xl|2xl):/.test(cls)) {
|
|
21
|
-
responsiveClasses.push(cls);
|
|
22
|
-
}
|
|
23
|
-
else {
|
|
24
|
-
nonResponsiveClasses.push(cls);
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
// Convert non-responsive classes to CSS
|
|
28
|
-
let inlineStyles = '';
|
|
29
|
-
const invalidClasses = [];
|
|
30
|
-
if (nonResponsiveClasses.length > 0) {
|
|
31
|
-
const classesStr = nonResponsiveClasses.join(' ');
|
|
32
|
-
try {
|
|
33
|
-
// Generate CSS from Tailwind classes
|
|
34
|
-
const css = tailwindConverter(classesStr, {
|
|
35
|
-
merge: false,
|
|
36
|
-
ignoreMediaQueries: true
|
|
37
|
-
});
|
|
38
|
-
// Extract styles from CSS
|
|
39
|
-
const styles = extractStylesFromCSS(css, nonResponsiveClasses);
|
|
40
|
-
inlineStyles = styles.validStyles;
|
|
41
|
-
invalidClasses.push(...styles.invalidClasses);
|
|
42
|
-
}
|
|
43
|
-
catch (error) {
|
|
44
|
-
console.warn('Failed to convert Tailwind classes:', error);
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
return {
|
|
48
|
-
inlineStyles,
|
|
49
|
-
responsiveClasses,
|
|
50
|
-
invalidClasses
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
/**
|
|
54
|
-
* Extract CSS properties from generated CSS
|
|
55
|
-
* Handles the format: .classname { prop: value; }
|
|
56
|
-
*/
|
|
57
|
-
function extractStylesFromCSS(css, originalClasses) {
|
|
58
|
-
const invalidClasses = [];
|
|
59
|
-
const styleProperties = [];
|
|
60
|
-
// Remove media queries (we handle those separately)
|
|
61
|
-
const cssWithoutMedia = css.replace(/@media[^{]+\{(?:[^{}]|\{[^{}]*\})*\}/g, '');
|
|
62
|
-
// Create a map of class name -> CSS rules
|
|
63
|
-
const classMap = new Map();
|
|
64
|
-
// Match .classname { rules }
|
|
65
|
-
const classRegex = /\.([^\s{]+)\s*\{([^}]+)\}/g;
|
|
66
|
-
let match;
|
|
67
|
-
while ((match = classRegex.exec(cssWithoutMedia)) !== null) {
|
|
68
|
-
const className = match[1];
|
|
69
|
-
const rules = match[2].replace(/\\/g, '').trim();
|
|
70
|
-
// Normalize class name (tw-to-css might transform special chars)
|
|
71
|
-
const normalizedClass = className.replace(/\\/g, '').replace(/[:#\-[\]/.%!_]+/g, '_');
|
|
72
|
-
classMap.set(normalizedClass, rules);
|
|
73
|
-
}
|
|
74
|
-
// For each original class, try to find its CSS
|
|
75
|
-
for (const originalClass of originalClasses) {
|
|
76
|
-
// Normalize the original class name to match what tw-to-css produces
|
|
77
|
-
const normalized = originalClass.replace(/[:#\-[\]/.%!_]+/g, '_');
|
|
78
|
-
if (classMap.has(normalized)) {
|
|
79
|
-
const rules = classMap.get(normalized);
|
|
80
|
-
// Ensure rules end with semicolon for proper concatenation
|
|
81
|
-
const rulesWithSemicolon = rules.trim().endsWith(';') ? rules.trim() : rules.trim() + ';';
|
|
82
|
-
styleProperties.push(rulesWithSemicolon);
|
|
83
|
-
}
|
|
84
|
-
else {
|
|
85
|
-
// Class not found - might be invalid Tailwind
|
|
86
|
-
invalidClasses.push(originalClass);
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
// Combine all style properties with space separator
|
|
90
|
-
const validStyles = styleProperties.join(' ').trim();
|
|
91
|
-
return { validStyles, invalidClasses };
|
|
92
|
-
}
|
|
93
|
-
/**
|
|
94
|
-
* Generate media query CSS for responsive classes
|
|
95
|
-
*/
|
|
96
|
-
export function generateMediaQueries(responsiveClasses, tailwindConverter, tailwindConfig) {
|
|
97
|
-
if (responsiveClasses.length === 0) {
|
|
98
|
-
return [];
|
|
99
|
-
}
|
|
100
|
-
const mediaQueries = [];
|
|
101
|
-
// Default breakpoints (can be overridden by config)
|
|
102
|
-
const breakpoints = {
|
|
103
|
-
sm: '475px',
|
|
104
|
-
md: '768px',
|
|
105
|
-
lg: '1024px',
|
|
106
|
-
xl: '1280px',
|
|
107
|
-
'2xl': '1536px',
|
|
108
|
-
...tailwindConfig?.theme?.screens
|
|
109
|
-
};
|
|
110
|
-
// Group classes by breakpoint
|
|
111
|
-
const classesByBreakpoint = new Map();
|
|
112
|
-
for (const cls of responsiveClasses) {
|
|
113
|
-
const match = cls.match(/^(sm|md|lg|xl|2xl):(.+)/);
|
|
114
|
-
if (match) {
|
|
115
|
-
const [, breakpoint] = match;
|
|
116
|
-
if (!classesByBreakpoint.has(breakpoint)) {
|
|
117
|
-
classesByBreakpoint.set(breakpoint, []);
|
|
118
|
-
}
|
|
119
|
-
classesByBreakpoint.get(breakpoint).push(cls);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
// Generate CSS for each breakpoint
|
|
123
|
-
for (const [breakpoint, classes] of classesByBreakpoint) {
|
|
124
|
-
const breakpointValue = breakpoints[breakpoint];
|
|
125
|
-
if (!breakpointValue)
|
|
126
|
-
continue;
|
|
127
|
-
// Generate full CSS including media queries
|
|
128
|
-
const fullCSS = tailwindConverter(classes.join(' '), {
|
|
129
|
-
merge: false,
|
|
130
|
-
ignoreMediaQueries: false
|
|
131
|
-
});
|
|
132
|
-
// Extract just the media query portion
|
|
133
|
-
const mediaQueryRegex = new RegExp(`@media[^{]*\\{([^{}]|\\{[^{}]*\\})*\\}`, 'g');
|
|
134
|
-
let match;
|
|
135
|
-
while ((match = mediaQueryRegex.exec(fullCSS)) !== null) {
|
|
136
|
-
const mediaQueryBlock = match[0];
|
|
137
|
-
// Make all rules !important for email clients
|
|
138
|
-
const withImportant = mediaQueryBlock.replace(/([a-z-]+)\s*:\s*([^;!}]+)/gi, '$1: $2 !important');
|
|
139
|
-
// Parse out the query and content
|
|
140
|
-
const queryMatch = withImportant.match(/@media\s*([^{]+)/);
|
|
141
|
-
if (queryMatch) {
|
|
142
|
-
const query = `@media ${queryMatch[1].trim()}`;
|
|
143
|
-
mediaQueries.push({
|
|
144
|
-
query,
|
|
145
|
-
className: `responsive-${breakpoint}`,
|
|
146
|
-
rules: withImportant
|
|
147
|
-
});
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
return mediaQueries;
|
|
152
|
-
}
|
|
153
|
-
/**
|
|
154
|
-
* Sanitize class names for use in CSS (replace special characters)
|
|
155
|
-
*/
|
|
156
|
-
export function sanitizeClassName(className) {
|
|
157
|
-
return className.replace(/[:#\-[\]/.%!]+/g, '_');
|
|
158
|
-
}
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
import type { TailwindConfig } from 'tw-to-css';
|
|
2
|
-
/**
|
|
3
|
-
* Options for the preprocessor
|
|
4
|
-
*/
|
|
5
|
-
export interface PreprocessorOptions {
|
|
6
|
-
/**
|
|
7
|
-
* Custom Tailwind configuration
|
|
8
|
-
*/
|
|
9
|
-
tailwindConfig?: TailwindConfig;
|
|
10
|
-
/**
|
|
11
|
-
* Path to folder containing email components
|
|
12
|
-
* @default '/src/lib/emails'
|
|
13
|
-
*/
|
|
14
|
-
pathToEmailFolder?: string;
|
|
15
|
-
/**
|
|
16
|
-
* Enable debug logging
|
|
17
|
-
* @default false
|
|
18
|
-
*/
|
|
19
|
-
debug?: boolean;
|
|
20
|
-
}
|
|
21
|
-
/**
|
|
22
|
-
* Represents a class attribute found in the AST
|
|
23
|
-
*/
|
|
24
|
-
export interface ClassAttribute {
|
|
25
|
-
/**
|
|
26
|
-
* Raw class string (e.g., "text-red-500 sm:bg-blue")
|
|
27
|
-
*/
|
|
28
|
-
raw: string;
|
|
29
|
-
/**
|
|
30
|
-
* Start position in source code
|
|
31
|
-
*/
|
|
32
|
-
start: number;
|
|
33
|
-
/**
|
|
34
|
-
* End position in source code
|
|
35
|
-
*/
|
|
36
|
-
end: number;
|
|
37
|
-
/**
|
|
38
|
-
* Parent element/component name
|
|
39
|
-
*/
|
|
40
|
-
elementName: string;
|
|
41
|
-
/**
|
|
42
|
-
* Whether this is a static string or dynamic expression
|
|
43
|
-
*/
|
|
44
|
-
isStatic: boolean;
|
|
45
|
-
}
|
|
46
|
-
/**
|
|
47
|
-
* Represents a style attribute found in the AST
|
|
48
|
-
*/
|
|
49
|
-
export interface StyleAttribute {
|
|
50
|
-
/**
|
|
51
|
-
* Raw style string (e.g., "background-color: red;")
|
|
52
|
-
*/
|
|
53
|
-
raw: string;
|
|
54
|
-
/**
|
|
55
|
-
* Start position in source code
|
|
56
|
-
*/
|
|
57
|
-
start: number;
|
|
58
|
-
/**
|
|
59
|
-
* End position in source code
|
|
60
|
-
*/
|
|
61
|
-
end: number;
|
|
62
|
-
/**
|
|
63
|
-
* Parent element/component name
|
|
64
|
-
*/
|
|
65
|
-
elementName: string;
|
|
66
|
-
}
|
|
67
|
-
/**
|
|
68
|
-
* Result of transforming Tailwind classes
|
|
69
|
-
*/
|
|
70
|
-
export interface TransformResult {
|
|
71
|
-
/**
|
|
72
|
-
* CSS styles for inline styleString prop
|
|
73
|
-
*/
|
|
74
|
-
inlineStyles: string;
|
|
75
|
-
/**
|
|
76
|
-
* Responsive classes to keep in class attribute
|
|
77
|
-
*/
|
|
78
|
-
responsiveClasses: string[];
|
|
79
|
-
/**
|
|
80
|
-
* Classes that couldn't be converted (warnings)
|
|
81
|
-
*/
|
|
82
|
-
invalidClasses: string[];
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* Media query CSS to inject into head
|
|
86
|
-
*/
|
|
87
|
-
export interface MediaQueryStyle {
|
|
88
|
-
/**
|
|
89
|
-
* Media query condition (e.g., "@media (max-width: 475px)")
|
|
90
|
-
*/
|
|
91
|
-
query: string;
|
|
92
|
-
/**
|
|
93
|
-
* CSS class name
|
|
94
|
-
*/
|
|
95
|
-
className: string;
|
|
96
|
-
/**
|
|
97
|
-
* CSS rules
|
|
98
|
-
*/
|
|
99
|
-
rules: string;
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* Information about a component's transformations
|
|
103
|
-
*/
|
|
104
|
-
export interface ComponentTransform {
|
|
105
|
-
/**
|
|
106
|
-
* Original source code
|
|
107
|
-
*/
|
|
108
|
-
originalCode: string;
|
|
109
|
-
/**
|
|
110
|
-
* Transformed source code
|
|
111
|
-
*/
|
|
112
|
-
transformedCode: string;
|
|
113
|
-
/**
|
|
114
|
-
* Media queries to inject
|
|
115
|
-
*/
|
|
116
|
-
mediaQueries: MediaQueryStyle[];
|
|
117
|
-
/**
|
|
118
|
-
* Whether <Head> component was found
|
|
119
|
-
*/
|
|
120
|
-
hasHead: boolean;
|
|
121
|
-
/**
|
|
122
|
-
* Warnings encountered during transformation
|
|
123
|
-
*/
|
|
124
|
-
warnings: string[];
|
|
125
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|