How fix __dirname not defined when using electron events with Vue? - vue.js

I used nklayman/vue-cli-plugin-electron-builder to create an electron app prepared with Vue/Vuex. It ships with files main.js, background.js including Vue component starting point. But I can't get the events to work. My attempt below yields Uncaught ReferenceError: __dirname is not defined when rendering (compile is fine).
Component: Splash.vue
<template>
<div #click="open">open</div>
</template>
<script>
const { ipcMain } = require('electron')
export default {
methods: {
open()
{
ipcMain.on('my-open-event', (event, arg) => {
console.log(event, arg)
})
}
}
}
</script>
background.js
import { app, protocol, BrowserWindow } from 'electron'
...
app.on('my-open-event', async () => {
try {
"Will call some executable here";
} catch (e) {
console.error(e)
}
})
main.js
import Vue from 'vue'
import App from './App.vue'
import store from './store'
Vue.config.productionTip = false
new Vue({
store,
render: h => h(App)
}).$mount('#app')
Full error:
Uncaught ReferenceError: __dirname is not defined
at eval (webpack-internal:///./node_modules/electron/index.js:4)
at Object../node_modules/electron/index.js (chunk-vendors.js:1035)
at __webpack_require__ (app.js:849)
at fn (app.js:151)
at eval (webpack-internal:///./node_modules/cache-loader/dist/cjs.js?!./node_modules/babel-loader/lib/index.js!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/components/Splash.vue?vue&type=script&lang=js&:6)
at Module../node_modules/cache-loader/dist/cjs.js?!./node_modules/babel-loader/lib/index.js!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader/lib/index.js?!./src/components/Splash.vue?vue&type=script&lang=js& (app.js:986)
at __webpack_require__ (app.js:849)
at fn (app.js:151)
at eval (webpack-internal:///./src/components/Splash.vue?vue&type=script&lang=js&:2)
at Module../src/components/Splash.vue?vue&type=script&lang=js& (app.js:1271)
Any ideas what I'm doing wrong?

To solve this I created a file vue.config.js in project root with content
module.exports = {
pluginOptions: {
electronBuilder: {
nodeIntegration: true
}
}
}

There are two processes in electron, the main process and the renderer process. Your Vue component is the renderer process. In order to communicate between these two processes, you need inter-processes communication. So in your case, you'd define a channel perhaps in background.js using ipcMain. You'd write something like:
ipcMain.on("my-custom-channel", (event, args) => handleEvent(event, args));
Then in your Vue component, you'd use the renderer process, ipcRendere, such as:
import { ipcRenderer } from "electron";
export default {
methods: {
open() {
ipcRenderer.send('my-custom-channel', "hello from Vue!")
}
}
}
Point is: you cannot use app.on for your custom events. app.on handles predefined electron events. Use ipcMain.on instead.
reference: https://www.electronjs.org/docs/api/ipc-main

You can apply the solution described on this post
How to import ipcRenderer in vue.js ? __dirname is not defined
In this way you can call window.ipcRenderer.send(channel, args...) from vue files.
Just make sure you configure preload.js on vue.config.js:
// vue.config.js - project root
module.exports = {
pluginOptions: {
electronBuilder: {
preload: 'src/preload.js' //make sure you have this line added
}
}
}
Another solution can be found here https://medium.com/swlh/how-to-safely-set-up-an-electron-app-with-vue-and-webpack-556fb491b83 and it use __static to refer to preload file instead of configure it on vue.config.js. To make it work you can disable preload es-lint warning inside of BrowserWindow constructor:
// eslint-disable-next-line no-undef
preload: path.resolve(__static, 'preload.js')
And make sure you added preload.js file on /public folder

Related

Configuring Pusher on Vue3

I have a project in Vue3 and want to implement a real time API or a web socket. So I attempted to use pusher using Vue third part libraries which are pusher-vue and vue-pusher. Using pusher-vue I am getting the error: Uncaught TypeError: e.prototype is undefined. Using vue-pusher I am getting the error: Uncaught TypeError: Vue.prototype is undefined. The following are the libraries' configurations:
PUSHER VUE
Component.vue
export default{
channels: {
applications_channel: {
subscribeOnMount: true,
subscribed(){
console.log("Some text")
},
bind:{
add_application_event(data){
console.log(data)
}
}
}
}
}
main.js
createApp(App)
.use(PusherVue, {
app_key: "MY_KEY",
cluster: 'MY_CLUSTER',
debug: true,
debugLevel: "all"
})
.mount("#app")
VUE PUSHER
Component.vue
export default{
read(){
var channel = this.$pusher.subscribe('applications-channel')
channel.bind('add-application-event', ({ log }) => {
console.log(log);
})
}
}
main.js
createApp(App)
.use(require("vue-pusher"), {
api_key: "MY_KEY",
options: {
cluster: 'MY_CLUSTER',
ecrypted: true,
}
})
.mount("#app")
May you please help with how can I configure this on Vue3 or recommend any beginner friendly alternatives to achieve the same functionality on Vue3.
Both pusher-vue and vue-pusher were built for Vue 2, so you need to use the Vue 3 migration build to make the library work in your project.
To setup your Vue CLI scaffolded project:
Install the Vue compatibility build and SFC compiler that matches your Vue build version (i.e., install #vue/compat#^3.1.0 and #vue/compiler-sfc#^3.1.0 if you have vue#^3.1.0 in package.json):
npm i -S #vue/compat#^3.1.0
npm i -S #vue/compiler-sfc#^3.1.0
Configure Webpack to alias vue to the #vue/compat build, and set vue-loader's compatibility mode to Vue 2:
// vue.config.js
module.exports = {
chainWebpack: config => {
config.resolve.alias.set('vue', '#vue/compat')
config.module
.rule('vue')
.use('vue-loader')
.tap(options => {
return {
...options,
compilerOptions: {
compatConfig: {
MODE: 2
}
}
}
})
}
}
demo: vue-pusher in Vue 3 w/migration build
However, vue-pusher 1.1.0 seems to only expose a new instance of Pusher (from pusher-js) as this.$pusher on the Vue instance. That code could easily be migrated to Vue 3 as a plugin:
// plugins/pusher.js
export default (app, { apiKey, ...options }) => {
const Pusher = require('pusher-js')
app.config.globalProperties.$pusher = new Pusher(apiKey, options)
}
// main.js
const { createApp } = require('vue')
import App from './App.vue'
import PusherPlugin from './plugins/pusher'
createApp(App)
.use(PusherPlugin, { apiKey: 'YOUR_API_KEY', cluster: 'YOUR_CLUSTER' })
.mount('#app')
demo: pusher-js in Vue 3

How do I mock this Vue injection?

I have a Vue 3 component that, when mounted in tests, cause warnings:
console.warn node_modules/#vue/runtime-core/dist/runtime-core.cjs.js:40
[Vue warn]: injection "Symbol(VueToastification)" not found.
at <ModifyJob ref="VTU_COMPONENT" >
at <VTUROOT>
I assume it's this one complaining https://github.com/Maronato/vue-toastification/blob/master/composition/index.js#L30.
I have nearly 100 of these warnings, so it's kind of hard to read test-run output. I've tried to mock provide for this dependency, but I can't seem to succeed:
let provide = {}
provide[VueToastification] = VueToastification
provide['VueToastification'] = VueToastification
provide[Symbol(VueToastification)] = VueToastification
provide[Symbol('VueToastification')] = VueToastification
provide['Symbol(VueToastification)'] = VueToastification
let options = {
global: {
provide: provide,
}
}
mount(ModifyJob, options)
Is this some Vue2/Vue3 incompatibility or do I just not understand the docs at https://vue-test-utils.vuejs.org/v2/api/#global-provide ? Can someone help me get rid of these warnings, ideally by allowing me to inject a mock so I can test that toasts are made?
That error actually indicates that the plugin isn't installed in the test Vue instance. You could make VueToastification available to the component under test through the global.plugins mounting option:
import { shallowMount } from '#vue/test-utils'
import MyComponent from '#/components/MyComponent.vue'
import VueToastificationPlugin from 'vue-toastification'
it('initializes', () => {
shallowMount(MyComponent, {
global: {
plugins: [VueToastificationPlugin]
}
})
})
Alternatively, if you want to verify that toast() (from VueToastification's useToast()) is called, you could mock vue-toastification:
import { shallowMount } from '#vue/test-utils'
import MyComponent from '#/components/MyComponent.vue'
jest.mock('vue-toastification')
it('should call toast', () => {
const toast = jest.fn()
require('vue-toastification').useToast.mockReturnValueOnce(toast)
shallowMount(MyComponent).vm.showToast()
expect(toast).toHaveBeenCalled()
})
I solved setting a global list of plugins according to https://next.vue-test-utils.vuejs.org/api/#config-global:
// In a jest.setup.js file
import { config } from "#vue/test-utils";
import VueToastificationPlugin from "vue-toastification";
config.global.plugins = [VueToastificationPlugin];
// In your jest.config.js
module.exports = {
...
setupFilesAfterEnv: ["./jest.setup.js"],
};

How to mock modules in storybook's stories?

I have a Vue component which includes some external modules with complicated logic. For example:
// Component.vue
import Vue from 'vue';
import { externalModule } from '#/path/to/module';
export default {
methods: {
useExternalModule() {
externalModule.doSomethig();
}
}
};
Is it possible to mock the externalModule inside the story?
I'm using Storybook v6.
You can create a __mocks__ folder to put your mock components in. Then in your .storybook/main.js file use this to point webpack to your mock file.
module.exports = {
// your Storybook configuration
webpackFinal: (config) => {
config.resolve.alias['externalModule'] = require.resolve('../__mocks__/externalModule.js');
return config;
},
};
This is covered in the docs under "Mocking imports".
However, this is a global configuration and not a story level configuration.

Understanding context and app methods in NUXT

I am trying to use bugsnagClient and its notify method in plugins/axios.js I have this code in plugins/bugsnag.js
import Vue from "vue"
import bugsnag from "#bugsnag/js"
import bugsnagVue from "#bugsnag/plugin-vue"
// const bugsnagClient = bugsnag(`${process.env.BUGSNAG_API_KEY}`)
var bugsnagClient = bugsnag({
apiKey: "",
notifyReleaseStages: ["production"]
})
bugsnagClient.use(bugsnagVue, Vue)
I want to attach a method to app or context as
export default ({ app }, inject) => {
function bugsnagNotify(error) {
return bugsnagClient.notify(new Error(error))
}
// Set the function directly on the context.app object
app.bugsnagNotify = bugsnagNotify
}
And I want to use it in plugins/axios.js
export default function({ store, app }) {
if (store.getters.token) {
console.log(app.bugsnagNotify("ss"))
app.$axios.setToken(store.getters.token, "Bearer")
} else {
//app.$bugsnag.notify(new Error("Bearer tooken is missing in Axios request."))
}
}
In this file, when I do console.log for just app
I can see bugsnagNotify: ƒ bugsnagNotify(error)
but when I call app.bugsnagNotify("error") I only get error such as VM73165:37 TypeError: app.bugsnagNotify is not a function
I have also tried this in plugins/bugsnag.js
export default (ctx, inject) => {
inject('bugsnag', bugsnagClient)
}
I only get an error as
app.$bugsnag.notify(new Error("Bearer tooken is missing in Axios request."))
If you are injecting into context inside one plugin and want to use that function inside another, you need to make sure that the plugin in which you are injecting comes first inside nuxt.config.js
...
plugins: [
'~/plugins/bugsnag.js',
'~/plugins/axios.js'
],
...

Vue test-utils how to test a router.push()

In my component , I have a method which will execute a router.push()
import router from "#/router";
// ...
export default {
// ...
methods: {
closeAlert: function() {
if (this.msgTypeContactForm == "success") {
router.push("/home");
} else {
return;
}
},
// ....
}
}
I want to test it...
I wrote the following specs..
it("should ... go to home page", async () => {
// given
const $route = {
name: "home"
},
options = {
...
mocks: {
$route
}
};
wrapper = mount(ContactForm, options);
const closeBtn = wrapper.find(".v-alert__dismissible");
closeBtn.trigger("click");
await wrapper.vm.$nextTick();
expect(alert.attributes().style).toBe("display: none;")
// router path '/home' to be called ?
});
1 - I get an error
console.error node_modules/#vue/test-utils/dist/vue-test-utils.js:15
[vue-test-utils]: could not overwrite property $route, this is usually caused by a plugin that has added the property asa read-only value
2 - How I should write the expect() to be sure that this /home route has been called
thanks for feedback
You are doing something that happens to work, but I believe is wrong, and also is causing you problems to test the router. You're importing the router in your component:
import router from "#/router";
Then calling its push right away:
router.push("/home");
I don't know how exactly you're installing the router, but usually you do something like:
new Vue({
router,
store,
i18n,
}).$mount('#app');
To install Vue plugins. I bet you're already doing this (in fact, is this mechanism that expose $route to your component). In the example, a vuex store and a reference to vue-i18n are also being installed.
This will expose a $router member in all your components. Instead of importing the router and calling its push directly, you could call it from this as $router:
this.$router.push("/home");
Now, thise makes testing easier, because you can pass a fake router to your component, when testing, via the mocks property, just as you're doing with $route already:
const push = jest.fn();
const $router = {
push: jest.fn(),
}
...
mocks: {
$route,
$router,
}
And then, in your test, you assert against push having been called:
expect(push).toHaveBeenCalledWith('/the-desired-path');
Assuming that you have setup the pre-requisities correctly and similar to this
Just use
it("should ... go to home page", async () => {
const $route = {
name: "home"
}
...
// router path '/home' to be called ?
expect(wrapper.vm.$route.name).toBe($route.name)
});