Save 37% off PRO during our Black Friday Sale! »

Adding Vue to an existing stack and get ready to scale

9b3ce79a4e2f5656234300be6c321f88?s=47 Roman Kuba
February 16, 2018

Adding Vue to an existing stack and get ready to scale

Greenfield projects are a luxury you very often don't have. In this talk, we will look at how we accomplish the task of adding Vue to an existing codebase while avoiding the pitfalls and get to a scale ready state.

9b3ce79a4e2f5656234300be6c321f88?s=128

Roman Kuba

February 16, 2018
Tweet

Transcript

  1. Photo by ⻉贝莉⼉儿 NG on Unsplash SCALING VUE IN AN

    EXISTING STACK
  2. IT’S NOT ALWAYS A GREENFIELD PROJECT Photo by Dawid Zawiła

    on Unsplash
  3. ADDING NEW TECH IS ALWAYS AN INVESTMENT Photo by Glenn

    Carstens-Peters on Unsplash
  4. BUT NECESSARY Photo by Glenn Carstens-Peters on Unsplash

  5. COMPETING TECH WILL RUN IN PARALLEL Photo by David Marcu

    on Unsplash
  6. WHERE TO DRAW THE LINE? Photo by Mariusz Prusaczyk on

    Unsplash
  7. A FULL SPA IS PROBABLY NOT POSSIBLE AT ALL Photo

    by Mariusz Prusaczyk on Unsplash
  8. HI MY NAME IS ROMAN KUBA SEN. SOFTWARE ENGINEER Photo

    by Mauro Licul on Unsplash @CODEBRYO @CODESHIP
  9. BEEN THERE, DONE THAT. Photo by Joanna Kosinska on Unsplash

  10. BEEN THERE, DONE THAT. Photo by Joanna Kosinska on Unsplash

    ‣ Stack dictated by jQuery, Angular and CoffeeScript ‣ Not a lot of resources ‣ Large amount of users ‣ A very backend heavy system
  11. YOU TEAM

  12. YOU TEAM Photo by Sweet Ice Cream Photography on Unsplash

  13. SPLIT THE PROCESS INTO PHASES

  14. REDUCE ALL THE THINGS PHASE 1 Photo by Brian Yu

    on Unsplash
  15. REDUCE Photo by Joanna Kosinska on Unsplash ‣ Decide on

    one language ‣ Remove unknowns ‣ Maybe refactor little bits and pieces
  16. Coffee $ !-> $('a.ajax').on 'click', (e) !-> e.prenventDefault() $.get($(this).attr('href')) .success

    (response) !-> console.log(response) $(function() { $('a.ajax').on('click', function(e) { e.preventDefault() $.get($(this).attr('href')) .success(function(response) { console.log(response) }) }) }) JS JS const nodes = document.querySelectorAll('a.ajax') Array.from(nodes).forEach( a !=> { a.addEventListener('click', (e) !=> { e.preventDefault() axios.get(a.href) .then(response !=> console.log(response) ) }) })
  17. Coffee $ !-> $('a.ajax').on 'click', (e) !-> e.prenventDefault() $.get($(this).attr('href')) .success

    (response) !-> console.log(response) $(function() { $('a.ajax').on('click', function(e) { e.preventDefault() $.get($(this).attr('href')) .success(function(response) { console.log(response) }) }) }) JS JS const nodes = document.querySelectorAll('a.ajax') Array.from(nodes).forEach( a !=> { a.addEventListener('click', (e) !=> { e.preventDefault() axios.get(a.href) .then(response !=> console.log(response) ) }) })
  18. Coffee $ !-> $('a.ajax').on 'click', (e) !-> e.prenventDefault() $.get($(this).attr('href')) .success

    (response) !-> console.log(response) $(function() { $('a.ajax').on('click', function(e) { e.preventDefault() $.get($(this).attr('href')) .success(function(response) { console.log(response) }) }) }) JS JS const nodes = document.querySelectorAll('a.ajax') Array.from(nodes).forEach( a !=> { a.addEventListener('click', (e) !=> { e.preventDefault() fetch(a.href) .then(response !=> console.log(response) ) }) })
  19. INTRODUCE VUE PHASE 2 Photo by Clem Onojeghuo on Unsplash

  20. PHASE 2 INTRODUCE VUE ▸ Choose a manageable target ▸

    Define one clear entry point for Vue ▸ Load Vue from a CDN ▸ Use what Vue offers out of the box
  21. <div vue="feature">!</div> HTML

  22. <div vue="feature">!</div> <script src="https:!//cdn.jsdelivr.net/npm/vue">!</script> <script> const vm = new Vue({

    el: '[vue="feature"]', data() { return { status: 'loading' } }, template: ` <div> <span class="status">{{ status }}!</span> !</div> `, }) !</script> HTML
  23. <div vue="feature" data-url="https:!//api.statuspage.com/codeship">!</div> HTML

  24. <div vue="feature" data-url="https:!//api.statuspage.com/codeship">!</div> HTML LEVERAGE VUE LIFECYCLE

  25. <div vue="feature" data-url="https:!//api.statuspage.com/codeship">!</div> <script> const vm = new Vue({ el:

    '[vue="feature"]', data() { return { url: null, status: 'loading' } }, template: `…`, methods: { fetchStatus() { !// !!... } }, beforeMount() { this.url = this.$el.dataset.url this.fetchStatus() } }) !</script> HTML
  26. BETTER BUILD-PROCESS PHASE 3 Photo by Clem Onojeghuo on Unsplash

  27. WEBPACK IS YOUR FRIEND

  28. APP JS JS V N

  29. APP JS JS V N

  30. APP JS JS N V

  31. PHASE3 BETTER BUILD-PROCESS ▸ Aim for clean separation (No Globals,

    No legacy) ▸ Perfect chance to introduce Vue SFC
  32. <template> <div> <span class="status">{{ status }}!</span> !</div> !</template> <script> export

    default { props: { url: String }, data() { return { status: 'loading' } }, methods: { fetchStatus() { !// !!... } }, created() { this.fetchStatus() } } !</script> VUE
  33. ARE YOU READY Photo by Geran de Klerk on Unsplash

  34. BUILD A SPA PHASE 4 Photo by Clem Onojeghuo on

    Unsplash
  35. DIDN’T YOU SAY AN SPA DOESN’T MAKE SENSE? Photo by

    Mariusz Prusaczyk on Unsplash
  36. APP SPA

  37. APP SPA

  38. APP SPA SPA SPA SPA TREAT PAGES AS 
 SEPARATE

    SPAS
  39. PERFECT TIME TO GO ALL IN ON THE VUE ECOSYSTEM

    AND IT’S POWERS Photo by Glenn Carstens-Peters on Unsplash
  40. ADD VUEX Photo by Samuel Zeller on Unsplash

  41. None
  42. APP JS JS N V

  43. APP JS JS N V

  44. APP JS PACK N V PACK PACK

  45. head body .content = vue_app :users SLIM <head> !</head> <body>

    <div class="content"> <!?= vue_app('users') ?> !</div> !</body> PHP
  46. head body .content = vue_app :users SLIM <head> !</head> <body>

    <div class="content"> <!?= vue_app('users') ?> !</div> !</body> PHP
  47. head body .content = vue_app :users SLIM <head> !</head> <body>

    <div class="content"> <!?= vue_app('users') ?> !</div> !</body> PHP users-pack
  48. head body .content = vue_app :users SLIM <div id="vue-app" vue-app="users">!</div>

  49. head body .content = vue_app :users, users: @users.to_json, role: current_user.role

    SLIM <div id="vue-app" 
 vue-app="users" 
 data-users="!!..." 
 data-role="admin">!</div>
  50. head body .content = vue_app :users, users: @users.to_json, role: current_user.role

    SLIM <div id="vue-app" 
 vue-app="users" 
 data-users="!!..." 
 data-role="admin">!</div> 1. MOUNT VUE 2. ADD STORE 3. PASS DATA
  51. JS import Vue from 'vue' import Vuex from 'vuex' import

    { entries, merge } from 'lodash' export function app(name, component, customStore = {}) { Vue.use(Vuex) const node = document.querySelector(`#vue-app[vue-app=${name}]`) !// Basic Root store const defaultStore = { state: { !!... }, getters: { !!... }, plugins: [!!...] } const store = merge({}, defaultStore, customStore) !// Read and pass props into the main coponent const props = {} entries(node.dataset).forEach(([key, value]) !=> { try { props[key] = JSON.parse(value) } catch (e) { props[key] = value } }) !// Basic Root instance
  52. JS import Vue from 'vue' import Vuex from 'vuex' import

    { entries, merge } from 'lodash' export function app(name, component, customStore = {}) { Vue.use(Vuex) const node = document.querySelector(`#vue-app[vue-app=${name}]`) !// Basic Root store const defaultStore = { state: { !!... }, getters: { !!... }, plugins: [!!...] } const store = merge({}, defaultStore, customStore) !// Read and pass props into the main coponent const props = {} entries(node.dataset).forEach(([key, value]) !=> { try { props[key] = JSON.parse(value) } catch (e) { props[key] = value } }) !// Basic Root instance
  53. JS import Vue from 'vue' import Vuex from 'vuex' import

    { entries, merge } from 'lodash' export function app(name, component, customStore = {}) { Vue.use(Vuex) const node = document.querySelector(`#vue-app[vue-app=${name}]`) !// Basic Root store const defaultStore = { state: { !!... }, getters: { !!... }, plugins: [!!...] } const store = merge({}, defaultStore, customStore) !// Read and pass props into the main coponent const props = {} entries(node.dataset).forEach(([key, value]) !=> { try { props[key] = JSON.parse(value) } catch (e) { props[key] = value } }) !// Basic Root instance
  54. JS import Vue from 'vue' import Vuex from 'vuex' import

    { entries, merge } from 'lodash' export function app(name, component, customStore = {}) { Vue.use(Vuex) const node = document.querySelector(`#vue-app[vue-app=${name}]`) !// Basic Root store const defaultStore = { state: { !!... }, getters: { !!... }, plugins: [!!...] } const store = merge({}, defaultStore, customStore) !// Read and pass props into the main coponent const props = {} entries(node.dataset).forEach(([key, value]) !=> { try { props[key] = JSON.parse(value) } catch (e) { props[key] = value } }) !// Basic Root instance
  55. JS entries(node.dataset).forEach(([key, value]) !=> { try { props[key] = JSON.parse(value)

    } catch (e) { props[key] = value } }) !// Basic Root instance const instance = { name: 'App', store: new Vuex.Store(store), render(h) { return h( 'div', { attrs: { id: `app-${name}`, }, }, [h(component, { props })] ) }, } return new Vue(instance).$mount(node) }
  56. JS import { app } from '!../lib/app' import Store from

    '!../apps/users/store' import Users from '!../apps/users' app('users', Users, Store) VUE <template> <ul> <user v-for="user in users" :key="user.id" :canEdit="canEdit" !/> !</ul> !</template> <script> import User from './user' export default { name: 'Users', props: ['users', 'role'], computed: { canEdit() { return this.role !!=== 'admin' } } } !</script>
  57. JS import { app } from '!../lib/app' import Store from

    '!../apps/users/store' import Users from '!../apps/users' app('users', Users, Store) VUE <template> <ul> <user v-for="user in users" :key="user.id" :canEdit="canEdit" !/> !</ul> !</template> <script> import User from './user' export default { name: 'Users', props: ['users', 'role'], computed: { canEdit() { return this.role !!=== 'admin' } } } !</script>
  58. GET READY TO SCALE PHASE 5

  59. APP SPA SPA SPA SPA

  60. APP SPA SPA SPA SPA PACK PACK PACK

  61. SPA SPA SPA SPA PACK PACK PACK

  62. PREPARE SCAFFOLDING AND GENERATORS Photo by Rachael Gorjestani on Unsplash

  63. Photo by Rachael Gorjestani on Unsplash

  64. BACK IT UP BY PATTERNS AND SPECS Photo by Rachael

    Gorjestani on Unsplash
  65. GOOD TIMES AHEAD CONCLUSION

  66. Acceptance Specs Unit Specs

  67. 2s 0,25s

  68. feature feature feature

  69. VUE MADE IT ENJOYABLE Photo by Yvette de Wit on

    Unsplash
  70. VUE MADE IT ENJOYABLE … BECAUSE IT’S JUST JS DONE

    RIGHT Photo by Yvette de Wit on Unsplash
  71. THANKS Photo by Yvette de Wit on Unsplash