From e6dbb8ef8bd980aa87a069a7b95229d8fb40aa16 Mon Sep 17 00:00:00 2001 From: Chirag Gupta Date: Mon, 15 May 2017 11:48:17 -0700 Subject: [PATCH] Using some 3rd party libraries with React Native is not possible due 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 the 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. --- .../polyfills/__tests__/require-test.js | 135 ++++++++++++++++++ packager/src/Resolver/polyfills/require.js | 18 ++- 2 files changed, 149 insertions(+), 4 deletions(-) create mode 100644 packager/src/Resolver/polyfills/__tests__/require-test.js diff --git a/packager/src/Resolver/polyfills/__tests__/require-test.js b/packager/src/Resolver/polyfills/__tests__/require-test.js new file mode 100644 index 00000000000000..484a353b559b13 --- /dev/null +++ b/packager/src/Resolver/polyfills/__tests__/require-test.js @@ -0,0 +1,135 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. An additional grant + * of patent rights can be found in the PATENTS file in the same directory. + */ + +// TODO: figure out why jest.resetModules() doesn't reset the polyfill's module cache +const origGlobalRequire = global.require; +require('../require'); +const requireShim = global.require; +const defineShim = global.__d; +global.require = origGlobalRequire; + +describe('require', () => { + describe('exports', () => { + it('should be an object containing named exports', () => { + defineShim(function(global, require, module, exports) { + expect(exports).toEqual({}); + exports.hello = 'world'; + },1); + expect(requireShim(1)).toEqual({hello: 'world'}); + }); + }); + + describe('this', () => { + it('should be the exports object', () => { + defineShim(function(global, require, module, exports) { + expect(this).toBe(exports); + },2); + requireShim(2); + }); + }); + + describe('module.id', () => { + it('should be the identifier of the module passed to require', () => { + defineShim(function(global, require, module, exports) { + expect(module.id).toBe(3); + },3); + requireShim(3); + }); + }); + + describe('module.exports', () => { + it('should be the same as exports', () => { + defineShim(function(global, require, module, exports) { + expect(module.exports).toBe(exports); + },11); + requireShim(11); + }); + + describe('should support re-assignements', () => { + let moduleId = 21; + function testModuleExportsValue(value) { + defineShim(function(global, require, module, exports) { + module.exports = value; + },moduleId); + expect(requireShim(moduleId)).toBe(value); + moduleId++; + } + + it('to a primitive value', () => { + testModuleExportsValue(123); + }); + + it('to an object', () => { + testModuleExportsValue({ + hello: 'world' + }); + }); + + it('to a function', () => { + testModuleExportsValue(function Test() {}); + }); + }); + }); + + describe('dependencies', () => { + it('should be resolved', () => { + defineShim(function(global, require, module, exports) { + exports.dep2 = require(32); + },31); + defineShim(function(global, require, module, exports) { + exports.hello = 'world'; + },32); + expect(requireShim(31)).toEqual({ + dep2: { + hello: 'world' + } + }); + }); + + it('should handle circular deps using exports', () => { + defineShim(function(global, require, module, exports) { + exports.first = 'first value'; + var p = require(42).p; + exports.first = 'second value'; + exports.firstWas = p(); + },41); + defineShim(function(global, require, module, exports) { + var first = require(41).first; + exports.p = function() { + return first; + } + },42); + expect(requireShim(41)).toEqual({ + first: 'second value', + firstWas: 'first value' + }); + }); + + it('should handle circular deps using module.exports', () => { + defineShim(function(global, require, module, exports) { + module.exports = { + first: 'first value' + }; + var p = require(52).p; + module.exports.first = 'second value'; + module.exports.firstWas = p(); + },51); + defineShim(function(global, require, module, exports) { + var first = require(51).first; + exports.p = function() { + return first; + } + },52); + expect(requireShim(51)).toEqual({ + first: 'second value', + firstWas: 'first value' + }); + }); + }); +}); diff --git a/packager/src/Resolver/polyfills/require.js b/packager/src/Resolver/polyfills/require.js index 3c1f0b24bad46b..806ec02bf19d6f 100644 --- a/packager/src/Resolver/polyfills/require.js +++ b/packager/src/Resolver/polyfills/require.js @@ -155,7 +155,7 @@ function loadModuleImplementation(moduleId, module) { // factory to keep any require cycles inside the factory from causing an // infinite require loop. module.isInitialized = true; - const exports = module.exports = {}; + module.exports = {}; const {factory, dependencyMap} = module; try { if (__DEV__) { @@ -163,7 +163,17 @@ function loadModuleImplementation(moduleId, module) { Systrace.beginEvent('JS_require_' + (module.verboseName || moduleId)); } - const moduleObject: Module = {exports}; + const moduleObject = { + get id() { + return moduleId; + }, + get exports() { + return module.exports; + }, + set exports(value) { + module.exports = value; + } + }; if (__DEV__ && module.hot) { moduleObject.hot = module.hot; } @@ -171,7 +181,7 @@ function loadModuleImplementation(moduleId, module) { // keep args in sync with with defineModuleCode in // packager/src//Resolver/index.js // and packager/src//ModuleGraph/worker.js - factory(global, require, moduleObject, exports, dependencyMap); + factory.call(module.exports, global, require, moduleObject, module.exports, dependencyMap); // avoid removing factory in DEV mode as it breaks HMR if (!__DEV__) { @@ -183,7 +193,7 @@ function loadModuleImplementation(moduleId, module) { // $FlowFixMe: we know that __DEV__ is const and `Systrace` exists Systrace.endEvent(); } - return (module.exports = moduleObject.exports); + return module.exports; } catch (e) { module.hasError = true; module.error = e;