Skip to content

Commit 51b315f

Browse files
feat: tolerate syntax errors (#1437)
Co-authored-by: Aaron <[email protected]>
1 parent 60f5117 commit 51b315f

File tree

2 files changed

+62
-3
lines changed

2 files changed

+62
-3
lines changed

packages/wxt/src/core/create-server.ts

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { debounce } from 'perfect-debounce';
2+
import chokidar from 'chokidar';
23
import {
34
BuildStepOutput,
45
EntrypointGroup,
@@ -28,6 +29,7 @@ import {
2829
mapWxtOptionsToRegisteredContentScript,
2930
} from './utils/content-scripts';
3031
import { createKeyboardShortcuts } from './keyboard-shortcuts';
32+
import { isBabelSyntaxError, logBabelSyntaxError } from './utils/syntax-errors';
3133

3234
/**
3335
* Creates a dev server and pre-builds all the files that need to exist before loading the extension.
@@ -156,8 +158,25 @@ async function createServerInternal(): Promise<WxtDevServer> {
156158
const keyboardShortcuts = createKeyboardShortcuts(server);
157159

158160
const buildAndOpenBrowser = async () => {
159-
// Build after starting the dev server so it can be used to transform HTML files
160-
server.currentOutput = await internalBuild();
161+
try {
162+
// Build after starting the dev server so it can be used to transform HTML files
163+
server.currentOutput = await internalBuild();
164+
} catch (err) {
165+
if (!isBabelSyntaxError(err)) {
166+
throw err;
167+
}
168+
logBabelSyntaxError(err);
169+
wxt.logger.info('Waiting for syntax error to be fixed...');
170+
await new Promise<void>((resolve) => {
171+
const watcher = chokidar.watch(err.id, { ignoreInitial: true });
172+
watcher.on('all', () => {
173+
watcher.close();
174+
wxt.logger.info('Syntax error resolved, rebuilding...');
175+
resolve();
176+
});
177+
});
178+
return buildAndOpenBrowser();
179+
}
161180

162181
// Add file watchers for files not loaded by the dev server. See
163182
// https://github.com/wxt-dev/wxt/issues/428#issuecomment-1944731870
@@ -187,7 +206,7 @@ function createFileReloader(server: WxtDevServer) {
187206
const cb = async (event: string, path: string) => {
188207
changeQueue.push([event, path]);
189208

190-
await fileChangedMutex.runExclusive(async () => {
209+
const reloading = fileChangedMutex.runExclusive(async () => {
191210
if (server.currentOutput == null) return;
192211

193212
const fileChanges = changeQueue
@@ -256,6 +275,14 @@ function createFileReloader(server: WxtDevServer) {
256275
// Catch build errors instead of crashing. Don't log error either, builder should have already logged it
257276
}
258277
});
278+
279+
await reloading.catch((error) => {
280+
if (!isBabelSyntaxError(error)) {
281+
throw error;
282+
}
283+
// Log syntax errors without crashing the server.
284+
logBabelSyntaxError(error);
285+
});
259286
};
260287

261288
return debounce(cb, wxt.config.dev.server!.watchDebounce, {
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { relative } from 'node:path';
2+
import pc from 'picocolors';
3+
import { wxt } from '../wxt';
4+
5+
export interface BabelSyntaxError extends SyntaxError {
6+
code: 'BABEL_PARSER_SYNTAX_ERROR';
7+
frame?: string;
8+
id: string;
9+
loc: { line: number; column: number };
10+
}
11+
12+
export function isBabelSyntaxError(error: unknown): error is BabelSyntaxError {
13+
return (
14+
error instanceof SyntaxError &&
15+
(error as any).code === 'BABEL_PARSER_SYNTAX_ERROR'
16+
);
17+
}
18+
19+
export function logBabelSyntaxError(error: BabelSyntaxError) {
20+
let filename = relative(wxt.config.root, error.id);
21+
if (filename.startsWith('..')) {
22+
filename = error.id;
23+
}
24+
let message = error.message.replace(
25+
/\(\d+:\d+\)$/,
26+
`(${filename}:${error.loc.line}:${error.loc.column + 1})`,
27+
);
28+
if (error.frame) {
29+
message += '\n\n' + pc.red(error.frame);
30+
}
31+
wxt.logger.error(message);
32+
}

0 commit comments

Comments
 (0)