Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Adding Vue to an existing stack and get ready to scale

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.

Roman Kuba

February 16, 2018
Tweet

More Decks by Roman Kuba

Other Decks in Programming

Transcript

  1. A FULL SPA IS PROBABLY NOT POSSIBLE AT ALL Photo

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

    by Mauro Licul on Unsplash @CODEBRYO @CODESHIP
  3. 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
  4. REDUCE Photo by Joanna Kosinska on Unsplash ‣ Decide on

    one language ‣ Remove unknowns ‣ Maybe refactor little bits and pieces
  5. 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) ) }) })
  6. 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) ) }) })
  7. 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) ) }) })
  8. 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
  9. <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
  10. <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
  11. PHASE3 BETTER BUILD-PROCESS ▸ Aim for clean separation (No Globals,

    No legacy) ▸ Perfect chance to introduce Vue SFC
  12. <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
  13. PERFECT TIME TO GO ALL IN ON THE VUE ECOSYSTEM

    AND IT’S POWERS Photo by Glenn Carstens-Peters on Unsplash
  14. head body .content = vue_app :users SLIM <head> !</head> <body>

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

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

    <div class="content"> <!?= vue_app('users') ?> !</div> !</body> PHP users-pack
  17. 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>
  18. 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
  19. 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
  20. 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
  21. 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
  22. 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
  23. 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) }
  24. 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>
  25. 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>
  26. VUE MADE IT ENJOYABLE … BECAUSE IT’S JUST JS DONE

    RIGHT Photo by Yvette de Wit on Unsplash