🛠️

Front-End engineering principles

Intro

As you will learn working with us, we hold some software engineering principles close to our hearts especially the separation of concerns.

Respecting these principles allow us to be relatively fast and efficient while building and shipping features but this comes at a price: some constraints are to be respected.

The goal of this document is to explain how we work and the choices we made.

We hope that once you get familiar with the codebase and acquire these reflexes, coding will be as enjoyable as possible for you 😌

File structure

Domains

The front-end code is grouped by Domains.

A domain very loosely designates either a product feature (available to the user) or a helper functionality (a suite of utils fonctions achieving a particular role).

When you work on a particular functionality, chances are you will only edit files in a single domain. This is good for keeping technical debt in check [1].

If you find yourself editing a large number of files across multiple directories, chances are you need to refactor your code into a new domain. Creating new domains is allowed and encouraged!

Files

Every domain contains very specific files which are the following:

  • index.ts : the entry point for every domain
  • api.ts : communication with the server
  • linkedin.ts : communication with LinkedIn server
  • logic.ts : interaction with the pages and complex logic
  • components.ts : React components or HTML populated templates
  • types.ts : TypeScript types (available publicly)
  • Sometimes utils.ts for bits of logic that don’t fit in logic.ts

Domain Directories as of 1.26.0
Domain Directories as of 1.26.0

Outside of the domains, the files utils.ts and utilsApi.ts contain small utility functions and are available globally.

💡

What about the rest of the files? Outside the /app directory, other important files are: - inject.ts , the starting point of every page reload - inject.scss injecting all the styles located in /styles - popup.ts , code related to the extension popup However the large majority of the logic is located in /app and this is the primary focus of this document.

Organizing dependencies

Organizing the code by domains is good for grouping related bits together and avoids having to switch across multiple parts of the repository while coding.

However we found that another way to constraint our system and therefore making it more scalable was controlling where dependency injections happen [2].

Private scopes

Each domain enables a private scope, index.ts is the only possible access point from the outside.

This enables us to import functions in the following fashion:

import * as contact from /Contact

And use them in the following way: contact.get() , contact.add() , contact.update()

Which makes for cleaner and more readable code.

However, and this is the part when things can get a bit tedious, you have to manually export your functions in index.ts to make them available to the rest of the application.

This won’t be done automatically and is a necessary step for each pull request you will submit.

The exact rules

  1. Files from one domain (including index.ts) should only import:
    1. global files OR
    2. files from its own domain OR
    3. index.ts from other domains OR
    4. types from anywhere
    5. (which basically means you can import from anything you want except “private” files from other domains, ie logic.ts, api.ts, etc.)

  2. Circular dependencies are not allowed (ie. file1file2file3file1)

💡

What if I forget to do this ? We integrated a CI tool called dependency-cruiser that will scream at you kindly remind you that you’re injecting dependencies the wrong way and fail your pull request.

Resources

Here are some tech talks that go more in depth in the underlying concepts of the decisions we took: