Skip to content

Customisable target to append the styles tag #3940

@asinglebit

Description

@asinglebit

We are aiming to use Svelte with compilation to custom-elements in our company. However it feels like custom elements compilation is rather limited at the moment - there are multiple issues, including:

  1. Not being able to use :global with styles properly, to avoid stylesheet trimming
  2. Not being able to create an output, where the root component is a custom element, and nested components are usual Svelte components. This is needed to have only one shadow-dom per Svelte app, trapped in a web component. This feature severely lacks documentation. When the compiler is used to traverse such component declarations, the output lacks styles.
  3. When using a custom wrapper for a Svelte app that is not compiled into custom-elements, the styles always get appended to the tag of the dom. This results in poor encapsulation of styles when the custom wrapper is utilising shadow-dom.

My proposal is to deliver a pragmatic fix to the third issue, so that people can successfully trap Svelte apps in web-components with shadow-dom with little effort and reliance on the black-box compiler.

I pretty much had only one weekend to try and read into the compiler code and solve the issue, to secure the future use of Svelte in our company. This is what can be done:

We need to change the definiton of generated function named "add_css", adding an argument, named customStyleTag, and using it before @_document.head:

function ${add_css}(customStyleTag) {
	var style = @element("style");
	style.id = '${component.stylesheet.id}-style';
	style.textContent = ${styles};
	@append(customStyleTag || @_document.head, style);
}

Also, the generated definition of the component class needs to be altered:

  1. By saving the passed anchor element from component options, customStyleTag property somewhere (I chose the parent component class, due to not finding a better place in the limited time I had).
  2. Passing the customStyleTag property as an argument to the generated add_css function call.
const superclass = options.dev ? 'SvelteComponentDev' : 'SvelteComponent';
    builder.add_block(deindent `
	class ${name} extends @${superclass} {
		constructor(options) {
			super(${options.dev && `options`});
			if (options.customStyleTag) {
				${superclass}.customStyleTag = ${superclass}.customStyleTag || options.customStyleTag;
			};
			${should_add_css && `if (!@_document.getElementById("${component.stylesheet.id}-style")) ${add_css}(${superclass}.customStyleTag);`}
			@init(this, options, ${definition}, create_fragment, ${not_equal}, ${prop_names});
			${options.dev && `@dispatch_dev("SvelteRegisterComponent", { component: this, tagName: "${name}", options, id: create_fragment.name });`}
			${dev_props_check}
		}
		${body.length > 0 && body.join('\n\n')}
	}
`);

This will allow to specify the root Svelte component in the following manner:

import MySvelteComponent from './my-svelte-component.svelte';
class MySvelteComponentWrapper extends HTMLElement {
	constructor() {
		super();
		const shadow = this.attachShadow({ mode: "open" });
		const root = document.createElement('div');
		shadow.appendChild(root);
		const app = new MySvelteComponent({
			target: root,
			customStyleTag: root
		});
	}
}

customElements.define("my-svelte-component", MySvelteComponentWrapper)

and have the styles trapped in the shadow-dom properly.

Not being able to use Svelte app as a custom element is pretty much a deal breaker for our company. We have considered all of the documented approaches but each one of them lacks functionality to be considered seriously.

PS. Yes, I feel that this might be a hacky way of doing things, especially considering the fact that Im not familiar with the codebase, conventions, etc. However this is a deal breaker, and we really want to use Svelte in production and give it a warm welcome to our codebase.

Thanks for reading through :)

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions