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

styled customElement generator function #26

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
108 changes: 108 additions & 0 deletions text/0000-styled-customelement-generators.md
Original file line number Diff line number Diff line change
@@ -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 = "<style>.changed-css-class{display:flex;}</style>";
}
}

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.