How to use Pinia inside of an npm package? - vue.js

I have similar issue as mentioned here, but with Pinia in my case. It's much harder to get Pinia to work outside of Vue components, because of "Uncaught Error: [🍍]: getActivePinia was called with no active Pinia. Did you forget to install pinia?", but in this case it is even harder.

Not the cleanest solution, because it requires to have Pinia installed and initialized in the project where your package will be used, but if you're doing this for internal use it is totally okay.
So, in my package it looks like this:
install(app, options = {}) {
greetings()
const { $pinia } = options
if (!$pinia) {
throw new Error(`No active Pinia instance was passed to your package`)
}
let core_store = useCoreStore($pinia)
// moar code
And in other project:
import MyPackage from '#rusinas/my-package'
app.use(ModernEditor)
const pinia = createPinia()
app.use(MyPackage, {
$pinia: pinia
})
app.use(pinia)
app.mount('#app')
I noticed that in SPA mode you may not need to provide active pinia to your package, it could figure it out itself, you just need to make sure to app.use(pinia) before you initialize you package. But this doesn't work in Nuxt SSR mode, so yeah, this workaround required :(
I think we should raise this question in Pinia's repository. I don't see why it have to work this way. Cases where you need stores outside of setup() are so often and even crucial sometimes for applications, so it should be much easier.
P.S.
Also, keep in mind the possibility of store names collisions. Names should be unique across entire application
P.P.S.
SSR solution:
plugins/MyPackage.plugin.js:
import { defineNuxtPlugin } from '#app'
import MyPackage from '#rusinas/my-package'
export default defineNuxtPlugin(nuxtApp => {
nuxtApp.vueApp.use(MyPackage, {
$pinia: nuxtApp.$pinia,
})
})

Related

Astro/Vue is executing appEntrypoint one time per component

I have few Vue components in an Astro page that are sharing the state via Pinia
I initialised the Pinia plugin as the Astro documentation mentions, via astro.config.mjs:
import {defineConfig} from 'astro/config';
import vue from '#astrojs/vue';
// https://astro.build/config
export default defineConfig({
// Enable Vue to support Vue components.
integrations: [vue({
isProduction: false,
appEntrypoint: '/src/_app'
})],
});
And then in /src/_app.ts:
import type { App } from "vue";
import { createPinia } from 'pinia'
export default (app: App) => {
console.log("Why is initializing one time per component!", app.config)
app.use(createPinia());
};
The problem is that app.use(createPinia()); is executed one time per component. So looks like because this issue, Pinia creates one Storage per component instead one sharing.
Of course, the problem is happening with all plugins. For example, app.use(InstantSearch); is executed one time per component as well, so it is doing extra calls to the server and creating X searchs plugins instances.
Here a really simple project reproducing the error, and Here the link to the stackblitz running example
Also, I created a bug issue but not sure if it is.
If this is not a bug, how to init Vue plugins per page?

Nuxt avoid import of client-side script for server-side rendering

In my nuxt.js application, I have a script that imports an NPM package which is only compatible with browser contexts (it references document, location, window, etc.)
Is there a way to exclude this from SSR?
import thing from "#vendor/thing"; // causes `document not defined` error
export default showThing(){
if (process.client) {
thing();
}
}
I can use the method with process.client but this file is still imported in my components.
You could import it dynamically rather than in every context.
As explained in my answer here: https://stackoverflow.com/a/67825061/8816585
In your example, that would be something like this
export default showThing(){
if (process.client) {
const thing = await import('#vendor/thing')
thing()
}
}

How to import javascript library in main.js for global availability throughout vue app?

My main.js contains import "mathjs"; and seems to be working fine within main.js as
console.log(median(5,4,6,1));
> 4.5
median() is however not available outside of main.js, i.e. in components. After reading this answer, I assumed that the simple import should be enough for global availability throughout the vue app?
In general when you are working with modern modules or a bundler like webpack, you have to import mathjs in every module (think file) you want to use it.
What is often done in the vue context, is adding the library to the Vue context itself with a plugin.
See https://v3.vuejs.org/guide/plugins.html#writing-a-plugin
So this should be as easy as:
const mathjsPlugin = {
install(app){
app.config.globalProperties.$mathjs = mathjs;
}
}
const app = createApp(...);
app.use(mathjsPlugin);
// inside of a component
export default {
created(){
console.log(this.$mathjs....);
}
}
I personally think explicitly importing is a cleaner approach, but if mathjs is used in most of the components, this approach can make sense
in main.js import all as math then add it as global property :
import * as math from 'mathjs'
const app=createApp({....})
app.config.globalProperties.$math =math
then in any component you could use it like this.$math.theFunctionName

Why is graphql-tag needed to use Apollo with Nuxt?

I'm trying to understand why most tutorials on Nuxt and Apollo also suggest installing the graphql-tag package.
The #nuxt/apollo module docs say this is optional, if you want to load separated .gql files (as opposed to... writing your queries inline in your components?) And the official Nuxt/Apollo demo uses separated .gql files despite not having graphql-tag installed.
I've read this, but it doesn't really seem to answer my question. It just talks about query parsing.
Can anyone tell me precisely what graphql-tag does and whether (and how, in theory) you'd use Apollo with Nuxt without it installed?
I'm not a graphQL expert but I still achieved to work with it the few last months. At the end, I found out that graphql-tag is only useful when using inline queries directly into your .vue components.
Like in this documentation section
<template>
<div>{{ hello }}</div>
</template>
<script>
import gql from 'graphql-tag'
export default {
apollo: {
// Simple query that will update the 'hello' vue property
hello: gql`query {
hello
}`,
},
}
</script>
Meanwhile, since it's not very flexible, I've ended up importing and using it in Vuex like this
import myGqlQuery from '~/apollo/queries/test.gql'
const actions = {
async myFancyVuexAction({ commit, dispatch }) {
try {
const { errors, data } = await this.app.apolloProvider.defaultClient.query({ query: myGqlQuery })
} else {
// error
}
}
}
With the .gql imported file being written like this
query{
Entity {
things
}
}
For this kind of configuration, you don't need graphql-tag. Also, some people have some flexible configuration inside of their graphql files, they are using vanilla .js files and a mix of their actual GQL query + graphql-tag (needed if it's not a .gql file).
It is also useful if using TypeScript, here is an example: https://github.com/DNature/chef-assignment/blob/master/apollo/queries/index.ts
So yeah, those are my 2cts. Not a pro, but things that I understood from my few months fiddling with GraphQL.

Snapshot Testing with Vue-Router

I'm trying to run jest-snapshot tests in a Vue app created using the Vue-cli app. When I test a component that have a router-link in it, I receive the following warning.
[Vue warn]: Unknown custom element: <router-link> - did you register
the component correctly? For recursive components, make sure to
provide the "name" option.
Following the documentation here Unit tests with View Router I can stub out the router-link, but that doesn't pass my snapshot test. Has anyone run into this issue before?
Updated: A more consistent solution
tl;dr:
The solution I found that works consistently is to create a localVue (usually outside the scope of the describe() blocks) and add the RoutherLinkStub as a component.
import { mount, RouterLinkStub, createLocalVue } from '#vue/test-utils';
const localVue = createLocalVue();
localVue.component('router-link', RouterLinkStub);
const wrapper = mount(Component, { localVue });
The documentation doesn't make the solution obvious because it seems to be something of a hybrid of the page you linked to and this one about RouterLinkStub.
From that page:
import { mount, RouterLinkStub } from '#vue/test-utils'
const wrapper = mount(Component, {
stubs: {
RouterLink: RouterLinkStub
}
})
That solution works in most cases. If you have a case where you need to use mount and your router-link is a not in the component being tested but the one below it, you will still get that warning. Now, if you are in that situation, it's worth reflecting if you're writing your test properly (are you testing too much rather than a small unit?), but I have a situation or two where if I shallow instead of mount, the snapshot test is worthless because it renders children as <!----> when I callexpect(wrapper.html()).toMatchSnapshot()`.
A Note on Snapshot Tests:
I haven't actually seen examples of people using the Wrapper.html() method for snapshot testing, but it really seems like the best solution in my experimenting. vue-server-renderer is a long walk for what appears to be the same result. Accessing the vm property of Wrapper gives you access to the $el property, without much trouble, but it's just a more whitespace-heavy rendering of html().