From 1d1e064e96fb877b0c21610c4760d0a86e0d33ae Mon Sep 17 00:00:00 2001 From: Michael James Date: Mon, 9 Sep 2024 11:11:25 -0400 Subject: [PATCH] feat: expose native build context (#404) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Allow access to rspack and webpack LoaderContext in the UnpluginBuildContext that is available in the load and transform hooks * refactor * fix: no native context for vite and roll{up,down} --------- Co-authored-by: 三咲智子 Kevin Deng --- src/esbuild/index.ts | 9 ++++----- src/esbuild/utils.ts | 10 +++++++--- src/farm/context.ts | 3 +++ src/farm/index.ts | 6 +++--- src/rspack/context.ts | 30 +++++++++++------------------- src/rspack/index.ts | 8 ++++---- src/rspack/loaders/load.ts | 4 ++-- src/rspack/loaders/transform.ts | 4 ++-- src/types.ts | 26 ++++++++++++++------------ src/utils.ts | 9 +++++++++ src/webpack/context.ts | 7 +++++-- src/webpack/index.ts | 6 +++--- src/webpack/loaders/load.ts | 11 ++++++----- src/webpack/loaders/transform.ts | 15 ++++----------- 14 files changed, 77 insertions(+), 71 deletions(-) diff --git a/src/esbuild/index.ts b/src/esbuild/index.ts index 6d86f035..4def8b0d 100644 --- a/src/esbuild/index.ts +++ b/src/esbuild/index.ts @@ -141,16 +141,15 @@ export function getEsbuildPlugin>( function buildSetup(meta: UnpluginContextMeta & { framework: 'esbuild' }) { return (plugin: UnpluginOptions): EsbuildPlugin['setup'] => { - return (build) => { - meta.build = build as EsbuildPluginBuild - const { onStart, onEnd, onResolve, onLoad, onTransform, initialOptions } - = build as EsbuildPluginBuild + return (_build) => { + const build = meta.build = _build as EsbuildPluginBuild + const context = createBuildContext(build) + const { onStart, onEnd, onResolve, onLoad, onTransform, initialOptions } = build const onResolveFilter = plugin.esbuild?.onResolveFilter ?? /.*/ const onLoadFilter = plugin.esbuild?.onLoadFilter ?? /.*/ const loader = plugin.esbuild?.loader ?? guessLoader - const context = createBuildContext(initialOptions) if (plugin.esbuild?.config) plugin.esbuild.config.call(context, initialOptions) diff --git a/src/esbuild/utils.ts b/src/esbuild/utils.ts index b0d934b4..cedbb5b0 100644 --- a/src/esbuild/utils.ts +++ b/src/esbuild/utils.ts @@ -4,9 +4,10 @@ import { Buffer } from 'buffer' import remapping from '@ampproject/remapping' import { Parser } from 'acorn' import type { DecodedSourceMap, EncodedSourceMap } from '@ampproject/remapping' -import type { BuildOptions, Loader, Location, Message, PartialMessage } from 'esbuild' +import type { Loader, Location, Message, PartialMessage } from 'esbuild' import type { SourceMap } from 'rollup' import type { UnpluginBuildContext, UnpluginContext, UnpluginMessage } from '../types' +import type { EsbuildPluginBuild } from '.' export * from '../utils' @@ -111,9 +112,9 @@ export function combineSourcemaps( return map as EncodedSourceMap } -export function createBuildContext(initialOptions: BuildOptions): UnpluginBuildContext { +export function createBuildContext(build: EsbuildPluginBuild): UnpluginBuildContext { const watchFiles: string[] = [] - + const { initialOptions } = build return { parse(code: string, opts: any = {}) { return Parser.parse(code, { @@ -140,6 +141,9 @@ export function createBuildContext(initialOptions: BuildOptions): UnpluginBuildC getWatchFiles() { return watchFiles }, + getNativeBuildContext() { + return { framework: 'esbuild', build } + }, } } diff --git a/src/farm/context.ts b/src/farm/context.ts index b1cbfe72..b59571ab 100644 --- a/src/farm/context.ts +++ b/src/farm/context.ts @@ -35,6 +35,9 @@ export function createFarmContext( getWatchFiles() { return context.getWatchFiles() }, + getNativeBuildContext() { + return { framework: 'farm', context } + }, } } diff --git a/src/farm/index.ts b/src/farm/index.ts index 03546b5a..347348b2 100644 --- a/src/farm/index.ts +++ b/src/farm/index.ts @@ -1,13 +1,13 @@ import path from 'path' - import type { + CompilationContext, + JsPlugin, PluginLoadHookParam, PluginLoadHookResult, PluginResolveHookParam, PluginTransformHookParam, PluginTransformHookResult, -} from '@farmfe/core/binding' -import type { CompilationContext, JsPlugin } from '@farmfe/core' +} from '@farmfe/core' import { toArray } from '../utils' import type { JsPluginExtended, diff --git a/src/rspack/context.ts b/src/rspack/context.ts index e63247e4..e267b3b8 100644 --- a/src/rspack/context.ts +++ b/src/rspack/context.ts @@ -1,27 +1,25 @@ import { resolve } from 'path' import { Buffer } from 'buffer' -import type { Compilation, LoaderContext } from '@rspack/core' +import type { Compilation, Compiler, LoaderContext } from '@rspack/core' import { Parser } from 'acorn' import type { UnpluginBuildContext, UnpluginContext, UnpluginMessage } from '../types' -interface ContextOptions { - addWatchFile: (file: string) => void - getWatchFiles: () => string[] -} - -export function contextOptionsFromCompilation(compilation: Compilation): ContextOptions { +export function createBuildContext(compiler: Compiler, compilation: Compilation, loaderContext?: LoaderContext): UnpluginBuildContext { return { + getNativeBuildContext() { + return { + framework: 'rspack', + compiler, + compilation, + loaderContext, + } + }, addWatchFile(file) { - compilation.fileDependencies.add(file) + compilation.fileDependencies.add(resolve(process.cwd(), file)) }, getWatchFiles() { return Array.from(compilation.fileDependencies) }, - } -} - -export function createBuildContext(options: ContextOptions, compilation: Compilation): UnpluginBuildContext { - return { parse(code: string, opts: any = {}) { return Parser.parse(code, { sourceType: 'module', @@ -30,9 +28,6 @@ export function createBuildContext(options: ContextOptions, compilation: Compila ...opts, }) }, - addWatchFile(id) { - options.addWatchFile(resolve(process.cwd(), id)) - }, emitFile(emittedFile) { const outFileName = emittedFile.fileName || emittedFile.name if (emittedFile.source && outFileName) { @@ -47,9 +42,6 @@ export function createBuildContext(options: ContextOptions, compilation: Compila ) } }, - getWatchFiles() { - return options.getWatchFiles() - }, } } diff --git a/src/rspack/index.ts b/src/rspack/index.ts index 4381a9ca..b4043f9e 100644 --- a/src/rspack/index.ts +++ b/src/rspack/index.ts @@ -9,7 +9,7 @@ import type { UnpluginFactory, UnpluginInstance, } from '../types' -import { contextOptionsFromCompilation, createBuildContext, normalizeMessage } from './context' +import { createBuildContext, normalizeMessage } from './context' import { decodeVirtualModuleId, encodeVirtualModuleId } from './utils' const TRANSFORM_LOADER = resolve( @@ -74,7 +74,7 @@ export function getRspackPlugin>( const importer = requestContext.issuer !== '' ? requestContext.issuer : undefined const isEntry = requestContext.issuer === '' - const context = createBuildContext(contextOptionsFromCompilation(compilation), compilation) + const context = createBuildContext(compiler, compilation) let error: Error | undefined const pluginContext: UnpluginContext = { error(msg) { @@ -165,7 +165,7 @@ export function getRspackPlugin>( if (plugin.watchChange || plugin.buildStart) { compiler.hooks.make.tapPromise(plugin.name, async (compilation) => { - const context = createBuildContext(contextOptionsFromCompilation(compilation), compilation) + const context = createBuildContext(compiler, compilation) if (plugin.watchChange && (compiler.modifiedFiles || compiler.removedFiles)) { const promises: Promise[] = [] if (compiler.modifiedFiles) { @@ -188,7 +188,7 @@ export function getRspackPlugin>( if (plugin.buildEnd) { compiler.hooks.emit.tapPromise(plugin.name, async (compilation) => { - await plugin.buildEnd!.call(createBuildContext(contextOptionsFromCompilation(compilation), compilation)) + await plugin.buildEnd!.call(createBuildContext(compiler, compilation)) }) } diff --git a/src/rspack/loaders/load.ts b/src/rspack/loaders/load.ts index 2e9d8dc3..d4189919 100644 --- a/src/rspack/loaders/load.ts +++ b/src/rspack/loaders/load.ts @@ -1,5 +1,5 @@ import type { LoaderContext } from '@rspack/core' -import { contextOptionsFromCompilation, createBuildContext, createContext } from '../context' +import { createBuildContext, createContext } from '../context' import { normalizeAbsolutePath } from '../../utils' import { decodeVirtualModuleId, isVirtualModuleId } from '../utils' @@ -19,7 +19,7 @@ export default async function load(this: LoaderContext, source: string, map: any const res = await plugin.load.call( Object.assign( {}, - this._compilation && createBuildContext(contextOptionsFromCompilation(this._compilation), this._compilation), + this._compilation && createBuildContext(this._compiler, this._compilation, this), context, ), normalizeAbsolutePath(id), diff --git a/src/rspack/loaders/transform.ts b/src/rspack/loaders/transform.ts index 891b7931..a142a2ae 100644 --- a/src/rspack/loaders/transform.ts +++ b/src/rspack/loaders/transform.ts @@ -1,5 +1,5 @@ import type { LoaderContext } from '@rspack/core' -import { contextOptionsFromCompilation, createBuildContext, createContext } from '../context' +import { createBuildContext, createContext } from '../context' export default async function transform( this: LoaderContext, @@ -27,7 +27,7 @@ export default async function transform( const res = await plugin.transform.call( Object.assign( {}, - this._compilation && createBuildContext(contextOptionsFromCompilation(this._compilation), this._compilation), + this._compilation && createBuildContext(this._compiler, this._compilation, this), context, ), source, diff --git a/src/types.ts b/src/types.ts index 4277ebb5..461a28a8 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,11 +1,11 @@ import type { AstNode, EmittedAsset, PluginContextMeta as RollupContextMeta, Plugin as RollupPlugin, SourceMapInput } from 'rollup' -import type { Compiler as WebpackCompiler, WebpackPluginInstance } from 'webpack' +import type { Compilation as WebpackCompilation, Compiler as WebpackCompiler, LoaderContext as WebpackLoaderContext, WebpackPluginInstance } from 'webpack' import type { Plugin as VitePlugin } from 'vite' import type { Plugin as RolldownPlugin } from 'rolldown' import type { BuildOptions, Plugin as EsbuildPlugin, Loader } from 'esbuild' -import type { Compiler as RspackCompiler, RspackPluginInstance } from '@rspack/core' +import type { Compilation as RspackCompilation, Compiler as RspackCompiler, LoaderContext as RspackLoaderContext, RspackPluginInstance } from '@rspack/core' import type VirtualModulesPlugin from 'webpack-virtual-modules' -import type { JsPlugin as FarmPlugin } from '@farmfe/core' +import type { CompilationContext as FarmCompilationContext, JsPlugin as FarmPlugin } from '@farmfe/core' import type { EsbuildPluginBuild } from './esbuild' export { @@ -40,11 +40,18 @@ export type TransformResult = string | { code: string, map?: SourceMapInput | So export interface ExternalIdResult { id: string, external?: boolean } +export type NativeBuildContext = + { framework: 'webpack', compiler: WebpackCompiler, compilation?: WebpackCompilation, loaderContext?: WebpackLoaderContext<{ unpluginName: string }> } | + { framework: 'esbuild', build: EsbuildPluginBuild } | + { framework: 'rspack', compiler: RspackCompiler, compilation: RspackCompilation, loaderContext?: RspackLoaderContext } | + { framework: 'farm', context: FarmCompilationContext } + export interface UnpluginBuildContext { addWatchFile: (id: string) => void emitFile: (emittedFile: EmittedAsset) => void getWatchFiles: () => string[] parse: (input: string, options?: any) => AstNode + getNativeBuildContext?: () => NativeBuildContext } export interface UnpluginOptions { @@ -117,24 +124,19 @@ export interface UnpluginInstance } export type UnpluginContextMeta = Partial & ({ - framework: 'rollup' | 'vite' | 'rolldown' + framework: 'rollup' | 'vite' | 'rolldown' | 'farm' } | { framework: 'webpack' - webpack: { - compiler: WebpackCompiler - } + webpack: { compiler: WebpackCompiler } } | { framework: 'esbuild' + /** @deprecated {getNativeBuildContext} */ build?: EsbuildPluginBuild /** Set the host plugin name of esbuild when returning multiple plugins */ esbuildHostName?: string } | { framework: 'rspack' - rspack: { - compiler: RspackCompiler - } -} | { - framework: 'farm' + rspack: { compiler: RspackCompiler } }) export interface UnpluginMessage { diff --git a/src/utils.ts b/src/utils.ts index f18c2aee..84fef5cc 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -63,3 +63,12 @@ export function transformUse( } return [] } + +export function resolveQuery(query: string | { unpluginName: string }) { + if (typeof query === 'string') { + return new URLSearchParams(query).get('unpluginName')! + } + else { + return query.unpluginName + } +} diff --git a/src/webpack/context.ts b/src/webpack/context.ts index ccc86641..da2d8a20 100644 --- a/src/webpack/context.ts +++ b/src/webpack/context.ts @@ -2,7 +2,7 @@ import { resolve } from 'path' import { Buffer } from 'buffer' import process from 'process' import { createRequire } from 'module' -import type { Compilation, LoaderContext } from 'webpack' +import type { Compilation, Compiler, LoaderContext } from 'webpack' import { Parser } from 'acorn' import type { UnpluginBuildContext, UnpluginContext, UnpluginMessage } from '../types' @@ -22,7 +22,7 @@ export function contextOptionsFromCompilation(compilation: Compilation): Context } } -export function createBuildContext(options: ContextOptions, compilation?: Compilation): UnpluginBuildContext { +export function createBuildContext(options: ContextOptions, compiler: Compiler, compilation?: Compilation, loaderContext?: LoaderContext<{ unpluginName: string }>): UnpluginBuildContext { const require = createRequire(import.meta.url) const sources = require('webpack-sources') as typeof import('webpack-sources') @@ -62,6 +62,9 @@ export function createBuildContext(options: ContextOptions, compilation?: Compil getWatchFiles() { return options.getWatchFiles() }, + getNativeBuildContext() { + return { framework: 'webpack', compiler, compilation, loaderContext } + }, } } diff --git a/src/webpack/index.ts b/src/webpack/index.ts index 23f41da3..6dea6a02 100644 --- a/src/webpack/index.ts +++ b/src/webpack/index.ts @@ -99,7 +99,7 @@ export function getWebpackPlugin>( getWatchFiles() { return Array.from(fileDependencies) }, - }) + }, compiler) let error: Error | undefined const pluginContext: UnpluginContext = { error(msg) { @@ -189,7 +189,7 @@ export function getWebpackPlugin>( if (plugin.watchChange || plugin.buildStart) { compiler.hooks.make.tapPromise(plugin.name, async (compilation) => { - const context = createBuildContext(contextOptionsFromCompilation(compilation), compilation) + const context = createBuildContext(contextOptionsFromCompilation(compilation), compiler, compilation) if (plugin.watchChange && (compiler.modifiedFiles || compiler.removedFiles)) { const promises: Promise[] = [] if (compiler.modifiedFiles) { @@ -212,7 +212,7 @@ export function getWebpackPlugin>( if (plugin.buildEnd) { compiler.hooks.emit.tapPromise(plugin.name, async (compilation) => { - await plugin.buildEnd!.call(createBuildContext(contextOptionsFromCompilation(compilation), compilation)) + await plugin.buildEnd!.call(createBuildContext(contextOptionsFromCompilation(compilation), compiler, compilation)) }) } diff --git a/src/webpack/loaders/load.ts b/src/webpack/loaders/load.ts index e7e3cb38..16a1010c 100644 --- a/src/webpack/loaders/load.ts +++ b/src/webpack/loaders/load.ts @@ -1,10 +1,11 @@ import type { LoaderContext } from 'webpack' import { createBuildContext, createContext } from '../context' -import { normalizeAbsolutePath } from '../../utils' +import { normalizeAbsolutePath, resolveQuery } from '../../utils' -export default async function load(this: LoaderContext, source: string, map: any) { +export default async function load(this: LoaderContext<{ unpluginName: string }>, source: string, map: any) { const callback = this.async() - const { unpluginName } = this.query + + const unpluginName = resolveQuery(this.query) const plugin = this._compiler?.$unpluginContext[unpluginName] let id = this.resource @@ -16,14 +17,14 @@ export default async function load(this: LoaderContext, source: string, map const context = createContext(this) const res = await plugin.load.call( - { ...createBuildContext({ + Object.assign({}, createBuildContext({ addWatchFile: (file) => { this.addDependency(file) }, getWatchFiles: () => { return this.getDependencies() }, - }, this._compilation), ...context }, + }, this._compiler!, this._compilation, this), context), normalizeAbsolutePath(id), ) diff --git a/src/webpack/loaders/transform.ts b/src/webpack/loaders/transform.ts index f8556c3f..f4fe960e 100644 --- a/src/webpack/loaders/transform.ts +++ b/src/webpack/loaders/transform.ts @@ -1,18 +1,11 @@ import type { LoaderContext } from 'webpack' import { createBuildContext, createContext } from '../context' +import { resolveQuery } from '../../utils' export default async function transform(this: LoaderContext<{ unpluginName: string }>, source: string, map: any) { const callback = this.async() - let unpluginName: string - if (typeof this.query === 'string') { - const query = new URLSearchParams(this.query) - unpluginName = query.get('unpluginName')! - } - else { - unpluginName = this.query.unpluginName - } - + const unpluginName = resolveQuery(this.query) const plugin = this._compiler?.$unpluginContext[unpluginName] if (!plugin?.transform) @@ -20,14 +13,14 @@ export default async function transform(this: LoaderContext<{ unpluginName: stri const context = createContext(this) const res = await plugin.transform.call( - { ...createBuildContext({ + Object.assign({}, createBuildContext({ addWatchFile: (file) => { this.addDependency(file) }, getWatchFiles: () => { return this.getDependencies() }, - }, this._compilation), ...context }, + }, this._compiler!, this._compilation, this), context), source, this.resource, )