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
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
package.json, just use simple predictable values in the
npm start and
npm build. That’s enough.
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
Above all, make sure that:
npm startstarts your application.
npm run buildbuilds your application.
npm run testruns your test suite.
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
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.
Building on item 3, the same goes for any TypeScript code you write. This means, for example no TS decorators.
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.
This is a weird piece of advice. TODO: describe this