Skip to content

Commit f185cdf

Browse files
committed
feat (vs-code-ext): limit the renderOpenEmailFile function to only run once at a time
1 parent a58af10 commit f185cdf

File tree

4 files changed

+189
-78
lines changed

4 files changed

+189
-78
lines changed

packages/vs-code-extension/src/extension.ts

Lines changed: 16 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -3,93 +3,52 @@
33
import * as vscode from "vscode";
44
import { readFileSync } from "fs";
55

6-
import { renderOpenEmailFile } from "./renderOpenEmailFile";
7-
import { convertAllEmailAssetSourcesIntoWebviewURIs } from "./convertAllEmailAssetSourcesIntoWebviewURIs";
8-
import { basename } from "path";
6+
import { updatePreiewPanel } from "./updatePreviewPanel";
7+
8+
export let noEmailOpenHTML: string;
9+
export let emailWithErrorHTML: string;
910

1011
export function activate(context: vscode.ExtensionContext) {
1112
let previewPanel: vscode.WebviewPanel | undefined = undefined;
1213

13-
const noEmailOpenHTML = readFileSync(
14+
// loads in the default htmls
15+
noEmailOpenHTML = readFileSync(
1416
context.asAbsolutePath("./assets/no email open.html"),
1517
{ encoding: "utf-8" },
1618
);
17-
const emailWithErrorHTML = readFileSync(
19+
emailWithErrorHTML = readFileSync(
1820
context.asAbsolutePath("./assets/email with error.html"),
1921
{ encoding: "utf-8" },
2022
);
2123

22-
const updatePreviewPanelContent = async () => {
23-
if (previewPanel) {
24-
if (vscode.window.activeTextEditor) {
25-
try {
26-
let builtEmail = await renderOpenEmailFile(
27-
vscode.window.activeTextEditor,
28-
);
29-
if (builtEmail && builtEmail.valid) {
30-
previewPanel.title = `react-email preview - ${builtEmail.filename}`;
31-
previewPanel.webview.html =
32-
convertAllEmailAssetSourcesIntoWebviewURIs(
33-
builtEmail.html,
34-
vscode.Uri.joinPath(
35-
vscode.window.activeTextEditor.document.uri,
36-
"..",
37-
), // the emails folder
38-
previewPanel,
39-
);
40-
}
41-
// keeps the current content if the email is invalid and did not error
42-
// this invalidness can happen if the focused content is a image,
43-
// does not a export a default and for some other similar situations
44-
} catch (exception) {
45-
previewPanel.title = `react-email preview - error on the email ${basename(
46-
vscode.window.activeTextEditor.document.fileName,
47-
)}`;
48-
let errorMessage: string;
49-
if (exception instanceof Error) {
50-
errorMessage = exception.stack ?? exception.message;
51-
} else {
52-
errorMessage = exception as string;
53-
}
54-
previewPanel.webview.html = emailWithErrorHTML.replace(
55-
"{ERROR MESSAGE}",
56-
errorMessage,
57-
);
58-
}
59-
} else if (previewPanel.webview.html.trim().length === 0) {
60-
previewPanel.title = `react-email preview - try opening an email!`;
61-
previewPanel.webview.html = noEmailOpenHTML;
62-
}
63-
}
64-
};
65-
6624
let disposable = vscode.commands.registerCommand(
6725
"react-email-preview.open",
6826
() => {
69-
if (typeof previewPanel !== 'undefined') return;
27+
if (typeof previewPanel !== "undefined") {
28+
return;
29+
}
7030

7131
previewPanel = vscode.window.createWebviewPanel(
7232
"react-email preview - try opening an email",
7333
"react-email preview - try opening an email",
7434
vscode.ViewColumn.Two,
7535
{ enableScripts: true },
7636
);
37+
previewPanel.webview.html = noEmailOpenHTML;
7738

78-
previewPanel.onDidDispose(() => previewPanel = undefined);
79-
80-
updatePreviewPanelContent();
39+
previewPanel.onDidDispose(() => (previewPanel = undefined));
8140

82-
vscode.workspace.onDidChangeTextDocument((ev) => {
41+
vscode.workspace.onDidChangeTextDocument(async (ev) => {
8342
if (
8443
ev.document.fileName ===
8544
vscode.window.activeTextEditor?.document.fileName
8645
) {
87-
updatePreviewPanelContent();
46+
await updatePreiewPanel(previewPanel);
8847
}
8948
});
9049

91-
vscode.window.onDidChangeActiveTextEditor(() => {
92-
updatePreviewPanelContent();
50+
vscode.window.onDidChangeActiveTextEditor(async () => {
51+
await updatePreiewPanel(previewPanel);
9352
});
9453
},
9554
);

packages/vs-code-extension/src/renderOpenEmailFile.ts

Lines changed: 24 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
import * as vscode from "vscode";
22

3+
import * as crypto from "crypto";
34
import { basename, join } from "path";
4-
import { rm, unlink, writeFile } from "fs/promises";
5+
import { unlink, writeFile } from "fs/promises";
56
import * as esbuild from "esbuild";
67

78
import { render } from "@react-email/render";
8-
import { existsSync } from "fs";
99

1010
const extensionPreviewFolder = ".vscpreview" as const;
1111

@@ -18,15 +18,19 @@ export type BuiltEmail =
1818
}
1919
| { valid: false };
2020

21+
let isBuilding = false;
22+
2123
export async function renderOpenEmailFile(
2224
activeEditor: vscode.TextEditor | undefined,
2325
): Promise<BuiltEmail | undefined> {
24-
if (typeof activeEditor !== "undefined") {
26+
if (typeof activeEditor !== "undefined" && !isBuilding) {
27+
isBuilding = true;
2528
if (
2629
typeof activeEditor.document.fileName === "undefined" ||
2730
activeEditor.document.fileName.length === 0 ||
2831
activeEditor.document.getText().length === 0
2932
) {
33+
isBuilding = false;
3034
return { valid: false };
3135
}
3236

@@ -36,11 +40,15 @@ export async function renderOpenEmailFile(
3640
const emailsDirectory = join(currentlyOpenTabFilePath, "..");
3741
const previewDirectory = join(emailsDirectory, extensionPreviewFolder);
3842

43+
// this hash is needed so the temporary files don't get mixed up
44+
// when changing the email very fast
45+
const renderingHash = crypto.randomBytes(20).toString('hex');
46+
3947
// this is necessary so that we can still build things in a stable way
4048
// and have a up-to date version of the email preview on the extension
4149
const currentlyOpenTabFilesPathWithCurrentContents = join(
4250
emailsDirectory,
43-
`${currentlyOpenTabFilename}.vscpreview.tsx`,
51+
`${currentlyOpenTabFilename}-${renderingHash}.vscpreview.tsx`,
4452
);
4553
const currentContents = activeEditor.document.getText();
4654
await writeFile(
@@ -50,7 +58,7 @@ export async function renderOpenEmailFile(
5058

5159
const builtFileWithCurrentContents = join(
5260
previewDirectory,
53-
`${currentlyOpenTabFilename}.js`,
61+
`${currentlyOpenTabFilename}-${renderingHash}.js`,
5462
);
5563

5664
try {
@@ -62,15 +70,15 @@ export async function renderOpenEmailFile(
6270
outfile: builtFileWithCurrentContents,
6371
});
6472

65-
if (existsSync(currentlyOpenTabFilesPathWithCurrentContents)) {
66-
await unlink(currentlyOpenTabFilesPathWithCurrentContents); // unlink the temporary file after building it
67-
}
73+
await unlink(currentlyOpenTabFilesPathWithCurrentContents);
6874

6975
delete require.cache[builtFileWithCurrentContents];
7076
// we need to use require since it has a way to programatically invalidate its cache
7177
const email = require(builtFileWithCurrentContents);
7278

7379
if (typeof email.default === "undefined") {
80+
isBuilding = false;
81+
7482
// this means there is no "export default ..." in the file
7583
return { valid: false };
7684
}
@@ -82,7 +90,10 @@ export async function renderOpenEmailFile(
8290
});
8391

8492
const emailAsHTML = render(comp, { pretty: false });
85-
await rm(previewDirectory, { recursive: true });
93+
94+
await unlink(builtFileWithCurrentContents);
95+
96+
isBuilding = false;
8697

8798
return {
8899
filename: currentlyOpenTabFilename,
@@ -91,6 +102,8 @@ export async function renderOpenEmailFile(
91102
valid: true,
92103
};
93104
} catch (exception) {
105+
isBuilding = false;
106+
94107
console.warn(
95108
"Exception happenned on rendering or building of an email, but maybe its because it just was invalid anyways",
96109
exception,
@@ -100,5 +113,7 @@ export async function renderOpenEmailFile(
100113
}
101114
}
102115

116+
isBuilding = false;
117+
103118
return undefined;
104119
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import * as vscode from "vscode";
2+
3+
import { basename } from "path";
4+
5+
import { renderOpenEmailFile } from "./renderOpenEmailFile";
6+
import { convertAllEmailAssetSourcesIntoWebviewURIs } from "./convertAllEmailAssetSourcesIntoWebviewURIs";
7+
8+
import { emailWithErrorHTML, noEmailOpenHTML } from "./extension";
9+
10+
export async function updatePreiewPanel(
11+
previewPanel: vscode.WebviewPanel | undefined,
12+
) {
13+
if (previewPanel) {
14+
if (vscode.window.activeTextEditor) {
15+
try {
16+
let builtEmail = await renderOpenEmailFile(
17+
vscode.window.activeTextEditor,
18+
);
19+
if (
20+
builtEmail &&
21+
builtEmail.valid &&
22+
builtEmail.html &&
23+
builtEmail.html.trim().length > 0
24+
) {
25+
previewPanel.title = `react-email preview - ${builtEmail.filename}`;
26+
previewPanel.webview.html =
27+
convertAllEmailAssetSourcesIntoWebviewURIs(
28+
builtEmail.html,
29+
vscode.Uri.joinPath(
30+
vscode.window.activeTextEditor.document.uri,
31+
"..",
32+
), // the emails folder
33+
previewPanel,
34+
);
35+
}
36+
// keeps the current content if the email is invalid and did not error
37+
// this invalidness can happen if the focused content is a image,
38+
// does not a export a default and for some other similar situations
39+
} catch (exception) {
40+
previewPanel.title = `react-email preview - error on the email ${basename(
41+
vscode.window.activeTextEditor.document.fileName,
42+
)}`;
43+
let errorMessage: string;
44+
if (exception instanceof Error) {
45+
errorMessage = exception.stack ?? exception.message;
46+
} else {
47+
errorMessage = exception as string;
48+
}
49+
previewPanel.webview.html = emailWithErrorHTML.replace(
50+
"{ERROR MESSAGE}",
51+
errorMessage,
52+
);
53+
}
54+
} else if (previewPanel.webview.html.trim().length === 0) {
55+
previewPanel.title = `react-email preview - try opening an email!`;
56+
previewPanel.webview.html = noEmailOpenHTML;
57+
}
58+
}
59+
}

0 commit comments

Comments
 (0)