Skip to content

Commit 104c3bc

Browse files
devsnektargos
authored andcommitted
esm: provide named exports for builtin libs
Provide named exports for all builtin libraries so that the libraries may be imported in a nicer way for esm users. The default export is left as the entire namespace (module.exports) and wrapped in a proxy such that APMs and other behavior are still left intact. PR-URL: #20403 Reviewed-By: Bradley Farias <bradley.meck@gmail.com> Reviewed-By: Guy Bedford <guybedford@gmail.com> Reviewed-By: Jan Krems <jan.krems@gmail.com>
1 parent ebd102e commit 104c3bc

8 files changed

Lines changed: 282 additions & 16 deletions

File tree

‎doc/api/esm.md‎

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,16 +95,43 @@ When loaded via `import` these modules will provide a single `default` export
9595
representing the value of `module.exports` at the time they finished evaluating.
9696

9797
```js
98-
importfsfrom'fs';
99-
fs.readFile('./foo.txt', (err, body) => {
98+
// foo.js
99+
module.exports= { one:1 };
100+
101+
// bar.js
102+
importfoofrom'./foo.js';
103+
foo.one===1; // true
104+
```
105+
106+
Builtin modules will provide named exports of their public API, as well as a
107+
default export which can be used for, among other things, modifying the named
108+
exports. Named exports of builtin modules are updated when the corresponding
109+
exports property is accessed, redefined, or deleted.
110+
111+
```js
112+
importEventEmitterfrom'events';
113+
conste=newEventEmitter();
114+
```
115+
116+
```js
117+
import { readFile } from'fs';
118+
readFile('./foo.txt', (err, source) => {
100119
if (err) {
101120
console.error(err);
102121
} else {
103-
console.log(body);
122+
console.log(source);
104123
}
105124
});
106125
```
107126

127+
```js
128+
importfs, { readFileSync } from'fs';
129+
130+
fs.readFileSync= () =>Buffer.from('Hello, ESM');
131+
132+
fs.readFileSync=== readFileSync;
133+
```
134+
108135
## Loader hooks
109136

110137
<!-- type=misc -->

‎lib/internal/bootstrap/loaders.js‎

Lines changed: 81 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,26 @@
4141

4242
(functionbootstrapInternalLoaders(process,getBinding,getLinkedBinding,
4343
getInternalBinding){
44+
const{
45+
apply: ReflectApply,
46+
deleteProperty: ReflectDeleteProperty,
47+
get: ReflectGet,
48+
getOwnPropertyDescriptor: ReflectGetOwnPropertyDescriptor,
49+
has: ReflectHas,
50+
set: ReflectSet,
51+
}=Reflect;
52+
const{
53+
prototype: {
54+
hasOwnProperty: ObjectHasOwnProperty,
55+
},
56+
create: ObjectCreate,
57+
defineProperty: ObjectDefineProperty,
58+
keys: ObjectKeys,
59+
}=Object;
4460

4561
// Set up process.moduleLoadList
4662
constmoduleLoadList=[];
47-
Object.defineProperty(process,'moduleLoadList',{
63+
ObjectDefineProperty(process,'moduleLoadList',{
4864
value: moduleLoadList,
4965
configurable: true,
5066
enumerable: true,
@@ -53,7 +69,7 @@
5369

5470
// Set up process.binding() and process._linkedBinding()
5571
{
56-
constbindingObj=Object.create(null);
72+
constbindingObj=ObjectCreate(null);
5773

5874
process.binding=functionbinding(module){
5975
module=String(module);
@@ -77,7 +93,7 @@
7793
// Set up internalBinding() in the closure
7894
letinternalBinding;
7995
{
80-
constbindingObj=Object.create(null);
96+
constbindingObj=ObjectCreate(null);
8197
internalBinding=functioninternalBinding(module){
8298
letmod=bindingObj[module];
8399
if(typeofmod!=='object'){
@@ -95,6 +111,8 @@
95111
this.filename=`${id}.js`;
96112
this.id=id;
97113
this.exports={};
114+
this.reflect=undefined;
115+
this.exportKeys=undefined;
98116
this.loaded=false;
99117
this.loading=false;
100118
}
@@ -193,6 +211,12 @@
193211
'\n});'
194212
];
195213

214+
constgetOwn=(target,property,receiver)=>{
215+
returnReflectApply(ObjectHasOwnProperty,target,[property]) ?
216+
ReflectGet(target,property,receiver) :
217+
undefined;
218+
};
219+
196220
NativeModule.prototype.compile=function(){
197221
letsource=NativeModule.getSource(this.id);
198222
source=NativeModule.wrap(source);
@@ -208,6 +232,60 @@
208232
NativeModule.require;
209233
fn(this.exports,requireFn,this,process);
210234

235+
if(config.experimentalModules&&!NativeModule.isInternal(this.id)){
236+
this.exportKeys=ObjectKeys(this.exports);
237+
238+
constupdate=(property,value)=>{
239+
if(this.reflect!==undefined&&
240+
ReflectApply(ObjectHasOwnProperty,
241+
this.reflect.exports,[property]))
242+
this.reflect.exports[property].set(value);
243+
};
244+
245+
consthandler={
246+
__proto__: null,
247+
defineProperty: (target,prop,descriptor)=>{
248+
// Use `Object.defineProperty` instead of `Reflect.defineProperty`
249+
// to throw the appropriate error if something goes wrong.
250+
ObjectDefineProperty(target,prop,descriptor);
251+
if(typeofdescriptor.get==='function'&&
252+
!ReflectHas(handler,'get')){
253+
handler.get=(target,prop,receiver)=>{
254+
constvalue=ReflectGet(target,prop,receiver);
255+
if(ReflectApply(ObjectHasOwnProperty,target,[prop]))
256+
update(prop,value);
257+
returnvalue;
258+
};
259+
}
260+
update(prop,getOwn(target,prop));
261+
returntrue;
262+
},
263+
deleteProperty: (target,prop)=>{
264+
if(ReflectDeleteProperty(target,prop)){
265+
update(prop,undefined);
266+
returntrue;
267+
}
268+
returnfalse;
269+
},
270+
set: (target,prop,value,receiver)=>{
271+
constdescriptor=ReflectGetOwnPropertyDescriptor(target,prop);
272+
if(ReflectSet(target,prop,value,receiver)){
273+
if(descriptor&&typeofdescriptor.set==='function'){
274+
for(constkeyofthis.exportKeys){
275+
update(key,getOwn(target,key,receiver));
276+
}
277+
}else{
278+
update(prop,getOwn(target,prop,receiver));
279+
}
280+
returntrue;
281+
}
282+
returnfalse;
283+
}
284+
};
285+
286+
this.exports=newProxy(this.exports,handler);
287+
}
288+
211289
this.loaded=true;
212290
}finally{
213291
this.loading=false;

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,10 @@ const createDynamicModule = (exports, url = '', evaluate) => {
5252
constmodule=newModuleWrap(reexports,`${url}`);
5353
module.link(async()=>reflectiveModule);
5454
module.instantiate();
55+
reflect.namespace=module.namespace();
5556
return{
5657
module,
57-
reflect
58+
reflect,
5859
};
5960
};
6061

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

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,18 @@ translators.set('cjs', async (url) => {
5959
// through normal resolution
6060
translators.set('builtin',async(url)=>{
6161
debug(`Translating BuiltinModule ${url}`);
62-
returncreateDynamicModule(['default'],url,(reflect)=>{
63-
debug(`Loading BuiltinModule ${url}`);
64-
constexports=NativeModule.require(url.slice(5));
65-
reflect.exports.default.set(exports);
66-
});
62+
// slice 'node:' scheme
63+
constid=url.slice(5);
64+
NativeModule.require(id);
65+
constmodule=NativeModule.getCached(id);
66+
returncreateDynamicModule(
67+
[...module.exportKeys,'default'],url,(reflect)=>{
68+
debug(`Loading BuiltinModule ${url}`);
69+
module.reflect=reflect;
70+
for(constkeyofmodule.exportKeys)
71+
reflect.exports[key].set(module.exports[key]);
72+
reflect.exports.default.set(module.exports);
73+
});
6774
});
6875

6976
// Strategy for loading a node native module

‎test/es-module/test-esm-dynamic-import.js‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ function expectFsNamespace(result) {
5252
Promise.resolve(result)
5353
.then(common.mustCall(ns=>{
5454
assert.strictEqual(typeofns.default.writeFile,'function');
55+
assert.strictEqual(typeofns.writeFile,'function');
5556
}));
5657
}
5758

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
// Flags: --experimental-modules
2+
3+
import'../common';
4+
importassertfrom'assert';
5+
6+
importfs,{readFile,readFileSync}from'fs';
7+
importevents,{defaultMaxListeners}from'events';
8+
importutilfrom'util';
9+
10+
constreadFileDescriptor=Reflect.getOwnPropertyDescriptor(fs,'readFile');
11+
constreadFileSyncDescriptor=
12+
Reflect.getOwnPropertyDescriptor(fs,'readFileSync');
13+
14+
consts=Symbol();
15+
constfn=()=>s;
16+
17+
Reflect.deleteProperty(fs,'readFile');
18+
19+
assert.deepStrictEqual([fs.readFile,readFile],[undefined,undefined]);
20+
21+
fs.readFile=fn;
22+
23+
assert.deepStrictEqual([fs.readFile(),readFile()],[s,s]);
24+
25+
Reflect.defineProperty(fs,'readFile',{
26+
value: fn,
27+
configurable: true,
28+
writable: true,
29+
});
30+
31+
assert.deepStrictEqual([fs.readFile(),readFile()],[s,s]);
32+
33+
Reflect.deleteProperty(fs,'readFile');
34+
35+
assert.deepStrictEqual([fs.readFile,readFile],[undefined,undefined]);
36+
37+
letcount=0;
38+
39+
Reflect.defineProperty(fs,'readFile',{
40+
get(){returncount;},
41+
configurable: true,
42+
});
43+
44+
count++;
45+
46+
assert.deepStrictEqual([readFile,fs.readFile,readFile],[0,1,1]);
47+
48+
letotherValue;
49+
50+
Reflect.defineProperty(fs,'readFile',{// eslint-disable-line accessor-pairs
51+
set(value){
52+
Reflect.deleteProperty(fs,'readFile');
53+
otherValue=value;
54+
},
55+
configurable: true,
56+
});
57+
58+
Reflect.defineProperty(fs,'readFileSync',{
59+
get(){
60+
returnotherValue;
61+
},
62+
configurable: true,
63+
});
64+
65+
fs.readFile=2;
66+
67+
assert.deepStrictEqual([readFile,readFileSync],[undefined,2]);
68+
69+
Reflect.defineProperty(fs,'readFile',readFileDescriptor);
70+
Reflect.defineProperty(fs,'readFileSync',readFileSyncDescriptor);
71+
72+
constoriginDefaultMaxListeners=events.defaultMaxListeners;
73+
constutilProto=util.__proto__;// eslint-disable-line no-proto
74+
75+
count=0;
76+
77+
Reflect.defineProperty(Function.prototype,'defaultMaxListeners',{
78+
configurable: true,
79+
enumerable: true,
80+
get: function(){return++count;},
81+
set: function(v){
82+
Reflect.defineProperty(this,'defaultMaxListeners',{
83+
configurable: true,
84+
enumerable: true,
85+
writable: true,
86+
value: v,
87+
});
88+
},
89+
});
90+
91+
assert.strictEqual(defaultMaxListeners,originDefaultMaxListeners);
92+
assert.strictEqual(events.defaultMaxListeners,originDefaultMaxListeners);
93+
94+
assert.strictEqual(++events.defaultMaxListeners,
95+
originDefaultMaxListeners+1);
96+
97+
assert.strictEqual(defaultMaxListeners,originDefaultMaxListeners+1);
98+
assert.strictEqual(Function.prototype.defaultMaxListeners,1);
99+
100+
Function.prototype.defaultMaxListeners='foo';
101+
102+
assert.strictEqual(Function.prototype.defaultMaxListeners,'foo');
103+
assert.strictEqual(events.defaultMaxListeners,originDefaultMaxListeners+1);
104+
assert.strictEqual(defaultMaxListeners,originDefaultMaxListeners+1);
105+
106+
count=0;
107+
108+
constp={
109+
getfoo(){return++count;},
110+
setfoo(v){
111+
Reflect.defineProperty(this,'foo',{
112+
configurable: true,
113+
enumerable: true,
114+
writable: true,
115+
value: v,
116+
});
117+
},
118+
};
119+
120+
util.__proto__=p;// eslint-disable-line no-proto
121+
122+
assert.strictEqual(util.foo,1);
123+
124+
util.foo='bar';
125+
126+
assert.strictEqual(count,1);
127+
assert.strictEqual(util.foo,'bar');
128+
assert.strictEqual(p.foo,2);
129+
130+
p.foo='foo';
131+
132+
assert.strictEqual(p.foo,'foo');
133+
134+
events.defaultMaxListeners=originDefaultMaxListeners;
135+
util.__proto__=utilProto;// eslint-disable-line no-proto
136+
137+
Reflect.deleteProperty(util,'foo');
138+
Reflect.deleteProperty(Function.prototype,'defaultMaxListeners');
139+
140+
assert.throws(
141+
()=>Object.defineProperty(events,'defaultMaxListeners',{value: 3}),
142+
/TypeError:Cannotredefine/
143+
);

‎test/es-module/test-esm-namespace.mjs‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,13 @@
22
import'../common';
33
import*asfsfrom'fs';
44
importassertfrom'assert';
5+
importModulefrom'module';
56

6-
assert.deepStrictEqual(Object.keys(fs),['default']);
7+
constkeys=Object.entries(
8+
Object.getOwnPropertyDescriptors(newModule().require('fs')))
9+
.filter(([name,d])=>d.enumerable)
10+
.map(([name])=>name)
11+
.concat('default')
12+
.sort();
13+
14+
assert.deepStrictEqual(Object.keys(fs).sort(),keys);

0 commit comments

Comments
 (0)