Custom Directive in nuxt js - vue.js

is there a way how to write a custom directive in nuxt js, which will work for ssr and also for frontend (or even for ssr only)?
I tried it like in following documentation:
https://nuxtjs.org/api/configuration-render#bundleRenderer
so I added this code:
module.exports = {
render: {
bundleRenderer: {
directives: {
custom1: function (el, dir) {
// something ...
}
}
}
}
}
to nuxt.config.js
then I use it in template as:
<component v-custom1></component>
but it doesn't work, it just throw the frontend error
[Vue warn]: Failed to resolve directive: custom1
And it doesn't seem to be working even on server side.
Thanks for any advice.

If you want use custom directives in Nuxt you can do the following:
Create a file inside plugins folder, for example, directives.js
In nuxt.config.js add something like plugins: ['~/plugins/directives.js']
In your new file add your custom directive like this:
import Vue from 'vue'
Vue.directive('focus', {
inserted: (el) => {
el.focus()
}
})

How To Create A Directive
You can make directives run on the client by adding the .client.js extension to your directives file. This works for SSR and static rendering.
// plugins/directive.client.js
import Vue from 'vue'
Vue.directive('log-inner-text', {
inserted: el => {
console.log(el.innerText)
}
})
How To Insert Directives
In your nuxt.config.js file add it as a plugin like this.
plugins: [
'~/plugins/directive.client.js'
]
Don't forget to save your directive in the plugins folder.
How To Use A Directive
<div v-log-inner-text>Hello</div>
Console logs
> "Hello"
I have written a medium article that goes a lot more in-depth on how this works. It shows you how to make a directive that makes an element animate into view on scroll: Nuxt - Creating Custom Directives For Static & SSR Sites

Tested in nuxt-edge ( its nuxt 2.0 that will be out in this or next month, but its pretty stable as it is).
nuxt.config.js
render: {
bundleRenderer: {
directives: {
cww: function (vnode, dir) {
const style = vnode.data.style || (vnode.data.style = {})
style.backgroundColor = '#ff0016'
}
}
}
}
page.vue
<div v-cww>X</div>
Resulting html from server:
<div style="background-color:#ff0016;">X</div>

For anyone else coming here, the accepted answer allows you to run an SSR-only directive. This is helpful but a little unintuitive if you want to have a directive run everywhere.
If you only use nuxt.config.js to implement a directive via render, it will not be supported on the client-side without also adding a directive plugin and adding it to the config (see the How to Create a Directive Answer).
To test this out, try this experiment:
Follow the instructions to create directive using plugins (make one called loading)
Vue.directive('loading', function (el, binding) {
console.log('running loading directive client side')
})
Add this to your nuxt.config:
render: {
bundleRenderer: {
directives: {
loading (element, binding) {
console.log('running loading directive server side')
}
}
}
}
Use the directive on a Vue page file like:
<div v-loading="true">Test</div>
On page load, you will see both the client-side and SSR directives run. And if you remove the client-side directive, you will see errors thrown like the OP had: [Vue warn]: Failed to resolve directive: loading.
Tested on nuxt 2.12.2.

Related

Vue3 isCustomElement seems not working and vue router clash with custom element

I have VUE3 app with vue-router using third party web components imported as custom elements, I set the isCustomElement option to ignore custom element, but it seems not to be taken into account.
I set the "vue": { "runtimeCompiler": true } in package.json.
I set app.config.isCustomElement = (tag) => tag.startsWith('bdl-') to ignore customElements in main.js
I use web components - custom elements starting with bdl- in About.vue:
<template>
<div class="about">
<h1>This is an about page</h1>
<bdl-fmi></bdl-fmi>
<bdl-range></bdl-range>
<bdl-chartjs-time></bdl-chartjs-time>
</div>
</template>
However, it seems that it's not taken into account and the browser console log contains warning [Vue warn]: Failed to resolve component: bdl-fmi and custom elements fails to render in router view.
Tried VUE2 and the configuration Vue.config.ignoredElements = ['bdl-chartjs'] is working and similar application with vue-router do not try to interpret third party custom elements and renders as expected.
Any thoughts on isCustomElement will be appreciated.
Sample with this issue can be seen at CODESANDBOX: https://codesandbox.io/s/vue-3-router-with-bodylightjs-components-h8v50
The app.config.isCustomElement flag is intended for projects that use the runtime compiler, which could be enabled in a Vue CLI project via the runtimeCompiler flag in vue.config.js:
// vue.config.js
module.exports = {
runtimeCompiler: true,
}
But you could also resolve this without the runtime compiler by removing app.config.isCustomElement, and configuring vue-loader directly:
// vue.config.js
module.exports = {
chainWebpack: config => {
config.module
.rule('vue')
.use('vue-loader')
.tap(options => {
options.compilerOptions = {
...options.compilerOptions,
isCustomElement: tag => tag.startsWith('bdl-')
}
return options
})
}
}
I meet the problem because element-plus icon component not called correctly. I have to import ElementPlusIconsVue in my main.js and global register it like the following:
import * as ElementPlusIconsVue from '#element-plus/icons-vue'
const app = createApp(App)
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
app.component(key, component)
}
// then I can call the icon in my vue file directly
<Refresh
style="width:30px;cursor: pointer;float: right;"
#click="get_bili"
/>

How to dynamically load a vue directive?

For performance issues I want to dynamically load a Vue directive.
But when I try to use the same method as for the dynamic components it doesn't work
directives: {
mydirective: () => {
return import('#/src/directives/mydirective.js');
}
}
// In a .vue file
If someone has already done that with success it could be nice to share.
Thanks 😊

Vue/Nuxt: How to define a global method accessible to all components?

I just want to be able to call
{{ globalThing(0) }}
in templates, without needing to define globalThing in each .vue file.
I've tried all manner of plugin configurations (or mixins? not sure if Nuxt uses that terminology.), all to no avail. It seems no matter what I do, globalThing and this.globalThing remain undefined.
In some cases, I can even debug in Chrome and see this this.globalThing is indeed defined... but the code crashes anyway, which I find very hard to explain.
Here is one of my many attempts, this time using a plugin:
nuxt.config.js:
plugins: [
{
src: '~/plugins/global.js',
mode: 'client'
},
],
global.js:
import Vue from 'vue';
Vue.prototype.globalFunction = arg => {
console.log('arg', arg);
return arg;
};
and in the template in the .vue file:
<div>gloabal test {{globalFunction('toto')}}</div>
and... the result:
TypeError
_vm.globalFunction is not a function
Here's a different idea, using Vuex store.
store/index.js:
export const actions = {
globalThing(p) {
return p + ' test';
}
};
.vue file template:
test result: {{test('fafa')}}
.vue file script:
import { mapActions } from 'vuex';
export default {
methods: {
...mapActions({
test: 'globalThing'
}),
}
};
aaaaaaaaand the result is.........
test result: [object Promise]
OK, so at least the method exists this time. I would much prefer not to be forced to do this "import mapActions" dance etc. in each component... but if that's really the only way, whatever.
However, all I get is a Promise, since this call is async. When it completes, the promise does indeed contain the returned value, but that is of no use here, since I need it to be returned from the method.
EDIT
On the client, "this" is undefined, except that..... it isn't! That is to say,
console.log('this', this);
says "undefined", but Chrome's debugger claims that, right after this console log, "this" is exactly what it is supposed to be (the component instance), and so is this.$store!
I'm adding a screenshot here as proof, since I don't even believe my own eyes.
https://nuxtjs.org/guide/plugins/
Nuxt explain this in Inject in $root & context section.
you must inject your global methods to Vue instance and context.
for example we have a hello.js file.
in plugins/hello.js:
export default (context, inject) => {
const hello = (msg) => console.log(`Hello ${msg}!`)
// Inject $hello(msg) in Vue, context and store.
inject('hello', hello)
// For Nuxt <= 2.12, also add 👇
context.$hello = hello
}
and then add this file in nuxt.config.js:
export default {
plugins: ['~/plugins/hello.js']
}
Use Nuxt's inject to get the method available everywhere
export default ({ app }, inject) => {
inject('myInjectedFunction', (string) => console.log('That was easy!', string))
}
Make sure you access that function as $myInjectedFunction (note $)
Make sure you added it in nuxt.config.js plugins section
If all else fails, wrap the function in an object and inject object so you'd have something like $myWrapper.myFunction() in your templates - we use objects injected from plugins all over the place and it works (e.g. in v-if in template, so pretty sure it would work from {{ }} too).
for example, our analytics.js plugin looks more less:
import Vue from 'vue';
const analytics = {
setAnalyticsUsersData(store) {...}
...
}
//this is to help Webstorm with autocomplete
Vue.prototype.$analytics = analytics;
export default ({app}, inject) => {
inject('analytics', analytics);
}
Which is then called as $analytics.setAnalyticsUsersData(...)
P.S. Just noticed something. You have your plugin in client mode. If you're running in universal, you have to make sure that this plugin (and the function) is not used anywhere during SSR. If it's in template, it's likely it actually is used during SSR and thus is undefined. Change your plugin to run in both modes as well.
This would be the approach with Vuex and Nuxt:
// store/index.js
export const state = () => ({
globalThing: ''
})
export const mutations = {
setGlobalThing (state, value) {
state.globalThing = value
}
}
// .vue file script
export default {
created() {
this.$store.commit('setGlobalThing', 'hello')
},
};
// .vue file template
{{ this.$store.state.globalThing }}

“window is not defined” in Nuxt.js

I get an error porting from Vue.js to Nuxt.js.
I am trying to use vue-session in node_modules. It compiles successfully, but in the browser I see the error:
ReferenceError window is not defined
node_modules\vue-session\index.js:
VueSession.install = function(Vue, options) {
if (options && 'persist' in options && options.persist) STORAGE = window.localStorage;
else STORAGE = window.sessionStorage;
Vue.prototype.$session = {
flash: {
parent: function() {
return Vue.prototype.$session;
},
so, I followed this documentation:
rewardadd.vue:
import VueSession from 'vue-session';
Vue.use(VueSession);
if (process.client) {
require('vue-session');
}
nuxt.config.js:
build: {
vendor: ['vue-session'],
But I still cannot solve this problem.
UPDATED AUGUST 2021
The Window is not defined error results from nodejs server side scripts not recognising the window object which is native to browsers only.
As of nuxt v2.4 you don't need to add the process.client or process.browser object.
Typically your nuxt plugin directory is structured as below:
~/plugins/myplugin.js
import Vue from 'vue';
// your imported custom plugin or in this scenario the 'vue-session' plugin
import VueSession from 'vue-session';
Vue.use(VueSession);
And then in your nuxt.config.js you can now add plugins to your project using the two methods below:
METHOD 1:
Add the mode property with the value 'client' to your plugin
plugins: [
{ src: '~/plugins/myplugin.js', mode: 'client' }
]
METHOD 2: (Simpler in my opinion)
Rename your plugin with the extension .client.js and then add it to your plugins in the nuxt.config.js plugins. Nuxt 2.4.x will recognize the plugin extension as to be rendered on the server side .server.js or the client side .client.js depending on the extension used.
NOTE: Adding the file without either the .client.js or .server.js extensions will render the plugin on both the client side and the server side. Read more here.
plugins: ['~/plugins/myplugin.client.js']
There is no window object on the server side rendering side. But the quick fix is to check process.browser.
created(){
if (process.browser){
console.log(window.innerWidth, window.innerHeight);
}
}
This is a little bit sloppy but it works. Here's a good writeup about how to use plugins to do it better.
Its all covered in nuxt docs and in faq. First you need to make it a plugin. Second you need to make your plugin client side only
plugins: [
{ src: '~/plugins/vue-notifications', mode: 'client' }
]
Also vendor is not used in nuxt 2.x and your process.client not needed if its in plugin with ssr false
In Nuxt 3 you use process.client like so:
if (process.client) {
alert(window);
}
If you've tried most of the answers here and it isn't working for you, check this out, I also had the same problem when using Paystack, a payment package. I will use the OP's instances
Create a plugin with .client.js as extension so that it can be rendered on client side only. So in plugins folder,
create a file 'vue-session.client.js' which is the plugin and put in the code below
import Vue from 'vue'
import VueSession from 'vue-session'
//depending on what you need it for
Vue.use(VueSession)
// I needed mine as a component so I did something like this
Vue.component('vue-session', VueSession)
so in nuxt.config.js, Register the plugin depending on your plugin path
plugins:[
...
{ src: '~/plugins/vue-session.client.js'},
...
]
In index.vue or whatever page you want to use the package... import the package on mounted so it is available when the client page mounts...
export default {
...
mounted() {
if (process.client) {
const VueSession = () => import('vue-session')
}
}
...
}
You can check if you're running with client side or with the browser. window is not defined from the SSR
const isClientSide: boolean = typeof window !== 'undefined'
Lazy loading worked for me. Lazy loading a component in Vue is as easy as importing the component using dynamic import wrapped in a function. We can lazy load the StepProgress component as follows:
export default {
components: {
StepProgress: () => import('vue-step-progress')
}
};
On top of all the answers here, you can also face some other packages that are not compatible with SSR out of the box and that will require some hacks to work properly. Here is my answer in details.
The TLDR is that you'll sometimes need to:
use process.client
use the <client-only> tag
use a dynamic import if needed later on, like const Ace = await import('ace-builds/src-noconflict/ace')
load a component conditionally components: { [process.client && 'VueEditor']: () => import('vue2-editor') }
For me it was the case of using apex-charts in Nuxt, so I had to add ssr: false to nuxt.config.js.

VueJs warning when create custom directive plugin

I use Vuejs2 with a server side rendering, and I have create a plugin. When I declare a directive and use this on a DOM element, I have many warnings, but my directive working:
Here is my code for directive on plugins:
Vue.directive("htl-mouse-tracker", {
bind(el, binding, vnode, oldVnode) {
console.log(el)
}
})
and I use my directive like this:
button(v-on:click="toggleMobileNavigation", v-htl-mouse-tracker).AppHeader__headerButton