Vue in a legacy application
December 2018 - Stéphane Reiss
How we did it at Alayacare
Who am I?
- Web developer
- PHP background
- I love javascript
Stéphane Reiss
- Tech company providing software to home care agencies (B2B)
- 5 years old
- 133 employees in 3 countries
(Canada, US, Australia) - 87 clients in 4 continents
- Daily usage: 7k care providers and 2k admins
P.S. We are hiring!
Legacy App
- Monolith PHP app
- 1 big database schema
- jQuery everywhere
- No tests
- No documentation
<head>
<script src="/scripts/files/jquery.js"></script>
<script type="text/javascript" src="..."></script>
// ... more files ...
<script type="text/javascript" src="..."></script>
</head>
<body>
<div class="body_1">
<div id="top">
// ... more elements ...
<div id="content" class="content-center-resize relative">
<div class="primary-content"></div>
<div class="secondary-content"></div>
</div>
// ... more elements ...
</div>
</div>
</body>
$(window).bind('hashchange', function(e) {
var url = $.param.fragment();
if (url == '') {
return
}
Dash.loadContent(url);
})
ROUTING
PAGE LAYOUT
Game plan
- Micro service architecture for the backend
- Frontend needs modularity, maintainability
- Two way data-binding
REQUIREJS + LESS + BOWER
First try
<head>
//...
<?php if (ENVIRONMENT == 'prod'): ?>
<?= Ui::getVersionedCSSTag('/web/css/main_old.css'); ?>
<?= Ui::getVersionedScriptTag('/web/js/built/old_app.js') ?>
<?php else: ?>
<link rel="stylesheet/less" type="text/css" href="/web/less/old_layout/main.less" />
<script type="text/javascript">var less = less || {}; less.env='development';</script>
<script src="/web/js/lib/vendor/less.min.js"></script>
<script type="text/javascript" src="/web/js/lib/vendor/require.js"></script>
<script type="text/javascript">
require(['/web/js/config.js'], function() {
require(['old_app']);
});
</script>
<?php endif; ?>
//...
</head>
define(['t3', 'vue'], (Box, Vue) => Box.Application.addModule('calendar-legend', () => {
const el = 'calendar-legend';
const props = ['statusList']; // [ { name }, { name }, ... ]
const template = '#calendar-legend-template';
return {
init() {
this.vue = new Vue({
el,
props,
template,
});
},
destroy() {
this.vue.$destroy();
},
};
}));
<template id="calendar-legend-template">
<div class="calendar-legend-container">
<ul id="calendar-legend" class="calendar-legend">
<li v-for="status in statusList"
:class="status.cssClass">
{{ status.ui_name }}
</li>
</ul>
</div>
</template>
<div data-module="calendar-legend">
<calendar-legend :status-list="<?= //... ?>"></calendar-legend>
</div>
PHP
T3 + VUEJS in a require module
Our first vue component
Let's start over!
<body>
<div class="body_1">
<div id="top">
// ... more elements ...
<div id="content" class="content-center-resize relative">
<div class="primary-content"></div>
<div class="secondary-content"></div>
</div>
// ... more elements ...
</div>
</div>
</body>
PAGE LAYOUT
<script type="text/javascript" src="/web/js/built/build.js"></script>
with webpack and Vue.js
module.exports = {
entry: {
build: './web/js/main.js',
css: './web/less/main.less',
old_css: './web/less/old_layout/main.less',
},
output: {
path: path.resolve(__dirname, './web/js/built'),
publicPath: '/web/js/built/',
filename: '[name].js',
hotUpdateChunkFilename: 'hot/hot-update.js',
hotUpdateMainFilename: 'hot/hot-update.json',
},
//...
};
webpack.config.js
import Vue from 'vue';
import tagsDefined from './components-export';
let components = [];
observeDOMChanges();
scanNodeForVueComponent(document);
function observeDOMChanges() {...}
function scanNodeForVueComponent(node) {...}
function createVueComponent(node) {...}
function destroyVueComponent(node) {...}
function destroyChildren(vueInstance) {..}
main.js
new Vue({
el: node,
components: {
[tagName]: tagsDefined[tagName],
},
});
vueInstance.$destroy();
export default {'component-a': require('./<path>/component-a'), ... 'component-x': require('./<path>/component-x'), }
<div class="widget">
<calendar-legend
:status-list="<?= //... ?>"
/>
</div>
Backend response
- Detect DOM Change
- Scan for components
- Create Vue components
- Destroy removed components
Vuex
Allow cross components communication through a centralized state
import store from 'components/vuex/store';
//...
new Vue({
el: node,
store,
components: {
[tagName]: tagsDefined[tagName],
},
});
//...
main.js
Vue router
Vue Router is the official router for Vue.js
$(window).bind('hashchange', function(e) {
var url = $.param.fragment();
if (url == '') {
return
}
Dash.loadContent(url);
})
ROUTING
import store from 'components/vuex/store';
import router from 'components/router';
//...
new Vue({
el: node,
store,
router,
components: {
[tagName]: tagsDefined[tagName],
},
});
//...
main.js
import Vue from 'vue';
import VueRouter from 'vue-router';
Vue.use(VueRouter);
const router = new VueRouter({
routes: [
{
path: '*',
component: LegacySection,
}
],
});
export default router;
router.js
<div class="primary-content">
<app>
<router-view></router-view>
</app>
</div>
PAGE LAYOUT
//...
{
path: '/settings/scheduling',
redirect: '/settings/scheduling/offer-responses',
component: VerticalTabs,
props: {
header: 'Scheduling Settings',
headerIcon: 'fa fa-cog',
mode: 'vertical',
showBreadcrumb: true,
},
children: [
{
name: 'recurrence-template',
path: 'recurrence-template',
component: LegacySection,
props: {
legacyUrl: '/scheduling/recurrencetemplate/list',
},
meta: {
label: i18n.t('Recurrence Template'),
},
},
{
name: 'entity-tags',
path: 'entity-tags',
component: LegacySection,
props: {
legacyUrl: '/scheduling/tag/list',
},
meta: {
label: i18n.t('Entity Tags'),
},
}
],
},
//...
More complex routes
router.js
Things I wish I knew
- Don't use Vue inside T3 inside RequireJs
- Build mini app by detecting DOM mutation
- Define core components
- Use vuex for cross component communication
- Test your components!
- <component>.vue
- <component>.test.vue
- JEST + Vue Test Utils (https://jestjs.io - https://vue-test-utils.vuejs.org)
- Refactor your old components as you improve your code base
- To not end up with 5 differents patterns after 2 years
- https://github.com/chrisvfritz/vue-enterprise-boilerplate
Thank you!
VueJs in legacy app
By Stéphane Reiss
VueJs in legacy app
How we introduced Vue.js at Alayacare.
- 725