Gridsome Full Calendar build error - no SSR - vue.js

I'm trying to use the Full Calendar vue component (https://github.com/fullcalendar/fullcalendar-vue) in a Gridsome project like so:
<template>
<div class="tabStaffManage">
<div>
<FullCalendar
ref="staffCalendar"
class="fullCalendar"
defaultView="dayGridMonth"
:events="calendarEvents"
:plugins="calendarPlugins"
:allDaySlot="false"
:header="{
center: 'dayGridMonth, timeGridDay',
right: 'prev, next'
}"
minTime="09:00:00"
:selectable="true"
maxTime="18:30:00"
#eventClick="onEventClick"
#select="onDateSelect"
:showNonCurrentDates="false"
></FullCalendar>
</div>
</div>
</template>
<script>
import { formatDate } from "#fullcalendar/core"
import FullCalendar from "#fullcalendar/vue"
import timeGridPlugin from "#fullcalendar/timegrid"
import dayGridPlugin from "#fullcalendar/daygrid"
import interactionPlugin from "#fullcalendar/interaction"
export default {
components: {
FullCalendar,
},
data() {
return {
calendarPlugins: [dayGridPlugin, timeGridPlugin, interactionPlugin],
}
},
}
</script>
This, however, produces an error on build:
Could not generate HTML for "/staff/dashboard/":
ReferenceError: Element is not defined
at Object.338 (node_modules/#fullcalendar/core/main.esm.js:102:0)
at __webpack_require__ (webpack/bootstrap:25:0)
at Module.552 (assets/js/page--src-pages-staff-dashboard-vue.ea5234e7.js:598:16)
at __webpack_require__ (webpack/bootstrap:25:0)
I understand that Full Calendar does not support SSR. So as per the Gridsome documentation (https://gridsome.org/docs/assets-scripts/#without-ssr-support) I did this to import the component:
I created an alias for it's dependencies in gridsome.config.js like so:
var path = require('path');
api.configureWebpack({
resolve: {
alias: {
"timeGridPlugin": path.resolve('node_modules', '#fullcalendar/timegrid'),
etc....
}
},
})
and required those plugins in the mounted() lifecycle hook:
mounted() {
if (!process.isClient) return
let timeGridPlugin = require('timeGridPlugin')
...
},
components: {
FullCalendar: () =>
import ('#fullcalendar/vue')
.then(m => m.FullCalendar)
.catch(),
}
I then wrapped the FullCalendar component in:
<ClientOnly>
<FullCalendar></FullCalendar>
</ClientOnly>
The extra dependencies required in the mounted() hook are included no problem.
However I now get the following error:
TypeError: Cannot read property '__esModule' of undefined
It seems that components() is failing to import the '#fullcalendar/vue' component.
Am I doing something wrong when importing the '#fullcalendar/vue' component?
Is there another way to include both the '#fullcalendar/vue' component and the plugin dependencies with no SSR?

Requiring the full calendar vue component in main.js by checking the gridsome client API and registering the component globally in vue seems to work and does what I expected:
// Include no SSR
if (process.isClient) {
const FullCalendar = require("#fullcalendar/vue").default
Vue.component("full-calendar", FullCalendar)
}
I also was not pointing to the default object when requiring the other modules in the component:
mounted() {
if (!process.isClient) return
let timeGridPlugin = require('timeGridPlugin').default
...
}

Related

Nuxt local import client only

I'm trying to use VuePlyr in Nuxt 2. Right now I have it working as a plugin /plugins/vue-plyr.js,
import Vue from 'vue'
import VuePlyr from '#skjnldsv/vue-plyr'
import 'vue-plyr/dist/vue-plyr.css'
Vue.use(VuePlyr)
but it is just used in one page, so I would like to remove it from the main bundle and just import it locally when used. I've tried this in my page (the template part was working when using the plugin).
<template>
<client-only>
<vue-plyr>
<div data-plyr-provider="vimeo" :data-plyr-embed-id="id" />
</vue-plyr>
</client-only>
</template>
<script>
import 'vue-plyr/dist/vue-plyr.css'
import Vue from 'vue'
export default {
async mounted () {
const VuePlyr = await import('#skjnldsv/vue-plyr')
Vue.use(VuePlyr)
}
}
</script>
but unfortunately, I'm getting this error
[Vue warn]: Unknown custom element: <vue-plyr> - did you register the component correctly?
Any idea how I could achieve this? Related with How to make a dynamic import in Nuxt?
You could import it like that
export default {
components: {
[process.client && 'VuePlyr']: () => import('#skjnldsv/vue-plyr'),
}
}
As mentioned in a previous answer.
In your nuxt config define the plugin as client only:
plugins: [
{ src: "~/plugins/vue-plyr.js", mode: "client" }
],
Then also make sure there's a client-only tag around the use of the component:
<template>
<client-only>
<vue-plyr>
<div data-plyr-provider="vimeo" :data-plyr-embed-id="id" />
</vue-plyr>
</client-only>
</template>
Edit: importing the component again in the mounted method isn't necessary if you added it as a plugin

Why do I receive in TypeError: Object(...) is not a function in Vuejs?

Hi I have installed Vuejs 3 and I am trying to embed a vimeo video with this library: import vueVimeoPlayer from 'vue-vimeo-player'.
I imported this in the main.js like this:
import vueVimeoPlayer from 'vue-vimeo-player'
Vue.use(vueVimeoPlayer)
My view is this one:
<template>
<div id="app">
<vueVimeoPlayer
ref="player"
:video-url="url"
:player-height="500"
:player-width="500"
:autoplay="true"
/>
<div #click="updateUrl()">click me</div>
<div #click="errorUrl()">error pls</div>
</div>
</template>
<script>
import { vueVimeoPlayer } from 'vue-vimeo-player';
export default {
name: 'App',
components: {
vueVimeoPlayer,
},
data () {
return {
url: "https://vimeo.com/605358147/b5c4f01703",
};
},
methods: {
updateUrl() {
this.url = "https://vimeo.com/604413787/dd09a5711";
},
errorUrl() {
this.url = "https://vimeo.com/605266340/a7aa996ffc";
},
},
};
</script>
I receive this huge error:
TypeError: Object(...) is not a function
at Proxy.render (index.es.js?558f:174:1)
at VueComponent.Vue._render (vue.runtime.esm.js?2b0e:3569:1)
at VueComponent.updateComponent (vue.runtime.esm.js?2b0e:4081:1)
at Watcher.get (vue.runtime.esm.js?2b0e:4495:1)
at new Watcher (vue.runtime.esm.js?2b0e:4484:1)
etc
etc
So I wonder what am I doing wrong? Because I saw it working check this url:
https://codesandbox.io/s/m4z5v63jqy
Thanks
Vue.use is not directly available anymore in Vue3, you would have to use createApp().use
That's why you're seeing error. Try like this
Make sure you've install correct version of vue-vimeo-player to support Vue3
import { createApp } from 'vue'
import App from './App.vue'
import vueVimeoPlayer from 'vue-vimeo-player'
const app = createApp(App)
app.use(vueVimeoPlayer).mount("#app");

Vue 2: How to unit test component that uses Chart.js (vue-chart-3)

I have a vue2 project that uses ClassComponents and Chart.js (via vue-chart-3). I now have a simple component that wraps a DoughnutChart to manage data and stuff.
DBOverviewDoughnut.vue
<template>
<div>
<p>test</p>
<DoughnutChart ref="doughnutRef" :chartData="sharesData"></DoughnutChart>
</div>
</template>
<script lang="ts">
import Component from 'vue-class-component';
import Vue from 'vue';
import { DoughnutChart, ExtractComponentData } from 'vue-chart-3';
import { Prop, Ref } from 'vue-property-decorator';
import { ChartData } from 'chart.js';
#Component({ components: { DoughnutChart } })
export default class DBOverviewDoughnut extends Vue {
#Prop()
private sharesData!: ChartData<'doughnut'>;
#Ref()
private readonly doughnutRef!: ExtractComponentData<typeof DoughnutChart>;
created(): void {
this.assignColors();
}
mounted(): void {
if (this.doughnutRef.chartInstance) {
console.log(this.doughnutRef.chartInstance.data);
}
}
assignColors(): void {
this.sharesData.datasets[0].backgroundColor = [
'#77CEFF',
'#0079AF',
'#123E6B',
'#97B0C4',
'#A5C8ED',
];
}
}
</script>
Starting the program it will work fine and I can access the chartInstance inside the mounted hook.
But now I want to unit test my component. I thought on setting the propsData which will be the input data for the chart.
DBOverviewDoughnut.spec.ts
import DBOverviewDoughnut from '#/components/dashboard/DBOverviewDoughnut.vue';
import { mount, Wrapper } from '#vue/test-utils';
import { Share } from '#/Share';
describe('DBOverviewDoughnut', () => {
let cut: Wrapper<DBOverviewDoughnut>;
it('should render the correct amount of sections', () => {
cut = mount(DBOverviewDoughnut, {
propsData: {
sharesData: {
labels: ['TestShare1', 'TestShare2', 'TestShare3'],
datasets: [{ data: [11, 22, 33] }]
}
}
});
const chart = cut.findComponent({ ref: 'doughnutRef' });
console.log(chart);
});
});
Using shallowMount() doesn't seem to work, because I only get this from logging (no chartInstance and its properties as in the production code):
VueWrapper {
isFunctionalComponent: undefined,
_emitted: [Object: null prototype] {},
_emittedByOrder: [],
selector: { ref: 'doughnutRef' }
}
So I thought maybe I have to mount the component because the DoughnutChart is also a wrapper around the Chart.js charts. But when using mount() I get the following error:
console.error node_modules/vue/dist/vue.runtime.common.dev.js:621
[Vue warn]: `createElement()` has been called outside of render function.
console.error node_modules/vue/dist/vue.runtime.common.dev.js:621
[Vue warn]: Error in render: "Error: [vue-composition-api] must call Vue.use(VueCompositionAPI) before using any function."
found in
---> <DoughnutChart>
<DBOverviewDoughnut>
<Root>
I don't really know what I'm doing wrong. I registered the VueCompostionAPI in my main.ts:
import Vue from 'vue';
import { Chart, registerables } from 'chart.js';
import App from './App.vue';
import router from './router';
import store from './store';
import VueCompositionAPI from '#vue/composition-api';
Chart.register(...registerables);
Vue.use(VueCompositionAPI);
new Vue({
router,
store,
render: (h) => h(App),
}).$mount('#app');
Following this post doesn't solve the problem either.
Anyone got an idea what's going wrong? I'm a bit confused if the error has to do with my test setup or with the installation of chart.js or compositionApi.
You need to use VueCompositionAPI inside your spec as well when you mount the component. You can do this by creating a local Vue instance inside your spec, adding VueCompositionAPI as a plugin to the instance and using the instance when you mount the component. https://vue-test-utils.vuejs.org/api/options.html#localvue
Using localVue is really what I should have thought about. This and installing the canvas-package works, that I get additional information about my Ref-Element. However I still have to figure out what to do with it.
#AdriHM I want to test if the rendered chat gets the correct data I guess. Or if it displays it correctly (e.g. display the correct amount of sections) But the longer I think about it the less I'm sure it's the right thing to test. I don't want to test the Chart.js API though.

Personal Vue 3 component package missing template or render function

I recently uploaded my own Vue 3 component to NPM to make it usable for others. When using it in other projects it gives this warning:
[Vue warn]: Component is missing template or render function.
at <VueToggle id="1" title="Toggle me" >
at <App>
What could be the reason this is happening? The way I am building and publishing the app is by running this code.
import VueToggle from "./components/VueToggle";
export default {
install(app) {
app.component("vue-toggle", VueToggle);
}
};
And then running this build command in my package.json:
"build-library": "vue-cli-service build --target lib --name vue-toggle-component ./src/install.js",
How I use my component in a different project:
<template>
<VueToggle id="1" title="Toggle me"/>
</template>
<script>
import VueToggle from 'vue-toggle-component';
export default {
name: 'App',
components: {
VueToggle,
}
}
</script>
The component itself:
<template>
<section class="wrapper" :class="{dark: darkTheme}" :title="title">
<input
:id="id"
:name="name"
v-model="toggleState"
class="toggle"
type="checkbox"
#click="toggleState = !toggleState"
/>
<label :for="id" class="toggler" :style="[toggleState && {'background': activeColor}]"/>
<span class="title" v-text="title" #click="toggleState = !toggleState"/>
</section>
</template>
<script>
export default {
name: 'VueToggle',
props: {
activeColor: {type: String, default: '#9FD6AE'},
darkTheme: {type: Boolean, default: false},
id: {type: String, required: true},
name: {type: [String, Boolean], default: false},
title: {type: String, required: true},
toggled: {type: Boolean, default: false},
},
data() {
return {
toggleState: this.toggled
}
},
}
</script>
The package: https://www.npmjs.com/package/vue-toggle-component
The following concerns a new project using Vue 3 & Typescript created with Quasar CLI (v2 beta). Although I'm getting the same error reported by the OP, my source layout might be different as I'm not using single-file components.
[Vue warn]: Component is missing template or render function.
I resolved the above issue by specifying the vue file as the component source. I typically split my components into separate vue and ts files.
The related fragment:
MyComponent: require("./components/My.vue").default
In context:
export default defineComponent({
name: "App",
components: {
MyComponent: require("./components/My.vue").default
},
setup() {
...
To quiet the linters, I have the following es-lint comments
export default defineComponent({
name: "App",
components: {
// eslint-disable-next-line #typescript-eslint/no-unsafe-assignment, #typescript-eslint/no-unsafe-member-access, #typescript-eslint/no-var-requires
MyComponent: require("./components/My.vue").default
},
Ideally, the import statement would be used instead of the inline require assignment.
The problem is that you are trying to import 'vue-toggle-component' like a Vue component, when your library is exporting a Vue plugin (made up of an install function that declares your component).
There are two way to fix this.
Solution 1
Remove the component import entirely.
// component.vue (separate project)
<template>
<VueToggle id="1" title="Toggle me"/>
</template>
<script>
export default {
name: 'App'
}
</script>
Then import your library plugin and styles in index.js of the separate project. You should activate the plugin using Vue.use().
// index.js (separate project)
import { createApp } from "vue";
import App from './App.vue';
import VueToggleComponent from 'vue-toggle-component';
import '#vue-toggle-component/dist/style.css';
const app = createApp(App);
app.mount('#app');
app.use(VueToggleComponent);
Your component should now be imported by default into the project and can be used from anywhere.
Solution 2
Add anonymized exports for each component for individual importing.
// install.js
import VueToggle from "./components/VueToggle";
export default {
install(app) {
app.component("vue-toggle", VueToggle);
}
};
export { default as VueToggle } from "./components/VueToggle";
Then import the component as a non-default export in your separate project.
// component.vue (separate project)
<template>
<VueToggle id="1" title="Toggle me"/>
</template>
<script>
import { VueToggle } from 'vue-toggle-component';
export default {
name: 'App',
components: {
VueToggle,
}
}
</script>
Finally, install your library's styles.
// index.js (separate project)
import { createApp } from "vue";
import App from './App.vue';
import '#vue-toggle-component/dist/style.css';
const app = createApp(App);
app.mount('#app');
Conclusion
Personally, I think Solution #2 is more flexible and intuitive to use.
When you use your component, replace
import VueToggle from 'vue-toggle-component';
with
import VueToggle from 'vue-toggle-component.vue';
Or if component users use webpack, they may specify in config:
resolve: {
extensions: ['.vue', '.ts', '.js']
}

Failed to mount component: template or render function not defined. (Vue using plugins)

I'm trying to use this countdown-timer / on-github inside one of my single-file-components.
Even though I'm importing it like mentioned in the example, I'm getting this error:
21:27:20.553 [Vue warn]: Failed to mount component: template or render function not defined.
found in
---> <CircularCountDownTimer>
<Visualization> at src/views/Visualization.vue
<App> at src/App.vue
<Root> vue.runtime.esm.js:619
VueJS 17
run es6.promise.js:75
notify es6.promise.js:92
flush _microtask.js:18
Looking up the warning I've found the following pages:
vue-router-problem1
vue-router-problem2
What I've gathered/attempted from that:
Change vue-cli config to use runtime compiler (No change)
22:02:49.722 [Vue warn]: Failed to mount component: template or render function not defined.
found in
---> <CircularCountDownTimer>
<Visualization> at src/views/Visualization.vue
<App> at src/App.vue
<Root> vue.esm.js:628
VueJS 18
run es6.promise.js:75
notify es6.promise.js:92
flush _microtask.js:18
Import in Main.js with Vue.use(Plugin) (Same error)
Import it in the router component (Same error)
EDIT:
I've also looked at this question nested-components in vuejs
And changed the component registration like so:
beforeCreate() {
this.$options.components.CircularCountDownTimer = require('vue-circular-count-down-timer')
},
None of the above made this plugin work for me and I don't really understand why.
Here is my code:
main.js
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import CircularCountDownTimer from "vue-circular-count-down-timer";
Vue.use(CircularCountDownTimer)
Vue.config.productionTip = false;
export const eventBus = new Vue();
new Vue({
router,
render: h => h(App)
}).$mount("#app");
component (Visualization.vue):
<template>
<div id="content">
<circular-count-down-timer
v-for="counter in counters" :key="counter.id"
:initial-value="counter.seconds"
:show-minute="false"
:show-hour="false"
:show-negatives="false"
:second-label="counter.name"
:steps="1"
/>
</div>
</template>
<script>
import CircularCountDownTimer from "vue-circular-count-down-timer";
export default {
name: "Visualization",
components: {
CircularCountDownTimer
},
data() {
return {
counters: []
}
},
mounted() {
if (localStorage.getItem("delays")) {
try {
this.counters = JSON.parse(localStorage.getItem("delays"));
} catch (e) {
console.debug(e);
localStorage.removeItem("delays");
}
}
}
};
</script>
Also this is the data when reading from localStorage:
[{"id":1,"seconds":"60","name":"asdf"}]
Dependencies in package.json:
"dependencies": {
"#babel/preset-env": "^7.5.4",
"core-js": "^2.6.5",
"vue": "^2.6.10",
"vue-awesome-countdown": "^1.0.16",
"vue-circular-count-down-timer": "^1.0.4",
"vue-grid-layout": "^2.3.4",
"vue-router": "^3.0.3"
}
vue-circular-count-down-timer is a plugin, so this bit of the code seems to be correct:
import CircularCountDownTimer from "vue-circular-count-down-timer";
Vue.use(CircularCountDownTimer)
If you take a look at the source code for the plugin you'll see that all it does is register a component globally called circular-count-down-timer:
https://github.com/noorzaie/vue-circular-count-down-timer/blob/master/src/components/index.js
The problem occurs when you do this:
import CircularCountDownTimer from "vue-circular-count-down-timer";
export default {
name: "Visualization",
components: {
CircularCountDownTimer
},
You're just importing the plugin again and then trying to use it as a component. But it isn't a component, it's a plugin. Vue doesn't know this, it just sees an object without a template or render function.
Get rid of the local component import and it should just use the globally registered component instead.