Why my Vue3 Webcomponent has no css in its shadow dom - vuejs3

I have built a Vue3 web component and try to embed it in an other project.
This is my main.js for building the web component.
import {createApp, getCurrentInstance, h} from 'vue'
import App from './App.vue'
import {createPinia} from 'pinia'
import i18n from './services/i18n'
import {router} from '#/helpers'
import VueApexCharts from "vue3-apexcharts";
import "bootstrap/dist/css/bootstrap.min.css"
import "bootstrap"
import './assets/main.css'
import './assets/sweetalert2.min.css'
import contextmenu from "v-contextmenu";
import "v-contextmenu/dist/themes/default.css";
import {defineCustomElement} from "vue";
const clickOutside = {
beforeMount: (el, binding) => {
el.clickOutsideEvent = event => {
// here I check that click was outside the el and his children
if (!(el == event.target || el.contains(event.target))) {
// and if it did, call method provided in attribute value
binding.value();
}
};
document.addEventListener("click", el.clickOutsideEvent);
},
unmounted: el => {
document.removeEventListener("click", el.clickOutsideEvent);
},
};
const pinia = createPinia()
export const createElementInstance = ({
component = null,
props = [],
sharedStoreInstance = false,
plugins = [],
renderOptions = {}
} = {}) => {
return defineCustomElement({
props: props,
setup() {
const app = createApp();
if (!sharedStoreInstance) {
const pinia = createPinia();
app.use(pinia);
}
app
.use(router)
.use(VueApexCharts)
.use(pinia)
.use(contextmenu)
.use(i18n)
.directive("click-outside", clickOutside)
const inst = getCurrentInstance();
Object.assign(inst.appContext, app._context);
Object.assign(inst.provides, app._context.provides);
},
render: () => h(component, renderOptions)
})
}
const config = {
component: App,
props: {title: String},
sharedStoreInstance: true,
renderOptions: {ref: 'component'}
}
customElements.define('ma-ansicht', createElementInstance(config, {
shadow: true
}));
I have 2 files to import in the other project
<script type="module" crossorigin src="/assets/index.3f46efca.js"></script>
<link rel="stylesheet" href="/assets/index.ac2fca12.css">
The shadow dom of the component doesnt have any css and the generated css file appears in the header of the skeleton application.
How can I build a Web Component which includes my css in the shadow dom?
I have tried different build options like npm run build and
vue-cli-service build --target lib --name ma-ansicht src/main.js
I have tried to embed the wc in different skeleton apps
I expect that my app as web component is fully functional and styled

Related

How do I import a 2nd CSS framework (prefixed) into my Vite-based Vue 3 project (1st framework being TailwindCSS)

So I have a default Laravel app working with Vue 3 and assets configured by Vite, TailwindCSS and TailwindUI installed. This all works fine.
I understand to have another CSS framework, I need to prefix it to avoid clashes. According to these instructions, I need to add the following line: (after installing via npm):
import PrimeVue from 'primevue/config'; //I have included this in app.js
as well as reference these styles:
primevue/resources/themes/saga-blue/theme.css
primevue/resources/primevue.min.css
primeicons/primeicons.css
How exactly do I reference these css files with a prefix so as to avoid clashes?
My postcss.config.js file currently looks like this:
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
My app.js looks like this:
import './bootstrap';
import '../css/app.css';
import { createApp, h } from 'vue';
import { createInertiaApp } from '#inertiajs/inertia-vue3';
import { InertiaProgress } from '#inertiajs/progress';
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';
import { ZiggyVue } from '../../vendor/tightenco/ziggy/dist/vue.m';
/* added by me*/ import PrimeVue from 'primevue/config';
/* added by me*/ import InputMask from 'primevue/inputmask';
const appName = window.document.getElementsByTagName('title')[0]?.innerText || 'Laravel';
createInertiaApp({
title: (title) => `${title} - ${appName}`,
resolve: (name) => resolvePageComponent(`./Pages/${name}.vue`, import.meta.glob('./Pages/**/*.vue')),
setup({ el, app, props, plugin }) {
return createApp({ render: () => h(app, props) })
.use(plugin)
.use(ZiggyVue, Ziggy)
.use(PrimeVue)
/*added by me*/ .component('InputMask', InputMask)
.mount(el);
},
});
InertiaProgress.init({ color: '#4B5563' });

Vue - Use i18n within the setup script

I need to find a way to use the $t of i18n within the setup script for my vue project
my i18n file looks like this:
import { createI18n } from 'vue-i18n'
import en from './en';
import es from './es';
const messages = { en, es };
const locales = [
{ code: 'en', name: 'English' },
{ code: 'es', name: 'Español' }
];
const i18n = createI18n({
locales: locales,
defaultLocale: 'en',
fallbackLocale: 'en',
messages,
silentTranslationWarn: true,
silentFallbackWarn: true,
})
export default i18n
my main js look like this:
import i18n from './lang/settings'
const application = createApp({
render: () => h(app, props)
})
application.use(i18n)
I can perfectly use $t() in the template to translate but I have no clue how to access the same method within <script setup></script>
The i18n resource and the related files need to be placed in the way you have mentioned in your question.
You can use it in this way
I have Added everything in main.ts for better understanding.
you can use it in this way
Main.ts
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import { createI18n } from 'vue-i18n';
const i18n = createI18n({
locale: 'en', // set locale
messages: {
en: {
sample:{
item1: 'hello world'
}
}} // set locale messages
});
createApp(App).use(router).use(i18n).mount('#app');
In your component
<script lang="ts" setup>
import { useI18n } from "vue-i18n";
const { t } = useI18n();
let name = t('sample.item1');
</script>
<template>
{{name}}
</template>

Vue 3 extends vue.js components from third party library in defineComponent

I want to use third party library element-plus in my component. In setup defineComponent entends that component. In console, it would warn Failed to resolve component: el-radio at <App>
In about router, Here is the about.vue
<template>
<div id="popup-content"></div>
</template>
<script>
import {
onMounted, createApp, defineComponent, nextTick,
} from 'vue';
import Test from '#/components/Test.vue';
export default {
setup() {
onMounted(() => {
const myNewComponent = defineComponent({
extends: Test,
});
createApp(myNewComponent).mount('#popup-content');
nextTick(() => {
createApp(myNewComponent).mount('#popup-content');
});
});
},
}
Test component has used element-plus el-raido component, Test.vue
<template>
<el-radio v-model="radio" label="1">备选项</el-radio>
<el-radio v-model="radio" label="2">备选项</el-radio>
</template>
<script>
export default {
data() {
return {
radio: '1',
};
},
};
</script>
I have add element-plus, and register all in main.js
import { createApp } from 'vue';
import ElementPlus from 'element-plus';
import 'element-plus/lib/theme-chalk/index.css';
import App from './App.vue';
const app = createApp(App);
app.use(ElementPlus);
app.mount('#app');
I have found this question
Extend vue.js component from third-party library
I really really don't understand what are you trying to achieve by extending your perfectly fine Test component BUT...
Vue 3 is very different from Vue 2 - a lot of global API's (as component registration for example) are not global anymore but are tight to a "app instance" (created by createApp)
So even if you register Element components in main.js (app.use(ElementPlus);), the another app instance (why!?) created in onMounted hook of about.vue component knows nothing about the components! That is the reason for an error...
You must register components in every app instance created by createApp you want to use them in ....
As #Michal Levý answered, I need to register components in every app instance created by createApp.
Here is the working version about.vue, in case someone need.
<template>
<div id="popup-content"></div>
</template>
<script>
import {
onMounted, createApp, defineComponent, nextTick,
} from 'vue';
import Test from '#/components/Test.vue';
import ElementPlus from 'element-plus';
import 'element-plus/lib/theme-chalk/index.css';
export default {
setup() {
onMounted(() => {
const myNewComponent = defineComponent({
extends: Test,
});
const app1 = createApp(myNewComponent);
nextTick(() => {
app1.use(ElementPlus);
app1.mount('#popup-content');
});
});
},
}

Next.js and Styled Components go out of sync between the server and the client on refresh

I have a Next.js app using styled components. On first load of any page, there are no complaints, and everything looks properly styled. When I refresh a page however, everything still looks proper, but I get a console error reading:
Warning: Prop `className` did not match. Server: "sc-TXQaF bfnBGK" Client: "sc-bdnylx kKokSB"
I've tried simplifying the styles on the specific component, and the error persists. I've tried removing the component entirely from the DOM, and that results in the same error on the next element in the DOM. So it seems to be a global issue.
I've followed the guide for using Next.js and Styled Components found here: https://github.com/vercel/next.js/tree/master/examples/with-styled-components
I have the .babelrc file in the root:
{
"presets": ["next/babel"],
"plugins": [["styled-components", { "ssr": true }]]
}
I have the _document.js file in my pages directory:
import Document from 'next/document'
import { ServerStyleSheet } from 'styled-components'
export default class MyDocument extends Document {
static async getInitialProps(ctx) {
const sheet = new ServerStyleSheet()
const originalRenderPage = ctx.renderPage
try {
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) =>
sheet.collectStyles(<App {...props} />),
})
const initialProps = await Document.getInitialProps(ctx)
return {
...initialProps,
styles: (
<>
{initialProps.styles}
{sheet.getStyleElement()}
</>
),
}
} finally {
sheet.seal()
}
}
}
Here is an example of one of my styled components:
import styled from 'styled-components';
export const Block = styled.div`
margin: ${props => props.small ? '2rem 0' : '4rem 0'};
margin-top: ${props => props.clearTop ? '0' : null};
`;
... although I've tried to dumb it down to something as simple as this with no change in the console error:
import styled from 'styled-components';
export const Block = styled.div`
position: relative;
`;
Finally, here's a dumbed down page that still produces the error:
import { useContext, useEffect } from 'react';
import { useRouter } from 'next/router';
import Layout from '../components/layout';
import { Block } from '../components/styled/Block';
import { userContext } from '../context/userContext';;
function Profile() {
const router = useRouter();
const { loggedIn } = useContext(userContext);
useEffect(() => {
if (!loggedIn) router.push('/login');
}, [loggedIn]);
return (
<Layout>
<Block>
<h1>Test</h1>
</Block>
</Layout>
)
}
export default Profile;
Kind of at my wits end here.
I believe I figured out an answer. I didn't have the dev dependency for babel styled components.
npm install babel-plugin-styled-components --save-dev
Your package.json file should have this:
"devDependencies": {
"babel-plugin-styled-components": "^1.11.1"
}
After this was installed, along with the _document.js and .babelrc files correctly placed in your app, you shouldn't have any problems.
I had this issue for the last 1 month and finally got a solution that worked for me!
So the solution here is to get the styling exclusively from the server.
from the docs:
Basically you need to add a custom pages/_document.js (if you don't
have one). Then copy the logic for styled-components to inject the
server side rendered styles into the <head>
To solve this issue is you need something like this in your Document component:
import Document from 'next/document'
import { ServerStyleSheet } from 'styled-components'
export default class MyDocument extends Document {
static async getInitialProps(ctx) {
const sheet = new ServerStyleSheet()
const originalRenderPage = ctx.renderPage
try {
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) => (props) =>
sheet.collectStyles(<App {...props} />),
})
const initialProps = await Document.getInitialProps(ctx)
return {
...initialProps,
styles: (
<>
{initialProps.styles}
{sheet.getStyleElement()}
</>
),
}
} finally {
sheet.seal()
}
}
}
The final step (if the error persists) is to delete the cache: delete the .next folder and restart the server
The full example code from Next documentation is Here
with the Next Complier on the latest version of next, you should only update your next.config file and _document file, and you will be all set. Babel will cause conflict with the NextJS compiler.
Here you can check the files. If you don't use TS, just replace
ctx: DocumentContext
with only
ctx
Example app with styled-components

Unexpected browserHistory behaviour (Meteor + React + React Router)

(PS: I'm using Meteor + React + React Router. The application structure is not traditional, I'm making a package-esq application, an example is https://github.com/TelescopeJS/Telescope. I'm trying to do dynamic routing with react router and things are not working out well.)
There be something wrong with browserHistory. Navigation refreshes the page. Going back and forth through the browser buttons refreshes the page.
Example of this, with all codes, are here - https://github.com/dbx834/sandbox
React-Router specific codes follow,
In a core package, with a global export, allow registeration of routes and components
...
// ------------------------------------- Components -------------------------------- //
Sandbox.components = {};
Sandbox.registerComponent = (name, component) => {
Sandbox.components[name] = component;
};
Sandbox.getComponent = (name) => {
return Sandbox.components[name];
};
// ------------------------------------- Routes -------------------------------- //
Sandbox.routes = {};
Sandbox.routes.routes = [];
Sandbox.routes = {
routes: [],
add(routeOrRouteArray) {
const addedRoutes = Array.isArray(routeOrRouteArray) ? routeOrRouteArray : [routeOrRouteArray];
this.routes = this.routes.concat(addedRoutes);
},
};
...
In various implementations (domain specific logic, UI, etc), register components and routes
...
import TodoApp from './components/TodoApp.jsx';
Sandbox.registerComponent('TodoApp', TodoApp);
Sandbox.routes.add([
{ name: 'todoAppRoute', path: 'todo-app', component: Sandbox.components.TodoApp },
]);
...
In the main app
import React from 'react';
import { render } from 'react-dom';
import { Meteor } from 'meteor/meteor';
import { Router, browserHistory } from 'react-router';
import App from './components/App.jsx';
import Homepage from './components/Homepage.jsx';
Sandbox.registerComponent('App', App);
Sandbox.registerComponent('Homepage', Homepage);
Meteor.startup(() => {
const AppRoutes = {
path: '/',
component: Sandbox.components.App,
indexRoute: { name: 'home', component: Sandbox.components.Homepage },
childRoutes: Sandbox.routes.routes,
};
console.log(AppRoutes);
render(
<Router routes={AppRoutes} history={browserHistory} />,
document.getElementById('app-root')
);
});
What is wrong?
I uninstalled all npm packages, meteor packages, updated everything, re-installed latest packages, cleaned out all previous builds and everything works now!
There was something weird somewhere.
If anyone finds themselves in a similar situation, you can try this.
Best

Resources