jsx-email
Version:
Render JSX email components to HTML email
153 lines (149 loc) • 5.84 kB
JavaScript
/* eslint-disable no-use-before-define */
import { existsSync } from 'node:fs';
import { mkdir, rmdir } from 'node:fs/promises';
import { join, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import react from '@vitejs/plugin-react';
import chalk from 'chalk';
import { parse as assert } from 'valibot';
// TODO: re-enable this plugin to provide multiple paths for template assets
// import { DynamicPublicDirectory } from 'vite-multiple-assets';
import { build as viteBuild, createServer } from 'vite';
import { log } from '../../log.js';
import { buildForPreview, writePreviewDataFiles } from '../helpers.mjs';
import { reloadPlugin } from '../vite-reload.mjs';
import { staticPlugin } from '../vite-static.mjs';
import { watch } from '../watcher.mjs';
import { getTempPath } from './build.mjs';
import { PreviewCommandOptionsStruct } from './types.mjs';
// eslint-disable-next-line no-console
const newline = () => console.log('');
const { JSX_DEV_LOCAL = false } = process.env;
export const help = chalk `
{blue email preview}
Starts the preview server for a directory of email templates
{underline Usage}
$ email preview <template dir path> [...options]
{underline Options}
--build-path An absolute path. When set, builds the preview as a deployable app and saves to disk
--exclude A micromatch glob pattern that specifies files to exclude from the preview
--host Allow thew preview server to listen on all addresses (0.0.0.0)
--no-open Do not open a browser tab when the preview server starts
--port The local port number the preview server should run on. Default: 55420
{underline Examples}
$ email preview ./src/templates --port 55420
$ email preview ./src/templates --build-path /tmp/email-preview
`;
const buildDeployable = async ({ argv, targetPath }) => {
if (argv.basePath) {
log.warn(chalk `The {yellow basePath} flag is depcrecated. The preview now deploys to a relative root by default. This flag is no longer needed`);
}
const { basePath = './', buildPath } = argv;
const common = { argv, targetPath };
await prepareBuild(common);
const config = await getConfig(common);
await viteBuild({
...config,
base: basePath,
build: {
minify: false,
outDir: buildPath,
rollupOptions: {
external: ['react/jsx-runtime'],
output: {
manualChunks: {}
}
},
target: 'esnext',
watch: void 0
},
define: {
'process.env': '{}'
},
mode: 'development'
});
};
const getConfig = async ({ argv, targetPath }) => {
const buildPath = await getTempPath('preview');
const root = JSX_DEV_LOCAL
? fileURLToPath(import.meta.resolve('../../../../../../apps/preview/app'))
: fileURLToPath(import.meta.resolve('../../../preview'));
const { basePath = '/', host = false, port = 55420 } = argv;
if (JSX_DEV_LOCAL)
log.warn(chalk `{yellow JSX_DEV_LOCAL is set}. using preview source from ${root}`);
log.debug(`Vite Root: ${root}`);
newline();
log.info(chalk `{blue Starting build...}`);
process.env.VITE_JSXEMAIL_BASE_PATH = basePath;
process.env.VITE_JSXEMAIL_TARGET_PATH = targetPath;
// Note: If we don't do this, vite won't know where to run from.
// And apparently there's a tailwind bug if we set the `root` config property
process.chdir(root);
const config = {
clearScreen: false,
configFile: false,
optimizeDeps: {
include: ['classnames', 'cookie', 'react-dom', 'react-dom/client', 'set-cookie-parser']
},
// plugins: [DynamicPublicDirectory([join(targetPath, '**')], { ssr: false }), react()],
plugins: [
reloadPlugin({ globs: [join(buildPath, '**/*.js')] }),
staticPlugin({ paths: [join(targetPath, '**')] }),
react()
],
resolve: {
alias: {
'@jsxemailbuild': buildPath
}
},
server: { fs: { strict: false }, host, port: parseInt(port.toString(), 10) }
};
return config;
};
const prepareBuild = async ({ targetPath, argv }) => {
const buildPath = await getTempPath('preview');
const { exclude } = argv;
if (existsSync(buildPath))
await rmdir(buildPath, { recursive: true });
await mkdir(buildPath, { recursive: true });
const files = await buildForPreview({ buildPath, exclude, targetPath });
await writePreviewDataFiles(files);
return files;
};
const start = async ({ targetPath, argv }) => {
const common = { argv, targetPath };
const files = await prepareBuild(common);
const config = await getConfig(common);
const { open = true } = argv;
const server = await createServer(config);
newline();
log.info(chalk ` 🚀 {yellow jsx-email} Preview\n`);
await server.listen();
server.bindCLIShortcuts();
if (open)
server.openBrowser();
server.printUrls();
return { files, server };
};
export const command = async (argv, input) => {
if (input.length < 1)
return false;
assert(PreviewCommandOptionsStruct, argv);
const [target] = input;
const targetPath = resolve(target);
if (!existsSync(targetPath)) {
newline();
log.error(chalk `{red D'oh} The directory provided ({dim ${targetPath}}) doesn't exist`);
return true;
}
const common = { argv, targetPath };
if (argv.buildPath) {
await buildDeployable(common);
return true;
}
globalThis.isJsxEmailPreview = true;
const { files, server } = await start(common);
await watch({ common, files, server });
return true;
};
//# sourceMappingURL=preview.mjs.map