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

feat: vite glob import util + updated and faster site (hope) #2

Merged
merged 11 commits into from
Nov 5, 2024
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/bump.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: "16"
node-version: "20"
cache: "npm"
- name: Install dependencies
uses: bahmutov/npm-install@v1
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
- name: Use Node LTS
uses: actions/setup-node@v2
with:
node-version: "lts/*"
node-version: "20"

- name: Install dependencies
uses: bahmutov/npm-install@v1
Expand All @@ -31,7 +31,7 @@ jobs:
- name: Use Node LTS
uses: actions/setup-node@v2
with:
node-version: "lts/*"
node-version: "20"

- name: Install dependencies
uses: bahmutov/npm-install@v1
Expand All @@ -49,7 +49,7 @@ jobs:
- name: Use Node LTS
uses: actions/setup-node@v2
with:
node-version: "lts/*"
node-version: "20"

- name: Install dependencies
uses: bahmutov/npm-install@v1
Expand All @@ -67,7 +67,7 @@ jobs:
- name: Use Node LTS
uses: actions/setup-node@v2
with:
node-version: "lts/*"
node-version: "20"

- name: Install dependencies
uses: bahmutov/npm-install@v1
Expand Down
50 changes: 46 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,52 @@ If you and are having issues loading typescript types for `remix-mailer`, ensure

### Previews

Previews are easy to setup and rely on zero magic. Start by creating a route where you would like your previews to live. For this example we are using `app/routes/email.tsx` which will be a route at `/email`. Next, import `remix-mailer` utils and components along with your email templates and render function of choice. This example uses `@react-email/components` but you can essentially use render function that returns a string of html.
Previews are easy to setup and rely on zero magic. Below are examples of setting up an email preview route.

> Note that `requireDev` is a helper that ensures you are in either `development` or `test` when accessing this route. Otherwise a `403` response is thrown.

```tsx
// with vite

// app/routes/email.tsx -> /email
import { renderAsync } from "@react-email/components";
import { json, type LoaderFunctionArgs } from "@remix-run/node";

// remix-mailer
import { createPreviews } from "remix-mailer/server/create-previews";
import { requireDev } from "remix-mailer/server/require-dev";
import { emailsFromGlob } from "remix-mailer/server/emails-from-glob";
import { PreviewBrowser } from "remix-mailer/ui/preview-browser";

import "remix-mailer/ui/index.css";

// import emails using glob
const emails = import.meta.glob("~/emails/*.tsx");

export const loader = async ({ request }: LoaderFunctionArgs) => {
requireDev();

const previews = await createPreviews(
request,
// takes our glob import and turns it into an emails object
await emailsFromGlob(emails),
{
render: (Component) =>
renderAsync(<Component {...Component.PreviewProps} />),
}
);

return json({
...previews,
});
};

export default PreviewBrowser;
```

```tsx
// without vite

// app/routes/email.tsx -> /email
import { renderAsync } from "@react-email/components";
import {
Expand Down Expand Up @@ -84,14 +125,15 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {

const previews = await createPreviews(
request,
// our email components imported above
{
loginCode: LoginCode,
resetPassword: ResetPassword,
},
{
render: (Component) =>
renderAsync(<Component {...Component.PreviewProps} />),
},
}
);

return json({
Expand All @@ -104,7 +146,7 @@ export default PreviewBrowser;

### Intercept

Intercept allows you to catch emails that are being sent in development environments for an easier testing experience. When an email is sent in `development` or `test`, a new browser window will be opened with the rendered result of the email. This can be helpful for example if you are testing locally with magic-link based authentication. This example uses `nodemailer` as a transport, but any transport can be used.
Intercept allows you to catch emails that are being sent in development and test. When an email is sent in `development` or `test`, a new browser window will be opened to a preview of the email that was sent. This can be helpful for example if you are testing locally with magic-link based authentication. This example uses `nodemailer` as a transport, but any transport can be used.

```tsx
// /app/email.server.ts
Expand Down Expand Up @@ -139,7 +181,7 @@ const previews = await createPreviews(
interceptCache,
render: (Component) =>
renderAsync(<Component {...Component.PreviewProps} />),
},
}
);
```

Expand Down
File renamed without changes.
1 change: 1 addition & 0 deletions apps/example/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ node_modules
/build
/public/build
.env
.cache

/cypress/screenshots
/cypress/videos
Expand Down
48 changes: 48 additions & 0 deletions apps/example/app/example.shiki
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
tsx


import { renderAsync } from "@react-email/components";
import {
json,
type LinksFunction,
type LoaderFunctionArgs,
} from "@remix-run/node";

// remix-mailer
import { createPreviews } from "remix-mailer/server/create-previews";
import { requireDev } from "remix-mailer/server/require-dev";
import remixMailerStylesheet from "remix-mailer/ui/index.css";
import { PreviewBrowser } from "remix-mailer/ui/preview-browser";

// email templates
import { LoginCode } from "~/emails/login-code";
import { ResetPassword } from "~/emails/reset-password";

export const links: LinksFunction = () => [
{
rel: "stylesheet",
href: remixMailerStylesheet,
},
];

export const loader = async ({ request }: LoaderFunctionArgs) => {
requireDev();

const previews = await createPreviews(
request,
{
loginCode: LoginCode,
resetPassword: ResetPassword,
},
{
render: (Component) =>
renderAsync(<Component {...Component.PreviewProps} />),
}
);

return json({
...previews,
});
};

export default PreviewBrowser;
11 changes: 1 addition & 10 deletions apps/example/app/root.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,11 @@
import { cssBundleHref } from "@remix-run/css-bundle";
import type { LinksFunction } from "@remix-run/node";
import {
Links,
LiveReload,
Meta,
Outlet,
Scripts,
ScrollRestoration,
} from "@remix-run/react";
import stylesheet from "~/tailwind.css";

export const links: LinksFunction = () => [
{ rel: "stylesheet", href: stylesheet },
...(cssBundleHref ? [{ rel: "stylesheet", href: cssBundleHref }] : []),
];
import "~/tailwind.css";

export default function App() {
return (
Expand All @@ -28,7 +20,6 @@ export default function App() {
<Outlet />
<ScrollRestoration />
<Scripts />
<LiveReload />
</body>
</html>
);
Expand Down
109 changes: 25 additions & 84 deletions apps/example/app/routes/_index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,8 @@ import {
GitHubLogoIcon,
} from "@radix-ui/react-icons";
import { renderAsync } from "@react-email/components";
import {
type LoaderFunctionArgs,
json,
type LinksFunction,
} from "@remix-run/node";
import { Link, useLoaderData } from "@remix-run/react";
import { type LoaderFunctionArgs, json } from "@remix-run/node";
import { Link } from "@remix-run/react";
import { useEffect, useRef, useState } from "react";
import { createPreviews } from "remix-mailer/server/create-previews";
import { delay, fromEvent, mergeMap, tap } from "rxjs";
Expand All @@ -19,67 +15,13 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
import { cn } from "@/lib/utils";
import LoginCode from "~/emails/login-code";
import ResetPassword from "~/emails/reset-password";
import * as shiki from "~/shiki.server";
import { PreviewBrowser } from "remix-mailer/ui/preview-browser";
import exampleCode from "~/example.shiki";
import viteExampleCode from "~/vite-example.shiki";

import remixMailerStylesheet from "remix-mailer/ui/index.css";
import "remix-mailer/ui/index.css";
import { Logo } from "@/components/ui/logo";

export const links: LinksFunction = () => [
{
rel: "stylesheet",
href: remixMailerStylesheet,
},
];

export const code = `
import { renderAsync } from "@react-email/components";
import {
json,
type LinksFunction,
type LoaderFunctionArgs,
} from "@remix-run/node";

// remix-mailer
import { createPreviews } from "remix-mailer/server/create-previews";
import { requireDev } from "remix-mailer/server/require-dev";
import remixMailerStylesheet from "remix-mailer/ui/index.css";
import { PreviewBrowser } from "remix-mailer/ui/preview-browser";

// email templates
import { LoginCode } from "~/emails/login-code";
import { ResetPassword } from "~/emails/reset-password";

export const links: LinksFunction = () => [
{
rel: "stylesheet",
href: remixMailerStylesheet,
},
];

export const loader = async ({ request }: LoaderFunctionArgs) => {
requireDev();

const previews = await createPreviews(
request,
{
loginCode: LoginCode,
resetPassword: ResetPassword,
},
{
render: (Component) =>
renderAsync(<Component {...Component.PreviewProps} />),
}
);

return json({
...previews,
});
};

export default PreviewBrowser;
`;

export const loader = async ({ request }: LoaderFunctionArgs) => {
const previews = await createPreviews(
request,
Expand All @@ -95,12 +37,6 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {

return json({
...previews,
shikiized: await shiki.shikiize({
example: {
lang: "tsx",
code,
},
}),
});
};

Expand Down Expand Up @@ -191,11 +127,17 @@ export default function _Index() {
>
Previewer
</TabsTrigger>
<TabsTrigger
value="vite"
className="transition-colors border border-border/0 data-[state=active]:border-border/100"
>
Vite
</TabsTrigger>
<TabsTrigger
value="code"
className="transition-colors border border-border/0 data-[state=active]:border-border/100"
>
Code
No Vite
</TabsTrigger>
</TabsList>
</Tabs>
Expand All @@ -219,10 +161,17 @@ export default function _Index() {
</div>
</TabsContent>
<TabsContent value="code" className="m-0">
<Shikiize
id="example"
<CodeBlock
lang="tsx"
fileName="app/routes/email.tsx"
code={exampleCode}
/>
</TabsContent>
<TabsContent value="vite" className="m-0">
<CodeBlock
lang="tsx"
fileName="app/routes/email.tsx"
code={viteExampleCode}
/>
</TabsContent>
</Tabs>
Expand Down Expand Up @@ -250,25 +199,17 @@ export default function _Index() {
);
}

const Shikiize = ({
id,
const CodeBlock = ({
className,
fileName,
lang,
code,
}: {
id: string;
className?: string;
fileName?: string;
lang?: string;
code: string;
}) => {
const {
shikiized: { blocks },
} = useLoaderData<typeof loader>();

if (!blocks[id]) {
throw new Error("Highlighted code block does not exist for id: " + id);
}

return (
<div className="relative text-xs bg-muted/50 shadow-md rounded-lg leading-loose">
{typeof fileName === "string" && (
Expand All @@ -287,7 +228,7 @@ const Shikiize = ({
</code>
)}
<div
dangerouslySetInnerHTML={{ __html: blocks[id] }}
dangerouslySetInnerHTML={{ __html: code }}
className={cn("overflow-y-hidden overflow-x-auto px-4 py-3", className)}
/>
</div>
Expand Down
Loading
Loading