Show updated PWA created in Vue - vue.js

I uasing Vue.js with Vuetify and creating a PWA.
I have service-worker.js in /public folder
A snippet from vue.config.js:
pwa: {
// configure the workbox plugin
workboxPluginMode: 'InjectManifest',
workboxOptions: {
// swSrc is required in InjectManifest mode.
swSrc: 'public/service-worker.js',
// ...other Workbox options...
}
}
This looks too be working good and caching the shell etc.
I run build and serve up the project
npm run build
The problem i have is when i update any files, i can't see the updated changes.
when i navigate to the url in my android device the page remains as the old one (cached).
How can i get it to update?
I tried including this code in index.html, but no success:
https://developers.google.com/web/tools/workbox/guides/advanced-recipes#offer_a_page_reload_for_users
service-worker.js
importScripts("/precache-manifest.8812c20b1b3401cbe039782d13cce03d.js", "https://storage.googleapis.com/workbox-cdn/releases/3.6.3/workbox-sw.js");
console.log(`Hello from service worker`);
if (workbox) {
console.log(`Workbox is loaded`);
self.__precacheManifest = [].concat(self.__precacheManifest || []);
workbox.precaching.suppressWarnings();
workbox.precaching.precacheAndRoute(self.__precacheManifest, {});
addEventListener('message', (event) => {
if (event.data && event.data.type === 'SKIP_WAITING') {
skipWaiting();
}
});
}
else {
console.log(`Workbox didn't load`);
}

Not sure what exactly your setup setup is, but it should be similar. Using the #vue/cli-plugin-pwa and with minimal setup below.
This will show a dialog when a new version of your app is available. Clicking yes will update your app. You will have to refresh the page somehow to actually show the new version, but that is up to you on how solve that.
vue.config.js:
module.exports = {
pwa: {
name: "name-of-your-app",
short_name: "noya",
themeColor: "#000000",
workboxPluginMode: "InjectManifest",
workboxOptions: {
swSrc: "src/service-worker.js" // CHECK CORRECT PATH!
}
}
};
src/main.js:
import Vue from "vue";
import App from "./App.vue";
import "./registerServiceWorker";
// whatever other imports...
new Vue({
render: h => h(App)
}).$mount("#app");
src/registerServiceWorker.js:
import { register } from "register-service-worker";
if (process.env.NODE_ENV === "production") {
register(`${process.env.BASE_URL}service-worker.js`, {
updated(registration) {
if (window.confirm("A new version is available, update now?")) {
const worker = registration.waiting;
worker.postMessage({ action: "SKIP_WAITING" });
// refresh the page or trigger a refresh programatically!
}
}
});
}
src/service-worker.js:
self.__precacheManifest = [].concat(self.__precacheManifest || []);
workbox.precaching.suppressWarnings();
workbox.precaching.precacheAndRoute(self.__precacheManifest, {});
self.addEventListener("message", (event) => {
if (event.data.action == "SKIP_WAITING") self.skipWaiting();
});

I get it work by following the offer_a_page_reload_for_users. The original registerServiceWorker.js seems redundant though.
src/registerServiceWorker.js
import { Workbox } from "workbox-window";
if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) {
const wb = new Workbox("/service-worker.js");
wb.addEventListener("waiting", () => {
const result = window.confirm("refresh now?");
if (result) {
wb.messageSW({ type: "SKIP_WAITING" });
}
});
wb.addEventListener("controlling", () => {
window.location.reload();
});
wb.register();
}

Related

Built code errors in Electron: Failed to execute 'querySelector' on 'Document'

Using Vite to build the app, I am getting the following error inside Electron:
index.c160f204.js:9 DOMException: Failed to execute 'querySelector' on 'Document': 'link[href="/C:UsersrankDocumentsSchoolCheckInElectronReaderdist/assets/Home.b0f26e4d.js"]' is not a valid selector.
It appears to me that the path inside the built code has the slashes removed, but I have no idea on how to solve that since it's generated code.
Using Node 17.9.0 on Windows 11 10.0.22000 Build 22000
Electron main.js:
const { app, BrowserWindow } = require("electron");
const path = require("path");
function createWindow() {
const win = new BrowserWindow({
width: 800,
height: 600,
webPreferences: {
preload: path.join(__dirname, "preload.js"),
},
});
win.loadFile("dist/index.html");
}
app.whenReady().then(() => {
createWindow();
app.on("activate", () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow();
}
});
});
app.on("window-all-closed", () => {
if (process.platform !== "darwin") {
app.quit();
}
});
Electron preload.js:
window.addEventListener("DOMContentLoaded", () => {
const replaceText = (selector, text) => {
const element = document.getElementById(selector);
if (element) element.innerText = text;
};
for (const type of ["chrome", "node", "electron"]) {
replaceText(`${type}-version`, process.versions[type]);
}
});
Vite.config.ts:
import { defineConfig } from 'vite'
import vue from '#vitejs/plugin-vue'
import * as path from "path";
// https://vitejs.dev/config/
export default defineConfig({
base: path.resolve(__dirname, './dist'),
plugins: [
vue(),
],
})
Using vue-tsc --noEmit && vite build to build and electron . to start.
If you copy/paste the faulty code into a browser console, you will notice a non UTF-8 character in your link, between Users and rank.
Get rid of it and it should work.
The simplest way to fix this would be to move the project to a path which doesn't contain weird chars (e.g: C:/projects/)

vue3 creating global variables returns undefined

I am currently working on upgrading a project to Vue3. Inside the project we have several files that create global variables inside a boot directory:
src/boot/auth.js
src/boot/axios.js
src/boot/cranky.js
....
Each of these files creates global variables that I am using throughout the project. For example my auth.js file:
import auth from '../app/auth';
import { createApp } from 'vue';
export default async ({
router,
Vue
}) => {
const app = createApp({});
app.config.globalProperties.$auth = auth;
// Vue.prototype.$auth = auth;
//This works with logic in MainLayout to permit users to see particular screens
//after they are logged in.
router.beforeResolve((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
// eslint-disable-next-line no-unused-vars
let user;
if (app.config.globalProperties.$auth.isAuthenticated()) {
// TODO: record screens viewed by router url
next();
} else {
next({
path: '/home'
});
}
}
next();
});
};
After research and reading I understand that global variables are created like this:
app.config.globalProperties.$auth = auth;, Which then should be called from other component files using this.$auth. In my case, however, this returns as undefined.
My theory is that there is an issue with my createapp. Am I calling that correctly, or am I missing something else?
Thanks.
EDIT
added code requested by #tony19
The following the full script tag. My understanding and I probably am wrong, is that the this works as a global in vue3.
<script>
import HeaderScoreIndicator from '../components/HeaderScoreIndicator.vue';
import ExitResearchControl from '../components/ExitResearchControl.vue';
import {mq} from 'src/boot/mq'
export default {
components: {
HeaderScoreIndicator,
ExitResearchControl
},
data: function () {
return {
tab: 'denial',
signedIn: false,
username: null,
user: null
};
},
computed: {
/*
These 4 functions bind the colors to the background elements
*/
title: function () {
return this.$store.state.globals.title;
},
toolbarStyle: function () {
return 'padding-top: 10px; background-color: ' + this.$store.state.globals.toolbarColor;
},
footerStyle: function () {
return `background-color: ${this.$store.state.globals.toolbarColor};`;
},
backgroundStyle: function () {
if(!this.$mq.phone){
return `background: linear-gradient(180deg, ${this.$store.state.globals.backgroundGradientTop} 0%, ${this.$store.state.globals.backgroundGradientBottom} 100%);
display: flex;
justify-content: center;`;
} else{
return `background: linear-gradient(180deg, ${this.$store.state.globals.backgroundGradientTop} 0%, ${this.$store.state.globals.backgroundGradientBottom} 100%);`;
};
},
limitSize: function(){
if(!this.$mq.phone){
return 'max-width: 1023px; width: 100%;'
} else{
return
};
}
},
/*
In the beforeCreate function, we're setting up an event listener to detect
when we've logged in. At the successful login we can push the user to
the correct screen.
*/
beforeCreate: function () {
console.log(this.$auth);
return this.$auth.getState().then(data => {
if (this.$auth.isAuthenticated()) {
this.username = this.$auth.getEmail();
return this.initialize(data).then(() => this.signedIn = true);
} else {
this.signedIn = false;
return newUserLanguageSelection()
}
}).catch(e => {
this.signedIn = false;
console.error(e);
});
},
methods: {
newUserLanguageSelection: function(){
if(localStorage.getItem('languageSet') != 'true'){
return this.$router.push('/language');
}
},
initialize: function (data) {
this.$store.commit('globals/dataLoaded');
this.$store.commit('scoring/setUsername', this.username);
return this.$store.dispatch('scoring/initializeScoring', { points: data.score | 0 })
.then(() => {
if(this.$store.state.globals.testQuiz){
return;
} else if(localStorage.getItem('languageSet') != 'true'){
return this.$router.push('/language');
}else if (data.seenAnalyticsDialog == true) {
return this.$router.push('/home');
} else {
return this.$router.push('/consent');
}
})
.catch(e => {
//silence NavigationDuplicated errors
if (e.name != "NavigationDuplicated")
throw e;
});
}
}
};
</script>
In the previous Vue 2 project, it looks like Vue was passed to the auth.js module so that it could attach $auth to the Vue prototype.
However, in an attempt to upgrade that particular code in the auth.js module to Vue 3, you ignore the Vue argument, and create a throwaway application instance to create the $auth global property:
// src/boot/auth.js
export default async ({
router,
Vue,
}) => {
const app = createApp({}); ❌ local app instance
app.config.globalProperties.$auth = auth;
⋮
}
But that won't work because the app instance is not somehow hooked into the application instance you'll eventually mount in main.js.
Fixing the upgrade
Your main.js probably looks similar to this:
// src/main.js
import Vue from 'vue';
import App from './App.vue';
import router from './router';
import auth from './boot/auth';
auth({ router, Vue });
⋮
Vue.createApp(App).mount('#app');
To correctly upgrade the original code, you should pass the app instance from main.js to auth.js:
// src/main.js
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import auth from './boot/auth';
const app = createApp(App);
auth({ router, app });
⋮
app.mount('#app');
And use that app in the auth.js module:
// src/boot/auth.js
export default async ({
router,
app, 👈
}) => {
👇
app.config.globalProperties.$auth = auth;
⋮
}
Convert to Vue plugin
Assuming you don't need to await the return of the auth.js module, you could make that module a Vue plugin instead, which automatically receives the app instance as the first argument. The second argument receives the options passed in from app.use():
// src/boot/auth.js
export default {
install(app, { router }) {
app.config.globalProperties.$auth = auth;
router.beforeResolve(⋯);
}
}
// src/main.js
import Vue from 'vue';
import App from './App.vue';
import router from './router';
import auth from './boot/auth';
const app = createApp(App);
app.use(auth, { router });
⋮
app.mount('#app');

Clearing console on Nuxt hot reload?

In Vue I just edited the main.js file to add:
// on hot-reload clear the console
window.addEventListener("message", (e) => {
if (e.data && typeof e.data === "string" && e.data.match(/webpackHotUpdate/)) {
console.clear();
}
});
But in Nuxt you only have the config file. I tried adding that script to the plugins folder but the console never cleared.
onReload.js in plugins folder. This file only seems to run when I refresh the page manually but not on hot reload (i.e. when I save any file):
console.log("hello there");
window.addEventListener("message", (e) => {
console.log("e.data is > ", e.data);
console.log("=== string > ", typeof e.data === "string");
console.log("does it match > ", e.data.match(/webpackHotUpdate/));
if (e.data && typeof e.data === "string" && e.data.match(/webpackHotUpdate/)) {
console.log("hello world");
console.clear();
}
});
Nuxt config file:
plugins: [{ mode: "client", src: "~/plugins/onReload" }],
Any ideas are appreciated!
You will need to add a plugin.
/plugins/onReload.js would contain your function:
Object.keys(window.__whmEventSourceWrapper).forEach((key) => {
window.__whmEventSourceWrapper[key].addMessageListener((e) => {
if (e.data.match(/built/)) {
console.clear()
}
})
})
Then in nuxt.config.js you would need to import the plugin, and disable server side rendering
plugins: [
{ mode: client, src: '~/plugins/onReload' }
]
The accepted answer didn't work for me so I ended up creating clear-console.client.js with the following...
export default () => {
if (module.hot) {
module.hot.accept() // already had this init code
module.hot.addStatusHandler((status) => {
if (status === 'prepare') console.clear()
})
}
}
Then in nuxt.config.js add the follwing plugins: ['~/plugins/clear-console.client.js']
Hope this helps others.

how to fix blank page if i am using vue router with electron js?

I'm trying to use vue router with an application on an Electron JS. If I use a router on the render page, then the router work done. But I do not understand how to make the transition to the page, for example,- 'Settings' using the Tray. At attempt of transition the empty page opens.
I have prepared a working example of the project. This problem exists only build project. In development mode all work well.
This is my work example on github. Please need help.
git clone https://github.com/DmtryJS/electron-vue-example.git
cd electron-vue-example
npm install
npm run build
and run dist\win-unpacked\example_for_stackoverflow.exe
my main.js file
'use strict'
import { app, protocol, BrowserWindow, Menu, ipcMain, Tray } from 'electron'
import { format as formatUrl } from 'url'
const electron = require('electron');
const path = require('path');
const isDevelopment = process.env.NODE_ENV !== 'production';
let imgBasePath;
if(isDevelopment) {
imgBasePath = path.join('src','assets', 'img');
} else {
imgBasePath = path.join(path.dirname(__dirname), 'extraResources', 'img');
}
let win;
let tray;
protocol.registerStandardSchemes(['app'], { secure: true })
const trayIcon = path.join(__static, 'img', 'icon.png');
function createWindow () {
win = new BrowserWindow({
width: 800,
height: 600,
icon: trayIcon
})
routeTo(win, "")
win.on('closed', () => {
win = null
})
//убрать меню
win.setMenuBarVisibility(true)
win.on('show', function() {
tray.setHighlightMode('always')
})
win.on('hide', function() {
tray.setHighlightMode('never')
})
}
// Quit when all windows are closed.
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', () => {
if (win === null) {
createWindow()
}
})
app.on('ready', () => {
createWindow()
win.webContents.openDevTools(); //открыть dev tools
createTray()
})
// Exit cleanly on request from parent process in development mode.
if (isDevelopment) {
if (process.platform === 'win32') {
process.on('message', data => {
if (data === 'graceful-exit') {
app.quit()
}
})
} else {
process.on('SIGTERM', () => {
app.quit()
})
}
}
function createTray()
{
let traiIconPath = path.join(imgBasePath, 'preloader_tray_icon.png')
tray = new Tray(traiIconPath)
const contextMenu = Menu.buildFromTemplate(
[
{
label: 'Settings',
type: 'normal',
click: function()
{
routeTo(win, "/settings")
let contents = win.webContents
contents.on('dom-ready', function()
{
if(!win.isVisible())
{
showWindow()
}
})
}
},
{
label: 'Exit',
type: 'normal',
click: function()
{
win = null
app.quit()
}
}
])
tray.setContextMenu(contextMenu)
tray.on('click', function() {
toggleWindow();
})
}
function showWindow() {
var position = getPosition();
win.setPosition(position.x, position.y, false)
win.show()
win.focus()
}
ipcMain.on('routerEvent', function(event, arg) {
routeTo(win, arg)
})
function routeTo(win, to) {
if (isDevelopment) {
win.loadURL(`http://localhost:${process.env.ELECTRON_WEBPACK_WDS_PORT}` + to)
} else {
win.loadURL(formatUrl({
pathname: path.join(__dirname, 'index.html' + to);,
protocol: 'file',
slashes: true
}))
}
}
And
router.js
import Vue from 'vue'
import Router from 'vue-router'
import Main from './../views/Main.vue'
import Settings from './../views/Settings.vue'
Vue.use(Router)
export default new Router({
//mode: 'history',
routes: [
{
path: '/',
name: 'home',
component: Main
},
{
path: '/settings',
name: 'settings',
component: Settings
}
]
})
You need to add created to the main Vue app check docs
// src/main.js
new Vue({
router,
render: h => h(App),
created() {
// Prevent blank screen in Electron builds
this.$router.push('/')
}
}).$mount('#app')
The solution for me was to remove the history mode in the vue router.
Sorry, but after one day of googling, I just found a solution. The case turned out to be
win.loadURL(formatUrl({
pathname: path.join(__dirname, 'index.html' + to);,
protocol: 'file',
slashes: true
}))
I delete formaUrl and everything works well
win.loadURL(path.join(__dirname, 'index.html' + to));
For me solution was this:
Check if app is running at addresses like this:
Local: http://x86_64-apple-darwin13.4.0:8080/
Network: http://localhost:8080/
Check if you can access x86_64..url in browser. If you are not seeing a webpage but can see it from localhost, then map 127.0.0.1 to x86_64-apple-darwin13.4.0 in hosts file. in mac its located in /etc/hosts in windows its in system32/drivers/etc.

How to use Electron API with Vue js component?

I'm using the electron-vue boilerplate, and I want to make my mainWindow a fullScreen after clicking a button.
Electron Window API: electron.atom.io/docs/api/browser-window
HTML:
<button #click="setFullScreen">Switch to Full Screen</button>
Vue:
export default {
name: 'mainComponent',
methods: {
setFullScreen: function() {
mainWindow.setFullScreen(true);
}
}
This is not working. How can I use the Electron API in electron-vue?
and the index.js:
'use strict'
import { app, BrowserWindow } from 'electron'
let mainWindow
const winURL = process.env.NODE_ENV === 'development'
? `http://localhost:${require('../../../config').port}`
: `file://${__dirname}/index.html`
function createWindow () {
/**
* Initial window options
*/
mainWindow = new BrowserWindow({
height: 728,
width: 1024,
fullscreen: false,
})
mainWindow.loadURL(winURL)
mainWindow.on('closed', () => {
mainWindow = null
})
// eslint-disable-next-line no-console
console.log('mainWindow opened')
}
app.on('ready', createWindow)
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') {
app.quit()
}
})
app.on('activate', () => {
if (mainWindow === null) {
createWindow()
}
})
you will find it as it is in electron-Vue
the picture shows How the Structure of the folder
enter image description here
mainWindow is not available in your Vue code because it is defined in your main process.
In your single file component, however, you can import remote from electron, where you can get access to the current window. So your code would look something like this.
const {remote} = require("electron")
export default {
name: 'mainComponent',
methods: {
setFullScreen: function() {
remote.getCurrentWindow().setFullScreen(true);
}
}
}