Render external loaded vue component in react app - javascript

I want to load external vue components in a react app as plugins from an url.
I already bundled the vue component as a commonJs package, but it will not render in the react app.

I have found a solution
Vue Project
main.js
import Vue from 'vue'
import wrap from '#vue/web-component-wrapper'
import Plugin from './components/Plugin'
const CustomElement = wrap(Vue, Plugin)
window.customElements.define('vue-plugin', CustomElement)
./component/Plugin.vue
<template>
<div class>
<p>{{text}}</p>
<p>{{pluginData.text}}</p>
<button v-on:click="sendData({ text: 'primary click vue' })">primary</button>
<button v-on:click="sendData({ text: 'secondary click vue' })">secondary</button>
</div>
</template>
<script>
export default {
props: {
text: String,
data: String
},
computed: {
pluginData () {
return JSON.parse(this.data)
}
},
methods: {
sendData: function(payload) {
this.$emit('sendData', payload);
}
}
}
</script>
Then build the webcomponent with:
vue-cli-service build --target wc --name vue-plugin ./src/main.js
And loaded in the react app with:
const $script = require("scriptjs")
$script("https://external-url-to-your-component.com/plugin.js", () => {
this.setState({
Component: ({ data, children, ...props}) => React.createElement(
'vue-plugin',
{...props, data: JSON.stringify(data), ref: elem => this[name] = elem },
children
)
})
this[name].addEventListener('sendData', event => {
const payload = event.detail
payload && payload.length && sendData(payload[0])
})
})

Related

How to add multiple components dynamically at run time not globally but locally

Hi i have a situation where i need to register multiple components at run time within a file.
Display.vue
<template>
<div>
<component v-if="currentComponent" :is="currentComponent">
</component>
</div>
</template>
<script>
import Vue from 'vue';
export default {
data(){
return{
currentComponent:null,
}
},
methods:{
load(e){
for(let key in e.components){
Vue.component(key,e.components[key]);
}
this.$nextTick(() =>{
this.currentComponent = e.components.container;
});
},
},
created(){
document.body.addEventListener('component-ready',this.load, false);
},
}
</script>
in my above file how i want my components to be loaded something like shown below:
components:{
container: component Object{},
header: component Object{},
body: component Object {},
footer: component Object {},
}
here is how i'm dispatching event to above file Display.vue
const event = new Event('component-ready');
event.components = {
container: component Object{},
header: component Object{},
body: component Object {},
footer: component Object {},
};
document.body.dispatchEvent(event);
Execution sequence:
event dispatch component-ready
eventlisterner in created of file Display.vue will call load method
Problem: in my current approach components are registered globally, that i want to avoid. i want to register all components to Display.vue file only
To locally register the components on the fly, you can copy the component definitions into this.$options.components:
export default {
methods: {
load(e) {
this.$options.components = e.components 👈
this.$nextTick(() => {
this.currentComponent = e.components.container
})
},
},
}
demo

Vuejs implementing Vue-Select2 not showing on result

After installing vue3-select2-component with their document when i implementing that. it doesn't show in output on html but i have the html of that in source code
BTW: i'm using inertiajs on Laravel framework
install:
// npm install
npm install vue3-select2-component --save
Use as component:
import {createApp, h} from 'vue'
import BootstrapVue3 from 'bootstrap-vue-3'
import IconsPlugin from 'bootstrap-vue-3'
import {InertiaProgress} from "#inertiajs/progress";
import {createInertiaApp, Head} from '#inertiajs/inertia-vue3'
import {Link} from "#inertiajs/inertia-vue3"
///...
import Select2 from 'vue3-select2-component';
import {createStore} from "vuex"
///...
createInertiaApp({
resolve: async name => {
return (await import(`./pages/${name}`)).default;
},
setup({el, App, props, plugin}) {
createApp({render: () => h(App, props)})
.use(plugin)
.use(bootstrap)
.use(BootstrapVue3)
.use(IconsPlugin)
.use(VueSweetalert2)
.component('Link', Link)
.component('Select2', Select2)
.mount(el)
},
title: title => 'azizam - ' + title
}).then(r => {
});
vuejs page which i want to use into that:
<template>
<Select2 v-model="myValue" :options="myOptions"
:settings="{ settingOption: value, settingOption: value }"
#change="myChangeEvent($event)"
#select="mySelectEvent($event)" />
</template>
<script>
import menubar from "./menubar";
import emulator from "./emulator";
import {mapActions} from "vuex";
import notification from "../../../partials/notification";
export default {
name: "image",
data() {
return {
caption: '',
myValue: '',
myOptions: ['op1', 'op2', 'op3']
}
},
components: {
menubar,
emulator,
notification
},
methods: {
...mapActions([
'changeBreadcrumb'
]),
myChangeEvent(val){
console.log(val);
},
mySelectEvent({id, text}){
console.log({id, text})
}
},
mounted() {
const payload = {
title: 'محصولات',
subTitle: 'ایجاد محصول تک عکس در سامانه'
};
this.changeBreadcrumb(payload);
}
}
</script>
console log:
Warning - slinky.min.js is not loaded. application.js:336:21
[Vue warn]: A plugin must either be a function or an object with an "install" function. vendor.js:10544:17
[Vue warn]: Plugin has already been applied to target app. vendor.js:10544:17
Use of Mutation Events is deprecated. Use MutationObserver instead. content.js:19:11
Source map error: Error: request failed with status 404
Resource URL: http://127.0.0.1:8000/js/vendor.js?id=594b688c9609a79fb52afd907a69c736
Source Map URL: tooltip.js.map
in console as you can see i don't get any error for this component
html source code:
<select2 options="op1,op2,op3" settings="[object Object]"></select2>
and then webpack:
const mix = require('laravel-mix');
mix.js('resources/js/app.js', 'public/js')
//.sass('resources/scss/app.scss','public/css')
.extract()
.vue({
version: 3,
options: {
compilerOptions: {
isCustomElement: (tag) => ['Select2'].includes(tag),
},
},
})
.postCss('resources/css/app.css', 'public/css', [
//
])
.version();
The problem is you've configured Vue to treat <Select2> as a custom element, so the actual component does not get rendered.
The fix is to remove that configuration:
const mix = require('laravel-mix');
mix.js('resources/js/app.js', 'public/js')
//.sass('resources/scss/app.scss','public/css')
.extract()
.vue({
version: 3,
//options: {
// compilerOptions: {
// isCustomElement: (tag) => ['Select2'].includes(tag), ❌ remove this
// },
//},
})
.postCss('resources/css/app.css', 'public/css', [
//
])
.version();

How to make nested routing in Vuejs?

HelloWorld.vue
import axios from "axios";
export const router = () => axios.get("https://fakestoreapi.com/products");
<template>
<div>
<div v-for="item in items" :key="item.id">
<b> id: {{ item.id }}</b>
<router-link
:to="`/${item.id}`"
>
{{ item.title }}
</router-link>
</div><!-- end v-for -->
<router-view></router-view>
</div>
</template>
<script>
import { router } from "./router";
export default {
name: "HelloWorld",
components: {},
data() {
return {
items: [],
};
},
mounted() {
router().then((r) => {
this.items = r.data;
});
},
};
</script>
User.vue
import axios from "axios";
export const routerid = (itemId) =>
axios.get("https://fakestoreapi.com/products/" + itemId);
<template>
<div>
<div v-if="item">
<h1>Price: {{ item.price }}</h1>
</div>
<tabs />
</div>
</template>
<script>
import { routerid } from "./routerid";
import tabs from "./tabs";
export default {
name: "User",
components: {
tabs,
},
data() {
return {
item: null,
};
},
mounted() {
this.loadData();
},
computed: {
routeId() {
return this.$route.params.id;
},
},
watch: {
routeId() {
console.log("Reload (route change)");
this.loadData();
}, //reload when route id changes
},
methods: {
loadData() {
console.log("Reloading, ID", this.routeId);
if (!this.routeId) return; // no ID, leave early
routerid(this.$route.params.id).then((item) => {
this.item = item.data;
});
},
},
};
</script>
tabs.vue
import axios from "axios";
export const tabsandcontent = async (itemId) =>
await axios.get("https://fakestoreapi.com/products?limit=" + itemId);
<template>
<div>
<div v-if="item">
<h1>description: {{ item.description }}</h1>
</div>
</div>
</template>
<script>
import { tabsandcontent } from "./tabsandcontent";
export default {
name: "User",
components: {},
data() {
return {
item: null,
};
},
mounted() {
this.loadData();
},
computed: {
tabsandcontent() {
return this.$route.params.id;
},
},
watch: {
tabsandcontent() {
console.log("Reload (route change)");
this.loadData();
}, //reload when route id changes
},
methods: {
loadData() {
console.log("Reloading, ID", this.tabsandcontent);
if (!this.tabsandcontent) return; // no ID, leave early
tabsandcontent(this.$route.params.id).then((item) => {
this.item = item.data;
});
},
},
};
</script>
main.js
import Vue from "vue";
import App from "./App.vue";
import VueRouter from "vue-router";
import HelloWorld from "./components/HelloWorld";
import User from "./components/User";
Vue.use(VueRouter);
const router = new VueRouter({
routes: [
{
path: "/HelloWorld",
name: "HelloWorld",
component: HelloWorld,
children: [{ path: ":id", name: "User", component: User }]
}
]
});
Vue.config.productionTip = false;
new Vue({
router,
render: (h) => h(App)
}).$mount("#app");
code:- https://codesandbox.io/s/combined-logic-api-forked-41lh0f?file=/src/main.js
can you please answer this, In main.js routing I changed from path: "/" to path: "/HelloWorld" then all of sudden output not reflecting... because in my project path:'/' indicates login page??? In this scenario what changes, i need to make, to make logic work
also where is the relation between path:'/' and api call??
You have same name for the variables in tabs component (In watch and computed). And In tabsandcontent.js, you have missed to fetch description for the specific item as performed in routerId.js.
Have a look at modified version which is working as you expected.
https://codesandbox.io/embed/combined-logic-api-forked-ji5oh4?fontsize=14&hidenavigation=1&theme=dark
First thing first, I want you to know that I don't understand what are you asking for. But I'm going to try to answer.
Your first question:
In main.js routing I changed from path: "/" to path: "/HelloWorld" then all of sudden output not reflecting.
Yes, you will not see your HelloWorld.vue component. You can see your page however if you type <your-url>/HelloWorld. Usually the / path is used for something like "Home" page.
However, I've tried checking out your codesandbox. And take a look at your HelloWorld.vue component.
I think you are confused because when you changed the path from / to /HelloWorld apart from the HelloWorld.vue not showing up. It somehow broken the link which causes the API in tabs.vue not functioning.
If that's the case, you just have to simply add HelloWorld/${item.id} in tabs.vue,
<template>
<div>
<div v-for="item in items" :key="item.id">
<b> id: {{ item.id }}</b>
<router-link
:to="`HelloWorld/${item.id}`" // --> Notice this line
>
{{ item.title }}
</router-link>
</div><!-- end v-for -->
<router-view></router-view>
</div>
</template>
This however, isn't a common thing to do routing. You should add your App URLs to main.js. Which also isn't common, but I'm assuming this is just a little reproduction code you made for StackOverflow.
Here are my CodeSandbox edits.
https://codesandbox.io/s/combined-logic-api-forked-jttt8p
I will update the answer again later, I'm still not on my personal laptop.

How to use Vue 3 Meta with Vue.js 3?

It seems that Vue Meta has been upgraded to handle Vue.js 3 with a new npm package called vue-3-meta
Before Vue.js 3, it was easy to use vue-meta by adding it to the Vue instance:
import Vue from 'vue'
import VueMeta from 'vue-meta'
Vue.use(VueMeta, {
// optional pluginOptions
refreshOnceOnNavigation: true
})
However in Vue.js 3, there is no Vue instance; and instead you create the app by running createApp like such:
const app = createApp(App);
const router = createVueRouter();
app.use(router);
// need to make app use Vue-Meta here
I cannot find any documentation for vue-3-meta. import VueMeta from 'vue-meta' no longer works.
How do I import the vue-3-meta plugin properly and use it with app like in prior versions?
Disclaimer: vue-meta v3 is still in alpha!
This was the minimal implementation I needed to get started:
Update vue-meta to v3 (in package.json)
- "vue-meta": "^2.4.0",
+ "vue-meta": "^3.0.0-alpha.7",
...or with yarn:
yarn add vue-meta#alpha
Add metaManager to Vue app
import { createMetaManager } from 'vue-meta'
const app = createApp(App)
.use(router)
.use(store)
.use(createMetaManager()) // add this line
await router.isReady()
app.mount('#app')
Add <metainfo> to App.vue <template> (this is also where I set a "title template")
<template>
<metainfo>
<template v-slot:title="{ content }">{{ content ? `${content} | SITE_NAME` : `SITE_NAME` }}</template>
</metainfo>
<header />
<router-view />
<footer />
</template>
Set default meta in App.vue <script>
Vue 3 vanilla:
import { useMeta } from 'vue-meta'
export default {
setup () {
useMeta({
title: '',
htmlAttrs: { lang: 'en', amp: true }
})
}
}
or with vue-class-component:
import { setup, Vue } from 'vue-class-component'
import { useMeta } from 'vue-meta'
export default class App extends Vue {
meta = setup(() => useMeta({
title: '',
htmlAttrs: { lang: 'en', amp: true }
})
}
Override meta in each component
Vue 3 vanilla:
import { useMeta } from 'vue-meta'
export default {
setup () {
useMeta({ title: 'Some Page' })
}
}
or with vue-class-component:
import { computed } from '#vue/runtime-core'
import { setup, Vue } from 'vue-class-component'
import { useMeta } from 'vue-meta'
export default class SomePage extends Vue {
meta = setup(() => useMeta(
computed(() => ({ title: this.something?.field ?? 'Default' })))
)
}
See also:
"Quick Usage" (vue-meta next branch)
Vue Router Example (vue-meta next branch)
In addition to the previous answers, I also needed to add a transpileDependency in my vue.config.js, as I was using vue-cli:
module.exports = {
transpileDependencies: ['vue-meta']
}
Else, I would get the error:
error in ./node_modules/vue-meta/dist/vue-meta.esm-browser.min.js
Module parse failed: Unexpected token (8:7170)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
Thanks to this thread for pointing me to this: https://stackoverflow.com/a/65844988/3433137
metaManager is a MetaManager instance created from createMetaManager() of vue-meta.
Based on the Vue 3 + Vue Router example for vue-meta, here's an example usage:
import { createApp } from 'vue'
import { createMetaManager, defaultConfig, resolveOption, useMeta } from 'vue-meta'
const decisionMaker5000000 = resolveOption((prevValue, context) => {
const { uid = 0 } = context.vm || {}
if (!prevValue || prevValue < uid) {
return uid
}
})
const metaManager = createMetaManager({
...defaultConfig,
esi: {
group: true,
namespaced: true,
attributes: ['src', 'test', 'text']
}
}, decisionMaker5000000)
useMeta(
{
og: {
something: 'test'
}
},
metaManager
)
createApp(App).use(metaManager).mount('#app')

how to pass props to root App component in vuejs v3?

How can i pass some variable from the main div folder:
<div
id="app"
someVariable='some value'
></div>
and to have it as a prop in the main App component in VueJS v3.0:
name: "App",
components: {
},
props: {
someVariable: {
type: String,
default: "-"
}
}
You could not access that using props, but you could get the value of that attribute using some Vanilla js DOM like document.getElementById("app").getAttribute("someVariable")
const {
createApp
} = Vue;
const App = {
props: ["someVariable"],
data() {
return {
}
},
mounted() {
console.log(document.getElementById("app").getAttribute("someVariable"))
}
}
const app = createApp(App)
app.mount('#app')
<script src="https://unpkg.com/vue#3.0.0-rc.11/dist/vue.global.prod.js"></script>
<div id="app" someVariable='some value'>
Vue 3 app
</div>

Categories