Node.js is a Salad Bar

Thoughts on Boilerplate, Frameworks and Usability

Dan Abramov began his day as many open source maintainers do: fielding unrelated tech support questions on the issue tracker for his project.

Why “combineReducers” can not work at my project today suddenly, which could work well yesterday?

After some painful back-and-forth, the Redux team determined that the problem was caused by:

faulty package.json configuration conflict with “redux” being used as both a package name and a filepath imported via Webpack’s resolve.root

…with both of these problems being a result of copy-pasting a boilerplate configuration. Closing the issue, Dan writes:

Copy-pasting configs from boilerplate projects always leads to hard-to-debug issues like this. It’s easy to miss somebody’s configuration decisions when you’re not the one making them. Don’t use boilerplate projects unless you understand each and every technology it uses!

I understand Dan’s frustration. But could we look at this from a different perspective? Don Norman’s The Design of Everyday Things teaches that there’s no such thing as “user error” — humans always make mistakes, and the failure to deal with these is on the product, not the user. How would we approach these user errors if we looked at them as design failures?

Problem: package.json configuration

When we design user-facing software, we ensure that a user can never get their data into an inconsistent state. But package.json is a magnet for inconsistent state; it is incredibly easy to break it and not know why. It’s particularly sensitive to copy/paste errors because of how commas work in json — even if one copies a working configuration exactly, one can still end up with a dangling or missing comma.

Many build tools enable or expect configuration in package.json, exacerbating this issue. While users can do most of their dependency management safely via npm install, there’s no corresponding safe way to edit ESlint or Babel config; we’re expected to do it by hand. And even when command-line tools are available, they’re limited in flexibility and aren’t discoverable for novice users.

Solution: Structured Data UI

Git has benefited from graphical tools; why not npm?

npm-gui

Paweł Stefański’s npm-gui is one such tool, which manages dependencies and tasks with a graphical editor. Future versions of this tool could handle package metadata, updating outdated dependencies and integrations with the npm registry, just as Git GUIs integrate with GitHub.

Problem: Webpack path resolution conflicts

CommonJS’s require has two ways of resolving paths:

as packages in the node_modules directory, e.g. require(“redux”) loads node_modules/redux/lib/index.js

in the node_modules directory, e.g. require(“redux”) loads node_modules/redux/lib/index.js as files using the filesystem, e.g. require(“./foo.js”) loads foo.js from the same directory as the file requiring it

This works well for libraries, where dependency graphs are simple parent-child trees, but leads to code like require(“../../../lib/foo”) in complex applications.

Webpack’s has a few hacky workarounds for this: resolve.root and resolve.modulesDirectories, which allow one to require from a projects own source directories as it would from node_modules; adding src to modulesDirectories lets one use require(“foo”) to load src/foo.js.

However, this puts the app’s source files in the same namespace as the imported modules; that means that, if you have a directory like src/redux, require(“redux”) will load that instead of the “redux” in node_modules. Even if one avoids this particular problem, files that use Webpack’s module resolution no longer work as regular node scripts; any code that uses that module resolution, such as tests, must also be run through Webpack.

Solution: Node-friendly Application Managers

Node module resolution is designed for libraries, not applications. Webpack’s module resolution hacks try to fight against this, but an elegant solution would work within this system. Just as Legit and git-up implement high-level workflows using low-level git commands, we could use tools to implement application workflows over npm’s library-oriented commands. Instead of overriding require’s semantics, this tool could help us manage our applications as collections of modules, handling the generation of package.json boilerplate and npm linking them together. Lerna could be a good starting point for this tool.