diff --git a/src/compile/Component.ts b/src/compile/Component.ts index 1b843e9ca2..8589ef6af7 100644 --- a/src/compile/Component.ts +++ b/src/compile/Component.ts @@ -119,16 +119,15 @@ export default class Component { this.componentOptions = process_component_options(this, this.ast.html.children); this.namespace = namespaces[this.componentOptions.namespace] || this.componentOptions.namespace; - if (compileOptions.customElement === true && !this.componentOptions.tag) { - throw new Error(`No tag name specified`); // TODO better error + if (compileOptions.customElement) { + this.tag = compileOptions.customElement.tag || this.componentOptions.tag; + if (!this.tag) { + throw new Error(`Cannot compile to a custom element without specifying a tag name via options.customElement or `); + } + } else { + this.tag = this.name; } - this.tag = compileOptions.customElement - ? compileOptions.customElement === true - ? this.componentOptions.tag - : compileOptions.customElement as string - : this.name; - this.walk_module_js(); this.walk_instance_js_pre_template(); diff --git a/src/compile/index.ts b/src/compile/index.ts index 704d5bd706..ae9d554035 100644 --- a/src/compile/index.ts +++ b/src/compile/index.ts @@ -3,7 +3,7 @@ import Stats from '../Stats'; import parse from '../parse/index'; import renderDOM from './render-dom/index'; import renderSSR from './render-ssr/index'; -import { CompileOptions, Ast, Warning } from '../interfaces'; +import { CompileOptions, Ast, Warning, CustomElementOptions } from '../interfaces'; import Component from './Component'; import fuzzymatch from '../utils/fuzzymatch'; @@ -41,6 +41,10 @@ function validate_options(options: CompileOptions, warnings: Warning[]) { throw new Error(`options.name must be a valid identifier (got '${name}')`); } + if ('customElement' in options) { + options.customElement = normalize_customElement_option(options.customElement); + } + if (name && /^[a-z]/.test(name)) { const message = `options.name should be capitalised`; warnings.push({ @@ -52,6 +56,34 @@ function validate_options(options: CompileOptions, warnings: Warning[]) { } } +const valid_customElement_options = ['tag']; + +function normalize_customElement_option(customElement: boolean | string | CustomElementOptions) { + if (typeof customElement === 'boolean') { + return customElement ? {} : null; + } else if (typeof customElement === 'string') { + customElement = { tag: customElement }; + } else if (typeof customElement === 'object') { + Object.keys(customElement).forEach(key => { + if (valid_customElement_options.indexOf(key) === -1) { + const match = fuzzymatch(key, valid_customElement_options); + let message = `Unrecognized option 'customElement.${key}'`; + if (match) message += ` (did you mean 'customElement.${match}'?)`; + + throw new Error(message); + } + }); + } else { + throw new Error(`options.customElement must be a boolean, a string or an object`); + } + + if ('tag' in customElement && !/^[a-zA-Z][a-zA-Z0-9]*-[a-zA-Z0-9-]+$/.test(customElement.tag)) { + throw new Error(`options.customElement tag name must be two or more words joined by the '-' character`); + } + + return customElement; +} + function get_name(filename) { if (!filename) return null; const parts = filename.split(/[\/\\]/); diff --git a/src/interfaces.ts b/src/interfaces.ts index 4ec258c94a..fc2e6b260e 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -52,7 +52,7 @@ export interface CompileOptions { immutable?: boolean; hydratable?: boolean; legacy?: boolean; - customElement?: CustomElementOptions | true; + customElement?: CustomElementOptions; css?: boolean; preserveComments?: boolean | false; @@ -65,7 +65,6 @@ export interface Visitor { export interface CustomElementOptions { tag?: string; - props?: string[]; } export interface AppendTarget { diff --git a/src/parse/index.ts b/src/parse/index.ts index 4b1e0a225c..1618ee5e6e 100644 --- a/src/parse/index.ts +++ b/src/parse/index.ts @@ -9,7 +9,7 @@ import error from '../utils/error'; interface ParserOptions { filename?: string; bind?: boolean; - customElement?: CustomElementOptions | true; + customElement?: CustomElementOptions; } type ParserState = (parser: Parser) => (ParserState | void); @@ -17,7 +17,7 @@ type ParserState = (parser: Parser) => (ParserState | void); export class Parser { readonly template: string; readonly filename?: string; - readonly customElement: CustomElementOptions | true; + readonly customElement: CustomElementOptions; index = 0; stack: Array = [];