How do I set the baseURL that is used in useFetch composable globally (maybe nuxt.config.ts) so that I wouldn't have to define it in every useFetch.
You can define the baseURL in your nuxt.config.js|ts like this:
import { defineNuxtConfig } from 'nuxt'
export default defineNuxtConfig({
// ...
runtimeConfig: {
public: {
baseURL: process.env.BASE_URL || 'https://api.example.com/',
},
},
// ...
(or use a fixed value or only the environment variable - as you like)
And add this composable:
// /composables/useMyFetch.js
export const useMyFetch = (request, opts) => {
const config = useRuntimeConfig()
return useFetch(request, { baseURL: config.public.baseURL, ...opts })
}
If you want to have the full blown version with types it's a bit longer:
// /composables/useMyFetch.ts
import { UseFetchOptions } from '#app'
import { NitroFetchRequest } from 'nitropack'
import { KeyOfRes } from 'nuxt/dist/app/composables/asyncData'
export function useMyFetch<T>(
request: NitroFetchRequest,
opts?:
| UseFetchOptions<
T extends void ? unknown : T,
(res: T extends void ? unknown : T) => T extends void ? unknown : T,
KeyOfRes<
(res: T extends void ? unknown : T) => T extends void ? unknown : T
>
>
| undefined
) {
const config = useRuntimeConfig()
return useFetch<T>(request, {
baseURL: config.public.baseURL,
...opts,
})
}
You can then use useMyFetch as replacement for useFetch - but with baseURL being set :-)
The following composable could be set
/composables/useJsonP.ts
export const useJsonP = async (path) => {
return await useFetch(() => `https://jsonplaceholder.typicode.com/${path}`)
}
And you could call this in your view
<script setup>
const jsonP = await useJsonP('todos/1')
</script>
<template>
<div>
<pre>{{ jsonP.data }}</pre>
</div>
</template>
That way, no need to define it somewhere and hack somehow the configuration. You do have a simple way of defining reusable pieces of code, that are directly imported into your components/views thanks to the DX of Nuxt.
For anyone still looking for the answer to the original question you can do this in nuxt.config with runtimeConfig and env variables. You can of course replace the env variables with a hard-coded values if you prefer.
In your nuxt.config.js/ts
runtimeConfig: {
SOME_SECRET_KEY: process.env.SOME_SECRET_KEY,
public: {
SOME_API_KEY: process.env.SOME_API_KEY,
},
},
Then in someComposable.js:
const config = useRuntimeConfig();
You can then access your variables as eg config.public.SOME_API_KEY
Hope that helps. More info here: https://v3.nuxtjs.org/guide/features/runtime-config
You can also involve .env like this
in .env
NUXT_PUBLIC_BASE_URL = https://www.someapi.com
in nuxt.config.js/ts
runtimeConfig: {
public: {
BASE_URL: 'some fallback value',
},
},
as it said in the document BASE_URL will be replaced by NUXT_PUBLIC_BASE_URL automatically
( no need to use process.env.NUXT_PUBLIC_BASE_URL )
and in composable you can use
const config = useRuntimeConfig();
console.log('base url is' , config.baseUrl)
Related
I am using Vue test utils and Typescript. I have added Data Test ID Plugin.
How can I extend VueWrapper interface to avoid this error:
Property 'findByTestId' does not exist on type 'VueWrapper<{ $: ComponentInternalInstance; $data: { showUserMenu: boolean ...
One solution is to export a type that adds findByTestId:
// my-vue-test-utils-plugin.ts
import { config, DOMWrapper, createWrapperError, type VueWrapper } from '#vue/test-utils'
👇
export type TestWrapper = VueWrapper<any> & {
findByTestId: (selector: string) => DOMWrapper<HTMLElement>
}
const DataTestIdPlugin = (wrapper: VueWrapper<any>) => {
function findByTestId(selector: string) {
const dataSelector = `[data-testid='${selector}']`
const element = wrapper.element.querySelector(dataSelector)
if (element) {
return new DOMWrapper(element)
}
return createWrapperError('DOMWrapper')
}
return {
findByTestId
}
}
config.plugins.VueWrapper.install(DataTestIdPlugin as any)
Then, use type assertion (as keyword followed by the exported type above) on the mount() result:
// MyComponent.spec.ts
import type { TestWrapper } from './my-vue-test-utils-plugin.ts'
describe('MyComponent', () => {
it('renders properly', () => { 👇
const wrapper = mount(MyComponent) as TestWrapper
expect(wrapper.findByTestId('my-component').text()).toBe('Hello World')
})
})
Another option is to create a .d.ts file e.g. vue-test-utils.d.ts with the following content:
import { DOMWrapper } from '#vue/test-utils';
declare module '#vue/test-utils' {
export class VueWrapper {
findByTestId(selector: string): DOMWrapper[];
}
}
So you're able to extend the existing definition of the VueWrapper class.
I am trying to build a vue js 2 microfrontend with module federation. I dont want to use static remote imports via the webpack.config.js like this
module.exports = {
plugins: [
new ModuleFederationPlugin({
name: 'host',
remotes: {
app1: 'app1#http://localhost:3001/remoteEntry.js',
},
}),
],
};
I am looking for a way to dynamically import vue components into my host application. I tried this approach so far, but i only found examples that worked with angular or react.
The goal is to have multiple remote frontends that can automatically register somewhere, maybe in some kind of store. The host application then can access this store and get all of the registered remote applications (name, url, components). The host application then loads the components and should be able to use them. I remote import the component HelloDerp, the loading process is working fine but i dont know how to render it on my host application. I read the vue js doc about dynamic and async imports but i think that only works for local components.
What i've got so far in the host application:
<template>
<div id="app">
<HelloWorld />
<HelloDerp />
</div>
</template>
<script>
import HelloWorld from "./components/HelloWorld.vue";
const HelloDerp = null;
export default {
name: "App",
components: {
HelloWorld,
HelloDerp,
},
mounted() {
var remoteUrlWithVersion = "http://localhost:9000/remoteEntry.js";
const element = document.createElement("script");
element.type = "text/javascript";
element.async = true;
element.src = remoteUrlWithVersion;
element.onload = () => {
console.log(`Dynamic Script Loaded: ${element.src}`);
HelloDerp = loadComponent("core", "./HelloDerp");
};
document.head.appendChild(element);
return null;
},
};
async function loadComponent(scope, module) {
// Initializes the shared scope. Fills it with known provided modules from this build and all remotes
await __webpack_init_sharing__("default");
const container = window[scope]; // or get the container somewhere else
// Initialize the container, it may provide shared modules
await container.init(__webpack_share_scopes__.default);
const factory = await window[scope].get(module);
const Module = factory();
return Module;
}
</script>
Sorry i almost forgot about this. Here's my solution.
Load Modules:
export default async function loadModules(
host: string,
ownModuleName: string,
wantedNames: string[]
): Promise<RemoteComponent[]> {
...
uiApplications.forEach((uiApplication) => {
const remoteURL = `${uiApplication.protocol}://${uiApplication.host}:${uiApplication.port}/${uiApplication.moduleName}/${uiApplication.fileName}`;
const { componentNames } = uiApplication;
const { moduleName } = uiApplication;
const element = document.createElement('script');
element.type = 'text/javascript';
element.async = true;
element.src = remoteURL;
element.onload = () => {
componentNames?.forEach((componentName) => {
const component = loadModule(moduleName, `./${componentName}`);
component.then((result) => {
if (componentName.toLowerCase().endsWith('view')) {
// share views
components.push(new RemoteComponent(result.default, componentName));
} else {
// share business logic
components.push(new RemoteComponent(result, componentName));
}
});
});
};
document.head.appendChild(element);
});
});
...
}
export default async function loadModule(scope: string, module: string): Promise<any> {
await __webpack_init_sharing__('default');
const container = window[scope]; // or get the container somewhere else
await container.init(__webpack_share_scopes__.default);
const factory = await window[scope].get(module);
const Module = factory();
return Module;
}
Add Modules to routes
router.addRoute({
name: remoteComponent.componentName,
path: `/${remoteComponent.componentName}`,
component: remoteComponent.component,
});
In vue 2+ I can easily get the instance of this as a result I can write something like this,
// main.js
app.use(ElMessage)
// home.vue
this.$message({
showClose: true,
message: 'Success Message',
type: 'success',
})
What should I do for vue 3 as,
Inside setup(), this won't be a reference to the current active
instance Since setup() is called before other component options are
resolved, this inside setup() will behave quite differently from this
in other options. This might cause confusions when using setup() along
other Options API. - vue 3 doc.
Using ElMessage directly
ElementPlus supports using ElMessage the same way as $message(), as seen in this example:
import { ElMessage } from 'element-plus'
export default {
setup() {
const open1 = () => {
ElMessage('this is a message.')
}
const open2 = () => {
ElMessage({
message: 'Congrats, this is a success message.',
type: 'success',
})
}
return {
open1,
open2,
}
}
}
Using $message()
Vue 3 provides getCurrentInstance() (an internal API) inside the setup() hook. That instance allows access to global properties (installed from plugins) via appContext.config.globalProperties:
import { getCurrentInstance } from "vue";
export default {
setup() {
const globals = getCurrentInstance().appContext.config.globalProperties;
return {
sayHi() {
globals.$message({ message: "hello world" });
},
};
},
};
demo
Note: Being an internal API, getCurrentInstance() could potentially be removed/renamed in a future release. Use with caution.
Providing a different method where the idea is to set a globally scoped variable to the _component property of the viewmodel/app or component:
pageVM = Vue.createApp({
data: function () {
return {
renderComponent: true,
envInfo: [],
dependencies: [],
userGroups: []
}
},
mounted: function () {
//Vue version 3 made it harder to access the viewmodel's properties.
pageVM_props = pageVM._component;
this.init();
},
I would like to be able the same axios instance used in the auth module (https://auth.nuxtjs.org/) in a javascript module where I make my API calls
I have the following
const BASE_URL = 'job';
export default {
getJobs(params?: Filter) {
return axios.get(BASE_URL, { params });
},
getJob(slug: string, params?: Filter) {
return axios.get(`${BASE_URL}/${slug}`, { params });
}
}
I would like to be able to use the same $axios instance inside of this js module. Something like:
const BASE_URL = 'job';
export default {
getJobs(params?: Filter) {
return this.$axios.get(BASE_URL, { params });
},
getJob(slug: string, params?: Filter) {
return this.$axios.get(`${BASE_URL}/${slug}`, { params });
}
}
Is this possible?
Yes it is possible to get axios instance using this.$axios in Nuxt.js
In almost all guides, tutorial, posts, etc that I have seen on vuex module registration, if the module is registered by the component the createNamespacedHelpers are imported and defined prior to the export default component statement, e.g.:
import {createNamespacedHelpers} from 'vuex'
const {mapState} = createNamespacedHelpers('mymod')
import module from '#/store/modules/mymod'
export default {
beforeCreated() {
this.$store.registerModule('mymod', module)
}
}
this works as expected, but what if we want the module to have a unique or user defined namespace?
import {createNamespacedHelpers} from 'vuex'
import module from '#/store/modules/mymod'
export default {
props: { namespace: 'mymod' },
beforeCreated() {
const ns = this.$options.propData.namespace
this.$store.registerModule(ns, module)
const {mapState} = createNamespacedHelpers(ns)
this.$options.computed = {
...mapState(['testVar'])
}
}
}
I thought this would work, but it doesnt.
Why is something like this needed?
because
export default {
...
computed: {
...mapState(this.namespace, ['testVar']),
...
},
...
}
doesnt work
This style of work around by utilising beforeCreate to access the variables you want should work, I did this from the props passed into your component instance:
import { createNamespacedHelpers } from "vuex";
import module from '#/store/modules/mymod';
export default {
name: "someComponent",
props: ['namespace'],
beforeCreate() {
let namespace = this.$options.propsData.namespace;
const { mapActions, mapState } = createNamespacedHelpers(namespace);
// register your module first
this.$store.registerModule(namespace, module);
// now that createNamespacedHelpers can use props we can now use neater mapping
this.$options.computed = {
...mapState({
name: state => state.name,
description: state => state.description
}),
// because we use spread operator above we can still add component specifics
aFunctionComputed(){ return this.name + "functions";},
anArrowComputed: () => `${this.name}arrows`,
};
// set up your method bindings via the $options variable
this.$options.methods = {
...mapActions(["initialiseModuleData"])
};
},
created() {
// call your actions passing your payloads in the first param if you need
this.initialiseModuleData({ id: 123, name: "Tom" });
}
}
I personally use a helper function in the module I'm importing to get a namespace, so if I hadmy module storing projects and passed a projectId of 123 to my component/page using router and/or props it would look like this:
import projectModule from '#/store/project.module';
export default{
props['projectId'], // eg. 123
...
beforeCreate() {
// dynamic namespace built using whatever module you want:
let namespace = projectModule.buildNamespace(this.$options.propsData.projectId); // 'project:123'
// ... everything else as above
}
}
Hope you find this useful.
All posted answers are just workarounds leading to a code that feels verbose and way away from standard code people are used to when dealing with stores.
So I just wanted to let everyone know that brophdawg11 (one of the commenters on the issue #863) created (and open sourced) set of mapInstanceXXX helpers aiming to solve this issue.
There is also series of 3 blog posts explaining reasons behind. Good read...
I found this from veux github issue, it seems to meet your needs
https://github.com/vuejs/vuex/issues/863#issuecomment-329510765
{
props: ['namespace'],
computed: mapState({
state (state) {
return state[this.namespace]
},
someGetter (state, getters) {
return getters[this.namespace + '/someGetter']
}
}),
methods: {
...mapActions({
someAction (dispatch, payload) {
return dispatch(this.namespace + '/someAction', payload)
}
}),
...mapMutations({
someMutation (commit, payload) {
return commit(this.namespace + '/someMutation', payload)
})
})
}
}
... or maybe we don't need mapXXX helpers,
mentioned by this comment https://github.com/vuejs/vuex/issues/863#issuecomment-439039257
computed: {
state () {
return this.$store.state[this.namespace]
},
someGetter () {
return this.$store.getters[this.namespace + '/someGetter']
}
},