Skip to content

Commit e7f604f

Browse files
bfarias-godaddyBridgeAR
authored andcommitted
esm: remove proxy for builtin exports
PR-URL: #29737 Reviewed-By: Guy Bedford <guybedford@gmail.com> Reviewed-By: Gus Caplan <me@gus.host> Reviewed-By: Jan Krems <jan.krems@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com> Reviewed-By: Minwoo Jung <minwoo@nodesource.com> Reviewed-By: Benjamin Gruenbaum <benjamingr@gmail.com>
1 parent ae8b2b4 commit e7f604f

6 files changed

Lines changed: 107 additions & 69 deletions

File tree

‎doc/api/esm.md‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -555,10 +555,11 @@ cjs === 'cjs'; // true
555555
556556
## Builtin modules
557557
558-
Builtin modules will provide named exports of their public API, as well as a
559-
default export which can be used for, among other things, modifying the named
560-
exports. Named exports of builtin modules are updated when the corresponding
561-
exports property is accessed, redefined, or deleted.
558+
Builtin modules will provide named exports of their public API. A
559+
default export is also provided which is the value of the CommonJS exports.
560+
The default export can be used for, among other things, modifying the named
561+
exports. Named exports of builtin modules are updated only by calling
562+
[`module.syncBuiltinESMExports()`][].
562563
563564
```js
564565
importEventEmitterfrom'events';
@@ -578,8 +579,10 @@ readFile('./foo.txt', (err, source) => {
578579
579580
```js
580581
importfs, { readFileSync } from'fs';
582+
import { syncBuiltinESMExports } from'module';
581583

582584
fs.readFileSync= () =>Buffer.from('Hello, ESM');
585+
syncBuiltinESMExports();
583586

584587
fs.readFileSync=== readFileSync;
585588
```
@@ -1008,6 +1011,7 @@ success!
10081011
[`import.meta.url`]: #esm_import_meta
10091012
[`import`]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import
10101013
[`module.createRequire()`]: modules.html#modules_module_createrequire_filename
1014+
[`module.syncBuiltinESMExports()`]: modules.html#modules_module_syncbuiltinesmexports
10111015
[dynamic instantiate hook]: #esm_dynamic_instantiate_hook
10121016
[package exports]: #esm_package_exports
10131017
[special scheme]: https://url.spec.whatwg.org/#special-scheme

‎doc/api/modules.md‎

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -984,6 +984,36 @@ const requireUtil = createRequireFromPath('../src/utils/');
984984
requireUtil('./some-tool');
985985
```
986986
987+
### module.syncBuiltinESMExports()
988+
<!-- YAML
989+
added: REPLACEME
990+
-->
991+
992+
The `module.syncBuiltinESMExports()` method updates all the live bindings for
993+
builtin ES Modules to match the properties of the CommonJS exports. It does
994+
not add or remove exported names from the ES Modules.
995+
996+
```js
997+
constfs=require('fs');
998+
const { syncBuiltinESMExports } =require('module');
999+
1000+
fs.readFile=null;
1001+
1002+
deletefs.readFileSync;
1003+
1004+
fs.newAPI=functionnewAPI() {
1005+
// ...
1006+
};
1007+
1008+
syncBuiltinESMExports();
1009+
1010+
import('fs').then((esmFS) => {
1011+
assert.strictEqual(esmFS.readFile, null);
1012+
assert.strictEqual('readFileSync'in fs, true);
1013+
assert.strictEqual(esmFS.newAPI, undefined);
1014+
});
1015+
```
1016+
9871017
[GLOBAL_FOLDERS]: #modules_loading_from_the_global_folders
9881018
[`Error`]: errors.html#errors_class_error
9891019
[`__dirname`]: #modules_dirname

‎lib/internal/bootstrap/loaders.js‎

Lines changed: 37 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ function NativeModule(id) {
151151
this.id=id;
152152
this.exports={};
153153
this.reflect=undefined;
154+
this.esmFacade=undefined;
154155
this.exportKeys=undefined;
155156
this.loaded=false;
156157
this.loading=false;
@@ -211,15 +212,19 @@ function requireWithFallbackInDeps(request) {
211212
}
212213

213214
// This is exposed for public loaders
214-
NativeModule.prototype.compileForPublicLoader=function(needToProxify){
215+
NativeModule.prototype.compileForPublicLoader=function(needToSyncExports){
215216
if(!this.canBeRequiredByUsers){
216217
// No code because this is an assertion against bugs
217218
// eslint-disable-next-line no-restricted-syntax
218219
thrownewError(`Should not compile ${this.id} for public use`);
219220
}
220221
this.compile();
221-
if(needToProxify&&!this.exportKeys){
222-
this.proxifyExports();
222+
if(needToSyncExports){
223+
if(!this.exportKeys){
224+
this.exportKeys=Object.keys(this.exports);
225+
}
226+
this.getESMFacade();
227+
this.syncExports();
223228
}
224229
returnthis.exports;
225230
};
@@ -230,61 +235,38 @@ const getOwn = (target, property, receiver) => {
230235
undefined;
231236
};
232237

238+
NativeModule.prototype.getURL=function(){
239+
return`node:${this.id}`;
240+
};
241+
242+
NativeModule.prototype.getESMFacade=function(){
243+
if(this.esmFacade)returnthis.esmFacade;
244+
constcreateDynamicModule=nativeModuleRequire(
245+
'internal/modules/esm/create_dynamic_module');
246+
consturl=this.getURL();
247+
returnthis.esmFacade=createDynamicModule(
248+
[],[...this.exportKeys,'default'],url,(reflect)=>{
249+
this.reflect=reflect;
250+
this.syncExports();
251+
reflect.exports.default.set(this.exports);
252+
});
253+
};
254+
233255
// Provide named exports for all builtin libraries so that the libraries
234256
// may be imported in a nicer way for ESM users. The default export is left
235-
// as the entire namespace (module.exports) and wrapped in a proxy such
236-
// that APMs and other behavior are still left intact.
237-
NativeModule.prototype.proxifyExports=function(){
238-
this.exportKeys=Object.keys(this.exports);
239-
240-
constupdate=(property,value)=>{
241-
if(this.reflect!==undefined&&
242-
ObjectPrototype.hasOwnProperty(this.reflect.exports,property))
243-
this.reflect.exports[property].set(value);
244-
};
245-
246-
consthandler={
247-
__proto__: null,
248-
defineProperty: (target,prop,descriptor)=>{
249-
// Use `Object.defineProperty` instead of `Reflect.defineProperty`
250-
// to throw the appropriate error if something goes wrong.
251-
Object.defineProperty(target,prop,descriptor);
252-
if(typeofdescriptor.get==='function'&&
253-
!Reflect.has(handler,'get')){
254-
handler.get=(target,prop,receiver)=>{
255-
constvalue=Reflect.get(target,prop,receiver);
256-
if(ObjectPrototype.hasOwnProperty(target,prop))
257-
update(prop,value);
258-
returnvalue;
259-
};
260-
}
261-
update(prop,getOwn(target,prop));
262-
returntrue;
263-
},
264-
deleteProperty: (target,prop)=>{
265-
if(Reflect.deleteProperty(target,prop)){
266-
update(prop,undefined);
267-
returntrue;
268-
}
269-
returnfalse;
270-
},
271-
set: (target,prop,value,receiver)=>{
272-
constdescriptor=Reflect.getOwnPropertyDescriptor(target,prop);
273-
if(Reflect.set(target,prop,value,receiver)){
274-
if(descriptor&&typeofdescriptor.set==='function'){
275-
for(constkeyofthis.exportKeys){
276-
update(key,getOwn(target,key,receiver));
277-
}
278-
}else{
279-
update(prop,getOwn(target,prop,receiver));
280-
}
281-
returntrue;
282-
}
283-
returnfalse;
257+
// as the entire namespace (module.exports) and updates when this function is
258+
// called so that APMs and other behavior are supported.
259+
NativeModule.prototype.syncExports=function(){
260+
constnames=this.exportKeys;
261+
if(this.reflect){
262+
for(leti=0;i<names.length;i++){
263+
constexportName=names[i];
264+
if(exportName==='default')continue;
265+
this.reflect.exports[exportName].set(
266+
getOwn(this.exports,exportName,this.exports)
267+
);
284268
}
285-
};
286-
287-
this.exports=newProxy(this.exports,handler);
269+
}
288270
};
289271

290272
NativeModule.prototype.compile=function(){

‎lib/internal/modules/cjs/loader.js‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1130,6 +1130,14 @@ Module._preloadModules = function(requests) {
11301130
parent.require(requests[n]);
11311131
};
11321132

1133+
Module.syncBuiltinESMExports=functionsyncBuiltinESMExports(){
1134+
for(constmodofNativeModule.map.values()){
1135+
if(mod.canBeRequiredByUsers){
1136+
mod.syncExports();
1137+
}
1138+
}
1139+
};
1140+
11331141
// Backwards compatibility
11341142
Module.Module=Module;
11351143

‎lib/internal/modules/esm/translators.js‎

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -129,14 +129,8 @@ translators.set('builtin', async function builtinStrategy(url) {
129129
if(!module){
130130
thrownewERR_UNKNOWN_BUILTIN_MODULE(id);
131131
}
132-
returncreateDynamicModule(
133-
[],[...module.exportKeys,'default'],url,(reflect)=>{
134-
debug(`Loading BuiltinModule ${url}`);
135-
module.reflect=reflect;
136-
for(constkeyofmodule.exportKeys)
137-
reflect.exports[key].set(module.exports[key]);
138-
reflect.exports.default.set(module.exports);
139-
});
132+
debug(`Loading BuiltinModule ${url}`);
133+
returnmodule.getESMFacade();
140134
});
141135

142136
// Strategy for loading a JSON file

‎test/es-module/test-esm-live-binding.mjs‎

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Flags: --experimental-modules
22
import'../common/index.mjs';
33
importassertfrom'assert';
4+
import{syncBuiltinESMExports}from'module';
45

56
importfs,{readFile,readFileSync}from'fs';
67
importevents,{defaultMaxListeners}from'events';
@@ -14,10 +15,12 @@ const s = Symbol();
1415
constfn=()=>s;
1516

1617
Reflect.deleteProperty(fs,'readFile');
18+
syncBuiltinESMExports();
1719

1820
assert.deepStrictEqual([fs.readFile,readFile],[undefined,undefined]);
1921

2022
fs.readFile=fn;
23+
syncBuiltinESMExports();
2124

2225
assert.deepStrictEqual([fs.readFile(),readFile()],[s,s]);
2326

@@ -26,10 +29,12 @@ Reflect.defineProperty(fs, 'readFile', {
2629
configurable: true,
2730
writable: true,
2831
});
32+
syncBuiltinESMExports();
2933

3034
assert.deepStrictEqual([fs.readFile(),readFile()],[s,s]);
3135

3236
Reflect.deleteProperty(fs,'readFile');
37+
syncBuiltinESMExports();
3338

3439
assert.deepStrictEqual([fs.readFile,readFile],[undefined,undefined]);
3540

@@ -39,10 +44,14 @@ Reflect.defineProperty(fs, 'readFile', {
3944
get(){returncount;},
4045
configurable: true,
4146
});
47+
syncBuiltinESMExports();
48+
49+
assert.deepStrictEqual([readFile,fs.readFile],[0,0]);
4250

4351
count++;
52+
syncBuiltinESMExports();
4453

45-
assert.deepStrictEqual([readFile,fs.readFile,readFile],[0,1,1]);
54+
assert.deepStrictEqual([fs.readFile,readFile],[1,1]);
4655

4756
letotherValue;
4857

@@ -62,6 +71,7 @@ Reflect.defineProperty(fs, 'readFileSync', {
6271
});
6372

6473
fs.readFile=2;
74+
syncBuiltinESMExports();
6575

6676
assert.deepStrictEqual([readFile,readFileSync],[undefined,2]);
6777

@@ -86,17 +96,23 @@ Reflect.defineProperty(Function.prototype, 'defaultMaxListeners', {
8696
});
8797
},
8898
});
99+
syncBuiltinESMExports();
89100

90101
assert.strictEqual(defaultMaxListeners,originDefaultMaxListeners);
91102
assert.strictEqual(events.defaultMaxListeners,originDefaultMaxListeners);
92103

93-
assert.strictEqual(++events.defaultMaxListeners,
104+
events.defaultMaxListeners+=1;
105+
106+
assert.strictEqual(events.defaultMaxListeners,
94107
originDefaultMaxListeners+1);
95108

109+
syncBuiltinESMExports();
110+
96111
assert.strictEqual(defaultMaxListeners,originDefaultMaxListeners+1);
97112
assert.strictEqual(Function.prototype.defaultMaxListeners,1);
98113

99114
Function.prototype.defaultMaxListeners='foo';
115+
syncBuiltinESMExports();
100116

101117
assert.strictEqual(Function.prototype.defaultMaxListeners,'foo');
102118
assert.strictEqual(events.defaultMaxListeners,originDefaultMaxListeners+1);
@@ -117,16 +133,19 @@ const p = {
117133
};
118134

119135
util.__proto__=p;// eslint-disable-line no-proto
136+
syncBuiltinESMExports();
120137

121138
assert.strictEqual(util.foo,1);
122139

123140
util.foo='bar';
141+
syncBuiltinESMExports();
124142

125143
assert.strictEqual(count,1);
126144
assert.strictEqual(util.foo,'bar');
127145
assert.strictEqual(p.foo,2);
128146

129147
p.foo='foo';
148+
syncBuiltinESMExports();
130149

131150
assert.strictEqual(p.foo,'foo');
132151

@@ -135,6 +154,7 @@ util.__proto__ = utilProto; // eslint-disable-line no-proto
135154

136155
Reflect.deleteProperty(util,'foo');
137156
Reflect.deleteProperty(Function.prototype,'defaultMaxListeners');
157+
syncBuiltinESMExports();
138158

139159
assert.throws(
140160
()=>Object.defineProperty(events,'defaultMaxListeners',{value: 3}),

0 commit comments

Comments
 (0)