diff --git a/src/compile/Component.ts b/src/compile/Component.ts index 259144d691..86b43c1b64 100644 --- a/src/compile/Component.ts +++ b/src/compile/Component.ts @@ -152,10 +152,14 @@ export default class Component { this.namespace = namespaces[this.component_options.namespace] || this.component_options.namespace; if (compile_options.customElement) { - this.tag = this.component_options.tag || compile_options.tag; - if (!this.tag) { - throw new Error(`Cannot compile to a custom element without specifying a tag name via options.tag or `); + if (this.component_options.tag === undefined && compile_options.tag === undefined) { + const svelteOptions = ast.html.children.find(child => child.name === 'svelte:options'); + this.warn(svelteOptions, { + code: 'custom-element-no-tag', + message: `No custom element 'tag' option was specified. To automatically register a custom element, specify a name with a hyphen in it, e.g. . To hide this warning, use ` + }); } + this.tag = this.component_options.tag || compile_options.tag; } else { this.tag = this.name; } @@ -1267,9 +1271,9 @@ function process_component_options(component: Component, nodes) { const message = `'tag' must be a string literal`; const tag = get_value(attribute, code, message); - if (typeof tag !== 'string') component.error(attribute, { code, message }); + if (typeof tag !== 'string' && tag !== null) component.error(attribute, { code, message }); - if (!/^[a-zA-Z][a-zA-Z0-9]*-[a-zA-Z0-9-]+$/.test(tag)) { + if (tag && !/^[a-zA-Z][a-zA-Z0-9]*-[a-zA-Z0-9-]+$/.test(tag)) { component.error(attribute, { code: `invalid-tag-property`, message: `tag name must be two or more words joined by the '-' character` diff --git a/src/compile/render-dom/index.ts b/src/compile/render-dom/index.ts index 1cd0664ecf..0fd0d4b172 100644 --- a/src/compile/render-dom/index.ts +++ b/src/compile/render-dom/index.ts @@ -462,9 +462,13 @@ export default function dom( ${body.length > 0 && body.join('\n\n')} } - - customElements.define("${component.tag}", ${name}); `); + + if (component.tag != null) { + builder.add_block(deindent` + customElements.define("${component.tag}", ${name}); + `); + } } else { const superclass = options.dev ? 'SvelteComponentDev' : 'SvelteComponent'; diff --git a/test/custom-elements/index.js b/test/custom-elements/index.js index 32e95266ec..5ac0b73bbf 100644 --- a/test/custom-elements/index.js +++ b/test/custom-elements/index.js @@ -5,6 +5,7 @@ import { rollup } from 'rollup'; import * as virtual from 'rollup-plugin-virtual'; import * as puppeteer from 'puppeteer'; import { addLineNumbers, loadConfig, loadSvelte } from "../helpers.js"; +import { deepEqual } from 'assert'; const page = ` @@ -59,9 +60,11 @@ describe('custom-elements', function() { const skip = /\.skip$/.test(dir); const internal = path.resolve('internal.mjs'); const index = path.resolve('index.mjs'); + const warnings = []; (solo ? it.only : skip ? it.skip : it)(dir, async () => { const config = loadConfig(`./custom-elements/samples/${dir}/_config.js`); + const expected_warnings = config.warnings || []; const bundle = await rollup({ input: `test/custom-elements/samples/${dir}/test.js`, @@ -84,6 +87,8 @@ describe('custom-elements', function() { dev: config.dev }); + compiled.warnings.forEach(w => warnings.push(w)); + return compiled.js; } } @@ -112,6 +117,16 @@ describe('custom-elements', function() { } catch (err) { console.log(addLineNumbers(code)); throw err; + } finally { + if (expected_warnings) { + deepEqual(warnings.map(w => ({ + code: w.code, + message: w.message, + pos: w.pos, + start: w.start, + end: w.end + })), expected_warnings); + } } }); }); diff --git a/test/custom-elements/samples/no-tag-warning/_config.js b/test/custom-elements/samples/no-tag-warning/_config.js new file mode 100644 index 0000000000..7cdfdbaec6 --- /dev/null +++ b/test/custom-elements/samples/no-tag-warning/_config.js @@ -0,0 +1,17 @@ +export default { + warnings: [{ + code: "custom-element-no-tag", + message: "No custom element 'tag' option was specified. To automatically register a custom element, specify a name with a hyphen in it, e.g. . To hide this warning, use ", + pos: 0, + start: { + character: 0, + column: 0, + line: 1 + }, + end: { + character: 18, + column: 18, + line: 1 + } + }] +}; diff --git a/test/custom-elements/samples/no-tag-warning/main.svelte b/test/custom-elements/samples/no-tag-warning/main.svelte new file mode 100644 index 0000000000..4f7cdc52ca --- /dev/null +++ b/test/custom-elements/samples/no-tag-warning/main.svelte @@ -0,0 +1,7 @@ + + + + +

Hello {name}!

diff --git a/test/custom-elements/samples/no-tag-warning/test.js b/test/custom-elements/samples/no-tag-warning/test.js new file mode 100644 index 0000000000..c77f035088 --- /dev/null +++ b/test/custom-elements/samples/no-tag-warning/test.js @@ -0,0 +1,12 @@ +import * as assert from 'assert'; +import CustomElement from './main.svelte'; + +export default function (target) { + customElements.define('no-tag', CustomElement); + target.innerHTML = ``; + + const el = target.querySelector('no-tag'); + const h1 = el.shadowRoot.querySelector('h1'); + + assert.equal(h1.textContent, 'Hello world!'); +} diff --git a/test/custom-elements/samples/no-tag/_config.js b/test/custom-elements/samples/no-tag/_config.js new file mode 100644 index 0000000000..c81f1a9f82 --- /dev/null +++ b/test/custom-elements/samples/no-tag/_config.js @@ -0,0 +1,3 @@ +export default { + warnings: [] +}; diff --git a/test/custom-elements/samples/no-tag/main.svelte b/test/custom-elements/samples/no-tag/main.svelte new file mode 100644 index 0000000000..031bd93694 --- /dev/null +++ b/test/custom-elements/samples/no-tag/main.svelte @@ -0,0 +1,7 @@ + + + + +

Hello {name}!

diff --git a/test/custom-elements/samples/no-tag/test.js b/test/custom-elements/samples/no-tag/test.js new file mode 100644 index 0000000000..c77f035088 --- /dev/null +++ b/test/custom-elements/samples/no-tag/test.js @@ -0,0 +1,12 @@ +import * as assert from 'assert'; +import CustomElement from './main.svelte'; + +export default function (target) { + customElements.define('no-tag', CustomElement); + target.innerHTML = ``; + + const el = target.querySelector('no-tag'); + const h1 = el.shadowRoot.querySelector('h1'); + + assert.equal(h1.textContent, 'Hello world!'); +}