feat(compiler-sfc): handle pad option (#509)

This commit is contained in:
likui 2019-12-02 23:43:30 +08:00 committed by Evan You
parent 08a1de5e29
commit ef2786151e
2 changed files with 76 additions and 9 deletions

View File

@ -19,6 +19,51 @@ describe('compiler:sfc', () => {
}) })
}) })
test('pad content', () => {
const content = `
<template>
<div></div>
</template>
<script>
export default {}
</script>
<style>
h1 { color: red }
</style>`
const padFalse = parse(content.trim(), { pad: false })
expect(padFalse.template!.content).toBe('\n<div></div>\n')
expect(padFalse.script!.content).toBe('\nexport default {}\n')
expect(padFalse.styles[0].content).toBe('\nh1 { color: red }\n')
const padTrue = parse(content.trim(), { pad: true })
expect(padTrue.script!.content).toBe(
Array(3 + 1).join('//\n') + '\nexport default {}\n'
)
expect(padTrue.styles[0].content).toBe(
Array(6 + 1).join('\n') + '\nh1 { color: red }\n'
)
const padLine = parse(content.trim(), { pad: 'line' })
expect(padLine.script!.content).toBe(
Array(3 + 1).join('//\n') + '\nexport default {}\n'
)
expect(padLine.styles[0].content).toBe(
Array(6 + 1).join('\n') + '\nh1 { color: red }\n'
)
const padSpace = parse(content.trim(), { pad: 'space' })
expect(padSpace.script!.content).toBe(
`<template>\n<div></div>\n</template>\n<script>`.replace(/./g, ' ') +
'\nexport default {}\n'
)
expect(padSpace.styles[0].content).toBe(
`<template>\n<div></div>\n</template>\n<script>\nexport default {}\n</script>\n<style>`.replace(
/./g,
' '
) + '\nh1 { color: red }\n'
)
})
test('should ignore nodes with no content', () => { test('should ignore nodes with no content', () => {
expect(parse(`<template/>`).template).toBe(null) expect(parse(`<template/>`).template).toBe(null)
expect(parse(`<script/>`).script).toBe(null) expect(parse(`<script/>`).script).toBe(null)

View File

@ -14,7 +14,7 @@ export interface SFCParseOptions {
needMap?: boolean needMap?: boolean
filename?: string filename?: string
sourceRoot?: string sourceRoot?: string
pad?: 'line' | 'space' pad?: boolean | 'line' | 'space'
} }
export interface SFCBlock { export interface SFCBlock {
@ -61,7 +61,7 @@ export function parse(
pad = 'line' pad = 'line'
}: SFCParseOptions = {} }: SFCParseOptions = {}
): SFCDescriptor { ): SFCDescriptor {
const sourceKey = source + needMap + filename + sourceRoot const sourceKey = source + needMap + filename + sourceRoot + pad
const cache = sourceToSFC.get(sourceKey) const cache = sourceToSFC.get(sourceKey)
if (cache) { if (cache) {
return cache return cache
@ -87,27 +87,26 @@ export function parse(
if (!node.children.length) { if (!node.children.length) {
return return
} }
// TODO handle pad option
switch (node.tag) { switch (node.tag) {
case 'template': case 'template':
if (!sfc.template) { if (!sfc.template) {
sfc.template = createBlock(node) as SFCTemplateBlock sfc.template = createBlock(node, source, pad) as SFCTemplateBlock
} else { } else {
warnDuplicateBlock(source, filename, node) warnDuplicateBlock(source, filename, node)
} }
break break
case 'script': case 'script':
if (!sfc.script) { if (!sfc.script) {
sfc.script = createBlock(node) as SFCScriptBlock sfc.script = createBlock(node, source, pad) as SFCScriptBlock
} else { } else {
warnDuplicateBlock(source, filename, node) warnDuplicateBlock(source, filename, node)
} }
break break
case 'style': case 'style':
sfc.styles.push(createBlock(node) as SFCStyleBlock) sfc.styles.push(createBlock(node, source, pad) as SFCStyleBlock)
break break
default: default:
sfc.customBlocks.push(createBlock(node)) sfc.customBlocks.push(createBlock(node, source, pad))
break break
} }
}) })
@ -159,7 +158,11 @@ function warnDuplicateBlock(
) )
} }
function createBlock(node: ElementNode): SFCBlock { function createBlock(
node: ElementNode,
source: string,
pad: SFCParseOptions['pad']
): SFCBlock {
const type = node.tag const type = node.tag
const text = node.children[0] as TextNode const text = node.children[0] as TextNode
const attrs: Record<string, string | true> = {} const attrs: Record<string, string | true> = {}
@ -169,6 +172,9 @@ function createBlock(node: ElementNode): SFCBlock {
loc: text.loc, loc: text.loc,
attrs attrs
} }
if (node.tag !== 'template' && pad) {
block.content = padContent(source, block, pad) + block.content
}
node.props.forEach(p => { node.props.forEach(p => {
if (p.type === NodeTypes.ATTRIBUTE) { if (p.type === NodeTypes.ATTRIBUTE) {
attrs[p.name] = p.value ? p.value.content || true : true attrs[p.name] = p.value ? p.value.content || true : true
@ -192,13 +198,14 @@ function createBlock(node: ElementNode): SFCBlock {
const splitRE = /\r?\n/g const splitRE = /\r?\n/g
const emptyRE = /^(?:\/\/)?\s*$/ const emptyRE = /^(?:\/\/)?\s*$/
const replaceRE = /./g
function generateSourceMap( function generateSourceMap(
filename: string, filename: string,
source: string, source: string,
generated: string, generated: string,
sourceRoot: string, sourceRoot: string,
pad?: 'line' | 'space' pad?: SFCParseOptions['pad']
): RawSourceMap { ): RawSourceMap {
const map = new SourceMapGenerator({ const map = new SourceMapGenerator({
file: filename.replace(/\\/g, '/'), file: filename.replace(/\\/g, '/'),
@ -230,3 +237,18 @@ function generateSourceMap(
}) })
return JSON.parse(map.toString()) return JSON.parse(map.toString())
} }
function padContent(
content: string,
block: SFCBlock,
pad: SFCParseOptions['pad']
): string {
content = content.slice(0, block.loc.start.offset)
if (pad === 'space') {
return content.replace(replaceRE, ' ')
} else {
const offset = content.split(splitRE).length
const padChar = block.type === 'script' && !block.lang ? '//\n' : '\n'
return Array(offset).join(padChar)
}
}