From a3bb13fdf59794dc11e7615a28b6d8772a78b1d5 Mon Sep 17 00:00:00 2001 From: Devon Govett Date: Thu, 1 Feb 2024 07:36:27 -0500 Subject: [PATCH] fix: use webpack loader context to implement addWatchFile (#359) --- src/webpack/context.ts | 30 +++++++++++++++++++----------- src/webpack/index.ts | 19 +++++++++++++++---- src/webpack/loaders/load.ts | 9 ++++++++- src/webpack/loaders/transform.ts | 9 ++++++++- 4 files changed, 50 insertions(+), 17 deletions(-) diff --git a/src/webpack/context.ts b/src/webpack/context.ts index 18902e69..1e1db452 100644 --- a/src/webpack/context.ts +++ b/src/webpack/context.ts @@ -6,7 +6,23 @@ import type { Compilation } from 'webpack' import { Parser } from 'acorn' import type { UnpluginBuildContext } from '../types' -export function createContext(compilation?: Compilation): UnpluginBuildContext { +interface ContextOptions { + addWatchFile(file: string): void + getWatchFiles(): string[] +} + +export function contextOptionsFromCompilation(compilation: Compilation): ContextOptions { + return { + addWatchFile(file) { + (compilation.fileDependencies ?? compilation.compilationDependencies).add(file) + }, + getWatchFiles() { + return Array.from(compilation.fileDependencies ?? compilation.compilationDependencies) + }, + } +} + +export function createContext(options: ContextOptions, compilation?: Compilation): UnpluginBuildContext { return { parse(code: string, opts: any = {}) { return Parser.parse(code, { @@ -17,11 +33,7 @@ export function createContext(compilation?: Compilation): UnpluginBuildContext { }) }, addWatchFile(id) { - if (!compilation) - throw new Error('unplugin/webpack: addWatchFile outside supported hooks (buildStart, buildEnd, load, transform, watchChange)'); - (compilation.fileDependencies ?? compilation.compilationDependencies).add( - resolve(process.cwd(), id), - ) + options.addWatchFile(resolve(process.cwd(), id)) }, emitFile(emittedFile) { const outFileName = emittedFile.fileName || emittedFile.name @@ -45,11 +57,7 @@ export function createContext(compilation?: Compilation): UnpluginBuildContext { } }, getWatchFiles() { - if (!compilation) - throw new Error('unplugin/webpack: getWatchFiles outside supported hooks (buildStart, buildEnd, load, transform, watchChange)') - return Array.from( - compilation.fileDependencies ?? compilation.compilationDependencies, - ) + return options.getWatchFiles() }, } } diff --git a/src/webpack/index.ts b/src/webpack/index.ts index 3ff32d12..981604b8 100644 --- a/src/webpack/index.ts +++ b/src/webpack/index.ts @@ -5,7 +5,7 @@ import VirtualModulesPlugin from 'webpack-virtual-modules' import type { ResolvePluginInstance, Resolver } from 'webpack' import type { ResolvedUnpluginOptions, UnpluginContext, UnpluginContextMeta, UnpluginFactory, UnpluginInstance, WebpackCompiler } from '../types' import { normalizeAbsolutePath, shouldLoad, toArray, transformUse } from '../utils' -import { createContext } from './context' +import { contextOptionsFromCompilation, createContext } from './context' const TRANSFORM_LOADER = resolve( __dirname, @@ -89,7 +89,18 @@ export function getWebpackPlugin>( const isEntry = requestContext.issuer === '' // call hook - const context = createContext() + // resolveContext.fileDependencies is typed as a WriteOnlySet, so make our own copy here + // so we can return it from getWatchFiles. + const fileDependencies = new Set() + const context = createContext({ + addWatchFile(file) { + fileDependencies.add(file) + resolveContext.fileDependencies?.add(file) + }, + getWatchFiles() { + return Array.from(fileDependencies) + }, + }) let error: Error | undefined const pluginContext: UnpluginContext = { error(msg: string | Error) { @@ -178,7 +189,7 @@ export function getWebpackPlugin>( if (plugin.watchChange || plugin.buildStart) { compiler.hooks.make.tapPromise(plugin.name, async (compilation) => { - const context = createContext(compilation) + const context = createContext(contextOptionsFromCompilation(compilation), compilation) if (plugin.watchChange && (compiler.modifiedFiles || compiler.removedFiles)) { const promises: Promise[] = [] if (compiler.modifiedFiles) { @@ -201,7 +212,7 @@ export function getWebpackPlugin>( if (plugin.buildEnd) { compiler.hooks.emit.tapPromise(plugin.name, async (compilation) => { - await plugin.buildEnd!.call(createContext(compilation)) + await plugin.buildEnd!.call(createContext(contextOptionsFromCompilation(compilation), compilation)) }) } diff --git a/src/webpack/loaders/load.ts b/src/webpack/loaders/load.ts index 13c8ec0c..b9584bda 100644 --- a/src/webpack/loaders/load.ts +++ b/src/webpack/loaders/load.ts @@ -21,7 +21,14 @@ export default async function load(this: LoaderContext, source: string, map id = decodeURIComponent(id.slice(plugin.__virtualModulePrefix.length)) const res = await plugin.load.call( - { ...this._compilation && createContext(this._compilation) as any, ...context }, + { ...createContext({ + addWatchFile: (file) => { + this.addDependency(file) + }, + getWatchFiles: () => { + return this.getDependencies() + }, + }, this._compilation), ...context }, normalizeAbsolutePath(id), ) diff --git a/src/webpack/loaders/transform.ts b/src/webpack/loaders/transform.ts index 120a63d5..2f73dda4 100644 --- a/src/webpack/loaders/transform.ts +++ b/src/webpack/loaders/transform.ts @@ -24,7 +24,14 @@ export default async function transform(this: LoaderContext<{ unpluginName: stri warn: error => this.emitWarning(typeof error === 'string' ? new Error(error) : error), } const res = await plugin.transform.call( - { ...this._compilation && createContext(this._compilation) as any, ...context }, + { ...createContext({ + addWatchFile: (file) => { + this.addDependency(file) + }, + getWatchFiles: () => { + return this.getDependencies() + }, + }, this._compilation), ...context }, source, this.resource, )