Ultra Simple JS & TS Development Philosophy

This is a collection of concepts for writing readable, maintainable, frindly Node JS-based projects. This includes frontend JS projects, TypeScript projects, Node JS backend projects, anything with a package.json.

JavaScript is a very wide domain, the possibilities of coding styles and tooling are quite vast. There is no “Ruby on Rails of JS”, and therefore the ecosystem is quite literally lacking “rails” — a guiding set of constraints that make things predictable and master-able.

The goal here is to outline some philosophy that applies across these types of projects.

The overall philosophy: use constraints to make things predictable, avoid shiny things, keep things as simple as possible.

1. Simple scripts

In your package.json, just use simple predictable values in the scripts section.

Use npm start and npm build. That’s enough.

No npm run dev or npm run start-dev npm generate && npm build-dev.

If your codebase gets big, and/or has a lot of scripts, you can put arbitrary JS files into the scripts folder.

Above all, make sure that:

  • npm start starts your application.
  • npm run build builds your application.
  • npm run test runs your test suite.

2. Use NODE_ENV

There’s no need for a dev:true flag on your library. Use process.env.NODE_ENV = 'development'.

There’s no need for process.env.NODE_ENV to be anything besides development or production.

3. Don’t use weird language features

Build tools and bundlers are helpful. But JS compilers like babel often come hand-in-hand with bundlers. This creates a problem: babel allows us to use whatever language features we want, even, perhaps, language features which are not yet part of the language.

This is bad.

babel will eventually dissapear, as JS transpilation becomes less necessary.

Aim for your code to work in both Node JS runtime and browser runtime.

Avoid special language features which are not officially part of the Ecmascript 262 specification.

4. Typescript = JavaScript with types

Building on item 3, the same goes for any TypeScript code you write. This means, for example no TS decorators.

Think of TypeScript as JavaScript with types. It’s not JavaScript with types and special language features. Stick to the spec to avoid future headaches.

You don’t want a teammate somewhere in the world or in the future feeling 😞 frustrated after trying for 6 hours to get your code to compile or run in an environment somewhere outside of your particular TS build chain of what was hot in November of 2019.

5. Avoid source-to-source compilation

My worst experiences in software development have been using source-to-source compilation. You might also refer to these things as “meta frameworks”.

Frameworks are good. Meta frameworks are bad.

If you’re using a compiler or a framework that compiles to a source code format, i.e., it generates source code, be prepared for headaches. The “generated code” from tools such as website builders is usually unreadable and unchangeable by the human coder for similar reasons.

Reminder: JS can be considered a compile target, like in the case of Typescript. That’s okay.

But, for example, a tool that “generates” React components will create lots of problems for you down the road. These problems are usually:

  • The code is not readable
  • Code generation is one-directional (can’t change the generated code, then generate again)
  • Generated code is usually unable to integrate with other code or parts of your application
  • Generated code is usually brittle, easy to break
  • Poor future-proofing (the tool or meta framework may not be well-prepared for future dependency updates; may deprecate support or active development)
  • Lock-in, hard to switch away from or support is deprecated
  • Performance issues!

6. Avoid import path aliases

Referring to this pattern:

import someModule from '@/my/components/someModule'

This will create headaches in the long run. Is it really worth it for the refactor convenience?

7. Always ask: can the platform do that for me?

Before installing a dependency, always first check if the platform you’re running this code on supports your need natively. This is especially imperative because of the prevalance of superfluous NPM packages in the JS ecosystem. Many, many things that used to require an external dependency to achieve are now supported natively in the browser or Node JS.

For example, before adding a react-loadable, research how to achieve bundle splitting directly using an ES Module import statement. Before adding a library for lazy loading, try implementing using the browser’s built-in features for lazy loading images.

If you look closely, and understand the primitives available to you on the platform, you can accomplish quite a bit. As an example, Map is faster than Object for many operations, building your application around this principle may save you massively on performance in the long run.

8. Open node_modules

This is a weird piece of advice. TODO: describe this

end of storey Last modified: