From 884b9d012179085b6293b0e5c5bdf63c386941a8 Mon Sep 17 00:00:00 2001 From: Tim Fish Date: Thu, 18 Jun 2026 14:50:48 +0100 Subject: [PATCH] feat: Support sourcemap pass through with Roll{up,down}/Vite --- package.json | 2 +- src/core.ts | 4 +- src/rollup.ts | 19 +++++++--- test/vite.test.ts | 96 +++++++++++++++++++++++++++++++++++++++++++---- yarn.lock | 8 ++-- 5 files changed, 110 insertions(+), 19 deletions(-) diff --git a/package.json b/package.json index 53240f3..f87dba3 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,7 @@ "access": "public" }, "dependencies": { - "@apm-js-collab/code-transformer": "^0.14.0", + "@apm-js-collab/code-transformer": "^0.15.0", "es-module-lexer": "^2.1.0", "module-details-from-path": "^1.0.4", "magic-string": "^0.30.21" diff --git a/src/core.ts b/src/core.ts index b47cf95..68c60e2 100644 --- a/src/core.ts +++ b/src/core.ts @@ -172,7 +172,7 @@ export function createCodeTransformer(options: CodeTransformerPluginOptions) { const transform = ( code: string, id: string, - inputSourceMap?: string | null, + inputSourceMap?: string | object | null, ): TransformResult | null => { const moduleDetails = moduleDetailsFromPath(id); if (!moduleDetails) return null; @@ -208,7 +208,7 @@ export function createCodeTransformer(options: CodeTransformerPluginOptions) { transformedModules.add(transformer.moduleName); return { code: result.code, map: result.map }; } catch (error) { - console.warn(`Code transformation failed for ${id}: ${error}`); + console.warn(`Code transformation failed for '${id}'`, error); failedModules.add(moduleDetails.name); return null; } diff --git a/src/rollup.ts b/src/rollup.ts index afe05b0..65f2f9f 100644 --- a/src/rollup.ts +++ b/src/rollup.ts @@ -1,4 +1,4 @@ -import type { Plugin } from "rollup"; +import type { OutputOptions, Plugin, TransformPluginContext } from "rollup"; import { COMMENT_USE_STRICT_REGEX, createCodeTransformer, @@ -13,12 +13,18 @@ export default function codeTransformerRollup( ): Plugin { const { transform: transformCode, getCodeToInject } = createCodeTransformer(options); + + let sourcemapsEnabled = false; + + const outputOptions = (inputOptions: OutputOptions) => { + sourcemapsEnabled = !!inputOptions.sourcemap; + }; const renderChunk = ( code: string, chunk: { fileName: string; facadeModuleId?: string | null }, _?: unknown, - meta?: { magicString?: MagicString }, + meta?: { magicString?: MagicString, chunks: unknown }, ): { code: string; map?: SourceMap; @@ -68,8 +74,9 @@ export default function codeTransformerRollup( }; }; - const transform = (code: string, id: string) => { - const result = transformCode(code, id); + function transform(this: TransformPluginContext, code: string, id: string) { + const inputSourceMap = sourcemapsEnabled ? this.getCombinedSourcemap() : undefined; + const result = transformCode(code, id, inputSourceMap); if (!result) return null; return { code: result.code, map: result.map ?? null }; }; @@ -79,14 +86,16 @@ export default function codeTransformerRollup( if (!options.injectDiagnostics) { return { name, + outputOptions, transform, }; } return { name, + outputOptions, transform, - renderChunk: renderChunk as unknown as Plugin["renderChunk"], + renderChunk, }; } diff --git a/test/vite.test.ts b/test/vite.test.ts index d7f9284..4404cc0 100644 --- a/test/vite.test.ts +++ b/test/vite.test.ts @@ -5,6 +5,7 @@ import { join } from 'path'; import { writeFileSync, mkdirSync } from 'fs'; import { createTestFixture, commonTestCases, type TestFixture } from './test-utils.js'; import { builtinModules } from 'module'; +import MagicString from 'magic-string'; describe('Vite integration tests', () => { let fixture: TestFixture; @@ -144,12 +145,12 @@ export function testFunction() { channelName: 'test:channel', module: { name: 'test-module', - versionRange: '>=1.0.0' as any, + versionRange: '>=1.0.0', filePath: 'outside.js' }, functionQuery: { functionName: 'testFunction', - kind: 'Async' as const + kind: 'Async' } }] }); @@ -184,7 +185,7 @@ export function testFunction() { ...testCase.instrumentation, module: { ...testCase.instrumentation.module, - versionRange: '>=2.0.0' as any // Version doesn't match (module is 1.2.3) + versionRange: '>=2.0.0' } }] }); @@ -209,6 +210,87 @@ export function testFunction() { } }); + it('should generate a sourcemap when sourcemaps are enabled', async () => { + const testCase = commonTestCases.esmodule; + const testFile = join(fixture.moduleDir, testCase.filename); + writeFileSync(testFile, testCase.code); + + const plugin = codeTransformerPlugin({ + instrumentations: [testCase.instrumentation] + }); + + const result = await build({ + root: fixture.testDir, + build: { + write: false, + sourcemap: true, + rollupOptions: { + input: testFile, + external: Array.from(builtinModules) + } + }, + plugins: [plugin] + }); + + expect(result).toBeDefined(); + if ('output' in result) { + const chunk = result.output.find(o => o.type === 'chunk'); + expect(chunk).toBeDefined(); + if (chunk?.type === 'chunk') { + expect(chunk.code).toContain('test:esmodule'); + expect(chunk.map).toBeDefined(); + expect(chunk.map?.sources.some(s => s?.includes('esmodule.js'))).toBe(true); + } + } + }); + + it('should chain sourcemaps from a prior transform plugin', async () => { + const testCase = commonTestCases.esmodule; + const testFile = join(fixture.moduleDir, testCase.filename); + writeFileSync(testFile, testCase.code); + + // Runs before code-transformer (same enforce tier, listed first) and shifts line numbers + const priorTransformPlugin = { + name: 'prior-transform', + enforce: 'pre', + transform(code: string, id: string) { + if (!id.includes('esmodule.js')) return null; + const ms = new MagicString(code); + ms.prepend('// inserted by prior transform\n'); + return { code: ms.toString(), map: ms.generateMap({ hires: true, source: id }) }; + } + }; + + const plugin = codeTransformerPlugin({ + instrumentations: [testCase.instrumentation] + }); + + const result = await build({ + root: fixture.testDir, + build: { + write: false, + sourcemap: true, + rollupOptions: { + input: testFile, + external: Array.from(builtinModules) + } + }, + plugins: [priorTransformPlugin, plugin] + }); + + expect(result).toBeDefined(); + if ('output' in result) { + const chunk = result.output.find(o => o.type === 'chunk'); + expect(chunk).toBeDefined(); + if (chunk?.type === 'chunk') { + expect(chunk.code).toContain('test:esmodule'); + // Sourcemap must chain back to the original file, not the intermediate output + expect(chunk.map).toBeDefined(); + expect(chunk.map?.sources.some(s => s?.includes('esmodule.js'))).toBe(true); + } + } + }); + it('should handle multiple instrumentations correctly', async () => { const libFile = join(fixture.moduleDir, 'lib', 'http.js'); mkdirSync(join(fixture.moduleDir, 'lib'), { recursive: true }); @@ -232,26 +314,26 @@ export class HttpClient { channelName: 'http:fetch', module: { name: 'test-module', - versionRange: '>=1.0.0' as any, + versionRange: '>=1.0.0', filePath: 'lib/http.js' }, functionQuery: { className: 'HttpClient', methodName: 'fetch', - kind: 'Async' as const + kind: 'Async' } }, { channelName: 'http:post', module: { name: 'test-module', - versionRange: '>=1.0.0' as any, + versionRange: '>=1.0.0', filePath: 'lib/http.js' }, functionQuery: { className: 'HttpClient', methodName: 'post', - kind: 'Async' as const + kind: 'Async' } } ] diff --git a/yarn.lock b/yarn.lock index a078aca..316c37b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,10 +2,10 @@ # yarn lockfile v1 -"@apm-js-collab/code-transformer@^0.14.0": - version "0.14.0" - resolved "https://registry.yarnpkg.com/@apm-js-collab/code-transformer/-/code-transformer-0.14.0.tgz#b4a43bfbc67047038eac1e276d1388285f714bbe" - integrity sha512-6G+FETQ/VyRBsIkDDZ5sc9fb7O6d6W9rm8bZPHumISZw/6nwxvnS+VTyxd3sKM04aZwHG5hJIHb7VPdPCLieSQ== +"@apm-js-collab/code-transformer@^0.15.0": + version "0.15.0" + resolved "https://registry.yarnpkg.com/@apm-js-collab/code-transformer/-/code-transformer-0.15.0.tgz#a3a1b6c7b92db16f8277636b4a72a1626e2fa52a" + integrity sha512-XmXYVs8CzJ1Aj79noVbn2weUO/XWtRyURpGqx7aU7DOXlUQhR0WKOQNF0okh7PCeY37vxf7kU3v57OAkEPm3ww== dependencies: "@types/estree" "^1.0.8" astring "^1.9.0"