})
})
+ test('native element with `isNativeTag`', () => {
+ const ast = parse('<div></div><comp></comp><Comp></Comp>', {
+ isNativeTag: tag => tag === 'div'
+ })
+
+ expect(ast.children[0]).toMatchObject({
+ type: NodeTypes.ELEMENT,
+ tag: 'div',
+ tagType: ElementTypes.ELEMENT
+ })
+
+ expect(ast.children[1]).toMatchObject({
+ type: NodeTypes.ELEMENT,
+ tag: 'comp',
+ tagType: ElementTypes.COMPONENT
+ })
+
+ expect(ast.children[2]).toMatchObject({
+ type: NodeTypes.ELEMENT,
+ tag: 'Comp',
+ tagType: ElementTypes.COMPONENT
+ })
+ })
+
+ test('native element without `isNativeTag`', () => {
+ const ast = parse('<div></div><comp></comp><Comp></Comp>')
+
+ expect(ast.children[0]).toMatchObject({
+ type: NodeTypes.ELEMENT,
+ tag: 'div',
+ tagType: ElementTypes.ELEMENT
+ })
+
+ expect(ast.children[1]).toMatchObject({
+ type: NodeTypes.ELEMENT,
+ tag: 'comp',
+ tagType: ElementTypes.ELEMENT
+ })
+
+ expect(ast.children[2]).toMatchObject({
+ type: NodeTypes.ELEMENT,
+ tag: 'Comp',
+ tagType: ElementTypes.COMPONENT
+ })
+ })
+
test('attribute with no value', () => {
const ast = parse('<div id></div>')
const element = ast.children[0] as ElementNode
+import { NO } from '@vue/shared'
import {
ErrorCodes,
createCompilerError,
export interface ParserOptions {
isVoidTag?: (tag: string) => boolean // e.g. img, br, hr
+ isNativeTag?: (tag: string) => boolean // e.g. loading-indicator in weex
getNamespace?: (tag: string, parent: ElementNode | undefined) => Namespace
getTextMode?: (tag: string, ns: Namespace) => TextModes
delimiters?: [string, string] // ['{{', '}}']
onError?: (error: CompilerError) => void
}
-export const defaultParserOptions: Required<ParserOptions> = {
+// `isNativeTag` is optional, others are required
+type MergedParserOptions = Pick<
+ Required<ParserOptions>,
+ Exclude<keyof ParserOptions, 'isNativeTag'>
+> &
+ Pick<ParserOptions, 'isNativeTag'>
+
+export const defaultParserOptions: MergedParserOptions = {
delimiters: [`{{`, `}}`],
ignoreSpaces: true,
getNamespace: () => Namespaces.HTML,
getTextMode: () => TextModes.DATA,
- isVoidTag: () => false,
+ isVoidTag: NO,
namedCharacterReferences: {
'gt;': '>',
'lt;': '<',
}
interface ParserContext {
- options: Required<ParserOptions>
+ options: MergedParserOptions
readonly originalSource: string
source: string
offset: number
let tagType = ElementTypes.ELEMENT
if (!context.inPre) {
+ if (context.options.isNativeTag) {
+ if (!context.options.isNativeTag(tag)) tagType = ElementTypes.COMPONENT
+ } else {
+ if (/^[A-Z]/.test(tag)) tagType = ElementTypes.COMPONENT
+ }
+
if (tag === 'slot') tagType = ElementTypes.SLOT
else if (tag === 'template') tagType = ElementTypes.TEMPLATE
- else if (/[A-Z-]/.test(tag)) tagType = ElementTypes.COMPONENT
}
return {
})
})
+ test('native element', () => {
+ const ast = parse('<div></div><comp></comp><Comp></Comp>', parserOptions)
+
+ expect(ast.children[0]).toMatchObject({
+ type: NodeTypes.ELEMENT,
+ tag: 'div',
+ tagType: ElementTypes.ELEMENT
+ })
+
+ expect(ast.children[1]).toMatchObject({
+ type: NodeTypes.ELEMENT,
+ tag: 'comp',
+ tagType: ElementTypes.COMPONENT
+ })
+
+ expect(ast.children[2]).toMatchObject({
+ type: NodeTypes.ELEMENT,
+ tag: 'Comp',
+ tagType: ElementTypes.COMPONENT
+ })
+ })
+
test('Strict end tag detection for textarea.', () => {
const ast = parse(
'<textarea>hello</textarea</textarea0></texTArea a="<>">',
Namespaces,
NodeTypes
} from '@vue/compiler-core'
+import { isVoidTag, isHTMLTag, isSVGTag } from '@vue/shared'
export const enum DOMNamespaces {
HTML = Namespaces.HTML,
}
export const parserOptionsMinimal: ParserOptions = {
+ isVoidTag,
+
+ isNativeTag: tag => isHTMLTag(tag) || isSVGTag(tag),
+
// https://html.spec.whatwg.org/multipage/parsing.html#tree-construction-dispatcher
getNamespace(tag: string, parent: ElementNode | undefined): DOMNamespaces {
let ns = parent ? parent.ns : DOMNamespaces.HTML
}
}
return TextModes.DATA
- },
-
- isVoidTag(tag: string): boolean {
- return /^(?:area|base|br|col|embed|hr|img|input|link|meta|param|source|track|wbr)$/i.test(
- tag
- )
}
}
return app
},
- component(name: string, component?: Component) {
- // TODO component name validation
+ component(name: string, component?: Component): any {
if (!component) {
- return context.components[name] as any
+ return context.components[name]
} else {
context.components[name] = component
return app
--- /dev/null
+const HTMLTagSet = new Set([
+ 'html',
+ 'body',
+ 'base',
+ 'head',
+ 'link',
+ 'meta',
+ 'style',
+ 'title',
+ 'address',
+ 'article',
+ 'aside',
+ 'footer',
+ 'header',
+ 'h1',
+ 'h2',
+ 'h3',
+ 'h4',
+ 'h5',
+ 'h6',
+ 'hgroup',
+ 'nav',
+ 'section',
+ 'div',
+ 'dd',
+ 'dl',
+ 'dt',
+ 'figcaption',
+ 'figure',
+ 'picture',
+ 'hr',
+ 'img',
+ 'li',
+ 'main',
+ 'ol',
+ 'p',
+ 'pre',
+ 'ul',
+ 'a',
+ 'b',
+ 'abbr',
+ 'bdi',
+ 'bdo',
+ 'br',
+ 'cite',
+ 'code',
+ 'data',
+ 'dfn',
+ 'em',
+ 'i',
+ 'kbd',
+ 'mark',
+ 'q',
+ 'rp',
+ 'rt',
+ 'rtc',
+ 'ruby',
+ 's',
+ 'samp',
+ 'small',
+ 'span',
+ 'strong',
+ 'sub',
+ 'sup',
+ 'time',
+ 'u',
+ 'var',
+ 'wbr',
+ 'area',
+ 'audio',
+ 'map',
+ 'track',
+ 'video',
+ 'embed',
+ 'object',
+ 'param',
+ 'source',
+ 'canvas',
+ 'script',
+ 'noscript',
+ 'del',
+ 'ins',
+ 'caption',
+ 'col',
+ 'colgroup',
+ 'table',
+ 'thead',
+ 'tbody',
+ 'td',
+ 'th',
+ 'tr',
+ 'button',
+ 'datalist',
+ 'fieldset',
+ 'form',
+ 'input',
+ 'label',
+ 'legend',
+ 'meter',
+ 'optgroup',
+ 'option',
+ 'output',
+ 'progress',
+ 'select',
+ 'textarea',
+ 'details',
+ 'dialog',
+ 'menu',
+ 'menuitem',
+ 'summary',
+ 'content',
+ 'element',
+ 'shadow',
+ 'template',
+ 'blockquote',
+ 'iframe',
+ 'tfoot'
+])
+
+/**
+ * this list is intentionally selective, only covering SVG elements that may
+ * contain child elements.
+ */
+const SVGTagSet = new Set([
+ 'svg',
+ 'animate',
+ 'circle',
+ 'clippath',
+ 'cursor',
+ 'defs',
+ 'desc',
+ 'ellipse',
+ 'filter',
+ 'font-face',
+ 'foreignObject',
+ 'g',
+ 'glyph',
+ 'image',
+ 'line',
+ 'marker',
+ 'mask',
+ 'missing-glyph',
+ 'path',
+ 'pattern',
+ 'polygon',
+ 'polyline',
+ 'rect',
+ 'switch',
+ 'symbol',
+ 'text',
+ 'textpath',
+ 'tspan',
+ 'use',
+ 'view'
+])
+
+const VoidTagSet = new Set([
+ 'area',
+ 'base',
+ 'br',
+ 'col',
+ 'embed',
+ 'hr',
+ 'img',
+ 'input',
+ 'link',
+ 'meta',
+ 'param',
+ 'source',
+ 'track',
+ 'wbr'
+])
+
+export const isVoidTag = (tag: string) => VoidTagSet.has(tag)
+export const isHTMLTag = (tag: string) => HTMLTagSet.has(tag)
+export const isSVGTag = (tag: string) => SVGTagSet.has(tag)
export * from './patchFlags'
+export * from './element'
export { globalsWhitelist } from './globalsWhitelist'
export const EMPTY_OBJ: { readonly [key: string]: any } = __DEV__
export const NOOP = () => {}
+/**
+ * Always return false.
+ */
+export const NO = () => false
+
export const isOn = (key: string) => key[0] === 'o' && key[1] === 'n'
export const extend = <T extends object, U extends object>(