support rendering components in a shadow dom (#5870)

pull/6560/head
Hofer Ivan 3 years ago committed by GitHub
parent a6448185d6
commit 5cfefeb6e7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -949,7 +949,7 @@ The following initialisation options can be provided:
| option | default | description | | option | default | description |
| --- | --- | --- | | --- | --- | --- |
| `target` | **none** | An `HTMLElement` to render to. This option is required | `target` | **none** | An `HTMLElement` or `ShadowRoot` to render to. This option is required
| `anchor` | `null` | A child of `target` to render the component immediately before | `anchor` | `null` | A child of `target` to render the component immediately before
| `props` | `{}` | An object of properties to supply to the component | `props` | `{}` | An object of properties to supply to the component
| `context` | `new Map()` | A `Map` of root-level context key-value pairs to supply to the component | `context` | `new Map()` | A `Map` of root-level context key-value pairs to supply to the component

@ -50,11 +50,8 @@ export default function dom(
if (should_add_css) { if (should_add_css) {
body.push(b` body.push(b`
function ${add_css}() { function ${add_css}(target) {
var style = @element("style"); @append_styles(target, "${component.stylesheet.id}", "${styles}");
style.id = "${component.stylesheet.id}-style";
style.textContent = "${styles}";
@append(@_document.head, style);
} }
`); `);
} }
@ -486,7 +483,7 @@ export default function dom(
${css.code && b`this.shadowRoot.innerHTML = \`<style>${css.code.replace(/\\/g, '\\\\')}${options.dev ? `\n/*# sourceMappingURL=${css.map.toUrl()} */` : ''}</style>\`;`} ${css.code && b`this.shadowRoot.innerHTML = \`<style>${css.code.replace(/\\/g, '\\\\')}${options.dev ? `\n/*# sourceMappingURL=${css.map.toUrl()} */` : ''}</style>\`;`}
@init(this, { target: this.shadowRoot, props: ${init_props}, customElement: true }, ${definition}, ${has_create_fragment ? 'create_fragment' : 'null'}, ${not_equal}, ${prop_indexes}, ${dirty}); @init(this, { target: this.shadowRoot, props: ${init_props}, customElement: true }, ${definition}, ${has_create_fragment ? 'create_fragment' : 'null'}, ${not_equal}, ${prop_indexes}, null, ${dirty});
${dev_props_check} ${dev_props_check}
@ -533,12 +530,21 @@ export default function dom(
name: options.dev ? '@SvelteComponentDev' : '@SvelteComponent' name: options.dev ? '@SvelteComponentDev' : '@SvelteComponent'
}; };
const optional_parameters = [];
if (should_add_css) {
optional_parameters.push(add_css);
} else if (dirty) {
optional_parameters.push(x`null`);
}
if (dirty) {
optional_parameters.push(dirty);
}
const declaration = b` const declaration = b`
class ${name} extends ${superclass} { class ${name} extends ${superclass} {
constructor(options) { constructor(options) {
super(${options.dev && 'options'}); super(${options.dev && 'options'});
${should_add_css && b`if (!@_document.getElementById("${component.stylesheet.id}-style")) ${add_css}();`} @init(this, options, ${definition}, ${has_create_fragment ? 'create_fragment' : 'null'}, ${not_equal}, ${prop_indexes}, ${optional_parameters});
@init(this, options, ${definition}, ${has_create_fragment ? 'create_fragment' : 'null'}, ${not_equal}, ${prop_indexes}, ${dirty});
${options.dev && b`@dispatch_dev("SvelteRegisterComponent", { component: this, tagName: "${name.name}", options, id: create_fragment.name });`} ${options.dev && b`@dispatch_dev("SvelteRegisterComponent", { component: this, tagName: "${name.name}", options, id: create_fragment.name });`}
${dev_props_check} ${dev_props_check}

@ -38,6 +38,7 @@ interface T$$ {
on_destroy: any[]; on_destroy: any[];
skip_bound: boolean; skip_bound: boolean;
on_disconnect: any[]; on_disconnect: any[];
root:Element|ShadowRoot
} }
export function bind(component, name, callback) { export function bind(component, name, callback) {
@ -103,7 +104,7 @@ function make_dirty(component, i) {
component.$$.dirty[(i / 31) | 0] |= (1 << (i % 31)); component.$$.dirty[(i / 31) | 0] |= (1 << (i % 31));
} }
export function init(component, options, instance, create_fragment, not_equal, props, dirty = [-1]) { export function init(component, options, instance, create_fragment, not_equal, props, append_styles, dirty = [-1]) {
const parent_component = current_component; const parent_component = current_component;
set_current_component(component); set_current_component(component);
@ -128,9 +129,12 @@ export function init(component, options, instance, create_fragment, not_equal, p
// everything else // everything else
callbacks: blank_object(), callbacks: blank_object(),
dirty, dirty,
skip_bound: false skip_bound: false,
root: options.target || parent_component.$$.root
}; };
append_styles && append_styles($$.root);
let ready = false; let ready = false;
$$.ctx = instance $$.ctx = instance

@ -105,7 +105,7 @@ export interface SvelteComponentDev {
[accessor: string]: any; [accessor: string]: any;
} }
interface IComponentOptions<Props extends Record<string, any> = Record<string, any>> { interface IComponentOptions<Props extends Record<string, any> = Record<string, any>> {
target: Element; target: Element|ShadowRoot;
anchor?: Element; anchor?: Element;
props?: Props; props?: Props;
context?: Map<any, any>; context?: Map<any, any>;

@ -125,6 +125,42 @@ function init_hydrate(target: NodeEx) {
} }
} }
export function append_styles(
target: Node,
style_sheet_id: string,
styles: string
) {
const append_styles_to = get_root_for_styles(target);
if (!append_styles_to?.getElementById(style_sheet_id)) {
const style = element('style');
style.id = style_sheet_id;
style.textContent = styles;
append_stylesheet(append_styles_to, style);
}
}
export function get_root_for_node(node: Node) {
if (!node) return document;
return (node.getRootNode ? node.getRootNode() : node.ownerDocument); // check for getRootNode because IE is still supported
}
function get_root_for_styles(node: Node) {
const root = get_root_for_node(node);
return (root as ShadowRoot).host ? root as ShadowRoot : root as Document;
}
export function append_empty_stylesheet(node: Node) {
const style_element = element('style') as HTMLStyleElement;
append_stylesheet(get_root_for_styles(node), style_element);
return style_element;
}
function append_stylesheet(node: ShadowRoot | Document, style: HTMLStyleElement) {
append((node as Document).head || node, style);
}
export function append(target: NodeEx, node: NodeEx) { export function append(target: NodeEx, node: NodeEx) {
if (is_hydrating) { if (is_hydrating) {
init_hydrate(target); init_hydrate(target);

@ -1,4 +1,4 @@
import { element } from './dom'; import { append_empty_stylesheet, get_root_for_node } from './dom';
import { raf } from './environment'; import { raf } from './environment';
interface ExtendedDoc extends Document { interface ExtendedDoc extends Document {
@ -29,9 +29,9 @@ export function create_rule(node: Element & ElementCSSInlineStyle, a: number, b:
const rule = keyframes + `100% {${fn(b, 1 - b)}}\n}`; const rule = keyframes + `100% {${fn(b, 1 - b)}}\n}`;
const name = `__svelte_${hash(rule)}_${uid}`; const name = `__svelte_${hash(rule)}_${uid}`;
const doc = node.ownerDocument as ExtendedDoc; const doc = get_root_for_node(node) as unknown as ExtendedDoc;
active_docs.add(doc); active_docs.add(doc);
const stylesheet = doc.__svelte_stylesheet || (doc.__svelte_stylesheet = doc.head.appendChild(element('style') as HTMLStyleElement).sheet as CSSStyleSheet); const stylesheet = doc.__svelte_stylesheet || (doc.__svelte_stylesheet = append_empty_stylesheet(node).sheet as CSSStyleSheet);
const current_rules = doc.__svelte_rules || (doc.__svelte_rules = {}); const current_rules = doc.__svelte_rules || (doc.__svelte_rules = {});
if (!current_rules[name]) { if (!current_rules[name]) {

@ -89,7 +89,7 @@ export function create_slot(definition, ctx, $$scope, fn) {
} }
} }
export function get_slot_context(definition, ctx, $$scope, fn) { function get_slot_context(definition, ctx, $$scope, fn) {
return definition[1] && fn return definition[1] && fn
? assign($$scope.ctx.slice(), definition[1](fn(ctx))) ? assign($$scope.ctx.slice(), definition[1](fn(ctx)))
: $$scope.ctx; : $$scope.ctx;

@ -2,6 +2,7 @@
import { import {
SvelteComponent, SvelteComponent,
append, append,
append_styles,
attr, attr,
detach, detach,
element, element,
@ -13,11 +14,8 @@ import {
text text
} from "svelte/internal"; } from "svelte/internal";
function add_css() { function add_css(target) {
var style = element("style"); append_styles(target, "svelte-1a7i8ec", "p.svelte-1a7i8ec{color:red}");
style.id = "svelte-1a7i8ec-style";
style.textContent = "p.svelte-1a7i8ec{color:red}";
append(document.head, style);
} }
function create_fragment(ctx) { function create_fragment(ctx) {
@ -58,9 +56,8 @@ function instance($$self, $$props, $$invalidate) {
class Component extends SvelteComponent { class Component extends SvelteComponent {
constructor(options) { constructor(options) {
super(); super();
if (!document.getElementById("svelte-1a7i8ec-style")) add_css(); init(this, options, instance, create_fragment, safe_not_equal, { foo: 0 }, add_css);
init(this, options, instance, create_fragment, safe_not_equal, { foo: 0 });
} }
} }
export default Component; export default Component;

@ -1,7 +1,7 @@
/* generated by Svelte vX.Y.Z */ /* generated by Svelte vX.Y.Z */
import { import {
SvelteComponent, SvelteComponent,
append, append_styles,
attr, attr,
detach, detach,
element, element,
@ -11,11 +11,8 @@ import {
safe_not_equal safe_not_equal
} from "svelte/internal"; } from "svelte/internal";
function add_css() { function add_css(target) {
var style = element("style"); append_styles(target, "svelte-1slhpfn", "@media(min-width: 1px){div.svelte-1slhpfn{color:red}}");
style.id = "svelte-1slhpfn-style";
style.textContent = "@media(min-width: 1px){div.svelte-1slhpfn{color:red}}";
append(document.head, style);
} }
function create_fragment(ctx) { function create_fragment(ctx) {
@ -41,9 +38,8 @@ function create_fragment(ctx) {
class Component extends SvelteComponent { class Component extends SvelteComponent {
constructor(options) { constructor(options) {
super(); super();
if (!document.getElementById("svelte-1slhpfn-style")) add_css(); init(this, options, null, create_fragment, safe_not_equal, {}, add_css);
init(this, options, null, create_fragment, safe_not_equal, {});
} }
} }
export default Component; export default Component;

@ -46,7 +46,8 @@ class Component extends SvelteElement {
null, null,
create_fragment, create_fragment,
safe_not_equal, safe_not_equal,
{} {},
null
); );
if (options) { if (options) {
@ -58,4 +59,4 @@ class Component extends SvelteElement {
} }
customElements.define("custom-element", Component); customElements.define("custom-element", Component);
export default Component; export default Component;

@ -4,8 +4,8 @@ const b64dec = s => Buffer.from(s, 'base64').toString();
export async function test({ assert, css, js }) { export async function test({ assert, css, js }) {
// We check that the css source map embedded in the js is accurate // We check that the css source map embedded in the js is accurate
const match = js.code.match(/\tstyle\.textContent = "(.*?)(?:\\n\/\*# sourceMappingURL=data:(.*?);charset=(.*?);base64,(.*?) \*\/)?";\n/); const match = js.code.match(/\tappend_styles\(target, "svelte-.{6}", "(.*?)(?:\\n\/\*# sourceMappingURL=data:(.*?);charset=(.*?);base64,(.*?) \*\/)?"\);\n/);
assert.notEqual(match, null); assert.notEqual(match, null);
const [mimeType, encoding, cssMapBase64] = match.slice(2); const [mimeType, encoding, cssMapBase64] = match.slice(2);

Loading…
Cancel
Save