vitejs / vite

Next generation frontend tooling. It's fast!
http://vite.dev
MIT License
68.62k stars 6.2k forks source link

Option to add nonce value to style tag of vue3 web components #9404

Closed landoephan closed 2 years ago

landoephan commented 2 years ago

Description

When I create web components via custom elements in vue3 (as described here: https://vuejs.org/guide/extras/web-components.html#building-custom-elements-with-vue), the styles are loaded directly into the shadow dom with a <style> tag. In order to meet content security policies, I would need to include a nonce value to this style tag when building the web component. Otherwise the styles are rejected with the following error message:

Refused to apply inline style because it violates the following Content Security Policy directive: "style-src 'self' 'nonce-xxx='". Either the 'unsafe-inline' keyword, a hash ('sha256-xxx='), or a nonce ('nonce-...') is required to enable inline execution.

It would be great, if a nonce value could be applied to the style tags while building the

Suggested solution

Unfortunately, I don't know where these changes could be implemented

Alternative

No response

Additional context

No response

Validations

tony19 commented 2 years ago

The style tag is inserted in vuejs/core, so the feature request should be made there instead.

A workaround is to create a wrapper for defineCustomElement that updates the shadow root's <style> with the desired nonce value:

import { defineCustomElement } from 'vue'
import HelloWorld from '@/components/HelloWorld.vue'

const defineCustomElementWithNonce = (Comp: any, options?: { nonce: string }) => {
  const comp = defineCustomElement(Comp)

  if (typeof options?.nonce !== 'undefined') {
    const origApplyStyles = comp.prototype._applyStyles
    comp.prototype._applyStyles = function (styles: any) {
      origApplyStyles.call(this, styles)

      // add nonce to style element in shadow root
      if (styles) {
        const styleEl = this.shadowRoot.querySelector('style')
        styleEl?.setAttribute('nonce', options.nonce)
      }
    }
  }

  return comp
}

const HelloWorldElement = defineCustomElementWithNonce(HelloWorld, { nonce: /* nonce value pre-determined by server */ })

customElements.define('hello-world', HelloWorldElement)
landoephan commented 2 years ago

thank you, that helped a lot. The only problem with that solution is that I need to have the nonce value before the style Tag is added to the DOM. But I will just write my own applyStyles method I guess.

Thanks a lot!

tony19 commented 2 years ago

I'm not sure I understand the problem. Are you trying to add the nonce after the style is added to the DOM? What would your proposed _applyStyles do?

landoephan commented 2 years ago

I just override the _applyStyles function, so the nonce attribute is added to the style tag before appendChild(style) is called. Like this:

  _comp.prototype._applyStyles = function (styles: string[]) {
    if (styles) {
      styles.forEach((css) => {
        const s = document.createElement('style')
        s.textContent = css
        s.setAttribute('nonce', options?.nonce || defaultNonce)
        this.shadowRoot?.appendChild(s)
        // record for HMR
        if (import.meta.env.DEV) {
          ;(this._styles || (this._styles = [])).push(s)
        }
      })
    }
  }

My reverse-proxy needs it like that. This way all works great, thanks a lot for your help.

sapphi-red commented 2 years ago

Closing as this needs to be implemented on vue side.

@tony19 Thanks for the explanation!