{"id":13432549,"url":"https://github.com/benjamn/reify","last_synced_at":"2025-05-14T21:06:41.811Z","repository":{"id":8344993,"uuid":"58002212","full_name":"benjamn/reify","owner":"benjamn","description":"Enable ECMAScript 2015 modules in Node today. No caveats. Full stop.","archived":false,"fork":false,"pushed_at":"2023-01-23T03:15:09.000Z","size":1687,"stargazers_count":743,"open_issues_count":37,"forks_count":29,"subscribers_count":14,"default_branch":"main","last_synced_at":"2025-05-03T17:04:39.484Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/benjamn.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2016-05-03T21:17:35.000Z","updated_at":"2025-04-11T19:45:14.000Z","dependencies_parsed_at":"2023-02-12T20:16:02.890Z","dependency_job_id":null,"html_url":"https://github.com/benjamn/reify","commit_stats":null,"previous_names":[],"tags_count":192,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benjamn%2Freify","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benjamn%2Freify/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benjamn%2Freify/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/benjamn%2Freify/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/benjamn","download_url":"https://codeload.github.com/benjamn/reify/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254170077,"owners_count":22026220,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-07-31T02:01:13.227Z","updated_at":"2025-05-14T21:06:36.781Z","avatar_url":"https://github.com/benjamn.png","language":"JavaScript","funding_links":[],"categories":["JavaScript","others"],"sub_categories":[],"readme":"# re\u0026middot;i\u0026middot;fy \u003csub\u003e_verb, transitive_\u003c/sub\u003e \u0026nbsp; [![Build Status](https://github.com/benjamn/reify/actions/workflows/node.js.yml/badge.svg)](https://github.com/benjamn/reify/actions/workflows/node.js.yml)\n\n**re\u0026middot;i\u0026middot;fied** \u003csub\u003epast\u003c/sub\u003e \u0026nbsp; **re\u0026middot;i\u0026middot;fies** \u003csub\u003epresent\u003c/sub\u003e \u0026nbsp; **re\u0026middot;i\u0026middot;fy\u0026middot;ing** \u003csub\u003eparticiple\u003c/sub\u003e \u0026nbsp; **re\u0026middot;i\u0026middot;fi\u0026middot;ca\u0026middot;tion** \u003csub\u003enoun\u003c/sub\u003e \u0026nbsp; **re\u0026middot;i\u0026middot;fi\u0026middot;er** \u003csub\u003enoun\u003c/sub\u003e\n\n  1. to make (something abstract) more concrete or real\u003cbr\u003e\n     _\"these instincts are, in humans, reified as verbal constructs\"_\n  2. to regard or treat (an idea, concept, etc.) as if having material existence\n  3. **to enable [ECMAScript 2015 modules](http://www.2ality.com/2014/09/es6-modules-final.html) in *any* version of [Node.js](https://nodejs.org)**\n\nUsage\n---\n\n  1. Run `npm install --save reify` in your package or app directory. The\n     `--save` is important because reification only applies to modules in\n     packages that explicitly depend on the `reify` package.\n  2. Call `require(\"reify\")` before importing modules that contain `import`\n     and `export` declarations.\n\nYou can also easily `reify` the Node REPL:\n\n```sh\n% node\n\u003e require(\"reify\")\n{}\n\u003e import { strictEqual } from \"assert\"\n\u003e strictEqual(2 + 2, 5)\nAssertionError: 4 === 5\n    at repl:1:1\n    at REPLServer.defaultEval (repl.js:272:27)\n  ...\n```\n\nHow it works\n---\n\nCode generated by the `reify` compiler relies on a [simple runtime\nAPI](lib/runtime.js) that can be explained through a series of\nexamples. While you do not have to write this API by hand, it is designed\nto be easily human readable and writable, in part because that makes it\neasier to explain.\n\nI will explain the `Module.prototype.link` method first, then the\n`Module.prototype.export` method after that. Note that this `Module` is\nthe constructor of the CommonJS `module` object, and the `import` and\n`export` methods are custom additions to `Module.prototype`.\n\n### `module.link(id, setters)`\n\nHere we go:\n\n```js\nimport a, { b, c as d } from \"./module\";\n```\n\nbecomes\n\n```js\n// Local symbols are declared as ordinary variables.\nlet a, b, d;\nmodule.link(\"./module\", {\n  // The keys of this object literal are the names of exported symbols.\n  // The values are setter functions that take new values and update the\n  // local variables.\n  default(value) { a = value; },\n  b(value) { b = value; },\n  c(value) { d = value; },\n});\n```\n\nAll setter functions are called synchronously before `module.link` returns,\nwith whatever values are immediately available. However, when there are\nimport cycles, some setter functions may be called again, when the exported\nvalues change. Calling these setter functions one or more times is the key\nto implementing [*live bindings*](http://www.2ality.com/2015/07/es6-module-exports.html),\nas required by the ECMAScript 2015 specification.\n\nImporting a namespace object is no different from importing a named\nexport. The name is simply `\"*\"` instead of a legal identifier:\n\n```js\nimport * as utils from \"./utils\";\n```\nbecomes\n```js\nlet utils;\nmodule.link(\"./utils\", {\n  \"*\"(ns) { utils = ns; }\n});\n```\n\nNote that the `ns` object exposed here is `!== require(\"./utils\")`, but\ninstead a normalized view of the `require(\"./utils\")` object. This\napproach ensures that the actual `exports` object is never exposed to the\ncaller of `module.link`.\n\nNotice that this compilation strategy works equally well no matter where\nthe `import` declaration appears:\n\n```js\nif (condition) {\n  import { a as b } from \"./c\";\n  console.log(b);\n}\n```\nbecomes\n```js\nif (condition) {\n  let b;\n  module.link(\"./c\", {\n    a(value) { b = value; }\n  });\n  console.log(b);\n}\n```\n\nSee [`WHY_NEST_IMPORTS.md`](WHY_NEST_IMPORTS.md) for a much more detailed\ndiscussion of why nested `import` declarations are worthwhile.\n\n### `module.export(getters)`\n\nWhat about `export` declarations? One option would be to transform them into\nCommonJS code that updates the `exports` object, since interoperability\nwith Node and CommonJS is certainly a goal of this approach.\n\nHowever, if `Module.prototype.link` takes an `id` string and a map of\n*setter* functions, then it seems natural for `Module.prototype.export` to\nbe method that registers *getter* functions. Given these getter functions,\nwhenever `module.link(id, ...)` is called by a parent module, the getters\nfor the `id` module will run, updating its `module.exports` object, so\nthat the `module.link` method has access to the latest exported values.\n\nThe `module.export` method is called with a single object literal whose\nkeys are exported symbol names and whose values are getter functions for\nthose exported symbols. So, for example,\n\n```js\nexport const a = \"a\", b = \"b\", ...;\n```\n\nbecomes\n\n```js\nmodule.export({\n  a: () =\u003e a,\n  b: () =\u003e b,\n  ...\n});\nconst a = \"a\", b = \"b\", ...;\n```\n\nThis code registers getter functions for the variables `a`, `b`, ..., so\nthat `module.link` can easily retrieve the latest values of those\nvariables at any time. It's important that we register getter functions\nrather than storing computed values, so that other modules always can\nimport the newest values.\n\nExport remapping works, too:\n\n```js\nlet c = 123;\nexport { c as see }\n```\n\nbecomes\n\n```js\nmodule.export({ see: () =\u003e c });\nlet c = 123;\n```\n\nNote that the `module.export` call is \"hoisted\" to the top of the block\nwhere it appears. This is safe because the getter functions work equally\nwell anywhere in the scope where the exported variable is declared, and\na good idea because the hoisting ensures the getters are registered as\nearly as possible.\n\nWhat about `export default \u003cexpression\u003e` declarations? It would be a\nmistake to defer evaluation of the `default` expression until later, so\nwrapping it in a hoisted getter function is not exactly what we want.\n\nInstead,\n\n```js\nexport default computeDefault();\n```\n\ngets replaced where it is (without any hoisting) by\n\n```js\nmodule.exportDefault(computeDefault());\n```\n\nThe `module.exportDefault` method is just a convenient\n[wrapper](https://github.com/benjamn/reify/blob/d7c27163a77dac184979862f808ef4e88de91ba8/lib/runtime/index.js#L60-L67)\naround `module.export`:\n\n```js\nmodule.exportDefault = function (value) {\n  return this.export({\n    default: function () {\n      return value;\n    }\n  }, true);\n};\n```\n\nThat `true` argument we're passing to `module.export` is a hint that the\nvalue returned by this getter function will never change, which enables\n[some optimizations](https://github.com/benjamn/reify/issues/134) behind\nthe scenes.\n\n### `module.runSetters()`\n\nNow, suppose you change the value of an exported local variable after the\nmodule has finished loading. Then you need to let the module system know\nabout the update, and that's where `module.runSetters` comes in. The\nmodule system calls this method on your behalf whenever a module finishes\nloading, but you can also call it manually, or simply let `reify` generate\ncode that calls `module.runSetters` for you whenever you assign to an\nexported local variable.\n\nCalling `module.runSetters()` with no arguments causes any setters that\ndepend on the current module to be rerun, *but only if the value a setter\nwould receive is different from the last value passed to the setter*.\n\nIf you pass an argument to `module.runSetters`, the value of that argument\nwill be returned as-is, so that you can easily wrap assignment expressions\nwith calls to `module.runSetters`:\n\n```js\nexport let value = 0;\nexport function increment(by) {\n  return value += by;\n};\n```\n\nshould become\n\n```js\nmodule.export({\n  value: () =\u003e value,\n  increment: () =\u003e increment,\n});\nlet value = 0;\nfunction increment(by) {\n  return module.runSetters(value += by);\n};\n```\n\nNote that `module.runSetters(argument)` does not actually use `argument`.\nHowever, by having `module.runSetters(argument)` return `argument`\nunmodified, we can run setters immediately after the assignment without\ninterfering with evaluation of the larger expression.\n\nBecause `module.runSetters` runs any setters that have new values, it's\nalso useful for potentially risky expressions that are difficult to\nanalyze statically:\n\n```js\nexport let value = 0;\n\nfunction runCommand(command) {\n  // This picks up any new values of any exported local variables that may\n  // have been modified by eval.\n  return module.runSetters(eval(command));\n}\n\nrunCommand(\"value = 1234\");\n```\n\n### `export`s that are really `import`s\n\nWhat about `export ... from \"./module\"` declarations? The key insight here\nis that **`export` declarations with a `from \"...\"` clause are really just\n`import` declarations that update the `exports` object instead of updating\nlocal variables**:\n\n```js\nexport { a, b as c } from \"./module\";\n```\nbecomes\n```js\nmodule.link(\"./module\", {\n  a(value) { exports.a = value; },\n  b(value) { exports.c = value; },\n});\n```\n\nSince this pattern is so common, and no local variables need to be\nmodified by these setter functions, the runtime API supports an\nalternative shorthand for re-exporting values:\n\n```js\nmodule.link(\"./module\", { a: \"a\", b: \"c\" });\n```\n\nThis strategy cleanly generalizes to `export * from \"...\"` declarations:\n\n```js\nexport * from \"./module\";\n```\nbecomes\n```js\nmodule.link(\"./module\", {\n  \"*\"(ns) {\n    Object.assign(exports, ns);\n  }\n});\n```\n\nThough the basic principle is the same, in reality the Reify compiler\ngenerates shorthand notation for this pattern as well:\n\n```js\nmodule.link(\"./module\", { \"*\": \"*\" });\n```\n\nThis version is shorter, does not rely on `Object.assign` (or a polyfill),\ncan be a little smarter about copying special properties such as getters,\nand reliably modifies `module.exports` instead of the `exports` variable\n(whatever it may be). Win!\n\nExporting named namespaces ([proposal](https://github.com/leebyron/ecmascript-export-ns-from)):\n```js\nexport * as ns from \"./module\";\n```\nbecomes\n```js\nmodule.link(\"./module\", {\n  \"*\"(ns) { exports.ns = ns; }\n});\n```\n\nShorthand:\n\n```js\nmodule.link(\"./module\", { \"*\": \"ns\" });\n```\n\nRe-exporting default exports ([proposal](https://github.com/leebyron/ecmascript-export-default-from)):\n```js\nexport a, { b, c as d } from \"./module\";\n```\nbecomes\n```js\nmodule.link(\"./module\", {\n  default(value) { exports.a = value; },\n  b(value) { exports.b = value; },\n  c(value) { exports.d = value; }\n});\n```\n\nShorthand:\n\n```js\nmodule.link(\"./module\", {\n  default: \"a\",\n  b: \"b\",\n  c: \"d\"\n});\n```\n\nWhile these examples have not covered every possible syntax for `import`\nand `export` declarations, I hope they provide the intuition necessary to\nimagine how any declaration could be compiled.\n\nWhen I have some time, I hope to implement a [live-compiling text\neditor](https://github.com/benjamn/reify/issues/15) to enable\nexperimentation.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbenjamn%2Freify","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbenjamn%2Freify","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbenjamn%2Freify/lists"}