Skip to content

Commit e45b1fb

Browse files
authored
chore: Move createFileReloader into it's own file (#2307)
1 parent 0b3c3ab commit e45b1fb

2 files changed

Lines changed: 172 additions & 175 deletions

File tree

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

Lines changed: 6 additions & 175 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,15 @@
1-
import { debounce } from 'perfect-debounce';
21
import chokidar from 'chokidar';
3-
import {
4-
BuildStepOutput,
5-
EntrypointGroup,
6-
InlineConfig,
7-
ServerInfo,
8-
WxtDevServer,
9-
} from '../types';
10-
import { getEntrypointBundlePath, isHtmlEntrypoint } from './utils/entrypoints';
11-
import {
12-
getContentScriptCssFiles,
13-
getContentScriptsCssMap,
14-
} from './utils/manifest';
15-
import {
16-
internalBuild,
17-
detectDevChanges,
18-
rebuild,
19-
findEntrypoints,
20-
} from './utils/building';
2+
import { InlineConfig, ServerInfo, WxtDevServer } from '../types';
3+
import { internalBuild } from './utils/building';
214
import { createExtensionRunner } from './runners';
22-
import { Mutex } from 'async-mutex';
23-
import { relative } from 'node:path';
245
import { deinitWxtModules, initWxtModules, registerWxt, wxt } from './wxt';
256
import { unnormalizePath } from './utils/paths';
26-
import {
27-
getContentScriptJs,
28-
mapWxtOptionsToRegisteredContentScript,
29-
} from './utils/content-scripts';
307
import { createKeyboardShortcuts } from './keyboard-shortcuts';
318
import { isBabelSyntaxError, logBabelSyntaxError } from './utils/syntax-errors';
32-
import { styleText } from 'node:util';
9+
import {
10+
createFileReloader,
11+
reloadContentScripts,
12+
} from './utils/create-file-reloader';
3313

3414
/**
3515
* Creates a dev server and pre-builds all the files that need to exist before
@@ -199,155 +179,6 @@ async function createServerInternal(): Promise<WxtDevServer> {
199179
return server;
200180
}
201181

202-
/**
203-
* Returns a function responsible for reloading different parts of the extension
204-
* when a file changes.
205-
*/
206-
function createFileReloader(server: WxtDevServer) {
207-
const fileChangedMutex = new Mutex();
208-
const changeQueue: Array<[string, string]> = [];
209-
210-
const cb = async (event: string, path: string) => {
211-
changeQueue.push([event, path]);
212-
213-
const reloading = fileChangedMutex.runExclusive(async () => {
214-
if (server.currentOutput == null) return;
215-
216-
const fileChanges = changeQueue
217-
.splice(0, changeQueue.length)
218-
.map(([_, file]) => file);
219-
if (fileChanges.length === 0) return;
220-
221-
await wxt.reloadConfig();
222-
223-
const changes = detectDevChanges(fileChanges, server.currentOutput);
224-
if (changes.type === 'no-change') return;
225-
226-
if (changes.type === 'full-restart') {
227-
wxt.logger.info('Config changed, restarting server...');
228-
server.restart();
229-
return;
230-
}
231-
232-
if (changes.type === 'browser-restart') {
233-
wxt.logger.info('Runner config changed, restarting browser...');
234-
server.restartBrowser();
235-
return;
236-
}
237-
238-
// Log the entrypoints that were effected
239-
wxt.logger.info(
240-
`Changed: ${Array.from(new Set(fileChanges))
241-
.map((file) => styleText('dim', relative(wxt.config.root, file)))
242-
.join(', ')}`,
243-
);
244-
245-
// Rebuild entrypoints on change
246-
const allEntrypoints = await findEntrypoints();
247-
try {
248-
const { output: newOutput } = await rebuild(
249-
allEntrypoints,
250-
// TODO: this excludes new entrypoints, so they're not built until the dev command is restarted
251-
changes.rebuildGroups,
252-
changes.cachedOutput,
253-
);
254-
server.currentOutput = newOutput;
255-
256-
// Perform reloads
257-
switch (changes.type) {
258-
case 'extension-reload':
259-
server.reloadExtension();
260-
wxt.logger.success(`Reloaded extension`);
261-
break;
262-
case 'html-reload':
263-
const { reloadedNames } = reloadHtmlPages(
264-
changes.rebuildGroups,
265-
server,
266-
);
267-
wxt.logger.success(`Reloaded: ${getFilenameList(reloadedNames)}`);
268-
break;
269-
case 'content-script-reload':
270-
reloadContentScripts(changes.changedSteps, server);
271-
272-
const rebuiltNames = changes.rebuildGroups
273-
.flat()
274-
.map((entry) => entry.name);
275-
wxt.logger.success(`Reloaded: ${getFilenameList(rebuiltNames)}`);
276-
break;
277-
}
278-
} catch {
279-
// Catch build errors instead of crashing. Don't log error either, builder should have already logged it
280-
}
281-
});
282-
283-
await reloading.catch((error) => {
284-
if (!isBabelSyntaxError(error)) {
285-
throw error;
286-
}
287-
// Log syntax errors without crashing the server.
288-
logBabelSyntaxError(error);
289-
});
290-
};
291-
292-
return debounce(cb, wxt.config.dev.server!.watchDebounce, {
293-
leading: true,
294-
trailing: false,
295-
});
296-
}
297-
298-
/**
299-
* From the server, tell the client to reload content scripts from the provided
300-
* build step outputs.
301-
*/
302-
function reloadContentScripts(steps: BuildStepOutput[], server: WxtDevServer) {
303-
if (wxt.config.manifestVersion === 3) {
304-
steps.forEach((step) => {
305-
if (server.currentOutput == null) return;
306-
307-
const entry = step.entrypoints;
308-
if (Array.isArray(entry) || entry.type !== 'content-script') return;
309-
310-
const js = getContentScriptJs(wxt.config, entry);
311-
const cssMap = getContentScriptsCssMap(server.currentOutput, [entry]);
312-
const css = getContentScriptCssFiles([entry], cssMap);
313-
314-
server.reloadContentScript({
315-
registration: entry.options.registration,
316-
contentScript: mapWxtOptionsToRegisteredContentScript(
317-
entry.options,
318-
js,
319-
css,
320-
),
321-
});
322-
});
323-
} else {
324-
server.reloadExtension();
325-
}
326-
}
327-
328-
function reloadHtmlPages(
329-
groups: EntrypointGroup[],
330-
server: WxtDevServer,
331-
): { reloadedNames: string[] } {
332-
// groups might contain other files like background/content scripts, and we only care about the HTMl pages
333-
const htmlEntries = groups.flat().filter(isHtmlEntrypoint);
334-
335-
htmlEntries.forEach((entry) => {
336-
const path = getEntrypointBundlePath(entry, wxt.config.outDir, '.html');
337-
server.reloadPage(path);
338-
});
339-
340-
return {
341-
reloadedNames: htmlEntries.map((entry) => entry.name),
342-
};
343-
}
344-
345-
function getFilenameList(names: string[]): string {
346-
return names
347-
.map((name) => styleText('cyan', name))
348-
.join(styleText('dim', ', '));
349-
}
350-
351182
/**
352183
* Based on the current build output, return a list of files that are:
353184
*
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
import { debounce } from 'perfect-debounce';
2+
import { Mutex } from 'async-mutex';
3+
import { relative } from 'node:path';
4+
import { BuildStepOutput, EntrypointGroup, WxtDevServer } from '../../types';
5+
import { wxt } from '../wxt';
6+
import { detectDevChanges, findEntrypoints, rebuild } from './building';
7+
import { getEntrypointBundlePath, isHtmlEntrypoint } from './entrypoints';
8+
import { getContentScriptCssFiles, getContentScriptsCssMap } from './manifest';
9+
import {
10+
getContentScriptJs,
11+
mapWxtOptionsToRegisteredContentScript,
12+
} from './content-scripts';
13+
import { isBabelSyntaxError, logBabelSyntaxError } from './syntax-errors';
14+
import { styleText } from 'node:util';
15+
16+
/**
17+
* Returns a function responsible for reloading different parts of the extension
18+
* when a file changes.
19+
*/
20+
export function createFileReloader(server: WxtDevServer) {
21+
const fileChangedMutex = new Mutex();
22+
const changeQueue: Array<[string, string]> = [];
23+
24+
const cb = async (event: string, path: string) => {
25+
changeQueue.push([event, path]);
26+
27+
const reloading = fileChangedMutex.runExclusive(async () => {
28+
if (server.currentOutput == null) return;
29+
30+
const fileChanges = changeQueue
31+
.splice(0, changeQueue.length)
32+
.map(([_, file]) => file);
33+
if (fileChanges.length === 0) return;
34+
35+
await wxt.reloadConfig();
36+
37+
const changes = detectDevChanges(fileChanges, server.currentOutput);
38+
if (changes.type === 'no-change') return;
39+
40+
if (changes.type === 'full-restart') {
41+
wxt.logger.info('Config changed, restarting server...');
42+
server.restart();
43+
return;
44+
}
45+
46+
if (changes.type === 'browser-restart') {
47+
wxt.logger.info('Runner config changed, restarting browser...');
48+
server.restartBrowser();
49+
return;
50+
}
51+
52+
// Log the entrypoints that were effected
53+
wxt.logger.info(
54+
`Changed: ${Array.from(new Set(fileChanges))
55+
.map((file) => styleText('dim', relative(wxt.config.root, file)))
56+
.join(', ')}`,
57+
);
58+
59+
// Rebuild entrypoints on change
60+
const allEntrypoints = await findEntrypoints();
61+
try {
62+
const { output: newOutput } = await rebuild(
63+
allEntrypoints,
64+
// TODO: this excludes new entrypoints, so they're not built until the dev command is restarted
65+
changes.rebuildGroups,
66+
changes.cachedOutput,
67+
);
68+
server.currentOutput = newOutput;
69+
70+
// Perform reloads
71+
switch (changes.type) {
72+
case 'extension-reload':
73+
server.reloadExtension();
74+
wxt.logger.success(`Reloaded extension`);
75+
break;
76+
case 'html-reload':
77+
const { reloadedNames } = reloadHtmlPages(
78+
changes.rebuildGroups,
79+
server,
80+
);
81+
wxt.logger.success(`Reloaded: ${getFilenameList(reloadedNames)}`);
82+
break;
83+
case 'content-script-reload':
84+
reloadContentScripts(changes.changedSteps, server);
85+
86+
const rebuiltNames = changes.rebuildGroups
87+
.flat()
88+
.map((entry) => entry.name);
89+
wxt.logger.success(`Reloaded: ${getFilenameList(rebuiltNames)}`);
90+
break;
91+
}
92+
} catch {
93+
// Catch build errors instead of crashing. Don't log error either, builder should have already logged it
94+
}
95+
});
96+
97+
await reloading.catch((error) => {
98+
if (!isBabelSyntaxError(error)) {
99+
throw error;
100+
}
101+
// Log syntax errors without crashing the server.
102+
logBabelSyntaxError(error);
103+
});
104+
};
105+
106+
return debounce(cb, wxt.config.dev.server!.watchDebounce, {
107+
leading: true,
108+
trailing: false,
109+
});
110+
}
111+
112+
/**
113+
* From the server, tell the client to reload content scripts from the provided
114+
* build step outputs.
115+
*/
116+
export function reloadContentScripts(
117+
steps: BuildStepOutput[],
118+
server: WxtDevServer,
119+
) {
120+
if (wxt.config.manifestVersion === 3) {
121+
steps.forEach((step) => {
122+
if (server.currentOutput == null) return;
123+
124+
const entry = step.entrypoints;
125+
if (Array.isArray(entry) || entry.type !== 'content-script') return;
126+
127+
const js = getContentScriptJs(wxt.config, entry);
128+
const cssMap = getContentScriptsCssMap(server.currentOutput, [entry]);
129+
const css = getContentScriptCssFiles([entry], cssMap);
130+
131+
server.reloadContentScript({
132+
registration: entry.options.registration,
133+
contentScript: mapWxtOptionsToRegisteredContentScript(
134+
entry.options,
135+
js,
136+
css,
137+
),
138+
});
139+
});
140+
} else {
141+
server.reloadExtension();
142+
}
143+
}
144+
145+
function reloadHtmlPages(
146+
groups: EntrypointGroup[],
147+
server: WxtDevServer,
148+
): { reloadedNames: string[] } {
149+
// groups might contain other files like background/content scripts, and we only care about the HTMl pages
150+
const htmlEntries = groups.flat().filter(isHtmlEntrypoint);
151+
152+
htmlEntries.forEach((entry) => {
153+
const path = getEntrypointBundlePath(entry, wxt.config.outDir, '.html');
154+
server.reloadPage(path);
155+
});
156+
157+
return {
158+
reloadedNames: htmlEntries.map((entry) => entry.name),
159+
};
160+
}
161+
162+
function getFilenameList(names: string[]): string {
163+
return names
164+
.map((name) => styleText('cyan', name))
165+
.join(styleText('dim', ', '));
166+
}

0 commit comments

Comments
 (0)