Congratulations! If you are still JavaScripting in 2019, you made it to the end of dependency deployment hell.

Isomorphic (ES6 + Node.js Module Boilerplate W/ Universal Testing

Congratulations! If you are still JavaScripting in 2019, you made it to the end of dependency deployment hell.

Ira Miller

--

This post introduces the isomorphic module boilerplate.

  • Modules written in ES6, but also available in Node.js.
  • Install and build source code from git, not [npm, bower, centralized package manager].
  • Support a flat directory structure.
  • Also support node_modules folders for backwards compatability.
  • Run the same unit tests in Node.js as well as browsers
  • Run unit tests against every browser and OS (on travis-ci.org)

History

ECMAScript

First a little history of our problem. Originally, JavaScript (ECMAScript) had no concept of a module. Everything was just scripts on top of scripts.

Example ES5 “module”

CommonJS and AMD

After some time, the community came up with a few competing standards, including CommonJS (used by Node.js on servers) and Asynchronous Module Definition aka AMD (used by RequireJS in browsers).

CommonJS Example
AMD Example

For a long time, this division was a hard one, and modules did not cross the line without transpiler build tools such as gulp, babel, webpack, rollup. While there’s nothing inherently wrong with transpiling JavaScript, it is an expensive and potentially unnecessary step. Why can’t your module just run everywhere, as is?

UMD

Enter Universal Module Definitions aka UMD, a set of patterns that are both CommonJS and AMD compatible. UMD is not a single fixed pattern, but rather a complex set of choices, depending on exact deployment environment. The code looks quite complex, and it is indeed a headache to implement.

UMD Example

Phew, obviously that is not an ideal developer experience. But hey, it worked, and your code would run in the browser the same as it would run in on the server.

ES6

Finally, in 2015, ECMAScript 2015 aka ES6 was published, which included a standardized module format. This format was simple, clean, and easy to use.

ES6 Module Example

Problem solved, right? Not so fast!

ES6 modules are, unfortunately, not compatible with the others, including UMD. This is mainly due to it’s asyncronous nature, requiring all `import` and `export` statements to be at the top level of the module. (i.e. not inside any sort of conditional block or function)

Furthermore, as of 2019, parts of the module ecosystem such as the dynamic import statement are *still* in the draft status. Firefox only this year started supporting dynamic imports, and Node.JS still puts them behind an — experimental-modules flag.

esm Pattern

Thankfully, some nice community members created esm, a convenient tool for using ES6 modules in Node.JS today.

esm Example

This is a strong and future-proof module pattern, and is easy to use thanks to the `npm init esm` and `yarn init esm` commands. Still, it doesn’t solve every problem.

One big problem between the different module systems are dependencies. Node.js looks for dependencies by name in a special node_modules folder, while typical browser deployments have a flat file structure, or even a single bundled script. What type is our other-module dependency? Should we look for it in ./node_modules, or where?

Furthermore, how is the other-module distributed? npm? A CDN? How can you trust the code hasn’t been tampered with by middle men?

Future

Isomorphic Module Boilerplate

Welcome to the future! The isomorphic module boilerplate uses esm and gpm (git+npm) to publish modules compatible with both ES6 and Node.JS, as well as flat and node_modules directory structures.

Git Distribution

Isomorphic module boilerplate uses gpm to install packages from git source code instead of a centralized package manager. This eliminates middle men from the code distribution channel, and ensures the latest code is available.

Note that gpm is a peer dependency of Isomorphic module boilerplate, and must be installed globally. The following command will do the trick.

Install gpm pre-requisite

After this, npm can be used as normal, since gpm is set in the preinstall hook in package.json.

Install dependencies to .. using git not npm

This preinstall hook will install dependencies and devDendencies to the parent directory (..), preferring https to ssh as a git protocol.

A postinstall hook is currently required to ensure that the esm package is built, since the git branch does not include the build directory. The script is prettified here for your convenience.

Install esm if necessary

This script will check if esm is built, and run `npm i` in ../esm if it is not.

Flat + node_modules

Isomorphic module boilerplate is compatible with node_modules folders, as well as flat, deployable folders (i.e. every dependency in a single folder, side by side). The goal is for you to be able to deploy your isomorphic module to a browser environment as-is, without any bundling or mapping of package names.

It’s easiest to understand by following the directory tree in an example installation.

Step 1 create JS source directory

Step 2 clone this module (and/or fork your own)

Step 3 npm install

Test Everywhere

Isomorphic module boilerplate imports iso-test to run the same test code in both Node.js as well as the browser of your choice.

There are no complex test drivers, extra browser builds, or complex APIs to learn.

Write your unit test in test.js and call `finishTest` with the result. Anything beginning with “pass” will pass, everything else will fail, including uncaught errors.

Since iso-test is a devDependency, gpm does not install it automatically. Before testing, install it with:

Finally, Travis CI is integrated to test your code using chromium, chrome, firefox, and safari, on linux, osx, and windows.

That’s a lot of test coverage.

To get started, fork the boilerplate! It is licensed MIT and contributions are welcome.

--

--

Ira Miller
Ira Miller

Written by Ira Miller

Nature, homesteading, technology, and general pursuit of happiness. https://iramiller.com

No responses yet