|
1 | | -import { debounce } from 'perfect-debounce'; |
2 | 1 | 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'; |
21 | 4 | import { createExtensionRunner } from './runners'; |
22 | | -import { Mutex } from 'async-mutex'; |
23 | | -import { relative } from 'node:path'; |
24 | 5 | import { deinitWxtModules, initWxtModules, registerWxt, wxt } from './wxt'; |
25 | 6 | import { unnormalizePath } from './utils/paths'; |
26 | | -import { |
27 | | - getContentScriptJs, |
28 | | - mapWxtOptionsToRegisteredContentScript, |
29 | | -} from './utils/content-scripts'; |
30 | 7 | import { createKeyboardShortcuts } from './keyboard-shortcuts'; |
31 | 8 | 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'; |
33 | 13 |
|
34 | 14 | /** |
35 | 15 | * Creates a dev server and pre-builds all the files that need to exist before |
@@ -199,155 +179,6 @@ async function createServerInternal(): Promise<WxtDevServer> { |
199 | 179 | return server; |
200 | 180 | } |
201 | 181 |
|
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 | | - |
351 | 182 | /** |
352 | 183 | * Based on the current build output, return a list of files that are: |
353 | 184 | * |
|
0 commit comments