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

ShadowDom Support #689

Closed
NiewView opened this issue Jun 3, 2022 · 7 comments · Fixed by #1105
Closed

ShadowDom Support #689

NiewView opened this issue Jun 3, 2022 · 7 comments · Fixed by #1105

Comments

@NiewView
Copy link

NiewView commented Jun 3, 2022

Hi 07akioni,

I'm planing to use the naive-ui library in combination with custom-elements and a shadow-dom.
Unfortunately this combination is currently not possible, because the styles always get mounted to the main document (tusen-ai/naive-ui#2308).
So I had a look how to add support for this and came across this repository.

Since you are an active member in both repositories, I have the following questions.
Do you plan to continue using css-render in naive-ui and add support for custom-elements here as well?
If so, do you already have an idea how the API of css-render should look like (maybe the possibility to add a custom handler, like ssr)?

I'm asking to help out, not to ask when it's done 😉
In all cases thanks for you great work in both projects.

Best regards
NiewView

@07akioni
Copy link
Owner

07akioni commented Jun 4, 2022

I think we may provide a style mount hook in css-render. (props: { id: string, style: string }) => void. Then user can mount the style following their intention.

However I'm not very sure how it's going to be like exactly since I'm not familiar with shadow dom (for example why is the style, how many times it's mounted, how to check if some style is already mounted).

The key points are:

  1. Make sure style applied
  2. Do not generate style multiple times if it's already mounted (by id)

@NiewView
Copy link
Author

The shadow dom is mounted once, when the custom element is initiated.
The shadow dom is more or a less a additional document within an HTML-Element, by this it isolates all the styles in both directions (outside styles are not applied in the shadow dom and the other way around.) This also means one webpage can have x shadow-doms.

Because of this and your second point I don't think it is enough to provide a style mount hook. Then css-render would have no way to check, if styles are already mounted. Except the plan would be, to just always call the hook and leave it to the logic within it, to check if the styles are already mounted.

@07akioni
Copy link
Owner

The shadow dom is mounted once, when the custom element is initiated. The shadow dom is more or a less a additional document within an HTML-Element, by this it isolates all the styles in both directions (outside styles are not applied in the shadow dom and the other way around.) This also means one webpage can have x shadow-doms.

Because of this and your second point I don't think it is enough to provide a style mount hook. Then css-render would have no way to check, if styles are already mounted. Except the plan would be, to just always call the hook and leave it to the logic within it, to check if the styles are already mounted.

I wonder if style created in a component instance can be reused in another component instance?

For example:

<custom-button />
<custom-button />

Can these 2 buttons share same style (with only 1 time style generation)

@NiewView
Copy link
Author

I'm not sure, if I understand you correctly.
When I build a vue app as custom element the stlyes will only be applied within the custom element, so it would look something like this

<custom-button>
  # shadow-root
    <styles>/* styles from css-render */</styles>
    <button></button>
  #end shadow-root
</custom-button>
<custom-button>
  # shadow-root
    <styles>/* styles from css-render */</styles>
    <button></button>
  #end shadow-root
</custom-button>

It is ensured, that the implementation for both custom-button is the same, because you can only register a custom-element once. But the styles need to be added within each shadow-dom separate.

If you were only talking about the generation part within css-render, I think it could be theoretically possible to share the logic.
But I don't think it is a good idea, because by default the components don't know from each other and they also should be separated. There is even a shadow-dom mode, which explicitly prohibits any js mutation within a DOM from externally.

If you attach a shadow root to a custom element with mode: closed set, you won't be able to access the shadow DOM from the outside — myCustomElem.shadowRoot returns null. link

That's why I would recommend generating the styles for each element individually and not breaking the border between them.

@07akioni
Copy link
Owner

Current vue has some limitations. I can't access shadowRoot easily from the component. I think we may raise a feature request for vue. Then we can use the following method to reuse generated style.

https://jsbin.com/mafiyidave/3/edit?html,console,output

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width">
  <title>JS Bin</title>
</head>
<body>
  <script src="https://unpkg.com/vue"></script>
  <script>
    const style = '.foo { color: red; }'
    let styleUrl = ''
    let styleGenerated = false
    
    const _El = Vue.defineCustomElement({
      setup () {
        const self = Vue.getCurrentInstance()
        Vue.onBeforeMount(() => {
          const linkEl = document.createElement('link')
          linkEl.rel = 'stylesheet'
          if (!styleGenerated) {
            console.log('generate style')
            styleUrl = URL.createObjectURL(new Blob([style]))
            linkEl.href = styleUrl
            styleGenerated = true
          } else {
            console.log('reuse style style')
            linkEl.href = styleUrl
          }
          self.shadowRoot.appendChild(linkEl)
        })
      },
      render () {
        return Vue.h('div', { class: 'foo' }, 'custom element')
      }
    })
    // Hack into Vue
    class El extends _El {
      constructor(...args) {
        super(...args)
        this.VueElement = Object.getPrototypeOf(Object.getPrototypeOf(this))
      }
      connectedCallback() {
        this.VueElement.connectedCallback.apply(this)
      }
      _createVNode() {
        const vnode = this.VueElement._createVNode.apply(this)
        if (!this._instance) {
          const ce = vnode.ce
          vnode.ce = instance => {
            instance.shadowRoot = this.shadowRoot
            return ce(instance)
          }
        } 
        return vnode
      }
    }
    customElements.define('custom-el', El)
  </script>
  <custom-el></custom-el>
  <custom-el></custom-el>
  <custom-el></custom-el>
</body>
</html>

@07akioni
Copy link
Owner

At least there's not much JS runtime expense. If possible I think it's better to benchmark normal way & style reuse way.

@07akioni
Copy link
Owner

ref vuejs/core#6113

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

Successfully merging a pull request may close this issue.

2 participants