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..d72e22d 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) { + // 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')}` + } + const {messages} = await extractMessages(code, filename, { ...options, macro: mapOptions(ctx.linguiConfig, options.macro),