★ wanayoo — archive 1999 https://github.com/nodejs/node/pull/18131Nouvelle recherche | Portail wanayoo
Skip to content
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

esm: provide named exports for all builtin libraries #18131

Closed
wants to merge 1 commit into from

Conversation

@devsnek
Copy link
Member

@devsnek devsnek commented Jan 13, 2018 •

provide named exports for all builtin libraries so that the libraries may be
imported in a nicer way for esm users: import { readFile } from 'fs'
instead of importing the entire namespace, import fs from 'fs', and
calling fs.readFile

Checklist
  • make -j4 test (UNIX), or vcbuild test (Windows) passes
  • tests and/or benchmarks are included
  • documentation is changed or added
  • commit message follows commit guidelines
@devsnek devsnek force-pushed the devsnek:esm-builtin-module-namespaces branch Jan 13, 2018
Copy link
Member

@bnoordhuis bnoordhuis left a comment

Only very lightly reviewed. No opinion on whether this is a good or bad change.

doc/api/esm.md Outdated
foo.one === 1 // true
```

Builtin modules such as will provide the above with the addition of their

This comment has been minimized.

@bnoordhuis

bnoordhuis Jan 13, 2018
Member

Missing word after 'such as.'

tools/js2c.py Outdated
var = name.replace('-', '_').replace('/', '_')
if name.endswith('.js'):
name = name.split('.', 1)[0]
var = name.replace('-', '_').replace('/', '_').replace('.', '_')

This comment has been minimized.

@bnoordhuis

bnoordhuis Jan 13, 2018
Member

var = re.sub(r'[\-./]', '_', name)

tools/esmgen.js Outdated
@@ -0,0 +1,46 @@
const builtins = require('repl')._builtinLibs;

This comment has been minimized.

@bnoordhuis

bnoordhuis Jan 13, 2018
Member

'use strict';

lib/internal/loader/ModuleRequest.js Outdated
if (NativeModule.nonInternalExists(specifier)) {
if ((/^node:/.test(parentURL) &&
NativeModule.nonInternalExists(specifier.replace(/\.js$/, '')))
|| NativeModule.nonInternalExists(specifier)) {

This comment has been minimized.

@bnoordhuis

bnoordhuis Jan 13, 2018
Member

Operator should go on the previous line.

@devsnek devsnek force-pushed the devsnek:esm-builtin-module-namespaces branch Jan 13, 2018
@guybedford
Copy link
Contributor

@guybedford guybedford commented Jan 13, 2018

👍 strongly for merging this.

My only comment would be if there might not be a maintenance burden being created by this approach that might be mitigated with enumeration of the exports at load time, provided we can ensure we don't expose the wrong things (perhaps filtering _... properties etc). Would be interested to hear thoughts on this approach.

Copy link
Contributor

@guybedford guybedford left a comment

I've included my comments... hope you don't mind all the questions :)

lib/internal/loader/Loader.js Outdated
@@ -91,7 +91,7 @@ class Loader {
throw new errors.TypeError('ERR_INVALID_ARG_TYPE', 'url', 'string');
}

if (format === 'builtin') {
if (format === 'builtin/esm' || format === 'builtin/cjs') {

This comment has been minimized.

@guybedford

guybedford Jan 13, 2018
Contributor

Why do we need two forms of builtin? Can the builtin/esm not entirely replace the builtin/cjs? What is the use case for retaining builtin/cjs?

lib/internal/loader/ModuleRequest.js Outdated
format: 'builtin'
};
if (/^node:/.test(parentURL) && parentURL.slice(5) === specifier) {
return { url: `${specifier}.js`, format: 'builtin/cjs' };

This comment has been minimized.

@guybedford

guybedford Jan 13, 2018
Contributor

When does this case ever happen?

This comment has been minimized.

@devsnek

devsnek Jan 13, 2018 •
Author Member

when the esm wrapper imports the actual cjs it looks like node:assert requesting assert, and thats the only time the builtin cjs should get used

tools/esmgen.js Outdated
test.push(`check(${name}, ${key});`);
}

console.log(test.join('\n'));

This comment has been minimized.

@guybedford

guybedford Jan 13, 2018
Contributor

Is the assumption that this file is run periodically to update the lib folder? Is there a way to make this more automated? Why would you think this level of automation is preferable to runtime enumeration?

This comment has been minimized.

@devsnek

devsnek Jan 13, 2018 •
Author Member

hopefully it never needs to be used again, but i still included it since it might be useful at some point, maybe during a refactor or something in the far future

@devsnek
Copy link
Member Author

@devsnek devsnek commented Jan 13, 2018 •

@guybedford i chose this over runtime enumeration because of the results in my last pr where i attempted forward evaluation but i guess if we can guarantee our libs won't have errors on evaluation it should be fine? i don't think our public api changes that much for it to be a real burden. this also lays groundwork for writing core parts of node in esm, although i don't know if that matters much to people. it should be noted that if we switch to runtime enumerating with out of order evaluation then the current issues i'm having with tests will also go away, and it will get rid of people who make loaders worrying about resolving both kinds builtins, which is definitely a confusing process.

@guybedford
Copy link
Contributor

@guybedford guybedford commented Jan 13, 2018

@devsnek thanks for clarifying. Personally I think the runtime creation would be better as it would avoid the need for the new builtin interpretation mode, remove the maintenance burden of maintaining the wrappers (generated or not), and it would effectively be exactly the same algorithm to build up the exports, saving an extra file load anyway to do that.

@devsnek
Copy link
Member Author

@devsnek devsnek commented Jan 13, 2018

@guybedford i just know there was significant opposition to out-of-order evaluation in my previous pr, but maybe @bmeck its fine in this case?

@guybedford
Copy link
Contributor

@guybedford guybedford commented Jan 13, 2018 •

Personally I don't seen an issue with considering core modules "preevaluated" (from the esm loader perspective). It's user dependencies with circular references and errors that we have to worry about for that issue.

@bmeck
Copy link
Member

@bmeck bmeck commented Jan 13, 2018

I don't like that the exports can go out of sync but am not blocking that at this point. I'd love it if the backing object could stay in sync for a variety of reasons, but all approaches I've tested are problematic for performance.

@bmeck
Copy link
Member

@bmeck bmeck commented Jan 13, 2018

@devsnek could you make the ESM facades eagerly populate even when required so that they stay the true primordial form of the exports?

@devsnek
Copy link
Member Author

@devsnek devsnek commented Jan 13, 2018 •

@bmeck i'm not sure what you mean by "true primordial form" but my method would basically be:

loaders.set('builtin', async (url) => {
  const module = InternalModule.require(url.slice(5));
  const properties = Object.getOwnPropertyDescriptors(module);
  const keys = ['default'];
  for (const [name, prop] of Object.entries(properties)) {
    if (!prop.enumerable || !prop.value)
      continue;
    if (/(^_)|(_$)/.test(name))
      continue;
    keys.push(name);
  }
  return createDynamicModule(keys, url, (reflect) => {
    reflect.exports.default.set(module);
    for (const key of keys)
      reflect.exports[key].set(module[key]);
  });
});
@bmeck
Copy link
Member

@bmeck bmeck commented Jan 13, 2018

@devsnek I just want to be sure that if you mutate fs it does not mutate the named export value depending on timing.

const fs = require('fs');
fs.readFile = () => {}
import {readFile} from 'fs';
// should never be that noop function (even if this file is loaded after the one above)
@devsnek
Copy link
Member Author

@devsnek devsnek commented Jan 13, 2018

@bmeck i don't know of any way to guarantee at all, even requiring every builtin before user code runs and keeping a cache would still allow user mutation of the exports object.

@guybedford
Copy link
Contributor

@guybedford guybedford commented Jan 13, 2018

@devsnek the same sort of technique we use to eagerly inject CJS modules into the loader registry for this should work here for the core modules I think?

@guybedford
Copy link
Contributor

@guybedford guybedford commented Jan 13, 2018

(I know it's not pretty, but it provides the invariants)

lib/assert.mjs Outdated
@@ -0,0 +1,19 @@
/* eslint-disable no-restricted-properties */

This comment has been minimized.

@jasnell

jasnell Jan 13, 2018
Member

Just a thought... could we not devise a mechanism for generating these automatically during the build so we do not need to keep them manually in sync? The surface area of core modules is fixed at time of build.

This comment has been minimized.

@bmeck

bmeck Jan 13, 2018
Member

@jasnell we could somehow with static parsing maybe, but various things like getter/setters would need to be excluded.

lib/assert.mjs Outdated
export const notStrictEqual = assert.notStrictEqual;
export const throws = assert.throws;
export const doesNotThrow = assert.doesNotThrow;
export const ifError = assert.ifError;

This comment has been minimized.

@jasnell

jasnell Jan 13, 2018
Member

This appears to be missing the new strict export.

This comment has been minimized.

@devsnek

devsnek Jan 13, 2018
Author Member

it might not have been in my branch when i generated these, but since i'm changing this to generate the exports at runtime it shouldn't be an issue. (stay tuned!! 😄)

lib/crypto.mjs Outdated
export const pbkdf2Sync = crypto.pbkdf2Sync;
export const privateDecrypt = crypto.privateDecrypt;
export const privateEncrypt = crypto.privateEncrypt;
export const prng = crypto.prng;

This comment has been minimized.

@jasnell

jasnell Jan 13, 2018
Member

We should decide if we really want to export pure aliases or take the opportunity to begin limiting access to those

This comment has been minimized.

@bmeck

bmeck Jan 13, 2018
Member

can you expand on the reason to limit access to things?

lib/domain.mjs Outdated
@@ -0,0 +1,7 @@
import domain from 'domain';

This comment has been minimized.

@jasnell

jasnell Jan 13, 2018
Member

Should we export deprecated modules at all?

This comment has been minimized.

@bmeck

bmeck Jan 13, 2018
Member

+0 to removing them, not blocking on it

@devsnek devsnek force-pushed the devsnek:esm-builtin-module-namespaces branch Jan 13, 2018
@devsnek devsnek changed the title esm: provide wrappers for all builtin libraries esm: provide named exports for all builtin libraries Jan 13, 2018
@devsnek devsnek force-pushed the devsnek:esm-builtin-module-namespaces branch Jan 13, 2018
@guybedford
Copy link
Contributor

@guybedford guybedford commented Jan 13, 2018

New approach looks good.

@bmeck
bmeck approved these changes Jan 13, 2018
Copy link
Member

@bmeck bmeck left a comment

lgtm

lib/internal/bootstrap_node.js Outdated
const descriptors =
Object.getOwnPropertyDescriptors(nativeModule.exports);
for (const [name, d] of Object.entries(descriptors)) {
if (d.setter !== undefined || d.getter !== undefined || !d.enumerable)

This comment has been minimized.

@ljharb

ljharb Jan 13, 2018
Member

what is "setter" and "getter"? Property descriptors have set and get.

@devsnek devsnek force-pushed the devsnek:esm-builtin-module-namespaces branch Jan 13, 2018
@devsnek devsnek force-pushed the devsnek:esm-builtin-module-namespaces branch Apr 23, 2018
lib/internal/bootstrap/loaders.js Outdated
Reflect.defineProperty(target, prop, descriptor)) {
update(prop, valueDescriptor.value);
return true;
}

This comment has been minimized.

@jdalton

jdalton Apr 23, 2018 •
Member

👆 In the if (valueDescriptor && condition you can make it if (valueDescriptor) { then inside the block capture the result of Reflect.defineProperty(target, prop, descriptor), call update and return the captured result.

lib/internal/bootstrap/loaders.js Outdated
if (this.namespace.includes(prop))
return false;
return delete target[prop];
},

This comment has been minimized.

@jdalton

jdalton Apr 23, 2018
Member

👆 By returning false you're making the CJS export objects non-delete able. Instead you could allow the operation to happen, capturing the result of Reflect.deleteProperty(target, prop), calling update, then returning the captured result. If a property is deleted its updated value is undefined or whatever is exposed on its prototype.

This comment has been minimized.

@devsnek

devsnek Apr 23, 2018 •
Author Member

calling update

calling it with what exactly? the property is gone if we let it get deleted

This comment has been minimized.

@jdalton

jdalton Apr 23, 2018 •
Member

calling it with what exactly? the property is gone if we let it get deleted

Whatever the value of target[prop] that's remaining.

@devsnek devsnek force-pushed the devsnek:esm-builtin-module-namespaces branch Apr 23, 2018
lib/internal/bootstrap/loaders.js Outdated
}
return value;
},
});

This comment has been minimized.

@jdalton

jdalton Apr 23, 2018 •
Member

I take a slightly different approach to wrapping. Instead of creating a new wrap function, I proxy the original.

This comment has been minimized.

@TimothyGu

TimothyGu Apr 23, 2018
Member

Is there a reason why you would prefer that?

This comment has been minimized.

@jdalton

jdalton Apr 23, 2018 •
Member

@TimothyGu Yes. I first avoid wrapping for the 99% case (plain or bound functions) and then only wrap with a proxy for the native case. For the 1% case (native method) wrapping with a proxy lets all other traps passthru to the original function while I just hook the apply trap.

Beyond the fact that less wrapping is good in general this is important to the esm loader because folks can opt for CJS named export support beyond buitlins. Avoiding the function/proxy wrap means more methods are === to other references a user might store.

This comment has been minimized.

@devsnek

devsnek Apr 23, 2018
Author Member

I'll have to re-audit our exports to make sure we only need to bind for native methods, but that does seem like a better case

This comment has been minimized.

@devsnek

devsnek Apr 23, 2018 •
Author Member

@jdalton your proxy unfortunately breaks in certain cases which i think could be a v8 bug so i'll keep using my wrap function for now

@nodejs/v8

> Reflect.apply(EventEmitter.call, EventEmitter, [])
TypeError: Reflect.apply is not a function

This comment has been minimized.

@devsnek

devsnek Apr 23, 2018 •
Author Member

@jdalton i wasn't running any esm, it broke regular usage of event emitter. it seems like v8 doesn't enjoy Function.prototype.call and Reflect.apply together, this isn't just an issue with EventEmitter.

var EventEmitter = require('events');

// probably some ghetto es3 function extending EventEmitter
EventEmitter.call(...); 

i can special case the thisArg variable like value === Function.prototype.call but that feels nasty and the wrap works

This comment has been minimized.

@jdalton

jdalton Apr 23, 2018 •
Member

Dev error? Proxies should work.
Can you show how you attempted proxies. Might be able to spot the issue.

In my implementation

EventEmitter.call({})

works. The .call is proxy wrapped.

I have smth like this for the handler (poke around the implementation here)

wrapper = new Proxy(value, {
  apply(funcTarget, thisArg, args) {
    if (thisArg === proxy ||
        thisArg === entry.esmNamespace) {
      thisArg = target
    }

    return Reflect.apply(value, thisArg, args)
  }
})

When EventEmitter.call({}) is called the thisArg === proxy condition is met because var EventEmitter is the events module module.exports proxy. The thisArg is set to the original target (the unwrapped module.exports of events). This results in a successful invocation.

This comment has been minimized.

@TimothyGu

TimothyGu Apr 24, 2018
Member

@devsnek Can you find an isolated reproduction of the bug? The following seems to be working just fine here:

$ node
> const { EventEmitter } = events
undefined
> Reflect.apply(EventEmitter.call, EventEmitter, [])
TypeError: Cannot set property 'domain' of undefined
    at EventEmitter.init (domain.js:401:15)
    at EventEmitter (events.js:27:21)

This comment has been minimized.

@devsnek

devsnek Apr 24, 2018 •
Author Member

@jdalton i copied yours in, except for using regular proxies instead of your OwnProxy, which i doubt makes any difference in this case

This comment has been minimized.

@devsnek

devsnek Apr 24, 2018
Author Member

ok i just came back to this and it seems like the proxy works now so i'm going to just assume i need more sleep... i'll push in a few minutes after some more testing

lib/util.js Outdated
@@ -433,7 +436,7 @@ function formatValue(ctx, value, recurseTimes, ln) {
return ctx.stylize('null', 'null');
}

if (ctx.showProxy) {
if (ctx.showProxy && !nativeModuleProxies.has(value)) {

This comment has been minimized.

@TimothyGu

TimothyGu Apr 23, 2018
Member

I don't think we should make a distinction between "our proxies" versus "their proxies".

We should either disable proxy showing by default, or just show the proxy.

This comment has been minimized.

@jdalton

jdalton Apr 23, 2018 •
Member

@TimothyGu It's cosmetic and can totally be tackled in a follow-up PR. The idea from earlier in the thread was that folks thought it would be less-good displaying the Proxy prefix when inspecting builtin module exports in the repl.

This comment has been minimized.

@TimothyGu

TimothyGu Apr 23, 2018 •
Member

I'd like to argue in the other way: because it is cosmetic, this change can be done in a follow-up PR. Changes should be atomic and focused on one topic at a time.

This comment has been minimized.

@jdalton

jdalton Apr 23, 2018
Member

I'd like to argue in the other way: because it is cosmetic, this change can be done in a follow-up PR.

We're on the same page. I was saying that the masking of builtin exports could be done in a follow-up PR instead of this one 😁

This comment has been minimized.

@TimothyGu

TimothyGu Apr 24, 2018
Member

Oops 😛

Copy link
Member

@TimothyGu TimothyGu left a comment

Good progress, but a bit more work is still needed.


Also, how much does this slow down require()ing an internal module (and Node.js startup) from CJS with the --experimental-modules flag turned on? We could get this in with a performance hit, but we need to be able to quantify that and know what to fix in the future.

lib/internal/bootstrap/loaders.js Outdated
},
defineProperty(target, prop, descriptor) {
if (Reflect.defineProperty(target, prop, descriptor)) {
update(prop, Reflect.get(target, prop, target));

This comment has been minimized.

@TimothyGu

TimothyGu Apr 24, 2018
Member

Last , target is unneeded.

lib/internal/modules/esm/translators.js Outdated
const exports = NativeModule.require(url.slice(5));
reflect.exports.default.set(exports);
});
const id = url.substr(5);

This comment has been minimized.

@TimothyGu

TimothyGu Apr 24, 2018
Member

What exactly does this do? A comment would help with what exactly this truncates. Also we generally use .slice() (substr is part of Annex B).

@@ -1,10 +1,11 @@
import _url from 'url';
import { URL } from 'url';

This comment has been minimized.

@TimothyGu

TimothyGu Apr 24, 2018
Member

No need to import URL at all in Node.js v10.x :)

This comment has been minimized.

@devsnek

devsnek Apr 24, 2018
Author Member

you never know where this might be backported to

lib/internal/bootstrap/loaders.js Outdated
this.namespace = Object.entries(
Object.getOwnPropertyDescriptors(this.exports))
.filter(([name, d]) => d.enumerable)
.map(([name]) => name);

This comment has been minimized.

@TimothyGu

TimothyGu Apr 24, 2018
Member

Core code rarely use the functional array methods. Let's make this imperative instead.

this.namespace = [];
for (const key of Object.getOwnPropertyNames(this.exports)) {
  const desc = Object.getOwnPropertyDescriptor(this.exports, key);
  if (!desc.enumerable)
    continue;
  namespace.push(key);
}

This comment has been minimized.

@devsnek

devsnek Apr 24, 2018
Author Member

mfw thats what i was using before 😢 will change back

lib/internal/bootstrap/loaders.js Outdated
const proxy = new Proxy(this.exports, {
set(target, prop, value) {
if (Reflect.set(target, prop, value)) {
update(prop, value);

This comment has been minimized.

@TimothyGu

TimothyGu Apr 24, 2018
Member

For consistency with defineProperty, use update(prop, Reflect.get(target, prop)). (This behavior is observable through getters/setters.)

lib/internal/bootstrap/loaders.js Outdated
const methodWrapMap = new WeakMap();

const proxy = new Proxy(this.exports, {
set(target, prop, value) {

This comment has been minimized.

@TimothyGu

TimothyGu Apr 24, 2018
Member

set hook has a fourth receiver argument that you are not handling.
You need to at least make sure to forward the receiver argument to Reflect.set.

lib/internal/bootstrap/loaders.js Outdated
}
return false;
},
get(target, prop) {

This comment has been minimized.

@TimothyGu

TimothyGu Apr 24, 2018
Member

You need to handle receiver here as well.

lib/internal/bootstrap/loaders.js Outdated
wrap.prototype = value.prototype;
methodWrapMap.set(value, wrap);
return wrap;
}

This comment has been minimized.

@TimothyGu

TimothyGu Apr 24, 2018
Member

You might need to add this handling for getOwnPropertyDescriptor as well.

test/es-module/test-esm-live-binding.mjs Outdated
fs.readFile = () => s;

assert.strictEqual(fs.readFile(), s);
assert.strictEqual(readFile(), s);

This comment has been minimized.

@TimothyGu

TimothyGu Apr 24, 2018
Member

Still need to test:

  • delete
  • delete and set afterwards
  • set
  • defineProperty
  • All of the above, but with accessor properties
@devsnek devsnek force-pushed the devsnek:esm-builtin-module-namespaces branch 2 times, most recently Apr 24, 2018
lib/internal/bootstrap/loaders.js Outdated
for (const key of Object.getOwnPropertyNames(this.exports)) {
const desc = Object.getOwnPropertyDescriptor(this.exports, key);
if (desc.enumerable)
this.namespace.push(key);

This comment has been minimized.

@jdalton

jdalton Apr 24, 2018
Member

Instead of getOwnPropertyNames+enumerable check you could use Object.keys

@devsnek
Copy link
Member Author

@devsnek devsnek commented Apr 24, 2018 •

@TimothyGu

average startup time with child process + loop + console.log(perf_hooks.performance.nodeTiming) + shameless rounding:

without proxy (1000 runs) 79.71845515598729
with proxy (1000 runs) 90.33686945802346

i didn't bother testing time of an individual require because it can only happen at max once. its also worth nothing this increased time should be more than handled by snapshots, whenever we finish those.

lib/internal/bootstrap/loaders.js Outdated

if (typeof value.name === 'string' && /^bound /.test(value.name))
return value;

This comment has been minimized.

@jdalton

jdalton Apr 24, 2018 •
Member

Besides non-functions and bound-functions you can also skip non-native functions.
I have an inference method here for reference. (a v8 helper for this would be rad++)

This comment has been minimized.

@devsnek

devsnek Apr 24, 2018
Author Member

we can't skip native functions. for instance if you do module.exports = new Map() and we don't wrap the exports then Map.prototype.* will have improper receivers and throw

This comment has been minimized.

@jdalton

jdalton Apr 24, 2018
Member

we can't skip native functions.

I know, I'm saying skip non-native.

This comment has been minimized.

@devsnek

devsnek Apr 24, 2018
Author Member

so the only functions we bind then are unbound native functions? what about member functions written in js

This comment has been minimized.

@jdalton

jdalton Apr 24, 2018 •
Member

so the only functions we bind then are unbound native functions? what about member functions written in js

We aren't binding functions. The wrapper juggles the thisArg around for the one, maybe two, cases that cause native methods grief but beyond that we forward the thisArg along. So it makes sense to only wrap the methods that need the thisArg juggling in the first place (native methods).

lib/internal/bootstrap/loaders.js Outdated
apply(t, thisArg, args) {
if (thisArg === proxy)
thisArg = target;
return Reflect.apply(t, thisArg, args);

This comment has been minimized.

@jdalton

jdalton Apr 24, 2018
Member

You might end up needing a thisArg === nsObj check.
A test for calling a native method on the namespace object would cover it.

@devsnek devsnek force-pushed the devsnek:esm-builtin-module-namespaces branch 2 times, most recently Apr 24, 2018
@devsnek
Copy link
Member Author

@devsnek devsnek commented Apr 28, 2018 •

alright so this is the 400th comment in this thread and i'm starting to consistently get the github unicorn when loading this. at the moment it works and this pr seems to be ready. that being said, i need some approvals on this. i would love to land this by the end of next week.

@jdalton
Copy link
Member

@jdalton jdalton commented Apr 28, 2018

👍 As an initial landing of an experimental feature I think it's great. Every time I look at this implementation I find something in my own to improve! There are some things to dry-up and tweak here but they can be tackled in follow-up PRs.

provide named exports for all builtin libraries so that the libraries may be
imported in a nicer way for esm users: `import { readFile } from 'fs'`
instead of importing the entire namespace, `import fs from 'fs'`, and
calling `fs.readFile`. the default export is left as the entire
namespace (module.exports)
@devsnek devsnek force-pushed the devsnek:esm-builtin-module-namespaces branch to 3aabe35 Apr 29, 2018
@giltayar
Copy link
Contributor

@giltayar giltayar commented Apr 29, 2018

@ljharb
ljharb approved these changes Apr 29, 2018
this.exportKeys = Object.keys(this.exports);

const update = (property, value) => {
if (this.reflect !== undefined && this.exportKeys.includes(property))

This comment has been minimized.

@ljharb

ljharb Apr 29, 2018
Member

is it ok that delete Array.prototype.includes can break this code?

If not, you could copy Array.prototype.includes to be an own property on this.exportKeys, perhaps?

(same question on has/get/set on collection instances)

This comment has been minimized.

@jdalton

jdalton Apr 29, 2018 •
Member

@ljharb There's another issue (here) for isolating internals to avoid ad hoc primordial scaffolding in each module. Trying to isolate includes, Object.keys, Reflect or other is probably out of scope for this specific PR (esp. since it's experimental and the issue is larger than this PR).

This comment has been minimized.

@ljharb

ljharb Apr 29, 2018
Member

Fair point, just wanted to call it out :-)

NativeModule.require(id);
const module = NativeModule.getCached(id);
return createDynamicModule(
[...module.exportKeys, 'default'], url, (reflect) => {

This comment has been minimized.

@ljharb

ljharb Apr 29, 2018
Member

i'm not sure if the ordering matters here at all - is default always last?

https://tc39.github.io/ecma262/#sec-modulenamespacecreate step 7 suggests that all export keys, including "default" if present, should be alphabetically sorted. (i do see at least one test that validates the ordering, but i'm not sure if that test covers this code or not)

This comment has been minimized.

@devsnek

devsnek Apr 29, 2018
Author Member

the order doesn't matter there actually as it just gets injected into a generated source text

@guybedford
Copy link
Contributor

@guybedford guybedford commented Apr 29, 2018

My greatest concern here is the potential 10% performance slowdown for NodeJS app startup having all core modules as proxies in CommonJS code, as Gus provided in some numbers at #18131 (comment).

It may turn out that creating setter-based core modules could be an alternative to the proxy approach that is also faster, so I do think this would still be worth seriously considering, or at least comparing for performance.

The benefit of a proxy over just a setter is supporting dynamic properties and object.defineProperty configuration cases. But dynamic properties are already not supported, so perhaps benefits may be worth the loss of configuration hooks.

@devsnek
Copy link
Member Author

@devsnek devsnek commented Apr 29, 2018

@guybedford as long as the experimental flag is around i'd rather take it as an opportunity to experiment with the behaviour rather than perf optimisation. come time to ship it we can always make it more performant if needed. i'm also exceedingly hopeful that we will finish up snapshots by that time and then we won't have to worry this at all

@guybedford
Copy link
Contributor

@guybedford guybedford commented Apr 29, 2018

@devsnek
Copy link
Member Author

@devsnek devsnek commented Apr 29, 2018

@guybedford this code doesn't run unless the flag is given, and yes i would agree a new pr is probably a good idea

@devsnek devsnek closed this Apr 29, 2018
@devsnek devsnek mentioned this pull request Apr 29, 2018
4 of 4 tasks complete
BridgeAR added a commit to BridgeAR/node that referenced this pull request May 1, 2018
Docs-only deprecate the getter/setter crypto.fips and replace
with crypto.setFips() and crypto.getFips()

This is specifically in preparation for ESM module support

PR-URL: nodejs#18335
Refs: nodejs#18131
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Guy Bedford <guybedford@gmail.com>
Reviewed-By: Jon Moss <me@jonathanmoss.me>
Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com>
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
MayaLekova added a commit to MayaLekova/node that referenced this pull request May 8, 2018
Runtime deprecate the crypto.DEFAULT_ENCODING property.

This is specifically in preparation for eventual ESM support
Refs: nodejs#18131

PR-URL: nodejs#18333
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Сковорода Никита Андреевич <chalkerx@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Minwoo Jung <minwoo@nodesource.com>
Reviewed-By: Tobias Nießen <tniessen@tnie.de>
MayaLekova added a commit to MayaLekova/node that referenced this pull request May 8, 2018
Docs-only deprecate the getter/setter crypto.fips and replace
with crypto.setFips() and crypto.getFips()

This is specifically in preparation for ESM module support

PR-URL: nodejs#18335
Refs: nodejs#18131
Reviewed-By: Anna Henningsen <anna@addaleax.net>
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Guy Bedford <guybedford@gmail.com>
Reviewed-By: Jon Moss <me@jonathanmoss.me>
Reviewed-By: Michael Dawson <michael_dawson@ca.ibm.com>
Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked issues

Successfully merging this pull request may close these issues.

None yet