Search code examples
javascriptnode.jstypescriptecmascript-6browserify

Emit a Javascript library as an ES6 module with different browser and Node.js implementations


What is the best way to create an ES6 library, e.g. my-es6-crypto-lib, which can be used both in the browser and in Node.js, but for which the implementations are different on each platform?

(E.g. the Node.js implementation uses the built-in crypto module for better performance.)

ES6 module usage:

import { sha256 } from 'my-es6-crypto-lib'
let digest = sha256('abc')
console.log(digest)

Or Node.js-style requires:

let sha256 = require('my-es6-crypto-lib')
let digest = sha256('abc')
console.log(digest)

The package.json for my-es6-crypto-lib would include:

{
   "name": "my-es6-crypto-lib",
   "main": "transpiled-to-commonjs/node.js",
   "module": "es6-module/node.js",
   "browser": "es6-module/browser.js",
   ...
}
  • Node.js will follow the main key to resolve the CommonJS module.
  • Tools capable of consuming ES6 modules (like transpiler/bundling tools) follow the module key.
  • Tools which consume ES6 modules and bundle them for browsers (e.g. rollup-plugin-node-resolve) will follow the browser key.

The actual implementation for Node.js would look something like: (transpiled-to-commonjs/node.js)

// built-in module, faster than the pure Javascript implementation
let createHash = require('crypto')

export function sha256 (message) {
  return createHash('sha256').update(message).digest('hex')
}

While the browser implementation would look something like: (es6-module/browser.js)

// a javascript-only implementation available at es6-module/hashFunctions.js
import { sha256 } from './hashFunctions'

export function sha256 (message) {
  // slightly different API than the native module
  return sha256(message, 'hex')
}

Note, the implementation of each function is different on each platform, but both sha256 methods have the same parameter message and return a string.

What is the best way to structure my ES6 module to provide each of these implementations? Are there any javascript libraries out there which do this?

Ideally:

  • the unused implementation should be able to be tree shaken, and
  • no runtime checks should be used to determine the current environment.

(I also opened a GitHub issue for Rollup →)


Solution

  • After a while, I now think the best way to do this is actually to export different functionality for each environment.

    In the past, complex javascript libraries have used solutions like Browserify to bundle a version of their application for the browser. Most of these solutions work by allowing library developers to extensively configure and manually override various dependencies with respective browser versions.

    For example, where a Node.js application might use Node.js' built-in crypto module, a browser version would need to fall back to a polyfill-like alternative dependency like crypto-browserify.

    With es6, this customization and configuration is no longer necessary. Your library can now export different functionality for different consumers. While browser consumers may import a native JavaScript crypto implementation which your library exports, Node.js users can choose to import a different, faster implementation which your library exports.

    ...

    It's explained in depth, and with an example, in the typescript-starter readme →