mirror of https://github.com/vuejs/core.git
fix(compile-sfc): handle inline template source map in prod build (#12701)
close #12682 close vitejs/vite-plugin-vue#500
This commit is contained in:
parent
f44feed6fa
commit
89edc6cdcb
|
@ -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 <script setup>', () => {
|
||||
test('should compile JS syntax', () => {
|
||||
|
@ -690,6 +696,27 @@ describe('SFC compile <script setup>', () => {
|
|||
expect(content).toMatch(`new (_unref(Foo)).Bar()`)
|
||||
assertCode(content)
|
||||
})
|
||||
|
||||
// #12682
|
||||
test('source map', () => {
|
||||
const source = `
|
||||
<script setup>
|
||||
const count = ref(0)
|
||||
</script>
|
||||
<template>
|
||||
<button @click="throw new Error(\`msg\`);"></button>
|
||||
</template>
|
||||
`
|
||||
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', () => {
|
||||
|
|
|
@ -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<SFCTemplateCompileOptions, 'id'>) {
|
||||
return compileTemplate({
|
||||
|
@ -511,36 +512,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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
@ -809,6 +813,7 @@ export function compileScript(
|
|||
args += `, { ${destructureElements.join(', ')} }`
|
||||
}
|
||||
|
||||
let templateMap
|
||||
// 9. generate return statement
|
||||
let returned
|
||||
if (
|
||||
|
@ -858,7 +863,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,
|
||||
|
@ -876,6 +881,7 @@ export function compileScript(
|
|||
bindingMetadata: ctx.bindingMetadata,
|
||||
},
|
||||
})
|
||||
templateMap = map
|
||||
if (tips.length) {
|
||||
tips.forEach(warnOnce)
|
||||
}
|
||||
|
@ -1014,19 +1020,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,
|
||||
|
@ -1284,3 +1299,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()
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue