feat: expose input source map for webpack-like bundlers (#562)

This commit is contained in:
sethfowler-datadog 2025-11-20 21:26:06 -05:00 committed by GitHub
parent c1cf2213a8
commit a8975f7010
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 117 additions and 18 deletions

View File

@ -4,7 +4,7 @@ import { Buffer } from 'node:buffer'
import { resolve } from 'node:path'
import { parse } from '../utils/parse'
export function createBuildContext(compiler: Compiler, compilation: Compilation, loaderContext?: LoaderContext): UnpluginBuildContext {
export function createBuildContext(compiler: Compiler, compilation: Compilation, loaderContext?: LoaderContext, inputSourceMap?: any): UnpluginBuildContext {
return {
getNativeBuildContext() {
return {
@ -12,6 +12,7 @@ export function createBuildContext(compiler: Compiler, compilation: Compilation,
compiler,
compilation,
loaderContext,
inputSourceMap,
}
},
addWatchFile(file) {

View File

@ -23,7 +23,7 @@ export default async function transform(
const res = await handler.call(
Object.assign(
{},
this._compilation && createBuildContext(this._compiler, this._compilation, this),
this._compilation && createBuildContext(this._compiler, this._compilation, this, map),
context,
),
source,

View File

@ -50,9 +50,9 @@ export type TransformResult = string | { code: string, map?: SourceMapInput | So
export interface ExternalIdResult { id: string, external?: boolean | undefined }
export type NativeBuildContext
= { framework: 'webpack', compiler: WebpackCompiler, compilation?: WebpackCompilation | undefined, loaderContext?: WebpackLoaderContext<{ unpluginName: string }> | undefined }
= { framework: 'webpack', compiler: WebpackCompiler, compilation?: WebpackCompilation | undefined, loaderContext?: WebpackLoaderContext<{ unpluginName: string }> | undefined, inputSourceMap?: any }
| { framework: 'esbuild', build: PluginBuild }
| { framework: 'rspack', compiler: RspackCompiler, compilation: RspackCompilation, loaderContext?: RspackLoaderContext | undefined }
| { framework: 'rspack', compiler: RspackCompiler, compilation: RspackCompilation, loaderContext?: RspackLoaderContext | undefined, inputSourceMap?: any }
| { framework: 'farm', context: FarmCompilationContext }
| { framework: 'bun', build: BunPluginBuilder }

View File

@ -30,7 +30,7 @@ export function getSource(fileSource: string | Uint8Array): sources.RawSource {
)
}
export function createBuildContext(options: ContextOptions, compiler: Compiler, compilation?: Compilation, loaderContext?: LoaderContext<{ unpluginName: string }>): UnpluginBuildContext {
export function createBuildContext(options: ContextOptions, compiler: Compiler, compilation?: Compilation, loaderContext?: LoaderContext<{ unpluginName: string }>, inputSourceMap?: any): UnpluginBuildContext {
return {
parse,
addWatchFile(id) {
@ -51,7 +51,7 @@ export function createBuildContext(options: ContextOptions, compiler: Compiler,
return options.getWatchFiles()
},
getNativeBuildContext() {
return { framework: 'webpack', compiler, compilation, loaderContext }
return { framework: 'webpack', compiler, compilation, loaderContext, inputSourceMap }
},
}
}

View File

@ -24,7 +24,7 @@ export default async function transform(this: LoaderContext<any>, source: string
getWatchFiles: () => {
return this.getDependencies()
},
}, this._compiler!, this._compilation, this), context),
}, this._compiler!, this._compilation, this, map), context),
source,
this.resource,
)

View File

@ -7,14 +7,16 @@ describe('createBuildContext', () => {
const compiler = { name: 'testCompiler' }
const compilation = { name: 'testCompilation' }
const loaderContext = { name: 'testLoaderContext' }
const inputSourceMap = { name: 'inputSourceMap' }
const buildContext = createBuildContext(compiler as any, compilation as any, loaderContext as any)
const buildContext = createBuildContext(compiler as any, compilation as any, loaderContext as any, inputSourceMap as any)
expect(buildContext.getNativeBuildContext!()).toEqual({
framework: 'rspack',
compiler,
compilation,
loaderContext,
inputSourceMap,
})
})

View File

@ -1,4 +1,5 @@
import { describe, expect, it, vi } from 'vitest'
import type { NativeBuildContext, UnpluginBuildContext } from '../../../../src/types'
import { assert, describe, expect, it, vi } from 'vitest'
import transform from '../../../../src/rspack/loaders/transform'
describe('transform', () => {
@ -35,10 +36,6 @@ describe('transform', () => {
const source = 'test source'
const map = 'test map'
vi.mock('../../../../src/utils/filter', () => ({
normalizeObjectHook: vi.fn(() => ({ handler: vi.fn().mockRejectedValue(new Error('Handler error')), filter: vi.fn().mockReturnValue(true) })),
}))
await transform.call(mockLoaderContext, source, map)
expect(mockCallback).toHaveBeenCalledWith(expect.any(Error))
@ -63,13 +60,54 @@ describe('transform', () => {
const source = 'test source'
const map = 'test map'
vi.mock('../../../../src/utils/filter', () => ({
normalizeObjectHook: vi.fn(() => ({ handler: vi.fn().mockRejectedValue(new Error('Handler error')), filter: vi.fn().mockReturnValue(true) })),
}))
await transform.call(mockLoaderContext, source, map)
expect(mockCallback).toHaveBeenCalledWith(expect.any(Error))
expect(mockCallback.mock.calls[0][0].message).toBe('Handler error')
})
it('should include input source map on native build context', async () => {
const source = 'source code'
const map = 'source map'
const transformedCode = 'transformed code'
const transformedMap = 'transformed map'
let handlerSource: string | undefined
let handlerId: string | undefined
let handlerNativeBuildContext: NativeBuildContext | undefined
const handlerMock = vi.fn().mockImplementation(function (this: UnpluginBuildContext, source: string, id: string) {
handlerSource = source
handlerId = id
handlerNativeBuildContext = this.getNativeBuildContext?.()
return { code: transformedCode, map: transformedMap }
})
const mockCallback = vi.fn()
const mockLoaderContext = {
async: () => mockCallback,
query: {
plugin: {
transform: {
handler: handlerMock,
filter: vi.fn().mockReturnValue(true),
},
},
},
resource: 'test resource',
addDependency: vi.fn(),
getDependencies: vi.fn().mockReturnValue(['/path/to/dependency']),
_compiler: {},
_compilation: {},
} as any
await transform.call(mockLoaderContext as any, source, map)
expect(handlerMock).toHaveBeenCalled()
expect(handlerSource).toBe(source)
expect(handlerId).toBe(mockLoaderContext.resource)
assert(handlerNativeBuildContext?.framework === 'rspack')
expect(handlerNativeBuildContext?.inputSourceMap).toBe(map)
expect(mockCallback).toHaveBeenCalledWith(null, transformedCode, transformedMap)
})
})

View File

@ -46,6 +46,27 @@ describe('webpack - utils', () => {
expect.anything(),
)
})
it('should add expected values to native build context', () => {
const options = {
addWatchFile: vi.fn(),
getWatchFiles: vi.fn(() => ['file1.js']),
}
const compiler = { name: 'testCompiler' } as Compiler
const compilation = { name: 'testCompilation' } as Compilation
const loaderContext = { name: 'testLoaderContext' } as unknown as LoaderContext<{ unpluginName: string }>
const inputSourceMap = { name: 'inputSourceMap' }
const buildContext = createBuildContext(options, compiler, compilation, loaderContext, inputSourceMap)
expect(buildContext.getNativeBuildContext!()).toEqual({
framework: 'webpack',
compiler,
compilation,
loaderContext,
inputSourceMap,
})
})
})
describe('createContext', () => {

View File

@ -1,4 +1,5 @@
import { describe, expect, it, vi } from 'vitest'
import type { NativeBuildContext, UnpluginBuildContext } from '../../../../src/types'
import { assert, describe, expect, it, vi } from 'vitest'
import transform from '../../../../src/webpack/loaders/transform'
describe('transform loader', () => {
@ -104,4 +105,40 @@ describe('transform loader', () => {
expect(handlerMock).toHaveBeenCalled()
expect(mockCallback).toHaveBeenCalledWith(error)
})
it('should include input source map on native build context', async () => {
const source = 'source code'
const map = 'source map'
const transformedCode = 'transformed code'
const transformedMap = 'transformed map'
let handlerSource: string | undefined
let handlerId: string | undefined
let handlerNativeBuildContext: NativeBuildContext | undefined
const handlerMock = vi.fn().mockImplementation(function (this: UnpluginBuildContext, source: string, id: string) {
handlerSource = source
handlerId = id
handlerNativeBuildContext = this.getNativeBuildContext?.()
return { code: transformedCode, map: transformedMap }
})
mockLoaderContext.query = {
plugin: {
transform: {
handler: handlerMock,
filter: vi.fn().mockReturnValue(true),
},
},
}
await transform.call(mockLoaderContext as any, source, map)
expect(handlerMock).toHaveBeenCalled()
expect(handlerSource).toBe(source)
expect(handlerId).toBe(mockLoaderContext.resource)
assert(handlerNativeBuildContext?.framework === 'webpack')
expect(handlerNativeBuildContext?.inputSourceMap).toBe(map)
expect(mockCallback).toHaveBeenCalledWith(null, transformedCode, transformedMap)
})
})