Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(react-email): linter #2020

Open
wants to merge 19 commits into
base: canary
Choose a base branch
from
Open
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
b740a52
feat(react-email): added a theme switcher to the dev preview (#1749)
KayleeWilliams Feb 17, 2025
cda2b70
fix(cli): Correct typo in cliPacakgeLocation to cliPackageLocation (#…
hautest Feb 19, 2025
f582ba9
fix(deps): update dependency esbuild to v0.25.0 [security] (#1904)
renovate[bot] Feb 19, 2025
8545529
chore(deps-dev): bump vite from 5.4.13 to 5.4.14 (#1899)
dependabot[bot] Feb 19, 2025
b0cbc88
chore(deps-dev): bump vitest from 2.0.5 to 2.1.9 (#1898)
dependabot[bot] Feb 19, 2025
d994cf3
chore(all): remove vitest from individual pacakges
gabrielmfern Feb 19, 2025
c5ba696
chore(deps): update dependency clsx to v2.1.1 (#1894)
renovate[bot] Feb 19, 2025
7332c2b
chore(deps): update dependency @changesets/cli to v2.28.0 (#1893)
renovate[bot] Feb 19, 2025
08cb9c3
feat(tailwind): extract pseudo classes to stylesheet (#1864)
Sjoertjuh Feb 26, 2025
0219c33
fix(tailwind): Infinite loop during sanitization
gabrielmfern Feb 27, 2025
d1c1628
chore(tailwind): Improve code for running Tailwind integration test (…
gabrielmfern Feb 27, 2025
5d0f4ab
chore(root): Improve caching of CI (#1936)
gabrielmfern Feb 27, 2025
c334bce
chore(render): Remove duplicate code from renderAsync (#1934)
gabrielmfern Feb 27, 2025
38d6538
chore(deps): update dependency @types/node to v20.17.22 (#1940)
renovate[bot] Mar 3, 2025
6b0686e
chore(deps): update dependency @changesets/cli to v2.28.1 (#1939)
renovate[bot] Mar 3, 2025
28c0a99
fix(react-email): Detection of files with various export patterns (#…
DavidHuie Mar 13, 2025
7fc787f
chore(deps): upgrade prettier to 3.5.3 (#1959)
h4sohail Mar 20, 2025
30d0b6d
fix(deps): update dependency prismjs to v1.30.0 [security] (#1949)
renovate[bot] Mar 20, 2025
1cfe015
fix(react-email): linter
bukinoshita Mar 28, 2025
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
5 changes: 5 additions & 0 deletions .changeset/dirty-needles-chew.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"react-email": minor
---

Theme switcher for email template
5 changes: 5 additions & 0 deletions .changeset/great-parrots-yell.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@react-email/tailwind": minor
---

Extract tailwind pseudo classes to stylesheet
5 changes: 5 additions & 0 deletions .changeset/ninety-apes-love.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"react-email": patch
---

update esbuild to 0.25.0
5 changes: 5 additions & 0 deletions .changeset/shy-fans-allow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"react-email": patch
---

Fix detection of files with various export patterns
101 changes: 61 additions & 40 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -5,8 +5,10 @@ on:
- main
pull_request:
jobs:
lint:
build:
runs-on: buildjet-4vcpu-ubuntu-2204
outputs:
cache-hit: ${{ steps.pnpm-cache.outputs.cache-hit }}
container:
image: node:22
steps:
@@ -19,95 +21,114 @@ jobs:
corepack enable
corepack prepare pnpm@9.15.0 --activate
pnpm config set script-shell "/usr/bin/bash"
echo "::set-output name=pnpm_cache_dir::$(pnpm store path)"

- name: pnpm Cache
uses: buildjet/cache@v4
with:
path: ${{ steps.pnpm-setup.outputs.pnpm_cache_dir }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
path: |
~/.pnpm-store
node_modules
*/*/node_modules
key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
${{ runner.os }}-pnpm-

- name: Install packages
if: steps.pnpm-cache.outputs.cache-hit != 'true'
run: pnpm install --frozen-lockfile

- name: turborepo Cache
uses: buildjet/cache@v4
with:
path: |
.turbo
key: ${{ runner.os }}-turbo-${{ github.sha }}
restore-keys: |
${{ runner.os }}-turbo-

- name: Run Build
run: pnpm build

- name: Run Lint
run: pnpm lint

test:
lint:
runs-on: buildjet-4vcpu-ubuntu-2204
needs: [build]
container:
image: node:22
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Enable Corepack
id: pnpm-setup
- name: Setup pnpm
run: |
corepack enable
corepack prepare pnpm@9.15.0 --activate
pnpm config set script-shell "/usr/bin/bash"
echo "::set-output name=pnpm_cache_dir::$(pnpm store path)"

- name: pnpm Cache
- name: Restore dependencies
uses: buildjet/cache@v4
with:
path: ${{ steps.pnpm-setup.outputs.pnpm_cache_dir }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
path: |
~/.pnpm-store
node_modules
*/*/node_modules
key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}

- name: Install packages
run: pnpm install --frozen-lockfile
- name: turborepo Cache
uses: buildjet/cache@v4
with:
path: |
.turbo
key: ${{ runner.os }}-turbo-${{ github.sha }}
restore-keys: |
${{ runner.os }}-turbo-

- name: Run Build
run: pnpm build

- name: Run Tests
run: pnpm test
env:
SPAM_ASSASSIN_HOST: ${{ secrets.SPAM_ASSASSIN_HOST }}
SPAM_ASSASSIN_PORT: ${{ secrets.SPAM_ASSASSIN_PORT }}
- name: Run Lint
run: pnpm lint

build:
test:
runs-on: buildjet-4vcpu-ubuntu-2204
needs: [build]
container:
image: node:22
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Enable Corepack
id: pnpm-setup
- name: Setup pnpm
run: |
corepack enable
corepack prepare pnpm@9.15.0 --activate
pnpm config set script-shell "/usr/bin/bash"
echo "::set-output name=pnpm_cache_dir::$(pnpm store path)"

- name: pnpm Cache
- name: Restore dependencies
uses: buildjet/cache@v4
with:
path: ${{ steps.pnpm-setup.outputs.pnpm_cache_dir }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
path: |
~/.pnpm-store
node_modules
*/*/node_modules
key: ${{ runner.os }}-pnpm-${{ hashFiles('**/pnpm-lock.yaml') }}

- name: Install packages
run: pnpm install --frozen-lockfile
- name: turborepo Cache
uses: buildjet/cache@v4
with:
path: |
.turbo
key: ${{ runner.os }}-turbo-${{ github.sha }}
restore-keys: |
${{ runner.os }}-turbo-

- name: Run Build
run: pnpm build
- name: Run Tests
run: pnpm test
env:
SPAM_ASSASSIN_HOST: ${{ secrets.SPAM_ASSASSIN_HOST }}
SPAM_ASSASSIN_PORT: ${{ secrets.SPAM_ASSASSIN_PORT }}

dependencies:
runs-on: buildjet-4vcpu-ubuntu-2204
container:
image: node:18
image: node:22
steps:
- name: Checkout
uses: actions/checkout@v4
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -7,6 +7,7 @@ node_modules

# testing
coverage
package-lock.json

# next.js
.next/
8 changes: 4 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
@@ -16,19 +16,19 @@
},
"devDependencies": {
"@biomejs/biome": "1.9.4",
"@changesets/cli": "2.27.11",
"@changesets/cli": "2.28.1",
"@types/node": "22.10.2",
"@types/react": "19.0.1",
"@types/react-dom": "19.0.1",
"eslint": "8.50.0",
"happy-dom": "15.10.2",
"prettier": "3.4.2",
"prettier": "3.5.3",
"prettier-plugin-tailwindcss": "0.6.6",
"tsconfig": "workspace:*",
"tsup": "8.2.4",
"turbo": "2.3.1",
"vite": "5.4.13",
"vitest": "2.0.5"
"vite": "5.4.14",
"vitest": "2.1.9"
},
"pnpm": {
"overrides": {
2 changes: 1 addition & 1 deletion packages/code-block/package.json
Original file line number Diff line number Diff line change
@@ -47,6 +47,6 @@
"access": "public"
},
"dependencies": {
"prismjs": "1.29.0"
"prismjs": "1.30.0"
}
}
3 changes: 1 addition & 2 deletions packages/code-inline/package.json
Original file line number Diff line number Diff line change
@@ -38,7 +38,6 @@
"@react-email/render": "workspace:*",
"tsconfig": "workspace:*",
"tsup": "7.2.0",
"typescript": "5.1.6",
"vitest": "1.1.0"
"typescript": "5.1.6"
}
}
9 changes: 4 additions & 5 deletions packages/react-email/package.json
Original file line number Diff line number Diff line change
@@ -29,7 +29,7 @@
"chokidar": "4.0.3",
"commander": "11.1.0",
"debounce": "2.0.0",
"esbuild": "0.23.0",
"esbuild": "0.25.0",
"glob": "10.3.4",
"log-symbols": "4.1.0",
"mime-types": "2.1.35",
@@ -45,7 +45,7 @@
"@radix-ui/react-slot": "1.1.0",
"@radix-ui/react-toggle-group": "1.1.0",
"@radix-ui/react-tooltip": "1.1.2",
"@react-email/render": "workspace:*",
"@react-email/components": "workspace:*",
"@swc/core": "1.4.15",
"@types/babel__core": "7.20.5",
"@types/fs-extra": "11.0.1",
@@ -57,7 +57,7 @@
"@types/webpack": "5.28.5",
"@vercel/style-guide": "5.1.0",
"autoprefixer": "10.4.20",
"clsx": "2.1.0",
"clsx": "2.1.1",
"framer-motion": "12.0.0-alpha.2",
"postcss": "8.4.40",
"prism-react-renderer": "2.1.0",
@@ -73,7 +73,6 @@
"tailwindcss": "3.4.0",
"tsup": "7.2.0",
"tsx": "4.9.0",
"typescript": "5.1.6",
"vitest": "1.1.3"
"typescript": "5.1.6"
}
}
4 changes: 2 additions & 2 deletions packages/react-email/src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -11,8 +11,8 @@ const Home = () => {

return (
<Shell>
<div className="relative max-w-lg mx-auto p-8 flex items-center justify-center h-[inherit]">
<div className="relative z-10 flex flex-col text-center items-center">
<div className="relative mx-auto flex h-[inherit] max-w-lg items-center justify-center p-8">
<div className="relative z-10 flex flex-col items-center text-center">
<Image
alt="React Email Icon"
className="mb-8"
29 changes: 14 additions & 15 deletions packages/react-email/src/app/preview/[...slug]/preview.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
'use client';

import { usePathname, useRouter, useSearchParams } from 'next/navigation';
import React from 'react';
import React, { useRef } from 'react';
import { Toaster } from 'sonner';
import type { EmailRenderingResult } from '../../../actions/render-email-by-path';
import { CodeContainer } from '../../../components/code-container';
import { Shell } from '../../../components/shell';
import { Tooltip } from '../../../components/tooltip';
import { useEmailRenderingResult } from '../../../hooks/use-email-rendering-result';
import { useHotreload } from '../../../hooks/use-hot-reload';
import { useIframeColorScheme } from '../../../hooks/use-iframe-color-scheme';
import { useRenderingMetadata } from '../../../hooks/use-rendering-metadata';
import { RenderingError } from './rendering-error';

@@ -29,6 +30,7 @@ const Preview = ({
const pathname = usePathname();
const searchParams = useSearchParams();

const activeTheme = searchParams.get('theme') ?? 'light';
const activeView = searchParams.get('view') ?? 'desktop';
const activeLang = searchParams.get('lang') ?? 'jsx';

@@ -43,6 +45,9 @@ const Preview = ({
serverRenderingResult,
);

const iframeRef = useRef<HTMLIFrameElement>(null);
useIframeColorScheme(iframeRef, activeTheme);

if (process.env.NEXT_PUBLIC_IS_BUILDING !== 'true') {
// this will not change on runtime so it doesn't violate
// the rules of hooks
@@ -60,28 +65,20 @@ const Preview = ({
});
}

const handleViewChange = (view: string) => {
const params = new URLSearchParams(searchParams);
params.set('view', view);
router.push(`${pathname}?${params.toString()}`);
};
const hasNoErrors = typeof renderedEmailMetadata !== 'undefined';

const handleLangChange = (lang: string) => {
const setActiveLang = (lang: string) => {
const params = new URLSearchParams(searchParams);
params.set('view', 'source');
params.set('lang', lang);
router.push(`${pathname}?${params.toString()}`);
};

const hasNoErrors = typeof renderedEmailMetadata !== 'undefined';

return (
<Shell
activeView={hasNoErrors ? activeView : undefined}
currentEmailOpenSlug={slug}
markup={renderedEmailMetadata?.markup}
pathSeparator={pathSeparator}
setActiveView={hasNoErrors ? handleViewChange : undefined}
>
{/* This relative is so that when there is any error the user can still switch between emails */}
<div className="relative h-full">
@@ -93,22 +90,24 @@ const Preview = ({
<>
{activeView === 'desktop' && (
<iframe
className="w-full bg-white h-[calc(100vh_-_140px)] lg:h-[calc(100vh_-_70px)]"
className="h-[calc(100vh_-_140px)] w-full bg-white lg:h-[calc(100vh_-_70px)]"
ref={iframeRef}
srcDoc={renderedEmailMetadata.markup}
title={slug}
/>
)}

{activeView === 'mobile' && (
<iframe
className="w-[360px] bg-white h-[calc(100vh_-_140px)] lg:h-[calc(100vh_-_70px)] mx-auto"
className="mx-auto h-[calc(100vh_-_140px)] w-[360px] bg-white lg:h-[calc(100vh_-_70px)]"
ref={iframeRef}
srcDoc={renderedEmailMetadata.markup}
title={slug}
/>
)}

{activeView === 'source' && (
<div className="flex gap-6 mx-auto p-6 max-w-3xl">
<div className="mx-auto flex max-w-3xl gap-6 p-6">
<Tooltip.Provider>
<CodeContainer
activeLang={activeLang}
@@ -126,7 +125,7 @@ const Preview = ({
content: renderedEmailMetadata.plainText,
},
]}
setActiveLang={handleLangChange}
setActiveLang={setActiveLang}
/>
</Tooltip.Provider>
</div>
Loading