So, I come from Angular world where by default we are separating view from business logic... and I don't like single file components, especially when they are getting bigger and convoluted.
Before in vue 2, I would have myComponent.vue with script tag and have it like so:
<template src="./myComponent.html"></template>
To call the view file... and it would work... all the methods were accessible
but now, if I use the setup way
<script setup lang="ts">
const suggets = () => {
}
</script>
<template src="./myComponent.html"></template>
I would get a typescript error that suggest was declared and never used...
Also for example:
<carbon-search /> component would not work
Failed to resolve component: carbon-search If this is a native custom
element, make sure to exclude it from component resolution via
compilerOptions.isCustomElement.
Any good and elegant way to split the component view and business logic (and css)?
I'm trying to implement some Testing Library tests on a Vuejs app, but I can't figure out how to pass props to a component within the test.
For example, I want a unit test for a component that appears inside of its ParentComponent template like this. I am trying to write a unit test for the ChildComponent.
<ChildComponent hereIsAProp="important info" />
I'm surprised this scenario isn't covered in the Vue Testing Library basic examples. Makes me think I'm missing some best practice around using/testing Vuejs props.
I imagine something like render(ChildComponent, { props: { hereIsAProp: "new info"}) should do the trick. But I can't find this in the docs and whatnot.
Testing Libary's render() is a wrapper for Vue Test Util's mount().
The second argument to render() is passed onto mount() as mounting options, and mount() can set the component's props with the props option (in version 2x) or propsData (in version 1x).
So your guess is actually correct:
render(ChildComponent, { props: { hereIsAProp: "new info" } })
I am currently trying to write some unit tests for my web application that I made with Nuxt, Vue and Vuetify. I decided to use the Jest framework and the vue-test-utils to write unit tests, but it seems like Jest or vue-test-utils refuses to mount my components.
Example
Let’s take this very simple test where i shallowMount my component and see if the component renders a div:
require("jsdom-global")();
import AlbumHeader from "#/components/Album/AlbumHeader";
import { shallowMount } from "#vue/test-utils";
describe("AlbumHeader.vue", () => {
it("renders a div", () => {
const wrapper = shallowMount(AlbumHeader);
expect(wrapper.contains("div")).toBe(true);
});
});
When I run this unit test it always fails with the error:
TypeError: Cannot read property 'child' of undefined
I feel like the problem has something to do with Vuetify. But even after creating new Vuetify instances in my test and creating a localVue I still cannot get it to pass the test. I feel like I have read every article on this topic and none of them seem to help. This is also the first time I use this framework so I really do not have a clue where this problem takes place.
I hope someone can explain to me what i am doing wrong and how i can fix it, i would really appreciate that.
In order to use a dynamically-defined single page component, we use the component tag, thusly:
<component v-bind:is="componentName" :prop="someProperty"/>
...
import DynamicComponent from '#/components/DynamicComponent.vue';
...
components: {
DynamicComponent
},
props: {
componentName: String,
someProperty: null,
}
The problem is, this isn't really very dynamic at all, since every component we could ever possibly want to use here needs to be not only imported statically, but also registered in components.
We tried doing this, in order at least to avoid the need to import everything:
created() {
import(`#/components/${this.componentName}.vue`);
},
but of course this fails, as it seems that DynamicComponent must be defined before reaching created().
How can we use a component that is truly dynamic, i.e. imported and registered at runtime, given only its name?
From the documentation: Emphasis mine
<!-- Component changes when currentTabComponent changes -->
<component v-bind:is="currentTabComponent"></component>
In the example above, currentTabComponent can contain either:
the name of a registered component,
or a component’s options object
If currentTabComponent is a data property of your component you can simply import the component definition and directly pass it to the component tag without having to define it on the current template.
Here is an example where the component content will change if you click on the Vue logo.
Like this:
<component :is="dynamic" />
...
setComponentName() {
this.dynamic = () => import(`#/components/${this.componentName}.vue`);
},
Solution for Nuxt only
As of now its possible to auto-import components in Nuxt (nuxt/components). If you do so, you have a bunch of components ready to be registered whenever you use them in your vue template e.g.:
<MyComponent some-property="some-value" />
If you want to have truly dynamic components combined with nuxt/components you can make use of the way Nuxt prepares the components automagically. I created a package which enables dynamic components for auto-imported components (you can check it out here: #blokwise/dynamic).
Long story short: with the package you are able to dynamically import your components like this:
<NuxtDynamic :name="componentName" some-property="some-value" />
Where componentName might be 'MyComponent'. The name can either be statically stored in a variable or even be dynamically created through some API call to your backend / CMS.
If you are interested in how the underlying magic works you can checkout this article: Crank up auto import for dynamic Nuxt.js components
According to the official Documentation: Starting from v2.13, Nuxt can auto import your components when used in your templates, to activate this feature, set components: true in your configuration
you are talking about async components. You simply need to use the following syntax to return the component definition with a promise.
Vue.component('componentName', function (resolve, reject) {
requestTemplate().then(function (response) {
// Pass the component definition to the resolve callback
resolve({
template: response
})
});
})
In PHP when we include code from another file, we include it and that's it, the code is now available to us within the file in which we performed the include. But in Vue.js, after importing a component we must also export it.
Why? Why don't we simply import it?
in Vue.js, after importing a component we must also export it.
I think you might be referring to the following lines in User.vue and wondering why UserDetail and UserEdit are imported into the file and then exported in the script export's components property:
import UserDetail from './UserDetail.vue';
import UserEdit from './UserEdit.vue';
export default {
components: {
appUserDetail: UserDetail,
appUserEdit: UserEdit
}
}
vue-loader expects the script export of a .vue file to contain the component's definition, which effectively includes a recipe to assemble the component's template. If the template contained other Vue components, the definition of the other components would need to be provided, otherwise known as component registration. As #Sumurai8 indicated, the import of the .vue files itself does not register the corresponding single-file-components; rather those components must be explicitly registered in the importer's components property.
For example, if App.vue's template contained <user /> and User.vue were defined as:
<template>
<div class="user">
<app-user-edit></app-user-edit>
<app-user-detail></app-user-detail>
</div>
</template>
<script>
export default {
name: 'user'
}
</script>
...the User component would be rendered blank, and you would see the following console errors:
[Vue warn]: Unknown custom element: <app-user-edit> - did you register the component correctly? For recursive components, make sure to provide the "name" option.
[Vue warn]: Unknown custom element: <app-user-detail> - did you register the component correctly? For recursive components, make sure to provide the "name" option.
demo 1
When Vue attempts to render <user /> inside App.vue's template, Vue doesn't know how to resolve the inner <app-user-detail> and <app-user-edit> because their component registrations are missing. The errors can be resolved by local component registration in User.vue (i.e., the components property shown above).
Alternatively, the errors can be resolved with global component registration of UserDetail and UserEdit, which would obviate the local registration in User.vue. Note that global registration must be done before creating the Vue instance. Example:
// main.js
import Vue from 'vue';
import UserDetail from '#/components/UserDetail.vue';
import UserEdit from '#/components/UserEdit.vue';
Vue.component('app-user-detail', UserDetail);
Vue.component('app-user-edit', UserEdit);
new Vue(...);
demo 2
Components in vue can be tricky. If you haven't yet, I would highly recommend reading the documentation on the vue.js website on how component registration works, specifically, as tony19 mentions global and local registration. The code example you show in your screenshot is actually doing a couple of things. For one, it is making the components available locally, and only locally (as in that .vue file) for use. In addition, it is making it available to the template as the key you provide in the components object, in this case, app-user-detail and app-user-edit instead of user-detail and user-edit.
Importantly, it should be mentioned that an import is not actually required for this component registration to function. You could have multiple components defined in a single file. The components key gives a way to identify what that component is using. So that import isn't required, so vue does require the components key to understand what you are using as a component, and what is just other code.
Finally, as some of the other answers have alluded to, the components key is not actually an export. The default signature of a vue component requires an export but this is not exporting the components listed under the components key. What it is doing is letting vue build in a top down manner. Depending on what the rest of your application setup looks like, you may be using single file components, or not. Either way, vue will start with the top level vue instance and work its way down through components, with the exception of global registration, no top level component knows which components are being used below it.
This means for vue to render things properly, each component has to include a reference to the extra components it uses. This reference is exported as part of the higher level component (in your case User.vue), but is not the component itself (UserDetail.vue).
So it may appear that vue requires a second export after import, but it is actually doing something else to allow the root vue instance to render your component.
As an aside, the vue documentation on this subject really is quite good, if you haven't already please take a look at the sections I linked above. There is an additional section on module import/export systems that seems highly relevant to what you are asking, you can find that here: Module-systems.
import imports code into the current file, but it does not do anything on its own. Imagine the following non-vue code:
// File helpers.js
export function tickle(target) {
console.log(`You tickle ${target}`)
}
// File main.js
import { tickle } from 'helpers'
You have imported the code, but it does not do anything. To actually tickle something, you need to call the function.
tickle('polar bear');
In Vue this works the same. You define a component (or actually just an Object), but the component does not do anything on it's own. You export this component so you can import it in other places where the Vue library can do something with this object.
In a Vue component you export your current component, and import components you use in your template. You generally do the following:
<template>
<div class="my-component">
<custom-button color="red" value="Don't click me" #click="tickle" />
</div>
</template>
<script>
import CustomButton from './CustomButton';
export default {
name: 'my-component',
components: {
CustomButton
}
}
</script>
Your component mentions a component named "custom-button". This is not a normal html element. It does not know what to do with it normally. So what do we do? We import it, then put it in components. This maps the name CustomButton to the component you imported. It now knows how to render the component.
The "magic" happens when you mount the root component using Vue, usually in your main.js.
import Vue from "vue";
import App from "./App";
Vue.config.productionTip = false;
/* eslint-disable no-new */
new Vue({
el: "#app",
components: { App },
template: "<App/>"
});
What does this do? You tell Vue to render <App/> in a html element identified by #app, and you tell that it should find this element in ./App.vue.
But can't we just omit export if the Vue compiler was 'smarter'? Yes, and no. Yes, because a compiler can transform a lot of things into valid javascript, and no because it makes no sense and severely limits what you can do with your component, while also making the compiler more bug-prone, less understandable and overall less useful.
In order for App.vue to be able to use the User-component you need to export the default object of the User.vue-file.
In the export default { you don't actually export the newly imported components. You are just exporting a completely normal JavaScript Object. This object just happens to have a reference to another Object.
When you import an object (or function or array or ...) it does not actually load the content of that file in to your component like PHP. It simply makes sure that your compiler (probably webpack) knows how to structure the program. It basically creates a reference so webpack knows where to look for functionality.
TL;DR
The import and export here are conceptually different and unrelated things, and both have to be used.
Importing a Vue component is the same with any other importing in JavaScript:
// foo.mjs
export function hello() {
return "hello world!";
}
// bar.mjs
import { hello } from './foo.mjs';
console.log(hello());
Now run node bar.mjs, you will get a feeling how the importing works -- you want to use something that is defined/implemented somewhere else, then you have to import it, regardless of whether it is a Vue component or not.
With regard to export, you are not exporting the components you imported. The only thing you are exporting is the current component. However, this current component may use some other subcomponents in its <template>, so one has to register those subcomponents, by specifying them in the components field in the exported object.