Skip to content

Commit 9e4d40d

Browse files
ZerGo0PatrykKuniczak
authored andcommitted
fix: keep createFileReloader out of the public API
Move createFileReloader into an internal core utils file and update create-server/test imports.
1 parent 4d0062d commit 9e4d40d

File tree

3 files changed

+205
-204
lines changed

3 files changed

+205
-204
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
2-
import { createFileReloader } from '../create-server';
2+
import { createFileReloader } from '../utils/create-file-reloader';
33
import { findEntrypoints, rebuild } from '../utils/building';
44
import {
55
fakeBackgroundEntrypoint,

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

Lines changed: 6 additions & 203 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,15 @@
11
import chokidar from 'chokidar';
2-
import {
3-
BuildStepOutput,
4-
EntrypointGroup,
5-
InlineConfig,
6-
ServerInfo,
7-
WxtDevServer,
8-
} from '../types';
9-
import { getEntrypointBundlePath, isHtmlEntrypoint } from './utils/entrypoints';
10-
import {
11-
getContentScriptCssFiles,
12-
getContentScriptsCssMap,
13-
} from './utils/manifest';
14-
import {
15-
internalBuild,
16-
detectDevChanges,
17-
getRelevantDevChangedFiles,
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 pc from 'picocolors';
24-
import { relative } from 'node:path';
255
import { deinitWxtModules, initWxtModules, registerWxt, wxt } from './wxt';
266
import { unnormalizePath } from './utils/paths';
27-
import {
28-
getContentScriptJs,
29-
mapWxtOptionsToRegisteredContentScript,
30-
} from './utils/content-scripts';
317
import { createKeyboardShortcuts } from './keyboard-shortcuts';
328
import { isBabelSyntaxError, logBabelSyntaxError } from './utils/syntax-errors';
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 loading the extension.
@@ -198,183 +178,6 @@ async function createServerInternal(): Promise<WxtDevServer> {
198178
return server;
199179
}
200180

201-
/**
202-
* Returns a function responsible for reloading different parts of the extension when a file
203-
* changes.
204-
*/
205-
export function createFileReloader(server: WxtDevServer) {
206-
const fileChangedMutex = new Mutex();
207-
const changeQueue: Array<[string, string]> = [];
208-
let processLoop: Promise<void> | undefined;
209-
210-
const processQueue = async () => {
211-
const reloading = fileChangedMutex.runExclusive(async () => {
212-
const fileChanges = changeQueue
213-
.splice(0, changeQueue.length)
214-
.map(([_, file]) => file);
215-
if (fileChanges.length === 0) return;
216-
if (server.currentOutput == null) return;
217-
218-
const relevantFileChanges = getRelevantDevChangedFiles(
219-
fileChanges,
220-
server.currentOutput,
221-
);
222-
if (relevantFileChanges.length === 0) return;
223-
224-
await wxt.reloadConfig();
225-
226-
const changes = detectDevChanges(
227-
relevantFileChanges,
228-
server.currentOutput,
229-
);
230-
if (changes.type === 'no-change') return;
231-
232-
if (changes.type === 'full-restart') {
233-
wxt.logger.info('Config changed, restarting server...');
234-
server.restart();
235-
return;
236-
}
237-
238-
if (changes.type === 'browser-restart') {
239-
wxt.logger.info('Runner config changed, restarting browser...');
240-
server.restartBrowser();
241-
return;
242-
}
243-
244-
// Log the entrypoints that were effected
245-
wxt.logger.info(
246-
`Changed: ${relevantFileChanges
247-
.map((file) => pc.dim(relative(wxt.config.root, file)))
248-
.join(', ')}`,
249-
);
250-
251-
// Rebuild entrypoints on change
252-
const allEntrypoints = await findEntrypoints();
253-
try {
254-
const { output: newOutput } = await rebuild(
255-
allEntrypoints,
256-
// TODO: this excludes new entrypoints, so they're not built until the dev command is restarted
257-
changes.rebuildGroups,
258-
changes.cachedOutput,
259-
);
260-
server.currentOutput = newOutput;
261-
262-
// Perform reloads
263-
switch (changes.type) {
264-
case 'extension-reload':
265-
server.reloadExtension();
266-
wxt.logger.success(`Reloaded extension`);
267-
break;
268-
case 'html-reload':
269-
const { reloadedNames } = reloadHtmlPages(
270-
changes.rebuildGroups,
271-
server,
272-
);
273-
wxt.logger.success(`Reloaded: ${getFilenameList(reloadedNames)}`);
274-
break;
275-
case 'content-script-reload':
276-
reloadContentScripts(changes.changedSteps, server);
277-
278-
const rebuiltNames = changes.rebuildGroups
279-
.flat()
280-
.map((entry) => entry.name);
281-
wxt.logger.success(`Reloaded: ${getFilenameList(rebuiltNames)}`);
282-
break;
283-
}
284-
} catch {
285-
// Catch build errors instead of crashing. Don't log error either, builder should have already logged it
286-
}
287-
});
288-
289-
await reloading.catch((error) => {
290-
if (!isBabelSyntaxError(error)) {
291-
throw error;
292-
}
293-
// Log syntax errors without crashing the server.
294-
logBabelSyntaxError(error);
295-
});
296-
};
297-
298-
const waitForDebounceWindow = async () => {
299-
await new Promise((resolve) => {
300-
setTimeout(resolve, wxt.config.dev.server!.watchDebounce);
301-
});
302-
};
303-
304-
const queueWorker = async () => {
305-
while (true) {
306-
await processQueue();
307-
308-
await waitForDebounceWindow();
309-
if (changeQueue.length === 0) break;
310-
}
311-
};
312-
313-
return async (event: string, path: string) => {
314-
// Queue every event before debouncing so we never drop changes.
315-
changeQueue.push([event, path]);
316-
317-
processLoop ??= queueWorker().finally(() => {
318-
processLoop = undefined;
319-
});
320-
await processLoop;
321-
};
322-
}
323-
324-
/**
325-
* From the server, tell the client to reload content scripts from the provided build step outputs.
326-
*/
327-
function reloadContentScripts(steps: BuildStepOutput[], server: WxtDevServer) {
328-
if (wxt.config.manifestVersion === 3) {
329-
steps.forEach((step) => {
330-
if (server.currentOutput == null) return;
331-
332-
const entry = step.entrypoints;
333-
if (Array.isArray(entry) || entry.type !== 'content-script') return;
334-
335-
const js = getContentScriptJs(wxt.config, entry);
336-
const cssMap = getContentScriptsCssMap(server.currentOutput, [entry]);
337-
const css = getContentScriptCssFiles([entry], cssMap);
338-
339-
server.reloadContentScript({
340-
registration: entry.options.registration,
341-
contentScript: mapWxtOptionsToRegisteredContentScript(
342-
entry.options,
343-
js,
344-
css,
345-
),
346-
});
347-
});
348-
} else {
349-
server.reloadExtension();
350-
}
351-
}
352-
353-
function reloadHtmlPages(
354-
groups: EntrypointGroup[],
355-
server: WxtDevServer,
356-
): { reloadedNames: string[] } {
357-
// groups might contain other files like background/content scripts, and we only care about the HTMl pages
358-
const htmlEntries = groups.flat().filter(isHtmlEntrypoint);
359-
360-
htmlEntries.forEach((entry) => {
361-
const path = getEntrypointBundlePath(entry, wxt.config.outDir, '.html');
362-
server.reloadPage(path);
363-
});
364-
365-
return {
366-
reloadedNames: htmlEntries.map((entry) => entry.name),
367-
};
368-
}
369-
370-
function getFilenameList(names: string[]): string {
371-
return names
372-
.map((name) => {
373-
return pc.cyan(name);
374-
})
375-
.join(pc.dim(', '));
376-
}
377-
378181
/**
379182
* Based on the current build output, return a list of files that are:
380183
* 1. Not in node_modules

0 commit comments

Comments
 (0)