|
1 | 1 | 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'; |
21 | 4 | import { createExtensionRunner } from './runners'; |
22 | | -import { Mutex } from 'async-mutex'; |
23 | | -import pc from 'picocolors'; |
24 | | -import { relative } from 'node:path'; |
25 | 5 | import { deinitWxtModules, initWxtModules, registerWxt, wxt } from './wxt'; |
26 | 6 | import { unnormalizePath } from './utils/paths'; |
27 | | -import { |
28 | | - getContentScriptJs, |
29 | | - mapWxtOptionsToRegisteredContentScript, |
30 | | -} from './utils/content-scripts'; |
31 | 7 | import { createKeyboardShortcuts } from './keyboard-shortcuts'; |
32 | 8 | import { isBabelSyntaxError, logBabelSyntaxError } from './utils/syntax-errors'; |
| 9 | +import { |
| 10 | + createFileReloader, |
| 11 | + reloadContentScripts, |
| 12 | +} from './utils/create-file-reloader'; |
33 | 13 |
|
34 | 14 | /** |
35 | 15 | * 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> { |
198 | 178 | return server; |
199 | 179 | } |
200 | 180 |
|
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 | | - |
378 | 181 | /** |
379 | 182 | * Based on the current build output, return a list of files that are: |
380 | 183 | * 1. Not in node_modules |
|
0 commit comments