Part 2: The Front-end Stack

This article is part of a series about the technologies we use at eve. For the first part, refer to Part 1: The API Stack.

A Vue at the Core

At eve, we use Vue and its official plugins, Vuex and Vue Router, extensively, and for very good reasons.

Created by Evan You in 2014, Vue emerged around 2017 as one of the most promising JavaScript frameworks and a great alternative to Facebook's React. Describing itself as The Progressive JavaScript Framework, Vue quickly rose to become the most popular frontend framework on GitHub and widely regarded as arguably the most developer-friendly framework out there—according to the State of Vue 2019 report, a whopping 92% of the respondents claimed they would use Vue again for their next project thanks to how easy to use yet extremely powerful it was. To quote the report itself:

A framework can ask for no better review than that. Having this many developers with prior experience in Vue.js who are still strongly motivated to use it is undoubtedly impressive.

Nowadays, along with React, Vue is considered to be a de-facto standard for front-end JavaScript frameworks, coming packaged with exceptional performance, elegant syntax, an extremely low barrier to entry, a top-of-class documentation and a mature, battle-tested ecosystem. Among its users are such prominent names as IBM, Gitlab, GitHub, Adobe, Behance, Alibaba, EuroNews and even NASA. eve adopting Vue from the get-go means the core of our products will always be in good hands.

Vue continues leading the chart of most-starred front-end frameworks of 2020. Source: 2020 JavaScript Rising Stars.

And as if those are not enough reasons, at eve, we have extensive experience and inside-out knowledge of the framework and it's ecosystem. With one of the first Vue core members leading the team, we have an absolute advantage and the experience has proven to be very useful.

TypeScript for the Win

If Vue can get any better, it's through the use of TypeScript. Developed and maintained by Microsoft, TypeScript is an enhancement on top of the traditional JavaScript, which adds static type definitions, validates your code and helps prevent common bugs and pitfalls even during development. For fast-growing front-end web projects, TypeScript has proven itself to be so critical—in fact, many developers have claimed to not be able to go back to "normal JavaScript" after getting so used to TypeScript's features, toolings and type security.

Using TypeScript at eve, therefore, is a no-brainer, especially since TypeScript has always been receiving first-class support in Vue. As a demonstration, let's take a look at one of our Vue components, CreateEvent.vue (stripped down for brevity):

export default Vue.extend({
  props: {
    events: {
      type: Array,
      required: true
    } as PropOptions<Event[]>
  },

  computed: {
    eventItems (): DropdownOption[] {
      return this.events.map(event => ({
        id: event.id,
        text: event.attributes.name
      }))
    }
  },

  methods: {
    async searchItems (input: string): Promise<DropdownOption[]> {
      const events = this.eventItems.filter(item => includesLowerCase(item.text, input))
      const places = await this.searchPlaces(input)

      return [...events, ...places]
    },

    async searchPlaces (input: string): Promise<DropdownOption[]> {
      const places: google.maps.places.AutocompletePrediction[] = await getAutocompletePlaces(input)

      return places.map(place => ({
        id: place.place_id,
        text: place.description
      }))
    }
  }
})

Here through the power of TypeScript, we've ensured that:

  • the events prop will always be an array of Event objects, each exposing a predefined set of properties (name, location, dates and so on)
  • the eventItems computed property will always return a list of DropdownOption objects, each in the shape of { id: string, text: string }
  • the searchItems() method expects exactly one parameter of type string and will return an array of DropdownOptionobjects, and so on

Should we use traditional JavaScript, none of these rules would have been enforceable due to the dynamic nature of the language. With TypeScript, however, we can rest assured that all of our variables and methods are now bound to a type contract—for example, having this.searchItems(false) somewhere in the above component will cause TypeScript to yield a TS2345: Argument of type 'false' is not assignable to parameter of type 'string' error and refuse to compile our application altogether, preventing us from having a bug at runtime. On top of that, a modern IDE/editor like Visual Studio Code or WebStorm will be able to infer the types and provide us with intelligent code completion, error detection and powerful refactoring.

At eve, we use TypeScript everywhere in the front-end codebase: the Vue components, the Vuex stores, the services, helpers, utilities and of course, the tests, which brings us to the next topic…

Testing, Testing, Testing

We ship new features, improvements and bug fixes to production multiple times every sprint, under only one hard condition: The code must not be broken. For this condition to be met, we maintain an extensive test suite for our front-end application—actually, two of them: one for unit tests, and another for integration. Our approach to tests, of course, varies from case to case, but we follow a couple of (albeit unwritten) rules in general:

  • Tests should be treated as first-class citizens. Too many programmers see tests as afterthoughts. We don't. We treat our tests the same way we treat production code—with proper design and architecture. In fact, we even start with "promoting" our tests by placing their files right next to the corresponding components:
$ tree ./mod-header/components
./mod-header/components
├── EventsNavigation.spec.ts
├── EventsNavigation.vue
└── header-actions
    ├── ActionDropdown.spec.ts
    ├── ActionDropdown.vue
    ├── UserAction.spec.ts
    └── UserAction.vue

This way we can tell at a glance which file has been covered and which can use some coverage improvement.

  • Untested code is broken code. Whilst we believe 100% code coverage is pointless and good coverage is in no way a testament to a bug-free codebase, we strive to cover all critical paths in our application with meaningful tests—the registration and authentication flows, event creation and management processes, the subscription and payment system, etc.
  • Bad tests are worse than no tests. A bad test gives you a false sense of security (you think your code is fine just because the test passes when it actually isn't) and prevents you from attempting the proper test. At the same time, since tests are code, it still requires maintenance nonetheless, sometimes a significant amount thereof. In short, you would be wasting your effort on something that doesn't even work in the first place.

For unit tests, we use Jest, the most popular modern JavaScript testing framework, in conjunction with Vue Test Utils, the official unit testing library from Vue. For integration tests, Cypress is our test runner of choice. We integrate both with Istanbul and collect/report the coverage via Codecov. The result: A high-quality codebase with great coverage, providing absolute confidence for refactoring and other improvements of various shapes and sizes.

Sample coverage for one of our front-end modules
Automated Codecov report showing coverage information for every relevant pull request

Epilogue

It's never been easy to build an application. It's even harder to build an excellent application. Granted, the front-end landscape has changed quite rapidly in recent years for the better, but with all of those shiny new tools in the shed comes the struggling—and sometimes, the pain—of instrumenting them together. At eve, we believe we've been handling this critical task successfully and with elegance so far. What does the future of web development have in store for us? We'll see! In the meantime, stay tuned for part 3 of the series where we'll talk about our development environment and CI/CD pipelines.