From 3e185c42440d83095f9aada19e6329ff4aece472 Mon Sep 17 00:00:00 2001 From: Andrii Bodnar <29282228+andrii-bodnar@users.noreply.github.com> Date: Sun, 18 Oct 2026 13:59:15 +0300 Subject: [PATCH 1/2] fix(native-tools): honor ctx.sourceMaps in createSwcExtractor Wrapper extractors (e.g. @lingui/extractor-vue) pass the map of the compiled code in ctx.sourceMaps, as the Babel extractor expects. The native extractor ignored it and reported positions in the compiled code. Append the map as an inline source map comment before extraction. The native side reads the last such comment, so ctx.sourceMaps takes precedence over an inline map already in the code, as in the Babel extractor. Co-Authored-By: Claude Opus 5.5 --- packages/native-tools/README.md | 2 ++ packages/native-tools/__test__/index.spec.ts | 38 ++++++++++++++++++++ packages/native-tools/src-js/index.ts | 6 ++++ 3 files changed, 46 insertions(+) diff --git a/packages/native-tools/README.md b/packages/native-tools/README.md index c6691fe..fc1da36 100644 --- a/packages/native-tools/README.md +++ b/packages/native-tools/README.md @@ -46,6 +46,8 @@ export default defineConfig({ > **Note** > For the best performance, disable multithreading on the Lingui CLI side with `lingui extract --workers 1`. The native extractor processes files in parallel itself. +Other extractors can wrap it, for example to extract from compiled `.vue` files. When a wrapper passes a source map in `ctx.sourceMaps`, message origins point to the original file and line. + `createSwcExtractor()` accepts optional extractor options: ```ts diff --git a/packages/native-tools/__test__/index.spec.ts b/packages/native-tools/__test__/index.spec.ts index 2c6c9d7..0a2efd0 100644 --- a/packages/native-tools/__test__/index.spec.ts +++ b/packages/native-tools/__test__/index.spec.ts @@ -191,4 +191,42 @@ describe('createSwcExtractor', () => { expect(await extract(extractor, linguiConfig)).toEqual(['From custom package']) }) + + describe('source maps', () => { + const linguiConfig = makeConfig({locales: ['en']}, {skipValidation: true}) + + // Generated line 2 maps to line 10, column 2 (0-based: 9, 2) of App.vue + const sourceMaps = { + version: 3, + sources: ['/project/src/App.vue'], + names: [], + mappings: 'AAAA;AASE', + } + + const compiled = `import {t} from '@lingui/core/macro';\nt\`Hello\`;\n` + + async function extractOrigins(code: string, sourceMaps?: unknown) { + const messages: ExtractedMessage[] = [] + await createSwcExtractor().extract('App.vue.js', code, (msg) => messages.push(msg), { + linguiConfig, + sourceMaps, + }) + return messages.map((msg) => msg.origin) + } + + test('maps origins through ctx.sourceMaps', async () => { + expect(await extractOrigins(compiled)).toEqual([['App.vue.js', 2, 2]]) + expect(await extractOrigins(compiled, sourceMaps)).toEqual([['/project/src/App.vue', 10, 3]]) + }) + + test('ctx.sourceMaps takes precedence over an inline source map', async () => { + const inlineMap = {...sourceMaps, sources: ['/project/src/Other.vue']} + const code = + compiled + + `//# sourceMappingURL=data:application/json;base64,${Buffer.from(JSON.stringify(inlineMap)).toString('base64')}\n` + + expect(await extractOrigins(code)).toEqual([['/project/src/Other.vue', 10, 3]]) + expect(await extractOrigins(code, sourceMaps)).toEqual([['/project/src/App.vue', 10, 3]]) + }) + }) }) diff --git a/packages/native-tools/src-js/index.ts b/packages/native-tools/src-js/index.ts index 61e9a83..aa0f542 100644 --- a/packages/native-tools/src-js/index.ts +++ b/packages/native-tools/src-js/index.ts @@ -183,6 +183,12 @@ export function createSwcExtractor(options: ExtractorOptions = {}): ExtractorTyp }, async extract(filename, code, onMessageExtracted, ctx) { + if (ctx.sourceMaps) { + // The native extractor reads only the last inline source map comment, + // so the appended map takes precedence over one already in the code (as in the Babel extractor) + code += `\n//# sourceMappingURL=data:application/json;base64,${Buffer.from(JSON.stringify(ctx.sourceMaps)).toString('base64')}` + } + const {messages} = await extractMessages(code, filename, { ...options, macro: mapOptions(ctx.linguiConfig, options.macro), From 6124fb2c7c7262a482dbaf8396fe8adef406fa0a Mon Sep 17 00:00:00 2001 From: Andrii Bodnar <29282228+andrii-bodnar@users.noreply.github.com> Date: Mon, 19 Oct 2026 10:54:17 +0300 Subject: [PATCH 2/2] chore(native-tools): explain why createSwcExtractor appends ctx.sourceMaps Co-Authored-By: Claude Opus 5.5 --- packages/native-tools/src-js/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/native-tools/src-js/index.ts b/packages/native-tools/src-js/index.ts index aa0f542..d72e22d 100644 --- a/packages/native-tools/src-js/index.ts +++ b/packages/native-tools/src-js/index.ts @@ -184,8 +184,8 @@ export function createSwcExtractor(options: ExtractorOptions = {}): ExtractorTyp async extract(filename, code, onMessageExtracted, ctx) { if (ctx.sourceMaps) { - // The native extractor reads only the last inline source map comment, - // so the appended map takes precedence over one already in the code (as in the Babel extractor) + // Map origins through `ctx.sourceMaps`, as the Babel extractor does. + // The native extractor reads source maps only from an inline comment, so pass the map that way. code += `\n//# sourceMappingURL=data:application/json;base64,${Buffer.from(JSON.stringify(ctx.sourceMaps)).toString('base64')}` }