diff --git a/packages/compiler-core/__tests__/parse.spec.ts b/packages/compiler-core/__tests__/parse.spec.ts
index 0d3fab31..e4dcf2b2 100644
--- a/packages/compiler-core/__tests__/parse.spec.ts
+++ b/packages/compiler-core/__tests__/parse.spec.ts
@@ -564,6 +564,52 @@ describe('compiler: parse', () => {
})
})
+ test('native element with `isNativeTag`', () => {
+ const ast = parse('
', {
+ 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('')
+
+ 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('')
const element = ast.children[0] as ElementNode
diff --git a/packages/compiler-core/src/parse.ts b/packages/compiler-core/src/parse.ts
index 019eddb4..1e8a6d2a 100644
--- a/packages/compiler-core/src/parse.ts
+++ b/packages/compiler-core/src/parse.ts
@@ -1,3 +1,4 @@
+import { NO } from '@vue/shared'
import {
ErrorCodes,
createCompilerError,
@@ -30,6 +31,7 @@ import { extend } from '@vue/shared'
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] // ['{{', '}}']
@@ -42,12 +44,19 @@ export interface ParserOptions {
onError?: (error: CompilerError) => void
}
-export const defaultParserOptions: Required = {
+// `isNativeTag` is optional, others are required
+type MergedParserOptions = Pick<
+ Required,
+ Exclude
+> &
+ Pick
+
+export const defaultParserOptions: MergedParserOptions = {
delimiters: [`{{`, `}}`],
ignoreSpaces: true,
getNamespace: () => Namespaces.HTML,
getTextMode: () => TextModes.DATA,
- isVoidTag: () => false,
+ isVoidTag: NO,
namedCharacterReferences: {
'gt;': '>',
'lt;': '<',
@@ -68,7 +77,7 @@ export const enum TextModes {
}
interface ParserContext {
- options: Required
+ options: MergedParserOptions
readonly originalSource: string
source: string
offset: number
@@ -428,9 +437,14 @@ function parseTag(
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 {
diff --git a/packages/compiler-dom/__tests__/parse.spec.ts b/packages/compiler-dom/__tests__/parse.spec.ts
index fc7c5352..752d08c4 100644
--- a/packages/compiler-dom/__tests__/parse.spec.ts
+++ b/packages/compiler-dom/__tests__/parse.spec.ts
@@ -154,6 +154,28 @@ describe('DOM parser', () => {
})
})
+ test('native element', () => {
+ const ast = parse('', 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(
'">',
diff --git a/packages/compiler-dom/src/parserOptionsMinimal.ts b/packages/compiler-dom/src/parserOptionsMinimal.ts
index fe681ad5..82c7e7ee 100644
--- a/packages/compiler-dom/src/parserOptionsMinimal.ts
+++ b/packages/compiler-dom/src/parserOptionsMinimal.ts
@@ -5,6 +5,7 @@ import {
Namespaces,
NodeTypes
} from '@vue/compiler-core'
+import { isVoidTag, isHTMLTag, isSVGTag } from '@vue/shared'
export const enum DOMNamespaces {
HTML = Namespaces.HTML,
@@ -13,6 +14,10 @@ export const enum DOMNamespaces {
}
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
@@ -75,11 +80,5 @@ export const parserOptionsMinimal: ParserOptions = {
}
}
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
- )
}
}
diff --git a/packages/runtime-core/src/apiApp.ts b/packages/runtime-core/src/apiApp.ts
index ecf26fab..b80c502c 100644
--- a/packages/runtime-core/src/apiApp.ts
+++ b/packages/runtime-core/src/apiApp.ts
@@ -110,10 +110,9 @@ export function createAppAPI(
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
diff --git a/packages/shared/src/element.ts b/packages/shared/src/element.ts
new file mode 100644
index 00000000..340d2d82
--- /dev/null
+++ b/packages/shared/src/element.ts
@@ -0,0 +1,176 @@
+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)
diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts
index 485be989..2b8a1023 100644
--- a/packages/shared/src/index.ts
+++ b/packages/shared/src/index.ts
@@ -1,4 +1,5 @@
export * from './patchFlags'
+export * from './element'
export { globalsWhitelist } from './globalsWhitelist'
export const EMPTY_OBJ: { readonly [key: string]: any } = __DEV__
@@ -8,6 +9,11 @@ export const EMPTY_ARR: [] = []
export const NOOP = () => {}
+/**
+ * Always return false.
+ */
+export const NO = () => false
+
export const isOn = (key: string) => key[0] === 'o' && key[1] === 'n'
export const extend = (