Inject Vue component from diet js file - vue.js

I have a very simple app vue3:
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
let currentApp = createApp(App);
(window as any).load(currentApp);
currentApp.mount('#app');
In the index.html file i import a dist lib:
<script src="http://localhost:5173/dist/assets/index.59eda240.js">
The content of dist JS is:
import HelloWorld from './components/HelloWorld.vue'
(window as any).load = (app: any) => {
app.component('hello-world', HelloWorld);
}
If i use a very simple HelloWorld.vue with just one line of text "Hello", all works fine, but if i put some css classes or more complicated component i obtain:
[Vue warn]: Invalid VNode type: Symbol() (symbol) at <HelloWorld>
at <App>
How can i solve this? Or is there another way to load component at runtime?
Thanks in advance.
P.s. I have no problem doing the same thing with Vue
--------- UPDATE MORE INFO ----------
Vue version
3.2.38
Link to minimal reproduction
https://stackblitz.com/edit/vitejs-vite-744fbh?file=src/main.js
Steps to reproduce
The base idea is to have a main app the create a Vue app, and another external lib the load component at runtime.
Open the sample project and show console log to see "Invalid VNode type: Symbol() (symbol)"
What is expected?
I expect to see the components rendered correctly
What is actually happening?
I don't see the component UI, but looking at the console I see the logic code works correctly but we have a warning: Invalid VNode type: Symbol() (symbol) Invalid VNode type: Symbol() (symbol)
System Info
No response
Any additional comments?
This warning appears only in some components, for example, fur a very simple component, it works fine, see here: https://stackblitz.com/edit/vitejs-vite-nueucw?file=src/App.vue.
You can see the source of helloworld component at the top of lib.js file, inside a comment block

The actual problem is that there are multiple Vue library copies per page that interact with each other. This should be avoided, preferably by bundling multiple parts of the application together, so multiple apps would reuse the same modules from vendors chunk. In case this isn't possible or practical, a simple workaround is to use Vue CDN for all applications, either by using window.Vue instead of vue import, or aliasing the import to global variable by means of a bundler.
The root cause is that Vue built-in element are specific to Vue copy in use - Fragment, Teleport, Comment, etc. They are defined with Symbol() to be distinguished from other elements and components and used in compiled component templates. Symbol is used in JavaScript to define unique tokens, Symbol('foo') !== Symbol('foo'). The symbols of built-in elements from one Vue copy mean nothing to another Vue copy, this is the meaning of this error message.
It can be seen in this analyzer that this component template:
<h1 style="background-color:Red; font-size: 44px;">{{ msg }}</h1>
<button #click="msg = 'click';">CLICK</button>
is compiled to this render function:
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createElementBlock(_Fragment, null, [
_createElementVNode("h1", { style: {"background-color":"Red","font-size":"44px"} }, _toDisplayString(_ctx.msg), 1 /* TEXT */),
_createElementVNode("button", {
onClick: $event => {_ctx.msg = 'click';}
}, "CLICK", 8 /* PROPS */, ["onClick"])
], 64 /* STABLE_FRAGMENT */))
}
Notice that the use of multiple root elements results in wrapping them in Fragment, which won't be correctly processed by multiple Vue copies, etc.

Change hello world with another component:
<template>
<div class="relative flex items-top justify-center min-h-screen sm:items-center py-4 sm:pt-0">
<div class="max-w-6xl mx-auto sm:px-6 lg:px-8">
<div class="card rounded-lg shadow-lg" style="background-color: yellow;">
<div class="card-body p-0 m-0 pt-2" style="background-color: orange;">
<div class="row border-bottom p-0 m-0 pt-2" style="background-color: brown;">
<div class="bg-secondary">
<h5 class="bg-info">test</h5>
<p>
test
</p>
</div>
</div>
<div>
<div>
test2
</div>
</div>
</div>
</div>
</div>
</div>
</template>
Only one root element, I have the same issue.

Related

Precompile VueJS template only files

I am writing a single page app with VueJS. I have a lot of html elements sharing the same style across differents files. To simplify my job and clear the template section I've created .vue files to encapsulate commom html block.
For example I have files like AccentBox.vue:
<template>
<div class="bg-accent rounded-lg p-4 flex cursor-pointer">
<div class="m-auto flex flex-row">
<slot></slot>
</div>
</div>
</template>
The problem is that it increase the bundle size a lot and in the browser side it has a big performance overhead.
Example of the Home.vue:
<Box class="mt-5">
<GrowingAnimation>
<Header1>Titre:</Header1>
<Text class="mt-2">{{todo.title}}</Text>
<ClickScale>
<AccentBox class="mt-5 flex flex-row gap-4">
<TrashIcon class="h-10" />
</AccentBox>
</ClickScale>
</Animation>
</Box>
I want to know if there is a way to "precompile / transform" those component so that in the browser vuejs does not have to render the AccentBox.vue component and avoid having huge trees of nested vuejs components.
I already use the vuejs build command but browser side AccentBox.vue still is a component and take time to be processed by vue.
Transforming this:
<Header1>Titre:</Header1>
into
<h1 class="text-3xl font-bold">Titre:</h1>
Is there any way to do it? Thanks in advance.
Because your AccentBox.vue file represents a full-blown component, it does contain more overhead than simply writing out some divs. However, in Vue 3, this overhead is rather minimal, and there isn't a way to precompile something like a macro.
Vue allows us to write stateless components that simply render HTML, using something called a render function. We can rewrite your AccentBox component like this:
import { h } from 'vue'
const AccentBox = (props, context) => {
return h(
'div',
{ class: 'bg-accent rounded-lg p-4 flex cursor-pointer' },
h(
'div',
{ class: 'm-auto flex flex-row' },
context.slots
)
)
}
export default AccentBox
We can then import it in exactly the same way as a standard Vue file:
import AccentBox from 'AccentBox.js'

Passing props to vue instance from .blade.php file using createElement and render function

EDIT: Some more information:
I have a main-page.blade.php file, with this:
//main-page.blade.php where vue instance will mount
<div id="main-page" class=" bg-uoi-green">
<main-page :data=['one'] ></main-page>
</div>
There is also a main.js file importing the app.js file of Vue, where the instance for main-page is defined ( I have installed vue-loader and template compiler).
The app.js for Vue is:
//app.js of Vue
import Vue from 'vue'
import './assets/tailwind.css'
//
// import MainPage from './components/MainPage.vue'
// const mainpage_inst=new Vue({
// el: '#main-page',
// components: {
// MainPage,
// },
// props: ['data'],
// });
Vue.config.productionTip = false
export default {
mainpage_inst,
}
Compiling for my application was giving me exactly what was needed. But then, if I try to also check the Vue directory using vue client, I get an empty page; this was to be expected since the vue-cli does not use the full-build. But even when I changed
// import Vue from 'vue'
to
import Vue from 'vue/dist/vue.js';
I was getting a blank page when visiting localhost:8080 from vue-cli.
I was able to fix it by using the render function of vue. So now, the instance became:
import MainPage from './components/MainPage.vue'
// Main page instance
const mainpage_inst = new Vue({
render: h => h(MainPage),
}).$mount('#main-page')
But, no matter how I try to pass props as before, my main application cannot see them, in contrast to the previous writing where it was able, but vue-cli was producing a blank page.
For clarification: I have a Vue-app inside a different app that is loaded with all the required( I think) modules to read and compile .vue files. But I would also like to be able to run the Vue-app using the vue-cli.
So, is my main-app missing a module and is not able to get the props? Or is there some other way to make 'understand'?
Thanks again.
In the past, I was using the following code to create the application instance :
// const mainpage_inst=new Vue({
// el: '#main-page',
// components: {
// MainPage,
// },
// props: ['data'],
// });
and I was calling the MainPage component from a .blade.php file by passing the props:
<main-page :data=" {{ $data_to_send_as_props }}" > </main-page>
Now I want to use the following code:
import MainPage from './components/MainPage.vue'
// Main page instance
const mainpage_inst = new Vue({
render: h => h(MainPage),
}).$mount('#main-page')
but if I try to define the props as before, the application does not recognizes them. What is the correct way to write the above excerpt of code, so that props will be passed ? In other words, how and were should I add the props definition on the instance above (note also that I should implement also the change mentioned in the answer of Daniel)
Edit:
This is a simplified version of the MainPage.vue component
<template>
<div class=" h-screen w-screen md:text-2xl text:2xl " id="main-page ">
<div class=" flex flex-shrink text-4xl md:text-9xl font-bold md:ml-20 md:my-5 md:py-0 py-10 title">
A Title
</div>
<div class="flex flex-col flex-grow h-1/3 ">
<div v-for="(item,index) in slicedArray " v-bind:key="item.id" >
<div class=" flex flex-col md:flex-row md:flex-grow w-full hover:bg-uoi-green px-10 border-black border-2 transition duration-500 cursor-pointer ease-in-out bg-opacity-70 transform hover:bg-opacity-95" :class="{active: isActive === item.id}">
<div class=" date-font h-full w-1/6 md:flex-grow px-2 py-4"> {{item.date}} — </div>
<div class=" h-full w-2/3 md:flex-grow px-2 py-4 text-left"> {{item.title}} </div>
<div class=" date-font h-full w-1/6 md:flex-grow px-2 py-4 text-right"> {{item.exp_date}} </div>
</div>
<div class=" bg-white py-10 px-20" v-if="isActive === item.id">
<div > {{item.text}} </div>
<div class='file'> <a href=''> Σχετικό αρχείο </a>
<!-- <img src="../assets/curved-up-arrow-.png" class="arrow"/> -->
</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name:'MainPage',
props: ['data'],
data(){
return{
isActive: null,
newsToShow: 3,
totalnews: 0,
array:[],
slicedArray:[],
}
},
mounted(){
this.newsToShow=3;
this.array=this.data;
this.totalnews = this.array.length;
// console.log(this.array)
this.slicedArray=this.array.slice(0,this.newsToShow)
},
}
</script>
<style scoped>
</style>
One of the breaking changes in Vue 3 was that the Global Vue API was changed to use an application instance. To create a vue app, use the createApp function to create the instance.
a-new-global-api-createapp
The render function (h) is not provided by the render function, so you need to import it. docs
so your instantiation should look something like this:
import { h, createApp } from 'vue'
import MainPage from './components/MainPage.vue'
// Main page instance
const mainpage_inst = new createApp({
render: () => h(MainPage),
}).$mount('#main-page')

Nuxt.js could not found component with errors "Failed to mount component: template or render function not defined."

I am having problems with my Nuxt.js site.
I have defined a page, with a dynamic slug param, like this:
/solutions/:slug
If I visit the page directly in the browser, it loads correctly!
But if I click the nuxt-link in my NavBar component, I get the following error in the console, and the page does not load:
vue.runtime.esm.js?2b0e:619
[Vue warn]: Failed to mount component: template or render function not defined.
found in
---> <Anonymous>
<Nuxt>
<Layouts/default.vue> at layouts/default.vue
<Root>
I have a default layout file that looks like this:
layouts/default.vue
<template>
<div>
<NavBar />
<Nuxt />
</div>
</template>
<script>
import NavBar from "~/components/Layout/NavBar"
export default {
components: {
NavBar,
},
}
</script>
My navbar contains the following nuxt-link:
components/Layout/NavBar.vue
<template>
<b-navbar wrapper-class="container" fixed-top>
<template slot="end">
<nuxt-link
:to="{
name: 'solutions-slug',
params: { slug: 'performance' },
}"
class="navbar-item"
target="self"
>
<span class="icon is-medium">
<i class="ic ic--larger ic-b1-graph-bars-chart-rise" />
</span>
<span class="label">
Performance
</span>
</nuxt-link>
</template>
</b-navbar>
</template>
I have a page, defined by the slug param:
pages/solutions/_slug.vue
<template>
<div class="solution">
This is my solution page.
</div>
</template>
I am trying to understand why clicking the nuxt-link fails to load the page, even though I see the URL change in the browser correctly.
Thanks
After version v2.13, Nuxt can auto-import your components when used in your templates.
check the nuxt.config.js if components attribute is true then you don't need to import your component on the .vue files.
in your layouts/default.vue remove script tag ;-)
<template>
<div>
<NavBar />
<Nuxt />
</div>
</template>
If you need to categorize your components by folder, do the following.
goto nuxt.config.js and change your components attribute
export default {
components: {
dirs: [
'~/components',
{
path : '~/components/site/',
prefix: 'Site'
},
{
path : '~/components/admin',
prefix: 'Admin'
},
{
path : '~/components/admin/sub',
prefix: 'AdminSub'
}
]
}
}
for example, we have these components :
components
| site
- header
| admin
- header
- footer
| sub
- header
- footer
when we need to call components just separate prefixes and component names with a dash or write camelcase.
in your layouts/default.vue remove script tag ;-)
<template>
<div>
<!-- with dash -->
<site-header></site-header>
<admin-header></admin-header>
<admin-sub-header></admin-sub-header>
<!-- cammel -->
<SiteHeader></SiteHeader>
<AdminHeader></AdminHeader>
<AdminSubHeader></AdminSubHeader>
</div>
</template>
Attention: For Root Components /components/nav.vue, We Must Use CammelCase <Nav/> and if we call this component like this <nav/> it doesn't work.
Probably the problem is not related to anything described above.
First, check if your configuration is correct. I see you are using 'nuxtjs/content' module, so you are probably using Contentful as well. In the past, I have encountered a similar situation ('Failed to mount component: template or render function not defined' issue) due to incorrect installation of the 'dotenv' module that I used to store variables.
In my case, the application did not load variables from the .env file. As a consequence, they went to the Contentful client unidentified and caused the js error. For some reason, this error did not always appear in the console. Instead, the above-mentioned Warn appeared.
Make sure you have the 'dotenv' module correctly installed (if you use it). I remember that in my case it was necessary to install 'nuxtjs/dotenv' instead of the usual dotenv.
Let me know if this is the case. Good luck

Vue $emit and #click handler behaving differently on Firefox

New to Vue and wrapping my head around the main concepts.
I currently have two separate components BodyHero.vue and SelectedHero.vue that I need to pass data from BodyHero.vue to SelectedHero.vue. Since there isn't a parent-child relationship between them I've set up a simple EventBus.
import Vue from 'vue'
export const EventBus = new Vue()
I've set up a simple emitter to pass information from BodyHero.vue to SelectedHero.vueupon click on router-link. (I'm using vue-router to route pages and the routes work appropriately - upon click the user is taken from BodyHero.vue to SelectedHero.vue.)
This is a portion of BodyHero.vue:
<template>
<div class="columns" style="margin: 0px 10px">
<div v-for="cryptoCurrency in firstFiveCryptoCurrencies" class="column">
<router-link to="/selected" #click.native="selectCryptoCurrency(cryptoCurrency)">
<div class="card">
<div class="card-image">
<figure class="image is-4by3">
<img :src="`/static/${cryptoCurrency.id}_logo.png`">
</figure>
</div>
<div class="card-content">
<p class="title is-5">{{ cryptoCurrency.name }}</p>
</div>
</div>
</router-link>
</div>
</div>
</template>
<script>
import { EventBus } from '../../event-bus.js'
const cryptoCurrencyData = require('../../cryptocurrency-data.json')
export default {
props: {},
name: 'bodyHero',
selectCryptoCurrency (cryptoCurrency) {
EventBus.$emit('cryptoCurrencySelected', cryptoCurrency)
}
And in SelectedHero.vue, upon created I listen for the emit.
created () {
EventBus.$on('cryptoCurrencySelected', cryptoCurrency => {
...
})
}
Now for the weird part. Chrome/Safari everything seems to work fine - the user is routed to the next component, the emit is successfully called and the cryptoCurrency object is appropriately passed.
For Firefox however, it appears that the EventBus.$emit('cryptoCurrencySelected', cryptoCurrency) doesn't seem to fire upon click (#click.native). So the user is directed to the next screen - and the object is empty. Not sure what's really the issue here or if I'm missing something. Would be happy to hear other opinions!

How to register vue components

I have recently started using Vue and I was looking for getting to use Vue.Draggable on my project. Due to framework and current setup in use I haven't been able to implement single-page-component .vue-files, and have instead simply written app & components with string-templates to .js -files.
I'm unable to figure out how to include draggable to my application.
Basic structure looks like follow:
#HTML
<div id="app">
<mycomponent></mycomponent>
</div>
<script src="vuedraggable.js"></script>
<script src="component.js"></script>
<script src="app.js"></script>
--
#app.js (simply a wrapper)
var vm = new Vue({
el: '#app',
});
--
#component.js
Vue.component('mycomponent',{
template: `
<form>
<h2>{{ form.label }}</h2>
<draggable v-model="form.fields" class="dragArea">
<div v-for="field in form.fields">
<field :field="field"></field>
</div>
</draggable>
</form>
`,
data: function() {
return {
form: { ... form-structure-to-be ...}
};
},
});
With this structure I'm getting error "vue.js:440 [Vue warn]: Unknown custom lement: - did you register the component correctly?"
I haven't been able to figure out either "require" nor "import" method and assume that both require bootstrapping. Due to environment I'm working with I'm having hard time installing libraries with NPM & strapping, and I would prefer to register components manually - is this possible and how?
Could someone please give me directions ?