diff --git a/packages/compiler-sfc/__tests__/compileScript.spec.ts b/packages/compiler-sfc/__tests__/compileScript.spec.ts
index 11b5661c16c..73c6d316a40 100644
--- a/packages/compiler-sfc/__tests__/compileScript.spec.ts
+++ b/packages/compiler-sfc/__tests__/compileScript.spec.ts
@@ -1,5 +1,11 @@
import { BindingTypes } from '@vue/compiler-core'
-import { assertCode, compileSFCScript as compile, mockId } from './utils'
+import {
+ assertCode,
+ compileSFCScript as compile,
+ getPositionInCode,
+ mockId,
+} from './utils'
+import { type RawSourceMap, SourceMapConsumer } from 'source-map-js'
describe('SFC compile
+
+
+
+ `
+ const { content, map } = compile(source, { inlineTemplate: true })
+ expect(map).not.toBeUndefined()
+ const consumer = new SourceMapConsumer(map as RawSourceMap)
+ expect(
+ consumer.originalPositionFor(getPositionInCode(content, 'count')),
+ ).toMatchObject(getPositionInCode(source, `count`))
+ expect(
+ consumer.originalPositionFor(getPositionInCode(content, 'Error')),
+ ).toMatchObject(getPositionInCode(source, `Error`))
+ })
})
describe('with TypeScript', () => {
diff --git a/packages/compiler-sfc/__tests__/compileTemplate.spec.ts b/packages/compiler-sfc/__tests__/compileTemplate.spec.ts
index 2ea1eb9d378..d4ddc763812 100644
--- a/packages/compiler-sfc/__tests__/compileTemplate.spec.ts
+++ b/packages/compiler-sfc/__tests__/compileTemplate.spec.ts
@@ -6,6 +6,7 @@ import {
} from '../src/compileTemplate'
import { type SFCTemplateBlock, parse } from '../src/parse'
import { compileScript } from '../src'
+import { getPositionInCode } from './utils'
function compile(opts: Omit) {
return compileTemplate({
@@ -482,36 +483,3 @@ test('non-identifier expression in legacy filter syntax', () => {
babelParse(compilationResult.code, { sourceType: 'module' })
}).not.toThrow()
})
-
-interface Pos {
- line: number
- column: number
- name?: string
-}
-
-function getPositionInCode(
- code: string,
- token: string,
- expectName: string | boolean = false,
-): Pos {
- const generatedOffset = code.indexOf(token)
- let line = 1
- let lastNewLinePos = -1
- for (let i = 0; i < generatedOffset; i++) {
- if (code.charCodeAt(i) === 10 /* newline char code */) {
- line++
- lastNewLinePos = i
- }
- }
- const res: Pos = {
- line,
- column:
- lastNewLinePos === -1
- ? generatedOffset
- : generatedOffset - lastNewLinePos - 1,
- }
- if (expectName) {
- res.name = typeof expectName === 'string' ? expectName : token
- }
- return res
-}
diff --git a/packages/compiler-sfc/__tests__/utils.ts b/packages/compiler-sfc/__tests__/utils.ts
index 5a58a6b58ae..b5cfc9606d5 100644
--- a/packages/compiler-sfc/__tests__/utils.ts
+++ b/packages/compiler-sfc/__tests__/utils.ts
@@ -40,3 +40,36 @@ export function assertCode(code: string): void {
}
expect(code).toMatchSnapshot()
}
+
+interface Pos {
+ line: number
+ column: number
+ name?: string
+}
+
+export function getPositionInCode(
+ code: string,
+ token: string,
+ expectName: string | boolean = false,
+): Pos {
+ const generatedOffset = code.indexOf(token)
+ let line = 1
+ let lastNewLinePos = -1
+ for (let i = 0; i < generatedOffset; i++) {
+ if (code.charCodeAt(i) === 10 /* newline char code */) {
+ line++
+ lastNewLinePos = i
+ }
+ }
+ const res: Pos = {
+ line,
+ column:
+ lastNewLinePos === -1
+ ? generatedOffset
+ : generatedOffset - lastNewLinePos - 1,
+ }
+ if (expectName) {
+ res.name = typeof expectName === 'string' ? expectName : token
+ }
+ return res
+}
diff --git a/packages/compiler-sfc/src/compileScript.ts b/packages/compiler-sfc/src/compileScript.ts
index fee05beed96..67af92df5e7 100644
--- a/packages/compiler-sfc/src/compileScript.ts
+++ b/packages/compiler-sfc/src/compileScript.ts
@@ -23,7 +23,11 @@ import type {
Statement,
} from '@babel/types'
import { walk } from 'estree-walker'
-import type { RawSourceMap } from 'source-map-js'
+import {
+ type RawSourceMap,
+ SourceMapConsumer,
+ SourceMapGenerator,
+} from 'source-map-js'
import {
normalScriptDefaultVar,
processNormalScript,
@@ -817,6 +821,7 @@ export function compileScript(
args += `, { ${destructureElements.join(', ')} }`
}
+ let templateMap
// 9. generate return statement
let returned
if (
@@ -866,7 +871,7 @@ export function compileScript(
}
// inline render function mode - we are going to compile the template and
// inline it right here
- const { code, ast, preamble, tips, errors } = compileTemplate({
+ const { code, ast, preamble, tips, errors, map } = compileTemplate({
filename,
ast: sfc.template.ast,
source: sfc.template.content,
@@ -884,6 +889,7 @@ export function compileScript(
bindingMetadata: ctx.bindingMetadata,
},
})
+ templateMap = map
if (tips.length) {
tips.forEach(warnOnce)
}
@@ -1022,19 +1028,28 @@ export function compileScript(
)
}
+ const content = ctx.s.toString()
+ let map =
+ options.sourceMap !== false
+ ? (ctx.s.generateMap({
+ source: filename,
+ hires: true,
+ includeContent: true,
+ }) as unknown as RawSourceMap)
+ : undefined
+ // merge source maps of the script setup and template in inline mode
+ if (templateMap && map) {
+ const offset = content.indexOf(returned)
+ const templateLineOffset =
+ content.slice(0, offset).split(/\r?\n/).length - 1
+ map = mergeSourceMaps(map, templateMap, templateLineOffset)
+ }
return {
...scriptSetup,
bindings: ctx.bindingMetadata,
imports: ctx.userImports,
- content: ctx.s.toString(),
- map:
- options.sourceMap !== false
- ? (ctx.s.generateMap({
- source: filename,
- hires: true,
- includeContent: true,
- }) as unknown as RawSourceMap)
- : undefined,
+ content,
+ map,
scriptAst: scriptAst?.body,
scriptSetupAst: scriptSetupAst?.body,
deps: ctx.deps ? [...ctx.deps] : undefined,
@@ -1291,3 +1306,42 @@ function isStaticNode(node: Node): boolean {
}
return false
}
+
+export function mergeSourceMaps(
+ scriptMap: RawSourceMap,
+ templateMap: RawSourceMap,
+ templateLineOffset: number,
+): RawSourceMap {
+ const generator = new SourceMapGenerator()
+ const addMapping = (map: RawSourceMap, lineOffset = 0) => {
+ const consumer = new SourceMapConsumer(map)
+ ;(consumer as any).sources.forEach((sourceFile: string) => {
+ ;(generator as any)._sources.add(sourceFile)
+ const sourceContent = consumer.sourceContentFor(sourceFile)
+ if (sourceContent != null) {
+ generator.setSourceContent(sourceFile, sourceContent)
+ }
+ })
+ consumer.eachMapping(m => {
+ if (m.originalLine == null) return
+ generator.addMapping({
+ generated: {
+ line: m.generatedLine + lineOffset,
+ column: m.generatedColumn,
+ },
+ original: {
+ line: m.originalLine,
+ column: m.originalColumn,
+ },
+ source: m.source,
+ name: m.name,
+ })
+ })
+ }
+
+ addMapping(scriptMap)
+ addMapping(templateMap, templateLineOffset)
+ ;(generator as any)._sourceRoot = scriptMap.sourceRoot
+ ;(generator as any)._file = scriptMap.file
+ return (generator as any).toJSON()
+}