Quasar SSR app, "window is not defined" from vue-router - vue.js

I'm taking a fairly simple Vue app built with Quasar, and adding ssr.
I'm using the Quasar CLI, and added ssr with quasar mode add ssr. This created the src-ssr/ folder and files as expected.
When I launch it with quasar dev -m ssr, I get:
App • Opening default browser at http://localhost:9100/
/Users/stevex/src/myapp/node_modules/vue-router/dist/vue-router.cjs:478
const { history, location } = window;
^
ReferenceError: window is not defined
at useHistoryStateNavigation (/Users/stevex/src/myapp/node_modules/vue-router/dist/vue-router.cjs:478:35)
at createWebHistory (/Users/stevex/src/myapp/node_modules/vue-router/dist/vue-router.cjs:570:31)
at Module.eval [as default] (/src/router/index.ts:14:14)
at __vite_ssr_exports__.default (/.quasar/app.js:91:44)
at async eval (/Users/stevex/src/myapp/.quasar/server-entry.js:86:9)
Node.js v18.7.0

Found the problem, it was the history mode.
I was using createWebHistory in routes/index.ts, but for SSR the history mode must be createMemoryHistory. With that change, the server-side-rendered page loads.
Details here:
https://router.vuejs.org/guide/essentials/history-mode.html#memory-mode
The default createHistory checks for the environment and picks a compatible one; I'd replaced that. Here's the default, which works:
const createHistory = process.env.SERVER
? createMemoryHistory
: process.env.VUE_ROUTER_MODE === 'history'
? createWebHistory
: createWebHashHistory;

Related

How to switch app’s UI and the Storybook UI

I am setting up Storybook to develop components in my app, what is the best way to switch between UI's?
Currently I am replacing my app entry with :
export default from './storybook';
How do I remove this when in production?
yarn add react-native-config, then pod install
If you're developing for Android, add an import line as described here
In your project root, create an .env file
Any environment variables you add to .env
can be accessed as Config.YOUR_ENVIRONMENT_VARIABLE
Now, add the environment variable LOAD_STORYBOOK=true to .env.
In App.tsx, change your code like below so that Storybook is rendered conditionally.
import StorybookUI from './storybook'
import Config from 'react-native-config'
const App = () => {
return (
// Your actual app
)
}
export default Config.LOAD_STORYBOOK === 'true' ? StorybookUI : App

Vue 3 external component/plugin loading in runtime

I am designing an architecture for the Vue 3 app with distributed module-based ownership. Module system will be represented with plugins (seems like the most appropriate solution allowing vuex module and vue-router dynamic injects). Each such module/plugin will be developed by dedicated team working within isolated repos. We cannot use npm package-per-plugin approach as deployment process should be isolated as well, and with npm approach core app team will have to rebuild app each time npm package plugin has updates. This means we will have to load such plugins/pages at runtime via http.
So far this approach by Markus Oberlehner seems like some sort of the way to go - it uses custom Promise based solution for webpack's missing "load external url script at runtime" functionality. While it works fine with Vue 2, Vue 3 gives VNode type: undefined error.
The above mentioned article offers the following webpack external component loading solution:
// src/utils/external-component.js
export default async function externalComponent(url) {
const name = url.split('/').reverse()[0].match(/^(.*?)\.umd/)[1];
if (window[name]) return window[name];
window[name] = new Promise((resolve, reject) => {
const script = document.createElement('script');
script.async = true;
script.addEventListener('load', () => {
resolve(window[name]);
});
script.addEventListener('error', () => {
reject(new Error(`Error loading ${url}`));
});
script.src = url;
document.head.appendChild(script);
});
return window[name];
}
But above, as I said, does not work with Vue 3 defineAsyncComponent mechanism.
// 2.x version WORKS
const oldAsyncComponent = () => externalComponent('http://some-external-script-url.js')
// 3.x version DOES NOT WORK
const asyncComponent = defineAsyncComponent(
() => externalComponent('http://some-external-script-url.js')
)
So I have two questions:
Are there any known better solutions/suggestions for above architectural specification?
Is there any working webpack dynamic external import solutions tested with Vue 3 out there?
UPD: Here is small reproduction repo
We solved this problem together via chat.
Components built via the Vue 3 vue-cli rely on Vue being available in the global scope. So in order to render components loaded via the technique described in my article, you need to set window.Vue to a reference to Vue itself. Then everything works as expected.
update:
If import vue from vue/dist/vue.esm-bundler and set to global, then no need to change webpack / Vite config, and no need to load vue from cdn.
import * as Vue from 'vue/dist/vue.esm-bundler';
window.Vue = Vue;
Besides setting window.Vue, some other webpack or Vite configuration should also be set, otherwise some error is presented in console: vue warn invalid vnode type symbol(static) (symbol)
Vue3 + webpack:(https://github.com/vuejs/vue-next/issues/2913#issuecomment-753716888)
// index.html:
<script src="https://cdn.jsdelivr.net/npm/vue#3.0.4"></script>
// vue.config.js
configureWebpack: config => {
...
config.externals = { vue: 'Vue' }
...
}
Vue3 + vite:(https://github.com/crcong/vite-plugin-externals)
// vite.config.js
import { viteExternalsPlugin } from 'vite-plugin-externals'
export default {
plugins: [
viteExternalsPlugin({
vue: 'Vue'
}),
]
}

Vuejs DevTools not showing panel in developer tools

I cannot get the vue development tools to show it's panel.
I've tried deleting and re-installing the extension, Hard refreshing, closing the tools and opening again, adding Vue.config.devtools = true; and a combination of all of them and it still does not show the panel. Any ideas?
I did notice that __VUE_DEVTOOLS_GLOBAL_HOOK__ doesn't have a Vue value... but I don't have a working dev tool to see if that should be otherwise.
macOS Catalina (version 10.15.5)
Version 83.0.4103.106 (Official Build) (64-bit)
UPDATE: Turns out the devTool github repo had an even better answer:
const app = new Vue(vueConfig).$mount('#app');
window.__VUE_DEVTOOLS_GLOBAL_HOOK__.Vue = app.constructor;
See here: https://github.com/vuejs/vue-devtools
Turns out it was a jest work around that was causing the problem. My jest tests weren't working with my normal vue instance so I had to mock it with the createLocalVue.
const localVue = createLocalVue();
localVue.use(VueRouter);
const router = new VueRouter();
The problem was that some tests were not liking that I had two vue instances (the one in main.js) with a router.
So I added logic to only add the router if it wasn't a test:
import Vue from 'vue';
import VueRouter from 'vue-router';
if (!process || process.env.NODE_ENV !== 'test') {
Vue.use(VueRouter);
}
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes,
});
const vueConfig = {
render: (h) => h(App),
};
// excluding for jest tests
if (!process || process.env.NODE_ENV !== 'test') {
vueConfig.router = router;
}
new Vue(vueConfig).$mount('#app');
Unfortunately the if around the Vue.use() is what broke it:
// removing this if() fixed it and the vue dev tools panel now shows up.
if (!process || process.env.NODE_ENV !== 'test') {
Vue.use(VueRouter);
}
Something about the way the dev tools inits needed the router to be installed. I also wonder if they use a process with "test" or something. Either way, this is resolved for me. Hope it helps someone else.
I was facing the same issue and able to solve by avoiding vue.min.js for development purpose. Use original version (vue.js) instead of the minify version.
load the page without Devtools open
press the Vue Devtools button in the extensions area (might say "Vue.js not
detected", but don't let that bother you). In some setups, this step is
crucial.
only then open Devtools by hitting F12. Vue tab should appear (check to the
very right of all tabs, you can drag it to the left)
https://stackoverflow.com/a/62446093/15265413

vue.js register webcomponent

I am using vue.js and created a webcomponent with stencil.js. I don't want to publish the webcomponent to npm, which is why I simply include it in the src/assets directory in my vue project.
However I get the error
[Vue warn]: Unknown custom element: <my-component> - did you register the component correctly? For recursive components, make sure to provide the "name" option.
found in --->
<App> at src/app.vue
<Root>
It works without problems with another component I already have in the assets directory.
It also doesn't help to use
Vue.config.ignoredElements = ['my-component'];
since the component is still empty when I run it locally.
Thanks for your help!
If you're including the web component locally, you can use #vue/web-component-wrapper:
import Vue from 'vue'
import wrap from '#vue/web-component-wrapper'
const Component = {
// any component options
}
const CustomElement = wrap(Vue, Component)
window.customElements.define('my-element', CustomElement)
just in case, for me that was a version issue:
my components were defined like that:
export default class Foo extends ScopedElementsMixin(LitElement) {
...
}
And in the vue I did
customElements.define('foo', Foo);
I started received that error when I updated scoped-elements 1.x.x -> 2.x.x.
So when I put everything back to 1.x.x the error just gone.

How do I use require('electron') in a quasar vue component environment?

How do I access the main electron process fs module from within a renderer side module like a vue component running within the Quasar framework.
I've tried a few variations in a component with the following error:
const { app } = require('electron')
vue-router.esm.js?8c4f:1897 TypeError: fs.existsSync is not a function
const { app } = window.require('electron')
TypeError: window.require is not a function
After looking at what I could find through my friend Google, I am still searching for an answer on how to access the electron main process functions from within a vue component running under the quasar framework. Anyone... anyone? I've seen some github examples of file explorers, but based on the electron documentation it seems the implementation of just simply calling something like fs.readdirSync() should be a lot simpler than what I'm seeing in those implementations.
Your problem is explained in the Quasar docs
https://quasar.dev/quasar-cli/developing-electron-apps/node-integration
Quasar's suggestion is to use a preload script to attach the node APIs that you want in your renderer processes (ie: BrowserWindows) to the global window object.
https://quasar.dev/quasar-cli/developing-electron-apps/electron-preload-script
Attach preload script to BrowserWindow (Main Process)
src-electron/electron-main.js:
import path from 'path'
win = new BrowserWindow({
...
webPreferences: {
preload: path.resolve(__dirname, 'electron-preload.js')
}
})
Attach Node APIs to window global (Preload Script)
src-electron/electron-preload.js:
window.electron = require('electron')
Use Node API through the window global (Renderer Process)
somefile.vue
window.electron.ipcRenderer.sendSync(
'message',
payload
)
The answer was just beyond my understanding of how all these components are working together. Hopefully this will help someone else just coming up to speed on developing a Quasar/Vue/Electron app. If you launch your app/website using
quasar dev
you get a browser (renderer) that communicates with main electron process that cannot handle node main process stuff like:
const electron = require('electron')
const fs = require('fs')
const files = fs.readdirSync('/')
console.log(files)
I couldn't find a clear, concise and simple way. It appears there is a webpack config that can provide the same 'deep' integration, but I was looking for a more out of the box solution.
If you launch your app
quasar dev -m electron
You get deep integration and now can 'require()' or import the above modules within Vue components in your Quasar app.
const electron = require('electron')