Skip to content
Draft
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
12 changes: 6 additions & 6 deletions packages/preview-server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
"description": "A live preview of your emails right in your browser.",
"scripts": {
"build": "cross-env NODE_OPTIONS=\"--experimental-vm-modules --disable-warning=ExperimentalWarning\" tsx ./scripts/build-preview-server.mts",
"caniemail:fetch": "tsx ./scripts/fill-caniemail-data.mts",
"clean": "rimraf dist",
"dev": "cross-env NODE_OPTIONS=\"--experimental-vm-modules --disable-warning=ExperimentalWarning\" tsx ./scripts/dev.mts",
"dev:seed": "tsx ./scripts/seed.mts",
Expand All @@ -13,7 +12,6 @@
},
"main": "./index.mjs",
"dependencies": {
"next": "16.1.5",
"@babel/core": "7.26.10",
"@babel/parser": "7.27.0",
"@babel/traverse": "7.27.0",
Expand All @@ -27,7 +25,6 @@
"@radix-ui/react-toggle": "1.1.10",
"@radix-ui/react-toggle-group": "1.1.11",
"@radix-ui/react-tooltip": "1.2.8",
"@react-email/tailwind": "workspace:2.0.4-canary.0",
"@react-email/body": "workspace:*",
"@react-email/button": "workspace:*",
"@react-email/code-block": "workspace:*",
Expand All @@ -38,6 +35,7 @@
"@react-email/img": "workspace:*",
"@react-email/link": "workspace:*",
"@react-email/preview": "workspace:*",
"@react-email/tailwind": "workspace:2.0.4-canary.0",
"@react-email/text": "workspace:*",
"@tailwindcss/postcss": "4.1.17",
"clsx": "2.1.1",
Expand All @@ -46,6 +44,7 @@
"framer-motion": "12.23.22",
"log-symbols": "4.1.0",
"module-punycode": "npm:punycode@2.3.1",
"next": "16.1.5",
"next-safe-action": "8.0.11",
"node-html-parser": "7.0.2",
"ora": "5.4.1",
Expand All @@ -54,6 +53,7 @@
"prism-react-renderer": "2.4.1",
"react": "19.0.0",
"react-dom": "19.0.0",
"react-email": "workspace:5.3.0-canary.2",
"resend": "6.4.0",
"socket.io-client": "4.8.3",
"sonner": "2.0.7",
Expand All @@ -68,17 +68,17 @@
"@react-email/components": "workspace:*",
"@types/babel__core": "7.20.5",
"@types/babel__traverse": "7.20.7",
"cross-env": "10.1.0",
"@types/css-tree": "2.3.11",
"rimraf": "6.1.2",
"sharp": "0.34.5",
"@types/fs-extra": "11.0.4",
"@types/mime-types": "2.1.4",
"@types/node": "22.19.7",
"@types/normalize-path": "3.0.2",
"@types/react": "19.0.10",
"@types/react-dom": "19.0.4",
"@types/webpack": "5.28.5",
"cross-env": "10.1.0",
"rimraf": "6.1.2",
"sharp": "0.34.5",
"typescript": "5.8.3"
},
"license": "MIT",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,115 +1,27 @@
'use server';
/eleuse server';

import { parse } from '@babel/parser';
import traverse from '@babel/traverse';
import type {
SourceLocation,
StylePropertyUsage,
} from '../../utils/caniemail/ast/get-used-style-properties';
} from '../../utils/ast/get-used-style-properties';
import {
convertLocationIntoObject,
doesPropertyHaveLocation,
getUsedStyleProperties,
} from '../../utils/caniemail/ast/get-used-style-properties';
import type {
CompatibilityStats,
SupportStatus,
} from '../../utils/caniemail/get-compatibility-stats-for-entry';
import { getCompatibilityStatsForEntry } from '../../utils/caniemail/get-compatibility-stats-for-entry';
import { getCssFunctions } from '../../utils/caniemail/get-css-functions';
import { getCssPropertyNames } from '../../utils/caniemail/get-css-property-names';
import { getCssPropertyWithValue } from '../../utils/caniemail/get-css-property-with-value';
import { getCssUnit } from '../../utils/caniemail/get-css-unit';
import { getElementAttributes } from '../../utils/caniemail/get-element-attributes';
import { getElementNames } from '../../utils/caniemail/get-element-names';
} from '../../utils/ast/get-used-style-properties';
import {
type CompatibilityStats,
type SupportStatus,
type EmailClient,
checkSupportForAttribute,
checkSupportForElement,
checkSupportForStyleDeclaration,
} from 'react-email';
import { snakeToCamel } from '../../utils/snake-to-camel';
import { supportEntries } from './caniemail-data';

export interface CompatibilityCheckingResult {
location: SourceLocation;
source: string;
entry: SupportEntry;
status: SupportStatus;
statsPerEmailClient: CompatibilityStats['perEmailClient'];
}

export type EmailClient =
| 'gmail'
| 'outlook'
| 'yahoo'
| 'apple-mail'
| 'aol'
| 'thunderbird'
| 'microsoft'
| 'samsung-email'
| 'sfr'
| 'orange'
| 'protonmail'
| 'hey'
| 'mail-ru'
| 'fastmail'
| 'laposte'
| 't-online-de'
| 'free-fr'
| 'gmx'
| 'web-de'
| 'ionos-1and1'
| 'rainloop'
| 'wp-pl';

export type Platform =
| 'desktop-app'
| 'desktop-webmail'
| 'mobile-webmail'
| 'webmail'
| 'ios'
| 'android'
| 'windows'
| 'macos'
| 'windows-mail'
| 'outlook-com';

export type SupportEntryCategory = 'html' | 'css' | 'image' | 'others';

export interface SupportEntry {
slug: string;
title: string;
description: string | null;
url: string;
category: SupportEntryCategory;
tags: string[];
keywords: string | null;
last_test_date: string;
test_url: string;
test_results_url: string | null;
stats: Partial<
Record<
EmailClient,
Partial<
Record<
Platform,
/*
This last Record<string, string> has only one key, as the
ordered version of caniemail's data is meant to be something like:

[
{ "1.0": "u" },
{ "2.0": "y" },
{ "3.0": "p #1" },
]

So only one key for each object inside of this array, TypeScript can't really
describe this though AFAIK.
*/
Record</* version */ string, string>[]
>
>
>
>;
notes: string | null;
notes_by_num: Record<number, string> | null;
}

const relevantEmailClients: EmailClient[] = [
'gmail',
'apple-mail',
Expand Down Expand Up @@ -146,6 +58,23 @@ export const checkCompatibility = async (
);
const readableStream = new ReadableStream<CompatibilityCheckingResult>({
async start(controller) {
traverse(ast, {
JSXOpeningElement(path) {
if (path.node.name.type === 'JSXIdentifier' && path.node.name.loc) {
const elementName = path.node.name.name;
const stats = checkSupportForElement(elementName, relevantEmailClients);
if (stats.length > 0) {
controller.enqueue({
source: getSourceCodeAt(path.node.name.loc),
location: convertLocationIntoObject(path.node.name.loc),
statsPerEmailClient: compatibilityStats.perEmailClient,
status: compatibilityStats.status,
});
}
}
},
});

for (const entry of supportEntries) {
const compatibilityStats = getCompatibilityStatsForEntry(
entry,
Expand Down Expand Up @@ -176,26 +105,6 @@ export const checkCompatibility = async (

let addedInsight = false;
if (htmlEntryType === 'element') {
traverse(ast, {
JSXOpeningElement(path) {
if (path.node.name.type === 'JSXIdentifier' && !addedInsight) {
const elementName = path.node.name.name;
if (
entryElements.includes(elementName) &&
path.node.name.loc
) {
addedInsight = true;
controller.enqueue({
entry,
source: getSourceCodeAt(path.node.name.loc),
location: convertLocationIntoObject(path.node.name.loc),
statsPerEmailClient: compatibilityStats.perEmailClient,
status: compatibilityStats.status,
});
}
}
},
});
} else {
traverse(ast, {
JSXAttribute(path) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import traverse, { type Node, type NodePath } from '@babel/traverse';
import { inlineStyles, sanitizeStyleSheet } from '@react-email/tailwind';
import type { StyleSheet } from 'css-tree';
import type { AST } from '../../../actions/email-validation/check-compatibility';
import { getTailwindMetadata } from '../tailwind/get-tailwind-metadata';
import type { AST } from '../../actions/email-validation/check-compatibility';
import { getTailwindMetadata } from '../caniemail/tailwind/get-tailwind-metadataata';

export interface StylePropertyUsage {
location: SourceLocation | undefined | null;
Expand Down

This file was deleted.

Loading
Loading