Edit: I said several times in the post below that I'm "not in a module". Any time JavaScript code in one file is called from another that's technically a module. What I meant to say was that this module is called automatically by Quasar at script initialization time and is expected to have global side effects. The module is never imported into any code that I write.
Succint post:
I'm using Quasar 2.0.0 with TypeScript support and Vue Class Components. I wish to add an instance property to be accessible in my classes as: this.$fetch(...);
I've created a boot file with the following:
import Vue from "vue";
declare module "vue/types/vue"
{
interface Vue
{
prototype: {
$fetch: (resource: (string | Request), init?: RequestInit) => Promise<Response>
},
$fetch: (resource: (string | Request), init?: RequestInit) => Promise<Response>
}
}
type Context = {
Vue: Vue
};
export default async ({ Vue: vue }:Context) =>
{
vue.prototype.$fetch = async (resource, init) =>
{
return fetch(resource, init);
};
};
This doesn't throw any errors with TypeScript. But when I try to use it in a component, I get an error:
<template>
<q-layout>
<q-page-container>
<q-page class="flex flex-center">
<h1>Test</h1>
</q-page>
</q-page-container>
</q-layout>
</template>
<script lang="ts">
import { Vue, Component } from "vue-property-decorator";
@Component
export default class TestPage extends Vue
{
mounted ()
{
this.$fetch("https://www.google.com");
}
}
</script>
Property '$fetch' does not exist on type 'TestPage'.
Previous (rambly) post:
I'm working on a Quasar app and as a convenience I'd like to create a modified fetch
method that injects my API key if the requested domain matches my back-end domain. The interface would be something like:
@Component
export default class VueComponent extends Vue
{
mounted()
{
this.$fetch("https://www.example.com/api/endpoint").then(console.log);
}
}
I know how to define something like this. I just need to update the Vue
prototype, per https://v2.vuejs.org/v2/cookbook/adding-instance-properties.html:
Vue.prototype.$fetch = async function(request, init)
{
// ... blah ...
};
Per Quasar's documentation, the proper time to update the Vue prototype is in a boot file. So I created one:
quasar new boot fetch
My boot file started out fairly simple:
export default async ({ Vue }) =>
{
Vue.prototype.$fetch = async (resource, init) =>
{
return fetch(resource, init);
};
};
When everything fell apart is when I tried to convert to TypeScript. The parameter Vue
implicitly has an any
type, because there's no type provided to the parameter object.
I tried defining the type like so:
import Vue from "vue";
type Context = {
Vue: Vue
};
export default async ({ Vue }:Context) =>
However this caused complains about variable shadowing (Vue
is defined in the upper scope). I noticed that the Vue
variable had a type of VueConstructor<Vue>
, so I instead tried:
import { VueConstructor } from "vue";
type Context = {
Vue: VueConstructor
};
export default async ({ Vue }:Context) =>
That worked for the function definition, but threw an error on the Vue.prototype.$fetch = ...
line: "Unsafe member access .$fetch on any value". TypeScript doesn't expect Vue.prototype
to have a $fetch
property. If I were making a module definition I would extend the interface like so:
declare module "abc" {
VueConstructor: {
prototype: {
$fetch: ...
}
}
}
However I'm not declaring a module -- this is a boot file. I tried a quick hack to work around this, defining my own VueConstructor
only within the context of my boot file:
interface VueConstructor
{
prototype: {
$fetch: (resource: (string | Request), init?: RequestInit) => Promise<Response>
}
}
type Context = {
Vue: VueConstructor
};
export default async ({ Vue }:Context) =>
{
Vue.prototype.$fetch = async (resource: (string | Request), init?: RequestInit) =>
{
return fetch(resource, init);
};
};
This finally got TypeScript to stop complaining about this file, but the moment I try to utilize this.$fetch
in a component:
Property '$fetch' does not exist on type 'VueComponent'.
Since VueComponent
extends Vue
I think I need to update the definition of the Vue
class, but I'm lost on how to do this. I tried adding the following to my boot file:
interface Vue
{
$fetch: (resource: (string | Request), init?: RequestInit) => Promise<Response>
}
But this didn't work. I can't just use declare module
because, again, I'm not in a module. How do you extend a type globally without a module?
The code example in my "succinct post" (showing what I came up with after trial and error) actually works -- I just had to restart my computer (possibly just had to restart my IDE but hitting close wasn't fully shutting it down for some reason)