135 lines
3.2 KiB
JavaScript
135 lines
3.2 KiB
JavaScript
/* @flow */
|
|
|
|
import deindent from 'de-indent'
|
|
import { parseHTML } from 'compiler/parser/html-parser'
|
|
import { makeMap } from 'shared/util'
|
|
|
|
const splitRE = /\r?\n/g
|
|
const replaceRE = /./g
|
|
const isSpecialTag = makeMap('script,style,template', true)
|
|
|
|
/**
|
|
* Parse a single-file component (*.vue) file into an SFC Descriptor Object.
|
|
*/
|
|
export function parseComponent (
|
|
content: string,
|
|
options?: Object = {}
|
|
): SFCDescriptor {
|
|
const sfc: SFCDescriptor = {
|
|
template: null,
|
|
script: null,
|
|
styles: [],
|
|
customBlocks: [],
|
|
errors: []
|
|
}
|
|
let depth = 0
|
|
let currentBlock: ?SFCBlock = null
|
|
|
|
let warn = msg => {
|
|
sfc.errors.push(msg)
|
|
}
|
|
|
|
if (process.env.NODE_ENV !== 'production' && options.outputSourceRange) {
|
|
warn = (msg, range) => {
|
|
const data: WarningMessage = { msg }
|
|
if (range.start != null) {
|
|
data.start = range.start
|
|
}
|
|
if (range.end != null) {
|
|
data.end = range.end
|
|
}
|
|
sfc.errors.push(data)
|
|
}
|
|
}
|
|
|
|
function start (
|
|
tag: string,
|
|
attrs: Array<ASTAttr>,
|
|
unary: boolean,
|
|
start: number,
|
|
end: number
|
|
) {
|
|
if (depth === 0) {
|
|
currentBlock = {
|
|
type: tag,
|
|
content: '',
|
|
start: end,
|
|
attrs: attrs.reduce((cumulated, { name, value }) => {
|
|
cumulated[name] = value || true
|
|
return cumulated
|
|
}, {})
|
|
}
|
|
if (isSpecialTag(tag)) {
|
|
checkAttrs(currentBlock, attrs)
|
|
if (tag === 'style') {
|
|
sfc.styles.push(currentBlock)
|
|
} else {
|
|
sfc[tag] = currentBlock
|
|
}
|
|
} else { // custom blocks
|
|
sfc.customBlocks.push(currentBlock)
|
|
}
|
|
}
|
|
if (!unary) {
|
|
depth++
|
|
}
|
|
}
|
|
|
|
function checkAttrs (block: SFCBlock, attrs: Array<ASTAttr>) {
|
|
for (let i = 0; i < attrs.length; i++) {
|
|
const attr = attrs[i]
|
|
if (attr.name === 'lang') {
|
|
block.lang = attr.value
|
|
}
|
|
if (attr.name === 'scoped') {
|
|
block.scoped = true
|
|
}
|
|
if (attr.name === 'module') {
|
|
block.module = attr.value || true
|
|
}
|
|
if (attr.name === 'src') {
|
|
block.src = attr.value
|
|
}
|
|
}
|
|
}
|
|
|
|
function end (tag: string, start: number) {
|
|
if (depth === 1 && currentBlock) {
|
|
currentBlock.end = start
|
|
let text = content.slice(currentBlock.start, currentBlock.end)
|
|
if (options.deindent !== false) {
|
|
text = deindent(text)
|
|
}
|
|
// pad content so that linters and pre-processors can output correct
|
|
// line numbers in errors and warnings
|
|
if (currentBlock.type !== 'template' && options.pad) {
|
|
text = padContent(currentBlock, options.pad) + text
|
|
}
|
|
currentBlock.content = text
|
|
currentBlock = null
|
|
}
|
|
depth--
|
|
}
|
|
|
|
function padContent (block: SFCBlock, pad: true | "line" | "space") {
|
|
if (pad === 'space') {
|
|
return content.slice(0, block.start).replace(replaceRE, ' ')
|
|
} else {
|
|
const offset = content.slice(0, block.start).split(splitRE).length
|
|
const padChar = block.type === 'script' && !block.lang
|
|
? '//\n'
|
|
: '\n'
|
|
return Array(offset).join(padChar)
|
|
}
|
|
}
|
|
|
|
parseHTML(content, {
|
|
warn,
|
|
start,
|
|
end,
|
|
outputSourceRange: options.outputSourceRange
|
|
})
|
|
|
|
return sfc
|
|
}
|