Skip to content

Commit

Permalink
support rendering components in a shadow dom (#5870)
Browse files Browse the repository at this point in the history
  • Loading branch information
ivanhofer authored Jul 21, 2021
1 parent a644818 commit 5cfefeb
Show file tree
Hide file tree
Showing 11 changed files with 77 additions and 37 deletions.
2 changes: 1 addition & 1 deletion site/content/docs/03-run-time.md
Original file line number Diff line number Diff line change
Expand Up @@ -949,7 +949,7 @@ The following initialisation options can be provided:

| 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
| `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
Expand Down
22 changes: 14 additions & 8 deletions src/compiler/compile/render_dom/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,11 +50,8 @@ export default function dom(

if (should_add_css) {
body.push(b`
function ${add_css}() {
var style = @element("style");
style.id = "${component.stylesheet.id}-style";
style.textContent = "${styles}";
@append(@_document.head, style);
function ${add_css}(target) {
@append_styles(target, "${component.stylesheet.id}", "${styles}");
}
`);
}
Expand Down Expand Up @@ -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>\`;`}
@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}
Expand Down Expand Up @@ -533,12 +530,21 @@ export default function dom(
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`
class ${name} extends ${superclass} {
constructor(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}, ${dirty});
@init(this, options, ${definition}, ${has_create_fragment ? 'create_fragment' : 'null'}, ${not_equal}, ${prop_indexes}, ${optional_parameters});
${options.dev && b`@dispatch_dev("SvelteRegisterComponent", { component: this, tagName: "${name.name}", options, id: create_fragment.name });`}
${dev_props_check}
Expand Down
8 changes: 6 additions & 2 deletions src/runtime/internal/Component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ interface T$$ {
on_destroy: any[];
skip_bound: boolean;
on_disconnect: any[];
root:Element|ShadowRoot
}

export function bind(component, name, callback) {
Expand Down Expand Up @@ -103,7 +104,7 @@ function make_dirty(component, i) {
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;
set_current_component(component);

Expand All @@ -128,9 +129,12 @@ export function init(component, options, instance, create_fragment, not_equal, p
// everything else
callbacks: blank_object(),
dirty,
skip_bound: false
skip_bound: false,
root: options.target || parent_component.$$.root
};

append_styles && append_styles($$.root);

let ready = false;

$$.ctx = instance
Expand Down
2 changes: 1 addition & 1 deletion src/runtime/internal/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ export interface SvelteComponentDev {
[accessor: string]: any;
}
interface IComponentOptions<Props extends Record<string, any> = Record<string, any>> {
target: Element;
target: Element|ShadowRoot;
anchor?: Element;
props?: Props;
context?: Map<any, any>;
Expand Down
36 changes: 36 additions & 0 deletions src/runtime/internal/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
if (is_hydrating) {
init_hydrate(target);
Expand Down
6 changes: 3 additions & 3 deletions src/runtime/internal/style_manager.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { element } from './dom';
import { append_empty_stylesheet, get_root_for_node } from './dom';
import { raf } from './environment';

interface ExtendedDoc extends Document {
Expand Down Expand Up @@ -29,9 +29,9 @@ export function create_rule(node: Element & ElementCSSInlineStyle, a: number, b:

const rule = keyframes + `100% {${fn(b, 1 - b)}}\n}`;
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);
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 = {});

if (!current_rules[name]) {
Expand Down
2 changes: 1 addition & 1 deletion src/runtime/internal/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
? assign($$scope.ctx.slice(), definition[1](fn(ctx)))
: $$scope.ctx;
Expand Down
13 changes: 5 additions & 8 deletions test/js/samples/collapses-text-around-comments/expected.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
import {
SvelteComponent,
append,
append_styles,
attr,
detach,
element,
Expand All @@ -13,11 +14,8 @@ import {
text
} from "svelte/internal";

function add_css() {
var style = element("style");
style.id = "svelte-1a7i8ec-style";
style.textContent = "p.svelte-1a7i8ec{color:red}";
append(document.head, style);
function add_css(target) {
append_styles(target, "svelte-1a7i8ec", "p.svelte-1a7i8ec{color:red}");
}

function create_fragment(ctx) {
Expand Down Expand Up @@ -58,9 +56,8 @@ function instance($$self, $$props, $$invalidate) {
class Component extends SvelteComponent {
constructor(options) {
super();
if (!document.getElementById("svelte-1a7i8ec-style")) add_css();
init(this, options, instance, create_fragment, safe_not_equal, { foo: 0 });
init(this, options, instance, create_fragment, safe_not_equal, { foo: 0 }, add_css);
}
}

export default Component;
export default Component;
14 changes: 5 additions & 9 deletions test/js/samples/css-media-query/expected.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* generated by Svelte vX.Y.Z */
import {
SvelteComponent,
append,
append_styles,
attr,
detach,
element,
Expand All @@ -11,11 +11,8 @@ import {
safe_not_equal
} from "svelte/internal";

function add_css() {
var style = element("style");
style.id = "svelte-1slhpfn-style";
style.textContent = "@media(min-width: 1px){div.svelte-1slhpfn{color:red}}";
append(document.head, style);
function add_css(target) {
append_styles(target, "svelte-1slhpfn", "@media(min-width: 1px){div.svelte-1slhpfn{color:red}}");
}

function create_fragment(ctx) {
Expand All @@ -41,9 +38,8 @@ function create_fragment(ctx) {
class Component extends SvelteComponent {
constructor(options) {
super();
if (!document.getElementById("svelte-1slhpfn-style")) add_css();
init(this, options, null, create_fragment, safe_not_equal, {});
init(this, options, null, create_fragment, safe_not_equal, {}, add_css);
}
}

export default Component;
export default Component;
5 changes: 3 additions & 2 deletions test/js/samples/css-shadow-dom-keyframes/expected.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ class Component extends SvelteElement {
null,
create_fragment,
safe_not_equal,
{}
{},
null
);

if (options) {
Expand All @@ -58,4 +59,4 @@ class Component extends SvelteElement {
}

customElements.define("custom-element", Component);
export default Component;
export default Component;
4 changes: 2 additions & 2 deletions test/sourcemaps/samples/compile-option-dev/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ const b64dec = s => Buffer.from(s, 'base64').toString();

export async function test({ assert, css, js }) {

// 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/);
// We check that the css source map embedded in the js is accurate
const match = js.code.match(/\tappend_styles\(target, "svelte-.{6}", "(.*?)(?:\\n\/\*# sourceMappingURL=data:(.*?);charset=(.*?);base64,(.*?) \*\/)?"\);\n/);
assert.notEqual(match, null);

const [mimeType, encoding, cssMapBase64] = match.slice(2);
Expand Down

0 comments on commit 5cfefeb

Please sign in to comment.