CategoryVueJS

VueJS Plugins: developing with HRM

I’ve never being a fan of keeping backend and frontend codebase together, though some of the well known project achieve it quiet well like Discourse.

Keeping JavaScript aside for backend solutions, as part of standalone UI elements seemed as decent solution. But when you have to develop Vue components as plugins and keep the project running – testing becomes messy.

I spent half a day trying to figure out the ways how you can use symlinks to pick local packages to work in dummy VueJS app, but no luck. Both for yarn and npm. There’ve been a long story of resolving these issues for numerous packages like here, or here.

Simple solution with mocking an App behaviour with a sprinkle of chokidar package solves this problem. Well, at least to some extend:

  • You can independently develop Vue Components as plugins
  • From the box testing
  • Once e2e and unit tests are done, you can publish and integrate these plugins in to your application.

Hot Reload for Development environment

First things first – tools being used:

  • vue: 2.9.6
  • node: v10.16.0 (LTS)
  • yarn|npm: whatever make you happy!
  • webpack-dev-server: ^3.7.2

Build instructions are split in 3 parts (base, dev, prod). I’m using webpack-merge to overwrite base with required instructions, depending on the yarn command:

//package.json part with live reload instructions
"scripts": {
     "serve": "webpack-dev-server --config ./build/webpack.dev.config.js --hot --progress -d",
     "test": "jest"
}

Going further – HRM specs, to make things run:

const merge = require('webpack-merge')
const chokidar = require('chokidar')
const HtmlWebpackPlugin = require('html-webpack-plugin')
const base = require('./webpack.base.config.js')

module.exports = merge(base, {
  entry: './dev/dev.js',
  plugins: [
      new HtmlWebpackPlugin({
        template: './dev/index.html',
        inject: true,
      }),
  ],
  optimization: {
   noEmitOnErrors: true,
  },
  mode: 'development',
  devtool: '#eval-source-map',
  devServer: {
    hot: true,
    hotOnly: true,
    open: true,
    inline: true,
    stats: {
      children: false,
      modules: false,
      chunks: false,
    },
    port: 8080,
    before (app, server) {
      chokidar.watch([
        './**/*.html',
      ]).on('all', function () {
        server.sockWrite(server.sockets, 'content-changed');
      })
    },
  }
})

Whenever we run yarn serve script, it will load a node server, and open a ./dev/index.html file template which solely has a <div id="app"></div> container.

Example of Dev structure

Now we simply add a wrapper component Dev.vue that will contain whatever we want to test:

//dev.js
import Vue from 'vue'
import Dev from './Dev.vue'

Vue.config.productionTip = false

new Vue({
  render: h => h(Dev)
}).$mount("#app")

Let’s say we have a simple Calendar.vue component we need to load when running serve script:

<template>
  <div id="app">
    <Calendar/>
  </div>
</template>
<script>
import Calendar from '@/components/Calendar.vue'

export default {
  components: {
    Calendar
  }
}
</script>

Fullcalendar.io for Vue: No more custom scripts

Few years, ago, I had to dance around fullcalendar.io library to get it working nicely in VueJS framework. Some wrappers, custom components and lots of external jQuery code to make it work.

No more of it! Now, cakephp-calendar can be re-written using fullcalendar-vue package. And it sound like a perfect weekend task:

  • Getting rid of custom event treating
  • RFC all the way with Recurrences
  • Clean State Management and stand-alone JS npm package (instead of all-in-one solution)

Congratulations to developers from fullcalendar community, who did a great job moving away to TypeScript, and making the codebase more incorporating with modern frameworks like Vue/React/Angular.

VueJS.London – Jest for Vue testing

Images Copyright www.edtelling.com

In September, I was lucky to attend VueJS London conference. It was an amazing experience, with lots of interesting talented people from different corners of the EU and the States.

I’ll slowly start assembling the info on the talks that took place there. Once the slides become available I will start adding them here as well.

One of the greatest talks at the conference was by Roman Kuba. It was about test utils for VueJS, and actual experience on integrating Jest for Vue infrastructure.

Qobrix is slowly integrating VueJS as a sidekick for all the sophisticated UI/UX solutions, trying to reduce jQuery impact on the platform. One of the reasons behind a slow pace of integration – kind of frustrating situation with plethora of test tools.

Quick googling on the subject, brings some more structured materials from Roman, on VueJS/Jest integrations and how they do it in CodeShip.

One of the things that’s definitely worth checking – vue-test-utils.

ReactJS: BSD + Patents licensing model

The devil is in details, so people say. Few months ago, while working on prototype of cakephp-calendar, we had an interesting debate over front-end stack for Calendar component.

At the same time, WordPress community was buzzing about their text editor – Gutenberg whether to use ReactJS vs VueJS libraries. One of the reasons of choosing VueJS, was licensing agreement.

The license granted hereunder will terminate, automatically and without notice,
if you (or any of your subsidiaries, corporate affiliates or agents) initiate directly or indirectly, or take a direct financial interest in, any Patent Assertion…(c)

Investing man-hours into VueJS research and prototyping was right, especially after Raul Kripalani published an interesting article on the license review of ReactJS.

Updated: One more review of FB React JS licensing from WordPress Tavern.

VueJS watchers for MomentJS

It took me some time to figure out what’s been going wrong with the watchers working on datepicker integration for CakePHP-Calendar plugin.

Problem domain

On loading modal window for creating calendar event, one of the dropdowns pass default start/end time for the event interval (start/end dates). This config object is passed to as a property into interval component that contains two child datepicker components.

Whenever the config property changes, it adjusts startMoment and endMoment objects and passes them into datepickers. The change is monitored via watch functionality of VueJS.

Example

Vue.component('interval-holder', {
    template: `<div>
        <datepicker :date-moment="startMoment"></datepicker>
        <datepicker :date-moment="endMoment"></datepicker>
    </div>`,
    props: ['config', 'initialMoment'],
    data: function() {
        return {
            startMoment: null,
            endMoment: null
        };
    },
    watch: {
        config: function() {
            this.startMoment = this.initialMoment;
            this.startMoment.set({'hour': this.config.start.hour, 'minute': this.config.start.min});
            
            this.endMoment = this.initialMoment; 
            this.endMoment.set({'hour': this.config.end.hour, 'minute': this.config.end.min});
        }
    }
});

Vue.component('datepicker', {
    // value var is useless here, as we use daterangepicker from jQuery 🙁
    template: `<div>
        <input type="text" :value="value"/>
    </div>`,
    props: ['dateMoment'],
    mounted: function() {
        var self = this;
        //load jquery bootstrap-datepicker instance
        this.instance = $(this.$el).find('input').daterangepicker().data('daterangepicker');
        
        $(this.$el).find('input').on('apply.daterangepicker', function(ev, picker) {
            self.momentObject = picker.startDate;
            self.value = picker.startDate.format('YYYY-MM-DD HH:mm');
        });
    },
    data: function() {
        return {
            instance:null,
            value: null,
            momentObject: null,
        };
    },
    watch: {
        dateMoment: function() {
            this.momentObject = this.dateMoment;
            this.value = this.momentObject.format('YYYY-MM-DD HH:mm');
        },
    }
});

If we set hours and minutes the following way as in the example above, dateMoment watcher won’t catch the change. According to the docs, it’s adviced to use deep param:

To also detect nested value changes inside Objects, you need to pass in deep: true in the options argument. Note that you don’t need to do so to listen for Array mutations.

watch: {
    dateMoment: {
        handler: function(val, old) {
            console.log('dateMoment changed');
        },
        deep: true
    }
}

Still no luck. Apparently, MomentJS object “too deep”. The only way to get it working ended up like this, change the parent component moment objects assignment, keeping the child-component watch function unchanged.

watch: {
    config: function() {
        var newStart = moment(this.startMoment);
        newStart.set({'hour': .., 'minute': ..});
        this.startMoment = newStart;
        
        //create a new instance of newEnd and assigning it to this.endMoment
        // get the dateMoment observe the change.
        // Ugly? Yes. Working? Yes..
    }
}

If anyone has a better (more elegant) way to fix this issue – welcome in the comments section! 🙂