Skip to content

Commit

Permalink
feat(jsx-email): improve preview speed, hot reload, routing (#253)
Browse files Browse the repository at this point in the history
  • Loading branch information
shellscape authored Dec 2, 2024
1 parent 841b831 commit ae2fad5
Show file tree
Hide file tree
Showing 24 changed files with 351 additions and 220 deletions.
File renamed without changes.
4 changes: 2 additions & 2 deletions apps/preview/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
# @jsx-email/app-preview
# app-preview

This package contains the components and vite app which power the [`jsx-email`](https://github.com/shellscape/jsx-email) preview app.
This package contains the components and vite app which power the [`jsx-email`](https://github.com/shellscape/jsx-email) preview app. It is copied to the distribution directory before publishing.
2 changes: 1 addition & 1 deletion apps/preview/app/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
rel="shortcut icon"
href="data:image/svg+xml;charset=UTF-8,%3csvg width='126' height='113' viewBox='0 0 126 113' fill='none' xmlns='http://www.w3.org/2000/svg'%3e%3cpath fill-rule='evenodd' clip-rule='evenodd' d='M0.199951 50V109V113H4.19995H121.8H125.8V109V50H117.8V105H8.19995V50H0.199951Z' fill='%23659CC8'/%3e%3cpath d='M0 53.429V47.4258L48.3069 22.8124V32.4176L11.2516 50.2773L11.5517 49.677V51.1778L11.2516 50.5775L48.3069 68.4372V78.0424L0 53.429Z' fill='%23659CC8'/%3e%3cpath d='M79.4367 0L54.6832 92H46.582L71.3356 0H79.4367Z' fill='%23659CC8'/%3e%3cpath d='M126 53.429L77.6931 78.0424V68.4372L114.748 50.5775L114.448 51.1778V49.677L114.748 50.2773L77.6931 32.4176V22.8124L126 47.4258V53.429Z' fill='%23659CC8'/%3e%3c/svg%3e "
/>
<title>JSX Email</title>
<title>jsx-email</title>
</head>
<body style="display: block">
<div id="root"></div>
Expand Down
File renamed without changes.
4 changes: 2 additions & 2 deletions apps/preview/app/src/home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Shell } from './components/shell';

export const Home = ({ templateParts }: { templateParts: any }) => {
React.useEffect(() => {
document.title = 'JSX email';
document.title = 'jsx-email Preview';
}, []);

return (
Expand All @@ -16,7 +16,7 @@ export const Home = ({ templateParts }: { templateParts: any }) => {
id="landing"
className="bg-dark-bg max-w-md border border-dark-bg-border m-auto mt-56 rounded-md p-8"
>
<h2 className="font-medium">JSX Email Preview</h2>
<h2 className="font-medium">jsx-email Preview</h2>
<SlotPrimitive.Slot className="mt-2 mb-4 text-sm">
<SlotPrimitive.Slottable>
<p>
Expand Down
2 changes: 1 addition & 1 deletion apps/preview/app/src/preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ export const Preview = ({ html, jsx, plainText, templateParts, title }: PreviewP
let iframeStyle = {};

React.useEffect(() => {
document.title = `JSX email • ${title}`;
document.title = `jsx-email • ${title}`;

if (view && validViews.includes(view)) setActiveView(view);
}, [searchParams]);
Expand Down
5 changes: 2 additions & 3 deletions apps/preview/app/src/templates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,15 @@ export const gather = async () => {
import: 'default'
});
const builtFiles = await Promise.all(Object.values(imports).map((imp) => imp()));
const dirParts = builtFiles[0].sourceFile.split('/');
const baseDir = dirParts.length ? dirParts[0] : '';
const targetPath = import.meta.env.VITE_JSXEMAIL_TARGET_PATH;
const templateFiles: Record<string, TemplateData> = builtFiles.reduce((acc, file) => {
const templateName = file.templateName || file.sourceFile.split('/').at(-1);

return {
...acc,
[file.sourceFile]: {
html: file.html,
path: file.sourceFile.replace(`${baseDir}/`, ''),
path: file.sourcePath.replace(`${targetPath}/`, ''),
plain: file.plain,
source: file.source,
templateName
Expand Down
File renamed without changes.
2 changes: 0 additions & 2 deletions apps/preview/index.d.ts

This file was deleted.

5 changes: 0 additions & 5 deletions apps/preview/index.js

This file was deleted.

8 changes: 0 additions & 8 deletions apps/preview/moon.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,3 @@ $schema: 'https://moonrepo.dev/schemas/tasks.json'
workspace:
inheritedTasks:
exclude: ['build', 'compile', 'release', 'test']

tasks:
release:
command: versioner --target $projectRoot
options:
cache: false
outputStyle: 'stream'
runDepsInParallel: false
39 changes: 8 additions & 31 deletions apps/preview/package.json
Original file line number Diff line number Diff line change
@@ -1,50 +1,27 @@
{
"name": "@jsx-email/app-preview",
"name": "app-preview",
"version": "3.0.0",
"publishConfig": {
"access": "public"
},
"private": true,
"description": "The preview app source for jsx-email",
"license": "MIT",
"repository": {
"type": "git",
"url": "https://github.com/shellscape/jsx-email.git",
"directory": "apps/preview"
},
"homepage": "https://jsx.email/",
"main": "index.js",
"module": "index.js",
"engines": {
"node": ">=18.0.0"
},
"files": [
"app",
"index.d.ts",
"index.js",
"README.md"
],
"peerDependencies": {
"react": "^18.2.0"
},
"dependencies": {
"type": "module",
"devDependencies": {
"@radix-ui/colors": "3.0.0",
"@radix-ui/react-collapsible": "1.1.1",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-popover": "1.1.2",
"@radix-ui/react-select": "^2.0.0",
"@radix-ui/react-slot": "1.1.0",
"@radix-ui/react-toggle-group": "1.1.0",
"autoprefixer": "^10.4.16",
"classnames": "2.5.1",
"framer-motion": "11.12.0",
"postcss": "^8.4.32",
"react": "^18.2.0",
"react-dom": "18.3.1",
"react-router-dom": "7.0.1",
"shiki": "^1.18.0",
"tailwindcss": "3.4.15",
"titleize": "^4.0.0",
"vite": "^5.2.11"
},
"devDependencies": {
"autoprefixer": "^10.4.16",
"postcss": "^8.4.32",
"tailwindcss": "3.4.15"
}
}
6 changes: 6 additions & 0 deletions packages/jsx-email/moon.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ tasks:
- ~:clean.dist
- ~:compile
- ~:copy.package
- ~:copy.preview
- ~:copy.templates
inputs:
- src
Expand Down Expand Up @@ -41,6 +42,11 @@ tasks:
options:
cache: false

copy.preview:
command: cp -r ../../apps/preview/app dist/preview
options:
cache: false

copy.templates:
command: cp -r ../../assets/templates dist
options:
Expand Down
16 changes: 12 additions & 4 deletions packages/jsx-email/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,6 @@
"email"
],
"peerDependencies": {
"@jsx-email/app-preview": "workspace:^",
"@jsx-email/plugin-inline": "workspace:^",
"@jsx-email/plugin-minify": "workspace:^",
"@jsx-email/plugin-pretty": "workspace:^",
Expand All @@ -73,7 +72,13 @@
"@dot/log": "^0.1.5",
"@jsx-email/doiuse-email": "^1.0.1",
"@parcel/watcher": "^2.4.1",
"@radix-ui/colors": "3.0.0",
"@radix-ui/react-collapsible": "1.1.1",
"@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-popover": "1.1.2",
"@radix-ui/react-select": "^2.0.0",
"@radix-ui/react-slot": "1.1.0",
"@radix-ui/react-toggle-group": "1.1.0",
"@unocss/core": "^0.64.1",
"@unocss/preset-rem-to-px": "^0.64.1",
"@unocss/preset-typography": "^0.64.1",
Expand All @@ -88,6 +93,7 @@
"debug": "^4.3.4",
"esbuild": "^0.24.0",
"find-up": "^7.0.0",
"framer-motion": "11.12.0",
"globby": "14.0.2",
"hash-it": "^6.0.0",
"html-to-text": "9.0.5",
Expand All @@ -100,13 +106,14 @@
"postcss": "^8.4.32",
"postcss-var-replace": "^1.0.0",
"pretty-bytes": "^6.1.1",
"react-dom": "^18.2.0",
"react-router-dom": "7.0.1",
"rehype": "^13.0.1",
"rehype-stringify": "^10.0.0",
"rollup-plugin-hypothetical": "^2.1.1",
"shiki": "^1.1.2",
"source-map-js": "^1.0.2",
"shiki": "^1.18.0",
"source-map-support": "^0.5.21",
"std-env": "^3.6.0",
"tailwindcss": "3.4.15",
"titleize": "^4.0.0",
"unist-util-visit": "^5.0.0",
"valibot": "^0.42.1",
Expand All @@ -123,6 +130,7 @@
"@types/mime-types": "^2.1.4",
"@types/mustache": "^4.2.5",
"@types/pretty": "^2.0.1",
"@types/semver": "^7.5.8",
"@types/source-map-support": "^0.5.10",
"@types/yargs-parser": "^21.0.3",
"hast": "^1.0.0",
Expand Down
58 changes: 40 additions & 18 deletions packages/jsx-email/src/cli/commands/build.mts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import type { CommandFn, TemplateFn } from './types.mjs';

const BuildCommandOptionsStruct = object({
exclude: optional(string()),
html: optional(boolean()),
inlineCss: optional(boolean()),
minify: optional(boolean()),
out: optional(string()),
Expand All @@ -46,15 +47,21 @@ interface BuildOptions {
argv: BuildCommandOptions;
outputBasePath?: string;
path: string;
sourceFile: string;
}

export interface BuildResult {
compiledPath: string;
html: string | null;
metaPath?: string;
plainText: string | null;
sourceFile: string;
templateName: string | null;
writePath: string;
writePathBase: string;
}

export interface BuildTempatesResult extends BuildResult {
fileName: string;
}

export const help = chalk`
Expand All @@ -69,6 +76,7 @@ Builds a template and saves the result
--exclude A micromatch glob pattern that specifies files to exclude from the build
--inline-css Inlines all CSS classes as style attributes on elements
--minify Minify the rendered template before saving
--no-html Disable rendering the HTML for a template. Useful for when only needing plain text
--out File path to save the rendered template
--plain Emit template as plain text
--pretty Oututs HTML in a pretty-print format. Note: Don't use this for production.
Expand All @@ -93,8 +101,8 @@ export const getTempPath = async (type: 'build' | 'preview') => {
};

export const build = async (options: BuildOptions): Promise<BuildResult> => {
const { argv, outputBasePath, path } = options;
const { out, plain, props = '{}', usePreviewProps, writeToFile = true } = argv;
const { argv, outputBasePath, path, sourceFile } = options;
const { html = true, out, plain, props = '{}', usePreviewProps, writeToFile = true } = argv;
const compiledPath = isWindows ? pathToFileURL(normalizePath(path)).toString() : path;
const template = await import(compiledPath);
// proper named export
Expand All @@ -107,35 +115,48 @@ export const build = async (options: BuildOptions): Promise<BuildResult> => {
process.exit(1);
}

const extension = plain ? '.txt' : '.html';
// const extension = plain ? '.txt' : '.html';
const renderProps = usePreviewProps ? template.previewProps || {} : JSON.parse(props);
const fileExt = extname(path);
const templateName = basename(path, fileExt).replace(/-[^-]{8}$/, '');
const component = componentExport(renderProps);
const baseDir = dirname(path);
const writePath = outputBasePath
? join(out!, baseDir.replace(outputBasePath, ''), templateName + extension)
: join(out!, templateName + extension);
? join(out!, baseDir.replace(outputBasePath, ''), templateName)
: join(out!, templateName);
// const writePath = outputBasePath
// ? join(out!, baseDir.replace(outputBasePath, ''), templateName + extension)
// : join(out!, templateName + extension);
let plainText: string | null = null;

await mkdir(dirname(writePath), { recursive: true });

if (plain) {
const plainText = await render(component, { plainText: plain });
if (writeToFile) await writeFile(writePath, plainText, 'utf8');
return { compiledPath, html: null, plainText, templateName: template.templateName, writePath };
plainText = await render(component, { plainText: plain });
if (writeToFile) await writeFile(`${writePath}.txt`, plainText, 'utf8');
if (!html)
return {
compiledPath,
html: null,
plainText,
sourceFile,
templateName: template.templateName,
writePathBase: writePath
};
}

const html = await render(component, argv as any);
const htmlText = await render(component, argv as any);

if (writeToFile) await writeFile(writePath, html, 'utf8');
if (writeToFile) await writeFile(`${writePath}.html`, htmlText, 'utf8');

return {
compiledPath,
html,
html: htmlText,
metaPath: compiledPath.replace(/(\.js)$/, '.meta.json'),
plainText: null,
plainText,
sourceFile,
templateName: template.templateName,
writePath
writePathBase: writePath
};
};

Expand All @@ -162,15 +183,16 @@ export const buildTemplates = async ({ targetPath, buildOptions }: BuildTemplate
const compiledFiles = await compile({
files: targetFiles,
outDir: esbuildOutPath,
writeMeta: !buildOptions.plain
writeMeta: buildOptions.html ?? true
});

const result = await Promise.all(
compiledFiles.map(async (filePath, index) => {
compiledFiles.map(async ({ entryPoint, path }, index) => {
const buildResult = await build({
argv: { ...buildOptions, out: outputPath },
outputBasePath: esbuildOutPath,
path: filePath
path,
sourceFile: entryPoint
});
const res = {
fileName: targetFiles[index],
Expand Down Expand Up @@ -204,7 +226,7 @@ export const buildTemplates = async ({ targetPath, buildOptions }: BuildTemplate
}
}

return result;
return result as BuildTempatesResult[];
};

export const command: CommandFn = async (argv: BuildCommandOptions, input) => {
Expand Down
Loading

0 comments on commit ae2fad5

Please sign in to comment.