Skip to content

Commit 74194a5

Browse files
This PR was originally submitted to React Native project when bundler was part of that and it was suggested to merge with this project.
Link to original request facebook/react-native#13980 …to their reliance on some CommonJS/node module behavior not implemented by the React Native require polyfill from the packager. This PR adds some of these behaviors: Circular dependencies when exporting by assignment to the module.exports object. Should fix #3298 for good. Exporting by assignment to this object. Fixes #2862 The module.id represents a unique identifier that can be used in require() calls. There were no tests for the require polyfill so I added one. Motivation (required) What existing problem does the pull request solve? Circular dependencies when exporting by assignment to the module.exports object. Should fix #3298 for good. Exporting by assignment to this object. Fixes #2862 Test Plan (required) There is no change to UI or API. However, tests have been added for require.js polyfill. Next Steps Individual and company CLA have been signed already.
1 parent dbb2d44 commit 74194a5

File tree

6 files changed

+282
-236
lines changed

6 files changed

+282
-236
lines changed

package.json

+1-2
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@
5959
"timers": "fake",
6060
"setupFiles": [
6161
"<rootDir>/scripts/setupJest.js"
62-
],
63-
"testEnvironment": "node"
62+
]
6463
}
6564
}

packages/metro-bundler/src/Resolver/polyfills/__tests__/require-test.js

+124
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,13 @@
1414

1515
'use strict';
1616

17+
// TODO: figure out why jest.resetModules() doesn't reset the polyfill's module cache
18+
global.__DEV__ = false;
19+
const origGlobalRequire = global.require;
20+
require('../require');
21+
const requireShim = global.require;
22+
const defineShim = global.__d;
23+
global.require = origGlobalRequire;
1724
const babel = require('babel-core');
1825
const babelConfig = require('../../../babelRegisterOnly').config;
1926
const fs = require('fs');
@@ -102,4 +109,121 @@ describe('require', () => {
102109
expect(mockFactory.mock.calls[0][4]).toEqual([2, 3]);
103110
});
104111
});
112+
describe('exports', () => {
113+
it('should be an object containing named exports', () => {
114+
defineShim(function(global, require, module, exports) {
115+
expect(exports).toEqual({});
116+
exports.hello = 'world';
117+
}, 1);
118+
expect(requireShim(1)).toEqual({hello: 'world'});
119+
});
120+
});
121+
122+
describe('this', () => {
123+
it('should be the exports object', () => {
124+
defineShim(function(global, require, module, exports) {
125+
expect(this).toBe(exports);
126+
}, 2);
127+
requireShim(2);
128+
});
129+
});
130+
131+
describe('module.id', () => {
132+
it('should be the identifier of the module passed to require', () => {
133+
defineShim(function(global, require, module, exports) {
134+
expect(module.id).toBe(3);
135+
}, 3);
136+
requireShim(3);
137+
});
138+
});
139+
140+
describe('module.exports', () => {
141+
it('should be the same as exports', () => {
142+
defineShim(function(global, require, module, exports) {
143+
expect(module.exports).toBe(exports);
144+
}, 11);
145+
requireShim(11);
146+
});
147+
148+
describe('should support re-assignements', () => {
149+
let moduleId = 21;
150+
function testModuleExportsValue(value) {
151+
defineShim(function(global, require, module, exports) {
152+
module.exports = value;
153+
}, moduleId);
154+
expect(requireShim(moduleId)).toBe(value);
155+
moduleId++;
156+
}
157+
158+
it('to a primitive value', () => {
159+
testModuleExportsValue(123);
160+
});
161+
162+
it('to an object', () => {
163+
testModuleExportsValue({
164+
hello: 'world',
165+
});
166+
});
167+
168+
it('to a function', () => {
169+
testModuleExportsValue(function Test() {});
170+
});
171+
});
172+
});
173+
174+
describe('dependencies', () => {
175+
it('should be resolved', () => {
176+
defineShim(function(global, require, module, exports) {
177+
exports.dep2 = require(32);
178+
}, 31);
179+
defineShim(function(global, require, module, exports) {
180+
exports.hello = 'world';
181+
}, 32);
182+
expect(requireShim(31)).toEqual({
183+
dep2: {
184+
hello: 'world',
185+
},
186+
});
187+
});
188+
189+
it('should handle circular deps using exports', () => {
190+
defineShim(function(global, require, module, exports) {
191+
exports.first = 'first value';
192+
var p = require(42).p;
193+
exports.first = 'second value';
194+
exports.firstWas = p();
195+
}, 41);
196+
defineShim(function(global, require, module, exports) {
197+
var first = require(41).first;
198+
exports.p = function() {
199+
return first;
200+
};
201+
}, 42);
202+
expect(requireShim(41)).toEqual({
203+
first: 'second value',
204+
firstWas: 'first value',
205+
});
206+
});
207+
208+
it('should handle circular deps using module.exports', () => {
209+
defineShim(function(global, require, module, exports) {
210+
module.exports = {
211+
first: 'first value',
212+
};
213+
var p = require(52).p;
214+
module.exports.first = 'second value';
215+
module.exports.firstWas = p();
216+
}, 51);
217+
defineShim(function(global, require, module, exports) {
218+
var first = require(51).first;
219+
exports.p = function() {
220+
return first;
221+
};
222+
}, 52);
223+
expect(requireShim(51)).toEqual({
224+
first: 'second value',
225+
firstWas: 'first value',
226+
});
227+
});
228+
});
105229
});

packages/metro-bundler/src/Resolver/polyfills/require.js

+27-4
Original file line numberDiff line numberDiff line change
@@ -176,23 +176,46 @@ function loadModuleImplementation(moduleId, module) {
176176
// factory to keep any require cycles inside the factory from causing an
177177
// infinite require loop.
178178
module.isInitialized = true;
179-
const exports = (module.exports = {});
179+
module.exports = {};
180180
const {factory, dependencyMap} = module;
181181
try {
182182
if (__DEV__) {
183183
// $FlowFixMe: we know that __DEV__ is const and `Systrace` exists
184184
Systrace.beginEvent('JS_require_' + (module.verboseName || moduleId));
185185
}
186186

187-
const moduleObject: Module = {exports};
187+
const moduleObject: Module = {
188+
get id() {
189+
return moduleId;
190+
},
191+
get hot() {
192+
return module.hot;
193+
},
194+
set hot(value) {
195+
module.hot = value;
196+
},
197+
get exports() {
198+
return module.exports;
199+
},
200+
set exports(value) {
201+
module.exports = value;
202+
},
203+
};
188204
if (__DEV__ && module.hot) {
189205
moduleObject.hot = module.hot;
190206
}
191207

192208
// keep args in sync with with defineModuleCode in
193209
// metro-bundler/src/Resolver/index.js
194210
// and metro-bundler/src/ModuleGraph/worker.js
195-
factory(global, require, moduleObject, exports, dependencyMap);
211+
factory.call(
212+
module.exports,
213+
global,
214+
require,
215+
moduleObject,
216+
module.exports,
217+
dependencyMap,
218+
);
196219

197220
// avoid removing factory in DEV mode as it breaks HMR
198221
if (!__DEV__) {
@@ -205,7 +228,7 @@ function loadModuleImplementation(moduleId, module) {
205228
// $FlowFixMe: we know that __DEV__ is const and `Systrace` exists
206229
Systrace.endEvent();
207230
}
208-
return (module.exports = moduleObject.exports);
231+
return module.exports;
209232
} catch (e) {
210233
module.hasError = true;
211234
module.error = e;

packages/metro-bundler/src/integration_tests/__tests__/__snapshots__/basic_bundle-test.js.snap

+42-10
Original file line numberDiff line numberDiff line change
@@ -83,23 +83,39 @@ function loadModuleImplementation(moduleId, module) {
8383
}
8484
8585
module.isInitialized = true;
86-
var exports = module.exports = {};
86+
module.exports = {};
8787
var _module = module,
8888
factory = _module.factory,
8989
dependencyMap = _module.dependencyMap;
9090
9191
try {
9292
93-
var _moduleObject = { exports: exports };
94-
95-
factory(global, _require, _moduleObject, exports, dependencyMap);
93+
var _moduleObject = {
94+
get id() {
95+
return moduleId;
96+
},
97+
get hot() {
98+
return module.hot;
99+
},
100+
set hot(value) {
101+
module.hot = value;
102+
},
103+
get exports() {
104+
return module.exports;
105+
},
106+
set exports(value) {
107+
module.exports = value;
108+
}
109+
};
110+
111+
factory.call(module.exports, global, _require, _moduleObject, module.exports, dependencyMap);
96112
97113
{
98114
module.factory = undefined;
99115
module.dependencyMap = undefined;
100116
}
101117
102-
return module.exports = _moduleObject.exports;
118+
return module.exports;
103119
} catch (e) {
104120
module.hasError = true;
105121
module.error = e;
@@ -253,23 +269,39 @@ function loadModuleImplementation(moduleId, module) {
253269
}
254270
255271
module.isInitialized = true;
256-
var exports = module.exports = {};
272+
module.exports = {};
257273
var _module = module,
258274
factory = _module.factory,
259275
dependencyMap = _module.dependencyMap;
260276
261277
try {
262278
263-
var _moduleObject = { exports: exports };
264-
265-
factory(global, _require, _moduleObject, exports, dependencyMap);
279+
var _moduleObject = {
280+
get id() {
281+
return moduleId;
282+
},
283+
get hot() {
284+
return module.hot;
285+
},
286+
set hot(value) {
287+
module.hot = value;
288+
},
289+
get exports() {
290+
return module.exports;
291+
},
292+
set exports(value) {
293+
module.exports = value;
294+
}
295+
};
296+
297+
factory.call(module.exports, global, _require, _moduleObject, module.exports, dependencyMap);
266298
267299
{
268300
module.factory = undefined;
269301
module.dependencyMap = undefined;
270302
}
271303
272-
return module.exports = _moduleObject.exports;
304+
return module.exports;
273305
} catch (e) {
274306
module.hasError = true;
275307
module.error = e;

0 commit comments

Comments
 (0)