From 634519720a21fb5a6871454e1cadad7053a568b8 Mon Sep 17 00:00:00 2001 From: Evan You Date: Fri, 1 Dec 2023 16:45:53 +0800 Subject: [PATCH] fix(compiler-sfc): deindent pug/jade templates close #3231 close #3842 close #7723 --- .../__tests__/compileTemplate.spec.ts | 27 ++++++++++ packages/compiler-sfc/__tests__/parse.spec.ts | 20 ++++++++ packages/compiler-sfc/src/parse.ts | 51 +++++++++++++++++-- 3 files changed, 93 insertions(+), 5 deletions(-) diff --git a/packages/compiler-sfc/__tests__/compileTemplate.spec.ts b/packages/compiler-sfc/__tests__/compileTemplate.spec.ts index b471b67c9ca..9026a7e9055 100644 --- a/packages/compiler-sfc/__tests__/compileTemplate.spec.ts +++ b/packages/compiler-sfc/__tests__/compileTemplate.spec.ts @@ -60,6 +60,33 @@ body expect(result.errors.length).toBe(0) }) +test('preprocess pug with indents and blank lines', () => { + const template = parse( + ` + +`, + { filename: 'example.vue', sourceMap: true } + ).descriptor.template as SFCTemplateBlock + + const result = compile({ + filename: 'example.vue', + source: template.content, + preprocessLang: template.lang + }) + + expect(result.errors.length).toBe(0) + expect(result.source).toBe( + '

The next line contains four spaces.

The next line is empty.

This is the last line.

' + ) +}) + test('warn missing preprocessor', () => { const template = parse(`\n`, { filename: 'example.vue', diff --git a/packages/compiler-sfc/__tests__/parse.spec.ts b/packages/compiler-sfc/__tests__/parse.spec.ts index c7a17ab1739..ae362da02fe 100644 --- a/packages/compiler-sfc/__tests__/parse.spec.ts +++ b/packages/compiler-sfc/__tests__/parse.spec.ts @@ -34,6 +34,26 @@ describe('compiler:sfc', () => { }) }) + test('template block with lang + indent', () => { + // Padding determines how many blank lines will there be before the style block + const padding = Math.round(Math.random() * 10) + const template = parse( + `${'\n'.repeat(padding)}\n` + ).descriptor.template! + + expect(template.map).not.toBeUndefined() + + const consumer = new SourceMapConsumer(template.map!) + consumer.eachMapping(mapping => { + expect(mapping.originalLine - mapping.generatedLine).toBe(padding) + expect(mapping.originalColumn - mapping.generatedColumn).toBe(2) + }) + }) + test('custom block', () => { const padding = Math.round(Math.random() * 10) const custom = parse( diff --git a/packages/compiler-sfc/src/parse.ts b/packages/compiler-sfc/src/parse.ts index da8cf6d8669..60ee4b654d5 100644 --- a/packages/compiler-sfc/src/parse.ts +++ b/packages/compiler-sfc/src/parse.ts @@ -253,19 +253,31 @@ export function parse( } } + // dedent pug/jade templates + let templateColumnOffset = 0 + if ( + descriptor.template && + (descriptor.template.lang === 'pug' || descriptor.template.lang === 'jade') + ) { + ;[descriptor.template.content, templateColumnOffset] = dedent( + descriptor.template.content + ) + } + if (sourceMap) { - const genMap = (block: SFCBlock | null) => { + const genMap = (block: SFCBlock | null, columnOffset = 0) => { if (block && !block.src) { block.map = generateSourceMap( filename, source, block.content, sourceRoot, - !pad || block.type === 'template' ? block.loc.start.line - 1 : 0 + !pad || block.type === 'template' ? block.loc.start.line - 1 : 0, + columnOffset ) } } - genMap(descriptor.template) + genMap(descriptor.template, templateColumnOffset) genMap(descriptor.script) descriptor.styles.forEach(genMap) descriptor.customBlocks.forEach(genMap) @@ -369,7 +381,8 @@ function generateSourceMap( source: string, generated: string, sourceRoot: string, - lineOffset: number + lineOffset: number, + columnOffset: number ): RawSourceMap { const map = new SourceMapGenerator({ file: filename.replace(/\\/g, '/'), @@ -386,7 +399,7 @@ function generateSourceMap( source: filename, original: { line: originalLine, - column: i + column: i + columnOffset }, generated: { line: generatedLine, @@ -466,3 +479,31 @@ export function hmrShouldReload( return false } + +/** + * Dedent a string. + * + * This removes any whitespace that is common to all lines in the string from + * each line in the string. + */ +function dedent(s: string): [string, number] { + const lines = s.split('\n') + const minIndent = lines.reduce(function (minIndent, line) { + if (line.trim() === '') { + return minIndent + } + const indent = line.match(/^\s*/)?.[0]?.length || 0 + return Math.min(indent, minIndent) + }, Infinity) + if (minIndent === 0) { + return [s, minIndent] + } + return [ + lines + .map(function (line) { + return line.slice(minIndent) + }) + .join('\n'), + minIndent + ] +}