esm: provide named exports for all builtin libraries #18131
Conversation
|
Only very lightly reviewed. No opinion on whether this is a good or bad change. |
| foo.one === 1 // true | ||
| ``` | ||
|
|
||
| Builtin modules such as will provide the above with the addition of their |
bnoordhuis
Jan 13, 2018
Member
Missing word after 'such as.'
Missing word after 'such as.'
| var = name.replace('-', '_').replace('/', '_') | ||
| if name.endswith('.js'): | ||
| name = name.split('.', 1)[0] | ||
| var = name.replace('-', '_').replace('/', '_').replace('.', '_') |
bnoordhuis
Jan 13, 2018
Member
var = re.sub(r'[\-./]', '_', name)
var = re.sub(r'[\-./]', '_', name)
| @@ -0,0 +1,46 @@ | |||
| const builtins = require('repl')._builtinLibs; | |||
bnoordhuis
Jan 13, 2018
Member
'use strict';
'use strict';
| if (NativeModule.nonInternalExists(specifier)) { | ||
| if ((/^node:/.test(parentURL) && | ||
| NativeModule.nonInternalExists(specifier.replace(/\.js$/, ''))) | ||
| || NativeModule.nonInternalExists(specifier)) { |
bnoordhuis
Jan 13, 2018
Member
Operator should go on the previous line.
Operator should go on the previous line.
|
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 |
|
I've included my comments... hope you don't mind all the questions :) |
| @@ -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') { | |||
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?
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?
| format: 'builtin' | ||
| }; | ||
| if (/^node:/.test(parentURL) && parentURL.slice(5) === specifier) { | ||
| return { url: `${specifier}.js`, format: 'builtin/cjs' }; |
guybedford
Jan 13, 2018
Contributor
When does this case ever happen?
When does this case ever happen?
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
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
| test.push(`check(${name}, ${key});`); | ||
| } | ||
|
|
||
| console.log(test.join('\n')); |
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?
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?
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
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
|
@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. |
|
@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. |
|
@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? |
|
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. |
|
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. |
|
@devsnek could you make the ESM facades eagerly populate even when required so that they stay the true primordial form of the exports? |
|
@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]);
});
}); |
|
@devsnek I just want to be sure that if you mutate 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) |
|
@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. |
|
@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? |
|
(I know it's not pretty, but it provides the invariants) |
| @@ -0,0 +1,19 @@ | |||
| /* eslint-disable no-restricted-properties */ | |||
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.
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.
bmeck
Jan 13, 2018
Member
@jasnell we could somehow with static parsing maybe, but various things like getter/setters would need to be excluded.
@jasnell we could somehow with static parsing maybe, but various things like getter/setters would need to be excluded.
| export const notStrictEqual = assert.notStrictEqual; | ||
| export const throws = assert.throws; | ||
| export const doesNotThrow = assert.doesNotThrow; | ||
| export const ifError = assert.ifError; |
jasnell
Jan 13, 2018
Member
This appears to be missing the new strict export.
This appears to be missing the new strict export.
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!! 😄 )
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!!
| export const pbkdf2Sync = crypto.pbkdf2Sync; | ||
| export const privateDecrypt = crypto.privateDecrypt; | ||
| export const privateEncrypt = crypto.privateEncrypt; | ||
| export const prng = crypto.prng; |
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
We should decide if we really want to export pure aliases or take the opportunity to begin limiting access to those
bmeck
Jan 13, 2018
Member
can you expand on the reason to limit access to things?
can you expand on the reason to limit access to things?
| @@ -0,0 +1,7 @@ | |||
| import domain from 'domain'; | |||
jasnell
Jan 13, 2018
Member
Should we export deprecated modules at all?
Should we export deprecated modules at all?
bmeck
Jan 13, 2018
Member
+0 to removing them, not blocking on it
+0 to removing them, not blocking on it
|
New approach looks good. |
|
lgtm |
| const descriptors = | ||
| Object.getOwnPropertyDescriptors(nativeModule.exports); | ||
| for (const [name, d] of Object.entries(descriptors)) { | ||
| if (d.setter !== undefined || d.getter !== undefined || !d.enumerable) |
ljharb
Jan 13, 2018
Member
what is "setter" and "getter"? Property descriptors have set and get.
what is "setter" and "getter"? Property descriptors have set and get.
| Reflect.defineProperty(target, prop, descriptor)) { | ||
| update(prop, valueDescriptor.value); | ||
| return true; | ||
| } |
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.
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.
| if (this.namespace.includes(prop)) | ||
| return false; | ||
| return delete target[prop]; | ||
| }, |
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.
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.
devsnek
Apr 23, 2018
•
Author
Member
calling update
calling it with what exactly? the property is gone if we let it get deleted
calling update
calling it with what exactly? the property is gone if we let it get deleted
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.
calling it with what exactly? the property is gone if we let it get deleted
Whatever the value of target[prop] that's remaining.
| } | ||
| return value; | ||
| }, | ||
| }); |
jdalton
Apr 23, 2018
•
Member
I take a slightly different approach to wrapping. Instead of creating a new wrap function, I proxy the original.
I take a slightly different approach to wrapping. Instead of creating a new wrap function, I proxy the original.
TimothyGu
Apr 23, 2018
Member
Is there a reason why you would prefer that?
Is there a reason why you would prefer that?
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.
@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.
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
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
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
> Reflect.apply(EventEmitter.call, EventEmitter, [])
TypeError: Reflect.apply is not a function
@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
> Reflect.apply(EventEmitter.call, EventEmitter, [])
TypeError: Reflect.apply is not a function
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
@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
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.
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.
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)
@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)
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
@jdalton i copied yours in, except for using regular proxies instead of your OwnProxy, which i doubt makes any difference in this case
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
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
| @@ -433,7 +436,7 @@ function formatValue(ctx, value, recurseTimes, ln) { | |||
| return ctx.stylize('null', 'null'); | |||
| } | |||
|
|
|||
| if (ctx.showProxy) { | |||
| if (ctx.showProxy && !nativeModuleProxies.has(value)) { | |||
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.
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.
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.
@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.
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.
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.
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 😁
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
TimothyGu
Apr 24, 2018
Member
Oops 😛
Oops
|
Good progress, but a bit more work is still needed. Also, how much does this slow down |
| }, | ||
| defineProperty(target, prop, descriptor) { | ||
| if (Reflect.defineProperty(target, prop, descriptor)) { | ||
| update(prop, Reflect.get(target, prop, target)); |
TimothyGu
Apr 24, 2018
Member
Last , target is unneeded.
Last , target is unneeded.
| const exports = NativeModule.require(url.slice(5)); | ||
| reflect.exports.default.set(exports); | ||
| }); | ||
| const id = url.substr(5); |
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).
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'; | |||
TimothyGu
Apr 24, 2018
Member
No need to import URL at all in Node.js v10.x :)
No need to import URL at all in Node.js v10.x :)
devsnek
Apr 24, 2018
Author
Member
you never know where this might be backported to
you never know where this might be backported to
| this.namespace = Object.entries( | ||
| Object.getOwnPropertyDescriptors(this.exports)) | ||
| .filter(([name, d]) => d.enumerable) | ||
| .map(([name]) => name); |
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);
}
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);
}
devsnek
Apr 24, 2018
Author
Member
mfw thats what i was using before 😢 will change back
mfw thats what i was using before
| const proxy = new Proxy(this.exports, { | ||
| set(target, prop, value) { | ||
| if (Reflect.set(target, prop, value)) { | ||
| update(prop, value); |
TimothyGu
Apr 24, 2018
Member
For consistency with defineProperty, use update(prop, Reflect.get(target, prop)). (This behavior is observable through getters/setters.)
For consistency with defineProperty, use update(prop, Reflect.get(target, prop)). (This behavior is observable through getters/setters.)
| const methodWrapMap = new WeakMap(); | ||
|
|
||
| const proxy = new Proxy(this.exports, { | ||
| set(target, prop, value) { |
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.
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.
| } | ||
| return false; | ||
| }, | ||
| get(target, prop) { |
TimothyGu
Apr 24, 2018
Member
You need to handle receiver here as well.
You need to handle receiver here as well.
| wrap.prototype = value.prototype; | ||
| methodWrapMap.set(value, wrap); | ||
| return wrap; | ||
| } |
TimothyGu
Apr 24, 2018
Member
You might need to add this handling for getOwnPropertyDescriptor as well.
You might need to add this handling for getOwnPropertyDescriptor as well.
| fs.readFile = () => s; | ||
|
|
||
| assert.strictEqual(fs.readFile(), s); | ||
| assert.strictEqual(readFile(), s); |
TimothyGu
Apr 24, 2018
Member
Still need to test:
delete
delete and set afterwards
set
defineProperty
- All of the above, but with accessor properties
Still need to test:
deletedeleteandsetafterwardssetdefineProperty- All of the above, but with accessor properties
| for (const key of Object.getOwnPropertyNames(this.exports)) { | ||
| const desc = Object.getOwnPropertyDescriptor(this.exports, key); | ||
| if (desc.enumerable) | ||
| this.namespace.push(key); |
jdalton
Apr 24, 2018
Member
Instead of getOwnPropertyNames+enumerable check you could use Object.keys
Instead of getOwnPropertyNames+enumerable check you could use Object.keys
|
average startup time with child process + loop + without proxy (1000 runs) 79.71845515598729 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. |
|
|
||
| if (typeof value.name === 'string' && /^bound /.test(value.name)) | ||
| return value; | ||
|
|
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++)
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++)
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
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
jdalton
Apr 24, 2018
Member
we can't skip native functions.
I know, I'm saying skip non-native.
we can't skip native functions.
I know, I'm saying skip non-native.
devsnek
Apr 24, 2018
Author
Member
so the only functions we bind then are unbound native functions? what about member functions written in js
so the only functions we bind then are unbound native functions? what about member functions written in js
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).
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).
| apply(t, thisArg, args) { | ||
| if (thisArg === proxy) | ||
| thisArg = target; | ||
| return Reflect.apply(t, thisArg, args); |
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.
You might end up needing a thisArg === nsObj check.
A test for calling a native method on the namespace object would cover it.
|
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. |
|
|
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)
|
And, given that it's not a semver change (I think...), I believe there
should be no problem to land it?
…On Sun, Apr 29, 2018 at 2:26 AM John-David Dalton ***@***.***> wrote:
|
| this.exportKeys = Object.keys(this.exports); | ||
|
|
||
| const update = (property, value) => { | ||
| if (this.reflect !== undefined && this.exportKeys.includes(property)) |
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)
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)
ljharb
Apr 29, 2018
Member
Fair point, just wanted to call it out :-)
Fair point, just wanted to call it out :-)
| NativeModule.require(id); | ||
| const module = NativeModule.getCached(id); | ||
| return createDynamicModule( | ||
| [...module.exportKeys, 'default'], url, (reflect) => { |
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)
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)
devsnek
Apr 29, 2018
Author
Member
the order doesn't matter there actually as it just gets injected into a generated source text
the order doesn't matter there actually as it just gets injected into a generated source text
|
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. |
|
@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 |
|
There is an important distinction between experimentation that doesn't
affect legacy at all, and experimentation that creates a performance
regression for existing code. I really don't think a 10% reduction in
NodeJS startup time for existing codebases is a trivial matter to push
through. Yes lets move fast with the modules work, but let's not slow down
existing NodeJS apps in the process.
I'm also starting to get the unicorn for this page consistently - it is
probably worth closing this PR and opening a new one at this point I think
to reset discussion for review.
…On Sun, Apr 29, 2018 at 3:28 PM Gus Caplan ***@***.***> wrote:
@guybedford <https://github.com/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
—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
<#18131 (comment)>, or mute
the thread
<https://github.com/notifications/unsubscribe-auth/AAkiykOyOSII3TlhpYZvz4DcudoJs4Sjks5ttcAKgaJpZM4RdLYy>
.
|
|
@guybedford this code doesn't run unless the flag is given, and yes i would agree a new pr is probably a good idea |
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>
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>
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>
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', andcalling
fs.readFileChecklist
make -j4 test(UNIX), orvcbuild test(Windows) passes