Β My Experience Creating Open-Source

Abdelrahman Awad

Frontend Engineer @Robusta Studio

Open-source contributor and creator of vee-validate

Hello!

VeeValidate

An open-source library for Vue.js that offers client-side validation

GH Stars Monthly Downloads Releases Commits
7,100 530,000 134 3,148

Stats

How did it begin?

Early 2016

Early 2016 Code

function updateMessage(el, vm) {
  vm.errors = Object.assign({}, vm.errors, {
    [el.name]: el.validationMessage
  });
}

export const ValidateMixin = {
  data: () => ({
    errors: {}
  }),
  computed: {
    hasErrors() {
      // Check if we have errors.
      return Object.keys(this.errors).some(key => {
        return !!this.errors[key];
      });
    }
  },
  directives: {
    validate: {
      bind(el, _, vnode) {
        const vm = vnode.context;
        el.addEventListener("input", e => {
          updateMessage(e.target, vm);
        });
        vnode.context.$on("validate", () => {
          updateMessage(el, vm);
        });
      }
    }
  },
  methods: {
    validate() {
      this.$emit("validate");
    }
  }
};

So I searched for a package in Vue.js ecosystem that satisfied my needs

There was `vue-validator` but it wasn't actively maintained due to the author working on vue-i18n.

So I decided to build one

Decide the Target Audience

Also called the "Niche market"

Lesson #1

Target Audience

  • Backend Laravel Developers
  • Beginner JavaScript skills

I didn't plan that, I learned that after the fact

Decide the MVP

  • Custom Rule Validation.
  • As much as Laravel rules as possible.
  • Support all HTML5 input types.
  • Asynchronous Validation.
  • Cross-field validation.

Lesson #1: Revised

And it wasn't easy

Vue.js lifecycle

ES6 Promises

And of course....

Vue.js Reactivity

Input Events

Prototypes

Vue.js Rendering Mechanism

JavaScript concurrency

  • Easily extendable (userland-driven ecosystem)
  • Small API surface (Easier to learn and maintain)

API Considerations

β›”

β›”

Lesson #2

What makes React ecosystem so vibrant?

Small API surface area

React at its simplest level is a rendering layer/organization tool, it lets you describe your application in the form of simple primitives called components

How did I got indebted to a large API

Accepted Every New Feature PR

Implemented every feature-request

I was happy everyone was using my library and I intended to keep it that way, that affected how I see things and cost me a lot down the line

Trying to do 100%

Bundle size went from 55kb to 178kb in under a year

It stayed hovering around that size for another year

So I slowed everyone's apps for around:

1.02s

2G

0.62s

3G

Bundling

Webpack: Most Popular

Rollup: Was relatively New

Rolling your own bundler with Babel

Browserify: Very limited

Lesson #3

(function webpackUniversalModuleDefinition(root, factory) {
	if(typeof exports === 'object' && typeof module === 'object')
		module.exports = factory();
	else if(typeof define === 'function' && define.amd)
		define([], factory);
	else if(typeof exports === 'object')
		exports["VeeValidate"] = factory();
	else
		root["VeeValidate"] = factory();
})(this, function() {
return /******/ (function(modules) { // webpackBootstrap
/******/ 	// The module cache
/******/ 	var installedModules = {};

/******/ 	// The require function
/******/ 	function __webpack_require__(moduleId) {

/******/ 		// Check if module is in cache
/******/ 		if(installedModules[moduleId])
/******/ 			return installedModules[moduleId].exports;

/******/ 		// Create a new module (and put it into the cache)
/******/ 		var module = installedModules[moduleId] = {
/******/ 			i: moduleId,
/******/ 			l: false,
/******/ 			exports: {}
/******/ 		};

/******/ 		// Execute the module function
/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);

/******/ 		// Flag the module as loaded
/******/ 		module.l = true;

/******/ 		// Return the exports of the module
/******/ 		return module.exports;
/******/ 	}


/******/ 	// expose the modules object (__webpack_modules__)
/******/ 	__webpack_require__.m = modules;

/******/ 	// expose the module cache
/******/ 	__webpack_require__.c = installedModules;

/******/ 	// identity function for calling harmory imports with the correct context
/******/ 	__webpack_require__.i = function(value) { return value; };

/******/ 	// define getter function for harmory exports
/******/ 	__webpack_require__.d = function(exports, name, getter) {
/******/ 		Object.defineProperty(exports, name, {
/******/ 			configurable: false,
/******/ 			enumerable: true,
/******/ 			get: getter
/******/ 		});
/******/ 	};

/******/ 	// getDefaultExport function for compatibility with non-harmony modules
/******/ 	__webpack_require__.n = function(module) {
/******/ 		var getter = module && module.__esModule ?
/******/ 			function getDefault() { return module['default']; } :
/******/ 			function getModuleExports() { return module; };
/******/ 		__webpack_require__.d(getter, 'a', getter);
/******/ 		return getter;
/******/ 	};

/******/ 	// Object.prototype.hasOwnProperty.call
/******/ 	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };

/******/ 	// __webpack_public_path__
/******/ 	__webpack_require__.p = "";

/******/ 	// Load entry module and return exports
/******/ 	return __webpack_require__(__webpack_require__.s = 32);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ function(module, exports, __webpack_require__) {

"use strict";

console.log('hello world!');
  

Bundle Size

The cost of running JavaScript

Testing

Lesson #4

Why do we even test?

😎 Confidence

⌚ Backward Compatibility

🐞 Catching Bugs

🀝 For contributors

Testing 101: Terminology

Test Runner

Regression Tests

E2E Tests

Code Coverage

Integration Tests

Unit Tests

Testing Lessons

Coverage is not a goal its a metric

Testing Lessons

Testing Lessons

Start with integration tests

test('listens for input, blur events to set flags', async () => {
  const wrapper = mount(
    {
      data: () => ({
        value: ''
      }),
      template: `
        <ValidationProvider rules="required" v-slot="{ errors, ...rest }">
          <input v-model="value" type="text">
          <li v-for="(flag, name) in rest" v-if="flag" :id="name">{{ name }}</li>
        </ValidationProvider>
      `
    },
    { localVue: Vue, sync: false }
  );

  const input = wrapper.find('input');
  expect(wrapper).toHaveElement('#untouched');
  expect(wrapper).toHaveElement('#pristine');
});

Testing Lessons

With Pure Unit tests I had 300+ tests with 90% coverage

Right now I have a mix of mostly integration tests and some unit tests with a total of 123 tests with 95% coverage

Some Bugs cannot be tested

Semantic Versioning

3.2.4

Major

πŸ’€

Minor

🀩

Patch

😴

Semantic Versioning

Handling Issues

There are 10 types of people

Β Those who use GitHub issues correctly and those who don't

Non-english Speakers

Stackoverflow Proxies

😱

"Didn't read the docs"

Tips

Be patient, there might be a bug.

Don't be dismissive, be appreciative even if you are going to close the issue/PR

If someone is being rude, make it clear that they are overstepping and close/lock the issue.

Always ask for a reproduction via online sandbox tools or an online repo

Use issue templates and Template replies to save time

RT*M

Reading the manual is one thing, but what about....

WT*M

WT*M #1: Language

Verbosity

Tone

Content

Don't pre-annoucnce stuff

WT*M #2: Structure

Categorize by Topic and Sub-topics

Pictures

Pictures Examples

Always include code snippets and live examples if possible

WT*M #3: Examples

Other stuff

Marketing

Finding Time

Funding

Attracting Contributors

Thanks πŸ‘‹

Q&A

My Experience Creating Open Source

By Abdelrahman Awad

My Experience Creating Open Source

Small Lessons learned from the wild ride creating vee-validate

  • 1,483