/** * 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. */ 'use strict'; const path = require('path'); const Activity = require('../Activity'); const DependencyGraph = require('node-haste'); const declareOpts = require('../lib/declareOpts'); const Promise = require('promise'); const validateOpts = declareOpts({ projectRoots: { type: 'array', required: true, }, blacklistRE: { type: 'object', // typeof regex is object }, polyfillModuleNames: { type: 'array', default: [], }, moduleFormat: { type: 'string', default: 'haste', }, assetRoots: { type: 'array', default: [], }, fileWatcher: { type: 'object', required: true, }, assetExts: { type: 'array', required: true, }, cache: { type: 'object', required: true, }, getModuleId: { type: 'function', required: true, }, transformCode: { type: 'function', }, minifyCode: { type: 'function', }, }); const getDependenciesValidateOpts = declareOpts({ dev: { type: 'boolean', default: true, }, platform: { type: 'string', required: false, }, unbundle: { type: 'boolean', default: false }, recursive: { type: 'boolean', default: true, }, }); class Resolver { constructor(options) { const opts = validateOpts(options); this._depGraph = new DependencyGraph({ activity: Activity, roots: opts.projectRoots, assetRoots_DEPRECATED: opts.assetRoots, assetExts: opts.assetExts, ignoreFilePath: function(filepath) { return filepath.indexOf('__tests__') !== -1 || (opts.blacklistRE && opts.blacklistRE.test(filepath)); }, providesModuleNodeModules: [ 'react', 'react-native', // Parse requires AsyncStorage. They will // change that to require('react-native') which // should work after this release and we can // remove it from here. 'parse', ], platforms: ['ios', 'android'], preferNativePlatform: true, fileWatcher: opts.fileWatcher, cache: opts.cache, shouldThrowOnUnresolvedErrors: (_, platform) => platform === 'ios', transformCode: opts.transformCode, assetDependencies: ['react-native/Libraries/Image/AssetRegistry'], }); this._getModuleId = options.getModuleId; this._minifyCode = opts.minifyCode; this._polyfillModuleNames = opts.polyfillModuleNames || []; this._depGraph.load().catch(err => { console.error(err.message + '\n' + err.stack); process.exit(1); }); } getShallowDependencies(entryFile, transformOptions) { return this._depGraph.getShallowDependencies(entryFile, transformOptions); } stat(filePath) { return this._depGraph.getFS().stat(filePath); } getModuleForPath(entryFile) { return this._depGraph.getModuleForPath(entryFile); } getDependencies(entryPath, options, transformOptions, onProgress) { const {platform, recursive} = getDependenciesValidateOpts(options); return this._depGraph.getDependencies({ entryPath, platform, transformOptions, recursive, onProgress, }).then(resolutionResponse => { this._getPolyfillDependencies().reverse().forEach( polyfill => resolutionResponse.prependDependency(polyfill) ); // currently used by HMR resolutionResponse.getModuleId = this._getModuleId; return resolutionResponse.finalize(); }); } getModuleSystemDependencies(options) { const opts = getDependenciesValidateOpts(options); const prelude = opts.dev ? path.join(__dirname, 'polyfills/prelude_dev.js') : path.join(__dirname, 'polyfills/prelude.js'); const moduleSystem = opts.unbundle ? path.join(__dirname, 'polyfills/require-unbundle.js') : path.join(__dirname, 'polyfills/require.js'); return [ prelude, moduleSystem ].map(moduleName => this._depGraph.createPolyfill({ file: moduleName, id: moduleName, dependencies: [], })); } _getPolyfillDependencies() { const polyfillModuleNames = [ path.join(__dirname, 'polyfills/polyfills.js'), path.join(__dirname, 'polyfills/console.js'), path.join(__dirname, 'polyfills/error-guard.js'), path.join(__dirname, 'polyfills/Number.es6.js'), path.join(__dirname, 'polyfills/String.prototype.es6.js'), path.join(__dirname, 'polyfills/Array.prototype.es6.js'), path.join(__dirname, 'polyfills/Array.es6.js'), path.join(__dirname, 'polyfills/Object.es7.js'), path.join(__dirname, 'polyfills/babelHelpers.js'), ].concat(this._polyfillModuleNames); return polyfillModuleNames.map( (polyfillModuleName, idx) => this._depGraph.createPolyfill({ file: polyfillModuleName, id: polyfillModuleName, dependencies: polyfillModuleNames.slice(0, idx), }) ); } resolveRequires(resolutionResponse, module, code, dependencyOffsets = []) { const resolvedDeps = Object.create(null); // here, we build a map of all require strings (relative and absolute) // to the canonical ID of the module they reference resolutionResponse.getResolvedDependencyPairs(module) .forEach(([depName, depModule]) => { if (depModule) { resolvedDeps[depName] = this._getModuleId(depModule); } }); // if we have a canonical ID for the module imported here, // we use it, so that require() is always called with the same // id for every module. // Example: // -- in a/b.js: // require('./c') => require(3); // -- in b/index.js: // require('../a/c') => require(3); const replaceModuleId = (codeMatch, quote, depName) => depName in resolvedDeps ? `${JSON.stringify(resolvedDeps[depName])} /* ${depName} */` : codeMatch; code = dependencyOffsets.reduceRight((codeBits, offset) => { const first = codeBits.shift(); codeBits.unshift( first.slice(0, offset), first.slice(offset).replace(/(['"])([^'"']*)\1/, replaceModuleId), ); return codeBits; }, [code]); return code.join(''); } wrapModule({ resolutionResponse, module, name, map, code, meta = {}, minify = false }) { if (module.isJSON()) { code = `module.exports = ${code}`; } if (module.isPolyfill()) { code = definePolyfillCode(code); } else { const moduleId = this._getModuleId(module); code = this.resolveRequires( resolutionResponse, module, code, meta.dependencyOffsets ); code = defineModuleCode(moduleId, code, name); } return minify ? this._minifyCode(module.path, code, map) : Promise.resolve({code, map}); } minifyModule({path, code, map}) { return this._minifyCode(path, code, map); } getDebugInfo() { return this._depGraph.getDebugInfo(); } } function defineModuleCode(moduleName, code, verboseName = '') { return [ `__d(`, `${JSON.stringify(moduleName)} /* ${verboseName} */, `, `function(global, require, module, exports) {`, `${code}`, '\n});', ].join(''); } function definePolyfillCode(code,) { return [ `(function(global) {`, code, `\n})(typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : this);`, ].join(''); } module.exports = Resolver;