From 8cc67854b6e0493040406b793b2d65cbc6bdc097 Mon Sep 17 00:00:00 2001 From: Cris Ward Date: Mon, 27 Jan 2020 20:20:49 +0000 Subject: [PATCH 1/5] [WIP] #1748 - Custom elements light dom, open / closed --- src/compiler/compile/Component.ts | 15 +++++++++++++-- src/compiler/compile/index.ts | 1 + src/compiler/compile/render_dom/index.ts | 17 +++++++++++------ src/compiler/interfaces.ts | 7 ++++++- src/runtime/internal/Component.ts | 1 - .../css-shadow-dom-keyframes/expected.js | 1 + 6 files changed, 32 insertions(+), 10 deletions(-) diff --git a/src/compiler/compile/Component.ts b/src/compiler/compile/Component.ts index 9822529ece61..9ed74ea3a983 100644 --- a/src/compiler/compile/Component.ts +++ b/src/compiler/compile/Component.ts @@ -14,7 +14,7 @@ import Stylesheet from './css/Stylesheet'; import { test } from '../config'; import Fragment from './nodes/Fragment'; import internal_exports from './internal_exports'; -import { Ast, CompileOptions, Var, Warning, CssResult } from '../interfaces'; +import { Ast, CompileOptions, Var, Warning, CssResult, ShadowDomMode } from '../interfaces'; import error from '../utils/error'; import get_code_frame from '../utils/get_code_frame'; import flatten_reference from './utils/flatten_reference'; @@ -30,6 +30,7 @@ import check_graph_for_cycles from './utils/check_graph_for_cycles'; import { print, x, b } from 'code-red'; interface ComponentOptions { + shadowdom?: ShadowDomMode; namespace?: string; tag?: string; immutable?: boolean; @@ -158,6 +159,7 @@ export default class Component { }); } this.tag = this.component_options.tag || compile_options.tag; + this.compile_options.shadowDom = this.component_options.shadowdom || "open"; } else { this.tag = this.name.name; } @@ -170,7 +172,7 @@ export default class Component { this.walk_instance_js_post_template(); - if (!compile_options.customElement) this.stylesheet.reify(); + if (!compile_options.customElement || compile_options.shadowDom=="none") this.stylesheet.reify(); this.stylesheet.warn_on_unused_selectors(this); } @@ -1414,7 +1416,16 @@ function process_component_options(component: Component, nodes) { component_options[name] = value; break; } + case 'shadowdom':{ + const code = 'invalid-shadowdom-attribute'; + const message = `'shadowdom' must be set to 'open', 'closed or 'none'`; + const value = get_value(attribute, code, message) + if(value != "open" && value != "none" && value != "closed") + component.error(attribute, { code, message }); + component_options[name] = value; + break; + } default: component.error(attribute, { code: `invalid-options-attribute`, diff --git a/src/compiler/compile/index.ts b/src/compiler/compile/index.ts index 12b161aeeb0e..5fe11fd2f5eb 100644 --- a/src/compiler/compile/index.ts +++ b/src/compiler/compile/index.ts @@ -22,6 +22,7 @@ const valid_options = [ 'hydratable', 'legacy', 'customElement', + 'shadowDom', 'tag', 'css', 'loopGuardTimeout', diff --git a/src/compiler/compile/render_dom/index.ts b/src/compiler/compile/render_dom/index.ts index 26fa4a70f81f..0a3857ee1aea 100644 --- a/src/compiler/compile/render_dom/index.ts +++ b/src/compiler/compile/render_dom/index.ts @@ -20,7 +20,7 @@ export default function dom( block.has_outro_method = true; // prevent fragment being created twice (#1063) - if (options.customElement) block.chunks.create.push(b`this.c = @noop;`); + if (options.customElement && options.shadowDom !== "none") block.chunks.create.push(b`this.c = @noop;`); const body = []; @@ -29,7 +29,7 @@ export default function dom( body.push(b`const ${renderer.file_var} = ${file};`); } - const css = component.stylesheet.render(options.filename, !options.customElement); + const css = component.stylesheet.render(options.filename, (!options.customElement || options.shadowDom === "none")); const styles = component.stylesheet.has_styles && options.dev ? `${css.code}\n/*# sourceMappingURL=${css.map.toUrl()} */` : css.code; @@ -37,9 +37,9 @@ export default function dom( const add_css = component.get_unique_name('add_css'); const should_add_css = ( - !options.customElement && + (!options.customElement && !!styles && - options.css !== false + options.css !== false ) || options.shadowDom === "none" ); if (should_add_css) { @@ -437,14 +437,19 @@ export default function dom( } if (options.customElement) { + const lightDom = options.shadowDom === 'none'; const declaration = b` class ${name} extends @SvelteElement { constructor(options) { super(); + ${!lightDom && b` + this._root =this.attachShadow({ mode: '${options.shadowDom}' }); + `} - ${css.code && b`this.shadowRoot.innerHTML = \`\`;`} + ${css.code && !lightDom && b`this._root.innerHTML = \`\`;`} + ${should_add_css && lightDom && b`if (!@_document.getElementById("${component.stylesheet.id}-style")) ${add_css}();`} - @init(this, { target: this.shadowRoot }, ${definition}, ${has_create_fragment ? 'create_fragment': 'null'}, ${not_equal}, ${prop_indexes}, ${dirty}); + @init(this, { target: ${lightDom ? 'this' : 'this._root'} }, ${definition}, ${has_create_fragment ? 'create_fragment': 'null'}, ${not_equal}, ${prop_indexes}, ${dirty}); ${dev_props_check} diff --git a/src/compiler/interfaces.ts b/src/compiler/interfaces.ts index a5e286462ff3..a7932615fa33 100644 --- a/src/compiler/interfaces.ts +++ b/src/compiler/interfaces.ts @@ -120,6 +120,7 @@ export interface CompileOptions { hydratable?: boolean; legacy?: boolean; customElement?: boolean; + shadowDom?: ShadowDomMode; tag?: string; css?: boolean; loopGuardTimeout?: number; @@ -166,4 +167,8 @@ export interface Var { export interface CssResult { code: string; map: SourceMap; -} \ No newline at end of file +} + +export type ShadowDomMode = 'none' + | 'open' + | 'closed' \ No newline at end of file diff --git a/src/runtime/internal/Component.ts b/src/runtime/internal/Component.ts index 10588a08046b..f8fbcf8ced2d 100644 --- a/src/runtime/internal/Component.ts +++ b/src/runtime/internal/Component.ts @@ -167,7 +167,6 @@ if (typeof HTMLElement === 'function') { $$: T$$; constructor() { super(); - this.attachShadow({ mode: 'open' }); } connectedCallback() { diff --git a/test/js/samples/css-shadow-dom-keyframes/expected.js b/test/js/samples/css-shadow-dom-keyframes/expected.js index a0a0ebe0211b..aa069dd46550 100644 --- a/test/js/samples/css-shadow-dom-keyframes/expected.js +++ b/test/js/samples/css-shadow-dom-keyframes/expected.js @@ -33,6 +33,7 @@ function create_fragment(ctx) { class Component extends SvelteElement { constructor(options) { super(); + this.attachShadow({ mode: "open" }); this.shadowRoot.innerHTML = ``; init(this, { target: this.shadowRoot }, null, create_fragment, safe_not_equal, {}); From 6b62b69c3400c3f8379770979f19a946926c6246 Mon Sep 17 00:00:00 2001 From: Cris Ward Date: Tue, 28 Jan 2020 07:40:38 +0000 Subject: [PATCH 2/5] fixed linting issues --- src/compiler/compile/Component.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/compiler/compile/Component.ts b/src/compiler/compile/Component.ts index 9ed74ea3a983..3dfb4fd275cf 100644 --- a/src/compiler/compile/Component.ts +++ b/src/compiler/compile/Component.ts @@ -1416,11 +1416,11 @@ function process_component_options(component: Component, nodes) { component_options[name] = value; break; } - case 'shadowdom':{ + case 'shadowdom': { const code = 'invalid-shadowdom-attribute'; const message = `'shadowdom' must be set to 'open', 'closed or 'none'`; - const value = get_value(attribute, code, message) - if(value != "open" && value != "none" && value != "closed") + const value = get_value(attribute, code, message); + if (value != "open" && value != "none" && value != "closed") component.error(attribute, { code, message }); component_options[name] = value; From 2ce702293e9a612d53b8d978406e51eb44ccb768 Mon Sep 17 00:00:00 2001 From: Cris Ward Date: Tue, 28 Jan 2020 07:47:11 +0000 Subject: [PATCH 3/5] fixed tests --- test/js/samples/css-shadow-dom-keyframes/expected.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/js/samples/css-shadow-dom-keyframes/expected.js b/test/js/samples/css-shadow-dom-keyframes/expected.js index aa069dd46550..e05a7c02412a 100644 --- a/test/js/samples/css-shadow-dom-keyframes/expected.js +++ b/test/js/samples/css-shadow-dom-keyframes/expected.js @@ -33,9 +33,9 @@ function create_fragment(ctx) { class Component extends SvelteElement { constructor(options) { super(); - this.attachShadow({ mode: "open" }); - this.shadowRoot.innerHTML = ``; - init(this, { target: this.shadowRoot }, null, create_fragment, safe_not_equal, {}); + this._root = this.attachShadow({ mode: "open" }); + this._root.innerHTML = ``; + init(this, { target: this._root }, null, create_fragment, safe_not_equal, {}); if (options) { if (options.target) { From d21d020648b5ee025fc79b339939319529f28bf3 Mon Sep 17 00:00:00 2001 From: Cris Ward Date: Tue, 28 Jan 2020 13:06:00 +0000 Subject: [PATCH 4/5] Added tests for shadoom and css with none --- .../samples/shadowdom-closed/main.svelte | 8 ++++++++ .../samples/shadowdom-closed/test.js | 13 +++++++++++++ .../samples/shadowdom-none-css/main.svelte | 10 ++++++++++ .../samples/shadowdom-none-css/test.js | 10 ++++++++++ .../samples/shadowdom-none/main.svelte | 8 ++++++++ test/custom-elements/samples/shadowdom-none/test.js | 12 ++++++++++++ 6 files changed, 61 insertions(+) create mode 100644 test/custom-elements/samples/shadowdom-closed/main.svelte create mode 100644 test/custom-elements/samples/shadowdom-closed/test.js create mode 100644 test/custom-elements/samples/shadowdom-none-css/main.svelte create mode 100644 test/custom-elements/samples/shadowdom-none-css/test.js create mode 100644 test/custom-elements/samples/shadowdom-none/main.svelte create mode 100644 test/custom-elements/samples/shadowdom-none/test.js diff --git a/test/custom-elements/samples/shadowdom-closed/main.svelte b/test/custom-elements/samples/shadowdom-closed/main.svelte new file mode 100644 index 000000000000..43c2300bc04f --- /dev/null +++ b/test/custom-elements/samples/shadowdom-closed/main.svelte @@ -0,0 +1,8 @@ + + + + +

Hello {name}!

+ diff --git a/test/custom-elements/samples/shadowdom-closed/test.js b/test/custom-elements/samples/shadowdom-closed/test.js new file mode 100644 index 000000000000..be2b9743e0c9 --- /dev/null +++ b/test/custom-elements/samples/shadowdom-closed/test.js @@ -0,0 +1,13 @@ +import * as assert from 'assert'; +import './main.svelte'; + +export default function (target) { + target.innerHTML = ''; + const el = target.querySelector('custom-element'); + + assert.equal(el.name, 'world'); + + const h1 = el._root.querySelector('h1'); + assert.equal(h1.textContent, 'Hello world'); + assert.equal(el.shadowRoot, null); +} \ No newline at end of file diff --git a/test/custom-elements/samples/shadowdom-none-css/main.svelte b/test/custom-elements/samples/shadowdom-none-css/main.svelte new file mode 100644 index 000000000000..20061da3cbb1 --- /dev/null +++ b/test/custom-elements/samples/shadowdom-none-css/main.svelte @@ -0,0 +1,10 @@ + + + + +

Hello World

+ diff --git a/test/custom-elements/samples/shadowdom-none-css/test.js b/test/custom-elements/samples/shadowdom-none-css/test.js new file mode 100644 index 000000000000..9f6ec3c20ef6 --- /dev/null +++ b/test/custom-elements/samples/shadowdom-none-css/test.js @@ -0,0 +1,10 @@ +import * as assert from 'assert'; +import './main.svelte'; + +export default function (target) { + target.innerHTML = ''; + const el = target.querySelector('custom-element'); + const h1 = el.querySelector('h1'); + const colour = getComputedStyle(h1).color; + assert.equal(colour,"rgb(255, 0, 0)"); +} \ No newline at end of file diff --git a/test/custom-elements/samples/shadowdom-none/main.svelte b/test/custom-elements/samples/shadowdom-none/main.svelte new file mode 100644 index 000000000000..79a40384ec03 --- /dev/null +++ b/test/custom-elements/samples/shadowdom-none/main.svelte @@ -0,0 +1,8 @@ + + + + +

Hello {name}!

+ diff --git a/test/custom-elements/samples/shadowdom-none/test.js b/test/custom-elements/samples/shadowdom-none/test.js new file mode 100644 index 000000000000..7cd82a30eeec --- /dev/null +++ b/test/custom-elements/samples/shadowdom-none/test.js @@ -0,0 +1,12 @@ +import * as assert from 'assert'; +import './main.svelte'; + +export default function (target) { + target.innerHTML = ''; + const el = target.querySelector('custom-element'); + + assert.equal(el.name, 'world'); + + const h1 = el.querySelector('h1'); + assert.equal(h1.textContent, 'Hello world!'); +} \ No newline at end of file From cf732809d6a1e0d2ae0b334cb463b01b22ece3d6 Mon Sep 17 00:00:00 2001 From: Cris Ward Date: Tue, 28 Jan 2020 13:11:08 +0000 Subject: [PATCH 5/5] fixed test --- test/custom-elements/samples/shadowdom-closed/test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/custom-elements/samples/shadowdom-closed/test.js b/test/custom-elements/samples/shadowdom-closed/test.js index be2b9743e0c9..cc0ed2ff8ed6 100644 --- a/test/custom-elements/samples/shadowdom-closed/test.js +++ b/test/custom-elements/samples/shadowdom-closed/test.js @@ -8,6 +8,6 @@ export default function (target) { assert.equal(el.name, 'world'); const h1 = el._root.querySelector('h1'); - assert.equal(h1.textContent, 'Hello world'); + assert.equal(h1.textContent, 'Hello world!'); assert.equal(el.shadowRoot, null); } \ No newline at end of file