defineCustomElement() de Vue para crear un elemento personalizado, pero los estilos de los componentes secundarios no están incluidos en la raíz oculta por algún motivo.
Luego traté de crear manualmente mi shadow root usando la API nativa Element.attachShadow() en lugar de usar defineCustomElement() (basado en un Codesandbox ), pero luego no se cargaron estilos en absoluto:
Código : main.js :
import { createApp } from "vue"; import App from "./App.vue"; import router from "./router"; import store from "./store"; let treeHead = document.querySelector("#app"); let holder = document.createElement("div"); let shadow = treeHead.attachShadow({ mode: "open" }); shadow.appendChild(holder); createApp(App).use(store).use(router).mount(holder); Código vue.config.js :
module.exports = { chainWebpack: (config) => { config.module .rule("vue") .use("vue-loader") .loader("vue-loader") .tap((options) => { options.shadowMode = true; return options; }); config.module .rule("css") .oneOf("vue-modules") .use("vue-style-loader") .tap((options) => { options.shadowMode = true; return options; }); config.module .rule("css") .oneOf("vue") .use("vue-style-loader") .tap((options) => { options.shadowMode = true; return options; }); }, }; package.json :
{ "name": "shadow-root", "version": "0.1.0", "private": true, "scripts": { "serve": "vue-cli-service serve", "build": "vue-cli-service build" }, "dependencies": { "vue": "^3.2.20", "vue-loader": "^16.8.2", "vue-router": "^4.0.0-0", "vue-style-loader": "^4.1.3", "vuex": "^4.0.0-0" }, "devDependencies": { "@vue/cli-plugin-router": "~4.5.0", "@vue/cli-plugin-vuex": "~4.5.0", "@vue/cli-service": "~4.5.0", "@vue/compiler-sfc": "^3.0.0", "node-sass": "^4.12.0", "sass-loader": "^8.0.2" }, "browserslist": [ "> 1%", "last 2 versions", "not dead" ] }¿Cómo puedo crear un elemento personalizado con todos sus estilos en la raíz de la sombra?
Esa configuración de Vue no es necesaria en Vue 3. Solo la necesitaba el servidor de desarrollo en Vue 2 para representar los estilos en elementos personalizados.
El uso defineCustomElement() es la forma recomendada de registrar elementos personalizados. Sin embargo, hay un problema abierto al usar defineCustomElement() , donde los estilos de componentes anidados no se representan en absoluto ( @vuejs/vue-next#4462 ).
Una solución es importar todos los componentes como elementos personalizados para que los estilos se adjunten a la definición del componente en lugar de agregarse a <head> , luego inserte esos estilos en el DOM al montarlos:
Habilite el modo customElement vue-loader en vue.config.js :
// vue.config.js module.exports = { chainWebpack: config => { config.module .rule('vue') .use('vue-loader') .tap(options => { options.customElement = true return options }) } } Como alternativa, cambie el nombre de todas las extensiones de nombre de archivo de componentes de .vue a .ce.vue .
Cree una función de utilidad que envuelva defineCustomElement() de Vue y haga lo siguiente en un setup() :
mixin para los enlaces de ciclo de vida mounted y unmounted .mounted , inserte los estilos propios del componente desde this.$.type.styles en el DOM en una etiqueta <style> . Haga lo mismo con las definiciones de componentes del mapa this.$options.components .unmounted , elimine la etiqueta <style> que se insertó desde el mounted ._context de la instancia de la aplicación temporal en el contexto de la aplicación actual desde getCurrentInstance() . // defineCustomElementWithStyles.js import { defineCustomElement as VueDefineCustomElement, h, createApp, getCurrentInstance } from 'vue' const nearestElement = (el) => { while (el?.nodeType !== 1 /* ELEMENT */) el = el.parentElement return el } export const defineCustomElement = (component) => VueDefineCustomElement({ setup() { const app = createApp() 1️⃣ app.mixin({ mounted() { const insertStyles = (styles) => { if (styles?.length) { this.__style = document.createElement('style') this.__style.innerText = styles.join().replace(/\n/g, '') nearestElement(this.$el).prepend(this.__style) } } 2️⃣ insertStyles(this.$?.type.styles) if (this.$options.components) { for (const comp of Object.values(this.$options.components)) { insertStyles(comp.styles) } } }, unmounted() { this.__style?.remove() 3️⃣ }, }) 4️⃣ const inst = getCurrentInstance() Object.assign(inst.appContext, app._context) 5️⃣ return () => h(component) }, }) Edite public/index.html para reemplazar el <div id="app"> con un elemento personalizado (p. ej., llamado " app-root "):
Antes:
// public/index.html <body> <div id="app"></div> </body>Después:
// public/index.html <body> <app-root id="app"></app-root> </body> En lugar de createApp() , usa defineCustomElement() de arriba para crear un elemento personalizado de tu aplicación:
Antes:
// main.js import { createApp } from 'vue' import App from './App.vue' createApp(App).mount('#app')Después:
// main.js import { defineCustomElement } from './defineCustomElementWithStyles' import App from './App.vue' customElements.define('app-root', defineCustomElement(App))