-
Notifications
You must be signed in to change notification settings - Fork 74
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(endo): Add CommonJS and JSON module support #397
Conversation
cc @kumavis This came together faster than expected. |
52722d0
to
7c9aab3
Compare
7c9aab3
to
a6f6bc3
Compare
a6f6bc3
to
e1ad2fd
Compare
e1ad2fd
to
c9ebbbb
Compare
94de2dc
to
0cebbf5
Compare
Replaced the whacky regex with a proper Babel visitor, so static analysis for |
0cebbf5
to
c85bc45
Compare
76d153f
to
9d6b9b1
Compare
c85bc45
to
84f4b2b
Compare
9d6b9b1
to
e749986
Compare
Could you rebase this when you get a chance? The diff currently shows all the |
84f4b2b
to
ffb07e0
Compare
d73fdd1
to
b98aa35
Compare
ffb07e0
to
9872570
Compare
Rebased this stack. |
b98aa35
to
f2bb4fb
Compare
9872570
to
8010098
Compare
f2bb4fb
to
fcd85c2
Compare
9d1453c
to
8e6f345
Compare
61ad42e
to
edf01ad
Compare
8e6f345
to
12716a6
Compare
12716a6
to
9698520
Compare
1d2c4a8
to
8a2e109
Compare
9698520
to
12d4ec5
Compare
8a2e109
to
cbb7efc
Compare
1e1a19c
to
e4ef2b7
Compare
e4ef2b7
to
2c96932
Compare
165bf77
to
4ff24fb
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
minor changes. looks good!
test("parse unique require calls", t => { | ||
t.plan(1); | ||
const code = ` | ||
require("b"); // sorted later |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the code were failing to find inner-scope'd require
, would this second occurrence of require("b")
make this test fail to find the bug? I.e does this occurrence nullify the negative predictive value of the test?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
also, maybe this test should exercise a space betwen require
and (
, and perhaps a newline, if those are things that Node will accept
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
eh, probably not worth it, we are using a proper parser after all. (back in my day, we made do with regexps, and I would have been worried about something like this. Six feet of snow, uphill both ways, yadda yadda)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There’s a commit with the regex version in this very stack, borrowed from my back-in-the-day implementation ❤️
packages/endo/src/lockdown.js
Outdated
@@ -0,0 +1,6 @@ | |||
/* global lockdown */ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this file doesn't appear to be used (yet)?
* the module execution phase. | ||
* For example, `require(path)` will not be discovered by this parser, the | ||
* module will successfully load, but will likely be unable to synchronously | ||
* require the module with the given path. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in Jetpack, our recommendation for this was to combine the two effects:
if (false) { require('thing1'); }
if (false) { require('thing2'); }
...
const needed = condition ? 'thing1' : 'thing2';
require(needed);
.. but really, if you're reaching for something like that, there's probably something deeper going on, and you're going to get into trouble anyways.
} | ||
|
||
const imports = parseRequires(source, location); | ||
const execute = (exports, compartment, resolvedImports) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Curious, why is exports
passed into execute()
when it seems the only properties on it come from the module being loaded? I would have expected execute()
to return exports
instead. Is this some quirk of the new module loader specification?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CommonJS has always supported cyclic dependencies in this fashion. Two modules can mutually require as long as they lazy-bind properties of the exports returned by require
, and as long as they use exports.property =
assignment instead of module.exports =
replacement. The module.exports
property was not part of the original CommonJS, although it has become dominant usage in Node.js. The module
object originally existed in CommonJS to fulfill the same function as import.meta
.
}; | ||
|
||
const parserForLanguage = { | ||
mjs: parseMjs, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any thoughts about using "esm" for the name of the language, rather than "mjs"? I was puzzled for a while until I realized this was not an extension name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As you noted later, this becomes coherent with the "parsers"
directive in package.json
that we need in order to break the log-jab between Node.js and -r ESM’s interpretation of the "type": "module"
directive. I felt that translating between "esm" in the domain and "mjs" in the range for just this one format may have been more confusing.
require("a"); | ||
|
||
function b() { | ||
require("b"); // found despite inner scope |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this needs a different name (maybe d
) to test inner scopes without interference from the same-named require('b')
on line 9. If the parser had a bug in which inner scopes were ignored, this test would yield a false positive.
require("a"); // de-duplicated | ||
|
||
require("id\\""); // found, despite inner quote | ||
`; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you might consider a test that require('e', otherarg)
is ignored, since that's known behavior of the parser. OTOH I'd understand if you wanted to omit it, since ignoring multi-arg require
is not really part of the spec.
@@ -70,6 +73,21 @@ const findPackage = async (readDescriptor, directory, name) => { | |||
} | |||
}; | |||
|
|||
const commonParsers = { js: "cjs", cjs: "cjs", mjs: "mjs", json: "json" }; | |||
const moduleParsers = { js: "mjs", mjs: "mjs", cjs: "cjs", json: "json" }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for the sake of visual comparison, these should both have their keys in the same order, rather than swapping cjs/mjs between the two
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nevermind, I see this issue goes away in another couple of PRs
Achieves parity with Node.js behavior regarding the "type": "module" declaration in package.json, determining whether `.js` files are CommonJS or ECMAScript modules. Goes beyond parity to support JSON modules.
4ff24fb
to
cd6b87d
Compare
Achieves parity with Node.js behavior regarding the "type": "module" declaration in package.json, determining whether
.js
files are CommonJS or ECMAScript modules. Also treats the"module"
declaration as a hint that the denoted module is an ESM, regardless of its extension.The emulation of Node.js behavior is not exact.
require
to import CommonJS modules, but cannot useimport
from CommonJS. In Endo.js, an ESM can only useimport
and a CJS module can only userequire
, but both can be used to import any supported module including ECMAScript modules, CommonJS modules, and JSON modules.require
to import a CommonJS. module. In Endo.js, they can.require
will return thedefault
export if it exists and assigning tomodule.exports
is equivalent to assigning toexports.default
. This may break existing CommonJS code in the rare case that has an export nameddefault
. Consequently, ESM and CJS destructuring imports are the same.require
calls must be exclusively static, meaning the argument must be a string and all imports are unconditional.import
to load a JSON module. Endo.js can import JSON into any module. The module formats are orthogonal.