I think everyone already knows how to write a search for GitHub users on React , Svelte , Angular , or even without them . So how can you do without Vue? It's time to fill this gap.
So today we will create that application using Vue, write tests for it on Cypress and touch on Vue CLI 3 a bit.
There are gifs in the post
First, install the latest version of the Vue CLI:
npm i -g @vue/cli
And run the creation of the project:
vue create vue-github-search
Follow the steps of the generator. For our project, I chose Manual mode and the following configuration:
We will use Stylus as styles, so we will need a stylus and a stylus-loader. We also need Axios for network requests and Lodash , from which we will take the debounce function.
Go to the project folder and install the necessary packages:
cd vue-github-search npm i stylus stylus-loader axios lodash
We start the project and make sure that everything works:
npm run serve
All changes in the code will be instantly applied in the browser without reloading the page.
Let's start with what we write vuex store, where there will be all the data applications. All we need to do is store anything: a search query, user data, and a boot process flag.
Open the store.js
and describe the initial state of the application and the necessary mutations:
... const SET_SEARCH_QUERY = 'SET_SEARCH_QUERY'; const SET_LOADING = 'SET_LOADING'; const SET_USER = 'SET_USER'; const RESET_USER = 'RESET_USER'; export default new Vuex.Store({ state: { searchQuery: '', loading: false, user: null }, mutations: { [SET_SEARCH_QUERY]: (state, searchQuery) => state.searchQuery = searchQuery, [SET_LOADING]: (state, loading) => state.loading = loading, [SET_USER]: (state, user) => state.user = user, [RESET_USER]: state => state.user = null } });
Add action actions to load data from the GitHub API and to change the search query (we need it for the search string). As a result, our store will look like this:
store.js
import Vue from 'vue'; import Vuex from 'vuex'; import axios from 'axios'; Vue.use(Vuex); const SET_SEARCH_QUERY = 'SET_SEARCH_QUERY'; const SET_LOADING = 'SET_LOADING'; const SET_USER = 'SET_USER'; const RESET_USER = 'RESET_USER'; export default new Vuex.Store({ state: { searchQuery: '', loading: false, user: null }, mutations: { [SET_SEARCH_QUERY]: (state, searchQuery) => state.searchQuery = searchQuery, [SET_LOADING]: (state, loading) => state.loading = loading, [SET_USER]: (state, user) => state.user = user, [RESET_USER]: state => state.user = null }, actions: { setSearchQuery({commit}, searchQuery) { commit(SET_SEARCH_QUERY, searchQuery); }, async search({commit, state}) { commit(SET_LOADING, true); try { const {data} = await axios.get(`https://api.github.com/users/${state.searchQuery}`); commit(SET_USER, data); } catch (e) { commit(RESET_USER); } commit(SET_LOADING, false); } } });
Create a new Search.vue
component in the components
folder. Add a computed property to associate the component with the store. At changes of search query we will cause search with debounce.
Search.vue
<template> <input v-model="query" @input="debouncedSearch" placeholder="Enter username" /> </template> <script> import {mapActions, mapState} from 'vuex'; import debounce from 'lodash/debounce'; export default { name: 'search', computed: { ...mapState(['searchQuery']), query: { get() { return this.searchQuery; }, set(val) { return this.setSearchQuery(val); } } }, methods: { ...mapActions(['setSearchQuery', 'search']), debouncedSearch: debounce(function () { this.search(); }, 500) } }; </script> <style lang="stylus" scoped> input width 100% font-size 16px text-align center </style>
Now we connect our search string to the main component of App.vue
and in the App.vue
delete the extra lines created by the generator.
App.vue
<template> <div id="app"> <Search /> </div> </template> <script> import Search from './components/Search'; export default { name: 'app', components: { Search } }; </script> <style lang="stylus"> #app font-family 'Avenir', Helvetica, Arial, sans-serif font-smoothing antialiased margin 10px </style>
Let's see the result in the browser, making sure that everything works with the help of vue-devtools:
As you can see, we have all the application logic ready! We enter the user name, the request is executed and the profile data is stored in the store.
Create a User.vue
component and add logic to indicate loading, profile display, and an error when the user is not found. Also add a transition animation.
<template> <div class="github-card"> <transition name="fade" mode="out-in"> <div v-if="loading" key="loading"> Loading </div> <div v-else-if="user" key="user"> <div class="background" :style="{backgroundImage: `url(${user.avatar_url})`}" /> <div class="content"> <a class="avatar" :href="`https://github.com/${user.login}`" target="_blank"> <img :src="user.avatar_url" :alt="user.login" /> </a> <h1>{{user.name || user.login}}</h1> <ul class="status"> <li> <a :href="`https://github.com/${user.login}?tab=repositories`" target="_blank"> <strong>{{user.public_repos}}</strong> <span>Repos</span> </a> </li> <li> <a :href="`https://gist.github.com/${user.login}`" target="_blank"> <strong>{{user.public_gists}}</strong> <span>Gists</span> </a> </li> <li> <a :href="`https://github.com/${user.login}/followers`" target="_blank"> <strong>{{user.followers}}</strong> <span>Followers</span> </a> </li> </ul> </div> </div> <div v-else key="not-found"> User not found </div> </transition> </div> </template> <script> import {mapState} from 'vuex'; export default { name: 'User', computed: mapState(['loading', 'user']) }; </script> <style lang="stylus" scoped> .github-card margin-top 50px padding 20px text-align center background #fff color #000 position relative h1 margin 16px 0 20px line-height 1 font-size 24px font-weight 500 .background filter blur(10px) opacity(50%) z-index 1 position absolute top 0 left 0 right 0 bottom 0 background-size cover background-position center background-color #fff .content position relative z-index 2 .avatar display inline-block overflow hidden background #fff border-radius 100% text-decoration none img display block width 80px height 80px .status background white ul text-transform uppercase font-size 12px color gray list-style-type none margin 0 padding 0 border-top 1px solid lightgray border-bottom 1px solid lightgray zoom 1 &:after display block content '' clear both li width 33% float left padding 8px 0 box-shadow 1px 0 0 #eee &:last-of-type box-shadow none strong display block color #292f33 font-size 16px line-height 1.6 a color #707070 text-decoration none &:hover color #4183c4 .fade-enter-active, .fade-leave-active transition opacity .5s .fade-enter, .fade-leave-to opacity 0 </style>
App.vue
connect our component in App.vue
and enjoy the result:
<template> <div id="app"> <Search /> <User /> </div> </template> <script> import Search from './components/Search'; import User from './components/User'; export default { name: 'app', components: { User, Search } }; </script> <style lang="stylus"> #app font-family 'Avenir', Helvetica, Arial, sans-serif font-smoothing antialiased margin 10px </style>
We write simple tests for our application.
tests / e2e / specs / test.js
describe('Github User Search', () => { it('has input for username', () => { cy.visit('/'); cy.get('input'); }); it('has "User not found" caption', () => { cy.visit('/'); cy.contains('User not found'); }); it("finds Linus Torvalds' GitHub page", () => { cy.visit('/'); cy.get('input').type('torvalds'); cy.contains('Linus Torvalds'); cy.get('img'); cy.contains('span', 'Repos'); cy.contains('span', 'Gists'); cy.contains('span', 'Followers'); }); it("doesn't find nonexistent page", () => { cy.visit('/'); cy.get('input').type('_some_random_name_6m92msz23_2'); cy.contains('User not found'); }); });
Run the tests with the command
npm run test:e2e
In the window that opens, click the Run all specs button and see that the tests pass:
Vue CLI 3 supports the new application build mode, modern mode. It creates 2 versions of scripts: lightweight for modern browsers that support the latest JavaScript features, and the full version with all the necessary polyfiles for older ones. The main charm is that we absolutely do not need to bother with the deployment of such an application. It just works. If the browser supports <script type="module">
, it will tighten the lightweight build itself. How it works, you can read more in this article .
Add the modern flag to the package.json
to the build command:
"build": "vue-cli-service build --modern"
We collect the project:
npm run build
Let's look at the sizes of the final scripts:
8.0K ./app-legacy.cb7436d4.js 8.0K ./app.b16ff4f7.js 116K ./chunk-vendors-legacy.1f6dfb2a.js 96K ./chunk-vendors.a98036c9.js
As you can see, the new method really reduces the size of the assembly. The difference will be even more noticeable on large projects, so the feature definitely deserves attention.
That's all, thank you for your attention!
Source: https://habr.com/ru/post/420351/
All Articles