• Stars
    star
    741
  • Rank 61,194 (Top 2 %)
  • Language
    JavaScript
  • License
    MIT License
  • Created over 8 years ago
  • Updated almost 2 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Enable ECMAScript 2015 modules in Node today. No caveats. Full stop.

re路i路fy verb, transitiveBuild Status

re路i路fied pastre路i路fies presentre路i路fy路ing participlere路i路fi路ca路tion nounre路i路fi路er noun

  1. to make (something abstract) more concrete or real
    "these instincts are, in humans, reified as verbal constructs"
  2. to regard or treat (an idea, concept, etc.) as if having material existence
  3. to enable ECMAScript 2015 modules in any version of Node.js

Usage

  1. Run npm install --save reify in your package or app directory. The --save is important because reification only applies to modules in packages that explicitly depend on the reify package.
  2. Call require("reify") before importing modules that contain import and export declarations.

You can also easily reify the Node REPL:

% node
> require("reify")
{}
> import { strictEqual } from "assert"
> strictEqual(2 + 2, 5)
AssertionError: 4 === 5
    at repl:1:1
    at REPLServer.defaultEval (repl.js:272:27)
  ...

How it works

Code generated by the reify compiler relies on a simple runtime API that can be explained through a series of examples. While you do not have to write this API by hand, it is designed to be easily human readable and writable, in part because that makes it easier to explain.

I will explain the Module.prototype.link method first, then the Module.prototype.export method after that. Note that this Module is the constructor of the CommonJS module object, and the import and export methods are custom additions to Module.prototype.

module.link(id, setters)

Here we go:

import a, { b, c as d } from "./module";

becomes

// Local symbols are declared as ordinary variables.
let a, b, d;
module.link("./module", {
  // The keys of this object literal are the names of exported symbols.
  // The values are setter functions that take new values and update the
  // local variables.
  default(value) { a = value; },
  b(value) { b = value; },
  c(value) { d = value; },
});

All setter functions are called synchronously before module.link returns, with whatever values are immediately available. However, when there are import cycles, some setter functions may be called again, when the exported values change. Calling these setter functions one or more times is the key to implementing live bindings, as required by the ECMAScript 2015 specification.

Importing a namespace object is no different from importing a named export. The name is simply "*" instead of a legal identifier:

import * as utils from "./utils";

becomes

let utils;
module.link("./utils", {
  "*"(ns) { utils = ns; }
});

Note that the ns object exposed here is !== require("./utils"), but instead a normalized view of the require("./utils") object. This approach ensures that the actual exports object is never exposed to the caller of module.link.

Notice that this compilation strategy works equally well no matter where the import declaration appears:

if (condition) {
  import { a as b } from "./c";
  console.log(b);
}

becomes

if (condition) {
  let b;
  module.link("./c", {
    a(value) { b = value; }
  });
  console.log(b);
}

See WHY_NEST_IMPORTS.md for a much more detailed discussion of why nested import declarations are worthwhile.

module.export(getters)

What about export declarations? One option would be to transform them into CommonJS code that updates the exports object, since interoperability with Node and CommonJS is certainly a goal of this approach.

However, if Module.prototype.link takes an id string and a map of setter functions, then it seems natural for Module.prototype.export to be method that registers getter functions. Given these getter functions, whenever module.link(id, ...) is called by a parent module, the getters for the id module will run, updating its module.exports object, so that the module.link method has access to the latest exported values.

The module.export method is called with a single object literal whose keys are exported symbol names and whose values are getter functions for those exported symbols. So, for example,

export const a = "a", b = "b", ...;

becomes

module.export({
  a: () => a,
  b: () => b,
  ...
});
const a = "a", b = "b", ...;

This code registers getter functions for the variables a, b, ..., so that module.link can easily retrieve the latest values of those variables at any time. It's important that we register getter functions rather than storing computed values, so that other modules always can import the newest values.

Export remapping works, too:

let c = 123;
export { c as see }

becomes

module.export({ see: () => c });
let c = 123;

Note that the module.export call is "hoisted" to the top of the block where it appears. This is safe because the getter functions work equally well anywhere in the scope where the exported variable is declared, and a good idea because the hoisting ensures the getters are registered as early as possible.

What about export default <expression> declarations? It would be a mistake to defer evaluation of the default expression until later, so wrapping it in a hoisted getter function is not exactly what we want.

Instead,

export default computeDefault();

gets replaced where it is (without any hoisting) by

module.exportDefault(computeDefault());

The module.exportDefault method is just a convenient wrapper around module.export:

module.exportDefault = function (value) {
  return this.export({
    default: function () {
      return value;
    }
  }, true);
};

That true argument we're passing to module.export is a hint that the value returned by this getter function will never change, which enables some optimizations behind the scenes.

module.runSetters()

Now, suppose you change the value of an exported local variable after the module has finished loading. Then you need to let the module system know about the update, and that's where module.runSetters comes in. The module system calls this method on your behalf whenever a module finishes loading, but you can also call it manually, or simply let reify generate code that calls module.runSetters for you whenever you assign to an exported local variable.

Calling module.runSetters() with no arguments causes any setters that depend on the current module to be rerun, but only if the value a setter would receive is different from the last value passed to the setter.

If you pass an argument to module.runSetters, the value of that argument will be returned as-is, so that you can easily wrap assignment expressions with calls to module.runSetters:

export let value = 0;
export function increment(by) {
  return value += by;
};

should become

module.export({
  value: () => value,
  increment: () => increment,
});
let value = 0;
function increment(by) {
  return module.runSetters(value += by);
};

Note that module.runSetters(argument) does not actually use argument. However, by having module.runSetters(argument) return argument unmodified, we can run setters immediately after the assignment without interfering with evaluation of the larger expression.

Because module.runSetters runs any setters that have new values, it's also useful for potentially risky expressions that are difficult to analyze statically:

export let value = 0;

function runCommand(command) {
  // This picks up any new values of any exported local variables that may
  // have been modified by eval.
  return module.runSetters(eval(command));
}

runCommand("value = 1234");

exports that are really imports

What about export ... from "./module" declarations? The key insight here is that export declarations with a from "..." clause are really just import declarations that update the exports object instead of updating local variables:

export { a, b as c } from "./module";

becomes

module.link("./module", {
  a(value) { exports.a = value; },
  b(value) { exports.c = value; },
});

Since this pattern is so common, and no local variables need to be modified by these setter functions, the runtime API supports an alternative shorthand for re-exporting values:

module.link("./module", { a: "a", b: "c" });

This strategy cleanly generalizes to export * from "..." declarations:

export * from "./module";

becomes

module.link("./module", {
  "*"(ns) {
    Object.assign(exports, ns);
  }
});

Though the basic principle is the same, in reality the Reify compiler generates shorthand notation for this pattern as well:

module.link("./module", { "*": "*" });

This version is shorter, does not rely on Object.assign (or a polyfill), can be a little smarter about copying special properties such as getters, and reliably modifies module.exports instead of the exports variable (whatever it may be). Win!

Exporting named namespaces (proposal):

export * as ns from "./module";

becomes

module.link("./module", {
  "*"(ns) { exports.ns = ns; }
});

Shorthand:

module.link("./module", { "*": "ns" });

Re-exporting default exports (proposal):

export a, { b, c as d } from "./module";

becomes

module.link("./module", {
  default(value) { exports.a = value; },
  b(value) { exports.b = value; },
  c(value) { exports.d = value; }
});

Shorthand:

module.link("./module", {
  default: "a",
  b: "b",
  c: "d"
});

While these examples have not covered every possible syntax for import and export declarations, I hope they provide the intuition necessary to imagine how any declaration could be compiled.

When I have some time, I hope to implement a live-compiling text editor to enable experimentation.

More Repositories

1

recast

JavaScript syntax tree transformer, nondestructive pretty-printer, and automatic source map generator
TypeScript
4,937
star
2

ast-types

Esprima-compatible implementation of the Mozilla JS Parser API
TypeScript
1,061
star
3

kix-standalone

A standalone version of Google's new rich text editor, Kix
JavaScript
308
star
4

arson

Efficient encoder and decoder for arbitrary objects
JavaScript
192
star
5

optimism

Composable reactive caching with efficient invalidation.
TypeScript
112
star
6

wryware

A collection of packages that are probably a little too clever. Use at your own wrisk.
TypeScript
87
star
7

jsnext-skeleton

Skeleton project demonstrating best practices for authoring and publishing the latest version of JavaScript to NPM.
JavaScript
60
star
8

install

Minimal JavaScript module loader
JavaScript
58
star
9

private

Utility for associating truly private state with any JavaScript object
JavaScript
55
star
10

es7-async-await

Transformer that converts async functions and await expressions into ECMAScript 6 generator functions and yield expressions
JavaScript
43
star
11

immutable-tuple

Immutable finite list objects with constant-time equality testing (===) and no memory leaks.
JavaScript
35
star
12

empirenode-2015

Slides for my talk at EmpireNode 2015
JavaScript
9
star
13

populist

A JavaScript module loader that gives the people what they want
JavaScript
5
star
14

ast-path

Library for traversing syntax trees with easy access to an unbroken chain of parent references
JavaScript
5
star
15

brigade

Bucket brigade for bundling browser modules
JavaScript
4
star
16

garden

SCSS
4
star
17

offgrid

Art project using the Raspberry Pi camera module and Total Control Lighting LEDs
JavaScript
4
star
18

project-euler

Solutions and miscellaneous utilities, mostly written while I was intoxicated
Python
4
star
19

offgrid-camera

Node library for reading live video data from the Raspberry Pi camera module
C
4
star
20

dot-files

Various unixy .configuration files that I carry between machines
Emacs Lisp
3
star
21

deno_modules

TypeScript
3
star
22

arduino-wearables

A collection of code powering wearable Arduino projects
C
3
star
23

cls

Class factory featuring inheritance of static properties, static constructors, lazy population of prototypes, and this._super
JavaScript
3
star
24

patches

My mozilla-central patch queue
3
star
25

brane

Efficient inter-Worker messaging with fantastic TypeScript support
TypeScript
3
star
26

tricks

A growing library of client-side CommonJS modules
JavaScript
2
star
27

voodoodown

Left-recursive JavaScript packrat parser
JavaScript
2
star
28

devshop-july-2015

Slides for my talk about ECMAScript at the July 2015 Meteor Devshop
JavaScript
2
star
29

module-imports

Fast and accurate module dependency analyzer, with support for both CommonJS and ES2015.
JavaScript
2
star
30

e10s

My electrolysis patch queue
2
star
31

pivotal-meetup-talk

Slides for my talk about ECMAScript 6 and Regenerator at Pivotal Labs
JavaScript
2
star
32

fluent2014-talk

Technical and Social Progress Toward ECMAScript 6 at Facebook
JavaScript
1
star
33

hijinks

A simple, trivially scalable proxy server for the wry project
Python
1
star
34

hermes

Fleet-footed, fully-sealed CommonJS module loader
JavaScript
1
star
35

tm-patches

My tracemonkey patch queue
1
star
36

rye

Proof-of-concept using hermes & tricks
JavaScript
1
star
37

spec-parse

A buildbot harness for running speculative parsing performance tests
Python
1
star
38

callmethod

My XPCWrappedNative::CallMethod refactoring patch queue
1
star
39

offgrid-lights

Node library for manipulating Total Control Lighting LEDs from a Raspberry Pi
C++
1
star
40

benjamn.com

My still-nascent personal website
JavaScript
1
star
41

hammer-lab-talk

Slides for my talk about Recast and Regenerator at the Icahn Institute for Genomics and Multiscale Biology
JavaScript
1
star
42

empirejs-talk

Slides for my talk "Easing into ECMAScript 6 and Beyond" at EmpireJS 2014
JavaScript
1
star
43

regenerator-talk

Slides for a talk about my experiences developing github.com/facebook/regenerator
JavaScript
1
star
44

babel-plugin-jscript

Babel plugin to fix buggy JScript named function expressions.
JavaScript
1
star