diff --git a/text/0000-styled-customelement-generators.md b/text/0000-styled-customelement-generators.md new file mode 100644 index 0000000..1e87cd2 --- /dev/null +++ b/text/0000-styled-customelement-generators.md @@ -0,0 +1,108 @@ +- Start Date: 2020-08-29 +- RFC PR: (leave this empty) +- Svelte Issue: (leave this empty) + +# Styled `customElement` generators + +## Summary + +Adds an additional named export to the `customElement` modules generated by the compiler, named `createStyledElement`. + +This generator function allows third party consumers of Svelte-generated component ESModules to dynamically create their own variants of components with different CSS. + +## Motivation + +> Why are we doing this? What use cases does it support? What is the expected +outcome? + +It is currently impossible to cleanly override a compiled `SvelteElement`'s styles without first having its default bundled CSS applied to the component. + +Additionally, the order of operations in `svelte/internal.init` is such that component initialisation logic is fired *after* applying the bundled styles, but *before* the consumer of the module has any time to intervene and override them. This includes `before_update` hooks, transitions and component mount callbacks. For init behaviours which require interrogating the computed stylesheet of the component, this presents problems for inexperienced developers which can lead to workarounds such as `setTimeout` or `requestAnimationFrame`. + +This is particularly relevant for developers wanting fine control over the embedding of the `customElement` in their apps, ie. those compiling with `tag: null`. The end-result can look like a "flash of old-styled content" in some cases, since the following form of extension is currently the only solution available: + +```js +import CompiledElement from 'some-svelte-component-esmodule' + +class RestyledElement extends CompiledElement { + constructor (options) { + // can't mutate this.shadowRoot here, since not inited yet + super(options) + this.shadowRoot.innerHTML = ""; + } +} + +customElements.define('my-element', RestyledElement) +``` + +Instead, the generator function offered by the proposed design can service the same functionality cleanly, with only a single stylesheet assignment in the shadow DOM: + +```js +import { createStyledElement } from 'some-svelte-component-esmodule' + +customElements.define('my-element', createStyledElement('.changed-css-class{display:flex;}')) + +``` + +Further to the above, and **optional to this RFC**, is a revisiting of the conversation around the `css` compiler option. From the docs: + +> If `true`, styles will be included in the JavaScript class and injected at runtime. It's recommended that you set this to `false` and use the CSS that is statically generated, as it will result in smaller JavaScript bundles and better performance. + +This contract currently only applies to `SvelteComponent` builds. For `SvelteElement` output, CSS is currently always rendered into the output regardless of this setting. In use cases where a consumer of a module wants to override the component CSS, this leads to unnecessary bloat in the bundle due to the presence of styles which are immediately discarded. + +As such, this proposal also includes changing the behaviour of the `css` compiler option such that `css: false` omits styles for `customElement` builds as well. *(See also [svelte#4124](https://github.com/sveltejs/svelte/issues/4124).)* + +## Detailed design + +The internals of the DOM compiler should be tweaked slightly to include a generator function for styled variants of the base "unstyled" component. + +The "standard" version of the component with bundled styles is still provided as the default export of the module. + +Depending on the outcome of the discussion around `css: false` for `customElement`s, setting this compiler option may result in the ommission of any bundled styles included by the original component author. + +See [this branch](https://github.com/sveltejs/svelte/compare/master...pospi:injectable-customElement-styles) for a work-in-progress implementation. + +## How we teach this + +> Would the acceptance of this proposal mean the Svelte guides must be +re-organized or altered? Does it change how Svelte is taught to new users +at any level? + +No. + +> How should this feature be introduced and taught to existing Svelte +users? + +We could update the docs for WebComponent builds to include instructions for how to override component styles, targeted at app & component library authors who want to work with custom styling. + +## Drawbacks + +> Why should we *not* do this? Please consider the impact on teaching Svelte, +on the integration of this feature with other existing and planned features, +on the impact of the API churn on existing apps, etc. + +Slight increase in bundle size for `customElement` builds. This could be alleviated by adding a generic helper to `svelte/internal` to wrap up the generator functionality. + +> There are tradeoffs to choosing any path, please attempt to identify them here. + +No apparent changes to the external API contract. Note that some tests have been altered to add `css:true` to the configuration, but this appears to be a special case for the test harness since `css:true` is the default option for the compiler under normal use. + +## Alternatives + +> What other designs have been considered? + +Various attempts at component restyling but nothing which resolves the "multiple CSS reflows" issue as yet. + +> What is the impact of not doing this? + +Continuation of clunky workarounds by developers wanting deep customisation of component styles. + +> This section could also include prior art, that is, how other frameworks in the same domain have solved this problem. + +## Unresolved questions + +> Optional, but suggested for first drafts. What parts of the design are still +TBD? + +- Internal parameter name for the injected CSS string in the element's constructor. Currently using `_injectCSS` but could shift to additional constructor parameters or another preferred API. +- Standardised name for the generator function that such component modules export. Have chosen `createStyledElement` for now but can follow whatever convention is preferred by the project maintainers.