UNPKG

jsx-email

Version:

Render JSX email components to HTML email

173 lines (169 loc) 6.94 kB
/* eslint-disable no-use-before-define */ import { AssertionError } from 'node:assert'; import { existsSync } from 'node:fs'; import { mkdir, rmdir } from 'node:fs/promises'; import { isAbsolute, join, resolve, win32 } from 'node:path'; import os from 'node:os'; 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, originalCwd, 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 When set, builds the preview as a deployable app and saves to disk. Defaults to .deploy --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 = './.deploy' } = argv; const common = { argv, targetPath }; await prepareBuild(common); const config = await getConfig(common); const outDir = isAbsolute(buildPath) ? buildPath : resolve(join(originalCwd, buildPath)); await viteBuild({ ...config, base: basePath, build: { minify: false, outDir, rollupOptions: { 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}`); // On Windows, Vite's import.meta.glob cannot cross drive letters. If the // preview app root and the temporary build directory are on different // drives (e.g., D: vs C:), fail fast with a helpful error. if (os.platform() === 'win32') { const rootDrive = getDriveLetter(root); const buildDrive = getDriveLetter(buildPath); if (rootDrive && buildDrive && rootDrive !== buildDrive) { log.error(`jsx-email preview cannot run on Windows when the application root directory and the system temporary directory are on different drive letters. Please consider using WSL`); throw new AssertionError({ message: `Temporary directory drive letter different than root directory drive letter` }); } } 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 getDriveLetter = (path) => { if (os.platform() !== 'win32') return null; return win32.parse(path).root.slice(0, 2).toUpperCase(); }; 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