GueHub рдкрд░ Vue рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдЦреЛрдЬ рдХреИрд╕реЗ рдХрд░реЗрдВ

рдореБрдЭреЗ рд▓рдЧрддрд╛ рд╣реИ рдХрд┐ рд╣рд░ рдХреЛрдИ рдкрд╣рд▓реЗ рд╕реЗ рд╣реА рдЬрд╛рдирддрд╛ рд╣реИ рдХрд┐ рдХреИрд╕реЗ рд░рд┐рдПрдХреНрдЯ , рд╕реНрд╡реЗрд▓реНрдЯ , рдХреЛрдгреАрдп рдпрд╛ рдЙрди рдкрд░ рдЙрдирдХреЗ рдмрд┐рдирд╛ GitHub рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛рдУрдВ рдкрд░ рдПрдХ рдЦреЛрдЬ рд▓рд┐рдЦрдирд╛ рд╣реИред рдЦреИрд░, рдЖрдк рдмрд┐рдирд╛ рд╡реА рдХреЗ рдХреИрд╕реЗ рдХрд░ рд╕рдХрддреЗ рд╣реИрдВ? рдЗрд╕ рдЕрдВрддрд░рд╛рд▓ рдХреЛ рднрд░рдиреЗ рдХрд╛ рд╕рдордп рдЖ рдЧрдпрд╛ рд╣реИ


рдЫрд╡рд┐


рддреЛ, рдЖрдЬ рд╣рдо Vue рдХрд╛ рдЙрдкрдпреЛрдЧ рдХрд░рдХреЗ рдПрдХ рд╣реА рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдмрдирд╛рдПрдВрдЧреЗ, рдЗрд╕рдХреЗ рд▓рд┐рдП рд╕рд░реВ рдкрд░ рдЗрд╕рдХреЗ рд▓рд┐рдП рдкрд░реАрдХреНрд╖рдг рд▓рд┐рдЦреЗрдВрдЧреЗ рдФрд░ Vue CLI 3 рдХреЛ рдереЛрдбрд╝рд╛ рдкреНрд░рднрд╛рд╡рд┐рдд рдХрд░реЗрдВрдЧреЗред


рдкреЛрд╕реНрдЯ рдореЗрдВ gif рд╣реИрдВ


рдЯреНрд░реЗрдирд┐рдВрдЧ


рдЖрд░рдВрдн рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП, рдирд╡реАрдирддрдо рд╕рдВрд╕реНрдХрд░рдг рдХрд╛ Vue CLI рд╕реНрдерд╛рдкрд┐рдд рдХрд░реЗрдВ:


npm i -g @vue/cli 

рдФрд░ рдкрд░рд┐рдпреЛрдЬрдирд╛ рдХреЗ рдирд┐рд░реНрдорд╛рдг рдХреЛ рдЪрд▓рд╛рдиреЗ рдХреЗ рд▓рд┐рдП:


 vue create vue-github-search 

рдЬрдирд░реЗрдЯрд░ рдХреЗ рдЪрд░рдгреЛрдВ рдХрд╛ рдкрд╛рд▓рди рдХрд░реЗрдВред рд╣рдорд╛рд░реА рдкрд░рд┐рдпреЛрдЬрдирд╛ рдХреЗ рд▓рд┐рдП, рдореИрдВрдиреЗ рдореИрдиреБрдЕрд▓ рдореЛрдб рдФрд░ рдирд┐рдореНрдирд▓рд┐рдЦрд┐рдд рд╡рд┐рдиреНрдпрд╛рд╕ рдХреЛ рдЪреБрдирд╛:


рдЫрд╡рд┐


рдЕрддрд┐рд░рд┐рдХреНрдд рдореЙрдбреНрдпреВрд▓


рд╣рдо рд╕реНрдЯрд╛рдЗрд▓рд╕ рдХрд╛ рдЙрдкрдпреЛрдЧ рд╢реИрд▓рд┐рдпреЛрдВ рдХреЗ рд░реВрдк рдореЗрдВ рдХрд░реЗрдВрдЧреЗ, рдЗрд╕рд▓рд┐рдП рд╣рдореЗрдВ рд╕реНрдЯрд╛рдЗрд▓рд╕ рдФрд░ рд╕реНрдЯрд╛рдЗрд▓рд╕-рд▓реЛрдбрд░ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИред рд╣рдореЗрдВ рдиреЗрдЯрд╡рд░реНрдХ рдЕрдиреБрд░реЛрдзреЛрдВ рдФрд░ рд▓реЛрджрд╛рд╢ рдХреЗ рд▓рд┐рдП Axios рдХреА рднреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ, рдЬрд┐рд╕рдореЗрдВ рд╕реЗ рд╣рдо рдбрд┐рдмреНрдпреВ рдлрдВрдХреНрд╢рди рд▓реЗрдВрдЧреЗред


рдкреНрд░реЛрдЬреЗрдХреНрдЯ рдлрд╝реЛрд▓реНрдбрд░ рдореЗрдВ рдЬрд╛рдПрдВ рдФрд░ рдЖрд╡рд╢реНрдпрдХ рдкреИрдХреЗрдЬ рдЗрдВрд╕реНрдЯреЙрд▓ рдХрд░реЗрдВ:


 cd vue-github-search npm i stylus stylus-loader axios lodash 

рдЪреЗрдХ


рд╣рдо рдкрд░рд┐рдпреЛрдЬрдирд╛ рд╢реБрд░реВ рдХрд░рддреЗ рд╣реИрдВ рдФрд░ рд╕реБрдирд┐рд╢реНрдЪрд┐рдд рдХрд░рддреЗ рд╣реИрдВ рдХрд┐ рд╕рдм рдХреБрдЫ рдХрд╛рдо рдХрд░рддрд╛ рд╣реИ:


  npm run serve 

рдХреЛрдб рдореЗрдВ рд╕рднреА рдкрд░рд┐рд╡рд░реНрддрди рдкреГрд╖реНрда рдХреЛ рдкреБрдирдГ рд▓реЛрдб рдХрд┐рдП рдмрд┐рдирд╛ рддреБрд░рдВрдд рдмреНрд░рд╛рдЙрдЬрд╝рд░ рдореЗрдВ рд▓рд╛рдЧреВ рдХрд┐рдП рдЬрд╛рдПрдВрдЧреЗред


рджреБрдХрд╛рди


рдЪрд▓реЛ vuex рд╕реНрдЯреЛрд░ рд▓рд┐рдЦрдХрд░ рд╢реБрд░реВ рдХрд░реЗрдВ, рдЬрд╣рд╛рдВ рд╕рднреА рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдбреЗрдЯрд╛ рд╣реЛрдВрдЧреЗред рд╣рдореЗрдВ рдХреЗрд╡рд▓ рдХреБрдЫ рднреА рд╕рдВрдЧреНрд░рд╣реАрдд рдХрд░рдиреЗ рдХреА рдЖрд╡рд╢реНрдпрдХрддрд╛ рд╣реИ: рдПрдХ рдЦреЛрдЬ рдХреНрд╡реЗрд░реА, рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдбреЗрдЯрд╛ рдФрд░ рд▓реЛрдбрд┐рдВрдЧ рдкреНрд░рдХреНрд░рд┐рдпрд╛ рдХрд╛ рдПрдХ рдЭрдВрдбрд╛ред
store.js рдЦреЛрд▓реЗрдВред рдЖрд╡реЗрджрди рдХреА рдкреНрд░рд╛рд░рдВрднрд┐рдХ рд╕реНрдерд┐рддрд┐ рдФрд░ рдЖрд╡рд╢реНрдпрдХ рдЙрддреНрдкрд░рд┐рд╡рд░реНрддрди рдХрд╛ рд╡рд░реНрдгрди рдХрд░реЗрдВ:


 ... 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 } }); 

GitHub API рд╕реЗ рдбреЗрдЯрд╛ рд▓реЛрдб рдХрд░рдиреЗ рдФрд░ рдЦреЛрдЬ рдХреНрд╡реЗрд░реА рдХреЛ рд╕рдВрд╢реЛрдзрд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рдХреНрд░рд┐рдпрд╛рдПрдБ рдЬреЛрдбрд╝реЗрдВ (рд╣рдореЗрдВ рдЗрд╕реЗ рдЦреЛрдЬ рд╕реНрдЯреНрд░рд┐рдВрдЧ рдХреЗ рд▓рд┐рдП рдЖрд╡рд╢реНрдпрдХ рд╣реИ)ред рдкрд░рд┐рдгрд╛рдорд╕реНрд╡рд░реВрдк, рд╣рдорд╛рд░рд╛ рд╕реНрдЯреЛрд░ рдирд┐рдореНрдирд▓рд┐рдЦрд┐рдд рдлреЙрд░реНрдо рд▓реЗрдЧрд╛:


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); } } }); 

рд╕реНрдЯреНрд░рд┐рдВрдЧ рдЦреЛрдЬреЗрдВ


components рдлрд╝реЛрд▓реНрдбрд░ рдореЗрдВ рдПрдХ рдирдпрд╛ Search.vue components рдмрдирд╛рдПрдБред рд╕реНрдЯреЛрд░ рдХреЗ рд╕рд╛рде рдШрдЯрдХ рдХреЛ рдЬреЛрдбрд╝рдиреЗ рдХреЗ рд▓рд┐рдП рдПрдХ рд╕рдВрдЧрдгрд┐рдд рд╕рдВрдкрддреНрддрд┐ рдЬреЛрдбрд╝реЗрдВред рдЬрдм рдЦреЛрдЬ рдХреНрд╡реЗрд░реА рдмрджрд▓ рдЬрд╛рддреА рд╣реИ, рддреЛ рд╣рдо рдЦреЛрдЬ рдХреЛ рдмрд╣рд╕ рдХреЗ рд╕рд╛рде рдХрд╣реЗрдВрдЧреЗред


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> 

рдЕрдм рд╣рдо рдЕрдкрдиреЗ рдЦреЛрдЬ рд╕реНрдЯреНрд░рд┐рдВрдЧ рдХреЛ App.vue рдХреЗ рдореБрдЦреНрдп рдШрдЯрдХ рд╕реЗ App.vue рдФрд░ рд╕рд╛рде рд╣реА рд╕рд╛рде рдЬрдирд░реЗрдЯрд░ рджреНрд╡рд╛рд░рд╛ рдмрдирд╛рдИ рдЧрдИ рдЕрддрд┐рд░рд┐рдХреНрдд рд▓рд╛рдЗрдиреЛрдВ рдХреЛ рд╣рдЯрд╛ App.vue ред


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> 

рдЖрдЗрдП рдмреНрд░рд╛рдЙрдЬрд╝рд░ рдореЗрдВ рдкрд░рд┐рдгрд╛рдо рджреЗрдЦреЗрдВ, рд╕реБрдирд┐рд╢реНрдЪрд┐рдд рдХрд░реЗрдВ рдХрд┐ рд╕рдм рдХреБрдЫ vue-devtools рдХреЗ рд╕рд╛рде рдХрд╛рдо рдХрд░рддрд╛ рд╣реИ:


рдЫрд╡рд┐


рдЬреИрд╕рд╛ рдХрд┐ рдЖрдк рджреЗрдЦ рд╕рдХрддреЗ рд╣реИрдВ, рд╣рдорд╛рд░реЗ рдкрд╛рд╕ рд╕рднреА рдПрдкреНрд▓рд┐рдХреЗрд╢рди рд▓реЙрдЬрд┐рдХ рддреИрдпрд╛рд░ рд╣реИрдВ! рд╣рдо рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдирд╛рдо рджрд░реНрдЬ рдХрд░рддреЗ рд╣реИрдВ, рдЕрдиреБрд░реЛрдз рдирд┐рд╖реНрдкрд╛рджрд┐рдд рд╣реЛрддрд╛ рд╣реИ рдФрд░ рдкреНрд░реЛрдлрд╝рд╛рдЗрд▓ рдбреЗрдЯрд╛ рд╕реНрдЯреЛрд░ рдореЗрдВ рд╕рдВрдЧреНрд░рд╣реАрдд рдХрд┐рдпрд╛ рдЬрд╛рддрд╛ рд╣реИред


рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдкреНрд░реЛрдлрд╝рд╛рдЗрд▓


User.vue рдШрдЯрдХ рдмрдирд╛рдПрдВ рдФрд░ рд▓реЛрдб рдХреЛ рдЗрдВрдЧрд┐рдд рдХрд░рдиреЗ рдХреЗ рд▓рд┐рдП рддрд░реНрдХ рдЬреЛрдбрд╝реЗрдВ, рдЙрдкрдпреЛрдЧрдХрд░реНрддрд╛ рдирд╣реАрдВ рдорд┐рд▓рдиреЗ рдкрд░ рдкреНрд░реЛрдлрд╝рд╛рдЗрд▓ рдФрд░ рдПрдХ рддреНрд░реБрдЯрд┐ рдкреНрд░рджрд░реНрд╢рд┐рдд рдХрд░реЗрдВред рд╕рдВрдХреНрд░рдордг рдПрдирд┐рдореЗрд╢рди рднреА рдЬреЛрдбрд╝реЗрдВред


User.vue
 <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 рдореЗрдВ рд╣рдорд╛рд░реЗ рдШрдЯрдХ рдХреЛ рдХрдиреЗрдХреНрдЯ рдХрд░реЗрдВ рдФрд░ рдкрд░рд┐рдгрд╛рдо рдХрд╛ рдЖрдирдВрдж рд▓реЗрдВ:


App.vue
 <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> 

рдЫрд╡рд┐


рдкрд░реАрдХреНрд╖рдг


рд╣рдо рдЕрдкрдиреЗ рдЖрд╡реЗрджрди рдХреЗ рд▓рд┐рдП рд╕рд░рд▓ рдкрд░реАрдХреНрд╖рдг рд▓рд┐рдЦреЗрдВрдЧреЗред


рдкрд░реАрдХреНрд╖рдг / e2e / рдЪрд╢реНрдорд╛ / 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'); }); }); 

рдХрдорд╛рдВрдб рдХреЗ рд╕рд╛рде рдкрд░реАрдХреНрд╖рдг рдЪрд▓рд╛рдПрдВ


 npm run test:e2e 

рдЦреБрд▓рдиреЗ рд╡рд╛рд▓реА рд╡рд┐рдВрдбреЛ рдореЗрдВ, рд╕рднреА рд╕реНрдкреЗрдХреНрд╕ рдмрдЯрди рдкрд░ рдХреНрд▓рд┐рдХ рдХрд░реЗрдВ рдФрд░ рджреЗрдЦреЗрдВ рдХрд┐ рдкрд░реАрдХреНрд╖рдг рдкрд╛рд╕ рд╣реИрдВ:


рдЫрд╡рд┐


рд╕рднрд╛


Vue CLI 3 рдирдП рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдмрд┐рд▓реНрдб рдореЛрдб, рдЖрдзреБрдирд┐рдХ рдореЛрдб рдХрд╛ рд╕рдорд░реНрдерди рдХрд░рддрд╛ рд╣реИред рд╡рд╣ рд╕реНрдХреНрд░рд┐рдкреНрдЯ рдХреЗ 2 рд╕рдВрд╕реНрдХрд░рдг рдмрдирд╛рддрд╛ рд╣реИ: рдЖрдзреБрдирд┐рдХ рдмреНрд░рд╛рдЙрдЬрд╝рд░реЛрдВ рдХреЗ рд▓рд┐рдП рд╣рд▓реНрдХрд╛ рдЬреЛ рдирд╡реАрдирддрдо рдЬрд╛рд╡рд╛рд╕реНрдХреНрд░рд┐рдкреНрдЯ рд╕реБрд╡рд┐рдзрд╛рдУрдВ рдХрд╛ рд╕рдорд░реНрдерди рдХрд░рддрд╛ рд╣реИ, рдФрд░ рдкреБрд░рд╛рдиреЗ рд▓реЛрдЧреЛрдВ рдХреЗ рд▓рд┐рдП рд╕рднреА рдЖрд╡рд╢реНрдпрдХ рдкреЙрд▓реАрдлрд╝рд╛рдЗрд▓реНрд╕ рдХреЗ рд╕рд╛рде рдПрдХ рдкреВрд░реНрдг рд╕рдВрд╕реНрдХрд░рдгред рдореБрдЦреНрдп рдЖрдХрд░реНрд╖рдг рдпрд╣ рд╣реИ рдХрд┐ рд╣рдореЗрдВ рдРрд╕реЗ рдПрдкреНрд▓рд┐рдХреЗрд╢рди рдХреА рддреИрдирд╛рддреА рд╕реЗ рдкрд░реЗрд╢рд╛рди рд╣реЛрдиреЗ рдХреА рдмрд┐рд▓реНрдХреБрд▓ рдЬрд░реВрд░рдд рдирд╣реАрдВ рд╣реИред рдпрд╣ рд╕рд┐рд░реНрдл рдХрд╛рдо рдХрд░рддрд╛ рд╣реИред рдпрджрд┐ рдмреНрд░рд╛рдЙрдЬрд╝рд░ <script type="module"> рдХрд╛ рд╕рдорд░реНрдерди рдХрд░рддрд╛ рд╣реИ, рддреЛ рдпрд╣ рд╣рд▓реНрдХреЗ рдмрд┐рд▓реНрдб рдХреЛ рд╕реНрд╡рдпрдВ рдЦреАрдВрдЪ рд▓реЗрдЧрд╛ред рдпрд╣ рдХреИрд╕реЗ рдХрд╛рдо рдХрд░рддрд╛ рд╣реИ, рдЖрдк рдЗрд╕ рд▓реЗрдЦ рдореЗрдВ рдЕрдзрд┐рдХ рдкрдврд╝ рд╕рдХрддреЗ рд╣реИрдВред


рдЖрдзреБрдирд┐рдХ рдлреНрд▓реИрдЧ рдХреЛ package.json рдореЗрдВ рдЬреЛрдбрд╝реЗрдВред рдирд┐рд░реНрдорд╛рдг рдХрдорд╛рдВрдб рдореЗрдВ рд░рдЦреЗрдВ:


 "build": "vue-cli-service build --modern" 

рдПрдХ рдкрд░рд┐рдпреЛрдЬрдирд╛ рдореЗрдВ рдПрдХ рд╕рд╛рде рд▓рд╛рдирд╛:


 npm run build 

рдЖрдЗрдП рдкрд░рд┐рдгрд╛рдореА рд▓рд┐рдкрд┐рдпреЛрдВ рдХреЗ рдЖрдХрд╛рд░ рдХреЛ рджреЗрдЦреЗрдВ:


 8.0K ./app-legacy.cb7436d4.js 8.0K ./app.b16ff4f7.js 116K ./chunk-vendors-legacy.1f6dfb2a.js 96K ./chunk-vendors.a98036c9.js 

рдЬреИрд╕рд╛ рдХрд┐ рдЖрдк рджреЗрдЦ рд╕рдХрддреЗ рд╣реИрдВ, рдирдИ рд╡рд┐рдзрд┐ рд╡рд╛рд╕реНрддрд╡ рдореЗрдВ рд╡рд┐рдзрд╛рдирд╕рднрд╛ рдХреЗ рдЖрдХрд╛рд░ рдХреЛ рдХрдо рдХрд░рддреА рд╣реИред рдЕрдВрддрд░ рдмрдбрд╝реА рдкрд░рд┐рдпреЛрдЬрдирд╛рдУрдВ рдкрд░ рдФрд░ рднреА рдЕрдзрд┐рдХ рдзреНрдпрд╛рди рджреЗрдиреЗ рдпреЛрдЧреНрдп рд╣реЛрдЧрд╛, рдЗрд╕рд▓рд┐рдП рдирд┐рд╢реНрдЪрд┐рдд рд░реВрдк рд╕реЗ рдпрд╣ рд╕реБрд╡рд┐рдзрд╛ рдзреНрдпрд╛рди рджреЗрдиреЗ рдпреЛрдЧреНрдп рд╣реИред


рдХреЛрдб


GitHub


рдбреЗрдореЛ


рдпрд╣ рд╕рдм, рджреЗрдЦрдиреЗ рдХреЗ рд▓рд┐рдП рдзрдиреНрдпрд╡рд╛рдж!

Source: https://habr.com/ru/post/hi420351/


All Articles