Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Customisable target to append the styles tag #3940

Closed
asinglebit opened this issue Nov 16, 2019 · 7 comments
Closed

Customisable target to append the styles tag #3940

asinglebit opened this issue Nov 16, 2019 · 7 comments

Comments

@asinglebit
Copy link

asinglebit commented Nov 16, 2019

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 :)

@asinglebit asinglebit changed the title Custom root to append the styles tag Customisable target to append the styles tag Nov 16, 2019
@TehShrike
Copy link
Member

I'm also attempting to introduce Svelte into a codebase that uses custom elements/shadow DOM extensively.

I ran into the same issue with the <style> element being appended to document.head without any way to redirect it to the relevant shadow root.

Right now Svelte-constructed components that include the CSS in the JS aren't usable inside of a shadow root, but that use case feels like it should be supported somehow.

@TehShrike
Copy link
Member

The "custom element" tag might not be appropriate for this issue? Since this issue is a suggestion about how to change the vanilla JS component constructor interface to make it usable inside of external custom elements, as opposed to making any change to the Svelte compiler's CE output

TehShrike pushed a commit to EquipmentShare/date-range-input that referenced this issue Feb 19, 2020
This will let me work around some shadow DOM unfortunateness: sveltejs/svelte#3940
@ivanhofer
Copy link
Contributor

Is this issue something the svelte-team would consider implementing?

I would need the option to render my svelte-application inside a shadowDom and attach the styles inside the shadow root.
My svelte-application can be injected into any kind of website and would really need the style-encapsulation.
Currently I'm using CSS selectors with a high specificity combined with unique class-names and prefixes e.g. n:text and some reset styles in my root component. I also added !important in the end of every selector. With all that effort it can still happen, that some style from the Website will be applied to my svelte-application. Every few weeks I find another thing, where a reset-style is missing or the specificity of my selectors are not good enough. e.g a Website has following styles #some-id #some-other-id button:hover { background-image: url( ... ); } (it is bad CSS, but happens quite a few times). I then would need to add a reset style for exact that selector and style-attribute.

If I could choose, where the styles should be applied, it would be a lot easier to maintain my project.

PS: I don't have the option to write the CSS to a file and inject it into the shadowDom. I need to bundle everything into one JS file.

@i8ramin
Copy link

i8ramin commented Apr 5, 2021

hi any updates on this?

@ivanhofer
Copy link
Contributor

@i8ramin If you need to render your svelte-application inside a shadow dom, you can checkout this PR: #5870

@stale
Copy link

stale bot commented Jul 5, 2021

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

@stale stale bot added the stale-bot label Jul 5, 2021
@dummdidumm
Copy link
Member

Closing as duplicate of #5869 , PR #5870 should fix this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants