})
})
+ 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', () => {
expect(parse(`<template/>`).template).toBe(null)
expect(parse(`<script/>`).script).toBe(null)
needMap?: boolean
filename?: string
sourceRoot?: string
- pad?: 'line' | 'space'
+ pad?: boolean | 'line' | 'space'
}
export interface SFCBlock {
pad = 'line'
}: SFCParseOptions = {}
): SFCDescriptor {
- const sourceKey = source + needMap + filename + sourceRoot
+ const sourceKey = source + needMap + filename + sourceRoot + pad
const cache = sourceToSFC.get(sourceKey)
if (cache) {
return cache
if (!node.children.length) {
return
}
- // TODO handle pad option
switch (node.tag) {
case 'template':
if (!sfc.template) {
- sfc.template = createBlock(node) as SFCTemplateBlock
+ sfc.template = createBlock(node, source, pad) as SFCTemplateBlock
} else {
warnDuplicateBlock(source, filename, node)
}
break
case 'script':
if (!sfc.script) {
- sfc.script = createBlock(node) as SFCScriptBlock
+ sfc.script = createBlock(node, source, pad) as SFCScriptBlock
} else {
warnDuplicateBlock(source, filename, node)
}
break
case 'style':
- sfc.styles.push(createBlock(node) as SFCStyleBlock)
+ sfc.styles.push(createBlock(node, source, pad) as SFCStyleBlock)
break
default:
- sfc.customBlocks.push(createBlock(node))
+ sfc.customBlocks.push(createBlock(node, source, pad))
break
}
})
)
}
-function createBlock(node: ElementNode): SFCBlock {
+function createBlock(
+ node: ElementNode,
+ source: string,
+ pad: SFCParseOptions['pad']
+): SFCBlock {
const type = node.tag
const text = node.children[0] as TextNode
const attrs: Record<string, string | true> = {}
loc: text.loc,
attrs
}
+ if (node.tag !== 'template' && pad) {
+ block.content = padContent(source, block, pad) + block.content
+ }
node.props.forEach(p => {
if (p.type === NodeTypes.ATTRIBUTE) {
attrs[p.name] = p.value ? p.value.content || true : true
const splitRE = /\r?\n/g
const emptyRE = /^(?:\/\/)?\s*$/
+const replaceRE = /./g
function generateSourceMap(
filename: string,
source: string,
generated: string,
sourceRoot: string,
- pad?: 'line' | 'space'
+ pad?: SFCParseOptions['pad']
): RawSourceMap {
const map = new SourceMapGenerator({
file: filename.replace(/\\/g, '/'),
})
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)
+ }
+}