The Ultimate Guide to Local Development Setup

Zico Deng
7 min readJun 19, 2019

Every developer knows how crucial local development setup is. A good local development setup should make it super easy to run the app locally with just one or few commands, e.g. yarn start, but a bad one…

The new guy: yo! I just joined the company. How can I run the app locally?

The lead: yoooo! check out our contributing guide!

The new guy: word!

After an hour…

The new guy: WTF! Am I reading a contributing guide or an academic paper with 2000+ word count. I need to download xxx first, then xxx, and then xxx…

After three hours…

The new guy: finally, got everything installed and set up. Let me try running the app…

The coolest app: invalid ELF header :(

The new guy: Wut! I just did exactly what the contributing guide told me to do. Damn, already 5pm, time to go home.

You got the story, and unfortunately, it happens very frequently.

In this post, I will walk you through three different levels of local development setups, from newbie to journeyman, and finally expert.

The Newbie Setup

Manually link external stylesheets and scripts in HTML

Wut? what do you mean by setting up local development? Why can’t we just link external stylesheets and scripts in HTML by using <link> and <script> tags? Uhm…this is totally valid approach. However, there are a few problems:

  • How would you manage hundreds of dependencies? A simple application might only have a few dependencies, but a complicated one can have hundreds. Would you still want to manually link them one by one in HTML?
  • How would you control dependency orders? Dependency orders matter for both JavaScript modules and CSS stylesheets. You wouldn’t want to encounter undefined variables or styles being overridden unexpectedly.
  • How would you test new changes in the browser? With this approach, the only way to pick up new changes is by refreshing the entire page. What if your application is huge and reloading might take close to a minute?

The Journeyman Setup

Module bundlers

Module bundlers can transform, bundle, or package modules with dependencies into static assets for browser usage.

Webpack is currently the most popular module bundler for building modern web applications. However, working with Webpack is not that straightforward. Some basic knowledge of configuration is often needed.

Below are some commonly used loaders for Webpack:

  • babel-loader: transpiles advanced JavaScript syntax with Babel.
  • ts-loader: transpiles TypeScript to JavaScript.
  • sass-loader: compile SASS to CSS.
  • postcss-loader: processes CSS with PostCSS plugins.
  • css-loader: takes CSS files and generates JavaScript modules that exports our CSS code.
  • style-loader: takes output from css-loader and creates <style> tags for our CSS and injects them into HTML <head>. style-loader is often chained with css-loader and postcss-loader/sass-loader.
  • file-loader: allows us to import files such as images, videos, and fonts in JavaScript. It works by resolving a file import into a url and emitting the file into the output directory.
  • thread-loader: runs expensive loaders in a separate process.
  • cache-loader: caches result of expensive loaders on disk. Often used together with thread-loader, they can potentially improve Webpack build time if configured correctly.

In addition to loaders, we also need to configure various Webpack plugins. Here are some commonly used ones:

  • html-webpack-plugin: Even a newbie knows that to make our app show up in the browser, we have to link to external stylesheets and scripts with correct filenames in HTML. However, when we are working with loaders mentioned above, most of them output filenames with hashes. Who wants to manually inject those files with hashed names into HTML every time when Webpack rebuilds? Fortunately, we have html-webpack-plugin come to rescue. It smartly inserts those files for us when Webpack rebuilds.
  • fork-ts-checker-webpack-plugin: runs TypeScript type checking on a separate process. Only useful when we are working with TypeScript loaders such as ts-loader. This plugin can potentially improve build time because TypeScript loaders are by default responsible for two things: type checking and transpiling TypeScript. However, by using this plugin, we can delegate type checking on a separate process so that TypeScript loaders now only need to focus on transpiling TypeScript to JavaScript.
  • mini-css-extract-plugin: takes output of css-loader and extracts CSS into separate files. It is recommended to only use this plugin and loader for production. Use style-loader instead for development. (Note: this plugin only works with Webpack4).
  • dotenv-webpack: load environment variables from .env files into our application.
  • favicons-webpack-plugin: generates favicons for iOS, android devices and browsers for a given .png file.
  • filemanager-webpack-plugin: process files before and after Webpack build.

Webpack is not only for generating development bundles but for production bundles as well. Check out these plugins for optimization:

Oh…what it takes to become a journeyman is not as easy as expected. One last thing we need to know about Webpack is webpack-dev-server. This library spins up a development server with live reloading! We are no longer newbies who only know manually refreshing the browser when source code changes.

Linters

A linter is a program that walks through our source code and checks potential programmatic and stylistic errors. It often consists of two categories of rules:

  • Formatting rules: concerns how consistent code style looks. E.g. limiting lines of code to 80 characters.
  • Code quality rules: concerns how good code quality is written. These type of rules actually help us catch bugs. E.g. variables that are declared but not used are forbidden.

Linters are usually language-specific:

Formatters

A formater, as the name suggests, is a program that formats our source code in a consistent style. This especially useful when we are working with other programmers. Everyone has his or her own style writing code. For example, someone might prefer using space than tab. With formatters, we no longer need to argue with co-workers why they use space instead of tab. Formatters will make sure our code conforming to a consistent style.

Currently, the most popular formatter is prettier. Check it out, and set it up correctly, your code will look amazing (just stylistically)!

Enforcing linting and formatting source code before committing them to codebase

Linters and formatters are great only if we use them. There are some cases when people forget to lint and format code, and bad code just sneaks into codebase :( Fortunately, we have tools that help us enforce linting and formatting before committing code.

  • husky: allows us to easily create Git hooks. A git hook is just a script that will be executed by Git before and after events such as commit and push. In our case, we want to enforce running linters and formatters in pre-commit stage.
  • lint-staged: setting up Git hooks with husky alone is not enough because after code is linted and formatted, the new changes will not be automatically added to Git stage. This is where lint-staged is needed. It allows us to run linters and formatters on staged files.

The Expert Setup

Docker

Docker is a technology for running application software in isolated, secure, and reusable containers. Docker is not just for production. It can be extremely helpful for development as well.

If you recall the story in the intro section, the new guy encountered invalid ELF header error. This is usually caused by when a binary module is created on one OS (MacOS) but used on a different OS (Linux). Now we know the root cause of this error, but how can we avoid it? The solution it to create identical environment for testing, development, stage, and production across different machines. The old way to do this is to spin up a virtual machine (using Vagrant or VirtualBox for example), and it definitely works. But this is not the best solution. Why? because there is too much overhead for creating a virtual machine. Moreover, compared to VMs, containers are more portable, efficient, take less space, and can start almost instantly.

By running our application in an isolated container that has identical environment (e.g. Linux) across different machines, we can be confident that the app runs well on my machine should also run well on your machine.

Modify default host database

If we know our app will be served on example.com in production stage.example.com in stage, would it be great if we can test our app locally on dev.example.com instead of localhost? It is actually very easy to make this happen. Every computer keeps a default host database in /etc/hosts. Domain names and associated IP addresses in this database have the highest priority, meaning our computer will firstly look for answers in this file before DNS lookups during DNS resolution. With this knowledge in mind, we can customize entries in this database. For example, adding a new entry 127.0.0.1 dev.example.com will allow us to type dev.example.com in our browser, and it will resolve to 127.0.0.1, which has the same effect as typing localhost.

I hope after reading this post, you are on your way becoming a local development setup expert :)

--

--