diff --git a/site/content/docs/03-run-time.md b/site/content/docs/03-run-time.md index 386c18d5fd75..d07586ce4b76 100644 --- a/site/content/docs/03-run-time.md +++ b/site/content/docs/03-run-time.md @@ -906,6 +906,7 @@ The following initialisation options can be provided: | `props` | `{}` | An object of properties to supply to the component | `hydrate` | `false` | See below | `intro` | `false` | If `true`, will play transitions on initial render, rather than waiting for subsequent state changes +| `customStyleTag` | `document.head` | An `HTMLElement` the styles should be appended to. It will only work if the component was compiled with the [`css: true` option](docs#svelte_compile). Existing children of `target` are left where they are. diff --git a/src/compiler/compile/render_dom/index.ts b/src/compiler/compile/render_dom/index.ts index fe1ee368fcd8..e204858df4f5 100644 --- a/src/compiler/compile/render_dom/index.ts +++ b/src/compiler/compile/render_dom/index.ts @@ -49,11 +49,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}(options) { + @append_styles(options, "${component.stylesheet.id.replace('svelte-', '')}", "${styles}"); } `); } @@ -536,8 +533,10 @@ export default function dom( 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}); + + ${should_add_css && b`${add_css}(options);`} + + @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 });`} ${dev_props_check} diff --git a/src/runtime/internal/Component.ts b/src/runtime/internal/Component.ts index c503a507ffd2..73448c13133c 100644 --- a/src/runtime/internal/Component.ts +++ b/src/runtime/internal/Component.ts @@ -34,6 +34,7 @@ interface T$$ { on_mount: any[]; on_destroy: any[]; skip_bound: boolean; + customStyleTag?: HTMLElement; } export function bind(component, name, callback) { diff --git a/src/runtime/internal/dev.ts b/src/runtime/internal/dev.ts index 74bf581bab51..41a78528c560 100644 --- a/src/runtime/internal/dev.ts +++ b/src/runtime/internal/dev.ts @@ -117,7 +117,8 @@ export class SvelteComponentDev extends SvelteComponent { $$prop_def: Props; constructor(options: { - target: Element; + target: Element | ShadowRoot; + customStyleTag: Element | ShadowRoot; anchor?: Element; props?: Props; hydrate?: boolean; @@ -216,7 +217,8 @@ export class SvelteComponentTyped< $$slot_def: Slots; constructor(options: { - target: Element; + target: Element | ShadowRoot; + customStyleTag: Element | ShadowRoot; anchor?: Element; props?: Props; hydrate?: boolean; diff --git a/src/runtime/internal/dom.ts b/src/runtime/internal/dom.ts index 91e575ebf630..69cd8ed5e479 100644 --- a/src/runtime/internal/dom.ts +++ b/src/runtime/internal/dom.ts @@ -1,5 +1,27 @@ import { has_prop } from './utils'; +let appendStylesTo = document.head; + +export function append_styles( + { customStyleTag }, + styleSheetId: string, + styles: string, + styleId:string = `svelte-${styleSheetId}-style`) { + + if (customStyleTag) appendStylesTo = customStyleTag; + + if (!appendStylesTo.querySelector('#' + styleId)) { + const style = element('style'); + style.id = styleId; + style.textContent = styles; + append(appendStylesTo, style); + } +} + +export function append_empty_stylesheet() { + return appendStylesTo.appendChild(element('style') as HTMLStyleElement); +} + export function append(target: Node, node: Node) { target.appendChild(node); } diff --git a/src/runtime/internal/style_manager.ts b/src/runtime/internal/style_manager.ts index 8060e65a5d3f..4e16bdb3cf78 100644 --- a/src/runtime/internal/style_manager.ts +++ b/src/runtime/internal/style_manager.ts @@ -1,4 +1,4 @@ -import { element } from './dom'; +import { append_empty_stylesheet } from './dom'; import { raf } from './environment'; interface ExtendedDoc extends Document { @@ -31,7 +31,7 @@ export function create_rule(node: Element & ElementCSSInlineStyle, a: number, b: const name = `__svelte_${hash(rule)}_${uid}`; const doc = node.ownerDocument 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().sheet as CSSStyleSheet); const current_rules = doc.__svelte_rules || (doc.__svelte_rules = {}); if (!current_rules[name]) { diff --git a/test/js/samples/collapses-text-around-comments/expected.js b/test/js/samples/collapses-text-around-comments/expected.js index 67335ce2469e..e3e000094ff7 100644 --- a/test/js/samples/collapses-text-around-comments/expected.js +++ b/test/js/samples/collapses-text-around-comments/expected.js @@ -2,6 +2,7 @@ import { SvelteComponent, append, + append_styles, attr, detach, element, @@ -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(options) { + append_styles(options, "1a7i8ec", "p.svelte-1a7i8ec{color:red}"); } function create_fragment(ctx) { @@ -58,7 +56,7 @@ function instance($$self, $$props, $$invalidate) { class Component extends SvelteComponent { constructor(options) { super(); - if (!document.getElementById("svelte-1a7i8ec-style")) add_css(); + add_css(options); init(this, options, instance, create_fragment, safe_not_equal, { foo: 0 }); } } diff --git a/test/js/samples/css-media-query/expected.js b/test/js/samples/css-media-query/expected.js index f4776700599d..749c692aa143 100644 --- a/test/js/samples/css-media-query/expected.js +++ b/test/js/samples/css-media-query/expected.js @@ -1,7 +1,7 @@ /* generated by Svelte vX.Y.Z */ import { SvelteComponent, - append, + append_styles, attr, detach, element, @@ -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(options) { + append_styles(options, "1slhpfn", "@media(min-width: 1px){div.svelte-1slhpfn{color:red}}"); } function create_fragment(ctx) { @@ -41,7 +38,7 @@ function create_fragment(ctx) { class Component extends SvelteComponent { constructor(options) { super(); - if (!document.getElementById("svelte-1slhpfn-style")) add_css(); + add_css(options); init(this, options, null, create_fragment, safe_not_equal, {}); } } diff --git a/test/sourcemaps/samples/compile-option-dev/test.js b/test/sourcemaps/samples/compile-option-dev/test.js index bf240a5a8927..750a6afc640d 100644 --- a/test/sourcemaps/samples/compile-option-dev/test.js +++ b/test/sourcemaps/samples/compile-option-dev/test.js @@ -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\(options, ".{6}", "(.*?)(?:\\n\/\*# sourceMappingURL=data:(.*?);charset=(.*?);base64,(.*?) \*\/)?"\);\n/); assert.notEqual(match, null); const [mimeType, encoding, cssMapBase64] = match.slice(2);