Skip to content

Commit 89edc6c

Browse files
authored
fix(compile-sfc): handle inline template source map in prod build (#12701)
close #12682 close vitejs/vite-plugin-vue#500
1 parent f44feed commit 89edc6c

File tree

4 files changed

+127
-45
lines changed

4 files changed

+127
-45
lines changed

packages/compiler-sfc/__tests__/compileScript.spec.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import { BindingTypes } from '@vue/compiler-core'
2-
import { assertCode, compileSFCScript as compile, mockId } from './utils'
2+
import {
3+
assertCode,
4+
compileSFCScript as compile,
5+
getPositionInCode,
6+
mockId,
7+
} from './utils'
8+
import { type RawSourceMap, SourceMapConsumer } from 'source-map-js'
39

410
describe('SFC compile <script setup>', () => {
511
test('should compile JS syntax', () => {
@@ -690,6 +696,27 @@ describe('SFC compile <script setup>', () => {
690696
expect(content).toMatch(`new (_unref(Foo)).Bar()`)
691697
assertCode(content)
692698
})
699+
700+
// #12682
701+
test('source map', () => {
702+
const source = `
703+
<script setup>
704+
const count = ref(0)
705+
</script>
706+
<template>
707+
<button @click="throw new Error(\`msg\`);"></button>
708+
</template>
709+
`
710+
const { content, map } = compile(source, { inlineTemplate: true })
711+
expect(map).not.toBeUndefined()
712+
const consumer = new SourceMapConsumer(map as RawSourceMap)
713+
expect(
714+
consumer.originalPositionFor(getPositionInCode(content, 'count')),
715+
).toMatchObject(getPositionInCode(source, `count`))
716+
expect(
717+
consumer.originalPositionFor(getPositionInCode(content, 'Error')),
718+
).toMatchObject(getPositionInCode(source, `Error`))
719+
})
693720
})
694721

695722
describe('with TypeScript', () => {

packages/compiler-sfc/__tests__/compileTemplate.spec.ts

Lines changed: 1 addition & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
} from '../src/compileTemplate'
77
import { type SFCTemplateBlock, parse } from '../src/parse'
88
import { compileScript } from '../src'
9+
import { getPositionInCode } from './utils'
910

1011
function compile(opts: Omit<SFCTemplateCompileOptions, 'id'>) {
1112
return compileTemplate({
@@ -511,36 +512,3 @@ test('non-identifier expression in legacy filter syntax', () => {
511512
babelParse(compilationResult.code, { sourceType: 'module' })
512513
}).not.toThrow()
513514
})
514-
515-
interface Pos {
516-
line: number
517-
column: number
518-
name?: string
519-
}
520-
521-
function getPositionInCode(
522-
code: string,
523-
token: string,
524-
expectName: string | boolean = false,
525-
): Pos {
526-
const generatedOffset = code.indexOf(token)
527-
let line = 1
528-
let lastNewLinePos = -1
529-
for (let i = 0; i < generatedOffset; i++) {
530-
if (code.charCodeAt(i) === 10 /* newline char code */) {
531-
line++
532-
lastNewLinePos = i
533-
}
534-
}
535-
const res: Pos = {
536-
line,
537-
column:
538-
lastNewLinePos === -1
539-
? generatedOffset
540-
: generatedOffset - lastNewLinePos - 1,
541-
}
542-
if (expectName) {
543-
res.name = typeof expectName === 'string' ? expectName : token
544-
}
545-
return res
546-
}

packages/compiler-sfc/__tests__/utils.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,36 @@ export function assertCode(code: string): void {
4040
}
4141
expect(code).toMatchSnapshot()
4242
}
43+
44+
interface Pos {
45+
line: number
46+
column: number
47+
name?: string
48+
}
49+
50+
export function getPositionInCode(
51+
code: string,
52+
token: string,
53+
expectName: string | boolean = false,
54+
): Pos {
55+
const generatedOffset = code.indexOf(token)
56+
let line = 1
57+
let lastNewLinePos = -1
58+
for (let i = 0; i < generatedOffset; i++) {
59+
if (code.charCodeAt(i) === 10 /* newline char code */) {
60+
line++
61+
lastNewLinePos = i
62+
}
63+
}
64+
const res: Pos = {
65+
line,
66+
column:
67+
lastNewLinePos === -1
68+
? generatedOffset
69+
: generatedOffset - lastNewLinePos - 1,
70+
}
71+
if (expectName) {
72+
res.name = typeof expectName === 'string' ? expectName : token
73+
}
74+
return res
75+
}

packages/compiler-sfc/src/compileScript.ts

Lines changed: 65 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,11 @@ import type {
2323
Statement,
2424
} from '@babel/types'
2525
import { walk } from 'estree-walker'
26-
import type { RawSourceMap } from 'source-map-js'
26+
import {
27+
type RawSourceMap,
28+
SourceMapConsumer,
29+
SourceMapGenerator,
30+
} from 'source-map-js'
2731
import {
2832
normalScriptDefaultVar,
2933
processNormalScript,
@@ -809,6 +813,7 @@ export function compileScript(
809813
args += `, { ${destructureElements.join(', ')} }`
810814
}
811815

816+
let templateMap
812817
// 9. generate return statement
813818
let returned
814819
if (
@@ -858,7 +863,7 @@ export function compileScript(
858863
}
859864
// inline render function mode - we are going to compile the template and
860865
// inline it right here
861-
const { code, ast, preamble, tips, errors } = compileTemplate({
866+
const { code, ast, preamble, tips, errors, map } = compileTemplate({
862867
filename,
863868
ast: sfc.template.ast,
864869
source: sfc.template.content,
@@ -876,6 +881,7 @@ export function compileScript(
876881
bindingMetadata: ctx.bindingMetadata,
877882
},
878883
})
884+
templateMap = map
879885
if (tips.length) {
880886
tips.forEach(warnOnce)
881887
}
@@ -1014,19 +1020,28 @@ export function compileScript(
10141020
)
10151021
}
10161022

1023+
const content = ctx.s.toString()
1024+
let map =
1025+
options.sourceMap !== false
1026+
? (ctx.s.generateMap({
1027+
source: filename,
1028+
hires: true,
1029+
includeContent: true,
1030+
}) as unknown as RawSourceMap)
1031+
: undefined
1032+
// merge source maps of the script setup and template in inline mode
1033+
if (templateMap && map) {
1034+
const offset = content.indexOf(returned)
1035+
const templateLineOffset =
1036+
content.slice(0, offset).split(/\r?\n/).length - 1
1037+
map = mergeSourceMaps(map, templateMap, templateLineOffset)
1038+
}
10171039
return {
10181040
...scriptSetup,
10191041
bindings: ctx.bindingMetadata,
10201042
imports: ctx.userImports,
1021-
content: ctx.s.toString(),
1022-
map:
1023-
options.sourceMap !== false
1024-
? (ctx.s.generateMap({
1025-
source: filename,
1026-
hires: true,
1027-
includeContent: true,
1028-
}) as unknown as RawSourceMap)
1029-
: undefined,
1043+
content,
1044+
map,
10301045
scriptAst: scriptAst?.body,
10311046
scriptSetupAst: scriptSetupAst?.body,
10321047
deps: ctx.deps ? [...ctx.deps] : undefined,
@@ -1284,3 +1299,42 @@ function isStaticNode(node: Node): boolean {
12841299
}
12851300
return false
12861301
}
1302+
1303+
export function mergeSourceMaps(
1304+
scriptMap: RawSourceMap,
1305+
templateMap: RawSourceMap,
1306+
templateLineOffset: number,
1307+
): RawSourceMap {
1308+
const generator = new SourceMapGenerator()
1309+
const addMapping = (map: RawSourceMap, lineOffset = 0) => {
1310+
const consumer = new SourceMapConsumer(map)
1311+
;(consumer as any).sources.forEach((sourceFile: string) => {
1312+
;(generator as any)._sources.add(sourceFile)
1313+
const sourceContent = consumer.sourceContentFor(sourceFile)
1314+
if (sourceContent != null) {
1315+
generator.setSourceContent(sourceFile, sourceContent)
1316+
}
1317+
})
1318+
consumer.eachMapping(m => {
1319+
if (m.originalLine == null) return
1320+
generator.addMapping({
1321+
generated: {
1322+
line: m.generatedLine + lineOffset,
1323+
column: m.generatedColumn,
1324+
},
1325+
original: {
1326+
line: m.originalLine,
1327+
column: m.originalColumn,
1328+
},
1329+
source: m.source,
1330+
name: m.name,
1331+
})
1332+
})
1333+
}
1334+
1335+
addMapping(scriptMap)
1336+
addMapping(templateMap, templateLineOffset)
1337+
;(generator as any)._sourceRoot = scriptMap.sourceRoot
1338+
;(generator as any)._file = scriptMap.file
1339+
return (generator as any).toJSON()
1340+
}

0 commit comments

Comments
 (0)