Skip to content

Commit

Permalink
fix(jsx-email): rollback the watcher to only watch entrypoint directo…
Browse files Browse the repository at this point in the history
…ries
  • Loading branch information
shellscape committed Dec 3, 2024
1 parent 4f0fac9 commit a0023b3
Show file tree
Hide file tree
Showing 3 changed files with 243 additions and 55 deletions.
104 changes: 69 additions & 35 deletions packages/jsx-email/src/cli/watcher.mts
Original file line number Diff line number Diff line change
Expand Up @@ -33,62 +33,96 @@ const removeChildPaths = (paths: string[]): string[] =>
(p1) => !paths.some((p2) => p1 !== p2 && relative(p2, p1) && !relative(p2, p1).startsWith('..'))
);

const mapDeps = async (files: BuildTempatesResult[]) => {
const depPaths: string[] = [];
const metaReads = files.map(async ({ metaPath }) => {
const getEntrypoints = async (files: BuildTempatesResult[]) => {
const entrypoints: Set<string> = new Set();
const promises = files.map(async ({ metaPath }) => {
log.debug({ exists: await exists(metaPath ?? ''), metaPath });

if (!metaPath || !(await exists(metaPath))) return null;
const contents = await readFile(metaPath, 'utf-8');
const metafile = JSON.parse(contents) as Metafile;
const { outputs } = metafile;
const result = new Map<string, Set<string>>();

Object.entries(outputs).forEach(([_, meat]) => {
const { entryPoint, inputs } = meat;
const resolvedEntry = resolve(originalCwd, entryPoint!);
depPaths.push(resolvedEntry);

for (const dep of Object.keys(inputs)) {
const resolvedDepPath = resolve(originalCwd, dep);
const set = result.get(resolvedDepPath) ?? new Set();

depPaths.push(resolvedDepPath);
set.add(resolvedEntry);
result.set(resolvedDepPath, set);
}

Object.entries(metafile.outputs).forEach(([_, { entryPoint }]) => {
entrypoints.add(resolve(originalCwd, entryPoint!));
});

return result;
return null;
});
const deps = (await Promise.all(metaReads)).filter(Boolean);

return { depPaths, deps };
await Promise.all(promises);

log.debug({ entrypoints });

return Array.from(entrypoints).filter(Boolean);
};

const getWatchPaths = async (files: BuildTempatesResult[]) => {
const entrypoints = await getEntrypoints(files);
const paths = entrypoints.map((path) => dirname(path));
const uniquePaths = Array.from(new Set(paths));
const watchPaths = removeChildPaths(uniquePaths);

log.debug({ watchPaths });

return watchPaths;
};

// const mapDeps = async (files: BuildTempatesResult[]) => {
// const depPaths: string[] = [];
// const metaReads = files.map(async ({ metaPath }) => {
// log.debug({ exists: await exists(metaPath ?? ''), metaPath });

// if (!metaPath || !(await exists(metaPath))) return null;
// const contents = await readFile(metaPath, 'utf-8');
// const metafile = JSON.parse(contents) as Metafile;
// const { outputs } = metafile;
// const result = new Map<string, Set<string>>();

// Object.entries(outputs).forEach(([_, meat]) => {
// const { entryPoint, inputs } = meat;
// const resolvedEntry = resolve(originalCwd, entryPoint!);
// depPaths.push(resolvedEntry);

// for (const dep of Object.keys(inputs)) {
// const resolvedDepPath = resolve(originalCwd, dep);
// const set = result.get(resolvedDepPath) ?? new Set();

// depPaths.push(resolvedDepPath);
// set.add(resolvedEntry);
// result.set(resolvedDepPath, set);
// }
// });

// return result;
// });
// const deps = (await Promise.all(metaReads)).filter(Boolean);

// return { depPaths, deps };
// };

export const watch = async (args: WatchArgs) => {
newline();
log.info(chalk`{blue Starting watcher...}\n`);

const { common, files, server } = args;
const { argv } = common;
const extensions = ['.css', '.js', '.jsx', '.ts', '.tsx'];
const watchPaths = await getWatchPaths(files);

const { depPaths, deps: metaDeps } = await mapDeps(files);
const templateDeps = new Map<string, Set<string>>();
// const { depPaths, deps: metaDeps } = await mapDeps(files);
// const templateDeps = new Map<string, Set<string>>();

for (const map of metaDeps) {
map!.forEach((value, key) => templateDeps.set(key, value));
}
// for (const map of metaDeps) {
// map!.forEach((value, key) => templateDeps.set(key, value));
// }

const handler: watcher.SubscribeCallback = async (_, events) => {
const changedFiles = events
.filter((event) => event.type !== 'create' && event.type !== 'delete')
.map((e) => e.path)
.filter((path) => extensions.includes(extname(path)));
const changedTemplates = changedFiles
.flatMap((file) => [...(templateDeps.get(file) || [])])
.filter(Boolean);
const templateFileNames = files.map((file) => file.fileName);
const changedTemplates = changedFiles.filter((file) => templateFileNames.includes(file));
const createdFiles = events
.filter((event) => event.type === 'create')
.map((e) => e.path)
Expand Down Expand Up @@ -129,7 +163,7 @@ export const watch = async (args: WatchArgs) => {
log.info(
chalk`{cyan Building}`,
createdFiles.length,
`file${changedTemplates.length === 1 ? '' : 's'}:\n `,
`file${createdFiles.length === 1 ? '' : 's'}:\n `,
createdFiles.join('\n '),
'\n'
);
Expand Down Expand Up @@ -166,10 +200,10 @@ export const watch = async (args: WatchArgs) => {
});
};

const watchPathSet = new Set([
...depPaths.filter((path) => !path.includes('/node_modules/')).map((path) => dirname(path))
]);
const watchPaths = removeChildPaths([...watchPathSet]);
// const watchPathSet = new Set([
// ...depPaths.filter((path) => !path.includes('/node_modules/')).map((path) => dirname(path))
// ]);
// const watchPaths = removeChildPaths([...watchPathSet]);

log.debug('Watching Paths:', watchPaths.sort());

Expand Down
184 changes: 169 additions & 15 deletions test/smoke/tests/.snapshots/smoke.test.ts-watcher-chromium.snap
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,178 @@
xmlns:v="urn:schemas-microsoft-com:vml"
>
<head>
<meta
name="color-scheme"
content="normal"
>
<meta
name="supported-color-schemes"
content="normal"
>
<style>
:root {
color-scheme: normal;
supported-color-schemes: normal;
}
</style>
<meta
http-equiv="Content-Type"
content="text/html; charset=UTF-8"
>
<meta
name="viewport"
content="width=device-width, initial-scale=1, user-scalable=yes"
>
<meta name="x-apple-disable-message-reformatting">
<meta
name="format-detection"
content="telephone=no, date=no, address=no, email=no, url=no"
>
<meta
name="viewport"
content="width=device-width, initial-scale=1, user-scalable=yes"
>
<meta name="x-apple-disable-message-reformatting">
<meta
name="format-detection"
content="telephone=no, date=no, address=no, email=no, url=no"
>
<style>
@font-face {
font-family: 'Roboto';
font-style: normal;
font-weight: 400;
mso-font-alt: 'Verdana';
src: url(https://fonts.gstatic.com/s/roboto/v27/KFOmCnqEu92Fr1Mu4mxKKTU1Kg.woff2) format('woff2');
}

* {
font-family: 'Roboto', Verdana;
}
</style>
</head>
<body>
<!--[if mso]><meta content="batman"/><![endif]-->
<!--[if mso]><xml><o:OfficeDocumentSettings><o:AllowPNG></o:AllowPNG><o:PixelsPerInch>96</o:PixelsPerInch></o:OfficeDocumentSettings></xml><![endif]-->
<div
data-skip="true"
style="display:none;line-height:1px;max-height:0;max-width:0;opacity:0;overflow:hidden"
>
Preview Content
<div>
&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏&nbsp;‌​‍‎‏
</div>
</div>
<p style="font-size:14px;line-height:24px;margin:16px 0">
component batman
component test
</p>
<img
id="image"
alt="Cat"
src="/static/cat.jpeg"
width="200"
height="200"
style="border:none;display:block;outline:none;text-decoration:none"
>
<a
href="/static/cat.jpeg"
style="color:#067df7;text-decoration:none"
>
/static/cat.jpeg
</a>
<div style="table-layout:fixed;width:100%">
<div style="margin:0 auto;max-width:600px">
<span>
<!--[if mso]><table align="center" width="600" style="border-spacing: 0; width:600px;" role="presentation"><tr><td><![endif]-->
</span>
<table
align="center"
width="100%"
role="presentation"
cellspacing="0"
cellpadding="0"
border="0"
style="max-width:600px"
>
<tbody>
<tr style="width:100%">
<td align="center">
<table
align="center"
width="100%"
border="0"
cellpadding="0"
cellspacing="0"
role="presentation"
>
<tbody>
<tr>
<td>
<table
width="100%"
border="0"
cellpadding="0"
cellspacing="0"
style="border-collapse:collapse"
role="presentation"
>
<tbody>
<tr>
<td align="left">
<span>
<!--[if mso]>
<v:roundrect xmlns:v="urn:schemas-microsoft-com:vml" xmlns:w="urn:schemas-microsoft-com:office:word" style="height:40px;v-text-anchor:middle;width:200px;" arcsize="0%" strokeweight="1px" fillcolor=#FFFFFF>
<w:anchorlock/>
<center style="font-size:16px;color:#000000;">
Button Content
</center></v:roundrect>
<![endif]-->
</span>
<a
href="https://jsx.email"
style="-webkit-text-size-adjust:none;border-radius:0;display:inline-block;font-size:16px;line-height:38px;max-width:200px;text-align:center;text-decoration:none;width:100%;background-color:#FFFFFF;color:#000000;mso-hide:all"
>
Button Content
</a>
</td>
</tr>
</tbody>
</table>
<h1 style>
Heading Content
</h1>
<hr style="border:none;border-top:1px solid #eaeaea;width:100%">
<img
src="https://about.google/assets-main/img/glue-google-solid-logo.svg"
style="border:none;display:block;outline:none;text-decoration:none"
>
<a
href="https://jsx.email"
style="color:#067df7;text-decoration:none"
>
</a>
<div style="border:solid 1px black;padding:12px">
<h1 style="color:red">
Hello, World!
</h1>
</div>
<table
align="center"
width="100%"
role="presentation"
cellspacing="0"
cellpadding="0"
border="0"
>
<tbody style="width:100%">
<tr style="width:100%">
<td>
Column
</td>
</tr>
</tbody>
</table>
<p style="font-size:14px;line-height:24px;margin:16px 0">
Removed Content
</p>
</td>
</tr>
</tbody>
</table>
</td>
</tr>
</tbody>
</table>
<span>
<!--[if mso]></td></tr></table><![endif]-->
</span>
</div>
</div>
</body>
</html>
10 changes: 5 additions & 5 deletions test/smoke/tests/smoke.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,29 +75,29 @@ test('watcher', async ({ page }) => {

const isLocal = process.env.CI !== 'true';
const targetFilePath = isLocal
? join(__dirname, '../fixtures/components/text.tsx')
: '/home/runner/work/jsx-email/jsx-email/jsx-email-tests/smoke-test/fixtures/components/text.tsx';
? join(__dirname, '../fixtures/templates/base.jsx')
: '/home/runner/work/jsx-email/jsx-email/jsx-email-tests/smoke-test/fixtures/templates/base.jsx';

console.log({ isLocal, targetFilePath });

const contents = await readFile(targetFilePath, 'utf8');

console.log({ contents });

await writeFile(targetFilePath, contents.replace('test', 'batman'), 'utf8');
await writeFile(targetFilePath, contents.replace('Text Content', 'Removed Content'), 'utf8');

console.log('after write:', await readFile(targetFilePath, 'utf8'));

await page.waitForTimeout(45e3);
await page.waitForSelector('#link-Local-Assets', timeout);

page.locator('#link-Local-Assets').click();
page.locator('#link-Base').click();

const iframe = await page.frameLocator('#preview-frame');
const html = await getHTML(iframe.locator('html'), { deep: true });

expect(html).toMatchSnapshot({ name: `watcher.snap` });

// Note: so we don't have dirty files when running smoketest locally
await writeFile(targetFilePath, contents.replace('batman', 'test'), 'utf8');
await writeFile(targetFilePath, contents.replace('Removed Content', 'Text Content'), 'utf8');
});

0 comments on commit a0023b3

Please sign in to comment.