diff --git a/jest/preprocessor.js b/jest/preprocessor.js index 74a5740160d0e0..b1a71d7b0a0a3e 100644 --- a/jest/preprocessor.js +++ b/jest/preprocessor.js @@ -13,17 +13,14 @@ const babel = require('babel-core'); const babelRegisterOnly = require('metro-bundler/build/babelRegisterOnly'); const createCacheKeyFunction = require('fbjs-scripts/jest/createCacheKeyFunction'); -const path = require('path'); +const transformer = require('metro-bundler/build/transformer.js'); const nodeFiles = RegExp([ '/local-cli/', - '/packager/(?!src/Resolver/polyfills/)', ].join('|')); const nodeOptions = babelRegisterOnly.config([nodeFiles]); babelRegisterOnly([]); -// has to be required after setting up babelRegisterOnly -const transformer = require('metro-bundler/build/transformer.js'); module.exports = { process(src/*: string*/, file/*: string*/) { @@ -49,7 +46,7 @@ module.exports = { getCacheKey: createCacheKeyFunction([ __filename, - path.join(__dirname, '../packager/src/transformer.js'), + require.resolve('metro-bundler/build/transformer.js'), require.resolve('babel-core/package.json'), ]), }; diff --git a/jest/setup.js b/jest/setup.js index 669be550aba433..d709b19f90d56a 100644 --- a/jest/setup.js +++ b/jest/setup.js @@ -10,9 +10,9 @@ const mockComponent = require.requireActual('./mockComponent'); -require.requireActual('../packager/src/Resolver/polyfills/babelHelpers.js'); -require.requireActual('../packager/src/Resolver/polyfills/Object.es7.js'); -require.requireActual('../packager/src/Resolver/polyfills/error-guard'); +require.requireActual('metro-bundler/build/Resolver/polyfills/babelHelpers.js'); +require.requireActual('metro-bundler/build/Resolver/polyfills/Object.es7.js'); +require.requireActual('metro-bundler/build/Resolver/polyfills/error-guard'); global.__DEV__ = true; diff --git a/local-cli/link/__tests__/ios/writePlist.spec.js b/local-cli/link/__tests__/ios/writePlist.spec.js index a109d56d603a98..d95ee511e29f33 100644 --- a/local-cli/link/__tests__/ios/writePlist.spec.js +++ b/local-cli/link/__tests__/ios/writePlist.spec.js @@ -1,3 +1,12 @@ +/** + * Copyright (c) 2013-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'; jest.autoMockOff(); @@ -16,11 +25,7 @@ const writePlist = require('../../ios/writePlist'); const projectPath = path.join(__dirname, '../../__fixtures__/project.pbxproj'); const infoPlistPath = path.join(__dirname, '../../__fixtures__/Info.plist'); -fs.__setMockFilesystem({ - 'Basic': { - 'project.pbxproj': readFileSync(projectPath).toString(), - } -}); +fs.readFileSync = jest.fn(() => readFileSync(projectPath).toString()); const project = xcode.project('/Basic/project.pbxproj'); diff --git a/packager/.eslintrc b/packager/.eslintrc deleted file mode 100644 index 6f2bcc4db65a72..00000000000000 --- a/packager/.eslintrc +++ /dev/null @@ -1,10 +0,0 @@ -{ - "rules": { - "extra-arrow-initializer": 0, - "no-alert": 0, - "no-console-disallow": 0 - }, - "env": { - "node": true - } -} diff --git a/packager/Glossary.md b/packager/Glossary.md deleted file mode 100644 index 564db6466647d1..00000000000000 --- a/packager/Glossary.md +++ /dev/null @@ -1,22 +0,0 @@ -Glossary -=== - -Terminology commonly used in React Native Packager / Metro Bundler is explained -here. This document is work in progress, please help completing it. - -## Build Root - -Configuration files (`rn-cli.config.js`) support configuring one or more roots -that are watched for file changes during development. In the context of the -integration with the `js_*` rule family in [Buck][], there is only a single root, -the build root used by Buck. - - -## Local Path - -A *local path* / `localPath` is the path to a file relative to a -[*build root*](#build-root). - - - -[Buck]: http://buckbuild.com/ diff --git a/packager/README.md b/packager/README.md deleted file mode 100644 index 08a093b3eab2d8..00000000000000 --- a/packager/README.md +++ /dev/null @@ -1,9 +0,0 @@ -# metro-bundler - -πŸš‡ The JavaScript bundler for React Native. - -- **πŸš… Fast**: We aim for sub-second reload cycles, fast startup and quick bundling speeds. -- **βš–οΈ Scalable**: Works with thousands of modules in a single application. -- **βš›οΈ Integrated**: Supports every React Native project out of the box. - -This project was previously part of the [react-native](https://github.com/facebook/react-native) repository. In this smaller repository it is easier for the team working on Metro Bundler to respond to both issues and pull requests. See [react-native#13976](https://github.com/facebook/react-native/issues/13976) for the initial announcement. diff --git a/packager/package.json b/packager/package.json deleted file mode 100644 index d3c85b23daa39e..00000000000000 --- a/packager/package.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "version": "0.7.0", - "name": "metro-bundler", - "description": "πŸš‡ The JavaScript bundler for React Native.", - "main": "src/index.js", - "repository": { - "type": "git", - "url": "git@github.com:facebook/metro-bundler.git" - }, - "dependencies": { - "absolute-path": "^0.0.0", - "async": "^2.4.0", - "babel-core": "^6.24.1", - "babel-generator": "^6.24.1", - "babel-plugin-external-helpers": "^6.18.0", - "babel-preset-es2015-node": "^6.1.1", - "babel-preset-fbjs": "^2.1.0", - "babel-preset-react-native": "^1.9.1", - "babel-register": "^6.24.1", - "babylon": "^6.17.0", - "chalk": "^1.1.1", - "concat-stream": "^1.6.0", - "core-js": "^2.2.2", - "debug": "^2.2.0", - "denodeify": "^1.2.1", - "fbjs": "0.8.12", - "graceful-fs": "^4.1.3", - "image-size": "^0.3.5", - "jest-haste-map": "^20.0.4", - "json-stable-stringify": "^1.0.1", - "json5": "^0.4.0", - "left-pad": "^1.1.3", - "lodash": "^4.16.6", - "merge-stream": "^1.0.1", - "mime-types": "2.1.11", - "mkdirp": "^0.5.1", - "request": "^2.79.0", - "rimraf": "^2.5.4", - "source-map": "^0.5.6", - "temp": "0.8.3", - "throat": "^3.0.0", - "uglify-js": "2.7.5", - "write-file-atomic": "^1.2.0", - "xpipe": "^1.0.5" - } -} diff --git a/packager/rn-babelrc.json b/packager/rn-babelrc.json deleted file mode 100644 index bbc20ea981dbd1..00000000000000 --- a/packager/rn-babelrc.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "presets": [ "react-native" ], - "plugins": [] -} diff --git a/packager/src/AssetServer/__tests__/AssetServer-test.js b/packager/src/AssetServer/__tests__/AssetServer-test.js deleted file mode 100644 index 64692c49aad1bb..00000000000000 --- a/packager/src/AssetServer/__tests__/AssetServer-test.js +++ /dev/null @@ -1,301 +0,0 @@ -/** - * Copyright (c) 2013-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'; - -jest.disableAutomock(); - -jest.mock('fs'); - -const AssetServer = require('../'); -const crypto = require('crypto'); -const fs = require('fs'); - -const {objectContaining} = jasmine; - -describe('AssetServer', () => { - describe('assetServer.get', () => { - it('should work for the simple case', () => { - const server = new AssetServer({ - projectRoots: ['/root'], - assetExts: ['png'], - }); - - fs.__setMockFilesystem({ - 'root': { - imgs: { - 'b.png': 'b image', - 'b@2x.png': 'b2 image', - }, - }, - }); - - return Promise.all([ - server.get('imgs/b.png'), - server.get('imgs/b@1x.png'), - ]).then(resp => - resp.forEach(data => - expect(data).toBe('b image') - ) - ); - }); - - it('should work for the simple case with platform ext', () => { - const server = new AssetServer({ - projectRoots: ['/root'], - assetExts: ['png'], - }); - - fs.__setMockFilesystem({ - 'root': { - imgs: { - 'b.ios.png': 'b ios image', - 'b.android.png': 'b android image', - 'c.png': 'c general image', - 'c.android.png': 'c android image', - }, - }, - }); - - return Promise.all([ - server.get('imgs/b.png', 'ios').then( - data => expect(data).toBe('b ios image') - ), - server.get('imgs/b.png', 'android').then( - data => expect(data).toBe('b android image') - ), - server.get('imgs/c.png', 'android').then( - data => expect(data).toBe('c android image') - ), - server.get('imgs/c.png', 'ios').then( - data => expect(data).toBe('c general image') - ), - server.get('imgs/c.png').then( - data => expect(data).toBe('c general image') - ), - ]); - }); - - - it('should work for the simple case with jpg', () => { - const server = new AssetServer({ - projectRoots: ['/root'], - assetExts: ['png', 'jpg'], - }); - - fs.__setMockFilesystem({ - 'root': { - imgs: { - 'b.png': 'png image', - 'b.jpg': 'jpeg image', - }, - }, - }); - - return Promise.all([ - server.get('imgs/b.jpg'), - server.get('imgs/b.png'), - ]).then(data => - expect(data).toEqual([ - 'jpeg image', - 'png image', - ]) - ); - }); - - it('should pick the bigger one', () => { - const server = new AssetServer({ - projectRoots: ['/root'], - assetExts: ['png'], - }); - - fs.__setMockFilesystem({ - 'root': { - imgs: { - 'b@1x.png': 'b1 image', - 'b@2x.png': 'b2 image', - 'b@4x.png': 'b4 image', - 'b@4.5x.png': 'b4.5 image', - }, - }, - }); - - return server.get('imgs/b@3x.png').then(data => - expect(data).toBe('b4 image') - ); - }); - - it('should pick the bigger one with platform ext', () => { - const server = new AssetServer({ - projectRoots: ['/root'], - assetExts: ['png'], - }); - - fs.__setMockFilesystem({ - 'root': { - imgs: { - 'b@1x.png': 'b1 image', - 'b@2x.png': 'b2 image', - 'b@4x.png': 'b4 image', - 'b@4.5x.png': 'b4.5 image', - 'b@1x.ios.png': 'b1 ios image', - 'b@2x.ios.png': 'b2 ios image', - 'b@4x.ios.png': 'b4 ios image', - 'b@4.5x.ios.png': 'b4.5 ios image', - }, - }, - }); - - return Promise.all([ - server.get('imgs/b@3x.png').then(data => - expect(data).toBe('b4 image') - ), - server.get('imgs/b@3x.png', 'ios').then(data => - expect(data).toBe('b4 ios image') - ), - ]); - }); - - it('should support multiple project roots', () => { - const server = new AssetServer({ - projectRoots: ['/root', '/root2'], - assetExts: ['png'], - }); - - fs.__setMockFilesystem({ - 'root': { - imgs: { - 'b.png': 'b image', - }, - }, - 'root2': { - 'newImages': { - 'imgs': { - 'b@1x.png': 'b1 image', - }, - }, - }, - }); - - return server.get('newImages/imgs/b.png').then(data => - expect(data).toBe('b1 image') - ); - }); - }); - - describe('assetServer.getAssetData', () => { - it('should get assetData', () => { - const server = new AssetServer({ - projectRoots: ['/root'], - assetExts: ['png'], - }); - - fs.__setMockFilesystem({ - 'root': { - imgs: { - 'b@1x.png': 'b1 image', - 'b@2x.png': 'b2 image', - 'b@4x.png': 'b4 image', - 'b@4.5x.png': 'b4.5 image', - }, - }, - }); - - return server.getAssetData('imgs/b.png').then(data => { - expect(data).toEqual(objectContaining({ - type: 'png', - name: 'b', - scales: [1, 2, 4, 4.5], - files: [ - '/root/imgs/b@1x.png', - '/root/imgs/b@2x.png', - '/root/imgs/b@4x.png', - '/root/imgs/b@4.5x.png', - ], - })); - }); - }); - - it('should get assetData for non-png images', () => { - const server = new AssetServer({ - projectRoots: ['/root'], - assetExts: ['png', 'jpeg'], - }); - - fs.__setMockFilesystem({ - 'root': { - imgs: { - 'b@1x.jpg': 'b1 image', - 'b@2x.jpg': 'b2 image', - 'b@4x.jpg': 'b4 image', - 'b@4.5x.jpg': 'b4.5 image', - }, - }, - }); - - return server.getAssetData('imgs/b.jpg').then(data => { - expect(data).toEqual(objectContaining({ - type: 'jpg', - name: 'b', - scales: [1, 2, 4, 4.5], - files: [ - '/root/imgs/b@1x.jpg', - '/root/imgs/b@2x.jpg', - '/root/imgs/b@4x.jpg', - '/root/imgs/b@4.5x.jpg', - ], - })); - }); - }); - - describe('hash:', () => { - let server, mockFS; - beforeEach(() => { - server = new AssetServer({ - projectRoots: ['/root'], - assetExts: ['jpg'], - }); - - mockFS = { - 'root': { - imgs: { - 'b@1x.jpg': 'b1 image', - 'b@2x.jpg': 'b2 image', - 'b@4x.jpg': 'b4 image', - 'b@4.5x.jpg': 'b4.5 image', - }, - }, - }; - - fs.__setMockFilesystem(mockFS); - }); - - it('uses the file contents to build the hash', () => { - const hash = crypto.createHash('md5'); - for (const name in mockFS.root.imgs) { - hash.update(mockFS.root.imgs[name]); - } - - return server.getAssetData('imgs/b.jpg').then(data => - expect(data).toEqual(objectContaining({hash: hash.digest('hex')})) - ); - }); - - it('changes the hash when the passed-in file watcher emits an `all` event', () => { - return server.getAssetData('imgs/b.jpg').then(initialData => { - mockFS.root.imgs['b@4x.jpg'] = 'updated data'; - server.onFileChange('all', '/root/imgs/b@4x.jpg'); - return server.getAssetData('imgs/b.jpg').then(data => - expect(data.hash).not.toEqual(initialData.hash) - ); - }); - }); - }); - }); -}); diff --git a/packager/src/AssetServer/index.js b/packager/src/AssetServer/index.js deleted file mode 100644 index 0d5a62e8f4e0a8..00000000000000 --- a/packager/src/AssetServer/index.js +++ /dev/null @@ -1,253 +0,0 @@ -/** - * 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. - * - * @flow - */ - -'use strict'; - -const AssetPaths = require('../node-haste/lib/AssetPaths'); - -const crypto = require('crypto'); -const denodeify = require('denodeify'); -const fs = require('fs'); -const path = require('path'); - -import type {AssetData} from '../node-haste/lib/AssetPaths'; - -const stat = denodeify(fs.stat); -const readDir = denodeify(fs.readdir); -const readFile = denodeify(fs.readFile); - -class AssetServer { - - _roots: $ReadOnlyArray; - _assetExts: $ReadOnlyArray; - _hashes: Map; - _files: Map; - - constructor(options: {| - +assetExts: $ReadOnlyArray, - +projectRoots: $ReadOnlyArray, - |}) { - this._roots = options.projectRoots; - this._assetExts = options.assetExts; - this._hashes = new Map(); - this._files = new Map(); - } - - get(assetPath: string, platform: ?string = null): Promise { - const assetData = AssetPaths.parse( - assetPath, - new Set(platform != null ? [platform] : []), - ); - return this._getAssetRecord(assetPath, platform).then(record => { - for (let i = 0; i < record.scales.length; i++) { - if (record.scales[i] >= assetData.resolution) { - return readFile(record.files[i]); - } - } - - return readFile(record.files[record.files.length - 1]); - }); - } - - getAssetData(assetPath: string, platform: ?string = null): Promise<{| - files: Array, - hash: string, - name: string, - scales: Array, - type: string, - |}> { - const nameData = AssetPaths.parse( - assetPath, - new Set(platform != null ? [platform] : []), - ); - const {name, type} = nameData; - - return this._getAssetRecord(assetPath, platform).then(record => { - const {scales, files} = record; - - const hash = this._hashes.get(assetPath); - if (hash != null) { - return {files, hash, name, scales, type}; - } - - return new Promise((resolve, reject) => { - const hasher = crypto.createHash('md5'); - hashFiles(files.slice(), hasher, error => { - if (error) { - reject(error); - } else { - const freshHash = hasher.digest('hex'); - this._hashes.set(assetPath, freshHash); - files.forEach(f => this._files.set(f, assetPath)); - resolve({files, hash: freshHash, name, scales, type}); - } - }); - }); - }); - } - - onFileChange(type: string, filePath: string) { - this._hashes.delete(this._files.get(filePath)); - } - - /** - * Given a request for an image by path. That could contain a resolution - * postfix, we need to find that image (or the closest one to it's resolution) - * in one of the project roots: - * - * 1. We first parse the directory of the asset - * 2. We check to find a matching directory in one of the project roots - * 3. We then build a map of all assets and their scales in this directory - * 4. Then try to pick platform-specific asset records - * 5. Then pick the closest resolution (rounding up) to the requested one - */ - _getAssetRecord(assetPath: string, platform: ?string = null): Promise<{| - files: Array, - scales: Array, - |}> { - const filename = path.basename(assetPath); - - return ( - this._findRoot( - this._roots, - path.dirname(assetPath), - assetPath, - ) - .then(dir => Promise.all([ - dir, - readDir(dir), - ])) - .then(res => { - const dir = res[0]; - const files = res[1]; - const assetData = AssetPaths.parse( - filename, - new Set(platform != null ? [platform] : []), - ); - - const map = this._buildAssetMap(dir, files, platform); - - let record; - if (platform != null) { - record = map.get(getAssetKey(assetData.assetName, platform)) || - map.get(assetData.assetName); - } else { - record = map.get(assetData.assetName); - } - - if (!record) { - throw new Error( - /* $FlowFixMe: platform can be null */ - `Asset not found: ${assetPath} for platform: ${platform}` - ); - } - - return record; - }) - ); - } - - _findRoot(roots: $ReadOnlyArray, dir: string, debugInfoFile: string): Promise { - return Promise.all( - roots.map(root => { - const absRoot = path.resolve(root); - // important: we want to resolve root + dir - // to ensure the requested path doesn't traverse beyond root - const absPath = path.resolve(root, dir); - return stat(absPath).then(fstat => { - // keep asset requests from traversing files - // up from the root (e.g. ../../../etc/hosts) - if (!absPath.startsWith(absRoot)) { - return {path: absPath, isValid: false}; - } - return {path: absPath, isValid: fstat.isDirectory()}; - }, _ => { - return {path: absPath, isValid: false}; - }); - }) - ).then(stats => { - for (let i = 0; i < stats.length; i++) { - if (stats[i].isValid) { - return stats[i].path; - } - } - - const rootsString = roots.map(s => `'${s}'`).join(', '); - throw new Error( - `'${debugInfoFile}' could not be found, because '${dir}' is not a ` + - `subdirectory of any of the roots (${rootsString})`, - ); - }); - } - - _buildAssetMap(dir: string, files: $ReadOnlyArray, platform: ?string): Map, - scales: Array, - |}> { - const platforms = new Set(platform != null ? [platform] : []); - const assets = files.map(this._getAssetDataFromName.bind(this, platforms)); - const map = new Map(); - assets.forEach(function(asset, i) { - if (asset == null) { - return; - } - const file = files[i]; - const assetKey = getAssetKey(asset.assetName, asset.platform); - let record = map.get(assetKey); - if (!record) { - record = { - scales: [], - files: [], - }; - map.set(assetKey, record); - } - - let insertIndex; - const length = record.scales.length; - - for (insertIndex = 0; insertIndex < length; insertIndex++) { - if (asset.resolution < record.scales[insertIndex]) { - break; - } - } - record.scales.splice(insertIndex, 0, asset.resolution); - record.files.splice(insertIndex, 0, path.join(dir, file)); - }); - - return map; - } - - _getAssetDataFromName(platforms: Set, file: string): ?AssetData { - return AssetPaths.tryParse(file, platforms); - } -} - -function getAssetKey(assetName, platform) { - if (platform != null) { - return `${assetName} : ${platform}`; - } else { - return assetName; - } -} - -function hashFiles(files, hash, callback) { - if (!files.length) { - callback(null); - return; - } - - fs.createReadStream(files.shift()) - .on('data', data => hash.update(data)) - .once('end', () => hashFiles(files, hash, callback)) - .once('error', error => callback(error)); -} - -module.exports = AssetServer; diff --git a/packager/src/Bundler/Bundle.js b/packager/src/Bundler/Bundle.js deleted file mode 100644 index a8f24c89388c49..00000000000000 --- a/packager/src/Bundler/Bundle.js +++ /dev/null @@ -1,370 +0,0 @@ -/** - * 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. - * - * @flow - */ - -'use strict'; - -const BundleBase = require('./BundleBase'); -const ModuleTransport = require('../lib/ModuleTransport'); - -const _ = require('lodash'); -const crypto = require('crypto'); -const debug = require('debug')('RNP:Bundle'); -const invariant = require('fbjs/lib/invariant'); - -const {createRamBundleGroups} = require('./util'); -const {fromRawMappings} = require('./source-map'); -const {isMappingsMap} = require('../lib/SourceMap'); - -import type {IndexMap, MappingsMap, SourceMap} from '../lib/SourceMap'; -import type {GetSourceOptions, FinalizeOptions} from './BundleBase'; - -export type Unbundle = { - startupModules: Array<*>, - lazyModules: Array<*>, - groups: Map>, -}; - -type SourceMapFormat = 'undetermined' | 'indexed' | 'flattened'; - -const SOURCEMAPPING_URL = '\n\/\/# sourceMappingURL='; - -class Bundle extends BundleBase { - - _dev: boolean | void; - _inlineSourceMap: string | void; - _minify: boolean | void; - _numRequireCalls: number; - _ramBundle: Unbundle | null; - _ramGroups: ?Array; - _sourceMap: string | null; - _sourceMapFormat: SourceMapFormat; - _sourceMapUrl: ?string; - - constructor({sourceMapUrl, dev, minify, ramGroups}: { - sourceMapUrl: ?string, - dev?: boolean, - minify?: boolean, - ramGroups?: Array, - } = {}) { - super(); - this._sourceMap = null; - this._sourceMapFormat = 'undetermined'; - this._sourceMapUrl = sourceMapUrl; - this._numRequireCalls = 0; - this._dev = dev; - this._minify = minify; - - this._ramGroups = ramGroups; - this._ramBundle = null; // cached RAM Bundle - } - - addModule( - /** - * $FlowFixMe: this code is inherently incorrect, because it modifies the - * signature of the base class function "addModule". That means callsites - * using an instance typed as the base class would be broken. This must be - * refactored. - */ - resolver: {wrapModule: (options: any) => Promise<{code: any, map: any}>}, - resolutionResponse: mixed, - module: mixed, - /* $FlowFixMe: erroneous change of signature. */ - moduleTransport: ModuleTransport, - /* $FlowFixMe: erroneous change of signature. */ - ): Promise { - const index = super.addModule(moduleTransport); - return resolver.wrapModule({ - resolutionResponse, - module, - name: moduleTransport.name, - code: moduleTransport.code, - map: moduleTransport.map, - meta: moduleTransport.meta, - minify: this._minify, - dev: this._dev, - }).then(({code, map}) => { - // If we get a map from the transformer we'll switch to a mode - // were we're combining the source maps as opposed to - if (map) { - const usesRawMappings = isRawMappings(map); - - if (this._sourceMapFormat === 'undetermined') { - this._sourceMapFormat = usesRawMappings ? 'flattened' : 'indexed'; - } else if (usesRawMappings && this._sourceMapFormat === 'indexed') { - throw new Error( - `Got at least one module with a full source map, but ${ - moduleTransport.sourcePath} has raw mappings` - ); - } else if (!usesRawMappings && this._sourceMapFormat === 'flattened') { - throw new Error( - `Got at least one module with raw mappings, but ${ - moduleTransport.sourcePath} has a full source map` - ); - } - } - - this.replaceModuleAt( - index, new ModuleTransport({...moduleTransport, code, map})); - }); - } - - finalize(options: FinalizeOptions) { - options = options || {}; - if (options.runModule) { - /* $FlowFixMe: this is unsound, as nothing enforces runBeforeMainModule - * to be available if `runModule` is true. Refactor. */ - options.runBeforeMainModule.forEach(this._addRequireCall, this); - /* $FlowFixMe: this is unsound, as nothing enforces the module ID to have - * been set beforehand. */ - this._addRequireCall(this.getMainModuleId()); - } - - super.finalize(options); - } - - _addRequireCall(moduleId: string) { - const code = `;require(${JSON.stringify(moduleId)});`; - const name = 'require-' + moduleId; - super.addModule(new ModuleTransport({ - name, - id: -this._numRequireCalls - 1, - code, - virtual: true, - sourceCode: code, - sourcePath: name + '.js', - meta: {preloaded: true}, - })); - this._numRequireCalls += 1; - } - - _getInlineSourceMap(dev: ?boolean) { - if (this._inlineSourceMap == null) { - const sourceMap = this.getSourceMapString({excludeSource: true, dev}); - /*eslint-env node*/ - const encoded = new Buffer(sourceMap).toString('base64'); - this._inlineSourceMap = 'data:application/json;base64,' + encoded; - } - return this._inlineSourceMap; - } - - getSource(options: GetSourceOptions) { - this.assertFinalized(); - - options = options || {}; - - let source = super.getSource(options); - - if (options.inlineSourceMap) { - source += SOURCEMAPPING_URL + this._getInlineSourceMap(options.dev); - } else if (this._sourceMapUrl) { - source += SOURCEMAPPING_URL + this._sourceMapUrl; - } - - return source; - } - - getUnbundle(): Unbundle { - this.assertFinalized(); - if (!this._ramBundle) { - const modules = this.getModules().slice(); - - // separate modules we need to preload from the ones we don't - const [startupModules, lazyModules] = partition(modules, shouldPreload); - - const ramGroups = this._ramGroups; - let groups; - this._ramBundle = { - startupModules, - lazyModules, - get groups() { - if (!groups) { - groups = createRamBundleGroups(ramGroups || [], lazyModules, subtree); - } - return groups; - }, - }; - } - - return this._ramBundle; - } - - invalidateSource() { - debug('invalidating bundle'); - super.invalidateSource(); - this._sourceMap = null; - } - - /** - * Combine each of the sourcemaps multiple modules have into a single big - * one. This works well thanks to a neat trick defined on the sourcemap spec - * that makes use of of the `sections` field to combine sourcemaps by adding - * an offset. This is supported only by Chrome for now. - */ - _getCombinedSourceMaps(options: {excludeSource?: boolean}): IndexMap { - const result = { - version: 3, - file: this._getSourceMapFile(), - sections: [], - }; - - let line = 0; - this.getModules().forEach(module => { - invariant( - !Array.isArray(module.map), - `Unexpected raw mappings for ${module.sourcePath}`, - ); - let map: SourceMap = module.map == null || module.virtual - ? generateSourceMapForVirtualModule(module) - : module.map; - - - if (options.excludeSource && isMappingsMap(map)) { - map = {...map, sourcesContent: []}; - } - - result.sections.push({ - offset: {line, column: 0}, - map, - }); - line += module.code.split('\n').length; - }); - - return result; - } - - getSourceMap(options: {excludeSource?: boolean}): SourceMap { - this.assertFinalized(); - - return this._sourceMapFormat === 'indexed' - ? this._getCombinedSourceMaps(options) - : fromRawMappings(this.getModules()).toMap(); - } - - getSourceMapString(options: {excludeSource?: boolean}): string { - if (this._sourceMapFormat === 'indexed') { - return JSON.stringify(this.getSourceMap(options)); - } - - // The following code is an optimization specific to the development server: - // 1. generator.toSource() is faster than JSON.stringify(generator.toMap()). - // 2. caching the source map unless there are changes saves time in - // development settings. - let map = this._sourceMap; - if (map == null) { - debug('Start building flat source map'); - map = this._sourceMap = fromRawMappings(this.getModules()).toString(); - debug('End building flat source map'); - } else { - debug('Returning cached source map'); - } - return map; - } - - getEtag() { - /* $FlowFixMe: we must pass options, or rename the - * base `getSource` function, as it does not actually need options. */ - var eTag = crypto.createHash('md5').update(this.getSource()).digest('hex'); - return eTag; - } - - _getSourceMapFile() { - return this._sourceMapUrl - ? this._sourceMapUrl.replace('.map', '.bundle') - : 'bundle.js'; - } - - getJSModulePaths() { - return this.getModules() - // Filter out non-js files. Like images etc. - .filter(module => !module.virtual) - .map(module => module.sourcePath); - } - - getDebugInfo() { - return [ - /* $FlowFixMe: this is unsound as the module ID could be unset. */ - '

Main Module:

' + this.getMainModuleId() + '
', - '', - '

Module paths and transformed code:

', - this.getModules().map(function(m) { - return '

Path:

' + m.sourcePath + '

Source:

' + - '
'; - }).join('\n'), - ].join('\n'); - } - - setRamGroups(ramGroups: ?Array) { - this._ramGroups = ramGroups; - } -} - -function generateSourceMapForVirtualModule(module): MappingsMap { - // All lines map 1-to-1 - let mappings = 'AAAA;'; - - for (let i = 1; i < module.code.split('\n').length; i++) { - mappings += 'AACA;'; - } - - return { - version: 3, - sources: [module.sourcePath], - names: [], - mappings, - file: module.sourcePath, - sourcesContent: [module.sourceCode], - }; -} - -function shouldPreload({meta}) { - return meta && meta.preloaded; -} - -function partition(array, predicate) { - const included = []; - const excluded = []; - array.forEach(item => (predicate(item) ? included : excluded).push(item)); - return [included, excluded]; -} - -function * subtree( - moduleTransport: ModuleTransport, - moduleTransportsByPath: Map, - seen = new Set(), -) { - seen.add(moduleTransport.id); - const {meta} = moduleTransport; - invariant( - meta != null, - 'Unexpected module transport without meta information: ' + moduleTransport.sourcePath, - ); - for (const [, {path}] of meta.dependencyPairs || []) { - const dependency = moduleTransportsByPath.get(path); - if (dependency && !seen.has(dependency.id)) { - yield dependency.id; - yield * subtree(dependency, moduleTransportsByPath, seen); - } - } -} - -const isRawMappings = Array.isArray; - -module.exports = Bundle; diff --git a/packager/src/Bundler/BundleBase.js b/packager/src/Bundler/BundleBase.js deleted file mode 100644 index 5ddd966da72c38..00000000000000 --- a/packager/src/Bundler/BundleBase.js +++ /dev/null @@ -1,114 +0,0 @@ -/** - * 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. - * - * @flow - */ -'use strict'; - -const ModuleTransport = require('../lib/ModuleTransport'); - -export type FinalizeOptions = { - allowUpdates?: boolean, - runBeforeMainModule?: Array, - runModule?: boolean, -}; - -export type GetSourceOptions = { - inlineSourceMap?: boolean, - dev: boolean, -}; - -class BundleBase { - - _assets: Array; - _finalized: boolean; - _mainModuleId: number | void; - _source: ?string; - __modules: Array; - - constructor() { - this._finalized = false; - this.__modules = []; - this._assets = []; - this._mainModuleId = undefined; - } - - isEmpty() { - return this.__modules.length === 0 && this._assets.length === 0; - } - - getMainModuleId() { - return this._mainModuleId; - } - - setMainModuleId(moduleId: number) { - this._mainModuleId = moduleId; - } - - addModule(module: ModuleTransport) { - if (!(module instanceof ModuleTransport)) { - throw new Error('Expected a ModuleTransport object'); - } - - return this.__modules.push(module) - 1; - } - - replaceModuleAt(index: number, module: ModuleTransport) { - if (!(module instanceof ModuleTransport)) { - throw new Error('Expeceted a ModuleTransport object'); - } - - this.__modules[index] = module; - } - - getModules() { - return this.__modules; - } - - getAssets() { - return this._assets; - } - - addAsset(asset: mixed) { - this._assets.push(asset); - } - - finalize(options: FinalizeOptions) { - if (!options.allowUpdates) { - Object.freeze(this.__modules); - Object.freeze(this._assets); - } - - this._finalized = true; - } - - getSource(options: GetSourceOptions) { - this.assertFinalized(); - - if (this._source) { - return this._source; - } - - this._source = this.__modules.map(module => module.code).join('\n'); - return this._source; - } - - invalidateSource() { - this._source = null; - } - - assertFinalized(message?: string) { - if (!this._finalized) { - throw new Error(message || 'Bundle needs to be finalized before getting any source'); - } - } - - setRamGroups(ramGroups: Array) {} -} - -module.exports = BundleBase; diff --git a/packager/src/Bundler/HMRBundle.js b/packager/src/Bundler/HMRBundle.js deleted file mode 100644 index db3b0ac3a4efd1..00000000000000 --- a/packager/src/Bundler/HMRBundle.js +++ /dev/null @@ -1,88 +0,0 @@ -/** - * 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. - * - * @flow - * @format - */ - -'use strict'; - -const BundleBase = require('./BundleBase'); -const ModuleTransport = require('../lib/ModuleTransport'); - -import type Resolver from '../Resolver'; -import type ResolutionResponse - from '../node-haste/DependencyGraph/ResolutionResponse'; -import type Module from '../node-haste/Module'; - -class HMRBundle extends BundleBase { - _sourceMappingURLFn: (hmrpath: string) => mixed; - _sourceMappingURLs: Array; - _sourceURLFn: (hmrpath: string) => mixed; - _sourceURLs: Array; - - constructor({ - sourceURLFn, - sourceMappingURLFn, - }: { - sourceURLFn: (hmrpath: string) => mixed, - sourceMappingURLFn: (hmrpath: string) => mixed, - }) { - super(); - this._sourceURLFn = sourceURLFn; - this._sourceMappingURLFn = sourceMappingURLFn; - this._sourceURLs = []; - this._sourceMappingURLs = []; - } - - addModule( - /* $FlowFixMe: broken OOP design: function signature should be the same */ - resolver: Resolver, - /* $FlowFixMe: broken OOP design: function signature should be the same */ - response: ResolutionResponse, - /* $FlowFixMe: broken OOP design: function signature should be the same */ - module: Module, - /* $FlowFixMe: broken OOP design: function signature should be the same */ - moduleTransport: ModuleTransport, - ) { - const code = resolver.resolveRequires( - response, - module, - moduleTransport.code, - /* $FlowFixMe: may not exist */ - moduleTransport.meta.dependencyOffsets, - ); - - super.addModule(new ModuleTransport({...moduleTransport, code})); - this._sourceMappingURLs.push( - this._sourceMappingURLFn(moduleTransport.sourcePath), - ); - this._sourceURLs.push(this._sourceURLFn(moduleTransport.sourcePath)); - // inconsistent with parent class return type - return (Promise.resolve(): any); - } - - getModulesIdsAndCode(): Array<{id: string, code: string}> { - return this.__modules.map(module => { - return { - id: JSON.stringify(module.id), - code: module.code, - }; - }); - } - - getSourceURLs() { - return this._sourceURLs; - } - - getSourceMappingURLs() { - return this._sourceMappingURLs; - } -} - -module.exports = HMRBundle; diff --git a/packager/src/Bundler/__tests__/Bundle-test.js b/packager/src/Bundler/__tests__/Bundle-test.js deleted file mode 100644 index aebe7ffc8bfa04..00000000000000 --- a/packager/src/Bundler/__tests__/Bundle-test.js +++ /dev/null @@ -1,494 +0,0 @@ -/** - * 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. - * - * @format - */ -'use strict'; - -jest.disableAutomock(); - -const Bundle = require('../Bundle'); -const ModuleTransport = require('../../lib/ModuleTransport'); -const crypto = require('crypto'); - -describe('Bundle', () => { - var bundle; - - beforeEach(() => { - bundle = new Bundle({sourceMapUrl: 'test_url'}); - bundle.getSourceMap = jest.fn(() => { - return 'test-source-map'; - }); - }); - - describe('source bundle', () => { - it('should create a bundle and get the source', () => { - return Promise.resolve() - .then(() => { - return addModule({ - bundle, - code: 'transformed foo;', - sourceCode: 'source foo', - sourcePath: 'foo path', - }); - }) - .then(() => { - return addModule({ - bundle, - code: 'transformed bar;', - sourceCode: 'source bar', - sourcePath: 'bar path', - }); - }) - .then(() => { - bundle.finalize({}); - expect(bundle.getSource({dev: true})).toBe( - [ - 'transformed foo;', - 'transformed bar;', - '\/\/# sourceMappingURL=test_url', - ].join('\n'), - ); - }); - }); - - it('should be ok to leave out the source map url', () => { - const otherBundle = new Bundle(); - return Promise.resolve() - .then(() => { - return addModule({ - bundle: otherBundle, - code: 'transformed foo;', - sourceCode: 'source foo', - sourcePath: 'foo path', - }); - }) - .then(() => { - return addModule({ - bundle: otherBundle, - code: 'transformed bar;', - sourceCode: 'source bar', - sourcePath: 'bar path', - }); - }) - .then(() => { - otherBundle.finalize({}); - expect(otherBundle.getSource({dev: true})).toBe( - ['transformed foo;', 'transformed bar;'].join('\n'), - ); - }); - }); - - it('should create a bundle and add run module code', () => { - return Promise.resolve() - .then(() => { - return addModule({ - bundle, - code: 'transformed foo;', - sourceCode: 'source foo', - sourcePath: 'foo path', - }); - }) - .then(() => { - return addModule({ - bundle, - code: 'transformed bar;', - sourceCode: 'source bar', - sourcePath: 'bar path', - }); - }) - .then(() => { - bundle.setMainModuleId('foo'); - bundle.finalize({ - runBeforeMainModule: ['bar'], - runModule: true, - }); - expect(bundle.getSource({dev: true})).toBe( - [ - 'transformed foo;', - 'transformed bar;', - ';require("bar");', - ';require("foo");', - '\/\/# sourceMappingURL=test_url', - ].join('\n'), - ); - }); - }); - - it('inserts modules in a deterministic order, independent of timing of the wrapper process', () => { - const moduleTransports = [ - createModuleTransport({name: 'module1'}), - createModuleTransport({name: 'module2'}), - createModuleTransport({name: 'module3'}), - ]; - - const resolves = {}; - const resolver = { - wrapModule({name}) { - return new Promise(resolve => { - resolves[name] = resolve; - }); - }, - }; - - const promise = Promise.all( - moduleTransports.map(m => - bundle.addModule(resolver, null, {isPolyfill: () => false}, m), - ), - ).then(() => { - expect(bundle.getModules()).toEqual(moduleTransports); - }); - - resolves.module2({code: ''}); - resolves.module3({code: ''}); - resolves.module1({code: ''}); - - return promise; - }); - }); - - describe('sourcemap bundle', () => { - it('should create sourcemap', () => { - //TODO: #15357872 add a meaningful test here - }); - - it('should combine sourcemaps', () => { - const otherBundle = new Bundle({sourceMapUrl: 'test_url'}); - - return Promise.resolve() - .then(() => { - return addModule({ - bundle: otherBundle, - code: 'transformed foo;\n', - sourceCode: 'source foo', - map: {name: 'sourcemap foo'}, - sourcePath: 'foo path', - }); - }) - .then(() => { - return addModule({ - bundle: otherBundle, - code: 'transformed bar;\n', - sourceCode: 'source bar', - map: {name: 'sourcemap bar'}, - sourcePath: 'bar path', - }); - }) - .then(() => { - return addModule({ - bundle: otherBundle, - code: 'image module;\nimage module;', - virtual: true, - sourceCode: 'image module;\nimage module;', - sourcePath: 'image.png', - }); - }) - .then(() => { - otherBundle.setMainModuleId('foo'); - otherBundle.finalize({ - runBeforeMainModule: ['InitializeCore'], - runModule: true, - }); - - const sourceMap = otherBundle.getSourceMap({dev: true}); - expect(sourceMap).toEqual({ - file: 'test_url', - version: 3, - sections: [ - {offset: {line: 0, column: 0}, map: {name: 'sourcemap foo'}}, - {offset: {line: 2, column: 0}, map: {name: 'sourcemap bar'}}, - { - offset: { - column: 0, - line: 4, - }, - map: { - file: 'image.png', - mappings: 'AAAA;AACA;', - names: [], - sources: ['image.png'], - sourcesContent: ['image module;\nimage module;'], - version: 3, - }, - }, - { - offset: { - column: 0, - line: 6, - }, - map: { - file: 'require-InitializeCore.js', - mappings: 'AAAA;', - names: [], - sources: ['require-InitializeCore.js'], - sourcesContent: [';require("InitializeCore");'], - version: 3, - }, - }, - { - offset: { - column: 0, - line: 7, - }, - map: { - file: 'require-foo.js', - mappings: 'AAAA;', - names: [], - sources: ['require-foo.js'], - sourcesContent: [';require("foo");'], - version: 3, - }, - }, - ], - }); - }); - }); - }); - - describe('getAssets()', () => { - it('should save and return asset objects', () => { - var p = new Bundle({sourceMapUrl: 'test_url'}); - var asset1 = {}; - var asset2 = {}; - p.addAsset(asset1); - p.addAsset(asset2); - p.finalize(); - expect(p.getAssets()).toEqual([asset1, asset2]); - }); - }); - - describe('getJSModulePaths()', () => { - it('should return module paths', () => { - var otherBundle = new Bundle({sourceMapUrl: 'test_url'}); - return Promise.resolve() - .then(() => { - return addModule({ - bundle: otherBundle, - code: 'transformed foo;\n', - sourceCode: 'source foo', - sourcePath: 'foo path', - }); - }) - .then(() => { - return addModule({ - bundle: otherBundle, - code: 'image module;\nimage module;', - virtual: true, - sourceCode: 'image module;\nimage module;', - sourcePath: 'image.png', - }); - }) - .then(() => { - expect(otherBundle.getJSModulePaths()).toEqual(['foo path']); - }); - }); - }); - - describe('getEtag()', function() { - it('should return an etag', function() { - bundle.finalize({}); - var eTag = crypto - .createHash('md5') - .update(bundle.getSource()) - .digest('hex'); - expect(bundle.getEtag()).toEqual(eTag); - }); - }); - - describe('main module id:', function() { - it('can save a main module ID', function() { - const id = 'arbitrary module ID'; - bundle.setMainModuleId(id); - expect(bundle.getMainModuleId()).toEqual(id); - }); - }); - - describe('random access bundle groups:', () => { - let moduleTransports; - beforeEach(() => { - moduleTransports = [ - transport('Product1', ['React', 'Relay']), - transport('React', ['ReactFoo', 'ReactBar']), - transport('ReactFoo', ['invariant']), - transport('invariant', []), - transport('ReactBar', ['cx']), - transport('cx', []), - transport('OtherFramework', ['OtherFrameworkFoo', 'OtherFrameworkBar']), - transport('OtherFrameworkFoo', ['invariant']), - transport('OtherFrameworkBar', ['crc32']), - transport('crc32', ['OtherFrameworkBar']), - ]; - }); - - it('can create a single group', () => { - bundle = createBundle([fsLocation('React')]); - const {groups} = bundle.getUnbundle(); - expect(groups).toEqual( - new Map([ - [ - idFor('React'), - new Set(['ReactFoo', 'invariant', 'ReactBar', 'cx'].map(idFor)), - ], - ]), - ); - }); - - it('can create two groups', () => { - bundle = createBundle([fsLocation('ReactFoo'), fsLocation('ReactBar')]); - const {groups} = bundle.getUnbundle(); - expect(groups).toEqual( - new Map([ - [idFor('ReactFoo'), new Set([idFor('invariant')])], - [idFor('ReactBar'), new Set([idFor('cx')])], - ]), - ); - }); - - it('can handle circular dependencies', () => { - bundle = createBundle([fsLocation('OtherFramework')]); - const {groups} = bundle.getUnbundle(); - expect(groups).toEqual( - new Map([ - [ - idFor('OtherFramework'), - new Set( - [ - 'OtherFrameworkFoo', - 'invariant', - 'OtherFrameworkBar', - 'crc32', - ].map(idFor), - ), - ], - ]), - ); - }); - - it('omits modules that are contained by more than one group', () => { - bundle = createBundle([ - fsLocation('React'), - fsLocation('OtherFramework'), - ]); - expect(() => { - const {groups} = bundle.getUnbundle(); //eslint-disable-line no-unused-vars - }).toThrow( - new Error( - `Module ${fsLocation('invariant')} belongs to groups ${fsLocation('React')}` + - `, and ${fsLocation('OtherFramework')}. Ensure that each module is only part of one group.`, - ), - ); - }); - - it('ignores missing dependencies', () => { - bundle = createBundle([fsLocation('Product1')]); - const {groups} = bundle.getUnbundle(); - expect(groups).toEqual( - new Map([ - [ - idFor('Product1'), - new Set( - ['React', 'ReactFoo', 'invariant', 'ReactBar', 'cx'].map(idFor), - ), - ], - ]), - ); - }); - - it('throws for group roots that do not exist', () => { - bundle = createBundle([fsLocation('DoesNotExist')]); - expect(() => { - const {groups} = bundle.getUnbundle(); //eslint-disable-line no-unused-vars - }).toThrow( - new Error( - `Group root ${fsLocation('DoesNotExist')} is not part of the bundle`, - ), - ); - }); - - function idFor(name) { - const {map} = idFor; - if (!map) { - idFor.map = new Map([[name, 0]]); - idFor.next = 1; - return 0; - } - - if (map.has(name)) { - return map.get(name); - } - - const id = idFor.next++; - map.set(name, id); - return id; - } - function createBundle(ramGroups, options = {}) { - const b = new Bundle(Object.assign(options, {ramGroups})); - moduleTransports.forEach(t => addModule({bundle: b, ...t})); - b.finalize(); - return b; - } - function fsLocation(name) { - return `/fs/${name}.js`; - } - function module(name) { - return {path: fsLocation(name)}; - } - function transport(name, deps) { - return createModuleTransport({ - name, - id: idFor(name), - sourcePath: fsLocation(name), - meta: {dependencyPairs: deps.map(d => [d, module(d)])}, - }); - } - }); -}); - -function resolverFor(code, map) { - return { - wrapModule: () => Promise.resolve({code, map}), - }; -} - -function addModule({ - bundle, - code, - sourceCode, - sourcePath, - map, - virtual, - polyfill, - meta, - id = '', -}) { - return bundle.addModule( - resolverFor(code, map), - null, - {isPolyfill: () => polyfill}, - createModuleTransport({ - code, - sourceCode, - sourcePath, - id, - map, - meta, - virtual, - polyfill, - }), - ); -} - -function createModuleTransport(data) { - return new ModuleTransport({ - code: '', - sourceCode: '', - sourcePath: '', - id: 'id' in data ? data.id : '', - ...data, - }); -} diff --git a/packager/src/Bundler/__tests__/Bundler-test.js b/packager/src/Bundler/__tests__/Bundler-test.js deleted file mode 100644 index ef3ea89ca79cb7..00000000000000 --- a/packager/src/Bundler/__tests__/Bundler-test.js +++ /dev/null @@ -1,460 +0,0 @@ -/** - * 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'; - -jest.disableAutomock(); - -jest - .setMock('worker-farm', () => () => undefined) - .setMock('../../worker-farm', () => () => undefined) - .setMock('uglify-js') - .mock('image-size') - .mock('fs') - .mock('os') - .mock('assert') - .mock('progress') - .mock('../../node-haste/DependencyGraph') - .mock('../../JSTransformer') - .mock('../../Resolver') - .mock('../Bundle') - .mock('../HMRBundle') - .mock('../../Logger') - .mock('/path/to/transformer.js', () => ({}), {virtual: true}) - ; - -var Bundler = require('../'); -var Resolver = require('../../Resolver'); -var defaults = require('../../defaults'); -var sizeOf = require('image-size'); -var fs = require('fs'); -const os = require('os'); -const path = require('path'); - -const {any, objectContaining} = expect; - - -var commonOptions = { - allowBundleUpdates: false, - assetExts: defaults.assetExts, - cacheVersion: 'smth', - extraNodeModules: {}, - platforms: defaults.platforms, - resetCache: false, - sourceExts: defaults.sourceExts, - transformModulePath: '/path/to/transformer.js', - watch: false, -}; - -describe('Bundler', function() { - - function createModule({ - path, - id, - dependencies, - isAsset, - isJSON, - isPolyfill, - resolution, - }) { - return { - path, - resolution, - getDependencies: () => Promise.resolve(dependencies), - getName: () => Promise.resolve(id), - isJSON: () => isJSON, - isAsset: () => isAsset, - isPolyfill: () => isPolyfill, - read: () => ({ - code: 'arbitrary', - source: 'arbitrary', - }), - }; - } - - var getDependencies; - var getModuleSystemDependencies; - var bundler; - var assetServer; - var modules; - var projectRoots; - - beforeEach(function() { - os.cpus.mockReturnValue({length: 1}); - // local directory on purpose, because it should not actually write - // anything to the disk during a unit test! - os.tmpDir.mockReturnValue(path.join(__dirname)); - - getDependencies = jest.fn(); - getModuleSystemDependencies = jest.fn(); - projectRoots = ['/root']; - - Resolver.mockImplementation(function() { - return { - getDependencies, - getModuleSystemDependencies, - }; - }); - Resolver.load = jest.fn().mockImplementation(opts => Promise.resolve(new Resolver(opts))); - - fs.__setMockFilesystem({ - 'path': {'to': {'transformer.js': ''}}, - }); - - fs.statSync.mockImplementation(function() { - return { - isDirectory: () => true, - }; - }); - - fs.readFile.mockImplementation(function(file, callback) { - callback(null, '{"json":true}'); - }); - - assetServer = { - getAssetData: jest.fn(), - }; - - bundler = new Bundler({ - ...commonOptions, - projectRoots, - assetServer, - }); - - modules = [ - createModule({id: 'foo', path: '/root/foo.js', dependencies: []}), - createModule({id: 'bar', path: '/root/bar.js', dependencies: []}), - createModule({ - id: 'new_image.png', - path: '/root/img/new_image.png', - isAsset: true, - resolution: 2, - dependencies: [], - }), - createModule({ - id: 'package/file.json', - path: '/root/file.json', - isJSON: true, - dependencies: [], - }), - ]; - - getDependencies.mockImplementation((main, options, transformOptions) => - Promise.resolve({ - mainModuleId: 'foo', - dependencies: modules, - options: transformOptions, - getModuleId: () => 123, - getResolvedDependencyPairs: () => [], - }) - ); - - getModuleSystemDependencies.mockImplementation(function() { - return []; - }); - - sizeOf.mockImplementation(function(path, cb) { - cb(null, {width: 50, height: 100}); - }); - }); - - it('gets the list of dependencies from the resolver', function() { - const entryFile = '/root/foo.js'; - return bundler.getDependencies({entryFile, recursive: true}).then(() => - // jest calledWith does not support jasmine.any - expect(getDependencies.mock.calls[0].slice(0, -2)).toEqual([ - '/root/foo.js', - {dev: true, platform: undefined, recursive: true}, - { - preloadedModules: undefined, - ramGroups: undefined, - transformer: { - dev: true, - minify: false, - platform: undefined, - transform: { - dev: true, - generateSourceMaps: false, - hot: false, - inlineRequires: false, - platform: undefined, - projectRoot: projectRoots[0], - }, - }, - }, - ]) - ); - }); - - it('allows overriding the platforms array', () => { - expect(bundler._opts.platforms).toEqual(['ios', 'android', 'windows', 'web']); - const b = new Bundler({ - ...commonOptions, - projectRoots, - assetServer, - platforms: ['android', 'vr'], - }); - expect(b._opts.platforms).toEqual(['android', 'vr']); - }); - - describe('.bundle', () => { - const mockAsset = { - scales: [1, 2, 3], - files: [ - '/root/img/img.png', - '/root/img/img@2x.png', - '/root/img/img@3x.png', - ], - hash: 'i am a hash', - name: 'img', - type: 'png', - }; - - beforeEach(() => { - assetServer.getAssetData - .mockImplementation(() => Promise.resolve(mockAsset)); - }); - - it('creates a bundle', function() { - return bundler.bundle({ - entryFile: '/root/foo.js', - runBeforeMainModule: [], - runModule: true, - sourceMapUrl: 'source_map_url', - }).then(bundle => { - const ithAddedModule = i => bundle.addModule.mock.calls[i][2].path; - - expect(ithAddedModule(0)).toEqual('/root/foo.js'); - expect(ithAddedModule(1)).toEqual('/root/bar.js'); - expect(ithAddedModule(2)).toEqual('/root/img/new_image.png'); - expect(ithAddedModule(3)).toEqual('/root/file.json'); - - expect(bundle.finalize.mock.calls[0]).toEqual([{ - runModule: true, - runBeforeMainModule: [], - allowUpdates: false, - }]); - - expect(bundle.addAsset.mock.calls[0]).toEqual([{ - __packager_asset: true, - fileSystemLocation: '/root/img', - httpServerLocation: '/assets/img', - width: 50, - height: 100, - scales: [1, 2, 3], - files: [ - '/root/img/img.png', - '/root/img/img@2x.png', - '/root/img/img@3x.png', - ], - hash: 'i am a hash', - name: 'img', - type: 'png', - }]); - - // TODO(amasad) This fails with 0 != 5 in OSS - //expect(ProgressBar.prototype.tick.mock.calls.length).toEqual(modules.length); - }); - }); - - it('loads and runs asset plugins', function() { - jest.mock('mockPlugin1', () => { - return asset => { - asset.extraReverseHash = asset.hash.split('').reverse().join(''); - return asset; - }; - }, {virtual: true}); - - jest.mock('asyncMockPlugin2', () => { - return asset => { - expect(asset.extraReverseHash).toBeDefined(); - return new Promise(resolve => { - asset.extraPixelCount = asset.width * asset.height; - resolve(asset); - }); - }; - }, {virtual: true}); - - return bundler.bundle({ - entryFile: '/root/foo.js', - runBeforeMainModule: [], - runModule: true, - sourceMapUrl: 'source_map_url', - assetPlugins: ['mockPlugin1', 'asyncMockPlugin2'], - }).then(bundle => { - expect(bundle.addAsset.mock.calls[0]).toEqual([{ - __packager_asset: true, - fileSystemLocation: '/root/img', - httpServerLocation: '/assets/img', - width: 50, - height: 100, - scales: [1, 2, 3], - files: [ - '/root/img/img.png', - '/root/img/img@2x.png', - '/root/img/img@3x.png', - ], - hash: 'i am a hash', - name: 'img', - type: 'png', - extraReverseHash: 'hsah a ma i', - extraPixelCount: 5000, - }]); - }); - }); - - it('calls the module post-processing function', () => { - const postProcessModules = jest.fn().mockImplementation((ms, e) => ms); - - const b = new Bundler({ - ...commonOptions, - postProcessModules, - projectRoots, - assetServer, - }); - - const dev = false; - const minify = true; - const platform = 'arbitrary'; - - const entryFile = '/root/foo.js'; - return b.bundle({ - dev, - entryFile, - minify, - platform, - runBeforeMainModule: [], - runModule: true, - sourceMapUrl: 'source_map_url', - }).then(() => { - expect(postProcessModules) - .toBeCalledWith( - modules.map(x => objectContaining({ - name: any(String), - id: any(Number), - code: any(String), - sourceCode: any(String), - sourcePath: x.path, - meta: any(Object), - polyfill: !!x.isPolyfill(), - })), - entryFile, - {dev, minify, platform}, - ); - }); - }); - - it('respects the order of modules returned by the post-processing function', () => { - const postProcessModules = jest.fn().mockImplementation((ms, e) => ms.reverse()); - - const b = new Bundler({ - ...commonOptions, - postProcessModules, - projectRoots, - assetServer, - }); - - const entryFile = '/root/foo.js'; - return b.bundle({ - entryFile, - runBeforeMainModule: [], - runModule: true, - sourceMapUrl: 'source_map_url', - }).then(bundle => { - const ithAddedModule = i => bundle.addModule.mock.calls[i][2].path; - - [ - '/root/file.json', - '/root/img/new_image.png', - '/root/bar.js', - '/root/foo.js', - ].forEach((path, ix) => expect(ithAddedModule(ix)).toEqual(path)); - }); - }); - }); - - describe('.getOrderedDependencyPaths', () => { - beforeEach(() => { - assetServer.getAssetData.mockImplementation(function(relPath) { - if (relPath === 'img/new_image.png') { - return Promise.resolve({ - scales: [1, 2, 3], - files: [ - '/root/img/new_image.png', - '/root/img/new_image@2x.png', - '/root/img/new_image@3x.png', - ], - hash: 'i am a hash', - name: 'img', - type: 'png', - }); - } else if (relPath === 'img/new_image2.png') { - return Promise.resolve({ - scales: [1, 2, 3], - files: [ - '/root/img/new_image2.png', - '/root/img/new_image2@2x.png', - '/root/img/new_image2@3x.png', - ], - hash: 'i am a hash', - name: 'img', - type: 'png', - }); - } - - throw new Error('unknown image ' + relPath); - }); - }); - - it('should get the concrete list of all dependency files', () => { - modules.push( - createModule({ - id: 'new_image2.png', - path: '/root/img/new_image2.png', - isAsset: true, - resolution: 2, - dependencies: [], - }), - ); - - return bundler.getOrderedDependencyPaths('/root/foo.js', true) - .then(paths => expect(paths).toEqual([ - '/root/foo.js', - '/root/bar.js', - '/root/img/new_image.png', - '/root/img/new_image@2x.png', - '/root/img/new_image@3x.png', - '/root/file.json', - '/root/img/new_image2.png', - '/root/img/new_image2@2x.png', - '/root/img/new_image2@3x.png', - ])); - }); - - describe('number of workers', () => { - beforeEach(() => { - delete process.env.REACT_NATIVE_MAX_WORKERS; - }); - - afterEach(() => { - delete process.env.REACT_NATIVE_MAX_WORKERS; - }); - - it('return correct number of workers', () => { - os.cpus.mockReturnValue({length: 1}); - expect(Bundler.getMaxWorkerCount()).toBe(1); - os.cpus.mockReturnValue({length: 8}); - expect(Bundler.getMaxWorkerCount()).toBe(6); - os.cpus.mockReturnValue({length: 24}); - expect(Bundler.getMaxWorkerCount()).toBe(14); - process.env.REACT_NATIVE_MAX_WORKERS = 5; - expect(Bundler.getMaxWorkerCount()).toBe(5); - }); - }); - }); -}); diff --git a/packager/src/Bundler/index.js b/packager/src/Bundler/index.js deleted file mode 100644 index a0df0b54ab8b29..00000000000000 --- a/packager/src/Bundler/index.js +++ /dev/null @@ -1,873 +0,0 @@ -/** - * 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. - * - * @flow - */ - -'use strict'; - -const assert = require('assert'); -const crypto = require('crypto'); -const debug = require('debug')('RNP:Bundler'); -const emptyFunction = require('fbjs/lib/emptyFunction'); -const fs = require('fs'); -const Transformer = require('../JSTransformer'); -const Resolver = require('../Resolver'); -const Bundle = require('./Bundle'); -const HMRBundle = require('./HMRBundle'); -const ModuleTransport = require('../lib/ModuleTransport'); -const imageSize = require('image-size'); -const path = require('path'); -const denodeify = require('denodeify'); -const defaults = require('../defaults'); -const os = require('os'); -const invariant = require('fbjs/lib/invariant'); -const toLocalPath = require('../node-haste/lib/toLocalPath'); - -const {generateAssetTransformResult, isAssetTypeAnImage} = require('./util'); - -const { - sep: pathSeparator, - join: joinPath, - dirname: pathDirname, - extname, -} = require('path'); - -const VERSION = require('../../package.json').version; - -import type AssetServer from '../AssetServer'; -import type Module, {HasteImpl} from '../node-haste/Module'; -import type ResolutionResponse from '../node-haste/DependencyGraph/ResolutionResponse'; -import type {MappingsMap} from '../lib/SourceMap'; -import type {Options as JSTransformerOptions} from '../JSTransformer/worker'; -import type {Reporter} from '../lib/reporting'; -import type {TransformCache} from '../lib/TransformCaching'; -import type {GlobalTransformCache} from '../lib/GlobalTransformCache'; - -export type BundlingOptions = {| - +preloadedModules: ?{[string]: true} | false, - +ramGroups: ?Array, - +transformer: JSTransformerOptions, -|}; - -export type ExtraTransformOptions = { - +preloadedModules?: {[path: string]: true} | false, - +ramGroups?: Array, - +transform?: {+inlineRequires?: {+blacklist: {[string]: true}} | boolean}, -}; - -export type GetTransformOptionsOpts = {| - dev: boolean, - hot: boolean, - platform: ?string, -|}; - -export type GetTransformOptions = ( - mainModuleName: string, - options: GetTransformOptionsOpts, - getDependenciesOf: string => Promise>, -) => Promise; - -export type AssetDescriptor = { - +__packager_asset: boolean, - +httpServerLocation: string, - +width: ?number, - +height: ?number, - +scales: Array, - +hash: string, - +name: string, - +type: string, -}; - -export type ExtendedAssetDescriptor = AssetDescriptor & { - +fileSystemLocation: string, - +files: Array, -}; - -const sizeOf = denodeify(imageSize); - -const { - createActionStartEntry, - createActionEndEntry, - log, -} = require('../Logger'); - -export type PostProcessModulesOptions = {| - dev: boolean, - minify: boolean, - platform: string, -|}; - -export type PostProcessModules = ( - modules: Array, - entryFile: string, - options: PostProcessModulesOptions, -) => Array; - -export type PostMinifyProcess = ({ - code: string, - map: MappingsMap, -}) => {code: string, map: MappingsMap}; - -type Options = {| - +allowBundleUpdates: boolean, - +assetExts: Array, - +assetServer: AssetServer, - +blacklistRE?: RegExp, - +cacheVersion: string, - +extraNodeModules: {}, - +getTransformOptions?: GetTransformOptions, - +globalTransformCache: ?GlobalTransformCache, - +hasteImpl?: HasteImpl, - +platforms: Array, - +polyfillModuleNames: Array, - +postMinifyProcess: PostMinifyProcess, - +postProcessModules?: PostProcessModules, - +projectRoots: $ReadOnlyArray, - +providesModuleNodeModules?: Array, - +reporter: Reporter, - +resetCache: boolean, - +sourceExts: Array, - +transformCache: TransformCache, - +transformModulePath: string, - +transformTimeoutInterval: ?number, - +watch: boolean, - +workerPath: ?string, -|}; - -const {hasOwnProperty} = Object; - -class Bundler { - - _opts: Options; - _getModuleId: (opts: Module) => number; - _transformer: Transformer; - _resolverPromise: Promise; - _projectRoots: $ReadOnlyArray; - _assetServer: AssetServer; - _getTransformOptions: void | GetTransformOptions; - - constructor(opts: Options) { - this._opts = opts; - - opts.projectRoots.forEach(verifyRootExists); - - const transformModuleStr = fs.readFileSync(opts.transformModulePath); - const transformModuleHash = - crypto.createHash('sha1').update(transformModuleStr).digest('hex'); - - const stableProjectRoots = opts.projectRoots.map(p => { - return path.relative(path.join(__dirname, '../../../..'), p); - }); - - const cacheKeyParts = [ - 'react-packager-cache', - VERSION, - opts.cacheVersion, - stableProjectRoots.join(',').split(pathSeparator).join('-'), - transformModuleHash, - ]; - - this._getModuleId = createModuleIdFactory(); - - let getCacheKey = (options: mixed) => ''; - if (opts.transformModulePath) { - /* $FlowFixMe: dynamic requires prevent static typing :'( */ - const transformer = require(opts.transformModulePath); - if (typeof transformer.getCacheKey !== 'undefined') { - getCacheKey = transformer.getCacheKey; - } - } - - const transformCacheKey = crypto.createHash('sha1').update( - cacheKeyParts.join('$'), - ).digest('hex'); - - debug(`Using transform cache key "${transformCacheKey}"`); - - const maxWorkerCount = Bundler.getMaxWorkerCount(); - - this._transformer = new Transformer( - opts.transformModulePath, - maxWorkerCount, - { - stdoutChunk: chunk => opts.reporter.update({type: 'worker_stdout_chunk', chunk}), - stderrChunk: chunk => opts.reporter.update({type: 'worker_stderr_chunk', chunk}), - }, - opts.workerPath, - ); - - const getTransformCacheKey = options => { - return transformCacheKey + getCacheKey(options); - }; - - this._resolverPromise = Resolver.load({ - assetExts: opts.assetExts, - blacklistRE: opts.blacklistRE, - extraNodeModules: opts.extraNodeModules, - getTransformCacheKey, - globalTransformCache: opts.globalTransformCache, - hasteImpl: opts.hasteImpl, - maxWorkerCount, - minifyCode: this._transformer.minify, - postMinifyProcess: this._opts.postMinifyProcess, - platforms: new Set(opts.platforms), - polyfillModuleNames: opts.polyfillModuleNames, - projectRoots: opts.projectRoots, - providesModuleNodeModules: - opts.providesModuleNodeModules || defaults.providesModuleNodeModules, - reporter: opts.reporter, - resetCache: opts.resetCache, - sourceExts: opts.sourceExts, - transformCode: - (module, code, transformCodeOptions) => this._transformer.transformFile( - module.path, - module.localPath, - code, - transformCodeOptions, - ), - transformCache: opts.transformCache, - watch: opts.watch, - }); - - this._projectRoots = opts.projectRoots; - this._assetServer = opts.assetServer; - - this._getTransformOptions = opts.getTransformOptions; - } - - end() { - this._transformer.kill(); - return this._resolverPromise.then( - resolver => resolver.getDependencyGraph().getWatcher().end(), - ); - } - - bundle(options: { - dev: boolean, - minify: boolean, - unbundle: boolean, - sourceMapUrl: ?string, - }): Promise { - const {dev, minify, unbundle} = options; - return this._resolverPromise.then( - resolver => resolver.getModuleSystemDependencies({dev, unbundle}), - ).then(moduleSystemDeps => this._bundle({ - ...options, - bundle: new Bundle({dev, minify, sourceMapUrl: options.sourceMapUrl}), - moduleSystemDeps, - })); - } - - _sourceHMRURL(platform: ?string, hmrpath: string) { - return this._hmrURL( - '', - platform, - 'bundle', - hmrpath, - ); - } - - _sourceMappingHMRURL(platform: ?string, hmrpath: string) { - // Chrome expects `sourceURL` when eval'ing code - return this._hmrURL( - '\/\/# sourceURL=', - platform, - 'map', - hmrpath, - ); - } - - _hmrURL(prefix: string, platform: ?string, extensionOverride: string, filePath: string) { - const matchingRoot = this._projectRoots.find(root => filePath.startsWith(root)); - - if (!matchingRoot) { - throw new Error('No matching project root for ' + filePath); - } - - // Replaces '\' with '/' for Windows paths. - if (pathSeparator === '\\') { - filePath = filePath.replace(/\\/g, '/'); - } - - const extensionStart = filePath.lastIndexOf('.'); - const resource = filePath.substring( - matchingRoot.length, - extensionStart !== -1 ? extensionStart : undefined, - ); - - return ( - prefix + resource + - '.' + extensionOverride + '?' + - 'platform=' + (platform || '') + '&runModule=false&entryModuleOnly=true&hot=true' - ); - } - - hmrBundle(options: {platform: ?string}, host: string, port: number): Promise { - return this._bundle({ - ...options, - bundle: new HMRBundle({ - sourceURLFn: this._sourceHMRURL.bind(this, options.platform), - sourceMappingURLFn: this._sourceMappingHMRURL.bind( - this, - options.platform, - ), - }), - hot: true, - dev: true, - }); - } - - _bundle({ - assetPlugins, - bundle, - dev, - entryFile, - entryModuleOnly, - generateSourceMaps, - hot, - isolateModuleIDs, - minify, - moduleSystemDeps = [], - onProgress, - platform, - resolutionResponse, - runBeforeMainModule, - runModule, - unbundle, - }: { - assetPlugins?: Array, - bundle: Bundle | HMRBundle, - dev: boolean, - entryFile?: string, - entryModuleOnly?: boolean, - generateSourceMaps?: boolean, - hot?: boolean, - isolateModuleIDs?: boolean, - minify?: boolean, - moduleSystemDeps?: Array, - onProgress?: () => void, - platform?: ?string, - resolutionResponse?: ResolutionResponse, - runBeforeMainModule?: boolean, - runModule?: boolean, - unbundle?: boolean, - }) { - const onResolutionResponse = (response: ResolutionResponse) => { - /* $FlowFixMe: looks like ResolutionResponse is monkey-patched - * with `getModuleId`. */ - bundle.setMainModuleId(response.getModuleId(getMainModule(response))); - if (entryModuleOnly && entryFile) { - response.dependencies = response.dependencies.filter(module => - module.path.endsWith(entryFile || '') - ); - } else { - response.dependencies = moduleSystemDeps.concat(response.dependencies); - } - }; - const finalizeBundle = ({bundle: finalBundle, transformedModules, response, modulesByName}: { - bundle: Bundle, - transformedModules: Array<{module: Module, transformed: ModuleTransport}>, - response: ResolutionResponse, - modulesByName: {[name: string]: Module}, - }) => - this._resolverPromise.then(resolver => Promise.all( - transformedModules.map(({module, transformed}) => - finalBundle.addModule(resolver, response, module, transformed) - ) - )).then(() => { - const runBeforeMainModuleIds = Array.isArray(runBeforeMainModule) - ? runBeforeMainModule - .map(name => modulesByName[name]) - .filter(Boolean) - .map(response.getModuleId) - : undefined; - - finalBundle.finalize({ - runModule, - runBeforeMainModule: runBeforeMainModuleIds, - allowUpdates: this._opts.allowBundleUpdates, - }); - return finalBundle; - }); - - return this._buildBundle({ - entryFile, - dev, - minify, - platform, - bundle, - hot, - unbundle, - resolutionResponse, - onResolutionResponse, - finalizeBundle, - isolateModuleIDs, - generateSourceMaps, - assetPlugins, - onProgress, - }); - } - - _buildBundle({ - entryFile, - dev, - minify, - platform, - bundle, - hot, - unbundle, - resolutionResponse, - isolateModuleIDs, - generateSourceMaps, - assetPlugins, - onResolutionResponse = emptyFunction, - onModuleTransformed = emptyFunction, - finalizeBundle = emptyFunction, - onProgress = emptyFunction, - }: *) { - const transformingFilesLogEntry = - log(createActionStartEntry({ - action_name: 'Transforming files', - entry_point: entryFile, - environment: dev ? 'dev' : 'prod', - })); - - const modulesByName = Object.create(null); - - if (!resolutionResponse) { - resolutionResponse = this.getDependencies({ - entryFile, - dev, - platform, - hot, - onProgress, - minify, - isolateModuleIDs, - generateSourceMaps: unbundle || minify || generateSourceMaps, - }); - } - - return Promise.all( - [this._resolverPromise, resolutionResponse], - ).then(([resolver, response]) => { - bundle.setRamGroups(response.options.ramGroups); - - log(createActionEndEntry(transformingFilesLogEntry)); - onResolutionResponse(response); - - // get entry file complete path (`entryFile` is a local path, i.e. relative to roots) - let entryFilePath; - if (response.dependencies.length > 1) { // skip HMR requests - const numModuleSystemDependencies = - resolver.getModuleSystemDependencies({dev, unbundle}).length; - - const dependencyIndex = - (response.numPrependedDependencies || 0) + numModuleSystemDependencies; - - if (dependencyIndex in response.dependencies) { - entryFilePath = response.dependencies[dependencyIndex].path; - } - } - - const modulesByTransport: Map = new Map(); - const toModuleTransport: Module => Promise = - module => - this._toModuleTransport({ - module, - bundle, - entryFilePath, - assetPlugins, - options: response.options, - /* $FlowFixMe: `getModuleId` is monkey-patched */ - getModuleId: (response.getModuleId: () => number), - dependencyPairs: response.getResolvedDependencyPairs(module), - }).then(transformed => { - modulesByTransport.set(transformed, module); - modulesByName[transformed.name] = module; - onModuleTransformed({ - module, - response, - bundle, - transformed, - }); - return transformed; - }); - - const p = this._opts.postProcessModules; - const postProcess = p - ? modules => p(modules, entryFile, {dev, minify, platform}) - : null; - - return Promise.all(response.dependencies.map(toModuleTransport)) - .then(postProcess) - .then(moduleTransports => { - const transformedModules = moduleTransports.map(transformed => ({ - module: modulesByTransport.get(transformed), - transformed, - })); - return finalizeBundle({bundle, transformedModules, response, modulesByName}); - }).then(() => bundle); - }); - } - - getShallowDependencies({ - entryFile, - platform, - dev = true, - minify = !dev, - hot = false, - generateSourceMaps = false, - }: { - entryFile: string, - platform: ?string, - dev?: boolean, - minify?: boolean, - hot?: boolean, - generateSourceMaps?: boolean, - }): Promise> { - return this.getTransformOptions( - entryFile, - { - dev, - generateSourceMaps, - hot, - minify, - platform, - projectRoots: this._projectRoots, - }, - ).then(bundlingOptions => - this._resolverPromise.then(resolver => - resolver.getShallowDependencies(entryFile, bundlingOptions.transformer), - ) - ); - } - - getModuleForPath(entryFile: string): Promise { - return this._resolverPromise.then(resolver => resolver.getModuleForPath(entryFile)); - } - - async getDependencies({ - entryFile, - platform, - dev = true, - minify = !dev, - hot = false, - recursive = true, - generateSourceMaps = false, - isolateModuleIDs = false, - onProgress, - }: { - entryFile: string, - platform: ?string, - dev?: boolean, - minify?: boolean, - hot?: boolean, - recursive?: boolean, - generateSourceMaps?: boolean, - isolateModuleIDs?: boolean, - onProgress?: ?(finishedModules: number, totalModules: number) => mixed, - }): Promise> { - const bundlingOptions: BundlingOptions = await this.getTransformOptions( - entryFile, - { - dev, - platform, - hot, - generateSourceMaps, - minify, - projectRoots: this._projectRoots, - }, - ); - - const resolver = await this._resolverPromise; - const response = await resolver.getDependencies( - entryFile, - {dev, platform, recursive}, - bundlingOptions, - onProgress, - isolateModuleIDs ? createModuleIdFactory() : this._getModuleId, - ); - return response; - } - - getOrderedDependencyPaths({entryFile, dev, platform, minify, generateSourceMaps}: { - +entryFile: string, - +dev: boolean, - +platform: string, - +minify: boolean, - +generateSourceMaps: boolean, - }) { - return this.getDependencies({entryFile, dev, platform, minify, generateSourceMaps}).then( - ({dependencies}) => { - const ret = []; - const promises = []; - const placeHolder = {}; - dependencies.forEach(dep => { - if (dep.isAsset()) { - const localPath = toLocalPath( - this._projectRoots, - dep.path - ); - promises.push( - this._assetServer.getAssetData(localPath, platform) - ); - ret.push(placeHolder); - } else { - ret.push(dep.path); - } - }); - - return Promise.all(promises).then(assetsData => { - assetsData.forEach(({files}) => { - const index = ret.indexOf(placeHolder); - ret.splice(index, 1, ...files); - }); - return ret; - }); - } - ); - } - - _toModuleTransport({ - module, - bundle, - entryFilePath, - options, - getModuleId, - dependencyPairs, - assetPlugins, - }: { - module: Module, - bundle: Bundle, - entryFilePath: string, - options: BundlingOptions, - getModuleId: (module: Module) => number, - dependencyPairs: Array<[string, Module]>, - assetPlugins: Array, - }): Promise { - let moduleTransport; - const moduleId = getModuleId(module); - const transformOptions = options.transformer; - - if (module.isAsset()) { - moduleTransport = this._generateAssetModule( - bundle, module, moduleId, assetPlugins, transformOptions.platform); - } - - if (moduleTransport) { - return Promise.resolve(moduleTransport); - } - - return Promise.all([ - module.getName(), - module.read(transformOptions), - ]).then(( - [name, {code, dependencies, dependencyOffsets, map, source}] - ) => { - const {preloadedModules} = options; - const isPolyfill = module.isPolyfill(); - const preloaded = - module.path === entryFilePath || - isPolyfill || - preloadedModules && hasOwnProperty.call(preloadedModules, module.path); - - return new ModuleTransport({ - name, - id: moduleId, - code, - map, - meta: {dependencies, dependencyOffsets, preloaded, dependencyPairs}, - polyfill: isPolyfill, - sourceCode: source, - sourcePath: module.path, - }); - }); - } - - _generateAssetObjAndCode( - module: Module, - assetPlugins: Array, - platform: ?string = null, - ) { - const localPath = toLocalPath(this._projectRoots, module.path); - var assetUrlPath = joinPath('/assets', pathDirname(localPath)); - - // On Windows, change backslashes to slashes to get proper URL path from file path. - if (pathSeparator === '\\') { - assetUrlPath = assetUrlPath.replace(/\\/g, '/'); - } - - const isImage = isAssetTypeAnImage(extname(module.path).slice(1)); - - return this._assetServer.getAssetData(localPath, platform).then(assetData => { - return Promise.all([isImage ? sizeOf(assetData.files[0]) : null, assetData]); - }).then(res => { - const dimensions = res[0]; - const assetData = res[1]; - const scale = assetData.scales[0]; - const asset = { - __packager_asset: true, - fileSystemLocation: pathDirname(module.path), - httpServerLocation: assetUrlPath, - width: dimensions ? dimensions.width / scale : undefined, - height: dimensions ? dimensions.height / scale : undefined, - scales: assetData.scales, - files: assetData.files, - hash: assetData.hash, - name: assetData.name, - type: assetData.type, - }; - - return this._applyAssetPlugins(assetPlugins, asset); - }).then(asset => { - const {code, dependencies, dependencyOffsets} = generateAssetTransformResult(asset); - return { - asset, - code, - meta: {dependencies, dependencyOffsets, preloaded: null}, - }; - }); - } - - _applyAssetPlugins( - assetPlugins: Array, - asset: ExtendedAssetDescriptor, - ) { - if (!assetPlugins.length) { - return asset; - } - - const [currentAssetPlugin, ...remainingAssetPlugins] = assetPlugins; - /* $FlowFixMe: dynamic requires prevent static typing :'( */ - const assetPluginFunction = require(currentAssetPlugin); - const result = assetPluginFunction(asset); - - // If the plugin was an async function, wait for it to fulfill before - // applying the remaining plugins - if (typeof result.then === 'function') { - return result.then(resultAsset => - this._applyAssetPlugins(remainingAssetPlugins, resultAsset) - ); - } else { - return this._applyAssetPlugins(remainingAssetPlugins, result); - } - } - - _generateAssetModule( - bundle: Bundle, - module: Module, - moduleId: number, - assetPlugins: Array = [], - platform: ?string = null, - ) { - return Promise.all([ - module.getName(), - this._generateAssetObjAndCode(module, assetPlugins, platform), - ]).then(([name, {asset, code, meta}]) => { - bundle.addAsset(asset); - return new ModuleTransport({ - name, - id: moduleId, - code, - meta, - sourceCode: code, - sourcePath: module.path, - virtual: true, - }); - }); - } - - async getTransformOptions( - mainModuleName: string, - options: {| - dev: boolean, - generateSourceMaps: boolean, - hot: boolean, - minify: boolean, - platform: ?string, - projectRoots: $ReadOnlyArray, - |}, - ): Promise { - const getDependencies = (entryFile: string) => - this.getDependencies({...options, entryFile}) - .then(r => r.dependencies.map(d => d.path)); - - const {dev, hot, platform} = options; - const extraOptions: ExtraTransformOptions = this._getTransformOptions - ? await this._getTransformOptions(mainModuleName, {dev, hot, platform}, getDependencies) - : {}; - - const {transform = {}} = extraOptions; - - return { - transformer: { - dev, - minify: options.minify, - platform, - transform: { - dev, - generateSourceMaps: options.generateSourceMaps, - hot, - inlineRequires: transform.inlineRequires || false, - platform, - projectRoot: options.projectRoots[0], - }, - }, - preloadedModules: extraOptions.preloadedModules, - ramGroups: extraOptions.ramGroups, - }; - } - - getResolver(): Promise { - return this._resolverPromise; - } - - /** - * Unless overriden, we use a diminishing amount of workers per core, because - * using more and more of them does not scale much. Ex. 6 workers for 8 - * cores, or 14 workers for 24 cores. - */ - static getMaxWorkerCount() { - const cores = os.cpus().length; - const envStr = process.env.REACT_NATIVE_MAX_WORKERS; - if (envStr == null) { - return Math.max(1, Math.ceil(cores * (0.5 + 0.5 * Math.exp(-cores * 0.07)) - 1)); - } - const envCount = parseInt(process.env.REACT_NATIVE_MAX_WORKERS, 10); - invariant( - Number.isInteger(envCount), - 'environment variable `REACT_NATIVE_MAX_WORKERS` must be a valid integer', - ); - return Math.min(cores, envCount); - } - -} - -function verifyRootExists(root) { - // Verify that the root exists. - assert(fs.statSync(root).isDirectory(), 'Root has to be a valid directory'); -} - -function createModuleIdFactory() { - const fileToIdMap = Object.create(null); - let nextId = 0; - return ({path: modulePath}) => { - if (!(modulePath in fileToIdMap)) { - fileToIdMap[modulePath] = nextId; - nextId += 1; - } - return fileToIdMap[modulePath]; - }; -} - -function getMainModule({dependencies, numPrependedDependencies = 0}) { - return dependencies[numPrependedDependencies]; -} - -module.exports = Bundler; diff --git a/packager/src/Bundler/source-map/B64Builder.js b/packager/src/Bundler/source-map/B64Builder.js deleted file mode 100644 index a4d31f9d1ce1a7..00000000000000 --- a/packager/src/Bundler/source-map/B64Builder.js +++ /dev/null @@ -1,108 +0,0 @@ -/** - * 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. - * - * @flow - */ - -'use strict'; - -const encode = require('./encode'); - -const MAX_SEGMENT_LENGTH = 7; -const ONE_MEG = 1024 * 1024; -const COMMA = 0x2c; -const SEMICOLON = 0x3b; - -/** - * Efficient builder for base64 VLQ mappings strings. - * - * This class uses a buffer that is preallocated with one megabyte and is - * reallocated dynamically as needed, doubling its size. - * - * Encoding never creates any complex value types (strings, objects), and only - * writes character values to the buffer. - * - * For details about source map terminology and specification, check - * https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit - */ -class B64Builder { - buffer: Buffer; - pos: number; - hasSegment: boolean; - - constructor() { - this.buffer = new Buffer(ONE_MEG); - this.pos = 0; - this.hasSegment = false; - } - - /** - * Adds `n` markers for generated lines to the mappings. - */ - markLines(n: number) { - if (n < 1) { - return this; - } - this.hasSegment = false; - if (this.pos + n >= this.buffer.length) { - this._realloc(); - } - while (n--) { - this.buffer[this.pos++] = SEMICOLON; - } - return this; - } - - /** - * Starts a segment at the specified column offset in the current line. - */ - startSegment(column: number) { - if (this.hasSegment) { - this._writeByte(COMMA); - } else { - this.hasSegment = true; - } - - this.append(column); - return this; - } - - /** - * Appends a single number to the mappings. - */ - append(value: number) { - if (this.pos + MAX_SEGMENT_LENGTH >= this.buffer.length) { - this._realloc(); - } - - this.pos = encode(value, this.buffer, this.pos); - return this; - } - - /** - * Returns the string representation of the mappings. - */ - toString() { - return this.buffer.toString('ascii', 0, this.pos); - } - - _writeByte(byte: number) { - if (this.pos === this.buffer.length) { - this._realloc(); - } - this.buffer[this.pos++] = byte; - } - - _realloc() { - const {buffer} = this; - this.buffer = new Buffer(buffer.length * 2); - buffer.copy(this.buffer); - } -} - -module.exports = B64Builder; diff --git a/packager/src/Bundler/source-map/Generator.js b/packager/src/Bundler/source-map/Generator.js deleted file mode 100644 index cce6a1d2870563..00000000000000 --- a/packager/src/Bundler/source-map/Generator.js +++ /dev/null @@ -1,195 +0,0 @@ -/** - * 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. - * - * @flow - */ - -'use strict'; - -const B64Builder = require('./B64Builder'); - -import type {MappingsMap} from '../../lib/SourceMap'; - -/** - * Generates a source map from raw mappings. - * - * Raw mappings are a set of 2, 4, or five elements: - * - * - line and column number in the generated source - * - line and column number in the original source - * - symbol name in the original source - * - * Mappings have to be passed in the order appearance in the generated source. - */ -class Generator { - builder: B64Builder; - last: {| - generatedColumn: number, - generatedLine: number, - name: number, - source: number, - sourceColumn: number, - sourceLine: number, - |}; - names: IndexedSet; - source: number; - sources: Array; - sourcesContent: Array; - - constructor() { - this.builder = new B64Builder(); - this.last = { - generatedColumn: 0, - generatedLine: 1, // lines are passed in 1-indexed - name: 0, - source: 0, - sourceColumn: 0, - sourceLine: 1, - }; - this.names = new IndexedSet(); - this.source = -1; - this.sources = []; - this.sourcesContent = []; - } - - /** - * Mark the beginning of a new source file. - */ - startFile(file: string, code: string) { - this.source = this.sources.push(file) - 1; - this.sourcesContent.push(code); - } - - /** - * Mark the end of the current source file - */ - endFile() { - this.source = -1; - } - - /** - * Adds a mapping for generated code without a corresponding source location. - */ - addSimpleMapping(generatedLine: number, generatedColumn: number): void { - const last = this.last; - if (this.source === -1 || - generatedLine === last.generatedLine && - generatedColumn < last.generatedColumn || - generatedLine < last.generatedLine) { - const msg = this.source === -1 - ? 'Cannot add mapping before starting a file with `addFile()`' - : 'Mapping is for a position preceding an earlier mapping'; - throw new Error(msg); - } - - if (generatedLine > last.generatedLine) { - this.builder.markLines(generatedLine - last.generatedLine); - last.generatedLine = generatedLine; - last.generatedColumn = 0; - } - - this.builder.startSegment(generatedColumn - last.generatedColumn); - last.generatedColumn = generatedColumn; - } - - /** - * Adds a mapping for generated code with a corresponding source location. - */ - addSourceMapping( - generatedLine: number, - generatedColumn: number, - sourceLine: number, - sourceColumn: number, - ): void { - this.addSimpleMapping(generatedLine, generatedColumn); - - const last = this.last; - this.builder - .append(this.source - last.source) - .append(sourceLine - last.sourceLine) - .append(sourceColumn - last.sourceColumn); - - last.source = this.source; - last.sourceColumn = sourceColumn; - last.sourceLine = sourceLine; - } - - /** - * Adds a mapping for code with a corresponding source location + symbol name. - */ - addNamedSourceMapping( - generatedLine: number, - generatedColumn: number, - sourceLine: number, - sourceColumn: number, - name: string, - ): void { - this.addSourceMapping( - generatedLine, generatedColumn, sourceLine, sourceColumn); - - const last = this.last; - const nameIndex = this.names.indexFor(name); - this.builder.append(nameIndex - last.name); - last.name = nameIndex; - } - - /** - * Return the source map as object. - */ - toMap(file?: string): MappingsMap { - return { - version: 3, - file, - sources: this.sources.slice(), - sourcesContent: this.sourcesContent.slice(), - names: this.names.items(), - mappings: this.builder.toString(), - }; - } - - /** - * Return the source map as string. - * - * This is ~2.5x faster than calling `JSON.stringify(generator.toMap())` - */ - toString(file?: string): string { - return ('{' + - '"version":3,' + - (file ? `"file":${JSON.stringify(file)},` : '') + - `"sources":${JSON.stringify(this.sources)},` + - `"sourcesContent":${JSON.stringify(this.sourcesContent)},` + - `"names":${JSON.stringify(this.names.items())},` + - `"mappings":"${this.builder.toString()}"` + - '}'); - } -} - -class IndexedSet { - map: Map; - nextIndex: number; - - constructor() { - this.map = new Map(); - this.nextIndex = 0; - } - - indexFor(x: string) { - let index = this.map.get(x); - if (index == null) { - index = this.nextIndex++; - this.map.set(x, index); - } - return index; - } - - items() { - return Array.from(this.map.keys()); - } -} - -module.exports = Generator; diff --git a/packager/src/Bundler/source-map/__tests__/B64Builder-test.js b/packager/src/Bundler/source-map/__tests__/B64Builder-test.js deleted file mode 100644 index 4f62614ff1ac4a..00000000000000 --- a/packager/src/Bundler/source-map/__tests__/B64Builder-test.js +++ /dev/null @@ -1,126 +0,0 @@ -/** - * Copyright (c) 2017-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'; - -jest.disableAutomock(); - -const B64Builder = require('../B64Builder'); - -let builder; -beforeEach(() => { - builder = new B64Builder(); -}); - -it('exposes a fluent interface', () => { - expect(builder.markLines(0)).toBe(builder); - expect(builder.markLines(3)).toBe(builder); - expect(builder.startSegment()).toBe(builder); - expect(builder.append(4)).toBe(builder); -}); - -it('can create an empty string', () => { - expect(builder.toString()).toEqual(''); -}); - -it('can mark a new line in the generated code', () => { - builder.markLines(1); - expect(builder.toString()).toEqual(';'); -}); - -it('can mark multiple new lines in the generated code', () => { - builder.markLines(4); - expect(builder.toString()).toEqual(';;;;'); -}); - -it('can mark zero new lines in the generated code', () => { - builder.markLines(0); - expect(builder.toString()).toEqual(''); -}); - -it('does not add commas when just starting a segment', () => { - builder.startSegment(0); - expect(builder.toString()).toEqual('A'); -}); - -it('adds a comma when starting a segment after another segment', () => { - builder.startSegment(0); - builder.startSegment(1); - expect(builder.toString()).toEqual('A,C'); -}); - -it('does not add a comma when starting a segment after marking a line', () => { - builder.startSegment(0); - builder.markLines(1); - builder.startSegment(0); - expect(builder.toString()).toEqual('A;A'); -}); - -it('adds a comma when starting a segment after calling `markLines(0)`', () => { - builder.startSegment(0); - builder.markLines(0); - builder.startSegment(1); - expect(builder.toString()).toEqual('A,C'); -}); - -it('can append values that fit within 5 bits (including sign bit)', () => { - builder.append(0b1111); - builder.append(-0b1111); - expect(builder.toString()).toEqual('ef'); -}); - -it('can append values that fit within 10 bits (including sign bit)', () => { - builder.append(0b111100110); - builder.append(-0b110110011); - expect(builder.toString()).toEqual('senb'); -}); - -it('can append values that fit within 15 bits (including sign bit)', () => { - builder.append(0b10011111011001); - builder.append(-0b11001010001001); - expect(builder.toString()).toEqual('y9TzoZ'); -}); - -it('can append values that fit within 20 bits (including sign bit)', () => { - builder.append(0b1110010011101110110); - builder.append(-0b1011000010100100110); - expect(builder.toString()).toEqual('s3zctyiW'); -}); - -it('can append values that fit within 25 bits (including sign bit)', () => { - builder.append(0b100010001111011010110111); - builder.append(-0b100100111100001110101111); - expect(builder.toString()).toEqual('ur7jR/6hvS'); -}); - -it('can append values that fit within 30 bits (including sign bit)', () => { - builder.append(0b10001100100001101010001011111); - builder.append(-0b11111000011000111110011111101); - expect(builder.toString()).toEqual('+lqjyR7v+xhf'); -}); - -it('can append values that fit within 32 bits (including sign bit)', () => { - builder.append(0b1001100101000101001011111110011); - builder.append(-0b1101101101011000110011001110000); - expect(builder.toString()).toEqual('m/rq0sChnzx1tD'); -}); - -it('can handle multiple operations', () => { - builder - .markLines(3) - .startSegment(4) - .append(2) - .append(2) - .append(0) - .append(2345) - .startSegment(12) - .append(987543) - .markLines(1) - .startSegment(0); - expect(builder.toString()).toEqual(';;;IEEAyyE,Yu5o8B;A'); -}); diff --git a/packager/src/Bundler/source-map/__tests__/Generator-test.js b/packager/src/Bundler/source-map/__tests__/Generator-test.js deleted file mode 100644 index 0cad199969a607..00000000000000 --- a/packager/src/Bundler/source-map/__tests__/Generator-test.js +++ /dev/null @@ -1,113 +0,0 @@ -/** - * Copyright (c) 2017-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'; - -jest.disableAutomock(); - -const Generator = require('../Generator'); - -const {objectContaining} = expect; - -let generator; -beforeEach(() => { - generator = new Generator(); -}); - -it('adds file name and source code when starting a file', () => { - const file1 = 'just/a/file'; - const file2 = 'another/file'; - const source1 = 'var a = 1;'; - const source2 = 'var a = 2;'; - - generator.startFile(file1, source1); - generator.startFile(file2, source2); - - expect(generator.toMap()) - .toEqual(objectContaining({ - sources: [file1, file2], - sourcesContent: [source1, source2], - })); -}); - -it('throws when adding a mapping without starting a file', () => { - expect(() => generator.addSimpleMapping(1, 2)).toThrow(); -}); - -it('throws when adding a mapping after ending a file', () => { - generator.startFile('apples', 'pears'); - generator.endFile(); - expect(() => generator.addSimpleMapping(1, 2)).toThrow(); -}); - -it('can add a mapping for generated code without corresponding original source', () => { - generator.startFile('apples', 'pears'); - generator.addSimpleMapping(12, 87); - expect(generator.toMap()) - .toEqual(objectContaining({ - mappings: ';;;;;;;;;;;uF', - })); -}); - -it('can add a mapping with corresponding location in the original source', () => { - generator.startFile('apples', 'pears'); - generator.addSourceMapping(2, 3, 456, 7); - expect(generator.toMap()) - .toEqual(objectContaining({ - mappings: ';GAucO', - })); -}); - -it('can add a mapping with source location and symbol name', () => { - generator.startFile('apples', 'pears'); - generator.addNamedSourceMapping(9, 876, 54, 3, 'arbitrary'); - expect(generator.toMap()) - .toEqual(objectContaining({ - mappings: ';;;;;;;;42BAqDGA', - names: ['arbitrary'], - })); -}); - -describe('full map generation', () => { - beforeEach(() => { - generator.startFile('apples', 'pears'); - generator.addSimpleMapping(1, 2); - generator.addNamedSourceMapping(3, 4, 5, 6, 'plums'); - generator.endFile(); - generator.startFile('lemons', 'oranges'); - generator.addNamedSourceMapping(7, 8, 9, 10, 'tangerines'); - generator.addNamedSourceMapping(11, 12, 13, 14, 'tangerines'); - generator.addSimpleMapping(15, 16); - }); - - it('can add multiple mappings for each file', () => { - expect(generator.toMap()).toEqual({ - version: 3, - mappings: 'E;;IAIMA;;;;QCIIC;;;;YAIIA;;;;gB', - sources: ['apples', 'lemons'], - sourcesContent: ['pears', 'oranges'], - names: ['plums', 'tangerines'], - }); - }); - - it('can add a `file` property to the map', () => { - expect(generator.toMap('arbitrary')) - .toEqual(objectContaining({ - file: 'arbitrary', - })); - }); - - it('supports direct JSON serialization', () => { - expect(JSON.parse(generator.toString())).toEqual(generator.toMap()); - }); - - it('supports direct JSON serialization with a file name', () => { - const file = 'arbitrary/file'; - expect(JSON.parse(generator.toString(file))).toEqual(generator.toMap(file)); - }); -}); diff --git a/packager/src/Bundler/source-map/__tests__/source-map-test.js b/packager/src/Bundler/source-map/__tests__/source-map-test.js deleted file mode 100644 index fab0aab60db91b..00000000000000 --- a/packager/src/Bundler/source-map/__tests__/source-map-test.js +++ /dev/null @@ -1,85 +0,0 @@ - /** - * Copyright (c) 2017-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'; - -jest.disableAutomock(); - -const Generator = require('../Generator'); -const {compactMapping, fromRawMappings} = require('..'); - -describe('flattening mappings / compacting', () => { - it('flattens simple mappings', () => { - expect(compactMapping({generated: {line: 12, column: 34}})) - .toEqual([12, 34]); - }); - - it('flattens mappings with a source location', () => { - expect(compactMapping({ - generated: {column: 34, line: 12}, - original: {column: 78, line: 56}, - })).toEqual([12, 34, 56, 78]); - }); - - it('flattens mappings with a source location and a symbol name', () => { - expect(compactMapping({ - generated: {column: 34, line: 12}, - name: 'arbitrary', - original: {column: 78, line: 56}, - })).toEqual([12, 34, 56, 78, 'arbitrary']); - }); -}); - -describe('build map from raw mappings', () => { - it('returns a `Generator` instance', () => { - expect(fromRawMappings([])).toBeInstanceOf(Generator); - }); - - it('returns a working source map containing all mappings', () => { - const input = [{ - code: lines(11), - map: [ - [1, 2], - [3, 4, 5, 6, 'apples'], - [7, 8, 9, 10], - [11, 12, 13, 14, 'pears'], - ], - sourceCode: 'code1', - sourcePath: 'path1', - }, { - code: lines(3), - map: [ - [1, 2], - [3, 4, 15, 16, 'bananas'], - ], - sourceCode: 'code2', - sourcePath: 'path2', - }, { - code: lines(23), - map: [ - [11, 12], - [13, 14, 15, 16, 'bananas'], - [17, 18, 19, 110], - [21, 112, 113, 114, 'pears'], - ], - sourceCode: 'code3', - sourcePath: 'path3', - }]; - - expect(fromRawMappings(input).toMap()) - .toEqual({ - mappings: 'E;;IAIMA;;;;QAII;;;;YAIIC;E;;ICEEC;;;;;;;;;;;Y;;cCAAA;;;;kBAI8F;;;;gHA8FID', - names: ['apples', 'pears', 'bananas'], - sources: ['path1', 'path2', 'path3'], - sourcesContent: ['code1', 'code2', 'code3'], - version: 3, - }); - }); -}); - -const lines = n => Array(n).join('\n'); diff --git a/packager/src/Bundler/source-map/encode.js b/packager/src/Bundler/source-map/encode.js deleted file mode 100644 index cc05fa10308cd6..00000000000000 --- a/packager/src/Bundler/source-map/encode.js +++ /dev/null @@ -1,127 +0,0 @@ -/** - * 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. - * - * @flow - */ - -/** - * Copyright 2011 Mozilla Foundation and contributors - * Licensed under the New BSD license. See LICENSE or: - * http://opensource.org/licenses/BSD-3-Clause - * - * Based on the Base 64 VLQ implementation in Closure Compiler: - * https://git.io/vymuA - * - * Copyright 2011 The Closure Compiler Authors. All rights reserved. - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions are - * met: - * - * * Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * * Redistributions in binary form must reproduce the above - * copyright notice, this list of conditions and the following - * disclaimer in the documentation and/or other materials provided - * with the distribution. - * * Neither the name of Google Inc. nor the names of its - * contributors may be used to endorse or promote products derived - * from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS - * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT - * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR - * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT - * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, - * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY - * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE - * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - * - * @copyright - */ - -/* eslint-disable no-bitwise */ - -'use strict'; - -// A map of values to characters for the b64 encoding -const CHAR_MAP = [ - 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, - 0x49, 0x4a, 0x4b, 0x4c, 0x4d, 0x4e, 0x4f, 0x50, - 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, - 0x59, 0x5a, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, - 0x67, 0x68, 0x69, 0x6a, 0x6b, 0x6c, 0x6d, 0x6e, - 0x6f, 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, - 0x77, 0x78, 0x79, 0x7a, 0x30, 0x31, 0x32, 0x33, - 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x2b, 0x2f, -]; - -// A single base 64 digit can contain 6 bits of data. For the base 64 variable -// length quantities we use in the source map spec, the first bit is the sign, -// the next four bits are the actual value, and the 6th bit is the -// continuation bit. The continuation bit tells us whether there are more -// digits in this value following this digit. -// -// Continuation -// | Sign -// | | -// V V -// 101011 - -const VLQ_BASE_SHIFT = 5; - -// binary: 100000 -const VLQ_BASE = 1 << VLQ_BASE_SHIFT; - -// binary: 011111 -const VLQ_BASE_MASK = VLQ_BASE - 1; - -// binary: 100000 -const VLQ_CONTINUATION_BIT = VLQ_BASE; - -/** - * Converts from a two-complement value to a value where the sign bit is - * placed in the least significant bit. For example, as decimals: - * 1 becomes 2 (10 binary), -1 becomes 3 (11 binary) - * 2 becomes 4 (100 binary), -2 becomes 5 (101 binary) - */ -function toVLQSigned(value) { - return value < 0 - ? ((-value) << 1) + 1 - : (value << 1) + 0; -} - -/** - * Encodes a number to base64 VLQ format and appends it to the passed-in buffer - * - * DON'T USE COMPOUND OPERATORS (eg `>>>=`) ON `let`-DECLARED VARIABLES! - * V8 WILL DEOPTIMIZE THIS FUNCTION AND MAP CREATION WILL BE 25% SLOWER! - * - * DON'T ADD MORE COMMENTS TO THIS FUNCTION TO KEEP ITS LENGTH SHORT ENOUGH FOR - * V8 OPTIMIZATION! - */ -function encode(value: number, buffer: Buffer, position: number): number { - let vlq = toVLQSigned(value); - let digit; - do { - digit = vlq & VLQ_BASE_MASK; - vlq >>>= VLQ_BASE_SHIFT; - if (vlq > 0) { - // There are still more digits in this value, so we must make sure the - // continuation bit is marked. - digit |= VLQ_CONTINUATION_BIT; - } - buffer[position++] = CHAR_MAP[digit]; - } while (vlq > 0); - - return position; -} - -module.exports = encode; diff --git a/packager/src/Bundler/source-map/package.json b/packager/src/Bundler/source-map/package.json deleted file mode 100644 index be5a9ee426bf1f..00000000000000 --- a/packager/src/Bundler/source-map/package.json +++ /dev/null @@ -1 +0,0 @@ -{"main": "source-map.js"} diff --git a/packager/src/Bundler/source-map/source-map.js b/packager/src/Bundler/source-map/source-map.js deleted file mode 100644 index fcc2e9a395a951..00000000000000 --- a/packager/src/Bundler/source-map/source-map.js +++ /dev/null @@ -1,104 +0,0 @@ -/** - * 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. - * - * @flow - */ - -'use strict'; - -const Generator = require('./Generator'); - -import type ModuleTransport from '../../lib/ModuleTransport'; -import type {RawMapping as BabelRawMapping} from 'babel-generator'; - -type GeneratedCodeMapping = [number, number]; -type SourceMapping = [number, number, number, number]; -type SourceMappingWithName = [number, number, number, number, string]; - -export type RawMapping = - SourceMappingWithName | SourceMapping | GeneratedCodeMapping; - -/** - * Creates a source map from modules with "raw mappings", i.e. an array of - * tuples with either 2, 4, or 5 elements: - * generated line, generated column, source line, source line, symbol name. - */ -function fromRawMappings(modules: Array): Generator { - const generator = new Generator(); - let carryOver = 0; - - for (var j = 0, o = modules.length; j < o; ++j) { - var module = modules[j]; - var {code, map} = module; - - if (Array.isArray(map)) { - addMappingsForFile(generator, map, module, carryOver); - } else if (map != null) { - throw new Error( - `Unexpected module with full source map found: ${module.sourcePath}` - ); - } - - carryOver += countLines(code); - } - - return generator; -} - -function compactMapping(mapping: BabelRawMapping): RawMapping { - const {column, line} = mapping.generated; - const {name, original} = mapping; - - if (original == null) { - return [line, column]; - } - - if (typeof name !== 'string') { - return [line, column, original.line, original.column]; - } - - return [line, column, original.line, original.column, name]; -} - -function addMappingsForFile(generator, mappings, module, carryOver) { - generator.startFile(module.sourcePath, module.sourceCode); - - const columnOffset = module.code.indexOf('{') + 1; - for (let i = 0, n = mappings.length; i < n; ++i) { - addMapping(generator, mappings[i], carryOver, columnOffset); - } - - generator.endFile(); - -} - -function addMapping(generator, mapping, carryOver, columnOffset) { - const n = mapping.length; - const line = mapping[0] + carryOver; - // lines start at 1, columns start at 0 - const column = mapping[0] === 1 ? mapping[1] + columnOffset : mapping[1]; - if (n === 2) { - generator.addSimpleMapping(line, column); - } else if (n === 4) { - // $FlowIssue #15579526 - generator.addSourceMapping(line, column, mapping[2], mapping[3]); - } else if (n === 5) { - generator.addNamedSourceMapping( - // $FlowIssue #15579526 - line, column, mapping[2], mapping[3], mapping[4]); - } else { - throw new Error(`Invalid mapping: [${mapping.join(', ')}]`); - } -} - -function countLines(string) { - return string.split('\n').length; -} - -exports.fromRawMappings = fromRawMappings; -exports.compactMapping = compactMapping; diff --git a/packager/src/Bundler/util.js b/packager/src/Bundler/util.js deleted file mode 100644 index f57b55019a6645..00000000000000 --- a/packager/src/Bundler/util.js +++ /dev/null @@ -1,157 +0,0 @@ -/** - * 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. - * - * @flow - */ - -'use strict'; - -const babel = require('babel-core'); -const babelGenerate = require('babel-generator').default; -const babylon = require('babylon'); - -import type {AssetDescriptor} from '.'; -import type {ModuleTransportLike} from '../shared/types.flow'; - -type SubTree = ( - moduleTransport: T, - moduleTransportsByPath: Map, -) => Generator; - -const assetPropertyBlacklist = new Set([ - 'files', - 'fileSystemLocation', - 'path', -]); - -const ASSET_REGISTRY_PATH = 'react-native/Libraries/Image/AssetRegistry'; - -function generateAssetCodeFileAst(assetDescriptor: AssetDescriptor): Object { - const properDescriptor = filterObject(assetDescriptor, assetPropertyBlacklist); - const descriptorAst = babylon.parseExpression(JSON.stringify(properDescriptor)); - const t = babel.types; - const moduleExports = t.memberExpression(t.identifier('module'), t.identifier('exports')); - const requireCall = - t.callExpression(t.identifier('require'), [t.stringLiteral(ASSET_REGISTRY_PATH)]); - const registerAssetFunction = t.memberExpression(requireCall, t.identifier('registerAsset')); - const registerAssetCall = t.callExpression(registerAssetFunction, [descriptorAst]); - return t.file(t.program([ - t.expressionStatement(t.assignmentExpression('=', moduleExports, registerAssetCall)), - ])); -} - -function generateAssetTransformResult(assetDescriptor: AssetDescriptor): {| - code: string, - dependencies: Array, - dependencyOffsets: Array, -|} { - const {code} = babelGenerate( - generateAssetCodeFileAst(assetDescriptor), - {comments: false, compact: true}, - ); - const dependencies = [ASSET_REGISTRY_PATH]; - const dependencyOffsets = [code.indexOf(ASSET_REGISTRY_PATH) - 1]; - return {code, dependencies, dependencyOffsets}; -} - -// Test extension against all types supported by image-size module. -// If it's not one of these, we won't treat it as an image. -function isAssetTypeAnImage(type: string): boolean { - return [ - 'png', 'jpg', 'jpeg', 'bmp', 'gif', 'webp', 'psd', 'svg', 'tiff', - ].indexOf(type) !== -1; -} - -function filterObject(object, blacklist) { - const copied = Object.assign({}, object); - for (const key of blacklist) { - delete copied[key]; - } - return copied; -} - -function createRamBundleGroups( - ramGroups: $ReadOnlyArray, - groupableModules: $ReadOnlyArray, - subtree: SubTree, -): Map> { - // build two maps that allow to lookup module data - // by path or (numeric) module id; - const byPath = new Map(); - const byId = new Map(); - groupableModules.forEach(m => { - byPath.set(m.sourcePath, m); - byId.set(m.id, m.sourcePath); - }); - - // build a map of group root IDs to an array of module IDs in the group - const result: Map> = new Map( - ramGroups - .map(modulePath => { - const root = byPath.get(modulePath); - if (root == null) { - throw Error(`Group root ${modulePath} is not part of the bundle`); - } - return [ - root.id, - // `subtree` yields the IDs of all transitive dependencies of a module - new Set(subtree(root, byPath)), - ]; - }) - ); - - if (ramGroups.length > 1) { - // build a map of all grouped module IDs to an array of group root IDs - const all = new ArrayMap(); - for (const [parent, children] of result) { - for (const module of children) { - all.get(module).push(parent); - } - } - - // find all module IDs that are part of more than one group - const doubles = filter(all, ([, parents]) => parents.length > 1); - for (const [moduleId, parents] of doubles) { - const parentNames = parents.map(byId.get, byId); - const lastName = parentNames.pop(); - throw new Error( - `Module ${byId.get(moduleId) || moduleId} belongs to groups ${ - parentNames.join(', ')}, and ${String(lastName) - }. Ensure that each module is only part of one group.` - ); - } - } - - return result; -} - -function * filter(iterator, predicate) { - for (const value of iterator) { - if (predicate(value)) { - yield value; - } - } -} - -class ArrayMap extends Map { - get(key) { - let array = super.get(key); - if (!array) { - array = []; - this.set(key, array); - } - return array; - } -} - -module.exports = { - createRamBundleGroups, - generateAssetCodeFileAst, - generateAssetTransformResult, - isAssetTypeAnImage, -}; diff --git a/packager/src/JSTransformer/README.md b/packager/src/JSTransformer/README.md deleted file mode 100644 index e69de29bb2d1d6..00000000000000 diff --git a/packager/src/JSTransformer/__mocks__/lodash.js b/packager/src/JSTransformer/__mocks__/lodash.js deleted file mode 100644 index ac8224e6042b92..00000000000000 --- a/packager/src/JSTransformer/__mocks__/lodash.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * 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'; - -// Bug with Jest because we're going to the node_modules that is a sibling -// of what jest thinks our root (the dir with the package.json) should be. -module.exports = require.requireActual('lodash'); diff --git a/packager/src/JSTransformer/__mocks__/worker.js b/packager/src/JSTransformer/__mocks__/worker.js deleted file mode 100644 index bc445e2542d080..00000000000000 --- a/packager/src/JSTransformer/__mocks__/worker.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * 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'; - -module.exports = function(data, callback) { - callback(null, {}); -}; diff --git a/packager/src/JSTransformer/__tests__/Transformer-test.js b/packager/src/JSTransformer/__tests__/Transformer-test.js deleted file mode 100644 index ae9b59b13f6def..00000000000000 --- a/packager/src/JSTransformer/__tests__/Transformer-test.js +++ /dev/null @@ -1,95 +0,0 @@ -/** - * 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'; - -jest - .unmock('stream') - .unmock('crypto') - .unmock('../../lib/ModuleTransport') - .unmock('../'); - -const fs = {writeFileSync: jest.fn()}; -const temp = {path: () => '/arbitrary/path'}; -const workerFarm = jest.fn(); -jest.setMock('fs', fs); -jest.setMock('temp', temp); -jest.setMock('worker-farm', workerFarm); -jest.setMock('../../worker-farm', workerFarm); - -var Transformer = require('../'); - -const {any} = jasmine; -const {Readable} = require('stream'); - -describe('Transformer', function() { - let workers, Cache; - const fileName = '/an/arbitrary/file.js'; - const localPath = 'arbitrary/file.js'; - const transformModulePath = __filename; - - beforeEach(function() { - Cache = jest.fn(); - Cache.prototype.get = jest.fn((a, b, c) => c()); - - fs.writeFileSync.mockClear(); - workerFarm.mockClear(); - workerFarm.mockImplementation((opts, path, methods) => { - const api = workers = {}; - methods.forEach(method => {api[method] = jest.fn();}); - return {methods: api, stdout: new Readable({read() {}}), stderr: new Readable({read() {}})}; - }); - }); - - it('passes transform module path, file path, source code' + - ' to the worker farm when transforming', () => { - const transformOptions = {arbitrary: 'options'}; - const code = 'arbitrary(code)'; - new Transformer(transformModulePath).transformFile(fileName, localPath, code, transformOptions); - expect(workers.transformAndExtractDependencies).toBeCalledWith( - transformModulePath, - fileName, - localPath, - code, - transformOptions, - any(Function), - ); - }); - - it('should add file info to parse errors', function() { - const transformer = new Transformer(transformModulePath); - var message = 'message'; - var snippet = 'snippet'; - - workers.transformAndExtractDependencies.mockImplementation( - function(transformPath, filename, localPth, code, opts, callback) { - var babelError = new SyntaxError(message); - babelError.type = 'SyntaxError'; - babelError.description = message; - babelError.loc = { - line: 2, - column: 15, - }; - babelError.codeFrame = snippet; - callback(babelError); - }, - ); - - expect.assertions(7); - return transformer.transformFile(fileName, localPath, '', {}) - .catch(function(error) { - expect(error.type).toEqual('TransformError'); - expect(error.message).toBe('SyntaxError ' + message); - expect(error.lineNumber).toBe(2); - expect(error.column).toBe(15); - expect(error.filename).toBe(fileName); - expect(error.description).toBe(message); - expect(error.snippet).toBe(snippet); - }); - }); -}); diff --git a/packager/src/JSTransformer/index.js b/packager/src/JSTransformer/index.js deleted file mode 100644 index 4cf2a99388f8ad..00000000000000 --- a/packager/src/JSTransformer/index.js +++ /dev/null @@ -1,204 +0,0 @@ -/** - * 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. - * - * @flow - */ - -'use strict'; - -const Logger = require('../Logger'); - -const debug = require('debug')('RNP:JStransformer'); -const denodeify: Denodeify = require('denodeify'); -const invariant = require('fbjs/lib/invariant'); -const path = require('path'); -const util = require('util'); -const workerFarm = require('../worker-farm'); - -import type {Data as TransformData, Options as WorkerOptions} from './worker'; -import type {LocalPath} from '../node-haste/lib/toLocalPath'; -import type {MappingsMap} from '../lib/SourceMap'; -import typeof {minify as Minify, transformAndExtractDependencies as TransformAndExtractDependencies} from './worker'; - -type CB = (?Error, ?T) => mixed; -type Denodeify = - & (((A, B, C, CB) => void) => (A, B, C) => Promise) - & (((A, B, C, D, E, CB) => void) => (A, B, C, D, E) => Promise); - -// Avoid memory leaks caused in workers. This number seems to be a good enough number -// to avoid any memory leak while not slowing down initial builds. -// TODO(amasad): Once we get bundle splitting, we can drive this down a bit more. -const MAX_CALLS_PER_WORKER = 600; - -// Worker will timeout if one of the callers timeout. -const TRANSFORM_TIMEOUT_INTERVAL = 301000; - -// How may times can we tolerate failures from the worker. -const MAX_RETRIES = 2; - -function makeFarm(worker, methods, timeout, maxConcurrentWorkers) { - return workerFarm( - { - autoStart: true, - execArgv: [], - maxConcurrentCallsPerWorker: 1, - maxConcurrentWorkers, - maxCallsPerWorker: MAX_CALLS_PER_WORKER, - maxCallTime: timeout, - maxRetries: MAX_RETRIES, - }, - worker, - methods, - ); -} - -type Reporters = { - +stdoutChunk: (chunk: string) => mixed, - +stderrChunk: (chunk: string) => mixed, -}; - -class Transformer { - - _workers: {[name: string]: Function}; - _transformModulePath: string; - _transform: ( - transform: string, - filename: string, - localPath: LocalPath, - sourceCode: string, - options: WorkerOptions, - ) => Promise; - minify: ( - filename: string, - code: string, - sourceMap: MappingsMap, - ) => Promise<{code: string, map: MappingsMap}>; - - constructor( - transformModulePath: string, - maxWorkerCount: number, - reporters: Reporters, - workerPath: ?string, - ) { - invariant(path.isAbsolute(transformModulePath), 'transform module path should be absolute'); - this._transformModulePath = transformModulePath; - - const farm = makeFarm( - workerPath || require.resolve('./worker'), - ['minify', 'transformAndExtractDependencies'], - TRANSFORM_TIMEOUT_INTERVAL, - maxWorkerCount, - ); - farm.stdout.on('data', chunk => { - reporters.stdoutChunk(chunk.toString('utf8')); - }); - farm.stderr.on('data', chunk => { - reporters.stderrChunk(chunk.toString('utf8')); - }); - - this._workers = farm.methods; - this._transform = denodeify((this._workers.transformAndExtractDependencies: TransformAndExtractDependencies)); - this.minify = denodeify((this._workers.minify: Minify)); - } - - kill() { - this._workers && workerFarm.end(this._workers); - } - - transformFile( - fileName: string, - localPath: LocalPath, - code: string, - options: WorkerOptions) { - if (!this._transform) { - return Promise.reject(new Error('No transform module')); - } - debug('transforming file', fileName); - return this - ._transform( - this._transformModulePath, - fileName, - localPath, - code, - options, - ) - .then(data => { - Logger.log(data.transformFileStartLogEntry); - Logger.log(data.transformFileEndLogEntry); - debug('done transforming file', fileName); - return data.result; - }) - .catch(error => { - if (error.type === 'TimeoutError') { - const timeoutErr = new Error( - `TimeoutError: transforming ${fileName} took longer than ` + - `${TRANSFORM_TIMEOUT_INTERVAL / 1000} seconds.\n` + - 'You can adjust timeout via the \'transformTimeoutInterval\' option' - ); - /* $FlowFixMe: monkey-patch Error */ - timeoutErr.type = 'TimeoutError'; - throw timeoutErr; - } else if (error.type === 'ProcessTerminatedError') { - const uncaughtError = new Error( - 'Uncaught error in the transformer worker: ' + - this._transformModulePath - ); - /* $FlowFixMe: monkey-patch Error */ - uncaughtError.type = 'ProcessTerminatedError'; - throw uncaughtError; - } - - throw formatError(error, fileName); - }); - } - - static TransformError; -} - -Transformer.TransformError = TransformError; - -function TransformError() { - Error.captureStackTrace && Error.captureStackTrace(this, TransformError); -} -util.inherits(TransformError, SyntaxError); - -function formatError(err, filename) { - if (err.loc) { - return formatBabelError(err, filename); - } else { - return formatGenericError(err, filename); - } -} - -function formatGenericError(err, filename) { - var msg = 'TransformError: ' + filename + ': ' + err.message; - var error = new TransformError(); - var stack = (err.stack || '').split('\n').slice(0, -1); - stack.push(msg); - error.stack = stack.join('\n'); - error.message = msg; - error.type = 'TransformError'; - error.lineNumber = 0; - error.description = ''; - return error; -} - -function formatBabelError(err, filename) { - var error = new TransformError(); - error.type = 'TransformError'; - error.message = (err.type || error.type) + ' ' + err.message; - error.stack = err.stack; - error.snippet = err.codeFrame; - error.lineNumber = err.loc.line; - error.column = err.loc.column; - error.filename = filename; - error.description = err.message; - return error; -} - -module.exports = Transformer; diff --git a/packager/src/JSTransformer/worker/JsMinification.js b/packager/src/JSTransformer/worker/JsMinification.js deleted file mode 100644 index 580212ebb72bf3..00000000000000 --- a/packager/src/JSTransformer/worker/JsMinification.js +++ /dev/null @@ -1,19 +0,0 @@ -/** - * Copyright (c) 2016-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. - * - * @flow - */ - -'use strict'; - -const UGLIFY_JS_OUTPUT_OPTIONS = { - ascii_only: true, - screw_ie8: true, -}; - -module.exports = {UGLIFY_JS_OUTPUT_OPTIONS}; diff --git a/packager/src/JSTransformer/worker/__tests__/constant-folding-test.js b/packager/src/JSTransformer/worker/__tests__/constant-folding-test.js deleted file mode 100644 index b1899fca6ccf37..00000000000000 --- a/packager/src/JSTransformer/worker/__tests__/constant-folding-test.js +++ /dev/null @@ -1,122 +0,0 @@ -/** - * 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'; - -jest.disableAutomock(); -const babel = require('babel-core'); -const constantFolding = require('../constant-folding'); - -function parse(code) { - return babel.transform(code, {code: false, babelrc: false, compact: true}); -} - -const babelOptions = { - babelrc: false, - compact: true, - retainLines: false, -}; - -function normalize({code}) { - return babel.transform(code, babelOptions).code; -} - -describe('constant expressions', () => { - it('can optimize conditional expressions with constant conditions', () => { - const code = ` - a( - 'production'=="production", - 'production'!=='development', - false && 1 || 0 || 2, - true || 3, - 'android'==='ios' ? null : {}, - 'android'==='android' ? {a:1} : {a:0}, - 'foo'==='bar' ? b : c, - f() ? g() : h() - );`; - expect(normalize(constantFolding('arbitrary.js', parse(code)))) - .toEqual('a(true,true,2,true,{},{a:1},c,f()?g():h());'); - }); - - it('can optimize ternary expressions with constant conditions', () => { - const code = - `var a = true ? 1 : 2; - var b = 'android' == 'android' - ? ('production' != 'production' ? 'a' : 'A') - : 'i';`; - expect(normalize(constantFolding('arbitrary.js', parse(code)))) - .toEqual('var a=1;var b=\'A\';'); - }); - - it('can optimize logical operator expressions with constant conditions', () => { - const code = ` - var a = true || 1; - var b = 'android' == 'android' && - 'production' != 'production' || null || "A";`; - expect(normalize(constantFolding('arbitrary.js', parse(code)))) - .toEqual('var a=true;var b="A";'); - }); - - it('can optimize logical operators with partly constant operands', () => { - const code = ` - var a = "truthy" || z(); - var b = "truthy" && z(); - var c = null && z(); - var d = null || z(); - var e = !1 && z(); - `; - expect(normalize(constantFolding('arbitrary.js', parse(code)))) - .toEqual('var a="truthy";var b=z();var c=null;var d=z();var e=false;'); - }); - - it('can remode an if statement with a falsy constant test', () => { - const code = ` - if ('production' === 'development' || false) { - var a = 1; - } - `; - expect(normalize(constantFolding('arbitrary.js', parse(code)))) - .toEqual(''); - }); - - it('can optimize if-else-branches with constant conditions', () => { - const code = ` - if ('production' == 'development') { - var a = 1; - var b = a + 2; - } else if ('development' == 'development') { - var a = 3; - var b = a + 4; - } else { - var a = 'b'; - } - `; - expect(normalize(constantFolding('arbitrary.js', parse(code)))) - .toEqual('{var a=3;var b=a+4;}'); - }); - - it('can optimize nested if-else constructs', () => { - const code = ` - if ('ios' === "android") { - if (true) { - require('a'); - } else { - require('b'); - } - } else if ('android' === 'android') { - if (true) { - require('c'); - } else { - require('d'); - } - } - `; - expect(normalize(constantFolding('arbitrary.js', parse(code)))) - .toEqual('{{require(\'c\');}}'); - }); -}); diff --git a/packager/src/JSTransformer/worker/__tests__/extract-dependencies-test.js b/packager/src/JSTransformer/worker/__tests__/extract-dependencies-test.js deleted file mode 100644 index 998ee7da9c3ae9..00000000000000 --- a/packager/src/JSTransformer/worker/__tests__/extract-dependencies-test.js +++ /dev/null @@ -1,112 +0,0 @@ -/** - * 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'; - -jest.disableAutomock(); - -const extractDependencies = require('../extract-dependencies'); - -describe('Dependency extraction:', () => { - it('can extract calls to require', () => { - const code = `require('foo/bar'); - var React = require("React"); - var A = React.createClass({ - render: function() { - return require ( "Component" ); - } - }); - require - ('more');`; - const {dependencies, dependencyOffsets} = extractDependencies(code); - expect(dependencies) - .toEqual(['foo/bar', 'React', 'Component', 'more']); - expect(dependencyOffsets).toEqual([8, 46, 147, 203]); - }); - - it('does not extract require method calls', () => { - const code = ` - require('a'); - foo.require('b'); - bar. - require ( 'c').require('d');require('e')`; - - const {dependencies, dependencyOffsets} = extractDependencies(code); - expect(dependencies).toEqual(['a', 'e']); - expect(dependencyOffsets).toEqual([15, 98]); - }); - - it('does not extract require calls from strings', () => { - const code = `require('foo'); - var React = '\\'require("React")'; - var a = ' // require("yadda")'; - var a = ' /* require("yadda") */'; - var A = React.createClass({ - render: function() { - return require ( "Component" ); - } - }); - " \\" require('more')";`; - - const {dependencies, dependencyOffsets} = extractDependencies(code); - expect(dependencies).toEqual(['foo', 'Component']); - expect(dependencyOffsets).toEqual([8, 226]); - }); - - it('does not extract require calls in comments', () => { - const code = `require('foo')//require("not/this") - /* A comment here with a require('call') that should not be extracted */require('bar') - // ending comment without newline require("baz")`; - - const {dependencies, dependencyOffsets} = extractDependencies(code); - expect(dependencies).toEqual(['foo', 'bar']); - expect(dependencyOffsets).toEqual([8, 122]); - }); - - it('deduplicates dependencies', () => { - const code = `require('foo');require( "foo" ); - require("foo");`; - - const {dependencies, dependencyOffsets} = extractDependencies(code); - expect(dependencies).toEqual(['foo']); - expect(dependencyOffsets).toEqual([8, 24, 47]); - }); - - it('does not extract calls to function with names that start with "require"', () => { - const code = 'arbitraryrequire(\'foo\');'; - - const {dependencies, dependencyOffsets} = extractDependencies(code); - expect(dependencies).toEqual([]); - expect(dependencyOffsets).toEqual([]); - }); - - it('does not extract calls to require with non-static arguments', () => { - const code = 'require(\'foo/\' + bar)'; - - const {dependencies, dependencyOffsets} = extractDependencies(code); - expect(dependencies).toEqual([]); - expect(dependencyOffsets).toEqual([]); - }); - - it('does not get confused by previous states', () => { - // yes, this was a bug - const code = 'require("a");/* a comment */ var a = /[a]/.test(\'a\');'; - - const {dependencies, dependencyOffsets} = extractDependencies(code); - expect(dependencies).toEqual(['a']); - expect(dependencyOffsets).toEqual([8]); - }); - - it('can handle regular expressions', () => { - const code = 'require(\'a\'); /["\']/.test(\'foo\'); require("b");'; - - const {dependencies, dependencyOffsets} = extractDependencies(code); - expect(dependencies).toEqual(['a', 'b']); - expect(dependencyOffsets).toEqual([8, 42]); - }); -}); diff --git a/packager/src/JSTransformer/worker/__tests__/inline-test.js b/packager/src/JSTransformer/worker/__tests__/inline-test.js deleted file mode 100644 index a08a242f791d93..00000000000000 --- a/packager/src/JSTransformer/worker/__tests__/inline-test.js +++ /dev/null @@ -1,337 +0,0 @@ -/** - * 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'; - -/* eslint-disable max-len */ - -jest.disableAutomock(); -const inline = require('../inline'); -const {transform, transformFromAst} = require('babel-core'); - -const babelOptions = { - babelrc: false, - compact: true, -}; - -function toString(ast) { - return normalize(transformFromAst(ast, babelOptions).code); -} - -function normalize(code) { - return transform(code, babelOptions).code; -} - -function toAst(code) { - return transform(code, {...babelOptions, code: false}).ast; -} - -describe('inline constants', () => { - it('replaces __DEV__ in the code', () => { - const code = `function a() { - var a = __DEV__ ? 1 : 2; - var b = a.__DEV__; - var c = function __DEV__(__DEV__) {}; - }`; - const {ast} = inline('arbitrary.js', {code}, {dev: true}); - expect(toString(ast)).toEqual(normalize(code.replace(/__DEV__/, 'true'))); - }); - - it('replaces Platform.OS in the code if Platform is a global', () => { - const code = `function a() { - var a = Platform.OS; - var b = a.Platform.OS; - }`; - const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'}); - expect(toString(ast)).toEqual(normalize(code.replace(/Platform\.OS/, '"ios"'))); - }); - - it('replaces Platform.OS in the code if Platform is a top level import', () => { - const code = ` - var Platform = require('Platform'); - function a() { - if (Platform.OS === 'android') a = function() {}; - var b = a.Platform.OS; - }`; - const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'}); - expect(toString(ast)).toEqual(normalize(code.replace(/Platform\.OS/, '"ios"'))); - }); - - it('replaces Platform.OS in the code if Platform is a top level import from react-native', () => { - const code = ` - var Platform = require('react-native').Platform; - function a() { - if (Platform.OS === 'android') a = function() {}; - var b = a.Platform.OS; - }`; - const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'}); - expect(toString(ast)).toEqual(normalize(code.replace(/Platform\.OS/, '"ios"'))); - }); - - it('replaces require("Platform").OS in the code', () => { - const code = `function a() { - var a = require('Platform').OS; - var b = a.require('Platform').OS; - }`; - const {ast} = inline('arbitrary.js', {code}, {platform: 'android'}); - expect(toString(ast)).toEqual( - normalize(code.replace(/require\('Platform'\)\.OS/, '"android"'))); - }); - - it('replaces React.Platform.OS in the code if React is a global', () => { - const code = `function a() { - var a = React.Platform.OS; - var b = a.React.Platform.OS; - }`; - const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'}); - expect(toString(ast)).toEqual(normalize(code.replace(/React\.Platform\.OS/, '"ios"'))); - }); - - it('replaces ReactNative.Platform.OS in the code if ReactNative is a global', () => { - const code = `function a() { - var a = ReactNative.Platform.OS; - var b = a.ReactNative.Platform.OS; - }`; - const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'}); - expect(toString(ast)).toEqual(normalize(code.replace(/ReactNative\.Platform\.OS/, '"ios"'))); - }); - - it('replaces React.Platform.OS in the code if React is a top level import', () => { - const code = ` - var React = require('React'); - function a() { - if (React.Platform.OS === 'android') a = function() {}; - var b = a.React.Platform.OS; - }`; - const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'}); - expect(toString(ast)).toEqual(normalize(code.replace(/React.Platform\.OS/, '"ios"'))); - }); - - it('replaces require("React").Platform.OS in the code', () => { - const code = `function a() { - var a = require('React').Platform.OS; - var b = a.require('React').Platform.OS; - }`; - const {ast} = inline('arbitrary.js', {code}, {platform: 'android'}); - expect(toString(ast)).toEqual( - normalize(code.replace(/require\('React'\)\.Platform\.OS/, '"android"'))); - }); - - it('replaces ReactNative.Platform.OS in the code if ReactNative is a top level import', () => { - const code = ` - var ReactNative = require('react-native'); - function a() { - if (ReactNative.Platform.OS === 'android') a = function() {}; - var b = a.ReactNative.Platform.OS; - }`; - const {ast} = inline('arbitrary.js', {code}, {platform: 'android'}); - expect(toString(ast)).toEqual(normalize(code.replace(/ReactNative.Platform\.OS/, '"android"'))); - }); - - it('replaces require("react-native").Platform.OS in the code', () => { - const code = `function a() { - var a = require('react-native').Platform.OS; - var b = a.require('react-native').Platform.OS; - }`; - const {ast} = inline('arbitrary.js', {code}, {platform: 'android'}); - expect(toString(ast)).toEqual( - normalize(code.replace(/require\('react-native'\)\.Platform\.OS/, '"android"'))); - }); - - it('inlines Platform.select in the code if Platform is a global and the argument is an object literal', () => { - const code = `function a() { - var a = Platform.select({ios: 1, android: 2}); - var b = a.Platform.select({ios: 1, android: 2}); - }`; - const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'}); - expect(toString(ast)).toEqual(normalize(code.replace(/Platform\.select[^;]+/, '1'))); - }); - - it('inlines Platform.select in the code if Platform is a global and the argument doesn\'t have target platform in it\'s keys', () => { - const code = `function a() { - var a = Platform.select({ios: 1, default: 2}); - var b = a.Platform.select({ios: 1, default: 2}); - }`; - const {ast} = inline('arbitrary.js', {code}, {platform: 'android'}); - expect(toString(ast)).toEqual(normalize(code.replace(/Platform\.select[^;]+/, '2'))); - }); - - it('replaces Platform.select in the code if Platform is a top level import', () => { - const code = ` - var Platform = require('Platform'); - function a() { - Platform.select({ios: 1, android: 2}); - var b = a.Platform.select({}); - }`; - const {ast} = inline('arbitrary.js', {code}, {platform: 'android'}); - expect(toString(ast)).toEqual(normalize(code.replace(/Platform\.select[^;]+/, '2'))); - }); - - it('replaces Platform.select in the code if Platform is a top level import from react-native', () => { - const code = ` - var Platform = require('react-native').Platform; - function a() { - Platform.select({ios: 1, android: 2}); - var b = a.Platform.select({}); - }`; - const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'}); - expect(toString(ast)).toEqual(normalize(code.replace(/Platform\.select[^;]+/, '1'))); - }); - - it('replaces require("Platform").select in the code', () => { - const code = `function a() { - var a = require('Platform').select({ios: 1, android: 2}); - var b = a.require('Platform').select({}); - }`; - const {ast} = inline('arbitrary.js', {code}, {platform: 'android'}); - expect(toString(ast)).toEqual(normalize(code.replace(/Platform\.select[^;]+/, '2'))); - }); - - it('replaces React.Platform.select in the code if React is a global', () => { - const code = `function a() { - var a = React.Platform.select({ios: 1, android: 2}); - var b = a.React.Platform.select({}); - }`; - const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'}); - expect(toString(ast)).toEqual(normalize(code.replace(/React\.Platform\.select[^;]+/, '1'))); - }); - - it('replaces ReactNative.Platform.select in the code if ReactNative is a global', () => { - const code = `function a() { - var a = ReactNative.Platform.select({ios: 1, android: 2}); - var b = a.ReactNative.Platform.select({}); - }`; - const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'}); - expect(toString(ast)).toEqual( - normalize(code.replace(/ReactNative\.Platform\.select[^;]+/, '1')), - ); - }); - - it('replaces React.Platform.select in the code if React is a top level import', () => { - const code = ` - var React = require('React'); - function a() { - var a = React.Platform.select({ios: 1, android: 2}); - var b = a.React.Platform.select({}); - }`; - const {ast} = inline('arbitrary.js', {code}, {platform: 'ios'}); - expect(toString(ast)).toEqual(normalize(code.replace(/React\.Platform\.select[^;]+/, '1'))); - }); - - it('replaces require("React").Platform.select in the code', () => { - const code = `function a() { - var a = require('React').Platform.select({ios: 1, android: 2}); - var b = a.require('React').Platform.select({}); - }`; - const {ast} = inline('arbitrary.js', {code}, {platform: 'android'}); - expect(toString(ast)).toEqual( - normalize(code.replace(/require\('React'\)\.Platform\.select[^;]+/, '2'))); - }); - - it('replaces ReactNative.Platform.select in the code if ReactNative is a top level import', () => { - const code = ` - var ReactNative = require('react-native'); - function a() { - var a = ReactNative.Plaftform.select({ios: 1, android: 2}); - var b = a.ReactNative.Platform.select; - }`; - const {ast} = inline('arbitrary.js', {code}, {platform: 'android'}); - expect(toString(ast)).toEqual( - normalize(code.replace(/ReactNative.Platform\.select[^;]+/, '2')), - ); - }); - - it('replaces require("react-native").Platform.select in the code', () => { - const code = ` - var a = require('react-native').Platform.select({ios: 1, android: 2}); - var b = a.require('react-native').Platform.select({}); - `; - const {ast} = inline('arbitrary.js', {code}, {platform: 'android'}); - expect(toString(ast)).toEqual( - normalize(code.replace(/require\('react-native'\)\.Platform\.select[^;]+/, '2'))); - }); - - it('replaces non-existing properties with `undefined`', () => { - const code = 'var a = Platform.select({ios: 1, android: 2})'; - const {ast} = inline('arbitrary.js', {code}, {platform: 'doesnotexist'}); - expect(toString(ast)).toEqual( - normalize(code.replace(/Platform\.select[^;]+/, 'undefined'))); - }); - - it('replaces process.env.NODE_ENV in the code', () => { - const code = `function a() { - if (process.env.NODE_ENV === 'production') { - return require('Prod'); - } - return require('Dev'); - }`; - const {ast} = inline('arbitrary.js', {code}, {dev: false}); - expect(toString(ast)).toEqual( - normalize(code.replace(/process\.env\.NODE_ENV/, '"production"'))); - }); - - it('accepts an AST as input', function() { - const code = 'function ifDev(a,b){return __DEV__?a:b;}'; - const {ast} = inline('arbitrary.hs', {ast: toAst(code)}, {dev: false}); - expect(toString(ast)).toEqual(code.replace(/__DEV__/, 'false')); - }); - - it('can work with wrapped modules', () => { - const code = `__arbitrary(function() { - var Platform = require('react-native').Platform; - var a = Platform.OS, b = Platform.select({android: 1, ios: 2}); - });`; - const {ast} = inline( - 'arbitrary', {code}, {dev: true, platform: 'android', isWrapped: true}); - expect(toString(ast)).toEqual( - normalize( - code - .replace(/Platform\.OS/, '"android"') - .replace(/Platform\.select[^)]+\)/, 1) - ) - ); - }); - - it('can work with transformed require calls', () => { - const code = `__arbitrary(require, function(arbitraryMapName) { - var a = require(arbitraryMapName[123], 'react-native').Platform.OS; - });`; - const {ast} = inline( - 'arbitrary', {code}, {dev: true, platform: 'android', isWrapped: true}); - expect(toString(ast)).toEqual( - normalize(code.replace(/require\([^)]+\)\.Platform\.OS/, '"android"'))); - }); - - it('works with flow-declared variables', () => { - const stripFlow = require('babel-plugin-transform-flow-strip-types'); - const code = `declare var __DEV__; - const a: boolean = __DEV__;`; - - const transformed = transform( - code, - {...babelOptions, plugins: [stripFlow, [inline.plugin, {dev: false}]]}, - ).code; - - expect(transformed).toEqual('const a=false;'); - }); - - it('works with flow-declared variables in wrapped modules', () => { - const stripFlow = require('babel-plugin-transform-flow-strip-types'); - const code = `__d(() => { - declare var __DEV__; - const a: boolean = __DEV__; - });`; - - const transformed = transform( - code, - {...babelOptions, plugins: [stripFlow, [inline.plugin, {dev: true}]]}, - ).code; - - expect(transformed).toEqual('__d(()=>{const a=true;});'); - }); -}); diff --git a/packager/src/JSTransformer/worker/__tests__/minify-test.js b/packager/src/JSTransformer/worker/__tests__/minify-test.js deleted file mode 100644 index 8eff672e8ddee1..00000000000000 --- a/packager/src/JSTransformer/worker/__tests__/minify-test.js +++ /dev/null @@ -1,57 +0,0 @@ -/** - * 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'; - -jest.disableAutomock(); - -const uglify = { - minify: jest.fn(code => { - return { - code: code.replace(/(^|\W)\s+/g, '$1'), - map: {}, - }; - }), -}; -jest.setMock('uglify-js', uglify); - -const minify = require('../minify'); -const {objectContaining} = jasmine; - -describe('Minification:', () => { - const filename = '/arbitrary/file.js'; - const code = 'arbitrary(code)'; - let map; - - beforeEach(() => { - uglify.minify.mockClear(); - uglify.minify.mockReturnValue({code: '', map: '{}'}); - map = {version: 3, sources: ['?'], mappings: ''}; - }); - - it('passes file name, code, and source map to `uglify`', () => { - minify(filename, code, map); - expect(uglify.minify).toBeCalledWith(code, objectContaining({ - fromString: true, - inSourceMap: map, - outSourceMap: true, - })); - }); - - it('returns the code provided by uglify', () => { - uglify.minify.mockReturnValue({code, map: '{}'}); - const result = minify('', '', {}); - expect(result.code).toBe(code); - }); - - it('parses the source map object provided by uglify and sets the sources property', () => { - uglify.minify.mockReturnValue({map: JSON.stringify(map), code: ''}); - const result = minify(filename, '', {}); - expect(result.map).toEqual({...map, sources: [filename]}); - }); -}); diff --git a/packager/src/JSTransformer/worker/__tests__/worker-test.js b/packager/src/JSTransformer/worker/__tests__/worker-test.js deleted file mode 100644 index c404aea5d7edd2..00000000000000 --- a/packager/src/JSTransformer/worker/__tests__/worker-test.js +++ /dev/null @@ -1,226 +0,0 @@ -/** - * 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'; - -jest.disableAutomock(); -jest.mock('../constant-folding'); -jest.mock('../extract-dependencies'); -jest.mock('../inline'); -jest.mock('../minify'); - -const {objectContaining} = jasmine; - -describe('code transformation worker:', () => { - let transformCode; - - let extractDependencies, transformer; - beforeEach(() => { - jest.resetModules(); - ({transformCode} = require('..')); - extractDependencies = - require('../extract-dependencies').mockReturnValue({}); - transformer = { - transform: jest.fn(({filename, options, src}) => ({ - code: src, - map: {}, - })), - }; - }); - - it('calls the transform with file name, source code, and transform options', function() { - const filename = 'arbitrary/file.js'; - const localPath = `local/${filename}`; - const sourceCode = 'arbitrary(code)'; - const transformOptions = {arbitrary: 'options'}; - transformCode(transformer, filename, localPath, sourceCode, {transform: transformOptions}, () => {}); - expect(transformer.transform).toBeCalledWith({ - filename, - localPath, - options: transformOptions, - src: sourceCode, - }); - }); - - it('prefixes JSON files with an assignment to module.exports to make the code valid', function() { - const filename = 'arbitrary/file.json'; - const localPath = `local/${filename}`; - const sourceCode = '{"arbitrary":"property"}'; - transformCode(transformer, filename, localPath, sourceCode, {}, () => {}); - expect(transformer.transform).toBeCalledWith({ - filename, - localPath, - options: undefined, - src: `module.exports=${sourceCode}`, - }); - }); - - it('calls back with the result of the transform in the cache', done => { - const result = { - code: 'some.other(code)', - map: {}, - }; - - transformCode(transformer, 'filename', 'local/filename', result.code, {}, (error, data) => { - expect(error).toBeNull(); - expect(data.result).toEqual(objectContaining(result)); - done(); - }); - }); - - it( - 'removes the leading assignment to `module.exports` before passing ' + - 'on the result if the file is a JSON file, even if minified', - done => { - const code = '{a:1,b:2}'; - const filePath = 'arbitrary/file.json'; - transformCode(transformer, filePath, filePath, code, {}, (error, data) => { - expect(error).toBeNull(); - expect(data.result.code).toEqual(code); - done(); - }, - ); - } - ); - - it('removes shebang when present', done => { - const shebang = '#!/usr/bin/env node'; - const result = { - code: `${shebang} \n arbitrary(code)`, - }; - const filePath = 'arbitrary/file.js'; - transformCode(transformer, filePath, filePath, result.code, {}, (error, data) => { - expect(error).toBeNull(); - const {code} = data.result; - expect(code).not.toContain(shebang); - expect(code.split('\n').length).toEqual(result.code.split('\n').length); - done(); - }); - }); - - it('calls back with any error yielded by the transform', done => { - const message = 'SyntaxError: this code is broken.'; - transformer.transform.mockImplementation(() => { - throw new Error(message); - }); - - transformCode(transformer, 'filename', 'local/filename', 'code', {}, error => { - expect(error.message).toBe(message); - done(); - }); - }); - - describe('dependency extraction', () => { - it('passes the transformed code the `extractDependencies`', done => { - const code = 'arbitrary(code)'; - - transformCode(transformer, 'filename', 'local/filename', code, {}, error => { - expect(error).toBeNull(); - expect(extractDependencies).toBeCalledWith(code); - done(); - }); - }); - - it( - 'uses `dependencies` and `dependencyOffsets` ' + - 'provided by `extractDependencies` for the result', - done => { - const dependencyData = { - dependencies: ['arbitrary', 'list', 'of', 'dependencies'], - dependencyOffsets: [12, 119, 185, 328, 471], - }; - extractDependencies.mockReturnValue(dependencyData); - - transformCode(transformer, 'filename', 'local/filename', 'code', {}, (error, data) => { - expect(error).toBeNull(); - expect(data.result).toEqual(objectContaining(dependencyData)); - done(); - }); - } - ); - - it('does not extract requires of JSON files', done => { - const jsonStr = '{"arbitrary":"json"}'; - transformCode(transformer, 'arbitrary.json', 'local/arbitrary.json', jsonStr, {}, (error, data) => { - expect(error).toBeNull(); - const {dependencies, dependencyOffsets} = data.result; - expect(extractDependencies).not.toBeCalled(); - expect(dependencies).toEqual([]); - expect(dependencyOffsets).toEqual([]); - done(); - } - ); - }); - }); - - describe('Minifications:', () => { - let constantFolding, inline, options; - let transformResult, dependencyData; - const filename = 'arbitrary/file.js'; - const foldedCode = 'arbitrary(folded(code));'; - const foldedMap = {version: 3, sources: ['fold.js']}; - - beforeEach(() => { - constantFolding = require('../constant-folding') - .mockReturnValue({code: foldedCode, map: foldedMap}); - extractDependencies = require('../extract-dependencies'); - inline = require('../inline'); - - options = {minify: true, transform: {generateSourceMaps: true}}; - dependencyData = { - dependencies: ['a', 'b', 'c'], - dependencyOffsets: [100, 120, 140], - }; - - extractDependencies.mockImplementation( - code => code === foldedCode ? dependencyData : {}); - - transformer.transform.mockImplementation((src, fileName, _) => transformResult); - }); - - it('passes the transform result to `inline` for constant inlining', done => { - transformResult = {map: {version: 3}, code: 'arbitrary(code)'}; - transformCode(transformer, filename, filename, 'code', options, () => { - expect(inline).toBeCalledWith(filename, transformResult, options); - done(); - }); - }); - - it('passes the result obtained from `inline` on to `constant-folding`', done => { - const inlineResult = {map: {version: 3, sources: []}, ast: {}}; - inline.mockReturnValue(inlineResult); - transformCode(transformer, filename, filename, 'code', options, () => { - expect(constantFolding).toBeCalledWith(filename, inlineResult); - done(); - }); - }); - - it('Uses the code obtained from `constant-folding` to extract dependencies', done => { - transformCode(transformer, filename, filename, 'code', options, () => { - expect(extractDependencies).toBeCalledWith(foldedCode); - done(); - }); - }); - - it('uses the dependencies obtained from the optimized result', done => { - transformCode(transformer, filename, filename, 'code', options, (_, data) => { - const result = data.result; - expect(result.dependencies).toEqual(dependencyData.dependencies); - done(); - }); - }); - - it('uses data produced by `constant-folding` for the result', done => { - transformCode(transformer, 'filename', 'local/filename', 'code', options, (_, data) => { - expect(data.result) - .toEqual(objectContaining({code: foldedCode, map: foldedMap})); - done(); - }); - }); - }); -}); diff --git a/packager/src/JSTransformer/worker/constant-folding.js b/packager/src/JSTransformer/worker/constant-folding.js deleted file mode 100644 index 36cfbf62c9419a..00000000000000 --- a/packager/src/JSTransformer/worker/constant-folding.js +++ /dev/null @@ -1,91 +0,0 @@ -/** - * Copyright (c) 2016-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. - * - * @flow - */ - -'use strict'; - -const babel = require('babel-core'); - -import type {Ast, SourceMap as MappingsMap} from 'babel-core'; -const t = babel.types; - -const Conditional = { - exit(path) { - const node = path.node; - const test = node.test; - if (t.isLiteral(test)) { - if (test.value || node.alternate) { - path.replaceWith(test.value ? node.consequent : node.alternate); - } else if (!test.value) { - path.remove(); - } - } - }, -}; - -const plugin = { - visitor: { - BinaryExpression: { - exit(path) { - const node = path.node; - if (t.isLiteral(node.left) && t.isLiteral(node.right)) { - const result = path.evaluate(); - if (result.confident) { - path.replaceWith(t.valueToNode(result.value)); - } - } - }, - }, - ConditionalExpression: Conditional, - IfStatement: Conditional, - LogicalExpression: { - exit(path) { - const node = path.node; - const left = node.left; - if (t.isLiteral(left)) { - const value = t.isNullLiteral(left) ? null : left.value; - if (node.operator === '||') { - path.replaceWith(value ? left : node.right); - } else { - path.replaceWith(value ? node.right : left); - } - } - }, - }, - UnaryExpression: { - exit(path) { - const node = path.node; - if (node.operator === '!' && t.isLiteral(node.argument)) { - path.replaceWith(t.valueToNode(!node.argument.value)); - } - }, - }, - }, -}; - -function constantFolding(filename: string, transformResult: { - ast: Ast, - code?: ?string, - map: ?MappingsMap, -}) { - return babel.transformFromAst(transformResult.ast, transformResult.code, { - filename, - plugins: [plugin], - inputSourceMap: transformResult.map, - sourceMaps: true, - sourceFileName: filename, - babelrc: false, - compact: true, - retainLines: true, - }); -} - -constantFolding.plugin = plugin; -module.exports = constantFolding; diff --git a/packager/src/JSTransformer/worker/extract-dependencies.js b/packager/src/JSTransformer/worker/extract-dependencies.js deleted file mode 100644 index 1ce908b98e5356..00000000000000 --- a/packager/src/JSTransformer/worker/extract-dependencies.js +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Copyright (c) 2016-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. - * - * @flow - */ - -'use strict'; - -const babel = require('babel-core'); -const babylon = require('babylon'); - -/** - * Extracts dependencies (module IDs imported with the `require` function) from - * a string containing code. This walks the full AST for correctness (versus - * using, for example, regular expressions, that would be faster but inexact.) - * - * The result of the dependency extraction is an de-duplicated array of - * dependencies, and an array of offsets to the string literals with module IDs. - * The index points to the opening quote. - */ -function extractDependencies(code: string) { - const ast = babylon.parse(code); - const dependencies = new Set(); - const dependencyOffsets = []; - - babel.traverse(ast, { - CallExpression(path) { - const node = path.node; - const callee = node.callee; - const arg = node.arguments[0]; - if ( - callee.type !== 'Identifier' || - callee.name !== 'require' || - !arg || - arg.type !== 'StringLiteral' - ) { - return; - } - dependencyOffsets.push(arg.start); - dependencies.add(arg.value); - }, - }); - - return {dependencyOffsets, dependencies: Array.from(dependencies)}; -} - -module.exports = extractDependencies; diff --git a/packager/src/JSTransformer/worker/index.js b/packager/src/JSTransformer/worker/index.js deleted file mode 100644 index f88706ff857042..00000000000000 --- a/packager/src/JSTransformer/worker/index.js +++ /dev/null @@ -1,191 +0,0 @@ -/** - * Copyright (c) 2016-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. - * - * @flow - */ - -'use strict'; - -const babelRegisterOnly = require('../../babelRegisterOnly'); -const constantFolding = require('./constant-folding'); -const extractDependencies = require('./extract-dependencies'); -const inline = require('./inline'); -const invariant = require('fbjs/lib/invariant'); -const minify = require('./minify'); - -import type {LogEntry} from '../../Logger/Types'; -import type {MappingsMap} from '../../lib/SourceMap'; -import type {LocalPath} from '../../node-haste/lib/toLocalPath'; -import type {Ast, Plugins as BabelPlugins} from 'babel-core'; - -export type TransformedCode = { - code: string, - dependencies: Array, - dependencyOffsets: Array, - map?: ?MappingsMap, -}; - -export type Transformer = { - transform: ({| - filename: string, - localPath: string, - options: ExtraOptions & TransformOptions, - plugins?: BabelPlugins, - src: string, - |}) => {ast: ?Ast, code: string, map: ?MappingsMap}, - getCacheKey: () => string, -}; - - -export type TransformOptionsStrict = {| - +dev: boolean, - +generateSourceMaps: boolean, - +hot: boolean, - +inlineRequires: {+blacklist: {[string]: true}} | boolean, - +platform: ?string, - +projectRoot: string, -|}; - -export type TransformOptions = { - +dev?: boolean, - +generateSourceMaps?: boolean, - +hot?: boolean, - +inlineRequires?: {+blacklist: {[string]: true}} | boolean, - +platform: ?string, - +projectRoot: string, -}; - -export type Options = {| - +dev: boolean, - +minify: boolean, - +platform: ?string, - +transform: TransformOptionsStrict, -|}; - -export type Data = { - result: TransformedCode, - transformFileStartLogEntry: LogEntry, - transformFileEndLogEntry: LogEntry, -}; - -type Callback = ( - error: ?Error, - data: ?T, -) => mixed; - -function transformCode( - transformer: Transformer<*>, - filename: string, - localPath: LocalPath, - sourceCode: string, - options: Options, - callback: Callback, -) { - invariant( - !options.minify || options.transform.generateSourceMaps, - 'Minifying source code requires the `generateSourceMaps` option to be `true`', - ); - - const isJson = filename.endsWith('.json'); - if (isJson) { - sourceCode = 'module.exports=' + sourceCode; - } - - const transformFileStartLogEntry = { - action_name: 'Transforming file', - action_phase: 'start', - file_name: filename, - log_entry_label: 'Transforming file', - start_timestamp: process.hrtime(), - }; - - let transformed; - try { - transformed = transformer.transform({ - filename, - localPath, - options: options.transform, - src: sourceCode, - }); - } catch (error) { - callback(error); - return; - } - - invariant( - transformed != null, - 'Missing transform results despite having no error.', - ); - - var code, map; - if (options.minify) { - ({code, map} = - constantFolding(filename, inline(filename, transformed, options))); - invariant(code != null, 'Missing code from constant-folding transform.'); - } else { - ({code, map} = transformed); - } - - if (isJson) { - code = code.replace(/^\w+\.exports=/, ''); - } else { - // Remove shebang - code = code.replace(/^#!.*/, ''); - } - - const depsResult = isJson - ? {dependencies: [], dependencyOffsets: []} - : extractDependencies(code); - - const timeDelta = process.hrtime(transformFileStartLogEntry.start_timestamp); - const duration_ms = Math.round((timeDelta[0] * 1e9 + timeDelta[1]) / 1e6); - const transformFileEndLogEntry = { - action_name: 'Transforming file', - action_phase: 'end', - file_name: filename, - duration_ms, - log_entry_label: 'Transforming file', - }; - - callback(null, { - result: {...depsResult, code, map}, - transformFileStartLogEntry, - transformFileEndLogEntry, - }); -} - -exports.transformAndExtractDependencies = ( - transform: string, - filename: string, - localPath: LocalPath, - sourceCode: string, - options: Options, - callback: Callback, -) => { - babelRegisterOnly([transform]); - /* $FlowFixMe: impossible to type a dynamic require */ - const transformModule: Transformer<*> = require(transform); - transformCode(transformModule, filename, localPath, sourceCode, options, callback); -}; - -exports.minify = ( - filename: string, - code: string, - sourceMap: MappingsMap, - callback: Callback<{code: string, map: MappingsMap}>, -) => { - var result; - try { - result = minify(filename, code, sourceMap); - } catch (error) { - callback(error); - } - callback(null, result); -}; - -exports.transformCode = transformCode; // for easier testing diff --git a/packager/src/JSTransformer/worker/inline.js b/packager/src/JSTransformer/worker/inline.js deleted file mode 100644 index 041c903ff620d7..00000000000000 --- a/packager/src/JSTransformer/worker/inline.js +++ /dev/null @@ -1,196 +0,0 @@ -/** - * Copyright (c) 2016-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. - * - * @flow - */ - -'use strict'; - -const babel = require('babel-core'); -const invariant = require('fbjs/lib/invariant'); - -import type {Ast, SourceMap as MappingsMap} from 'babel-core'; -const t = babel.types; - -const React = {name: 'React'}; -const ReactNative = {name: 'ReactNative'}; -const platform = {name: 'Platform'}; -const os = {name: 'OS'}; -const select = {name: 'select'}; -const requirePattern = {name: 'require'}; - -const env = {name: 'env'}; -const nodeEnv = {name: 'NODE_ENV'}; -const processId = {name: 'process'}; - -const dev = {name: '__DEV__'}; - -const importMap = new Map([['ReactNative', 'react-native']]); - -const isGlobal = binding => !binding; - -const isFlowDeclared = binding => - t.isDeclareVariable(binding.path); - -const isGlobalOrFlowDeclared = binding => - isGlobal(binding) || isFlowDeclared(binding); - -const isToplevelBinding = (binding, isWrappedModule) => - isGlobal(binding) || - !binding.scope.parent || - isWrappedModule && !binding.scope.parent.parent; - -const isRequireCall = (node, dependencyId, scope) => - t.isCallExpression(node) && - t.isIdentifier(node.callee, requirePattern) && - checkRequireArgs(node.arguments, dependencyId); - -const isImport = (node, scope, patterns) => - patterns.some(pattern => { - const importName = importMap.get(pattern.name) || pattern.name; - return isRequireCall(node, importName, scope); - }); - -function isImportOrGlobal(node, scope, patterns, isWrappedModule) { - const identifier = patterns.find(pattern => t.isIdentifier(node, pattern)); - return ( - identifier && - isToplevelBinding(scope.getBinding(identifier.name), isWrappedModule) || - isImport(node, scope, patterns) - ); -} - -const isPlatformOS = (node, scope, isWrappedModule) => - t.isIdentifier(node.property, os) && - isImportOrGlobal(node.object, scope, [platform], isWrappedModule); - -const isReactPlatformOS = (node, scope, isWrappedModule) => - t.isIdentifier(node.property, os) && - t.isMemberExpression(node.object) && - t.isIdentifier(node.object.property, platform) && - isImportOrGlobal( - node.object.object, scope, [React, ReactNative], isWrappedModule); - -const isProcessEnvNodeEnv = (node, scope) => - t.isIdentifier(node.property, nodeEnv) && - t.isMemberExpression(node.object) && - t.isIdentifier(node.object.property, env) && - t.isIdentifier(node.object.object, processId) && - isGlobal(scope.getBinding(processId.name)); - -const isPlatformSelect = (node, scope, isWrappedModule) => - t.isMemberExpression(node.callee) && - t.isIdentifier(node.callee.object, platform) && - t.isIdentifier(node.callee.property, select) && - isImportOrGlobal(node.callee.object, scope, [platform], isWrappedModule); - -const isReactPlatformSelect = (node, scope, isWrappedModule) => - t.isMemberExpression(node.callee) && - t.isIdentifier(node.callee.property, select) && - t.isMemberExpression(node.callee.object) && - t.isIdentifier(node.callee.object.property, platform) && - isImportOrGlobal( - node.callee.object.object, scope, [React, ReactNative], isWrappedModule); - -const isDev = (node, parent, scope) => - t.isIdentifier(node, dev) && - isGlobalOrFlowDeclared(scope.getBinding(dev.name)) && - !(t.isMemberExpression(parent)); - -function findProperty(objectExpression, key, fallback) { - const property = objectExpression.properties.find(p => p.key.name === key); - return property ? property.value : fallback(); -} - -const inlinePlugin = { - visitor: { - Identifier(path, state) { - if (isDev(path.node, path.parent, path.scope)) { - path.replaceWith(t.booleanLiteral(state.opts.dev)); - } - }, - MemberExpression(path, state) { - const node = path.node; - const scope = path.scope; - const opts = state.opts; - - if ( - isPlatformOS(node, scope, opts.isWrapped) || - isReactPlatformOS(node, scope, opts.isWrapped) - ) { - path.replaceWith(t.stringLiteral(opts.platform)); - } else if (isProcessEnvNodeEnv(node, scope)) { - path.replaceWith( - t.stringLiteral(opts.dev ? 'development' : 'production')); - } - }, - CallExpression(path, state) { - const node = path.node; - const scope = path.scope; - const arg = node.arguments[0]; - const opts = state.opts; - - if ( - isPlatformSelect(node, scope, opts.isWrapped) || - isReactPlatformSelect(node, scope, opts.isWrapped) - ) { - const fallback = () => - findProperty(arg, 'default', () => t.identifier('undefined')); - const replacement = t.isObjectExpression(arg) - ? findProperty(arg, opts.platform, fallback) - : node; - - path.replaceWith(replacement); - } - }, - }, -}; - -const plugin = () => inlinePlugin; - -function checkRequireArgs(args, dependencyId) { - const pattern = t.stringLiteral(dependencyId); - return t.isStringLiteral(args[0], pattern) || - t.isMemberExpression(args[0]) && - t.isNumericLiteral(args[0].property) && - t.isStringLiteral(args[1], pattern); -} - -type AstResult = { - ast: Ast, - code: ?string, - map: ?MappingsMap, -}; - -function inline( - filename: string, - transformResult: {ast?: ?Ast, code: string, map: ?MappingsMap}, - options: {+dev: boolean, +platform: ?string}, -): AstResult { - const code = transformResult.code; - const babelOptions = { - filename, - plugins: [[plugin, options]], - inputSourceMap: transformResult.map, - sourceMaps: true, - sourceFileName: filename, - code: false, - babelrc: false, - compact: true, - }; - - const result = transformResult.ast - ? babel.transformFromAst(transformResult.ast, code, babelOptions) - : babel.transform(code, babelOptions); - const {ast} = result; - invariant(ast != null, 'Missing AST in babel transform results.'); - return {ast, code: result.code, map: result.map}; -} - -inline.plugin = inlinePlugin; -module.exports = inline; diff --git a/packager/src/JSTransformer/worker/minify.js b/packager/src/JSTransformer/worker/minify.js deleted file mode 100644 index 54f3c6384fb190..00000000000000 --- a/packager/src/JSTransformer/worker/minify.js +++ /dev/null @@ -1,34 +0,0 @@ -/** - * Copyright (c) 2016-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. - * - * @flow - */ - -'use strict'; - -const uglify = require('uglify-js'); - -const {UGLIFY_JS_OUTPUT_OPTIONS} = require('./JsMinification'); - -import type {MappingsMap} from '../../lib/SourceMap'; - -function minify(filename: string, inputCode: string, sourceMap: ?MappingsMap) { - const result = uglify.minify(inputCode, { - fromString: true, - inSourceMap: sourceMap, - outSourceMap: true, - output: UGLIFY_JS_OUTPUT_OPTIONS, - }); - - const code = result.code; - const map = JSON.parse(result.map); - map.sources = [filename]; - return {code, map}; -} - -module.exports = minify; diff --git a/packager/src/Logger/Types.js b/packager/src/Logger/Types.js deleted file mode 100644 index b2dfab4b42de6e..00000000000000 --- a/packager/src/Logger/Types.js +++ /dev/null @@ -1,33 +0,0 @@ -/** - * 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. - * - * @flow - * - */ -'use strict'; - -export type ActionLogEntryData = { - action_name: string, -}; - -export type ActionStartLogEntry = { - action_name?: string, - action_phase?: string, - log_entry_label: string, - log_session?: string, - start_timestamp?: [number, number], -}; - -export type LogEntry = { - action_name?: string, - action_phase?: string, - duration_ms?: number, - log_entry_label: string, - log_session?: string, - start_timestamp?: [number, number], -}; diff --git a/packager/src/Logger/__mocks__/chalk.js b/packager/src/Logger/__mocks__/chalk.js deleted file mode 100644 index bff20b0aaf039a..00000000000000 --- a/packager/src/Logger/__mocks__/chalk.js +++ /dev/null @@ -1,37 +0,0 @@ -/** - * 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 mockColor = () => { - return { - bold: () => { return { }; }, - }; -}; - -mockColor.bold = function() { - return {}; -}; - -mockColor.bgRed = function() { - return {}; -}; - -module.exports = { - dim: s => s, - magenta: mockColor, - white: mockColor, - blue: mockColor, - yellow: mockColor, - green: mockColor, - bold: mockColor, - red: mockColor, - cyan: mockColor, - gray: mockColor, - black: mockColor, -}; diff --git a/packager/src/Logger/__tests__/Logger-test.js b/packager/src/Logger/__tests__/Logger-test.js deleted file mode 100644 index 96d10c3cfad369..00000000000000 --- a/packager/src/Logger/__tests__/Logger-test.js +++ /dev/null @@ -1,66 +0,0 @@ -/** - * 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. - * - * eslint-disable no-console-disallow - * - */ -'use strict'; - -jest.disableAutomock(); - -const { - createEntry, - createActionStartEntry, - createActionEndEntry, -} = require('../'); - -describe('Logger', () => { - const originalConsoleLog = console.log; - - beforeEach(() => { - console.log = jest.fn(); - }); - - afterEach(() => { - console.log = originalConsoleLog; - }); - - it('creates simple log entries', () => { - const logEntry = createEntry('Test'); - expect(logEntry).toEqual({ - log_entry_label: 'Test', - log_session: jasmine.any(String), - packager_version: jasmine.any(String), - }); - }); - - it('creates action start log entries', () => { - const actionStartLogEntry = createActionStartEntry('Test'); - expect(actionStartLogEntry).toEqual({ - action_name: 'Test', - action_phase: 'start', - log_entry_label: 'Test', - log_session: jasmine.any(String), - packager_version: jasmine.any(String), - start_timestamp: jasmine.any(Object), - }); - }); - - it('creates action end log entries', () => { - const actionEndLogEntry = createActionEndEntry(createActionStartEntry('Test')); - expect(actionEndLogEntry).toEqual({ - action_name: 'Test', - action_phase: 'end', - duration_ms: jasmine.any(Number), - log_entry_label: 'Test', - log_session: jasmine.any(String), - packager_version: jasmine.any(String), - start_timestamp: jasmine.any(Object), - }); - }); -}); diff --git a/packager/src/Logger/index.js b/packager/src/Logger/index.js deleted file mode 100644 index db53a1fca68b99..00000000000000 --- a/packager/src/Logger/index.js +++ /dev/null @@ -1,90 +0,0 @@ -/** - * 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. - * - * @flow - */ - -'use strict'; - -const os = require('os'); - -const {EventEmitter} = require('events'); - -const VERSION = require('../../package.json').version; - -import type { - ActionLogEntryData, - ActionStartLogEntry, - LogEntry, -} from './Types'; - -const log_session = `${os.hostname()}-${Date.now()}`; -const eventEmitter = new EventEmitter(); - -function on(event: string, handler: (logEntry: LogEntry) => void): void { - eventEmitter.on(event, handler); -} - -function createEntry(data: LogEntry | string): LogEntry { - const logEntry = typeof data === 'string' ? {log_entry_label: data} : data; - - return { - ...logEntry, - log_session, - packager_version: VERSION, - }; -} - -function createActionStartEntry(data: ActionLogEntryData | string): LogEntry { - const logEntry = typeof data === 'string' ? {action_name: data} : data; - const {action_name} = logEntry; - - return createEntry({ - ...logEntry, - action_name, - action_phase: 'start', - log_entry_label: action_name, - start_timestamp: process.hrtime(), - }); -} - -function createActionEndEntry(logEntry: ActionStartLogEntry): LogEntry { - const { - action_name, - action_phase, - start_timestamp, - } = logEntry; - - if (action_phase !== 'start' || !Array.isArray(start_timestamp)) { - throw new Error('Action has not started or has already ended'); - } - - const timeDelta = process.hrtime(start_timestamp); - const duration_ms = Math.round((timeDelta[0] * 1e9 + timeDelta[1]) / 1e6); - - return createEntry({ - ...logEntry, - action_name, - action_phase: 'end', - duration_ms, - log_entry_label: action_name, - }); -} - -function log(logEntry: LogEntry): LogEntry { - eventEmitter.emit('log', logEntry); - return logEntry; -} - -module.exports = { - on, - createEntry, - createActionStartEntry, - createActionEndEntry, - log, -}; diff --git a/packager/src/ModuleGraph/Graph.js b/packager/src/ModuleGraph/Graph.js deleted file mode 100644 index 7da3baf0b2edd0..00000000000000 --- a/packager/src/ModuleGraph/Graph.js +++ /dev/null @@ -1,170 +0,0 @@ -/** - * Copyright (c) 2016-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. - * - * @flow - */ -'use strict'; - -const emptyFunction = require('fbjs/lib/emptyFunction'); -const invariant = require('fbjs/lib/invariant'); -const memoize = require('async/memoize'); -const emptyModule = require('./module').empty; -const nullthrows = require('fbjs/lib/nullthrows'); -const queue = require('async/queue'); -const seq = require('async/seq'); - -import type { - Callback, - File, - GraphFn, - LoadFn, - ResolveFn, -} from './types.flow'; - -type Async$Queue = { - buffer: number, - concurrency: number, - drain: () => mixed, - empty: () => mixed, - error: (Error, T) => mixed, - idle(): boolean, - kill(): void, - length(): number, - pause(): void, - paused: boolean, - push(T | Array, void | C): void, - resume(): void, - running(): number, - saturated: () => mixed, - started: boolean, - unsaturated: () => mixed, - unshift(T, void | C): void, - workersList(): Array, -}; - -type LoadQueue = - Async$Queue<{id: string, parent: ?string}, Callback>>; - -const NO_OPTIONS = {}; - -exports.create = function create(resolve: ResolveFn, load: LoadFn): GraphFn { - function Graph(entryPoints, platform, options, callback = emptyFunction) { - const { - log = (console: any), - optimize = false, - skip, - } = options || NO_OPTIONS; - - if (typeof platform !== 'string') { - log.error('`Graph`, called without a platform'); - callback(Error('The target platform has to be passed')); - return; - } - - const loadQueue: LoadQueue = queue(seq( - ({id, parent}, cb) => resolve(id, parent, platform, options || NO_OPTIONS, cb), - memoize((file, cb) => load(file, {log, optimize}, cb)), - ), Number.MAX_SAFE_INTEGER); - - const {collect, loadModule} = createGraphHelpers(loadQueue, skip); - - loadQueue.drain = () => { - loadQueue.kill(); - callback(null, collect()); - }; - loadQueue.error = error => { - loadQueue.error = emptyFunction; - loadQueue.kill(); - callback(error); - }; - - let i = 0; - for (const entryPoint of entryPoints) { - loadModule(entryPoint, null, i++); - } - - if (i === 0) { - log.error('`Graph` called without any entry points'); - loadQueue.kill(); - callback(Error('At least one entry point has to be passed.')); - } - } - - return Graph; -}; - -function createGraphHelpers(loadQueue, skip) { - const modules = new Map([[null, emptyModule()]]); - - function collect( - path = null, - serialized = {entryModules: [], modules: []}, - seen = new Set(), - ) { - const module = modules.get(path); - if (module == null || seen.has(path)) { - return serialized; - } - - const {dependencies} = module; - if (path === null) { - serialized.entryModules = - dependencies.map(dep => nullthrows(modules.get(dep.path))); - } else { - serialized.modules.push(module); - seen.add(path); - } - - for (const dependency of dependencies) { - collect(dependency.path, serialized, seen); - } - - return serialized; - } - - function loadModule(id, parent, parentDepIndex) { - loadQueue.push( - {id, parent}, - (error, file, dependencyIDs) => - onFileLoaded(error, file, dependencyIDs, id, parent, parentDepIndex), - ); - } - - function onFileLoaded( - error, - file, - dependencyIDs, - id, - parent, - parentDependencyIndex, - ) { - if (error) { - return; - } - - const {path} = nullthrows(file); - dependencyIDs = nullthrows(dependencyIDs); - - const parentModule = modules.get(parent); - invariant(parentModule, 'Invalid parent module: ' + String(parent)); - parentModule.dependencies[parentDependencyIndex] = {id, path}; - - if ((!skip || !skip.has(path)) && !modules.has(path)) { - const module = { - dependencies: Array(dependencyIDs.length), - file: nullthrows(file), - }; - modules.set(path, module); - for (let i = 0; i < dependencyIDs.length; ++i) { - loadModule(dependencyIDs[i], path, i); - } - } - } - - return {collect, loadModule}; -} diff --git a/packager/src/ModuleGraph/ModuleGraph.js b/packager/src/ModuleGraph/ModuleGraph.js deleted file mode 100644 index d6eea08aa76930..00000000000000 --- a/packager/src/ModuleGraph/ModuleGraph.js +++ /dev/null @@ -1,113 +0,0 @@ -/** - * Copyright (c) 2016-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. - * - * @flow - */ -'use strict'; - -const defaults = require('../defaults'); -const nullthrows = require('fbjs/lib/nullthrows'); -const parallel = require('async/parallel'); -const seq = require('async/seq'); -const virtualModule = require('./module').virtual; - -import type { - BuildResult, - Callback, - GraphFn, - GraphResult, - Module, - PostProcessModules, -} from './types.flow'; - -type BuildFn = ( - entryPoints: Iterable, - options: BuildOptions, - callback: Callback, -) => void; - -type BuildOptions = {| - optimize: boolean, - platform: string, -|}; - -exports.createBuildSetup = ( - graph: GraphFn, - postProcessModules: PostProcessModules, - translateDefaultsPath: string => string = x => x, -): BuildFn => - (entryPoints, options, callback) => { - const { - optimize = false, - platform = defaults.platforms[0], - } = options; - const graphOptions = {optimize}; - - const graphWithOptions = - (entry, cb) => graph(entry, platform, graphOptions, cb); - const graphOnlyModules = seq(graphWithOptions, getModules); - - parallel({ - graph: cb => graphWithOptions(entryPoints, (error, result) => { - if (error) { - cb(error); - return; - } - /* $FlowFixMe: not undefined if there is no error */ - const {modules, entryModules} = result; - const prModules = postProcessModules(modules, [...entryPoints]); - cb(null, {modules: prModules, entryModules}); - }), - moduleSystem: cb => graphOnlyModules( - [translateDefaultsPath(defaults.moduleSystem)], - cb, - ), - polyfills: cb => graphOnlyModules( - defaults.polyfills.map(translateDefaultsPath), - cb, - ), - }, ( - error: ?Error, - result?: {graph: GraphResult, moduleSystem: Array, polyfills: Array}, - ) => { - if (error) { - callback(error); - return; - } - - - const { - graph: {modules, entryModules}, - moduleSystem, - polyfills, - } = nullthrows(result); - - const preludeScript = prelude(optimize); - const prependedScripts = [preludeScript, ...moduleSystem, ...polyfills]; - callback(null, { - entryModules, - modules: concat(prependedScripts, modules), - prependedScripts, - }); - }); - }; - -const getModules = (x, cb) => cb(null, x.modules); - -function* concat(...iterables: Array>): Iterable { - for (const it of iterables) { - yield* it; - } -} - -function prelude(optimize) { - return virtualModule( - `var __DEV__=${String(!optimize)},` + - '__BUNDLE_START_TIME__=this.nativePerformanceNow?nativePerformanceNow():Date.now();' - ); -} diff --git a/packager/src/ModuleGraph/__tests__/Graph-test.js b/packager/src/ModuleGraph/__tests__/Graph-test.js deleted file mode 100644 index 032bd3fda398a3..00000000000000 --- a/packager/src/ModuleGraph/__tests__/Graph-test.js +++ /dev/null @@ -1,378 +0,0 @@ -/** - * Copyright (c) 2016-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'; - -jest - .disableAutomock() - .useRealTimers() - .mock('console'); - -const {Console} = require('console'); -const Graph = require('../Graph'); -const {fn} = require('../test-helpers'); - -const {any, objectContaining} = jasmine; -const quiet = new Console(); - -describe('Graph:', () => { - const anyEntry = ['arbitrary/entry/point']; - const anyPlatform = 'arbitrary platform'; - const noOpts = undefined; - - let graph, load, resolve; - beforeEach(() => { - load = fn(); - resolve = fn(); - resolve.stub.yields(null, 'arbitrary file'); - load.stub.yields(null, createFile('arbitrary file'), []); - - graph = Graph.create(resolve, load); - }); - - it('calls back an error when called without any entry point', done => { - graph([], anyPlatform, {log: quiet}, error => { - expect(error).toEqual(any(Error)); - done(); - }); - }); - - it('resolves the entry point with the passed-in `resolve` function', done => { - const entryPoint = '/arbitrary/path'; - graph([entryPoint], anyPlatform, noOpts, () => { - expect(resolve).toBeCalledWith( - entryPoint, null, any(String), any(Object), any(Function)); - done(); - }); - }); - - it('allows to specify multiple entry points', done => { - const entryPoints = ['Arbitrary', '../entry.js']; - graph(entryPoints, anyPlatform, noOpts, () => { - expect(resolve).toBeCalledWith( - entryPoints[0], null, any(String), any(Object), any(Function)); - expect(resolve).toBeCalledWith( - entryPoints[1], null, any(String), any(Object), any(Function)); - done(); - }); - - }); - - it('calls back with an error when called without `platform` option', done => { - graph(anyEntry, undefined, {log: quiet}, error => { - expect(error).toEqual(any(Error)); - done(); - }); - }); - - it('forwards a passed-in `platform` to `resolve`', done => { - const platform = 'any'; - graph(anyEntry, platform, noOpts, () => { - expect(resolve).toBeCalledWith( - any(String), null, platform, any(Object), any(Function)); - done(); - }); - }); - - it('forwards a passed-in `log` option to `resolve`', done => { - const log = new Console(); - graph(anyEntry, anyPlatform, {log}, () => { - expect(resolve).toBeCalledWith( - any(String), null, any(String), objectContaining({log}), any(Function)); - done(); - }); - }); - - it('calls back with every error produced by `resolve`', done => { - const error = Error(); - resolve.stub.yields(error); - graph(anyEntry, anyPlatform, noOpts, e => { - expect(e).toBe(error); - done(); - }); - }); - - it('only calls back once if two parallel invocations of `resolve` fail', done => { - load.stub.yields(null, createFile('with two deps'), ['depA', 'depB']); - resolve.stub - .withArgs('depA').yieldsAsync(new Error()) - .withArgs('depB').yieldsAsync(new Error()); - - let calls = 0; - function callback() { - if (calls === 0) { - process.nextTick(() => { - expect(calls).toEqual(1); - done(); - }); - } - ++calls; - } - - graph(['entryA', 'entryB'], anyPlatform, noOpts, callback); - }); - - it('passes the files returned by `resolve` on to the `load` function', done => { - const modules = new Map([ - ['Arbitrary', '/absolute/path/to/Arbitrary.js'], - ['../entry.js', '/whereever/is/entry.js'], - ]); - for (const [id, file] of modules) { - resolve.stub.withArgs(id).yields(null, file); - } - const [file1, file2] = modules.values(); - - graph(modules.keys(), anyPlatform, noOpts, () => { - expect(load).toBeCalledWith(file1, any(Object), any(Function)); - expect(load).toBeCalledWith(file2, any(Object), any(Function)); - done(); - }); - }); - - it('passes the `optimize` flag on to `load`', done => { - graph(anyEntry, anyPlatform, {optimize: true}, () => { - expect(load).toBeCalledWith( - any(String), objectContaining({optimize: true}), any(Function)); - done(); - }); - }); - - it('uses `false` as the default for the `optimize` flag', done => { - graph(anyEntry, anyPlatform, noOpts, () => { - expect(load).toBeCalledWith( - any(String), objectContaining({optimize: false}), any(Function)); - done(); - }); - }); - - it('forwards a passed-in `log` to `load`', done => { - const log = new Console(); - graph(anyEntry, anyPlatform, {log}, () => { - expect(load) - .toBeCalledWith(any(String), objectContaining({log}), any(Function)); - done(); - }); - }); - - it('calls back with every error produced by `load`', done => { - const error = Error(); - load.stub.yields(error); - graph(anyEntry, anyPlatform, noOpts, e => { - expect(e).toBe(error); - done(); - }); - }); - - it('resolves any dependencies provided by `load`', done => { - const entryPath = '/path/to/entry.js'; - const id1 = 'required/id'; - const id2 = './relative/import'; - resolve.stub.withArgs('entry').yields(null, entryPath); - load.stub.withArgs(entryPath) - .yields(null, {path: entryPath}, [id1, id2]); - - graph(['entry'], anyPlatform, noOpts, () => { - expect(resolve).toBeCalledWith( - id1, entryPath, any(String), any(Object), any(Function)); - expect(resolve).toBeCalledWith( - id2, entryPath, any(String), any(Object), any(Function)); - done(); - }); - }); - - it('loads transitive dependencies', done => { - const entryPath = '/path/to/entry.js'; - const id1 = 'required/id'; - const id2 = './relative/import'; - const path1 = '/path/to/dep/1'; - const path2 = '/path/to/dep/2'; - - resolve.stub - .withArgs(id1).yields(null, path1) - .withArgs(id2).yields(null, path2) - .withArgs('entry').yields(null, entryPath); - load.stub - .withArgs(entryPath).yields(null, {path: entryPath}, [id1]) - .withArgs(path1).yields(null, {path: path1}, [id2]); - - graph(['entry'], anyPlatform, noOpts, () => { - expect(resolve).toBeCalledWith(id2, path1, any(String), any(Object), any(Function)); - expect(load).toBeCalledWith(path1, any(Object), any(Function)); - expect(load).toBeCalledWith(path2, any(Object), any(Function)); - done(); - }); - }); - - it('resolves modules in depth-first traversal order, regardless of the order of resolution', - done => { - load.stub.reset(); - resolve.stub.reset(); - - const ids = [ - 'a', - 'b', - 'c', 'd', - 'e', - 'f', 'g', - 'h', - ]; - ids.forEach(id => { - const path = idToPath(id); - resolve.stub.withArgs(id).yields(null, path); - load.stub.withArgs(path).yields(null, createFile(id), []); - }); - load.stub.withArgs(idToPath('a')).yields(null, createFile('a'), ['b', 'e', 'h']); - load.stub.withArgs(idToPath('b')).yields(null, createFile('b'), ['c', 'd']); - load.stub.withArgs(idToPath('e')).yields(null, createFile('e'), ['f', 'g']); - - // load certain ids later - ['b', 'e', 'h'].forEach(id => resolve.stub.withArgs(id).resetBehavior()); - resolve.stub.withArgs('h').func = (a, b, c, d, callback) => { - callback(null, idToPath('h')); - ['e', 'b'].forEach( - id => resolve.stub.withArgs(id).yield(null, idToPath(id))); - }; - - graph(['a'], anyPlatform, noOpts, (error, result) => { - expect(error).toEqual(null); - expect(result.modules).toEqual([ - createModule('a', ['b', 'e', 'h']), - createModule('b', ['c', 'd']), - createModule('c'), - createModule('d'), - createModule('e', ['f', 'g']), - createModule('f'), - createModule('g'), - createModule('h'), - ]); - done(); - }); - }, - ); - - it('calls back with the resolved modules of the entry points', done => { - load.stub.reset(); - resolve.stub.reset(); - - load.stub.withArgs(idToPath('a')).yields(null, createFile('a'), ['b']); - load.stub.withArgs(idToPath('b')).yields(null, createFile('b'), []); - load.stub.withArgs(idToPath('c')).yields(null, createFile('c'), ['d']); - load.stub.withArgs(idToPath('d')).yields(null, createFile('d'), []); - - 'abcd'.split('') - .forEach(id => resolve.stub.withArgs(id).yields(null, idToPath(id))); - - graph(['a', 'c'], anyPlatform, noOpts, (error, result) => { - expect(result.entryModules).toEqual([ - createModule('a', ['b']), - createModule('c', ['d']), - ]); - done(); - }); - }); - - it('resolves modules for all entry points correctly if one is a dependency of another', done => { - load.stub.reset(); - resolve.stub.reset(); - - load.stub.withArgs(idToPath('a')).yields(null, createFile('a'), ['b']); - load.stub.withArgs(idToPath('b')).yields(null, createFile('b'), []); - - 'ab'.split('') - .forEach(id => resolve.stub.withArgs(id).yields(null, idToPath(id))); - - graph(['a', 'b'], anyPlatform, noOpts, (error, result) => { - expect(result.entryModules).toEqual([ - createModule('a', ['b']), - createModule('b', []), - ]); - done(); - }); - }); - - it('does not include dependencies more than once', done => { - const ids = ['a', 'b', 'c', 'd']; - ids.forEach(id => { - const path = idToPath(id); - resolve.stub.withArgs(id).yields(null, path); - load.stub.withArgs(path).yields(null, createFile(id), []); - }); - ['a', 'd'].forEach(id => - load.stub - .withArgs(idToPath(id)).yields(null, createFile(id), ['b', 'c'])); - - graph(['a', 'd', 'b'], anyPlatform, noOpts, (error, result) => { - expect(error).toEqual(null); - expect(result.modules).toEqual([ - createModule('a', ['b', 'c']), - createModule('b'), - createModule('c'), - createModule('d', ['b', 'c']), - ]); - done(); - }); - }); - - it('handles dependency cycles', done => { - resolve.stub - .withArgs('a').yields(null, idToPath('a')) - .withArgs('b').yields(null, idToPath('b')) - .withArgs('c').yields(null, idToPath('c')); - load.stub - .withArgs(idToPath('a')).yields(null, createFile('a'), ['b']) - .withArgs(idToPath('b')).yields(null, createFile('b'), ['c']) - .withArgs(idToPath('c')).yields(null, createFile('c'), ['a']); - - graph(['a'], anyPlatform, noOpts, (error, result) => { - expect(result.modules).toEqual([ - createModule('a', ['b']), - createModule('b', ['c']), - createModule('c', ['a']), - ]); - done(); - }); - }); - - it('can skip files', done => { - ['a', 'b', 'c', 'd', 'e'].forEach( - id => resolve.stub.withArgs(id).yields(null, idToPath(id))); - load.stub - .withArgs(idToPath('a')).yields(null, createFile('a'), ['b', 'c', 'd']) - .withArgs(idToPath('b')).yields(null, createFile('b'), ['e']); - ['c', 'd', 'e'].forEach(id => - load.stub.withArgs(idToPath(id)).yields(null, createFile(id), [])); - const skip = new Set([idToPath('b'), idToPath('c')]); - - graph(['a'], anyPlatform, {skip}, (error, result) => { - expect(result.modules).toEqual([ - createModule('a', ['b', 'c', 'd']), - createModule('d', []), - ]); - done(); - }); - }); -}); - -function createDependency(id) { - return {id, path: idToPath(id)}; -} - -function createFile(id) { - return {ast: {}, path: idToPath(id)}; -} - -function createModule(id, dependencies = []): Module { - return { - file: createFile(id), - dependencies: dependencies.map(createDependency), - }; -} - -function idToPath(id) { - return '/path/to/' + id; -} diff --git a/packager/src/ModuleGraph/__tests__/ModuleGraph-test.js b/packager/src/ModuleGraph/__tests__/ModuleGraph-test.js deleted file mode 100644 index 8efc533f62f151..00000000000000 --- a/packager/src/ModuleGraph/__tests__/ModuleGraph-test.js +++ /dev/null @@ -1,109 +0,0 @@ -/** - * Copyright (c) 2017-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'; - -jest.disableAutomock(); - -const ModuleGraph = require('../ModuleGraph'); -const defaults = require('../../defaults'); - -const FILE_TYPE = 'module'; - -describe('build setup', () => { - const buildSetup = ModuleGraph.createBuildSetup(graph, mds => { - return [...mds].sort((l, r) => l.file.path > r.file.path); - }); - const noOptions = {}; - const noEntryPoints = []; - - it('adds a prelude containing start time and `__DEV__` to the build', done => { - buildSetup(noEntryPoints, noOptions, (error, result) => { - expect(error).toEqual(null); - - const [prelude] = result.modules; - expect(prelude).toEqual({ - dependencies: [], - file: { - code: 'var __DEV__=true,__BUNDLE_START_TIME__=' + - 'this.nativePerformanceNow?nativePerformanceNow():Date.now();', - map: null, - path: '', - type: 'script', - }, - }); - done(); - }); - }); - - it('sets `__DEV__` to false in the prelude if optimization is enabled', done => { - buildSetup(noEntryPoints, {optimize: true}, (error, result) => { - const [prelude] = result.modules; - expect(prelude.file.code) - .toEqual('var __DEV__=false,__BUNDLE_START_TIME__=' + - 'this.nativePerformanceNow?nativePerformanceNow():Date.now();'); - done(); - }); - }); - - it('places the module system implementation directly after the prelude', done => { - buildSetup(noEntryPoints, noOptions, (error, result) => { - const [, moduleSystem] = result.modules; - expect(moduleSystem).toEqual({ - dependencies: [], - file: { - code: '', - path: defaults.moduleSystem, - type: FILE_TYPE, - }, - }); - done(); - }); - }); - - it('places polyfills after the module system', done => { - buildSetup(noEntryPoints, noOptions, (error, result) => { - const polyfills = - Array.from(result.modules).slice(2, 2 + defaults.polyfills.length); - expect(polyfills).toEqual(defaults.polyfills.map(moduleFromPath)); - done(); - }); - }); - - it('places all entry points and dependencies at the end, post-processed', done => { - const entryPoints = ['b', 'c', 'd']; - buildSetup(entryPoints, noOptions, (error, result) => { - expect(Array.from(result.modules).slice(-4)) - .toEqual(['a', 'b', 'c', 'd'].map(moduleFromPath)); - done(); - }); - }); -}); - -function moduleFromPath(path) { - return { - dependencies: path === 'b' ? ['a'] : [], - file: { - code: '', - path, - type: FILE_TYPE, - }, - }; -} - -function graph(entryPoints, platform, options, callback) { - const modules = Array.from(entryPoints, moduleFromPath); - const depModules = Array.prototype.concat.apply( - [], - modules.map(x => x.dependencies.map(moduleFromPath)), - ); - callback(null, { - entryModules: modules, - modules: modules.concat(depModules), - }); -} diff --git a/packager/src/ModuleGraph/module.js b/packager/src/ModuleGraph/module.js deleted file mode 100644 index 05f8e72fb71399..00000000000000 --- a/packager/src/ModuleGraph/module.js +++ /dev/null @@ -1,28 +0,0 @@ -/** - * Copyright (c) 2016-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. - * - * @flow - */ -'use strict'; - - -import type {Module} from './types.flow'; - -exports.empty = (): Module => virtual(''); - -// creates a virtual module (i.e. not corresponding to a file on disk) -// with the given source code. -const virtual = exports.virtual = (code: string): Module => ({ - dependencies: [], - file: { - code, - map: null, - path: '', - type: 'script', - }, -}); diff --git a/packager/src/ModuleGraph/node-haste/HasteFS.js b/packager/src/ModuleGraph/node-haste/HasteFS.js deleted file mode 100644 index c48f7c25ff6f35..00000000000000 --- a/packager/src/ModuleGraph/node-haste/HasteFS.js +++ /dev/null @@ -1,90 +0,0 @@ -/** - * 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. - * - * @flow - */ - -'use strict'; - -const {dirname, join, parse} = require('path'); - -module.exports = class HasteFS { - directories: Set; - directoryEntries: Map>; - files: Set; - - constructor(files: Array) { - this.directories = buildDirectorySet(files); - this.directoryEntries = buildDirectoryEntries(files.map(parse)); - this.files = new Set(files); - } - - closest(path: string, fileName: string): ?string { - const parsedPath = parse(path); - const root = parsedPath.root; - let dir = parsedPath.dir; - do { - const candidate = join(dir, fileName); - if (this.files.has(candidate)) { - return candidate; - } - dir = dirname(dir); - } while (dir !== '.' && dir !== root); - return null; - } - - dirExists(path: string) { - return this.directories.has(path); - } - - exists(path: string) { - return this.files.has(path); - } - - getAllFiles() { - return Array.from(this.files.keys()); - } - - matchFiles() { - throw new Error( - 'HasteFS.matchFiles is not implemented yet.' - ); - } - - matches(directory: string, pattern: RegExp) { - const entries = this.directoryEntries.get(directory); - return entries ? entries.filter(pattern.test, pattern) : []; - } -}; - -function buildDirectorySet(files) { - const directories = new Set(); - files.forEach(path => { - const parsedPath = parse(path); - const root = parsedPath.root; - let dir = parsedPath.dir; - while (dir !== '.' && dir !== root && !directories.has(dir)) { - directories.add(dir); - dir = dirname(dir); - } - }); - return directories; -} - -function buildDirectoryEntries(files) { - const directoryEntries = new Map(); - files.forEach(({base, dir}) => { - const entries = directoryEntries.get(dir); - if (entries) { - entries.push(base); - } else { - directoryEntries.set(dir, [base]); - } - }); - return directoryEntries; -} diff --git a/packager/src/ModuleGraph/node-haste/Module.js b/packager/src/ModuleGraph/node-haste/Module.js deleted file mode 100644 index 6e00ea6d7ced50..00000000000000 --- a/packager/src/ModuleGraph/node-haste/Module.js +++ /dev/null @@ -1,64 +0,0 @@ -/** - * 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. - * - * @flow - */ - -'use strict'; - -import type {CachedReadResult, ReadResult} from '../../node-haste/Module'; -import type {TransformedCodeFile} from '../types.flow'; -import type ModuleCache from './ModuleCache'; - -module.exports = class Module { - hasteID: ?string; - moduleCache: ModuleCache; - name: Promise; - path: string; - type: 'Module'; - - constructor( - path: string, - moduleCache: ModuleCache, - info: TransformedCodeFile, - ) { - this.hasteID = info.hasteID; - this.moduleCache = moduleCache; - this.name = Promise.resolve(this.hasteID || getName(path)); - this.path = path; - this.type = 'Module'; - } - - readCached(): CachedReadResult { - throw new Error('not implemented'); - } - - readFresh(): Promise { - return Promise.reject(new Error('not implemented')); - } - - getName() { - return this.name; - } - - getPackage() { - return this.moduleCache.getPackageOf(this.path); - } - - isHaste() { - return Boolean(this.hasteID); - } - - hash() { - throw new Error('not implemented'); - } -}; - -function getName(path) { - return path.replace(/^.*[\/\\]node_modules[\///]/, ''); -} diff --git a/packager/src/ModuleGraph/node-haste/ModuleCache.js b/packager/src/ModuleGraph/node-haste/ModuleCache.js deleted file mode 100644 index 0062e32d9fbe73..00000000000000 --- a/packager/src/ModuleGraph/node-haste/ModuleCache.js +++ /dev/null @@ -1,71 +0,0 @@ -/** - * 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. - * - * @flow - */ - -'use strict'; - -const Module = require('./Module'); -const Package = require('./Package'); - -import type {PackageData, TransformedCodeFile} from '../types.flow'; - -type GetClosestPackageFn = (filePath: string) => ?string; - -module.exports = class ModuleCache { - _getClosestPackage: GetClosestPackageFn; - getTransformedFile: string => TransformedCodeFile; - modules: Map; - packages: Map; - - constructor( - getClosestPackage: GetClosestPackageFn, - getTransformedFile: string => TransformedCodeFile, - ) { - this._getClosestPackage = getClosestPackage; - this.getTransformedFile = getTransformedFile; - this.modules = new Map(); - this.packages = new Map(); - } - - getAssetModule(path: string): Module { - return this.getModule(path); - } - - getModule(path: string): Module { - let m = this.modules.get(path); - if (!m) { - m = new Module(path, this, this.getTransformedFile(path)); - this.modules.set(path, m); - } - return m; - } - - getPackage(path: string): Package { - let p = this.packages.get(path); - if (!p) { - p = new Package(path, this.getPackageData(path)); - this.packages.set(path, p); - } - return p; - } - - getPackageData(path: string): PackageData { - const pkg = this.getTransformedFile(path).package; - if (!pkg) { - throw new Error(`"${path}" does not exist`); - } - return pkg; - } - - getPackageOf(filePath: string): ?Package { - const candidate = this._getClosestPackage(filePath); - return candidate != null ? this.getPackage(candidate) : null; - } -}; diff --git a/packager/src/ModuleGraph/node-haste/Package.js b/packager/src/ModuleGraph/node-haste/Package.js deleted file mode 100644 index c6f42d45e6e5de..00000000000000 --- a/packager/src/ModuleGraph/node-haste/Package.js +++ /dev/null @@ -1,135 +0,0 @@ -/** - * 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. - * - * @flow - */ - -'use strict'; - -const nullthrows = require('fbjs/lib/nullthrows'); -const path = require('path'); - -import type {PackageData} from '../types.flow'; - -module.exports = class Package { - data: PackageData; - path: string; - root: string; - type: 'Package'; - - constructor(packagePath: string, data: PackageData) { - this.data = data; - this.path = packagePath; - this.root = path.dirname(packagePath); - this.type = 'Package'; - } - - getMain() { - // Copied from node-haste/Package.js - const replacements = getReplacements(this.data); - if (typeof replacements === 'string') { - return path.join(this.root, replacements); - } - - let main = getMain(this.data); - - if (replacements && typeof replacements === 'object') { - main = replacements[main] || - replacements[main + '.js'] || - replacements[main + '.json'] || - replacements[main.replace(/(\.js|\.json)$/, '')] || - main; - } - - return path.join(this.root, main); - } - - getName(): Promise { - return Promise.resolve(nullthrows(this.data.name)); - } - - isHaste(): boolean { - return !!this.data.name; - } - - redirectRequire(name: string) { - // Copied from node-haste/Package.js - const replacements = getReplacements(this.data); - - if (!replacements || typeof replacements !== 'object') { - return name; - } - - if (!path.isAbsolute(name)) { - const replacement = replacements[name]; - // support exclude with "someDependency": false - return replacement === false - ? false - : replacement || name; - } - - let relPath = './' + path.relative(this.root, name); - if (path.sep !== '/') { - relPath = relPath.replace(new RegExp('\\' + path.sep, 'g'), '/'); - } - - let redirect = replacements[relPath]; - - // false is a valid value - if (redirect == null) { - redirect = replacements[relPath + '.js']; - if (redirect == null) { - redirect = replacements[relPath + '.json']; - } - } - - // support exclude with "./someFile": false - if (redirect === false) { - return false; - } - - if (redirect) { - return path.join( - this.root, - redirect - ); - } - - return name; - } -}; - -function getMain(pkg) { - return pkg.main || 'index'; -} - -// Copied from node-haste/Package.js -function getReplacements(pkg) { - let rn = pkg['react-native']; - let browser = pkg.browser; - if (rn == null) { - return browser; - } - - if (browser == null) { - return rn; - } - - const main = getMain(pkg); - if (typeof rn !== 'object') { - rn = {[main]: rn}; - } - - if (typeof browser !== 'object') { - browser = {[main]: browser}; - } - - // merge with "browser" as default, - // "react-native" as override - return {...browser, ...rn}; -} diff --git a/packager/src/ModuleGraph/node-haste/node-haste.flow.js b/packager/src/ModuleGraph/node-haste/node-haste.flow.js deleted file mode 100644 index ff45d101f9296d..00000000000000 --- a/packager/src/ModuleGraph/node-haste/node-haste.flow.js +++ /dev/null @@ -1,70 +0,0 @@ -/** - * 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. - * - * @flow - */ -'use strict'; - -'use strict'; - -import DependencyGraphHelpers from '../../node-haste/DependencyGraph/DependencyGraphHelpers'; - -type ModuleID = string; -export type Path = string; -type Platform = string; -type Platforms = Set; - -export type Extensions = Array; - -export type Module = { - path: Path, - type: 'Module', - getName(): Promise, - getPackage(): ?Package, - isHaste(): Promise, -}; - -export type Package = { - path: Path, - root: Path, - type: 'Package', - getMain(): Path, - getName(): Promise, - isHaste(): Promise, - redirectRequire(id: ModuleID): Path | false, -}; - -export type ModuleCache = { - getAssetModule(path: Path): Module, - getModule(path: Path): Module, - getPackage(path: Path): Package, - getPackageOf(path: Path): ?Package, -} - -export type FastFS = { - dirExists(path: Path): boolean, - closest(path: string, fileName: string): ?string, - fileExists(path: Path): boolean, - getAllFiles(): Array, - matches(directory: Path, pattern: RegExp): Array, -}; - -type HasteMapOptions = {| - extensions: Extensions, - files: Array, - helpers: DependencyGraphHelpers, - moduleCache: ModuleCache, - platforms: Platforms, - preferNativePlatform: true, -|}; - -declare class HasteMap { - // node-haste/DependencyGraph/HasteMap.js - build(): Promise, - constructor(options: HasteMapOptions): void, -} diff --git a/packager/src/ModuleGraph/node-haste/node-haste.js b/packager/src/ModuleGraph/node-haste/node-haste.js deleted file mode 100644 index 262b80c550f26c..00000000000000 --- a/packager/src/ModuleGraph/node-haste/node-haste.js +++ /dev/null @@ -1,149 +0,0 @@ -/** - * 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. - * - * @flow - */ - -'use strict'; - -import type { // eslint-disable-line sort-requires - Extensions, - Path, -} from './node-haste.flow'; - -import type { - ResolveFn, - TransformedCodeFile, -} from '../types.flow'; - -const AssetResolutionCache = require('../../node-haste/AssetResolutionCache'); -const DependencyGraphHelpers = require('../../node-haste/DependencyGraph/DependencyGraphHelpers'); -const FilesByDirNameIndex = require('../../node-haste/FilesByDirNameIndex'); -const HasteFS = require('./HasteFS'); -const HasteMap = require('../../node-haste/DependencyGraph/HasteMap'); -const Module = require('./Module'); -const ModuleCache = require('./ModuleCache'); -const ResolutionRequest = require('../../node-haste/DependencyGraph/ResolutionRequest'); - -const defaults = require('../../defaults'); - -import type {Moduleish, Packageish} from '../../node-haste/DependencyGraph/ResolutionRequest'; - -type ResolveOptions = {| - assetExts: Extensions, - extraNodeModules: {[id: string]: string}, - +sourceExts: Extensions, - transformedFiles: {[path: Path]: TransformedCodeFile}, -|}; - -const platforms = new Set(defaults.platforms); - -/** - * We don't need to crawl the filesystem all over again so we just mock - * a jest-haste-map's ModuleMap instance. Eventually, though, we'll - * want to figure out how to reunify and get rid of `HasteMap`. - */ -function getFakeModuleMap(hasteMap: HasteMap) { - return { - getModule(name: string, platform: ?string): ?string { - const module = hasteMap.getModule(name, platform); - return module && module.type === 'Module' ? module.path : null; - }, - getPackage(name: string, platform: ?string): ?string { - const pkg = hasteMap.getPackage(name); - return pkg && pkg.path; - }, - }; -} - -const nullModule: Moduleish = { - path: '/', - getPackage() {}, - hash() { - throw new Error('not implemented'); - }, - readCached() { throw new Error('not implemented'); }, - readFresh() { return Promise.reject(new Error('not implemented')); }, - isHaste() { throw new Error('not implemented'); }, - getName() { throw new Error('not implemented'); }, -}; - -exports.createResolveFn = function(options: ResolveOptions): ResolveFn { - const { - assetExts, - extraNodeModules, - transformedFiles, - sourceExts, - } = options; - const files = Object.keys(transformedFiles); - function getTransformedFile(path) { - const result = transformedFiles[path]; - if (!result) { - throw new Error(`"${path} does not exist`); - } - return result; - } - - const helpers = new DependencyGraphHelpers({ - assetExts, - providesModuleNodeModules: defaults.providesModuleNodeModules, - }); - - const hasteFS = new HasteFS(files); - const moduleCache = new ModuleCache( - filePath => hasteFS.closest(filePath, 'package.json'), - getTransformedFile, - ); - const hasteMap = new HasteMap({ - extensions: sourceExts, - files, - helpers, - moduleCache, - platforms, - preferNativePlatform: true, - }); - - const hasteMapBuilt = hasteMap.build(); - const resolutionRequests = {}; - const filesByDirNameIndex = new FilesByDirNameIndex(hasteMap.getAllFiles()); - const assetResolutionCache = new AssetResolutionCache({ - assetExtensions: new Set(assetExts), - getDirFiles: dirPath => filesByDirNameIndex.getAllFiles(dirPath), - platforms, - }); - return (id, source, platform, _, callback) => { - let resolutionRequest = resolutionRequests[platform]; - if (!resolutionRequest) { - resolutionRequest = resolutionRequests[platform] = new ResolutionRequest({ - dirExists: filePath => hasteFS.dirExists(filePath), - entryPath: '', - extraNodeModules, - hasteFS, - helpers, - moduleCache, - moduleMap: getFakeModuleMap(hasteMap), - platform, - preferNativePlatform: true, - resolveAsset: (dirPath, assetName) => - assetResolutionCache.resolve(dirPath, assetName, platform), - sourceExts, - }); - } - - const from = source != null - ? new Module(source, moduleCache, getTransformedFile(source)) - : nullModule; - hasteMapBuilt - .then(() => resolutionRequest.resolveDependency(from, id)) - .then( - // nextTick to escape promise error handling - module => process.nextTick(callback, null, module.path), - error => process.nextTick(callback, error), - ); - }; -}; diff --git a/packager/src/ModuleGraph/node-haste/package.json b/packager/src/ModuleGraph/node-haste/package.json deleted file mode 100644 index f642436d96d53a..00000000000000 --- a/packager/src/ModuleGraph/node-haste/package.json +++ /dev/null @@ -1 +0,0 @@ -{"main":"node-haste.js"} diff --git a/packager/src/ModuleGraph/output/__tests__/indexed-ram-bundle-test.js b/packager/src/ModuleGraph/output/__tests__/indexed-ram-bundle-test.js deleted file mode 100644 index 8305146c33fb49..00000000000000 --- a/packager/src/ModuleGraph/output/__tests__/indexed-ram-bundle-test.js +++ /dev/null @@ -1,297 +0,0 @@ -/** - * Copyright (c) 2016-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. - * - * @flow - */ -'use strict'; - -declare var jest: any; -jest.disableAutomock(); - -const indexedRamBundle = require('../indexed-ram-bundle'); -const {addModuleIdsToModuleWrapper} = require('../util'); - -declare var describe: any; -declare var expect: any; -declare var it: (string, () => ?Promise) => void; -declare var beforeAll: (() => ?Promise) => void; - -let code: Buffer; -let map; -let ids, modules, requireCall; -const idForPath = ({path}) => getId(path); -beforeAll(() => { - modules = [ - makeModule('a', [], 'script'), - makeModule('b', ['c']), - makeModule('c', ['f']), - makeModule('d', ['e']), - makeModule('e'), - makeModule('f'), - ]; - requireCall = makeModule('r', [], 'script', 'require(1);'); - - ids = new Map(modules.map(({file}, i) => [file.path, i])); - ({code, map} = createRamBundle()); -}); - -it('starts the bundle file with the magic number', () => { - expect(code.readUInt32LE(0)).toBe(0xFB0BD1E5); -}); - -it('contains the number of modules in the module table', () => { - expect(code.readUInt32LE(SIZEOF_INT32)).toBe(modules.length); -}); - -it('has the length correct of the startup section', () => { - expect(code.readUInt32LE(SIZEOF_INT32 * 2)) - .toBe(requireCall.file.code.length + 1); -}); - -it('contains the code after the offset table', () => { - const {codeOffset, startupSectionLength, table} = parseOffsetTable(code); - - const startupSection = - code.slice(codeOffset, codeOffset + startupSectionLength - 1); - expect(startupSection.toString()).toBe(requireCall.file.code); - - table.forEach(([offset, length], i) => { - const moduleCode = - code.slice(codeOffset + offset, codeOffset + offset + length - 1); - expect(moduleCode.toString()).toBe(expectedCode(modules[i])); - }); -}); - -it('creates a source map', () => { - let line = countLines(requireCall); - expect(map.sections.slice(1)).toEqual(modules.map(m => { - const section = { - map: m.file.map || lineByLineMap(m.file.path), - offset: {column: 0, line}, - }; - line += countLines(m); - return section; - })); - expect(map.x_facebook_offsets).toEqual([1, 2, 3, 4, 5, 6]); -}); - -describe('Startup section optimization', () => { - let last, preloaded; - beforeAll(() => { - last = modules[modules.length - 1]; - preloaded = [modules[2], modules[3], last]; - ({code, map} = createRamBundle(new Set(preloaded.map(getPath)))); - }); - - it('supports additional modules in the startup section', () => { - const {codeOffset, startupSectionLength, table} = parseOffsetTable(code); - - const startupSection = - code.slice(codeOffset, codeOffset + startupSectionLength - 1); - expect(startupSection.toString()) - .toBe(preloaded.concat([requireCall]).map(expectedCode).join('\n')); - - - preloaded.forEach(m => { - const idx = idForPath(m.file); - expect(table[idx]).toEqual(m === last ? undefined : [0, 0]); - }); - - table.forEach(([offset, length], i) => { - if (offset !== 0 && length !== 0) { - const moduleCode = - code.slice(codeOffset + offset, codeOffset + offset + length - 1); - expect(moduleCode.toString()).toBe(expectedCode(modules[i])); - } - }); - }); - - it('reflects additional sources in the startup section in the source map', () => { - let line = preloaded.reduce( - (l, m) => l + countLines(m), - countLines(requireCall), - ); - - expect(map.x_facebook_offsets).toEqual([4, 5,,, 6]); // eslint-disable-line no-sparse-arrays - - expect(map.sections.slice(1)).toEqual( - modules - .filter(not(Set.prototype.has), new Set(preloaded)) - .map(m => { - const section = { - map: m.file.map || lineByLineMap(m.file.path), - offset: {column: 0, line}, - }; - line += countLines(m); - return section; - } - )); - }); -}); - -describe('RAM groups / common sections', () => { - let groups, groupHeads; - beforeAll(() => { - groups = [ - [modules[1], modules[2], modules[5]], - [modules[3], modules[4]], - ]; - groupHeads = groups.map(g => g[0]); - ({code, map} = createRamBundle(undefined, groupHeads.map(getPath))); - }); - - it('supports grouping the transitive dependencies of files into common sections', () => { - const {codeOffset, table} = parseOffsetTable(code); - - groups.forEach(group => { - const [head, ...deps] = group.map(x => idForPath(x.file)); - const groupEntry = table[head]; - deps.forEach(id => expect(table[id]).toEqual(groupEntry)); - - const [offset, length] = groupEntry; - const groupCode = code.slice(codeOffset + offset, codeOffset + offset + length - 1); - expect(groupCode.toString()) - .toEqual(group.map(expectedCode).join('\n')); - }); - }); - - it('reflects section groups in the source map', () => { - expect(map.x_facebook_offsets).toEqual([1, 2, 2, 5, 5, 2]); - const maps = map.sections.slice(-2); - const toplevelOffsets = [2, 5]; - - maps.map((groupMap, i) => [groups[i], groupMap]).forEach(([group, groupMap], i) => { - const offsets = group.reduce(moduleLineOffsets, [])[0]; - expect(groupMap).toEqual({ - map: { - version: 3, - sections: group.map((module, j) => ({ - map: module.file.map, - offset: {line: offsets[j], column: 0}, - })), - }, - offset: {line: toplevelOffsets[i], column: 0}, - }); - }); - }); - - function moduleLineOffsets([offsets = [], line = 0], module) { - return [[...offsets, line], line + countLines(module)]; - } -}); - -function createRamBundle(preloadedModules = new Set(), ramGroups) { - const build = indexedRamBundle.createBuilder(preloadedModules, ramGroups); - const result = build({ - filename: 'arbitrary/filename.js', - idForPath, - modules, - requireCalls: [requireCall], - }); - - if (typeof result.code === 'string') { - throw new Error('Expected a buffer, not a string'); - } - return {code: result.code, map: result.map}; -} - -function makeModule(name, deps = [], type = 'module', moduleCode = `var ${name};`) { - const path = makeModulePath(name); - return { - dependencies: deps.map(makeDependency), - file: { - code: type === 'module' ? makeModuleCode(moduleCode) : moduleCode, - map: type !== 'module' - ? null - : makeModuleMap(name, path), - path, - type, - }, - }; -} - -function makeModuleMap(name, path) { - return { - version: 3, - mappings: Array(parseInt(name, 36) + 1).join(','), - names: [name], - sources: [path], - }; -} - -function makeModuleCode(moduleCode) { - return `__d(() => {${moduleCode}})`; -} - -function makeModulePath(name) { - return `/${name}.js`; -} - -function makeDependency(name) { - const path = makeModulePath(name); - return { - id: name, - path, - }; -} - -function expectedCode(module) { - const {file} = module; - return file.type === 'module' - ? addModuleIdsToModuleWrapper(module, idForPath) - : file.code; -} - -function getId(path) { - if (path === requireCall.file.path) { - return -1; - } - - const id = ids.get(path); - if (id == null) { - throw new Error(`Unknown file: ${path}`); - } - return id; -} - -function getPath(module) { - return module.file.path; -} - -const SIZEOF_INT32 = 4; -function parseOffsetTable(buffer) { - const n = buffer.readUInt32LE(SIZEOF_INT32); - const startupSectionLength = buffer.readUInt32LE(SIZEOF_INT32 * 2); - const baseOffset = SIZEOF_INT32 * 3; - const table = Array(n); - for (let i = 0; i < n; ++i) { - const offset = baseOffset + i * 2 * SIZEOF_INT32; - table[i] = [buffer.readUInt32LE(offset), buffer.readUInt32LE(offset + SIZEOF_INT32)]; - } - return { - codeOffset: baseOffset + n * 2 * SIZEOF_INT32, - startupSectionLength, - table, - }; -} - -function countLines(module) { - return module.file.code.split('\n').length; -} - -function lineByLineMap(file) { - return { - file, - mappings: 'AAAA;', - names: [], - sources: [file], - version: 3, - }; -} - -const not = fn => function() { return !fn.apply(this, arguments); }; diff --git a/packager/src/ModuleGraph/output/__tests__/util-test.js b/packager/src/ModuleGraph/output/__tests__/util-test.js deleted file mode 100644 index 6984b76f35f6d7..00000000000000 --- a/packager/src/ModuleGraph/output/__tests__/util-test.js +++ /dev/null @@ -1,83 +0,0 @@ -/** - * Copyright (c) 2016-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'; - -jest.disableAutomock(); - -const {match} = require('sinon'); -const {fn} = require('../../test-helpers'); -const { - addModuleIdsToModuleWrapper, - createIdForPathFn, -} = require('../util'); - -const {any} = jasmine; - -describe('`addModuleIdsToModuleWrapper`:', () => { - const path = 'path/to/file'; - const createModule = (dependencies = []) => ({ - dependencies, - file: {code: '__d(function(){});', isModule: true, path}, - }); - - it('completes the module wrapped with module ID, and an array of dependency IDs', () => { - const dependencies = [ - {id: 'a', path: 'path/to/a.js'}, - {id: 'b', path: 'location/of/b.js'}, - ]; - const module = createModule(dependencies); - - const idForPath = fn(); - idForPath.stub - .withArgs(match({path})).returns(12) - .withArgs(match({path: dependencies[0].path})).returns(345) - .withArgs(match({path: dependencies[1].path})).returns(6); - - expect(addModuleIdsToModuleWrapper(module, idForPath)) - .toEqual('__d(function(){},12,[345,6]);'); - }); - - it('omits the array of dependency IDs if it is empty', () => { - const module = createModule(); - expect(addModuleIdsToModuleWrapper(module, () => 98)) - .toEqual(`__d(function(){},${98});`); - }); -}); - -describe('`createIdForPathFn`', () => { - let idForPath; - beforeEach(() => { - idForPath = createIdForPathFn(); - }); - - it('returns a number for a string', () => { - expect(idForPath({path: 'arbitrary'})).toEqual(any(Number)); - }); - - it('returns consecutive numbers', () => { - const strings = [ - 'arbitrary string', - 'looking/like/a/path', - '/absolute/path/to/file.js', - '/more files/are here', - ]; - - strings.forEach((string, i) => { - expect(idForPath({path: string})).toEqual(i); - }); - }); - - it('returns the same id if the same string is passed in again', () => { - const path = 'this/is/an/arbitrary/path.js'; - const id = idForPath({path}); - idForPath({path: '/other/file'}); - idForPath({path: 'and/another/file'}); - expect(idForPath({path})).toEqual(id); - }); -}); diff --git a/packager/src/ModuleGraph/output/indexed-ram-bundle.js b/packager/src/ModuleGraph/output/indexed-ram-bundle.js deleted file mode 100644 index 286cc27bc27f90..00000000000000 --- a/packager/src/ModuleGraph/output/indexed-ram-bundle.js +++ /dev/null @@ -1,106 +0,0 @@ -/** - * Copyright (c) 2016-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. - * - * @flow - */ -'use strict'; - -const buildSourceMapWithMetaData = require('../../shared/output/unbundle/build-unbundle-sourcemap-with-metadata.js'); -const nullthrows = require('fbjs/lib/nullthrows'); - -const {createRamBundleGroups} = require('../../Bundler/util'); -const {buildTableAndContents, createModuleGroups} = require('../../shared/output/unbundle/as-indexed-file'); -const {addModuleIdsToModuleWrapper, concat} = require('./util'); - -import type {FBIndexMap} from '../../lib/SourceMap.js'; -import type {OutputFn} from '../types.flow'; - -function asIndexedRamBundle({ - filename, - idForPath, - modules, - preloadedModules, - ramGroupHeads, - requireCalls, -}) { - const [startup, deferred] = partition(modules, preloadedModules); - const startupModules = Array.from(concat(startup, requireCalls)); - const deferredModules = deferred.map(m => toModuleTransport(m, idForPath)); - const ramGroups = createRamBundleGroups(ramGroupHeads || [], deferredModules, subtree); - const moduleGroups = createModuleGroups(ramGroups, deferredModules); - - const tableAndContents = buildTableAndContents( - startupModules.map(m => getModuleCode(m, idForPath)).join('\n'), - deferredModules, - moduleGroups, - 'utf8', - ); - - return { - code: Buffer.concat(tableAndContents), - map: buildSourceMapWithMetaData({ - fixWrapperOffset: false, - lazyModules: deferredModules, - moduleGroups, - startupModules: startupModules.map(m => toModuleTransport(m, idForPath)), - }), - }; -} - -function toModuleTransport(module, idForPath) { - const {dependencies, file} = module; - return { - code: getModuleCode(module, idForPath), - dependencies, - id: idForPath(file), - map: file.map, - name: file.path, - sourcePath: file.path, - }; -} - -function getModuleCode(module, idForPath) { - const {file} = module; - return file.type === 'module' - ? addModuleIdsToModuleWrapper(module, idForPath) - : file.code; -} - -function partition(modules, preloadedModules) { - const startup = []; - const deferred = []; - for (const module of modules) { - (preloadedModules.has(module.file.path) ? startup : deferred).push(module); - } - - return [startup, deferred]; -} - -function *subtree( - moduleTransport, - moduleTransportsByPath, - seen = new Set(), -) { - seen.add(moduleTransport.id); - for (const {path} of moduleTransport.dependencies) { - const dependency = nullthrows(moduleTransportsByPath.get(path)); - if (!seen.has(dependency.id)) { - yield dependency.id; - yield *subtree(dependency, moduleTransportsByPath, seen); - } - } -} - -function createBuilder( - preloadedModules: Set, - ramGroupHeads: ?$ReadOnlyArray, -): OutputFn { - return x => asIndexedRamBundle({...x, preloadedModules, ramGroupHeads}); -} - -exports.createBuilder = createBuilder; diff --git a/packager/src/ModuleGraph/output/plain-bundle.js b/packager/src/ModuleGraph/output/plain-bundle.js deleted file mode 100644 index cfbfcad556e773..00000000000000 --- a/packager/src/ModuleGraph/output/plain-bundle.js +++ /dev/null @@ -1,64 +0,0 @@ -/** - * Copyright (c) 2016-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. - * - * @flow - */ -'use strict'; - -const meta = require('../../shared/output/meta'); - -const {createIndexMap} = require('./source-map'); -const {addModuleIdsToModuleWrapper, concat} = require('./util'); - -import type {OutputFn} from '../types.flow'; - -function asPlainBundle({ - filename, - idForPath, - modules, - requireCalls, - sourceMapPath, -}) { - let code = ''; - let line = 0; - const sections = []; - - for (const module of concat(modules, requireCalls)) { - const {file} = module; - const moduleCode = file.type === 'module' - ? addModuleIdsToModuleWrapper(module, idForPath) - : file.code; - - code += moduleCode + '\n'; - if (file.map) { - sections.push({ - map: file.map, - offset: {column: 0, line}, - }); - } - line += countLines(moduleCode); - } - - if (sourceMapPath) { - code += `//# sourceMappingURL=${sourceMapPath}`; - } - - return { - code, - extraFiles: [[`${filename}.meta`, meta(code)]], - map: createIndexMap({file: filename, sections}), - }; -} - -module.exports = (asPlainBundle: OutputFn<>); - -const reLine = /^/gm; -function countLines(string: string): number { - //$FlowFixMe This regular expression always matches - return string.match(reLine).length; -} diff --git a/packager/src/ModuleGraph/output/source-map.js b/packager/src/ModuleGraph/output/source-map.js deleted file mode 100644 index 7f45ac9cfb97f7..00000000000000 --- a/packager/src/ModuleGraph/output/source-map.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2016-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. - * - * @flow - */ -'use strict'; - -import type {FBSourceMap, IndexMapSection, IndexMap} from '../../lib/SourceMap'; - -export type {FBSourceMap}; - -type CreateIndexMapOptions = {| - file?: string, - sections?: Array -|}; - -exports.createIndexMap = (opts?: CreateIndexMapOptions): IndexMap => ({ - version: 3, - file: opts && opts.file, - sections: opts && opts.sections || [], -}); diff --git a/packager/src/ModuleGraph/output/util.js b/packager/src/ModuleGraph/output/util.js deleted file mode 100644 index f9a652a7b30788..00000000000000 --- a/packager/src/ModuleGraph/output/util.js +++ /dev/null @@ -1,81 +0,0 @@ -/** - * Copyright (c) 2016-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. - * - * @flow - */ -'use strict'; - -const virtualModule = require('../module').virtual; - -import type {IdForPathFn, Module} from '../types.flow'; - -// Transformed modules have the form -// __d(function(require, module, global, exports, dependencyMap) { -// /* code */ -// }); -// -// This function adds the numeric module ID, and an array with dependencies of -// the dependencies of the module before the closing parenthesis. -exports.addModuleIdsToModuleWrapper = ( - module: Module, - idForPath: {path: string} => number, -): string => { - const {dependencies, file} = module; - const {code} = file; - const index = code.lastIndexOf(')'); - - // calling `idForPath` on the module itself first gives us a lower module id - // for the file itself than for its dependencies. That reflects their order - // in the bundle. - const fileId = idForPath(file); - - // This code runs for both development and production builds, after - // minification. That's why we leave out all spaces. - const depencyIds = - dependencies.length ? `,[${dependencies.map(idForPath).join(',')}]` : ''; - return ( - code.slice(0, index) + - `,${fileId}` + - depencyIds + - code.slice(index) - ); -}; - -exports.concat = function* concat( - ...iterables: Array> -): Iterable { - for (const it of iterables) { - yield* it; - } -}; - -// Creates an idempotent function that returns numeric IDs for objects based -// on their `path` property. -exports.createIdForPathFn = (): ({path: string} => number) => { - const seen = new Map(); - let next = 0; - return ({path}) => { - let id = seen.get(path); - if (id == null) { - id = next++; - seen.set(path, id); - } - return id; - }; -}; - -// creates a series of virtual modules with require calls to the passed-in -// modules. -exports.requireCallsTo = function* ( - modules: Iterable, - idForPath: IdForPathFn, -): Iterable { - for (const module of modules) { - yield virtualModule(`require(${idForPath(module.file)});`); - } -}; diff --git a/packager/src/ModuleGraph/package.json b/packager/src/ModuleGraph/package.json deleted file mode 100644 index 4c0d77b8ca55be..00000000000000 --- a/packager/src/ModuleGraph/package.json +++ /dev/null @@ -1 +0,0 @@ -{"main": "ModuleGraph.js"} diff --git a/packager/src/ModuleGraph/silent-console.js b/packager/src/ModuleGraph/silent-console.js deleted file mode 100644 index b738ddaf00be56..00000000000000 --- a/packager/src/ModuleGraph/silent-console.js +++ /dev/null @@ -1,15 +0,0 @@ -/** - * Copyright (c) 2016-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 {Console} = require('console'); -const {Writable} = require('stream'); - -const write = (_, __, callback) => callback(); -module.exports = new Console(new Writable({write, writev: write})); diff --git a/packager/src/ModuleGraph/test-helpers.js b/packager/src/ModuleGraph/test-helpers.js deleted file mode 100644 index 35deb46ae98f0a..00000000000000 --- a/packager/src/ModuleGraph/test-helpers.js +++ /dev/null @@ -1,23 +0,0 @@ -/** - * Copyright (c) 2016-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 generate = require('babel-generator').default; -const stub = require('sinon/lib/sinon/stub'); - -exports.fn = () => { - const s = stub(); - const f = jest.fn(s); - f.stub = s; - return f; -}; - -const generateOptions = {concise: true}; -exports.codeFromAst = ast => generate(ast, generateOptions).code; -exports.comparableCode = code => code.trim().replace(/\s\s+/g, ' '); diff --git a/packager/src/ModuleGraph/types.flow.js b/packager/src/ModuleGraph/types.flow.js deleted file mode 100644 index 6db31c26e04ca8..00000000000000 --- a/packager/src/ModuleGraph/types.flow.js +++ /dev/null @@ -1,172 +0,0 @@ -/** - * Copyright (c) 2016-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. - * - * @flow - */ -'use strict'; - -import type {FBSourceMap, MappingsMap, SourceMap} from '../lib/SourceMap'; -import type {Ast} from 'babel-core'; -import type {Console} from 'console'; -export type {Transformer} from '../JSTransformer/worker'; - -export type BuildResult = {| - ...GraphResult, - prependedScripts: $ReadOnlyArray, -|}; - -export type Callback - = (Error => void) - & ((null | void, A, B) => void); - -type Dependency = {| - id: string, - path: string, -|}; - -export type File = {| - code: string, - map: ?MappingsMap, - path: string, - type: CodeFileTypes, -|}; - -type CodeFileTypes = 'module' | 'script'; - -export type GraphFn = ( - entryPoints: Iterable, - platform: string, - options?: ?GraphOptions, - callback?: Callback, -) => void; - -type GraphOptions = {| - log?: Console, - optimize?: boolean, - skip?: Set, -|}; - -export type GraphResult = {| - entryModules: Iterable, - modules: Iterable, -|}; - -export type IdForPathFn = {path: string} => number; - -export type LoadFn = ( - file: string, - options: LoadOptions, - callback: Callback>, -) => void; - -type LoadOptions = {| - log?: Console, - optimize?: boolean, - platform?: string, -|}; - -export type Module = {| - dependencies: Array, - file: File, -|}; - -export type PostProcessModules = ( - modules: Iterable, - entryPoints: Array, -) => Iterable; - -export type OutputFn = ({| - filename: string, - idForPath: IdForPathFn, - modules: Iterable, - requireCalls: Iterable, - sourceMapPath?: string, -|}) => OutputResult; - -type OutputResult = {| - code: string | Buffer, - extraFiles?: Iterable<[string, string | Buffer]>, - map: M, -|}; - -export type PackageData = {| - browser?: Object | string, - main?: string, - name?: string, - 'react-native'?: Object | string, -|}; - -export type ResolveFn = ( - id: string, - source: ?string, - platform: string, - options?: ResolveOptions, - callback: Callback, -) => void; - -type ResolveOptions = { - log?: Console, -}; - -export type TransformerResult = {| - ast: ?Ast, - code: string, - map: ?MappingsMap, -|}; - -export type TransformResult = {| - code: string, - dependencies: Array, - dependencyMapName?: string, - map: ?MappingsMap, -|}; - -export type TransformResults = {[string]: TransformResult}; - -export type TransformVariants = {+[name: string]: {}, +default: {}}; - -export type TransformedCodeFile = { - +code: string, - +file: string, - +hasteID: ?string, - package?: PackageData, - +transformed: TransformResults, - +type: CodeFileTypes, -}; - -export type AssetFile = {| - +assetContentBase64: string, - +filePath: string, -|}; - -export type TransformedSourceFile = - | {| - +type: 'code', - +details: TransformedCodeFile, - |} - | {| - +type: 'asset', - +details: AssetFile, - |} - ; - -export type LibraryOptions = {| - dependencies?: Array, - optimize: boolean, - platform?: string, - rebasePath: string => string, -|}; - -export type Base64Content = string; -export type AssetContentsByPath = {[destFilePath: string]: Base64Content}; - -export type Library = {| - +files: Array, - /* cannot be a Map because it's JSONified later on */ - +assets: AssetContentsByPath, -|}; diff --git a/packager/src/ModuleGraph/worker.js b/packager/src/ModuleGraph/worker.js deleted file mode 100644 index 6a1a41f33376df..00000000000000 --- a/packager/src/ModuleGraph/worker.js +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Copyright (c) 2016-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. - * - * @flow - */ -'use strict'; - -const asyncify: Asyncify = require('async/asyncify'); -const optimizeModule = require('./worker/optimize-module'); -const transformModule = require('./worker/transform-module'); -const wrapWorkerFn = require('./worker/wrap-worker-fn'); - -import type {Callback} from './types.flow'; -import type {OptimizationOptions} from './worker/optimize-module'; -import type {TransformOptions} from './worker/transform-module'; -import type {WorkerFnWithIO} from './worker/wrap-worker-fn'; - -type Asyncify = ((A, B) => C) => (A, B, Callback) => void; - - -exports.optimizeModule = - (wrapWorkerFn(asyncify(optimizeModule)): WorkerFnWithIO); -exports.transformModule = - (wrapWorkerFn(transformModule): WorkerFnWithIO); diff --git a/packager/src/ModuleGraph/worker/JsFileWrapping.js b/packager/src/ModuleGraph/worker/JsFileWrapping.js deleted file mode 100644 index 37021a31e9f647..00000000000000 --- a/packager/src/ModuleGraph/worker/JsFileWrapping.js +++ /dev/null @@ -1,52 +0,0 @@ -/** - * Copyright (c) 2016-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. - * - * @flow - */ - -'use strict'; - -const babel = require('babel-core'); - -const MODULE_FACTORY_PARAMETERS = ['global', 'require', 'module', 'exports']; -const POLYFILL_FACTORY_PARAMETERS = ['global']; - -function wrapModule(fileAst: Object, dependencyMapName: string): Object { - const t = babel.types; - const params = MODULE_FACTORY_PARAMETERS.concat(dependencyMapName); - const factory = functionFromProgram(fileAst.program, params); - const def = t.callExpression(t.identifier('__d'), [factory]); - return t.file(t.program([t.expressionStatement(def)])); -} - -function wrapPolyfill(fileAst: Object): Object { - const t = babel.types; - const factory = functionFromProgram(fileAst.program, POLYFILL_FACTORY_PARAMETERS); - const iife = t.callExpression(factory, [t.identifier('this')]); - return t.file(t.program([t.expressionStatement(iife)])); -} - -function functionFromProgram(program: Object, parameters: Array): Object { - const t = babel.types; - return t.functionExpression( - t.identifier(''), - parameters.map(makeIdentifier), - t.blockStatement(program.body, program.directives), - ); -} - -function makeIdentifier(name: string): Object { - return babel.types.identifier(name); -} - -module.exports = { - MODULE_FACTORY_PARAMETERS, - POLYFILL_FACTORY_PARAMETERS, - wrapModule, - wrapPolyfill, -}; diff --git a/packager/src/ModuleGraph/worker/__tests__/collect-dependencies-test.js b/packager/src/ModuleGraph/worker/__tests__/collect-dependencies-test.js deleted file mode 100644 index 5ce6310ac3515d..00000000000000 --- a/packager/src/ModuleGraph/worker/__tests__/collect-dependencies-test.js +++ /dev/null @@ -1,135 +0,0 @@ -/** - * Copyright (c) 2017-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'; - -jest.disableAutomock(); - -const collectDependencies = require('../collect-dependencies'); -const astFromCode = require('babylon').parse; -const {codeFromAst, comparableCode} = require('../../test-helpers'); - -const {any} = expect; - -describe('dependency collection from ASTs:', () => { - it('collects dependency identifiers from the code', () => { - const ast = astFromCode(` - const a = require('b/lib/a'); - exports.do = () => require("do"); - if (!something) { - require("setup/something"); - } - `); - - expect(collectDependencies(ast).dependencies) - .toEqual(['b/lib/a', 'do', 'setup/something']); - }); - - it('supports template literals as arguments', () => { - const ast = astFromCode('require(`left-pad`)'); - - expect(collectDependencies(ast).dependencies) - .toEqual(['left-pad']); - }); - - it('ignores template literals with interpolations', () => { - const ast = astFromCode('require(`left${"-"}pad`)'); - - expect(collectDependencies(ast).dependencies) - .toEqual([]); - }); - - it('ignores tagged template literals', () => { - const ast = astFromCode('require(tag`left-pad`)'); - - expect(collectDependencies(ast).dependencies) - .toEqual([]); - }); - - it('exposes a string as `dependencyMapName`', () => { - const ast = astFromCode('require("arbitrary")'); - expect(collectDependencies(ast).dependencyMapName) - .toEqual(any(String)); - }); - - it('exposes a string as `dependencyMapName` even without collecting dependencies', () => { - const ast = astFromCode(''); - expect(collectDependencies(ast).dependencyMapName) - .toEqual(any(String)); - }); - - it('replaces all required module ID strings with array lookups, keeps the ID as second argument', - () => { - const ast = astFromCode(` - const a = require('b/lib/a'); - const b = require(123); - exports.do = () => require("do"); - if (!something) { - require("setup/something"); - } - `); - - const {dependencyMapName} = collectDependencies(ast); - - expect(codeFromAst(ast)).toEqual(comparableCode(` - const a = require(${dependencyMapName}[0], 'b/lib/a'); - const b = require(123); - exports.do = () => require(${dependencyMapName}[1], "do"); - if (!something) { - require(${dependencyMapName}[2], "setup/something"); - } - `)); - }, - ); -}); - -describe('Dependency collection from optimized ASTs:', () => { - const dependencyMapName = 'arbitrary'; - const {forOptimization} = collectDependencies; - let ast, names; - - beforeEach(() => { - ast = astFromCode(` - const a = require(${dependencyMapName}[0], 'b/lib/a'); - const b = require(123); - exports.do = () => require(${dependencyMapName}[1], "do"); - if (!something) { - require(${dependencyMapName}[2], "setup/something"); - } - `); - names = ['b/lib/a', 'do', 'setup/something']; - }); - - it('passes the `dependencyMapName` through', () => { - const result = forOptimization(ast, names, dependencyMapName); - expect(result.dependencyMapName).toEqual(dependencyMapName); - }); - - it('returns the list of passed in dependencies', () => { - const result = forOptimization(ast, names, dependencyMapName); - expect(result.dependencies).toEqual(names); - }); - - it('only returns dependencies that are in the code', () => { - ast = astFromCode(`require(${dependencyMapName}[1], 'do')`); - const result = forOptimization(ast, names, dependencyMapName); - expect(result.dependencies).toEqual(['do']); - }); - - it('replaces all call signatures inserted by a prior call to `collectDependencies`', () => { - forOptimization(ast, names, dependencyMapName); - expect(codeFromAst(ast)).toEqual(comparableCode(` - const a = require(${dependencyMapName}[0]); - const b = require(123); - exports.do = () => require(${dependencyMapName}[1]); - if (!something) { - require(${dependencyMapName}[2]); - } - `)); - }); -}); diff --git a/packager/src/ModuleGraph/worker/__tests__/optimize-module-test.js b/packager/src/ModuleGraph/worker/__tests__/optimize-module-test.js deleted file mode 100644 index 4f2c023e1d8aab..00000000000000 --- a/packager/src/ModuleGraph/worker/__tests__/optimize-module-test.js +++ /dev/null @@ -1,131 +0,0 @@ -/** - * Copyright (c) 2016-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'; - -jest.disableAutomock(); - -const optimizeModule = require('../optimize-module'); -const transformModule = require('../transform-module'); -const transformer = require('../../../transformer.js'); -const {SourceMapConsumer} = require('source-map'); -const {fn} = require('../../test-helpers'); - -const {objectContaining} = jasmine; - -describe('optimizing JS modules', () => { - const filename = 'arbitrary/file.js'; - const optimizationOptions = { - dev: false, - platform: 'android', - postMinifyProcess: x => x, - }; - const originalCode = - `if (Platform.OS !== 'android') { - require('arbitrary-dev'); - } else { - __DEV__ ? require('arbitrary-android-dev') : require('arbitrary-android-prod'); - }`; - - let transformResult; - beforeAll(done => { - transformModule(originalCode, {filename, transformer}, (error, result) => { - if (error) { - throw error; - } - transformResult = JSON.stringify({type: 'code', details: result.details}); - done(); - }); - }); - - it('copies everything from the transformed file, except for transform results', () => { - const result = optimizeModule(transformResult, optimizationOptions); - const expected = JSON.parse(transformResult).details; - delete expected.transformed; - expect(result.type).toBe('code'); - expect(result.details).toEqual(objectContaining(expected)); - }); - - describe('code optimization', () => { - let dependencyMapName, injectedVars, optimized, requireName; - beforeAll(() => { - const result = optimizeModule(transformResult, optimizationOptions); - optimized = result.details.transformed.default; - injectedVars = optimized.code.match(/function\(([^)]*)/)[1].split(','); - [, requireName,,, dependencyMapName] = injectedVars; - }); - - it('optimizes code', () => { - expect(optimized.code) - .toEqual(`__d(function(${injectedVars}){${requireName}(${dependencyMapName}[0])});`); - }); - - it('extracts dependencies', () => { - expect(optimized.dependencies).toEqual(['arbitrary-android-prod']); - }); - - it('creates source maps', () => { - const consumer = new SourceMapConsumer(optimized.map); - const column = optimized.code.lastIndexOf(requireName + '('); - const loc = findLast(originalCode, 'require'); - - expect(consumer.originalPositionFor({line: 1, column})) - .toEqual(objectContaining(loc)); - }); - - it('does not extract dependencies for polyfills', () => { - const result = optimizeModule( - transformResult, - {...optimizationOptions, isPolyfill: true}, - ).details; - expect(result.transformed.default.dependencies).toEqual([]); - }); - }); - - describe('post-processing', () => { - let postMinifyProcess, optimize; - beforeEach(() => { - postMinifyProcess = fn(); - optimize = () => - optimizeModule(transformResult, {...optimizationOptions, postMinifyProcess}); - }); - - it('passes the result to the provided postprocessing function', () => { - postMinifyProcess.stub.callsFake(x => x); - const result = optimize(); - const {code, map} = result.details.transformed.default; - expect(postMinifyProcess).toBeCalledWith({code, map}); - }); - - it('uses the result of the provided postprocessing function for the result', () => { - const code = 'var postprocessed = "code";'; - const map = {version: 3, mappings: 'postprocessed'}; - postMinifyProcess.stub.returns({code, map}); - expect(optimize().details.transformed.default) - .toEqual(objectContaining({code, map})); - }); - }); - - it('passes through non-code data unmodified', () => { - const data = {type: 'asset', details: {arbitrary: 'data'}}; - expect(optimizeModule(JSON.stringify(data), {dev: true, platform: ''})) - .toEqual(data); - }); -}); - -function findLast(code, needle) { - const lines = code.split(/(?:(?!.)\s)+/); - let line = lines.length; - while (line--) { - const column = lines[line].lastIndexOf(needle); - if (column !== -1) { - return {line: line + 1, column}; - } - } - return null; -} diff --git a/packager/src/ModuleGraph/worker/__tests__/transform-module-test.js b/packager/src/ModuleGraph/worker/__tests__/transform-module-test.js deleted file mode 100644 index 5503448e68da87..00000000000000 --- a/packager/src/ModuleGraph/worker/__tests__/transform-module-test.js +++ /dev/null @@ -1,277 +0,0 @@ -/** - * Copyright (c) 2016-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. - * - * @format - */ -'use strict'; - -jest.disableAutomock(); - -const transformModule = require('../transform-module'); - -const t = require('babel-types'); -const {SourceMapConsumer} = require('source-map'); -const {fn} = require('../../test-helpers'); -const {parse} = require('babylon'); -const generate = require('babel-generator').default; -const {traverse} = require('babel-core'); - -describe('transforming JS modules:', () => { - const filename = 'arbitrary'; - - let transformer; - - beforeEach(() => { - transformer = { - transform: fn(), - }; - transformer.transform.stub.returns(transformResult()); - }); - - const {bodyAst, sourceCode, transformedCode} = createTestData(); - - const options = variants => ({ - filename, - transformer, - variants, - }); - - const transformResult = (body = bodyAst) => ({ - ast: t.file(t.program(body)), - }); - - it('passes through file name and code', done => { - transformModule(sourceCode, options(), (error, result) => { - expect(result.type).toBe('code'); - expect(result.details).toEqual( - expect.objectContaining({ - code: sourceCode, - file: filename, - }), - ); - done(); - }); - }); - - it('exposes a haste ID if present', done => { - const hasteID = 'TheModule'; - const codeWithHasteID = `/** @providesModule ${hasteID} */`; - transformModule(codeWithHasteID, options(), (error, result) => { - expect(result.type).toBe('code'); - expect(result.details).toEqual(expect.objectContaining({hasteID})); - done(); - }); - }); - - it('sets `type` to `"module"` by default', done => { - transformModule(sourceCode, options(), (error, result) => { - expect(result.type).toBe('code'); - expect(result.details).toEqual(expect.objectContaining({type: 'module'})); - done(); - }); - }); - - it('sets `type` to `"script"` if the input is a polyfill', done => { - transformModule( - sourceCode, - {...options(), polyfill: true}, - (error, result) => { - expect(result.type).toBe('code'); - expect(result.details).toEqual( - expect.objectContaining({type: 'script'}), - ); - done(); - }, - ); - }); - - const defaults = { - dev: false, - generateSourceMaps: true, - hot: false, - inlineRequires: false, - platform: '', - projectRoot: '', - }; - - it( - 'calls the passed-in transform function with code, file name, and options ' + - 'for all passed in variants', - done => { - const variants = {dev: {dev: true}, prod: {dev: false}}; - - transformModule(sourceCode, options(variants), () => { - expect(transformer.transform).toBeCalledWith({ - filename, - localPath: filename, - options: {...defaults, ...variants.dev}, - src: sourceCode, - }); - expect(transformer.transform).toBeCalledWith({ - filename, - localPath: filename, - options: {...defaults, ...variants.prod}, - src: sourceCode, - }); - done(); - }); - }, - ); - - it('calls back with any error yielded by the transform function', done => { - const error = new Error(); - transformer.transform.stub.throws(error); - - transformModule(sourceCode, options(), e => { - expect(e).toBe(error); - done(); - }); - }); - - it('wraps the code produced by the transform function into a module factory', done => { - transformModule(sourceCode, options(), (error, result) => { - expect(error).toEqual(null); - - const {code, dependencyMapName} = result.details.transformed.default; - expect(code.replace(/\s+/g, '')).toEqual( - `__d(function(global,require,module,exports,${dependencyMapName}){${transformedCode}});`, - ); - done(); - }); - }); - - it('wraps the code produced by the transform function into an IIFE for polyfills', done => { - transformModule( - sourceCode, - {...options(), polyfill: true}, - (error, result) => { - expect(error).toEqual(null); - - const {code} = result.details.transformed.default; - expect(code.replace(/\s+/g, '')).toEqual( - `(function(global){${transformedCode}})(this);`, - ); - done(); - }, - ); - }); - - it('creates source maps', done => { - transformModule(sourceCode, options(), (error, result) => { - const {code, map} = result.details.transformed.default; - const column = code.indexOf('code'); - const consumer = new SourceMapConsumer(map); - expect(consumer.originalPositionFor({line: 1, column})).toEqual( - expect.objectContaining({line: 1, column: sourceCode.indexOf('code')}), - ); - done(); - }); - }); - - it('extracts dependencies (require calls)', done => { - const dep1 = 'foo'; - const dep2 = 'bar'; - const code = `require('${dep1}'),require('${dep2}')`; - const {body} = parse(code).program; - transformer.transform.stub.returns(transformResult(body)); - - transformModule(code, options(), (error, result) => { - expect(result.details.transformed.default).toEqual( - expect.objectContaining({dependencies: [dep1, dep2]}), - ); - done(); - }); - }); - - it('transforms for all variants', done => { - const variants = {dev: {dev: true}, prod: {dev: false}}; - transformer.transform.stub - .withArgs(filename, sourceCode, variants.dev) - .returns(transformResult(bodyAst)) - .withArgs(filename, sourceCode, variants.prod) - .returns(transformResult([])); - - transformModule(sourceCode, options(variants), (error, result) => { - const {dev, prod} = result.details.transformed; - expect(dev.code.replace(/\s+/g, '')).toEqual( - `__d(function(global,require,module,exports,${dev.dependencyMapName}){arbitrary(code);});`, - ); - expect(prod.code.replace(/\s+/g, '')).toEqual( - `__d(function(global,require,module,exports,${prod.dependencyMapName}){arbitrary(code);});`, - ); - done(); - }); - }); - - it('prefixes JSON files with `module.exports = `', done => { - const json = '{"foo":"bar"}'; - - transformModule( - json, - {...options(), filename: 'some.json'}, - (error, result) => { - const {code} = result.details.transformed.default; - expect(code.replace(/\s+/g, '')).toEqual( - '__d(function(global,require,module,exports){' + - `module.exports=${json}});`, - ); - done(); - }, - ); - }); - - it('does not create source maps for JSON files', done => { - transformModule( - '{}', - {...options(), filename: 'some.json'}, - (error, result) => { - expect(result.details.transformed.default).toEqual( - expect.objectContaining({map: null}), - ); - done(); - }, - ); - }); - - it('adds package data for `package.json` files', done => { - const pkg = { - name: 'package-name', - main: 'package/main', - browser: {browser: 'defs'}, - 'react-native': {'react-native': 'defs'}, - }; - - transformModule( - JSON.stringify(pkg), - {...options(), filename: 'arbitrary/package.json'}, - (error, result) => { - expect(result.details.package).toEqual(pkg); - done(); - }, - ); - }); -}); - -function createTestData() { - // creates test data with an transformed AST, so that we can test source - // map generation. - const sourceCode = 'some(arbitrary(code));'; - const fileAst = parse(sourceCode); - traverse(fileAst, { - CallExpression(path) { - if (path.node.callee.name === 'some') { - path.replaceWith(path.node.arguments[0]); - } - }, - }); - return { - bodyAst: fileAst.program.body, - sourceCode, - transformedCode: generate(fileAst).code, - }; -} diff --git a/packager/src/ModuleGraph/worker/__tests__/wrap-worker-fn-test.js b/packager/src/ModuleGraph/worker/__tests__/wrap-worker-fn-test.js deleted file mode 100644 index e543511da428f0..00000000000000 --- a/packager/src/ModuleGraph/worker/__tests__/wrap-worker-fn-test.js +++ /dev/null @@ -1,89 +0,0 @@ -/** - * Copyright (c) 2016-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'; - -jest - .disableAutomock() - .setMock('fs', jest.genMockFromModule('fs')) - .mock('mkdirp'); - -const wrapWorkerFn = require('../wrap-worker-fn'); -const {dirname} = require('path'); -const {fn} = require('../../test-helpers'); - -const {any} = jasmine; - -describe('wrapWorkerFn:', () => { - const infile = '/arbitrary/in/file'; - const outfile = '/arbitrary/in/file'; - - let workerFn, wrapped; - beforeEach(() => { - workerFn = fn(); - workerFn.stub.yields(); - wrapped = wrapWorkerFn(workerFn); - }); - - const fs = require('fs'); - const mkdirp = require('mkdirp'); - - it('reads the passed-in file synchronously as buffer', done => { - wrapped(infile, outfile, {}, () => { - expect(fs.readFileSync).toBeCalledWith(infile); - done(); - }); - }); - - it('calls the worker function with file contents and options', done => { - const contents = 'arbitrary(contents);'; - const options = {arbitrary: 'options'}; - fs.readFileSync.mockReturnValue(contents); - wrapped(infile, outfile, options, () => { - expect(workerFn).toBeCalledWith(contents, options, any(Function)); - done(); - }); - }); - - it('passes through any error that the worker function calls back with', done => { - const error = new Error(); - workerFn.stub.yields(error); - wrapped(infile, outfile, {}, e => { - expect(e).toBe(error); - done(); - }); - }); - - it('writes the result to disk', done => { - const result = {arbitrary: 'result'}; - workerFn.stub.yields(null, result); - wrapped(infile, outfile, {}, () => { - expect(mkdirp.sync).toBeCalledWith(dirname(outfile)); - expect(fs.writeFileSync).toBeCalledWith(outfile, JSON.stringify(result), 'utf8'); - done(); - }); - }); - - it('calls back with any error thrown by `mkdirp.sync`', done => { - const error = new Error(); - mkdirp.sync.mockImplementationOnce(() => { throw error; }); - wrapped(infile, outfile, {}, e => { - expect(e).toBe(error); - done(); - }); - }); - - it('calls back with any error thrown by `fs.writeFileSync`', done => { - const error = new Error(); - fs.writeFileSync.mockImplementationOnce(() => { throw error; }); - wrapped(infile, outfile, {}, e => { - expect(e).toBe(error); - done(); - }); - }); -}); diff --git a/packager/src/ModuleGraph/worker/collect-dependencies.js b/packager/src/ModuleGraph/worker/collect-dependencies.js deleted file mode 100644 index b17c8c9d32e344..00000000000000 --- a/packager/src/ModuleGraph/worker/collect-dependencies.js +++ /dev/null @@ -1,150 +0,0 @@ -/** - * Copyright (c) 2016-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. - * - * @flow - */ -'use strict'; - -const nullthrows = require('fbjs/lib/nullthrows'); - -const {traverse, types} = require('babel-core'); - -type AST = Object; - -class Replacement { - nameToIndex: Map; - nextIndex: number; - - constructor() { - this.nameToIndex = new Map(); - this.nextIndex = 0; - } - - isRequireCall(callee, firstArg) { - return ( - callee.type === 'Identifier' && callee.name === 'require' && - firstArg && isLiteralString(firstArg) - ); - } - - getIndex(stringLiteralOrTemplateLiteral) { - const name = stringLiteralOrTemplateLiteral.quasis - ? stringLiteralOrTemplateLiteral.quasis[0].value.cooked - : stringLiteralOrTemplateLiteral.value; - let index = this.nameToIndex.get(name); - if (index !== undefined) { - return index; - } - index = this.nextIndex++; - this.nameToIndex.set(name, index); - return index; - } - - getNames() { - return Array.from(this.nameToIndex.keys()); - } - - makeArgs(newId, oldId, dependencyMapIdentifier) { - const mapLookup = createMapLookup(dependencyMapIdentifier, newId); - return [mapLookup, oldId]; - } -} - -class ProdReplacement { - replacement: Replacement; - names: Array; - - constructor(names) { - this.replacement = new Replacement(); - this.names = names; - } - - isRequireCall(callee, firstArg) { - return ( - callee.type === 'Identifier' && - callee.name === 'require' && - firstArg && - firstArg.type === 'MemberExpression' && - firstArg.property && - firstArg.property.type === 'NumericLiteral' - ); - } - - getIndex(memberExpression) { - const id = memberExpression.property.value; - if (id in this.names) { - return this.replacement.getIndex({value: this.names[id]}); - } - - throw new Error( - `${id} is not a known module ID. Existing mappings: ${ - this.names.map((n, i) => `${i} => ${n}`).join(', ')}` - ); - } - - getNames() { - return this.replacement.getNames(); - } - - makeArgs(newId, _, dependencyMapIdentifier) { - const mapLookup = createMapLookup(dependencyMapIdentifier, newId); - return [mapLookup]; - } -} - -function createMapLookup(dependencyMapIdentifier, propertyIdentifier) { - return types.memberExpression( - dependencyMapIdentifier, - propertyIdentifier, - true, - ); -} - -function collectDependencies(ast, replacement, dependencyMapIdentifier) { - const traversalState = {dependencyMapIdentifier}; - traverse(ast, { - Program(path, state) { - if (!state.dependencyMapIdentifier) { - state.dependencyMapIdentifier = - path.scope.generateUidIdentifier('dependencyMap'); - } - }, - CallExpression(path, state) { - const node = path.node; - const arg = node.arguments[0]; - if (replacement.isRequireCall(node.callee, arg)) { - const index = replacement.getIndex(arg); - node.arguments = replacement.makeArgs( - types.numericLiteral(index), - arg, - state.dependencyMapIdentifier, - ); - } - }, - }, null, traversalState); - - return { - dependencies: replacement.getNames(), - dependencyMapName: nullthrows(traversalState.dependencyMapIdentifier).name, - }; -} - -function isLiteralString(node) { - return node.type === 'StringLiteral' || - node.type === 'TemplateLiteral' && node.quasis.length === 1; -} - -exports = module.exports = - (ast: AST) => collectDependencies(ast, new Replacement()); -exports.forOptimization = - (ast: AST, names: Array, dependencyMapName?: string) => - collectDependencies( - ast, - new ProdReplacement(names), - dependencyMapName ? types.identifier(dependencyMapName) : undefined, - ); diff --git a/packager/src/ModuleGraph/worker/generate.js b/packager/src/ModuleGraph/worker/generate.js deleted file mode 100644 index a0f2d7552ecbb3..00000000000000 --- a/packager/src/ModuleGraph/worker/generate.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2016-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. - * - * @flow - */ -'use strict'; - -const babelGenerate = require('babel-generator').default; - -function generate(ast: Object, filename: string, sourceCode: string) { - return babelGenerate(ast, { - comments: false, - compact: true, - filename, - sourceFileName: filename, - sourceMaps: true, - sourceMapTarget: filename, - }, sourceCode); -} - -module.exports = generate; diff --git a/packager/src/ModuleGraph/worker/optimize-module.js b/packager/src/ModuleGraph/worker/optimize-module.js deleted file mode 100644 index e77ad12dc68356..00000000000000 --- a/packager/src/ModuleGraph/worker/optimize-module.js +++ /dev/null @@ -1,125 +0,0 @@ -/** - * Copyright (c) 2016-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. - * - * @flow - */ -'use strict'; - -const babel = require('babel-core'); -const collectDependencies = require('./collect-dependencies'); -const constantFolding = require('../../JSTransformer/worker/constant-folding').plugin; -const generate = require('./generate'); -const inline = require('../../JSTransformer/worker/inline').plugin; -const minify = require('../../JSTransformer/worker/minify'); -const sourceMap = require('source-map'); - -import type {TransformedSourceFile, TransformResult} from '../types.flow'; -import type {MappingsMap, SourceMap} from '../../lib/SourceMap'; -import type {PostMinifyProcess} from '../../Bundler/index.js'; - - -export type OptimizationOptions = {| - dev: boolean, - isPolyfill?: boolean, - platform: string, - postMinifyProcess: PostMinifyProcess, -|}; - -function optimizeModule( - content: Buffer, - optimizationOptions: OptimizationOptions, -): TransformedSourceFile { - const data: TransformedSourceFile = JSON.parse(content.toString('utf8')); - - if (data.type !== 'code') { - return data; - } - - const {details} = data; - const {code, file, transformed} = details; - const result = {...details, transformed: {}}; - const {postMinifyProcess} = optimizationOptions; - - //$FlowIssue #14545724 - Object.entries(transformed).forEach(([k, t: TransformResult]: [*, TransformResult]) => { - const optimized = optimize(t, file, code, optimizationOptions); - const processed = postMinifyProcess({code: optimized.code, map: optimized.map}); - optimized.code = processed.code; - optimized.map = processed.map; - result.transformed[k] = optimized; - }); - - return {type: 'code', details: result}; -} - -function optimize(transformed, file, originalCode, options) { - const {code, dependencyMapName, map} = transformed; - const optimized = optimizeCode(code, map, file, options); - - let dependencies; - if (options.isPolyfill) { - dependencies = []; - } else { - ({dependencies} = collectDependencies.forOptimization( - optimized.ast, - transformed.dependencies, - dependencyMapName, - )); - } - - const inputMap = transformed.map; - const gen = generate(optimized.ast, file, originalCode); - - const min = minify( - file, - gen.code, - inputMap && mergeSourceMaps(file, inputMap, gen.map), - ); - return {code: min.code, map: min.map, dependencies}; -} - -function optimizeCode(code, map, filename, inliningOptions) { - return babel.transform(code, { - plugins: [ - [constantFolding], - [inline, {...inliningOptions, isWrapped: true}], - ], - babelrc: false, - code: false, - filename, - }); -} - -function mergeSourceMaps( - file: string, - originalMap: SourceMap, - secondMap: SourceMap, -): MappingsMap { - const merged = new sourceMap.SourceMapGenerator(); - const inputMap = new sourceMap.SourceMapConsumer(originalMap); - new sourceMap.SourceMapConsumer(secondMap) - .eachMapping(mapping => { - const original = inputMap.originalPositionFor({ - line: mapping.originalLine, - column: mapping.originalColumn, - }); - if (original.line == null) { - return; - } - - merged.addMapping({ - generated: {line: mapping.generatedLine, column: mapping.generatedColumn}, - original: {line: original.line, column: original.column || 0}, - source: file, - name: original.name || mapping.name, - }); - }); - return merged.toJSON(); -} - -module.exports = optimizeModule; diff --git a/packager/src/ModuleGraph/worker/transform-module.js b/packager/src/ModuleGraph/worker/transform-module.js deleted file mode 100644 index d361ae4691f65b..00000000000000 --- a/packager/src/ModuleGraph/worker/transform-module.js +++ /dev/null @@ -1,183 +0,0 @@ -/** - * Copyright (c) 2016-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. - * - * @flow - * @format - */ -'use strict'; - -const JsFileWrapping = require('./JsFileWrapping'); - -const asyncify = require('async/asyncify'); -const collectDependencies = require('./collect-dependencies'); -const defaults = require('../../defaults'); -const docblock = require('../../node-haste/DependencyGraph/docblock'); -const generate = require('./generate'); -const path = require('path'); -const series = require('async/series'); - -const {basename} = require('path'); - -import type { - Callback, - TransformedCodeFile, - TransformedSourceFile, - Transformer, - TransformerResult, - TransformResult, - TransformVariants, -} from '../types.flow'; - -export type TransformOptions = {| - filename: string, - polyfill?: boolean, - transformer: Transformer<*>, - variants?: TransformVariants, -|}; - -const defaultTransformOptions = { - dev: true, - generateSourceMaps: true, - hot: false, - inlineRequires: false, - platform: '', - projectRoot: '', -}; -const defaultVariants = {default: {}}; - -const ASSET_EXTENSIONS = new Set(defaults.assetExts); - -function transformModule( - content: Buffer, - options: TransformOptions, - callback: Callback, -): void { - if (ASSET_EXTENSIONS.has(path.extname(options.filename).substr(1))) { - transformAsset(content, options, callback); - return; - } - - const code = content.toString('utf8'); - if (options.filename.endsWith('.json')) { - transformJSON(code, options, callback); - return; - } - - const {filename, transformer, variants = defaultVariants} = options; - const tasks = {}; - Object.keys(variants).forEach(name => { - tasks[name] = asyncify(() => - transformer.transform({ - filename, - localPath: filename, - options: {...defaultTransformOptions, ...variants[name]}, - src: code, - }), - ); - }); - - series(tasks, (error, results: {[key: string]: TransformerResult}) => { - if (error) { - callback(error); - return; - } - - const transformed: {[key: string]: TransformResult} = {}; - - //$FlowIssue #14545724 - Object.entries(results).forEach(([key, value]: [*, TransformFnResult]) => { - transformed[key] = makeResult( - value.ast, - filename, - code, - options.polyfill, - ); - }); - - const annotations = docblock.parseAsObject(docblock.extract(code)); - - callback(null, { - type: 'code', - details: { - assetContent: null, - code, - file: filename, - hasteID: annotations.providesModule || null, - transformed, - type: options.polyfill ? 'script' : 'module', - }, - }); - }); - return; -} - -function transformJSON(json, options, callback) { - const value = JSON.parse(json); - const {filename} = options; - const code = `__d(function(${JsFileWrapping.MODULE_FACTORY_PARAMETERS.join(', ')}) { module.exports = \n${json}\n});`; - - const moduleData = { - code, - map: null, // no source map for JSON files! - dependencies: [], - }; - const transformed = {}; - - Object.keys(options.variants || defaultVariants).forEach( - key => (transformed[key] = moduleData), - ); - - const result: TransformedCodeFile = { - assetContent: null, - code: json, - file: filename, - hasteID: value.name, - transformed, - type: 'module', - }; - - if (basename(filename) === 'package.json') { - result.package = { - name: value.name, - main: value.main, - browser: value.browser, - 'react-native': value['react-native'], - }; - } - callback(null, {type: 'code', details: result}); -} - -function transformAsset( - content: Buffer, - options: TransformOptions, - callback: Callback, -) { - callback(null, { - details: { - assetContentBase64: content.toString('base64'), - filePath: options.filename, - }, - type: 'asset', - }); -} - -function makeResult(ast, filename, sourceCode, isPolyfill = false) { - let dependencies, dependencyMapName, file; - if (isPolyfill) { - dependencies = []; - file = JsFileWrapping.wrapPolyfill(ast); - } else { - ({dependencies, dependencyMapName} = collectDependencies(ast)); - file = JsFileWrapping.wrapModule(ast, dependencyMapName); - } - - const gen = generate(file, filename, sourceCode); - return {code: gen.code, map: gen.map, dependencies, dependencyMapName}; -} - -module.exports = transformModule; diff --git a/packager/src/ModuleGraph/worker/wrap-worker-fn.js b/packager/src/ModuleGraph/worker/wrap-worker-fn.js deleted file mode 100644 index 8d3df8c50c522b..00000000000000 --- a/packager/src/ModuleGraph/worker/wrap-worker-fn.js +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Copyright (c) 2016-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. - * - * @flow - */ -'use strict'; - -const fs = require('fs'); -const mkdirp = require('mkdirp'); - -const {dirname} = require('path'); - -import type {Callback} from '../types.flow'; - -type Path = string; -type WorkerFn = ( - fileContents: Buffer, - options: Options, - callback: Callback, -) => void; -export type WorkerFnWithIO = ( - infile: Path, - outfile: Path, - options: Options, - callback: Callback<>, -) => void; - -function wrapWorkerFn( - workerFunction: WorkerFn, -): WorkerFnWithIO { - return ( - infile: Path, - outfile: Path, - options: Options, - callback: Callback<>, - ) => { - const contents = fs.readFileSync(infile); - workerFunction(contents, options, (error, result) => { - if (error) { - callback(error); - return; - } - - try { - mkdirp.sync(dirname(outfile)); - fs.writeFileSync(outfile, JSON.stringify(result), 'utf8'); - } catch (writeError) { - callback(writeError); - return; - } - - callback(null); - }); - }; -} - -module.exports = wrapWorkerFn; diff --git a/packager/src/Resolver/__tests__/Resolver-test.js b/packager/src/Resolver/__tests__/Resolver-test.js deleted file mode 100644 index 353a68480c252b..00000000000000 --- a/packager/src/Resolver/__tests__/Resolver-test.js +++ /dev/null @@ -1,553 +0,0 @@ -/** - * 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'; - -jest.useRealTimers(); - -jest.unmock('../'); -jest.unmock('../../defaults'); -jest.mock('path'); - -const {join: pathJoin} = require.requireActual('path'); -const DependencyGraph = jest.fn(); -jest.setMock('../../node-haste/DependencyGraph', DependencyGraph); -let Module; -let Polyfill; - -describe('Resolver', function() { - let Resolver, path; - - beforeEach(function() { - Resolver = require('../'); - path = require('path'); - DependencyGraph.mockClear(); - Module = jest.fn(function() { - this.getName = jest.fn(); - this.getDependencies = jest.fn(); - this.isPolyfill = jest.fn().mockReturnValue(false); - this.isJSON = jest.fn().mockReturnValue(false); - }); - Polyfill = jest.fn(function() { - var polyfill = new Module(); - polyfill.isPolyfill.mockReturnValue(true); - return polyfill; - }); - - DependencyGraph.load = jest.fn().mockImplementation( - opts => Promise.resolve(new DependencyGraph(opts)), - ); - DependencyGraph.prototype.createPolyfill = jest.fn(); - DependencyGraph.prototype.getDependencies = jest.fn(); - - // For the polyfillDeps - path.join = jest.fn((a, b) => b); - - DependencyGraph.prototype.load = jest.fn(() => Promise.resolve()); - }); - - class ResolutionResponseMock { - constructor({dependencies, mainModuleId}) { - this.dependencies = dependencies; - this.mainModuleId = mainModuleId; - this.getModuleId = createGetModuleId(); - } - - prependDependency(dependency) { - this.dependencies.unshift(dependency); - } - - finalize() { - return Promise.resolve(this); - } - - getResolvedDependencyPairs() { - return []; - } - } - - function createModule(id, dependencies) { - var module = new Module({}); - module.path = id; - module.getName.mockImplementation(() => Promise.resolve(id)); - module.getDependencies.mockImplementation(() => Promise.resolve(dependencies)); - return module; - } - - function createJsonModule(id) { - const module = createModule(id, []); - module.isJSON.mockReturnValue(true); - return module; - } - - function createPolyfill(id, dependencies) { - var polyfill = new Polyfill({}); - polyfill.getName = jest.fn(() => Promise.resolve(id)); - polyfill.getDependencies = - jest.fn(() => Promise.resolve(dependencies)); - return polyfill; - } - - describe('getDependencies', function() { - it('forwards transform options to the dependency graph', function() { - expect.assertions(1); - const transformOptions = {arbitrary: 'options'}; - const platform = 'ios'; - const entry = '/root/index.js'; - - DependencyGraph.prototype.getDependencies.mockImplementation( - () => Promise.reject()); - return Resolver.load({projectRoot: '/root'}) - .then(r => r.getDependencies(entry, {platform}, transformOptions)) - .catch(() => { - expect(DependencyGraph.prototype.getDependencies).toBeCalledWith({ - entryPath: entry, - platform, - options: transformOptions, - recursive: true, - }); - }); - }); - - it('passes custom platforms to the dependency graph', function() { - expect.assertions(1); - return Resolver.load({ // eslint-disable-line no-new - projectRoot: '/root', - platforms: ['ios', 'windows', 'vr'], - }).then(() => { - const platforms = DependencyGraph.mock.calls[0][0].platforms; - expect(Array.from(platforms)).toEqual(['ios', 'windows', 'vr']); - }); - }); - - it('should get dependencies with polyfills', function() { - expect.assertions(5); - - var module = createModule('index'); - var deps = [module]; - - var depResolverPromise = Resolver.load({ - projectRoot: '/root', - }); - - DependencyGraph.prototype.getDependencies.mockImplementation(function() { - return Promise.resolve(new ResolutionResponseMock({ - dependencies: deps, - mainModuleId: 'index', - })); - }); - - const polyfill = { - id: 'polyfills/Object.es6.js', - file: 'polyfills/Object.es6.js', - dependencies: [], - }; - DependencyGraph.prototype.createPolyfill.mockReturnValueOnce(polyfill); - - return depResolverPromise - .then(r => r.getDependencies( - '/root/index.js', - {dev: false}, - undefined, - undefined, - createGetModuleId() - )).then(function(result) { - expect(result.mainModuleId).toEqual('index'); - expect(result.dependencies[result.dependencies.length - 1]).toBe(module); - - expect(DependencyGraph.mock.instances[0].getDependencies) - .toBeCalledWith({entryPath: '/root/index.js', recursive: true}); - expect(result.dependencies[0]).toEqual(polyfill); - - expect( - DependencyGraph - .prototype - .createPolyfill - .mock - .calls - .map(call => call[0])) - .toEqual([ - {id: 'polyfills/Object.es6.js', - file: 'polyfills/Object.es6.js', - dependencies: [], - }, - {id: 'polyfills/console.js', - file: 'polyfills/console.js', - dependencies: [ - 'polyfills/Object.es6.js', - ], - }, - {id: 'polyfills/error-guard.js', - file: 'polyfills/error-guard.js', - dependencies: [ - 'polyfills/Object.es6.js', - 'polyfills/console.js', - ], - }, - {id: 'polyfills/Number.es6.js', - file: 'polyfills/Number.es6.js', - dependencies: [ - 'polyfills/Object.es6.js', - 'polyfills/console.js', - 'polyfills/error-guard.js', - ], - }, - {id: 'polyfills/String.prototype.es6.js', - file: 'polyfills/String.prototype.es6.js', - dependencies: [ - 'polyfills/Object.es6.js', - 'polyfills/console.js', - 'polyfills/error-guard.js', - 'polyfills/Number.es6.js', - ], - }, - {id: 'polyfills/Array.prototype.es6.js', - file: 'polyfills/Array.prototype.es6.js', - dependencies: [ - 'polyfills/Object.es6.js', - 'polyfills/console.js', - 'polyfills/error-guard.js', - 'polyfills/Number.es6.js', - 'polyfills/String.prototype.es6.js', - ], - }, - {id: 'polyfills/Array.es6.js', - file: 'polyfills/Array.es6.js', - dependencies: [ - 'polyfills/Object.es6.js', - 'polyfills/console.js', - 'polyfills/error-guard.js', - 'polyfills/Number.es6.js', - 'polyfills/String.prototype.es6.js', - 'polyfills/Array.prototype.es6.js', - ], - }, - {id: 'polyfills/Object.es7.js', - file: 'polyfills/Object.es7.js', - dependencies: [ - 'polyfills/Object.es6.js', - 'polyfills/console.js', - 'polyfills/error-guard.js', - 'polyfills/Number.es6.js', - 'polyfills/String.prototype.es6.js', - 'polyfills/Array.prototype.es6.js', - 'polyfills/Array.es6.js', - ], - }, - {id: 'polyfills/babelHelpers.js', - file: 'polyfills/babelHelpers.js', - dependencies: [ - 'polyfills/Object.es6.js', - 'polyfills/console.js', - 'polyfills/error-guard.js', - 'polyfills/Number.es6.js', - 'polyfills/String.prototype.es6.js', - 'polyfills/Array.prototype.es6.js', - 'polyfills/Array.es6.js', - 'polyfills/Object.es7.js', - ], - }, - ].map(({id, file, dependencies}) => ({ - id: pathJoin(__dirname, '..', id), - file: pathJoin(__dirname, '..', file), - dependencies: dependencies.map(d => pathJoin(__dirname, '..', d)), - }))); - }); - }); - - it('should pass in more polyfills', function() { - expect.assertions(2); - - var module = createModule('index'); - var deps = [module]; - - var depResolverPromise = Resolver.load({ - projectRoot: '/root', - polyfillModuleNames: ['some module'], - }); - - DependencyGraph.prototype.getDependencies.mockImplementation(function() { - return Promise.resolve(new ResolutionResponseMock({ - dependencies: deps, - mainModuleId: 'index', - })); - }); - - return depResolverPromise - .then(r => r.getDependencies( - '/root/index.js', - {dev: false}, - undefined, - undefined, - createGetModuleId() - )).then(result => { - expect(result.mainModuleId).toEqual('index'); - const calls = - DependencyGraph.prototype.createPolyfill.mock.calls[result.dependencies.length - 2]; - expect(calls).toEqual([ - {file: 'some module', - id: 'some module', - dependencies: [ - 'polyfills/Object.es6.js', - 'polyfills/console.js', - 'polyfills/error-guard.js', - 'polyfills/Number.es6.js', - 'polyfills/String.prototype.es6.js', - 'polyfills/Array.prototype.es6.js', - 'polyfills/Array.es6.js', - 'polyfills/Object.es7.js', - 'polyfills/babelHelpers.js', - ].map(d => pathJoin(__dirname, '..', d)), - }, - ]); - }); - }); - }); - - describe('wrapModule', function() { - let depResolver; - beforeEach(() => { - return Resolver.load({ - projectRoot: '/root', - }).then(r => { depResolver = r; }); - }); - - it('should resolve modules', function() { - expect.assertions(1); - - /*eslint-disable */ - var code = [ - // require - 'require("x")', - 'require("y");require(\'abc\');', - 'require( \'z\' )', - 'require( "a")', - 'require("b" )', - ].join('\n'); - /*eslint-disable */ - - function *findDependencyOffsets() { - const re = /(['"']).*?\1/g; - let match; - while ((match = re.exec(code))) { - yield match.index; - } - } - - const dependencyOffsets = Array.from(findDependencyOffsets()); - const module = createModule('test module', ['x', 'y']); - const resolutionResponse = new ResolutionResponseMock({ - dependencies: [module], - mainModuleId: 'test module', - }); - - resolutionResponse.getResolvedDependencyPairs = (module) => { - return [ - ['x', createModule('changed')], - ['y', createModule('Y')], - ['abc', createModule('abc')] - ]; - } - - const moduleIds = new Map( - resolutionResponse - .getResolvedDependencyPairs() - .map(([importId, module]) => [ - importId, - padRight(resolutionResponse.getModuleId(module), importId.length + 2), - ]) - ); - - return depResolver.wrapModule({ - resolutionResponse, - module: module, - name: 'test module', - code, - meta: {dependencyOffsets}, - dev: false, - }).then(({code: processedCode}) => { - expect(processedCode).toEqual([ - '__d(/* test module */function(global, require, module, exports) {' + - // require - `require(${moduleIds.get('x')}) // ${moduleIds.get('x').trim()} = x`, - `require(${moduleIds.get('y')});require(${moduleIds.get('abc') - }); // ${moduleIds.get('abc').trim()} = abc // ${moduleIds.get('y').trim()} = y`, - 'require( \'z\' )', - 'require( "a")', - 'require("b" )', - `}, ${resolutionResponse.getModuleId(module)});`, - ].join('\n')); - }); - }); - - it('should add module transport names as fourth argument to `__d`', () => { - expect.assertions(1); - - const module = createModule('test module'); - const code = 'arbitrary(code)' - const resolutionResponse = new ResolutionResponseMock({ - dependencies: [module], - mainModuleId: 'test module', - }); - return depResolver.wrapModule({ - resolutionResponse, - code, - module, - name: 'test module', - dev: true, - }).then(({code: processedCode}) => - expect(processedCode).toEqual([ - '__d(/* test module */function(global, require, module, exports) {' + - code, - `}, ${resolutionResponse.getModuleId(module)}, null, "test module");` - ].join('\n')) - ); - }); - - it('should pass through passed-in source maps', () => { - expect.assertions(1); - const module = createModule('test module'); - const resolutionResponse = new ResolutionResponseMock({ - dependencies: [module], - mainModuleId: 'test module', - }); - const inputMap = {version: 3, mappings: 'ARBITRARY'}; - return depResolver.wrapModule({ - resolutionResponse, - module, - name: 'test module', - code: 'arbitrary(code)', - map: inputMap, - }).then(({map}) => expect(map).toBe(inputMap)); - }); - - it('should resolve polyfills', function () { - expect.assertions(1); - return Resolver.load({ - projectRoot: '/root', - }).then(depResolver => {; - const polyfill = createPolyfill('test polyfill', []); - const code = [ - 'global.fetch = () => 1;', - ].join(''); - return depResolver.wrapModule({ - module: polyfill, - code - }).then(({code: processedCode}) => { - expect(processedCode).toEqual([ - '(function(global) {', - 'global.fetch = () => 1;', - '\n})' + - "(typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : this);", - ].join('')); - }); - }); - }); - - describe('JSON files:', () => { - const code = JSON.stringify({arbitrary: "data"}); - const id = 'arbitrary.json'; - let depResolver, module, resolutionResponse; - - beforeEach(() => { - return Resolver.load({projectRoot: '/root'}).then(r => { - depResolver = r; - module = createJsonModule(id); - resolutionResponse = new ResolutionResponseMock({ - dependencies: [module], - mainModuleId: id, - }); - }); - }); - - it('should prefix JSON files with `module.exports=`', () => { - expect.assertions(1); - return depResolver - .wrapModule({resolutionResponse, module, name: id, code, dev: false}) - .then(({code: processedCode}) => - expect(processedCode).toEqual([ - `__d(/* ${id} */function(global, require, module, exports) {`, - `module.exports = ${code}\n}, ${resolutionResponse.getModuleId(module)});`, - ].join(''))); - }); - }); - - describe('minification:', () => { - const code ='arbitrary(code)'; - const id = 'arbitrary.js'; - let depResolver, minifyCode, module, resolutionResponse, sourceMap; - - beforeEach(() => { - minifyCode = jest.fn((filename, code, map) => - Promise.resolve({code, map})); - module = createModule(id); - module.path = '/arbitrary/path.js'; - resolutionResponse = new ResolutionResponseMock({ - dependencies: [module], - mainModuleId: id, - }); - sourceMap = {version: 3, sources: ['input'], mappings: 'whatever'}; - return Resolver.load({ - projectRoot: '/root', - minifyCode, - }).then(r => { depResolver = r; }); - }); - - it('should invoke the minifier with the wrapped code', () => { - expect.assertions(1); - const wrappedCode = - `__d(/* ${id} */function(global, require, module, exports) {${ - code}\n}, ${resolutionResponse.getModuleId(module)});` - return depResolver - .wrapModule({ - resolutionResponse, - module, - name: id, - code, - map: sourceMap, - minify: true, - dev: false, - }).then(() => { - expect(minifyCode).toBeCalledWith(module.path, wrappedCode, sourceMap); - }); - }); - - it('should use minified code', () => { - expect.assertions(2); - const minifiedCode = 'minified(code)'; - const minifiedMap = {version: 3, file: ['minified']}; - minifyCode.mockReturnValue(Promise.resolve({code: minifiedCode, map: minifiedMap})); - return depResolver - .wrapModule({resolutionResponse, module, name: id, code, minify: true}) - .then(({code, map}) => { - expect(code).toEqual(minifiedCode); - expect(map).toEqual(minifiedMap); - }); - }); - }); - }); - - function createGetModuleId() { - let nextId = 1; - const knownIds = new Map(); - function createId(path) { - const id = nextId; - nextId += 1; - knownIds.set(path, id); - return id; - } - - return ({path}) => knownIds.get(path) || createId(path); - } - - function padRight(value, width) { - const s = String(value); - const diff = width - s.length; - return diff > 0 ? s + Array(diff + 1).join(' ') : s; - } -}); diff --git a/packager/src/Resolver/index.js b/packager/src/Resolver/index.js deleted file mode 100644 index cedd0905578ff3..00000000000000 --- a/packager/src/Resolver/index.js +++ /dev/null @@ -1,290 +0,0 @@ -/** - * 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. - * - * @flow - */ - -'use strict'; - -const DependencyGraph = require('../node-haste/DependencyGraph'); - -const defaults = require('../defaults'); -const pathJoin = require('path').join; - -import type ResolutionResponse from '../node-haste/DependencyGraph/ResolutionResponse'; -import type Module, {HasteImpl, TransformCode} from '../node-haste/Module'; -import type {MappingsMap} from '../lib/SourceMap'; -import type {PostMinifyProcess} from '../Bundler'; -import type {Options as JSTransformerOptions} from '../JSTransformer/worker'; -import type {Reporter} from '../lib/reporting'; -import type {TransformCache, GetTransformCacheKey} from '../lib/TransformCaching'; -import type {GlobalTransformCache} from '../lib/GlobalTransformCache'; - -type MinifyCode = (filePath: string, code: string, map: MappingsMap) => - Promise<{code: string, map: MappingsMap}>; - -type ContainsTransformerOptions = {+transformer: JSTransformerOptions} - -type Options = {| - +assetExts: Array, - +blacklistRE?: RegExp, - +extraNodeModules: ?{}, - +getTransformCacheKey: GetTransformCacheKey, - +globalTransformCache: ?GlobalTransformCache, - +hasteImpl?: HasteImpl, - +maxWorkerCount: number, - +minifyCode: MinifyCode, - +postMinifyProcess: PostMinifyProcess, - +platforms: Set, - +polyfillModuleNames?: Array, - +projectRoots: $ReadOnlyArray, - +providesModuleNodeModules: Array, - +reporter: Reporter, - +resetCache: boolean, - +sourceExts: Array, - +transformCache: TransformCache, - +transformCode: TransformCode, - +watch: boolean, -|}; - -class Resolver { - - _depGraph: DependencyGraph; - _minifyCode: MinifyCode; - _postMinifyProcess: PostMinifyProcess; - _polyfillModuleNames: Array; - - constructor(opts: Options, depGraph: DependencyGraph) { - this._minifyCode = opts.minifyCode; - this._postMinifyProcess = opts.postMinifyProcess; - this._polyfillModuleNames = opts.polyfillModuleNames || []; - this._depGraph = depGraph; - } - - static async load(opts: Options): Promise { - const depGraphOpts = Object.assign(Object.create(opts), { - assetDependencies: ['react-native/Libraries/Image/AssetRegistry'], - forceNodeFilesystemAPI: false, - ignoreFilePath(filepath) { - return filepath.indexOf('__tests__') !== -1 || - (opts.blacklistRE != null && opts.blacklistRE.test(filepath)); - }, - moduleOptions: { - hasteImpl: opts.hasteImpl, - resetCache: opts.resetCache, - transformCache: opts.transformCache, - }, - preferNativePlatform: true, - roots: opts.projectRoots, - useWatchman: true, - }); - const depGraph = await DependencyGraph.load(depGraphOpts); - return new Resolver(opts, depGraph); - } - - getShallowDependencies( - entryFile: string, - transformOptions: JSTransformerOptions, - ): Promise> { - return this._depGraph.getShallowDependencies(entryFile, transformOptions); - } - - getModuleForPath(entryFile: string): Module { - return this._depGraph.getModuleForPath(entryFile); - } - - getDependencies( - entryPath: string, - options: {platform: ?string, recursive?: boolean}, - bundlingOptions: T, - onProgress?: ?(finishedModules: number, totalModules: number) => mixed, - getModuleId: mixed, - ): Promise> { - const {platform, recursive = true} = options; - return this._depGraph.getDependencies({ - entryPath, - platform, - options: bundlingOptions, - recursive, - onProgress, - }).then(resolutionResponse => { - this._getPolyfillDependencies().reverse().forEach( - polyfill => resolutionResponse.prependDependency(polyfill) - ); - - /* $FlowFixMe: monkey patching */ - resolutionResponse.getModuleId = getModuleId; - return resolutionResponse.finalize(); - }); - } - - getModuleSystemDependencies({dev = true}: {dev?: boolean}): Array { - - const prelude = dev - ? pathJoin(__dirname, 'polyfills/prelude_dev.js') - : pathJoin(__dirname, 'polyfills/prelude.js'); - - const moduleSystem = defaults.moduleSystem; - - return [ - prelude, - moduleSystem, - ].map(moduleName => this._depGraph.createPolyfill({ - file: moduleName, - id: moduleName, - dependencies: [], - })); - } - - _getPolyfillDependencies(): Array { - const polyfillModuleNames = defaults.polyfills.concat(this._polyfillModuleNames); - - return polyfillModuleNames.map( - (polyfillModuleName, idx) => this._depGraph.createPolyfill({ - file: polyfillModuleName, - id: polyfillModuleName, - dependencies: polyfillModuleNames.slice(0, idx), - }) - ); - } - - resolveRequires( - resolutionResponse: ResolutionResponse, - module: Module, - code: string, - dependencyOffsets: Array = [], - ): string { - 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) { - /* $FlowFixMe: `getModuleId` is monkey-patched so may not exist */ - resolvedDeps[depName] = resolutionResponse.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); - return dependencyOffsets.reduceRight( - ([unhandled, handled], offset) => [ - unhandled.slice(0, offset), - replaceDependencyID(unhandled.slice(offset) + handled, resolvedDeps), - ], - [code, ''], - ).join(''); - } - - wrapModule({ - resolutionResponse, - module, - name, - map, - code, - meta = {}, - dev = true, - minify = false, - }: { - resolutionResponse: ResolutionResponse, - module: Module, - name: string, - map: MappingsMap, - code: string, - meta?: { - dependencyOffsets?: Array, - }, - dev?: boolean, - minify?: boolean, - }) { - if (module.isJSON()) { - code = `module.exports = ${code}`; - } - - if (module.isPolyfill()) { - code = definePolyfillCode(code); - } else { - /* $FlowFixMe: `getModuleId` is monkey-patched so may not exist */ - const moduleId = resolutionResponse.getModuleId(module); - code = this.resolveRequires( - resolutionResponse, - module, - code, - meta.dependencyOffsets - ); - code = defineModuleCode(moduleId, code, name, dev); - } - - return minify - ? this._minifyCode(module.path, code, map).then(this._postMinifyProcess) - : Promise.resolve({code, map}); - } - - minifyModule( - {path, code, map}: {path: string, code: string, map: MappingsMap}, - ): Promise<{code: string, map: MappingsMap}> { - return this._minifyCode(path, code, map); - } - - getDependencyGraph(): DependencyGraph { - return this._depGraph; - } -} - -function defineModuleCode(moduleName, code, verboseName = '', dev = true) { - return [ - `__d(/* ${verboseName} */`, - 'function(global, require, module, exports) {', // module factory - code, - '\n}, ', - `${JSON.stringify(moduleName)}`, // module id, null = id map. used in ModuleGraph - dev ? `, null, ${JSON.stringify(verboseName)}` : '', - ');', - ].join(''); -} - -function definePolyfillCode(code) { - return [ - '(function(global) {', - code, - `\n})(typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : this);`, - ].join(''); -} - -const reDepencencyString = /^(['"])([^'"']*)\1/; -function replaceDependencyID(stringWithDependencyIDAtStart, resolvedDeps) { - const match = reDepencencyString.exec(stringWithDependencyIDAtStart); - const dependencyName = match && match[2]; - if (match != null && dependencyName in resolvedDeps) { - const {length} = match[0]; - const id = String(resolvedDeps[dependencyName]); - return ( - padRight(id, length) + - stringWithDependencyIDAtStart - .slice(length) - .replace(/$/m, ` // ${id} = ${dependencyName}`) - ); - } else { - return stringWithDependencyIDAtStart; - } -} - -function padRight(string, length) { - return string.length < length - ? string + Array(length - string.length + 1).join(' ') - : string; -} - -module.exports = Resolver; diff --git a/packager/src/Resolver/polyfills/Array.es6.js b/packager/src/Resolver/polyfills/Array.es6.js deleted file mode 100644 index 87c83ed25673c2..00000000000000 --- a/packager/src/Resolver/polyfills/Array.es6.js +++ /dev/null @@ -1,86 +0,0 @@ -/** - * Copyright (c) 2013-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. - * - * @polyfill - */ - -/* eslint-disable */ - -/** - * Creates an array from array like objects. - * - * https://people.mozilla.org/~jorendorff/es6-draft.html#sec-array.from - */ -if (!Array.from) { - Array.from = function(arrayLike /*, mapFn, thisArg */) { - if (arrayLike == null) { - throw new TypeError('Object is null or undefined'); - } - - // Optional args. - var mapFn = arguments[1]; - var thisArg = arguments[2]; - - var C = this; - var items = Object(arrayLike); - var symbolIterator = typeof Symbol === 'function' - ? Symbol.iterator - : '@@iterator'; - var mapping = typeof mapFn === 'function'; - var usingIterator = typeof items[symbolIterator] === 'function'; - var key = 0; - var ret; - var value; - - if (usingIterator) { - ret = typeof C === 'function' - ? new C() - : []; - var it = items[symbolIterator](); - var next; - - while (!(next = it.next()).done) { - value = next.value; - - if (mapping) { - value = mapFn.call(thisArg, value, key); - } - - ret[key] = value; - key += 1; - } - - ret.length = key; - return ret; - } - - var len = items.length; - if (isNaN(len) || len < 0) { - len = 0; - } - - ret = typeof C === 'function' - ? new C(len) - : new Array(len); - - while (key < len) { - value = items[key]; - - if (mapping) { - value = mapFn.call(thisArg, value, key); - } - - ret[key] = value; - - key += 1; - } - - ret.length = key; - return ret; - }; -} diff --git a/packager/src/Resolver/polyfills/Array.prototype.es6.js b/packager/src/Resolver/polyfills/Array.prototype.es6.js deleted file mode 100644 index 612d3c3849bc3b..00000000000000 --- a/packager/src/Resolver/polyfills/Array.prototype.es6.js +++ /dev/null @@ -1,95 +0,0 @@ -/** - * 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. - * - * @polyfill - */ - -/* eslint-disable */ - -// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex -function findIndex(predicate, context) { - if (this == null) { - throw new TypeError( - 'Array.prototype.findIndex called on null or undefined' - ); - } - if (typeof predicate !== 'function') { - throw new TypeError('predicate must be a function'); - } - var list = Object(this); - var length = list.length >>> 0; - for (var i = 0; i < length; i++) { - if (predicate.call(context, list[i], i, list)) { - return i; - } - } - return -1; -} - -if (!Array.prototype.findIndex) { - Object.defineProperty(Array.prototype, 'findIndex', { - enumerable: false, - writable: true, - configurable: true, - value: findIndex - }); -} - -// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find -if (!Array.prototype.find) { - Object.defineProperty(Array.prototype, 'find', { - enumerable: false, - writable: true, - configurable: true, - value: function(predicate, context) { - if (this == null) { - throw new TypeError( - 'Array.prototype.find called on null or undefined' - ); - } - var index = findIndex.call(this, predicate, context); - return index === -1 ? undefined : this[index]; - } - }); -} - -// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/includes -if (!Array.prototype.includes) { - Object.defineProperty(Array.prototype, 'includes', { - enumerable: false, - writable: true, - configurable: true, - value: function (searchElement) { - var O = Object(this); - var len = parseInt(O.length) || 0; - if (len === 0) { - return false; - } - var n = parseInt(arguments[1]) || 0; - var k; - if (n >= 0) { - k = n; - } else { - k = len + n; - if (k < 0) { - k = 0; - } - } - var currentElement; - while (k < len) { - currentElement = O[k]; - if (searchElement === currentElement || - (searchElement !== searchElement && currentElement !== currentElement)) { - return true; - } - k++; - } - return false; - } - }); -} diff --git a/packager/src/Resolver/polyfills/Number.es6.js b/packager/src/Resolver/polyfills/Number.es6.js deleted file mode 100644 index bd669c3c4b8644..00000000000000 --- a/packager/src/Resolver/polyfills/Number.es6.js +++ /dev/null @@ -1,41 +0,0 @@ -/** - * 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. - * - * @polyfill - */ - -/* eslint-disable strict */ - -if (Number.EPSILON === undefined) { - Object.defineProperty(Number, 'EPSILON', { - value: Math.pow(2, -52), - }); -} -if (Number.MAX_SAFE_INTEGER === undefined) { - Object.defineProperty(Number, 'MAX_SAFE_INTEGER', { - value: Math.pow(2, 53) - 1, - }); -} -if (Number.MIN_SAFE_INTEGER === undefined) { - Object.defineProperty(Number, 'MIN_SAFE_INTEGER', { - value: -(Math.pow(2, 53) - 1), - }); -} -if (!Number.isNaN) { - // eslint-disable-next-line max-len - // https://github.com/dherman/tc39-codex-wiki/blob/master/data/es6/number/index.md#polyfill-for-numberisnan - const globalIsNaN = global.isNaN; - Object.defineProperty(Number, 'isNaN', { - configurable: true, - enumerable: false, - value: function isNaN(value) { - return typeof value === 'number' && globalIsNaN(value); - }, - writable: true, - }); -} diff --git a/packager/src/Resolver/polyfills/Object.es6.js b/packager/src/Resolver/polyfills/Object.es6.js deleted file mode 100644 index 64943664305b75..00000000000000 --- a/packager/src/Resolver/polyfills/Object.es6.js +++ /dev/null @@ -1,68 +0,0 @@ -/** - * 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. - * - * @polyfill - */ - -/* eslint-disable strict */ - -// WARNING: This is an optimized version that fails on hasOwnProperty checks -// and non objects. It's not spec-compliant. It's a perf optimization. -// This is only needed for iOS 8 and current Android JSC. - -Object.assign = function(target, sources) { - if (__DEV__) { - if (target == null) { - throw new TypeError('Object.assign target cannot be null or undefined'); - } - if (typeof target !== 'object' && typeof target !== 'function') { - throw new TypeError( - 'In this environment the target of assign MUST be an object.' + - 'This error is a performance optimization and not spec compliant.' - ); - } - } - - for (var nextIndex = 1; nextIndex < arguments.length; nextIndex++) { - var nextSource = arguments[nextIndex]; - if (nextSource == null) { - continue; - } - - if (__DEV__) { - if (typeof nextSource !== 'object' && - typeof nextSource !== 'function') { - throw new TypeError( - 'In this environment the sources for assign MUST be an object.' + - 'This error is a performance optimization and not spec compliant.' - ); - } - } - - // We don't currently support accessors nor proxies. Therefore this - // copy cannot throw. If we ever supported this then we must handle - // exceptions and side-effects. - - for (var key in nextSource) { - if (__DEV__) { - var hasOwnProperty = Object.prototype.hasOwnProperty; - if (!hasOwnProperty.call(nextSource, key)) { - throw new TypeError( - 'One of the sources for assign has an enumerable key on the ' + - 'prototype chain. Are you trying to assign a prototype property? ' + - 'We don\'t allow it, as this is an edge case that we do not support. ' + - 'This error is a performance optimization and not spec compliant.' - ); - } - } - target[key] = nextSource[key]; - } - } - - return target; -}; diff --git a/packager/src/Resolver/polyfills/Object.es7.js b/packager/src/Resolver/polyfills/Object.es7.js deleted file mode 100644 index dc5dc893b54398..00000000000000 --- a/packager/src/Resolver/polyfills/Object.es7.js +++ /dev/null @@ -1,59 +0,0 @@ -/** - * 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. - * - * @polyfill - */ - -(function() { - 'use strict'; - - const hasOwnProperty = Object.prototype.hasOwnProperty; - - /** - * Returns an array of the given object's own enumerable entries. - * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/entries - */ - if (typeof Object.entries !== 'function') { - Object.entries = function(object) { - // `null` and `undefined` values are not allowed. - if (object == null) { - throw new TypeError('Object.entries called on non-object'); - } - - const entries = []; - for (const key in object) { - if (hasOwnProperty.call(object, key)) { - entries.push([key, object[key]]); - } - } - return entries; - }; - } - - /** - * Returns an array of the given object's own enumerable entries. - * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/values - */ - if (typeof Object.values !== 'function') { - Object.values = function(object) { - // `null` and `undefined` values are not allowed. - if (object == null) { - throw new TypeError('Object.values called on non-object'); - } - - const values = []; - for (const key in object) { - if (hasOwnProperty.call(object, key)) { - values.push(object[key]); - } - } - return values; - }; - } - -})(); diff --git a/packager/src/Resolver/polyfills/String.prototype.es6.js b/packager/src/Resolver/polyfills/String.prototype.es6.js deleted file mode 100644 index a033f35efd6be1..00000000000000 --- a/packager/src/Resolver/polyfills/String.prototype.es6.js +++ /dev/null @@ -1,92 +0,0 @@ -/** - * Copyright (c) 2013-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. - * - * @polyfill - */ - -/* eslint-disable strict, no-extend-native, no-bitwise */ - -/* - * NOTE: We use (Number(x) || 0) to replace NaN values with zero. - */ - -if (!String.prototype.startsWith) { - String.prototype.startsWith = function(search) { - 'use strict'; - if (this == null) { - throw TypeError(); - } - var string = String(this); - var pos = arguments.length > 1 ? - (Number(arguments[1]) || 0) : 0; - var start = Math.min(Math.max(pos, 0), string.length); - return string.indexOf(String(search), pos) === start; - }; -} - -if (!String.prototype.endsWith) { - String.prototype.endsWith = function(search) { - 'use strict'; - if (this == null) { - throw TypeError(); - } - var string = String(this); - var stringLength = string.length; - var searchString = String(search); - var pos = arguments.length > 1 ? - (Number(arguments[1]) || 0) : stringLength; - var end = Math.min(Math.max(pos, 0), stringLength); - var start = end - searchString.length; - if (start < 0) { - return false; - } - return string.lastIndexOf(searchString, start) === start; - }; -} - -if (!String.prototype.repeat) { - String.prototype.repeat = function(count) { - 'use strict'; - if (this == null) { - throw TypeError(); - } - var string = String(this); - count = Number(count) || 0; - if (count < 0 || count === Infinity) { - throw RangeError(); - } - if (count === 1) { - return string; - } - var result = ''; - while (count) { - if (count & 1) { - result += string; - } - if ((count >>= 1)) { - string += string; - } - } - return result; - }; -} - -if (!String.prototype.includes) { - String.prototype.includes = function(search, start) { - 'use strict'; - if (typeof start !== 'number') { - start = 0; - } - - if (start + search.length > this.length) { - return false; - } else { - return this.indexOf(search, start) !== -1; - } - }; -} diff --git a/packager/src/Resolver/polyfills/__tests__/Object.es7-test.js b/packager/src/Resolver/polyfills/__tests__/Object.es7-test.js deleted file mode 100644 index 9024d97f26906b..00000000000000 --- a/packager/src/Resolver/polyfills/__tests__/Object.es7-test.js +++ /dev/null @@ -1,127 +0,0 @@ -/** - * Copyright (c) 2013-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. - * - * @emails oncall+jsinfra - */ - -/* eslint-disable fb-www/object-create-only-one-param */ - -'use strict'; - -jest.disableAutomock(); - -describe('Object (ES7)', () => { - beforeEach(() => { - delete Object.entries; - delete Object.values; - jest.resetModules(); - require('../Object.es7'); - }); - - describe('Object.entries', () => { - it('should have a length of 1', () => { - expect(Object.entries.length).toBe(1); - }); - - it('should check for type', () => { - expect(Object.entries.bind(null, null)).toThrow(TypeError( - 'Object.entries called on non-object' - )); - expect(Object.entries.bind(null, undefined)).toThrow(TypeError( - 'Object.entries called on non-object' - )); - expect(Object.entries.bind(null, [])).not.toThrow(); - expect(Object.entries.bind(null, () => {})).not.toThrow(); - expect(Object.entries.bind(null, {})).not.toThrow(); - expect(Object.entries.bind(null, 'abc')).not.toThrow(); - }); - - it('should return enumerable entries', () => { - const foo = Object.defineProperties({}, { - x: {value: 10, enumerable: true}, - y: {value: 20}, - }); - - expect(Object.entries(foo)).toEqual([['x', 10]]); - - const bar = {x: 10, y: 20}; - expect(Object.entries(bar)).toEqual([['x', 10], ['y', 20]]); - }); - - it('should work with proto-less objects', () => { - const foo = Object.create(null, { - x: {value: 10, enumerable: true}, - y: {value: 20}, - }); - - expect(Object.entries(foo)).toEqual([['x', 10]]); - }); - - it('should return only own entries', () => { - const foo = Object.create({z: 30}, { - x: {value: 10, enumerable: true}, - y: {value: 20}, - }); - - expect(Object.entries(foo)).toEqual([['x', 10]]); - }); - - it('should convert to object primitive string', () => { - expect(Object.entries('ab')).toEqual([['0', 'a'], ['1', 'b']]); - }); - }); - - describe('Object.values', () => { - it('should have a length of 1', () => { - expect(Object.values.length).toBe(1); - }); - - it('should check for type', () => { - expect(Object.values.bind(null, null)).toThrow(TypeError( - 'Object.values called on non-object' - )); - expect(Object.values.bind(null, [])).not.toThrow(); - expect(Object.values.bind(null, () => {})).not.toThrow(); - expect(Object.values.bind(null, {})).not.toThrow(); - }); - - it('should return enumerable values', () => { - const foo = Object.defineProperties({}, { - x: {value: 10, enumerable: true}, - y: {value: 20}, - }); - - expect(Object.values(foo)).toEqual([10]); - - const bar = {x: 10, y: 20}; - expect(Object.values(bar)).toEqual([10, 20]); - }); - - it('should work with proto-less objects', () => { - const foo = Object.create(null, { - x: {value: 10, enumerable: true}, - y: {value: 20}, - }); - - expect(Object.values(foo)).toEqual([10]); - }); - - it('should return only own values', () => { - const foo = Object.create({z: 30}, { - x: {value: 10, enumerable: true}, - y: {value: 20}, - }); - - expect(Object.values(foo)).toEqual([10]); - }); - - it('should convert to object primitive string', () => { - expect(Object.values('ab')).toEqual(['a', 'b']); - }); - }); -}); diff --git a/packager/src/Resolver/polyfills/babelHelpers.js b/packager/src/Resolver/polyfills/babelHelpers.js deleted file mode 100644 index 51cb4523ec8af4..00000000000000 --- a/packager/src/Resolver/polyfills/babelHelpers.js +++ /dev/null @@ -1,247 +0,0 @@ -/** - * 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. - * - * @polyfill - */ - -/* eslint-disable */ - -// Created by running: -// require('babel-core').buildExternalHelpers('_extends classCallCheck createClass createRawReactElement defineProperty get inherits interopRequireDefault interopRequireWildcard objectWithoutProperties possibleConstructorReturn slicedToArray taggedTemplateLiteral toArray toConsumableArray '.split(' ')) -// then replacing the `global` reference in the last line to also use `this`. -// -// actually, that's a lie, because babel6 omits _extends and createRawReactElement - -var babelHelpers = global.babelHelpers = {}; - -babelHelpers.typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { - return typeof obj; -} : function (obj) { - return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; -}; - -babelHelpers.createRawReactElement = (function () { - var REACT_ELEMENT_TYPE = typeof Symbol === "function" && Symbol.for && Symbol.for("react.element") || 0xeac7; - return function createRawReactElement(type, key, props) { - return { - $$typeof: REACT_ELEMENT_TYPE, - type: type, - key: key, - ref: null, - props: props, - _owner: null - }; - }; -})(); - -babelHelpers.classCallCheck = function (instance, Constructor) { - if (!(instance instanceof Constructor)) { - throw new TypeError("Cannot call a class as a function"); - } -}; - -babelHelpers.createClass = (function () { - function defineProperties(target, props) { - for (var i = 0; i < props.length; i++) { - var descriptor = props[i]; - descriptor.enumerable = descriptor.enumerable || false; - descriptor.configurable = true; - if ("value" in descriptor) descriptor.writable = true; - Object.defineProperty(target, descriptor.key, descriptor); - } - } - - return function (Constructor, protoProps, staticProps) { - if (protoProps) defineProperties(Constructor.prototype, protoProps); - if (staticProps) defineProperties(Constructor, staticProps); - return Constructor; - }; -})(); - -babelHelpers.defineEnumerableProperties = function(obj, descs) { - for (var key in descs) { - var desc = descs[key]; - desc.configurable = (desc.enumerable = true); - if ('value' in desc) desc.writable = true; - Object.defineProperty(obj, key, desc); - } - return obj; -}; - -babelHelpers.defineProperty = function (obj, key, value) { - if (key in obj) { - Object.defineProperty(obj, key, { - value: value, - enumerable: true, - configurable: true, - writable: true - }); - } else { - obj[key] = value; - } - - return obj; -}; - -babelHelpers._extends = babelHelpers.extends = Object.assign || function (target) { - for (var i = 1; i < arguments.length; i++) { - var source = arguments[i]; - - for (var key in source) { - if (Object.prototype.hasOwnProperty.call(source, key)) { - target[key] = source[key]; - } - } - } - - return target; -}; - -babelHelpers.get = function get(object, property, receiver) { - if (object === null) object = Function.prototype; - var desc = Object.getOwnPropertyDescriptor(object, property); - - if (desc === undefined) { - var parent = Object.getPrototypeOf(object); - - if (parent === null) { - return undefined; - } else { - return get(parent, property, receiver); - } - } else if ("value" in desc) { - return desc.value; - } else { - var getter = desc.get; - - if (getter === undefined) { - return undefined; - } - - return getter.call(receiver); - } -}; - -babelHelpers.inherits = function (subClass, superClass) { - if (typeof superClass !== "function" && superClass !== null) { - throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); - } - - subClass.prototype = Object.create(superClass && superClass.prototype, { - constructor: { - value: subClass, - enumerable: false, - writable: true, - configurable: true - } - }); - if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; -}; - -babelHelpers.interopRequireDefault = function (obj) { - return obj && obj.__esModule ? obj : { - default: obj - }; -}; - -babelHelpers.interopRequireWildcard = function (obj) { - if (obj && obj.__esModule) { - return obj; - } else { - var newObj = {}; - - if (obj != null) { - for (var key in obj) { - if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; - } - } - - newObj.default = obj; - return newObj; - } -}; - -babelHelpers.objectWithoutProperties = function (obj, keys) { - var target = {}; - - for (var i in obj) { - if (keys.indexOf(i) >= 0) continue; - if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; - target[i] = obj[i]; - } - - return target; -}; - -babelHelpers.possibleConstructorReturn = function (self, call) { - if (!self) { - throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); - } - - return call && (typeof call === "object" || typeof call === "function") ? call : self; -}; - -babelHelpers.slicedToArray = (function () { - function sliceIterator(arr, i) { - var _arr = []; - var _n = true; - var _d = false; - var _e = undefined; - - try { - for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { - _arr.push(_s.value); - - if (i && _arr.length === i) break; - } - } catch (err) { - _d = true; - _e = err; - } finally { - try { - if (!_n && _i["return"]) _i["return"](); - } finally { - if (_d) throw _e; - } - } - - return _arr; - } - - return function (arr, i) { - if (Array.isArray(arr)) { - return arr; - } else if (Symbol.iterator in Object(arr)) { - return sliceIterator(arr, i); - } else { - throw new TypeError("Invalid attempt to destructure non-iterable instance"); - } - }; -})(); - -babelHelpers.taggedTemplateLiteral = function (strings, raw) { - return Object.freeze(Object.defineProperties(strings, { - raw: { - value: Object.freeze(raw) - } - })); -}; - -babelHelpers.toArray = function (arr) { - return Array.isArray(arr) ? arr : Array.from(arr); -}; - -babelHelpers.toConsumableArray = function (arr) { - if (Array.isArray(arr)) { - for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) arr2[i] = arr[i]; - - return arr2; - } else { - return Array.from(arr); - } -}; diff --git a/packager/src/Resolver/polyfills/console.js b/packager/src/Resolver/polyfills/console.js deleted file mode 100644 index da979a2fb957c9..00000000000000 --- a/packager/src/Resolver/polyfills/console.js +++ /dev/null @@ -1,514 +0,0 @@ -/** - * 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. - * - * @polyfill - * @nolint - */ - -/* eslint-disable */ - -/** - * This pipes all of our console logging functions to native logging so that - * JavaScript errors in required modules show up in Xcode via NSLog. - */ -const inspect = (function() { - // Copyright Joyent, Inc. and other Node contributors. - // - // Permission is hereby granted, free of charge, to any person obtaining a - // copy of this software and associated documentation files (the - // "Software"), to deal in the Software without restriction, including - // without limitation the rights to use, copy, modify, merge, publish, - // distribute, sublicense, and/or sell copies of the Software, and to permit - // persons to whom the Software is furnished to do so, subject to the - // following conditions: - // - // The above copyright notice and this permission notice shall be included - // in all copies or substantial portions of the Software. - // - // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN - // NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, - // DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR - // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE - // USE OR OTHER DEALINGS IN THE SOFTWARE. - // - // https://github.com/joyent/node/blob/master/lib/util.js - - function inspect(obj, opts) { - var ctx = { - seen: [], - stylize: stylizeNoColor - }; - return formatValue(ctx, obj, opts.depth); - } - - function stylizeNoColor(str, styleType) { - return str; - } - - function arrayToHash(array) { - var hash = {}; - - array.forEach(function(val, idx) { - hash[val] = true; - }); - - return hash; - } - - - function formatValue(ctx, value, recurseTimes) { - // Primitive types cannot have properties - var primitive = formatPrimitive(ctx, value); - if (primitive) { - return primitive; - } - - // Look up the keys of the object. - var keys = Object.keys(value); - var visibleKeys = arrayToHash(keys); - - // IE doesn't make error fields non-enumerable - // http://msdn.microsoft.com/en-us/library/ie/dww52sbt(v=vs.94).aspx - if (isError(value) - && (keys.indexOf('message') >= 0 || keys.indexOf('description') >= 0)) { - return formatError(value); - } - - // Some type of object without properties can be shortcutted. - if (keys.length === 0) { - if (isFunction(value)) { - var name = value.name ? ': ' + value.name : ''; - return ctx.stylize('[Function' + name + ']', 'special'); - } - if (isRegExp(value)) { - return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); - } - if (isDate(value)) { - return ctx.stylize(Date.prototype.toString.call(value), 'date'); - } - if (isError(value)) { - return formatError(value); - } - } - - var base = '', array = false, braces = ['{', '}']; - - // Make Array say that they are Array - if (isArray(value)) { - array = true; - braces = ['[', ']']; - } - - // Make functions say that they are functions - if (isFunction(value)) { - var n = value.name ? ': ' + value.name : ''; - base = ' [Function' + n + ']'; - } - - // Make RegExps say that they are RegExps - if (isRegExp(value)) { - base = ' ' + RegExp.prototype.toString.call(value); - } - - // Make dates with properties first say the date - if (isDate(value)) { - base = ' ' + Date.prototype.toUTCString.call(value); - } - - // Make error with message first say the error - if (isError(value)) { - base = ' ' + formatError(value); - } - - if (keys.length === 0 && (!array || value.length == 0)) { - return braces[0] + base + braces[1]; - } - - if (recurseTimes < 0) { - if (isRegExp(value)) { - return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); - } else { - return ctx.stylize('[Object]', 'special'); - } - } - - ctx.seen.push(value); - - var output; - if (array) { - output = formatArray(ctx, value, recurseTimes, visibleKeys, keys); - } else { - output = keys.map(function(key) { - return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array); - }); - } - - ctx.seen.pop(); - - return reduceToSingleString(output, base, braces); - } - - - function formatPrimitive(ctx, value) { - if (isUndefined(value)) - return ctx.stylize('undefined', 'undefined'); - if (isString(value)) { - var simple = '\'' + JSON.stringify(value).replace(/^"|"$/g, '') - .replace(/'/g, "\\'") - .replace(/\\"/g, '"') + '\''; - return ctx.stylize(simple, 'string'); - } - if (isNumber(value)) - return ctx.stylize('' + value, 'number'); - if (isBoolean(value)) - return ctx.stylize('' + value, 'boolean'); - // For some reason typeof null is "object", so special case here. - if (isNull(value)) - return ctx.stylize('null', 'null'); - } - - - function formatError(value) { - return '[' + Error.prototype.toString.call(value) + ']'; - } - - - function formatArray(ctx, value, recurseTimes, visibleKeys, keys) { - var output = []; - for (var i = 0, l = value.length; i < l; ++i) { - if (hasOwnProperty(value, String(i))) { - output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, - String(i), true)); - } else { - output.push(''); - } - } - keys.forEach(function(key) { - if (!key.match(/^\d+$/)) { - output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, - key, true)); - } - }); - return output; - } - - - function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) { - var name, str, desc; - desc = Object.getOwnPropertyDescriptor(value, key) || { value: value[key] }; - if (desc.get) { - if (desc.set) { - str = ctx.stylize('[Getter/Setter]', 'special'); - } else { - str = ctx.stylize('[Getter]', 'special'); - } - } else { - if (desc.set) { - str = ctx.stylize('[Setter]', 'special'); - } - } - if (!hasOwnProperty(visibleKeys, key)) { - name = '[' + key + ']'; - } - if (!str) { - if (ctx.seen.indexOf(desc.value) < 0) { - if (isNull(recurseTimes)) { - str = formatValue(ctx, desc.value, null); - } else { - str = formatValue(ctx, desc.value, recurseTimes - 1); - } - if (str.indexOf('\n') > -1) { - if (array) { - str = str.split('\n').map(function(line) { - return ' ' + line; - }).join('\n').substr(2); - } else { - str = '\n' + str.split('\n').map(function(line) { - return ' ' + line; - }).join('\n'); - } - } - } else { - str = ctx.stylize('[Circular]', 'special'); - } - } - if (isUndefined(name)) { - if (array && key.match(/^\d+$/)) { - return str; - } - name = JSON.stringify('' + key); - if (name.match(/^"([a-zA-Z_][a-zA-Z_0-9]*)"$/)) { - name = name.substr(1, name.length - 2); - name = ctx.stylize(name, 'name'); - } else { - name = name.replace(/'/g, "\\'") - .replace(/\\"/g, '"') - .replace(/(^"|"$)/g, "'"); - name = ctx.stylize(name, 'string'); - } - } - - return name + ': ' + str; - } - - - function reduceToSingleString(output, base, braces) { - var numLinesEst = 0; - var length = output.reduce(function(prev, cur) { - numLinesEst++; - if (cur.indexOf('\n') >= 0) numLinesEst++; - return prev + cur.replace(/\u001b\[\d\d?m/g, '').length + 1; - }, 0); - - if (length > 60) { - return braces[0] + - (base === '' ? '' : base + '\n ') + - ' ' + - output.join(',\n ') + - ' ' + - braces[1]; - } - - return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1]; - } - - - // NOTE: These type checking functions intentionally don't use `instanceof` - // because it is fragile and can be easily faked with `Object.create()`. - function isArray(ar) { - return Array.isArray(ar); - } - - function isBoolean(arg) { - return typeof arg === 'boolean'; - } - - function isNull(arg) { - return arg === null; - } - - function isNullOrUndefined(arg) { - return arg == null; - } - - function isNumber(arg) { - return typeof arg === 'number'; - } - - function isString(arg) { - return typeof arg === 'string'; - } - - function isSymbol(arg) { - return typeof arg === 'symbol'; - } - - function isUndefined(arg) { - return arg === void 0; - } - - function isRegExp(re) { - return isObject(re) && objectToString(re) === '[object RegExp]'; - } - - function isObject(arg) { - return typeof arg === 'object' && arg !== null; - } - - function isDate(d) { - return isObject(d) && objectToString(d) === '[object Date]'; - } - - function isError(e) { - return isObject(e) && - (objectToString(e) === '[object Error]' || e instanceof Error); - } - - function isFunction(arg) { - return typeof arg === 'function'; - } - - function isPrimitive(arg) { - return arg === null || - typeof arg === 'boolean' || - typeof arg === 'number' || - typeof arg === 'string' || - typeof arg === 'symbol' || // ES6 symbol - typeof arg === 'undefined'; - } - - function objectToString(o) { - return Object.prototype.toString.call(o); - } - - function hasOwnProperty(obj, prop) { - return Object.prototype.hasOwnProperty.call(obj, prop); - } - - return inspect; -})(); - - -const OBJECT_COLUMN_NAME = '(index)'; -const LOG_LEVELS = { - trace: 0, - info: 1, - warn: 2, - error: 3 -}; -const INSPECTOR_LEVELS = []; -INSPECTOR_LEVELS[LOG_LEVELS.trace] = 'debug'; -INSPECTOR_LEVELS[LOG_LEVELS.info] = 'log'; -INSPECTOR_LEVELS[LOG_LEVELS.warn] = 'warning'; -INSPECTOR_LEVELS[LOG_LEVELS.error] = 'error'; - -// Strip the inner function in getNativeLogFunction(), if in dev also -// strip method printing to originalConsole. -const INSPECTOR_FRAMES_TO_SKIP = __DEV__ ? 2 : 1; - -function setupConsole(global) { - if (!global.nativeLoggingHook) { - return; - } - - function getNativeLogFunction(level) { - return function() { - let str; - if (arguments.length === 1 && typeof arguments[0] === 'string') { - str = arguments[0]; - } else { - str = Array.prototype.map.call(arguments, function(arg) { - return inspect(arg, {depth: 10}); - }).join(', '); - } - - let logLevel = level; - if (str.slice(0, 9) === 'Warning: ' && logLevel >= LOG_LEVELS.error) { - // React warnings use console.error so that a stack trace is shown, - // but we don't (currently) want these to show a redbox - // (Note: Logic duplicated in ExceptionsManager.js.) - logLevel = LOG_LEVELS.warn; - } - if (global.__inspectorLog) { - global.__inspectorLog( - INSPECTOR_LEVELS[logLevel], - str, - [].slice.call(arguments), - INSPECTOR_FRAMES_TO_SKIP); - } - global.nativeLoggingHook(str, logLevel); - }; - } - - function repeat(element, n) { - return Array.apply(null, Array(n)).map(function() { return element; }); - }; - - function consoleTablePolyfill(rows) { - // convert object -> array - if (!Array.isArray(rows)) { - var data = rows; - rows = []; - for (var key in data) { - if (data.hasOwnProperty(key)) { - var row = data[key]; - row[OBJECT_COLUMN_NAME] = key; - rows.push(row); - } - } - } - if (rows.length === 0) { - global.nativeLoggingHook('', LOG_LEVELS.info); - return; - } - - var columns = Object.keys(rows[0]).sort(); - var stringRows = []; - var columnWidths = []; - - // Convert each cell to a string. Also - // figure out max cell width for each column - columns.forEach(function(k, i) { - columnWidths[i] = k.length; - for (var j = 0; j < rows.length; j++) { - var cellStr = (rows[j][k] || '?').toString(); - stringRows[j] = stringRows[j] || []; - stringRows[j][i] = cellStr; - columnWidths[i] = Math.max(columnWidths[i], cellStr.length); - } - }); - - // Join all elements in the row into a single string with | separators - // (appends extra spaces to each cell to make separators | alligned) - function joinRow(row, space) { - var cells = row.map(function(cell, i) { - var extraSpaces = repeat(' ', columnWidths[i] - cell.length).join(''); - return cell + extraSpaces; - }); - space = space || ' '; - return cells.join(space + '|' + space); - }; - - var separators = columnWidths.map(function(columnWidth) { - return repeat('-', columnWidth).join(''); - }); - var separatorRow = joinRow(separators, '-'); - var header = joinRow(columns); - var table = [header, separatorRow]; - - for (var i = 0; i < rows.length; i++) { - table.push(joinRow(stringRows[i])); - } - - // Notice extra empty line at the beginning. - // Native logging hook adds "RCTLog >" at the front of every - // logged string, which would shift the header and screw up - // the table - global.nativeLoggingHook('\n' + table.join('\n'), LOG_LEVELS.info); - } - - // Preserve the original `console` as `originalConsole` - var originalConsole = global.console; - var descriptor = Object.getOwnPropertyDescriptor(global, 'console'); - if (descriptor) { - Object.defineProperty(global, 'originalConsole', descriptor); - } - - global.console = { - error: getNativeLogFunction(LOG_LEVELS.error), - info: getNativeLogFunction(LOG_LEVELS.info), - log: getNativeLogFunction(LOG_LEVELS.info), - warn: getNativeLogFunction(LOG_LEVELS.warn), - trace: getNativeLogFunction(LOG_LEVELS.trace), - debug: getNativeLogFunction(LOG_LEVELS.trace), - table: consoleTablePolyfill - }; - - // If available, also call the original `console` method since that is - // sometimes useful. Ex: on OS X, this will let you see rich output in - // the Safari Web Inspector console. - if (__DEV__ && originalConsole) { - Object.keys(console).forEach(methodName => { - var reactNativeMethod = console[methodName]; - if (originalConsole[methodName]) { - console[methodName] = function() { - originalConsole[methodName](...arguments); - reactNativeMethod.apply(console, arguments); - }; - } - }); - } -} - -if (typeof module !== 'undefined') { - module.exports = setupConsole; -} else { - setupConsole(global); -} diff --git a/packager/src/Resolver/polyfills/error-guard.js b/packager/src/Resolver/polyfills/error-guard.js deleted file mode 100644 index b9330506982929..00000000000000 --- a/packager/src/Resolver/polyfills/error-guard.js +++ /dev/null @@ -1,90 +0,0 @@ -/** - * 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. - * - * @polyfill - */ - -/* eslint-disable strict */ - -let _inGuard = 0; - -/** - * This is the error handler that is called when we encounter an exception - * when loading a module. This will report any errors encountered before - * ExceptionsManager is configured. - */ -let _globalHandler = function onError(e) { - throw e; -}; - -/** - * The particular require runtime that we are using looks for a global - * `ErrorUtils` object and if it exists, then it requires modules with the - * error handler specified via ErrorUtils.setGlobalHandler by calling the - * require function with applyWithGuard. Since the require module is loaded - * before any of the modules, this ErrorUtils must be defined (and the handler - * set) globally before requiring anything. - */ -const ErrorUtils = { - setGlobalHandler(fun) { - _globalHandler = fun; - }, - getGlobalHandler() { - return _globalHandler; - }, - reportError(error) { - _globalHandler && _globalHandler(error); - }, - reportFatalError(error) { - _globalHandler && _globalHandler(error, true); - }, - applyWithGuard(fun, context, args) { - try { - _inGuard++; - return fun.apply(context, args); - } catch (e) { - ErrorUtils.reportError(e); - } finally { - _inGuard--; - } - return null; - }, - applyWithGuardIfNeeded(fun, context, args) { - if (ErrorUtils.inGuard()) { - return fun.apply(context, args); - } else { - ErrorUtils.applyWithGuard(fun, context, args); - } - return null; - }, - inGuard() { - return _inGuard; - }, - guard(fun, name, context) { - if (typeof fun !== 'function') { - console.warn('A function must be passed to ErrorUtils.guard, got ', fun); - return null; - } - name = name || fun.name || ''; - function guarded() { - return ( - ErrorUtils.applyWithGuard( - fun, - context || this, - arguments, - null, - name - ) - ); - } - - return guarded; - }, -}; - -global.ErrorUtils = ErrorUtils; diff --git a/packager/src/Resolver/polyfills/prelude.js b/packager/src/Resolver/polyfills/prelude.js deleted file mode 100644 index 38170fa115c566..00000000000000 --- a/packager/src/Resolver/polyfills/prelude.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Copyright (c) 2013-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. - * - * @polyfill - */ - -/* eslint-disable strict */ - -global.__DEV__ = false; - -global.__BUNDLE_START_TIME__ = global.nativePerformanceNow - ? global.nativePerformanceNow() - : Date.now(); diff --git a/packager/src/Resolver/polyfills/prelude_dev.js b/packager/src/Resolver/polyfills/prelude_dev.js deleted file mode 100644 index caf05c283bd83a..00000000000000 --- a/packager/src/Resolver/polyfills/prelude_dev.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Copyright (c) 2013-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. - * - * @polyfill - */ - -/* eslint-disable strict */ - -global.__DEV__ = true; - -global.__BUNDLE_START_TIME__ = global.nativePerformanceNow - ? global.nativePerformanceNow() - : Date.now(); diff --git a/packager/src/Resolver/polyfills/require.js b/packager/src/Resolver/polyfills/require.js deleted file mode 100644 index f0e9e646336be8..00000000000000 --- a/packager/src/Resolver/polyfills/require.js +++ /dev/null @@ -1,292 +0,0 @@ -/** - * Copyright (c) 2013-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. - * - * @polyfill - * @flow - */ - -'use strict'; - -declare var __DEV__: boolean; - -type DependencyMap = Array; -type Exports = any; -type FactoryFn = ( - global: Object, - require: RequireFn, - moduleObject: {exports: {}}, - exports: {}, - dependencyMap: ?DependencyMap, -) => void; -type HotModuleReloadingAcceptFn = Function; -type HotModuleReloadingData = {| - acceptCallback: ?HotModuleReloadingAcceptFn, - accept: (callback: HotModuleReloadingAcceptFn) => void, -|}; -type Module = { - exports: Exports, - hot?: HotModuleReloadingData, -}; -type ModuleID = number; -type ModuleDefinition = {| - dependencyMap: ?DependencyMap, - exports: Exports, - factory: FactoryFn, - hasError: boolean, - error?: any, - hot?: HotModuleReloadingData, - isInitialized: boolean, - verboseName?: string, -|}; -type ModuleMap = - {[key: ModuleID]: (ModuleDefinition)}; -type RequireFn = (id: ModuleID | VerboseModuleNameForDev) => Exports; -type VerboseModuleNameForDev = string; - -global.require = require; -global.__d = define; - -const modules: ModuleMap = Object.create(null); -if (__DEV__) { - var verboseNamesToModuleIds: {[key: string]: number} = Object.create(null); -} - -function define( - factory: FactoryFn, - moduleId: number, - dependencyMap?: DependencyMap, -) { - if (moduleId in modules) { - // prevent repeated calls to `global.nativeRequire` to overwrite modules - // that are already loaded - return; - } - modules[moduleId] = { - dependencyMap, - exports: undefined, - factory, - hasError: false, - isInitialized: false, - }; - if (__DEV__) { - // HMR - modules[moduleId].hot = createHotReloadingObject(); - - // DEBUGGABLE MODULES NAMES - // we take `verboseName` from `arguments` to avoid an unused named parameter - // in `define` in production. - const verboseName: string | void = arguments[3]; - if (verboseName) { - modules[moduleId].verboseName = verboseName; - verboseNamesToModuleIds[verboseName] = moduleId; - } - } -} - -function require(moduleId: ModuleID | VerboseModuleNameForDev) { - if (__DEV__ && typeof moduleId === 'string') { - const verboseName = moduleId; - moduleId = verboseNamesToModuleIds[moduleId]; - if (moduleId == null) { - throw new Error(`Unknown named module: '${verboseName}'`); - } else { - console.warn( - `Requiring module '${verboseName}' by name is only supported for ` + - 'debugging purposes and will BREAK IN PRODUCTION!' - ); - } - } - - //$FlowFixMe: at this point we know that moduleId is a number - const moduleIdReallyIsNumber: number = moduleId; - const module = modules[moduleIdReallyIsNumber]; - return module && module.isInitialized - ? module.exports - : guardedLoadModule(moduleIdReallyIsNumber, module); -} - -let inGuard = false; -function guardedLoadModule(moduleId: ModuleID, module) { - if (!inGuard && global.ErrorUtils) { - inGuard = true; - let returnValue; - try { - returnValue = loadModuleImplementation(moduleId, module); - } catch (e) { - global.ErrorUtils.reportFatalError(e); - } - inGuard = false; - return returnValue; - } else { - return loadModuleImplementation(moduleId, module); - } -} - -function loadModuleImplementation(moduleId, module) { - const nativeRequire = global.nativeRequire; - if (!module && nativeRequire) { - nativeRequire(moduleId); - module = modules[moduleId]; - } - - if (!module) { - throw unknownModuleError(moduleId); - } - - if (module.hasError) { - throw moduleThrewError(moduleId, module.error); - } - - // `require` calls int the require polyfill itself are not analyzed and - // replaced so that they use numeric module IDs. - // The systrace module will expose itself on the require function so that - // it can be used here. - // TODO(davidaurelio) Scan polyfills for dependencies, too (t9759686) - if (__DEV__) { - var {Systrace} = require; - } - - // We must optimistically mark module as initialized before running the - // factory to keep any require cycles inside the factory from causing an - // infinite require loop. - module.isInitialized = true; - const exports = module.exports = {}; - const {factory, dependencyMap} = module; - try { - if (__DEV__) { - // $FlowFixMe: we know that __DEV__ is const and `Systrace` exists - Systrace.beginEvent('JS_require_' + (module.verboseName || moduleId)); - } - - const moduleObject: Module = {exports}; - if (__DEV__ && module.hot) { - moduleObject.hot = module.hot; - } - - // 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); - - // avoid removing factory in DEV mode as it breaks HMR - if (!__DEV__) { - // $FlowFixMe: This is only sound because we never access `factory` again - module.factory = undefined; - module.dependencyMap = undefined; - } - - if (__DEV__) { - // $FlowFixMe: we know that __DEV__ is const and `Systrace` exists - Systrace.endEvent(); - } - return (module.exports = moduleObject.exports); - } catch (e) { - module.hasError = true; - module.error = e; - module.isInitialized = false; - module.exports = undefined; - throw e; - } -} - -function unknownModuleError(id) { - let message = 'Requiring unknown module "' + id + '".'; - if (__DEV__) { - message += - 'If you are sure the module is there, try restarting the packager. ' + - 'You may also want to run `npm install`, or `yarn` (depending on your environment).'; - } - return Error(message); -} - -function moduleThrewError(id, error: any) { - const displayName = __DEV__ && modules[id] && modules[id].verboseName || id; - return Error('Requiring module "' + displayName + '", which threw an exception: ' + error); -} - -if (__DEV__) { - require.Systrace = {beginEvent: () => {}, endEvent: () => {}}; - - // HOT MODULE RELOADING - var createHotReloadingObject = function() { - const hot: HotModuleReloadingData = { - acceptCallback: null, - accept: callback => { hot.acceptCallback = callback; }, - }; - return hot; - }; - - const acceptAll = function( - dependentModules, - inverseDependencies, - ) { - if (!dependentModules || dependentModules.length === 0) { - return true; - } - - const notAccepted = dependentModules.filter( - module => !accept(module, /*factory*/ undefined, inverseDependencies)); - - const parents = []; - for (let i = 0; i < notAccepted.length; i++) { - // if the module has no parents then the change cannot be hot loaded - if (inverseDependencies[notAccepted[i]].length === 0) { - return false; - } - - parents.push(...inverseDependencies[notAccepted[i]]); - } - - return acceptAll(parents, inverseDependencies); - }; - - const accept = function( - id: ModuleID, - factory?: FactoryFn, - inverseDependencies: {[key: ModuleID]: Array}, - ) { - const mod = modules[id]; - - if (!mod && factory) { // new modules need a factory - define(factory, id); - return true; // new modules don't need to be accepted - } - - const {hot} = mod; - if (!hot) { - console.warn( - 'Cannot accept module because Hot Module Replacement ' + - 'API was not installed.' - ); - return false; - } - - // replace and initialize factory - if (factory) { - mod.factory = factory; - } - mod.hasError = false; - mod.isInitialized = false; - require(id); - - if (hot.acceptCallback) { - hot.acceptCallback(); - return true; - } else { - // need to have inverseDependencies to bubble up accept - if (!inverseDependencies) { - throw new Error('Undefined `inverseDependencies`'); - } - - // accept parent modules recursively up until all siblings are accepted - return acceptAll(inverseDependencies[id], inverseDependencies); - } - }; - - global.__accept = accept; -} diff --git a/packager/src/Server/MultipartResponse.js b/packager/src/Server/MultipartResponse.js deleted file mode 100644 index cbc88ed18b9149..00000000000000 --- a/packager/src/Server/MultipartResponse.js +++ /dev/null @@ -1,84 +0,0 @@ -/** - * 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 CRLF = '\r\n'; -const BOUNDARY = '3beqjf3apnqeu3h5jqorms4i'; - -class MultipartResponse { - static wrap(req, res) { - if (acceptsMultipartResponse(req)) { - return new MultipartResponse(res); - } - // Ugly hack, ideally wrap function should always return a proxy - // object with the same interface - res.writeChunk = () => {}; // noop - return res; - } - - constructor(res) { - this.res = res; - this.headers = {}; - - res.writeHead(200, { - 'Content-Type': `multipart/mixed; boundary="${BOUNDARY}"`, - }); - res.write('If you are seeing this, your client does not support multipart response'); - } - - writeChunk(headers, data, isLast = false) { - let chunk = `${CRLF}--${BOUNDARY}${CRLF}`; - if (headers) { - chunk += MultipartResponse.serializeHeaders(headers) + CRLF + CRLF; - } - - if (data) { - chunk += data; - } - - if (isLast) { - chunk += `${CRLF}--${BOUNDARY}--${CRLF}`; - } - - this.res.write(chunk); - } - - writeHead(status, headers) { - // We can't actually change the response HTTP status code - // because the headers have already been sent - this.setHeader('X-Http-Status', status); - if (!headers) { - return; - } - for (const key in headers) { - this.setHeader(key, headers[key]); - } - } - - setHeader(name, value) { - this.headers[name] = value; - } - - end(data) { - this.writeChunk(this.headers, data, true); - this.res.end(); - } - - static serializeHeaders(headers) { - return Object.keys(headers) - .map(key => `${key}: ${headers[key]}`) - .join(CRLF); - } -} - -function acceptsMultipartResponse(req) { - return req.headers && req.headers.accept === 'multipart/mixed'; -} - -module.exports = MultipartResponse; diff --git a/packager/src/Server/__tests__/MultipartResponse-test.js b/packager/src/Server/__tests__/MultipartResponse-test.js deleted file mode 100644 index 3920b5089c23f6..00000000000000 --- a/packager/src/Server/__tests__/MultipartResponse-test.js +++ /dev/null @@ -1,149 +0,0 @@ -/** - * 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'; - -jest.dontMock('../MultipartResponse'); - -const MultipartResponse = require('../MultipartResponse'); - -describe('MultipartResponse', () => { - it('forwards calls to response', () => { - const nreq = mockNodeRequest({accept: 'text/html'}); - const nres = mockNodeResponse(); - const res = MultipartResponse.wrap(nreq, nres); - - expect(res).toBe(nres); - - res.writeChunk({}, 'foo'); - expect(nres.write).not.toBeCalled(); - }); - - it('writes multipart response', () => { - const nreq = mockNodeRequest({accept: 'multipart/mixed'}); - const nres = mockNodeResponse(); - const res = MultipartResponse.wrap(nreq, nres); - - expect(res).not.toBe(nres); - - res.setHeader('Result-Header-1', 1); - res.writeChunk({foo: 'bar'}, 'first chunk'); - res.writeChunk({test: 2}, 'second chunk'); - res.writeChunk(null, 'empty headers third chunk'); - res.setHeader('Result-Header-2', 2); - res.end('Hello, world!'); - - expect(nres.toString()).toEqual([ - 'HTTP/1.1 200', - 'Content-Type: multipart/mixed; boundary="3beqjf3apnqeu3h5jqorms4i"', - '', - 'If you are seeing this, your client does not support multipart response', - '--3beqjf3apnqeu3h5jqorms4i', - 'foo: bar', - '', - 'first chunk', - '--3beqjf3apnqeu3h5jqorms4i', - 'test: 2', - '', - 'second chunk', - '--3beqjf3apnqeu3h5jqorms4i', - 'empty headers third chunk', - '--3beqjf3apnqeu3h5jqorms4i', - 'Result-Header-1: 1', - 'Result-Header-2: 2', - '', - 'Hello, world!', - '--3beqjf3apnqeu3h5jqorms4i--', - '', - ].join('\r\n')); - }); - - it('sends status code as last chunk header', () => { - const nreq = mockNodeRequest({accept: 'multipart/mixed'}); - const nres = mockNodeResponse(); - const res = MultipartResponse.wrap(nreq, nres); - - res.writeChunk({foo: 'bar'}, 'first chunk'); - res.writeHead(500, { - 'Content-Type': 'application/json; boundary="3beqjf3apnqeu3h5jqorms4i"', - }); - res.end('{}'); - - expect(nres.toString()).toEqual([ - 'HTTP/1.1 200', - 'Content-Type: multipart/mixed; boundary="3beqjf3apnqeu3h5jqorms4i"', - '', - 'If you are seeing this, your client does not support multipart response', - '--3beqjf3apnqeu3h5jqorms4i', - 'foo: bar', - '', - 'first chunk', - '--3beqjf3apnqeu3h5jqorms4i', - 'X-Http-Status: 500', - 'Content-Type: application/json; boundary="3beqjf3apnqeu3h5jqorms4i"', - '', - '{}', - '--3beqjf3apnqeu3h5jqorms4i--', - '', - ].join('\r\n')); - }); - - it('supports empty responses', () => { - const nreq = mockNodeRequest({accept: 'multipart/mixed'}); - const nres = mockNodeResponse(); - const res = MultipartResponse.wrap(nreq, nres); - - res.writeHead(304, { - 'Content-Type': 'application/json; boundary="3beqjf3apnqeu3h5jqorms4i"', - }); - res.end(); - - expect(nres.toString()).toEqual([ - 'HTTP/1.1 200', - 'Content-Type: multipart/mixed; boundary="3beqjf3apnqeu3h5jqorms4i"', - '', - 'If you are seeing this, your client does not support multipart response', - '--3beqjf3apnqeu3h5jqorms4i', - 'X-Http-Status: 304', - 'Content-Type: application/json; boundary="3beqjf3apnqeu3h5jqorms4i"', - '', - '', - '--3beqjf3apnqeu3h5jqorms4i--', - '', - ].join('\r\n')); - }); -}); - -function mockNodeRequest(headers = {}) { - return {headers}; -} - -function mockNodeResponse() { - let status = 200; - let headers = {}; - let body = ''; - return { - writeHead: jest.fn((st, hdrs) => { - status = st; - headers = {...headers, ...hdrs}; - }), - setHeader: jest.fn((key, val) => { headers[key] = val; }), - write: jest.fn(data => { body += data; }), - end: jest.fn(data => { body += (data || ''); }), - - // For testing only - toString() { - return [ - `HTTP/1.1 ${status}`, - MultipartResponse.serializeHeaders(headers), - '', - body, - ].join('\r\n'); - }, - }; -} diff --git a/packager/src/Server/__tests__/Server-test.js b/packager/src/Server/__tests__/Server-test.js deleted file mode 100644 index 01e8ec46e64a73..00000000000000 --- a/packager/src/Server/__tests__/Server-test.js +++ /dev/null @@ -1,542 +0,0 @@ -/** - * 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'; - -jest.disableAutomock(); - -jest.mock('../../worker-farm', () => () => () => {}) - .mock('worker-farm', () => () => () => {}) - .mock('timers', () => ({setImmediate: fn => setTimeout(fn, 0)})) - .mock('uglify-js') - .mock('crypto') - .mock( - '../symbolicate', - () => ({createWorker: jest.fn().mockReturnValue(jest.fn())}), - ) - .mock('../../Bundler') - .mock('../../AssetServer') - .mock('../../node-haste/DependencyGraph') - .mock('../../Logger') - .mock('../../lib/GlobalTransformCache'); - -describe('processRequest', () => { - let Bundler, Server, AssetServer, Promise, symbolicate; - beforeEach(() => { - jest.resetModules(); - Bundler = require('../../Bundler'); - Server = require('../'); - AssetServer = require('../../AssetServer'); - Promise = require('promise'); - symbolicate = require('../symbolicate'); - }); - - let server; - - const options = { - projectRoots: ['root'], - blacklistRE: null, - cacheVersion: null, - polyfillModuleNames: null, - reporter: require('../../lib/reporting').nullReporter, - }; - - const makeRequest = (reqHandler, requrl, reqOptions) => new Promise(resolve => - reqHandler( - {url: requrl, headers:{}, ...reqOptions}, - { - statusCode: 200, - headers: {}, - getHeader(header) { return this.headers[header]; }, - setHeader(header, value) { this.headers[header] = value; }, - writeHead(statusCode) { this.statusCode = statusCode; }, - end(body) { - this.body = body; - resolve(this); - }, - }, - {next: () => {}}, - ) - ); - - const invalidatorFunc = jest.fn(); - let requestHandler; - - beforeEach(() => { - Bundler.prototype.bundle = jest.fn(() => - Promise.resolve({ - getModules: () => [], - getSource: () => 'this is the source', - getSourceMap: () => ({version: 3}), - getSourceMapString: () => 'this is the source map', - getEtag: () => 'this is an etag', - })); - - Bundler.prototype.invalidateFile = invalidatorFunc; - Bundler.prototype.getResolver = - jest.fn().mockReturnValue(Promise.resolve({ - getDependencyGraph: jest.fn().mockReturnValue({ - getHasteMap: jest.fn().mockReturnValue({on: jest.fn()}), - load: jest.fn(() => Promise.resolve()), - }), - })); - - server = new Server(options); - requestHandler = server.processRequest.bind(server); - }); - - it('returns JS bundle source on request of *.bundle', () => { - return makeRequest( - requestHandler, - 'mybundle.bundle?runModule=true', - null - ).then(response => - expect(response.body).toEqual('this is the source') - ); - }); - - it('returns JS bundle source on request of *.bundle (compat)', () => { - return makeRequest( - requestHandler, - 'mybundle.runModule.bundle' - ).then(response => - expect(response.body).toEqual('this is the source') - ); - }); - - it('returns ETag header on request of *.bundle', () => { - return makeRequest( - requestHandler, - 'mybundle.bundle?runModule=true' - ).then(response => { - expect(response.getHeader('ETag')).toBeDefined(); - }); - }); - - it('returns 304 on request of *.bundle when if-none-match equals the ETag', () => { - return makeRequest( - requestHandler, - 'mybundle.bundle?runModule=true', - {headers : {'if-none-match' : 'this is an etag'}} - ).then(response => { - expect(response.statusCode).toEqual(304); - }); - }); - - it('returns sourcemap on request of *.map', () => { - return makeRequest( - requestHandler, - 'mybundle.map?runModule=true' - ).then(response => - expect(response.body).toEqual('this is the source map') - ); - }); - - it('works with .ios.js extension', () => { - return makeRequest( - requestHandler, - 'index.ios.includeRequire.bundle' - ).then(response => { - expect(response.body).toEqual('this is the source'); - expect(Bundler.prototype.bundle).toBeCalledWith({ - assetPlugins: [], - dev: true, - entryFile: 'index.ios.js', - entryModuleOnly: false, - generateSourceMaps: false, - hot: false, - inlineSourceMap: false, - isolateModuleIDs: false, - minify: false, - onProgress: jasmine.any(Function), - platform: null, - resolutionResponse: null, - runBeforeMainModule: ['InitializeCore'], - runModule: true, - sourceMapUrl: 'index.ios.includeRequire.map', - unbundle: false, - }); - }); - }); - - it('passes in the platform param', function() { - return makeRequest( - requestHandler, - 'index.bundle?platform=ios' - ).then(function(response) { - expect(response.body).toEqual('this is the source'); - expect(Bundler.prototype.bundle).toBeCalledWith({ - assetPlugins: [], - dev: true, - entryFile: 'index.js', - entryModuleOnly: false, - generateSourceMaps: false, - hot: false, - inlineSourceMap: false, - isolateModuleIDs: false, - minify: false, - onProgress: jasmine.any(Function), - platform: 'ios', - resolutionResponse: null, - runBeforeMainModule: ['InitializeCore'], - runModule: true, - sourceMapUrl: 'index.map?platform=ios', - unbundle: false, - }); - }); - }); - - it('passes in the assetPlugin param', function() { - return makeRequest( - requestHandler, - 'index.bundle?assetPlugin=assetPlugin1&assetPlugin=assetPlugin2' - ).then(function(response) { - expect(response.body).toEqual('this is the source'); - expect(Bundler.prototype.bundle).toBeCalledWith({ - assetPlugins: ['assetPlugin1', 'assetPlugin2'], - dev: true, - entryFile: 'index.js', - entryModuleOnly: false, - generateSourceMaps: false, - hot: false, - inlineSourceMap: false, - isolateModuleIDs: false, - minify: false, - onProgress: jasmine.any(Function), - platform: null, - resolutionResponse: null, - runBeforeMainModule: ['InitializeCore'], - runModule: true, - sourceMapUrl: 'index.map?assetPlugin=assetPlugin1&assetPlugin=assetPlugin2', - unbundle: false, - }); - }); - }); - - describe('file changes', () => { - - it('does not rebuild the bundles that contain a file when that file is changed', () => { - const bundleFunc = jest.fn(); - bundleFunc - .mockReturnValueOnce( - Promise.resolve({ - getModules: () => [], - getSource: () => 'this is the first source', - getSourceMap: () => {}, - getSourceMapString: () => 'this is the source map', - getEtag: () => () => 'this is an etag', - }) - ) - .mockReturnValue( - Promise.resolve({ - getModules: () => [], - getSource: () => 'this is the rebuilt source', - getSourceMap: () => {}, - getSourceMapString: () => 'this is the source map', - getEtag: () => () => 'this is an etag', - }) - ); - - Bundler.prototype.bundle = bundleFunc; - - server = new Server(options); - - requestHandler = server.processRequest.bind(server); - - makeRequest(requestHandler, 'mybundle.bundle?runModule=true') - .done(response => { - expect(response.body).toEqual('this is the first source'); - expect(bundleFunc.mock.calls.length).toBe(1); - }); - - jest.runAllTicks(); - - server.onFileChange('all', options.projectRoots[0] + 'path/file.js'); - jest.runAllTimers(); - jest.runAllTicks(); - - expect(bundleFunc.mock.calls.length).toBe(1); - - makeRequest(requestHandler, 'mybundle.bundle?runModule=true') - .done(response => - expect(response.body).toEqual('this is the rebuilt source') - ); - jest.runAllTicks(); - }); - - it( - 'does not rebuild the bundles that contain a file ' + - 'when that file is changed, even when hot loading is enabled', - () => { - const bundleFunc = jest.fn(); - bundleFunc - .mockReturnValueOnce( - Promise.resolve({ - getModules: () => [], - getSource: () => 'this is the first source', - getSourceMap: () => {}, - getSourceMapString: () => 'this is the source map', - getEtag: () => () => 'this is an etag', - }) - ) - .mockReturnValue( - Promise.resolve({ - getModules: () => [], - getSource: () => 'this is the rebuilt source', - getSourceMap: () => {}, - getSourceMapString: () => 'this is the source map', - getEtag: () => () => 'this is an etag', - }) - ); - - Bundler.prototype.bundle = bundleFunc; - - server = new Server(options); - server.setHMRFileChangeListener(() => {}); - - requestHandler = server.processRequest.bind(server); - - makeRequest(requestHandler, 'mybundle.bundle?runModule=true') - .done(response => { - expect(response.body).toEqual('this is the first source'); - expect(bundleFunc.mock.calls.length).toBe(1); - }); - - jest.runAllTicks(); - - server.onFileChange('all', options.projectRoots[0] + 'path/file.js'); - jest.runAllTimers(); - jest.runAllTicks(); - - expect(bundleFunc.mock.calls.length).toBe(1); - server.setHMRFileChangeListener(null); - - makeRequest(requestHandler, 'mybundle.bundle?runModule=true') - .done(response => { - expect(response.body).toEqual('this is the rebuilt source'); - expect(bundleFunc.mock.calls.length).toBe(2); - }); - jest.runAllTicks(); - }); - }); - - describe('/onchange endpoint', () => { - let EventEmitter; - let req; - let res; - - beforeEach(() => { - EventEmitter = require.requireActual('events').EventEmitter; - req = scaffoldReq(new EventEmitter()); - req.url = '/onchange'; - res = { - writeHead: jest.fn(), - end: jest.fn(), - }; - }); - - it('should hold on to request and inform on change', () => { - server.processRequest(req, res); - server.onFileChange('all', options.projectRoots[0] + 'path/file.js'); - jest.runAllTimers(); - expect(res.end).toBeCalledWith(JSON.stringify({changed: true})); - }); - - it('should not inform changes on disconnected clients', () => { - server.processRequest(req, res); - req.emit('close'); - jest.runAllTimers(); - server.onFileChange('all', options.projectRoots[0] + 'path/file.js'); - jest.runAllTimers(); - expect(res.end).not.toBeCalled(); - }); - }); - - describe('/assets endpoint', () => { - it('should serve simple case', () => { - const req = scaffoldReq({url: '/assets/imgs/a.png'}); - const res = {end: jest.fn(), setHeader: jest.fn()}; - - AssetServer.prototype.get.mockImplementation(() => Promise.resolve('i am image')); - - server.processRequest(req, res); - jest.runAllTimers(); - expect(res.end).toBeCalledWith('i am image'); - }); - - it('should parse the platform option', () => { - const req = scaffoldReq({url: '/assets/imgs/a.png?platform=ios'}); - const res = {end: jest.fn(), setHeader: jest.fn()}; - - AssetServer.prototype.get.mockImplementation(() => Promise.resolve('i am image')); - - server.processRequest(req, res); - jest.runAllTimers(); - expect(AssetServer.prototype.get).toBeCalledWith('imgs/a.png', 'ios'); - expect(res.end).toBeCalledWith('i am image'); - }); - - it('should serve range request', () => { - const req = scaffoldReq({ - url: '/assets/imgs/a.png?platform=ios', - headers: {range: 'bytes=0-3'}, - }); - const res = {end: jest.fn(), writeHead: jest.fn(), setHeader: jest.fn()}; - const mockData = 'i am image'; - - AssetServer.prototype.get.mockImplementation(() => Promise.resolve(mockData)); - - server.processRequest(req, res); - jest.runAllTimers(); - expect(AssetServer.prototype.get).toBeCalledWith('imgs/a.png', 'ios'); - expect(res.end).toBeCalledWith(mockData.slice(0, 4)); - }); - - it('should serve assets files\'s name contain non-latin letter', () => { - const req = scaffoldReq({url: '/assets/imgs/%E4%B8%BB%E9%A1%B5/logo.png'}); - const res = {end: jest.fn(), setHeader: jest.fn()}; - - AssetServer.prototype.get.mockImplementation(() => Promise.resolve('i am image')); - - server.processRequest(req, res); - jest.runAllTimers(); - expect(AssetServer.prototype.get).toBeCalledWith( - 'imgs/\u{4E3B}\u{9875}/logo.png', - undefined - ); - expect(res.end).toBeCalledWith('i am image'); - }); - }); - - describe('buildbundle(options)', () => { - it('Calls the bundler with the correct args', () => { - return server.buildBundle({ - ...Server.DEFAULT_BUNDLE_OPTIONS, - entryFile: 'foo file', - }).then(() => - expect(Bundler.prototype.bundle).toBeCalledWith({ - assetPlugins: [], - dev: true, - entryFile: 'foo file', - entryModuleOnly: false, - generateSourceMaps: false, - hot: false, - inlineSourceMap: false, - isolateModuleIDs: false, - minify: false, - onProgress: null, - platform: undefined, - resolutionResponse: null, - runBeforeMainModule: ['InitializeCore'], - runModule: true, - sourceMapUrl: null, - unbundle: false, - }) - ); - }); - }); - - describe('buildBundleFromUrl(options)', () => { - it('Calls the bundler with the correct args', () => { - return server.buildBundleFromUrl('/path/to/foo.bundle?dev=false&runModule=false') - .then(() => - expect(Bundler.prototype.bundle).toBeCalledWith({ - assetPlugins: [], - dev: false, - entryFile: 'path/to/foo.js', - entryModuleOnly: false, - generateSourceMaps: true, - hot: false, - inlineSourceMap: false, - isolateModuleIDs: false, - minify: false, - onProgress: null, - platform: null, - resolutionResponse: null, - runBeforeMainModule: ['InitializeCore'], - runModule: false, - sourceMapUrl: '/path/to/foo.map?dev=false&runModule=false', - unbundle: false, - }) - ); - }); - }); - - describe('/symbolicate endpoint', () => { - let symbolicationWorker; - beforeEach(() => { - symbolicationWorker = symbolicate.createWorker(); - symbolicationWorker.mockReset(); - }); - - it('should symbolicate given stack trace', () => { - const inputStack = [{ - file: 'http://foo.bundle?platform=ios', - lineNumber: 2100, - column: 44, - customPropShouldBeLeftUnchanged: 'foo', - }]; - const outputStack = [{ - source: 'foo.js', - line: 21, - column: 4, - }]; - const body = JSON.stringify({stack: inputStack}); - - expect.assertions(2); - symbolicationWorker.mockImplementation(stack => { - expect(stack).toEqual(inputStack); - return outputStack; - }); - - return makeRequest( - requestHandler, - '/symbolicate', - {rawBody: body}, - ).then(response => - expect(JSON.parse(response.body)).toEqual({stack: outputStack})); - }); - }); - - describe('/symbolicate handles errors', () => { - it('should symbolicate given stack trace', () => { - const body = 'clearly-not-json'; - console.error = jest.fn(); - - return makeRequest( - requestHandler, - '/symbolicate', - {rawBody: body} - ).then(response => { - expect(response.statusCode).toEqual(500); - expect(JSON.parse(response.body)).toEqual({ - error: jasmine.any(String), - }); - expect(console.error).toBeCalled(); - }); - }); - }); - - describe('_getOptionsFromUrl', () => { - it('ignores protocol, host and port of the passed in URL', () => { - const short = '/path/to/entry-file.js??platform=ios&dev=true&minify=false'; - const long = `http://localhost:8081${short}`; - expect(server._getOptionsFromUrl(long)) - .toEqual(server._getOptionsFromUrl(short)); - }); - }); - - // ensures that vital properties exist on fake request objects - function scaffoldReq(req) { - if (!req.headers) { - req.headers = {}; - } - return req; - } -}); diff --git a/packager/src/Server/index.js b/packager/src/Server/index.js deleted file mode 100644 index d06153a5260c39..00000000000000 --- a/packager/src/Server/index.js +++ /dev/null @@ -1,957 +0,0 @@ -/** - * 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. - * - * @flow - */ - -'use strict'; - -const AssetServer = require('../AssetServer'); -const Bundler = require('../Bundler'); -const MultipartResponse = require('./MultipartResponse'); - -const defaults = require('../defaults'); -const emptyFunction = require('fbjs/lib/emptyFunction'); -const mime = require('mime-types'); -const parsePlatformFilePath = require('../node-haste/lib/parsePlatformFilePath'); -const path = require('path'); -const symbolicate = require('./symbolicate'); -const url = require('url'); - -const debug = require('debug')('RNP:Server'); - -import type Module, {HasteImpl} from '../node-haste/Module'; -import type {IncomingMessage, ServerResponse} from 'http'; -import type ResolutionResponse from '../node-haste/DependencyGraph/ResolutionResponse'; -import type Bundle from '../Bundler/Bundle'; -import type HMRBundle from '../Bundler/HMRBundle'; -import type {Reporter} from '../lib/reporting'; -import type {GetTransformOptions, PostProcessModules, PostMinifyProcess} from '../Bundler'; -import type {TransformCache} from '../lib/TransformCaching'; -import type {GlobalTransformCache} from '../lib/GlobalTransformCache'; -import type {SourceMap, Symbolicate} from './symbolicate'; - -const { - createActionStartEntry, - createActionEndEntry, - log, -} = require('../Logger'); - -function debounceAndBatch(fn, delay) { - let args = []; - let timeout; - return value => { - args.push(value); - clearTimeout(timeout); - timeout = setTimeout(() => { - const a = args; - args = []; - fn(a); - }, delay); - }; -} - -type Options = { - assetExts?: Array, - blacklistRE?: RegExp, - cacheVersion?: string, - extraNodeModules?: {}, - getTransformOptions?: GetTransformOptions, - globalTransformCache: ?GlobalTransformCache, - hasteImpl?: HasteImpl, - moduleFormat?: string, - platforms?: Array, - polyfillModuleNames?: Array, - postProcessModules?: PostProcessModules, - postMinifyProcess: PostMinifyProcess, - projectRoots: $ReadOnlyArray, - providesModuleNodeModules?: Array, - reporter: Reporter, - resetCache?: boolean, - silent?: boolean, - +sourceExts: ?Array, - +transformCache: TransformCache, - +transformModulePath: string, - transformTimeoutInterval?: number, - watch?: boolean, - workerPath: ?string, -}; - -export type BundleOptions = { - +assetPlugins: Array, - dev: boolean, - entryFile: string, - +entryModuleOnly: boolean, - +generateSourceMaps: boolean, - +hot: boolean, - +inlineSourceMap: boolean, - +isolateModuleIDs: boolean, - minify: boolean, - onProgress: ?(doneCont: number, totalCount: number) => mixed, - +platform: ?string, - +resolutionResponse: ?{}, - +runBeforeMainModule: Array, - +runModule: boolean, - sourceMapUrl: ?string, - unbundle: boolean, -}; - -type DependencyOptions = {| - +dev: boolean, - +entryFile: string, - +hot: boolean, - +minify: boolean, - +platform: ?string, - +recursive: boolean, -|}; - -const bundleDeps = new WeakMap(); -const NODE_MODULES = `${path.sep}node_modules${path.sep}`; - -class Server { - - _opts: { - assetExts: Array, - blacklistRE: void | RegExp, - cacheVersion: string, - extraNodeModules: {}, - getTransformOptions?: GetTransformOptions, - hasteImpl?: HasteImpl, - moduleFormat: string, - platforms: Array, - polyfillModuleNames: Array, - postProcessModules?: PostProcessModules, - postMinifyProcess: PostMinifyProcess, - projectRoots: $ReadOnlyArray, - providesModuleNodeModules?: Array, - reporter: Reporter, - resetCache: boolean, - silent: boolean, - +sourceExts: Array, - +transformCache: TransformCache, - +transformModulePath: string, - transformTimeoutInterval: ?number, - watch: boolean, - workerPath: ?string, - }; - _projectRoots: $ReadOnlyArray; - _bundles: {}; - _changeWatchers: Array<{ - req: IncomingMessage, - res: ServerResponse, - }>; - _fileChangeListeners: Array<(filePath: string) => mixed>; - _assetServer: AssetServer; - _bundler: Bundler; - _debouncedFileChangeHandler: (filePath: string) => mixed; - _hmrFileChangeListener: ?(type: string, filePath: string) => mixed; - _reporter: Reporter; - _symbolicateInWorker: Symbolicate; - _platforms: Set; - _nextBundleBuildID: number; - - constructor(options: Options) { - this._opts = { - assetExts: options.assetExts || defaults.assetExts, - blacklistRE: options.blacklistRE, - cacheVersion: options.cacheVersion || '1.0', - extraNodeModules: options.extraNodeModules || {}, - getTransformOptions: options.getTransformOptions, - globalTransformCache: options.globalTransformCache, - hasteImpl: options.hasteImpl, - moduleFormat: options.moduleFormat != null ? options.moduleFormat : 'haste', - platforms: options.platforms || defaults.platforms, - polyfillModuleNames: options.polyfillModuleNames || [], - postProcessModules: options.postProcessModules, - postMinifyProcess: options.postMinifyProcess, - projectRoots: options.projectRoots, - providesModuleNodeModules: options.providesModuleNodeModules, - reporter: options.reporter, - resetCache: options.resetCache || false, - silent: options.silent || false, - sourceExts: options.sourceExts || defaults.sourceExts, - transformCache: options.transformCache, - transformModulePath: options.transformModulePath, - transformTimeoutInterval: options.transformTimeoutInterval, - watch: options.watch || false, - workerPath: options.workerPath, - }; - - const processFileChange = - ({type, filePath}) => this.onFileChange(type, filePath); - - this._reporter = options.reporter; - this._projectRoots = this._opts.projectRoots; - this._bundles = Object.create(null); - this._changeWatchers = []; - this._fileChangeListeners = []; - this._platforms = new Set(this._opts.platforms); - - this._assetServer = new AssetServer({ - assetExts: this._opts.assetExts, - projectRoots: this._opts.projectRoots, - }); - - const bundlerOpts = Object.create(this._opts); - bundlerOpts.assetServer = this._assetServer; - bundlerOpts.allowBundleUpdates = this._opts.watch; - bundlerOpts.globalTransformCache = options.globalTransformCache; - bundlerOpts.watch = this._opts.watch; - bundlerOpts.reporter = options.reporter; - this._bundler = new Bundler(bundlerOpts); - - // changes to the haste map can affect resolution of files in the bundle - this._bundler.getResolver().then(resolver => { - resolver.getDependencyGraph().getWatcher().on( - 'change', - ({eventsQueue}) => eventsQueue.forEach(processFileChange), - ); - }); - - this._debouncedFileChangeHandler = debounceAndBatch(filePaths => { - // only clear bundles for non-JS changes - if (filePaths.every(RegExp.prototype.test, /\.js(?:on)?$/i)) { - for (const key in this._bundles) { - this._bundles[key].then(bundle => { - const deps = bundleDeps.get(bundle); - filePaths.forEach(filePath => { - // $FlowFixMe(>=0.37.0) - if (deps.files.has(filePath)) { - // $FlowFixMe(>=0.37.0) - deps.outdated.add(filePath); - } - }); - }).catch(e => { - debug(`Could not update bundle: ${e}, evicting from cache`); - delete this._bundles[key]; - }); - } - } else { - debug('Clearing bundles due to non-JS change'); - this._clearBundles(); - } - this._informChangeWatchers(); - }, 50); - - this._symbolicateInWorker = symbolicate.createWorker(); - this._nextBundleBuildID = 1; - } - - end(): mixed { - return this._bundler.end(); - } - - setHMRFileChangeListener(listener: ?(type: string, filePath: string) => mixed) { - this._hmrFileChangeListener = listener; - } - - addFileChangeListener(listener: (filePath: string) => mixed) { - if (this._fileChangeListeners.indexOf(listener) === -1) { - this._fileChangeListeners.push(listener); - } - } - - async buildBundle(options: BundleOptions): Promise { - const bundle = await this._bundler.bundle(options); - const modules = bundle.getModules(); - const nonVirtual = modules.filter(m => !m.virtual); - bundleDeps.set(bundle, { - files: new Map(nonVirtual.map(({sourcePath, meta}) => - [sourcePath, meta != null ? meta.dependencies : []], - )), - idToIndex: new Map(modules.map(({id}, i) => [id, i])), - dependencyPairs: new Map( - nonVirtual - .filter(({meta}) => meta && meta.dependencyPairs) - /* $FlowFixMe: the filter above ensures `dependencyPairs` is not null. */ - .map(m => [m.sourcePath, m.meta.dependencyPairs]) - ), - outdated: new Set(), - }); - return bundle; - } - - buildBundleFromUrl(reqUrl: string): Promise { - const options = this._getOptionsFromUrl(reqUrl); - return this.buildBundle(options); - } - - buildBundleForHMR( - options: {platform: ?string}, - host: string, - port: number, - ): Promise { - return this._bundler.hmrBundle(options, host, port); - } - - getShallowDependencies(options: DependencyOptions): Promise> { - return Promise.resolve().then(() => { - const platform = options.platform != null - ? options.platform : parsePlatformFilePath(options.entryFile, this._platforms).platform; - const {entryFile, dev, minify, hot} = options; - return this._bundler.getShallowDependencies( - {entryFile, platform, dev, minify, hot, generateSourceMaps: false}, - ); - }); - } - - getModuleForPath(entryFile: string): Promise { - return this._bundler.getModuleForPath(entryFile); - } - - getDependencies(options: DependencyOptions): Promise> { - return Promise.resolve().then(() => { - const platform = options.platform != null - ? options.platform : parsePlatformFilePath(options.entryFile, this._platforms).platform; - const {entryFile, dev, minify, hot} = options; - return this._bundler.getDependencies( - {entryFile, platform, dev, minify, hot, generateSourceMaps: false}, - ); - }); - } - - getOrderedDependencyPaths(options: { - +entryFile: string, - +dev: boolean, - +platform: string, - +minify: boolean, - +generateSourceMaps: boolean, - }): Promise { - return Promise.resolve().then(() => { - return this._bundler.getOrderedDependencyPaths(options); - }); - } - - onFileChange(type: string, filePath: string) { - this._assetServer.onFileChange(type, filePath); - - // If Hot Loading is enabled avoid rebuilding bundles and sending live - // updates. Instead, send the HMR updates right away and clear the bundles - // cache so that if the user reloads we send them a fresh bundle - const {_hmrFileChangeListener} = this; - if (_hmrFileChangeListener) { - // Clear cached bundles in case user reloads - this._clearBundles(); - _hmrFileChangeListener(type, filePath); - return; - } else if (type !== 'change' && filePath.indexOf(NODE_MODULES) !== -1) { - // node module resolution can be affected by added or removed files - debug('Clearing bundles due to potential node_modules resolution change'); - this._clearBundles(); - } - - Promise.all( - this._fileChangeListeners.map(listener => listener(filePath)) - ).then( - () => this._onFileChangeComplete(filePath), - () => this._onFileChangeComplete(filePath) - ); - } - - _onFileChangeComplete(filePath: string) { - // Make sure the file watcher event runs through the system before - // we rebuild the bundles. - this._debouncedFileChangeHandler(filePath); - } - - _clearBundles() { - this._bundles = Object.create(null); - } - - _informChangeWatchers() { - const watchers = this._changeWatchers; - const headers = { - 'Content-Type': 'application/json; charset=UTF-8', - }; - - watchers.forEach(function(w) { - w.res.writeHead(205, headers); - w.res.end(JSON.stringify({changed: true})); - }); - - this._changeWatchers = []; - } - - _processDebugRequest(reqUrl: string, res: ServerResponse) { - let ret = ''; - const pathname = url.parse(reqUrl).pathname; - /* $FlowFixMe: pathname would be null for an invalid URL */ - const parts = pathname.split('/').filter(Boolean); - if (parts.length === 1) { - ret += ''; - res.end(ret); - } else if (parts[1] === 'bundles') { - ret += '

Cached Bundles

'; - Promise.all(Object.keys(this._bundles).map(optionsJson => - this._bundles[optionsJson].then(p => { - ret += '

' + optionsJson + '

'; - ret += p.getDebugInfo(); - }) - )).then( - () => res.end(ret), - e => { - res.writeHead(500); - res.end('Internal Error'); - // FIXME: $FlowFixMe: that's a hack, doesn't work with JSON-mode output - this._reporter.terminal && this._reporter.terminal.log(e.stack); - } - ); - } else { - res.writeHead(404); - res.end('Invalid debug request'); - return; - } - } - - _processOnChangeRequest(req: IncomingMessage, res: ServerResponse) { - const watchers = this._changeWatchers; - - watchers.push({ - req, - res, - }); - - req.on('close', () => { - for (let i = 0; i < watchers.length; i++) { - if (watchers[i] && watchers[i].req === req) { - watchers.splice(i, 1); - break; - } - } - }); - } - - _rangeRequestMiddleware( - req: IncomingMessage, - res: ServerResponse, - data: string | Buffer, - assetPath: string, - ) { - if (req.headers && req.headers.range) { - const [rangeStart, rangeEnd] = req.headers.range.replace(/bytes=/, '').split('-'); - const dataStart = parseInt(rangeStart, 10); - const dataEnd = rangeEnd ? parseInt(rangeEnd, 10) : data.length - 1; - const chunksize = (dataEnd - dataStart) + 1; - - res.writeHead(206, { - 'Accept-Ranges': 'bytes', - 'Content-Length': chunksize.toString(), - 'Content-Range': `bytes ${dataStart}-${dataEnd}/${data.length}`, - 'Content-Type': mime.lookup(path.basename(assetPath[1])), - }); - - return data.slice(dataStart, dataEnd + 1); - } - - return data; - } - - _processAssetsRequest(req: IncomingMessage, res: ServerResponse) { - const urlObj = url.parse(decodeURI(req.url), true); - /* $FlowFixMe: could be empty if the url is invalid */ - const assetPath: string = urlObj.pathname.match(/^\/assets\/(.+)$/); - - const processingAssetRequestLogEntry = - log(createActionStartEntry({ - action_name: 'Processing asset request', - asset: assetPath[1], - })); - - /* $FlowFixMe: query may be empty for invalid URLs */ - this._assetServer.get(assetPath[1], urlObj.query.platform) - .then( - data => { - // Tell clients to cache this for 1 year. - // This is safe as the asset url contains a hash of the asset. - if (process.env.REACT_NATIVE_ENABLE_ASSET_CACHING === true) { - res.setHeader('Cache-Control', 'max-age=31536000'); - } - res.end(this._rangeRequestMiddleware(req, res, data, assetPath)); - process.nextTick(() => { - log(createActionEndEntry(processingAssetRequestLogEntry)); - }); - }, - error => { - console.error(error.stack); - res.writeHead(404); - res.end('Asset not found'); - } - ); - } - - optionsHash(options: {}) { - // onProgress is a function, can't be serialized - return JSON.stringify(Object.assign({}, options, {onProgress: null})); - } - - /** - * Ensure we properly report the promise of a build that's happening, - * including failed builds. We use that separately for when we update a bundle - * and for when we build for scratch. - */ - _reportBundlePromise( - buildID: string, - options: {entryFile: string}, - bundlePromise: Promise, - ): Promise { - this._reporter.update({ - buildID, - entryFilePath: options.entryFile, - type: 'bundle_build_started', - }); - return bundlePromise.then(bundle => { - this._reporter.update({ - buildID, - type: 'bundle_build_done', - }); - return bundle; - }, error => { - this._reporter.update({ - buildID, - type: 'bundle_build_failed', - }); - return Promise.reject(error); - }); - } - - useCachedOrUpdateOrCreateBundle( - buildID: string, - options: BundleOptions, - ): Promise { - const optionsJson = this.optionsHash(options); - const bundleFromScratch = () => { - const building = this.buildBundle(options); - this._bundles[optionsJson] = building; - return building; - }; - - if (optionsJson in this._bundles) { - return this._bundles[optionsJson].then(bundle => { - const deps = bundleDeps.get(bundle); - // $FlowFixMe(>=0.37.0) - const {dependencyPairs, files, idToIndex, outdated} = deps; - if (outdated.size) { - - const updatingExistingBundleLogEntry = - log(createActionStartEntry({ - action_name: 'Updating existing bundle', - outdated_modules: outdated.size, - })); - - debug('Attempt to update existing bundle'); - - // $FlowFixMe(>=0.37.0) - deps.outdated = new Set(); - - const {platform, dev, minify, hot} = options; - - // Need to create a resolution response to pass to the bundler - // to process requires after transform. By providing a - // specific response we can compute a non recursive one which - // is the least we need and improve performance. - const bundlePromise = this._bundles[optionsJson] = - Promise.all([ - this.getDependencies({ - platform, dev, hot, minify, - entryFile: options.entryFile, - recursive: false, - }), - Promise.all(Array.from(outdated, this.getModuleForPath, this)), - ]).then(([response, changedModules]) => { - debug('Update bundle: rebuild shallow bundle'); - - changedModules.forEach(m => { - response.setResolvedDependencyPairs( - m, - /* $FlowFixMe: should be enforced not to be null. */ - dependencyPairs.get(m.path), - {ignoreFinalized: true}, - ); - }); - - return this.buildBundle({ - ...options, - resolutionResponse: response.copy({ - dependencies: changedModules, - }), - }).then(updateBundle => { - const oldModules = bundle.getModules(); - const newModules = updateBundle.getModules(); - for (let i = 0, n = newModules.length; i < n; i++) { - const moduleTransport = newModules[i]; - const {meta, sourcePath} = moduleTransport; - if (outdated.has(sourcePath)) { - /* $FlowFixMe: `meta` could be empty */ - if (!contentsEqual(meta.dependencies, new Set(files.get(sourcePath)))) { - // bail out if any dependencies changed - return Promise.reject(Error( - `Dependencies of ${sourcePath} changed from [${ - /* $FlowFixMe: `get` can return empty */ - files.get(sourcePath).join(', ') - }] to [${ - /* $FlowFixMe: `meta` could be empty */ - meta.dependencies.join(', ') - }]` - )); - } - - oldModules[idToIndex.get(moduleTransport.id)] = moduleTransport; - } - } - - bundle.invalidateSource(); - - log(createActionEndEntry(updatingExistingBundleLogEntry)); - - debug('Successfully updated existing bundle'); - return bundle; - }); - }).catch(e => { - debug('Failed to update existing bundle, rebuilding...', e.stack || e.message); - return bundleFromScratch(); - }); - return this._reportBundlePromise(buildID, options, bundlePromise); - } else { - debug('Using cached bundle'); - return bundle; - } - }); - } - - return this._reportBundlePromise(buildID, options, bundleFromScratch()); - } - - processRequest( - req: IncomingMessage, - res: ServerResponse, - next: () => mixed, - ) { - const urlObj = url.parse(req.url, true); - const {host} = req.headers; - debug(`Handling request: ${host ? 'http://' + host : ''}${req.url}`); - /* $FlowFixMe: Could be empty if the URL is invalid. */ - const pathname: string = urlObj.pathname; - - let requestType; - if (pathname.match(/\.bundle$/)) { - requestType = 'bundle'; - } else if (pathname.match(/\.map$/)) { - requestType = 'map'; - } else if (pathname.match(/\.assets$/)) { - requestType = 'assets'; - } else if (pathname.match(/^\/debug/)) { - this._processDebugRequest(req.url, res); - return; - } else if (pathname.match(/^\/onchange\/?$/)) { - this._processOnChangeRequest(req, res); - return; - } else if (pathname.match(/^\/assets\//)) { - this._processAssetsRequest(req, res); - return; - } else if (pathname === '/symbolicate') { - this._symbolicate(req, res); - return; - } else { - next(); - return; - } - - const options = this._getOptionsFromUrl(req.url); - const requestingBundleLogEntry = - log(createActionStartEntry({ - action_name: 'Requesting bundle', - bundle_url: req.url, - entry_point: options.entryFile, - })); - - const buildID = this.getNewBuildID(); - let reportProgress = emptyFunction; - if (!this._opts.silent) { - reportProgress = (transformedFileCount, totalFileCount) => { - this._reporter.update({ - buildID, - type: 'bundle_transform_progressed', - transformedFileCount, - totalFileCount, - }); - }; - } - - const mres = MultipartResponse.wrap(req, res); - options.onProgress = (done, total) => { - reportProgress(done, total); - mres.writeChunk({'Content-Type': 'application/json'}, JSON.stringify({done, total})); - }; - - debug('Getting bundle for request'); - const building = this.useCachedOrUpdateOrCreateBundle(buildID, options); - building.then( - p => { - if (requestType === 'bundle') { - debug('Generating source code'); - const bundleSource = p.getSource({ - inlineSourceMap: options.inlineSourceMap, - minify: options.minify, - dev: options.dev, - }); - debug('Writing response headers'); - const etag = p.getEtag(); - mres.setHeader('Content-Type', 'application/javascript'); - mres.setHeader('ETag', etag); - - if (req.headers['if-none-match'] === etag) { - debug('Responding with 304'); - mres.writeHead(304); - mres.end(); - } else { - mres.end(bundleSource); - } - debug('Finished response'); - log(createActionEndEntry(requestingBundleLogEntry)); - } else if (requestType === 'map') { - const sourceMap = p.getSourceMapString({ - minify: options.minify, - dev: options.dev, - }); - - mres.setHeader('Content-Type', 'application/json'); - mres.end(sourceMap); - log(createActionEndEntry(requestingBundleLogEntry)); - } else if (requestType === 'assets') { - const assetsList = JSON.stringify(p.getAssets()); - mres.setHeader('Content-Type', 'application/json'); - mres.end(assetsList); - log(createActionEndEntry(requestingBundleLogEntry)); - } - }, - error => this._handleError(mres, this.optionsHash(options), error) - ).catch(error => { - process.nextTick(() => { - throw error; - }); - }); - } - - _symbolicate(req: IncomingMessage, res: ServerResponse) { - const symbolicatingLogEntry = - log(createActionStartEntry('Symbolicating')); - - debug('Start symbolication'); - - /* $FlowFixMe: where is `rowBody` defined? Is it added by - * the `connect` framework? */ - Promise.resolve(req.rawBody).then(body => { - const stack = JSON.parse(body).stack; - - // In case of multiple bundles / HMR, some stack frames can have - // different URLs from others - const urls = new Set(); - stack.forEach(frame => { - const sourceUrl = frame.file; - // Skip `/debuggerWorker.js` which drives remote debugging because it - // does not need to symbolication. - // Skip anything except http(s), because there is no support for that yet - if (!urls.has(sourceUrl) && - !sourceUrl.endsWith('/debuggerWorker.js') && - sourceUrl.startsWith('http')) { - urls.add(sourceUrl); - } - }); - - const mapPromises = - Array.from(urls.values()).map(this._sourceMapForURL, this); - - debug('Getting source maps for symbolication'); - return Promise.all(mapPromises).then(maps => { - debug('Sending stacks and maps to symbolication worker'); - const urlsToMaps = zip(urls.values(), maps); - return this._symbolicateInWorker(stack, urlsToMaps); - }); - }).then( - stack => { - debug('Symbolication done'); - res.end(JSON.stringify({stack})); - process.nextTick(() => { - log(createActionEndEntry(symbolicatingLogEntry)); - }); - }, - error => { - console.error(error.stack || error); - res.statusCode = 500; - res.end(JSON.stringify({error: error.message})); - } - ); - } - - _sourceMapForURL(reqUrl: string): Promise { - const options = this._getOptionsFromUrl(reqUrl); - // We're not properly reporting progress here. Reporting should be done - // from within that function. - const building = this.useCachedOrUpdateOrCreateBundle( - this.getNewBuildID(), - options, - ); - return building.then(p => p.getSourceMap({ - minify: options.minify, - dev: options.dev, - })); - } - - _handleError(res: ServerResponse, bundleID: string, error: { - status: number, - type: string, - description: string, - filename: string, - lineNumber: number, - errors: Array<{description: string, filename: string, lineNumber: number}>, - }) { - res.writeHead(error.status || 500, { - 'Content-Type': 'application/json; charset=UTF-8', - }); - - if (error instanceof Error && ( - error.type === 'TransformError' || - error.type === 'NotFoundError' || - error.type === 'UnableToResolveError' - )) { - error.errors = [{ - description: error.description, - filename: error.filename, - lineNumber: error.lineNumber, - }]; - res.end(JSON.stringify(error)); - - if (error.type === 'NotFoundError') { - delete this._bundles[bundleID]; - } - this._reporter.update({error, type: 'bundling_error'}); - } else { - console.error(error.stack || error); - res.end(JSON.stringify({ - type: 'InternalError', - message: 'react-packager has encountered an internal error, ' + - 'please check your terminal error output for more details', - })); - } - } - - _getOptionsFromUrl(reqUrl: string): BundleOptions { - // `true` to parse the query param as an object. - const urlObj = url.parse(reqUrl, true); - - /* $FlowFixMe: `pathname` could be empty for an invalid URL */ - const pathname = decodeURIComponent(urlObj.pathname); - - // Backwards compatibility. Options used to be as added as '.' to the - // entry module name. We can safely remove these options. - const entryFile = pathname.replace(/^\//, '').split('.').filter(part => { - if (part === 'includeRequire' || part === 'runModule' || - part === 'bundle' || part === 'map' || part === 'assets') { - return false; - } - return true; - }).join('.') + '.js'; - - // try to get the platform from the url - /* $FlowFixMe: `query` could be empty for an invalid URL */ - const platform = urlObj.query.platform || - parsePlatformFilePath(pathname, this._platforms).platform; - - /* $FlowFixMe: `query` could be empty for an invalid URL */ - const assetPlugin = urlObj.query.assetPlugin; - const assetPlugins = Array.isArray(assetPlugin) ? - assetPlugin : - (typeof assetPlugin === 'string') ? [assetPlugin] : []; - - const dev = this._getBoolOptionFromQuery(urlObj.query, 'dev', true); - const minify = this._getBoolOptionFromQuery(urlObj.query, 'minify', false); - return { - sourceMapUrl: url.format({ - hash: urlObj.hash, - pathname: pathname.replace(/\.bundle$/, '.map'), - query: urlObj.query, - search: urlObj.search, - }), - entryFile, - dev, - minify, - hot: this._getBoolOptionFromQuery(urlObj.query, 'hot', false), - runBeforeMainModule: defaults.runBeforeMainModule, - runModule: this._getBoolOptionFromQuery(urlObj.query, 'runModule', true), - inlineSourceMap: this._getBoolOptionFromQuery( - urlObj.query, - 'inlineSourceMap', - false - ), - isolateModuleIDs: false, - platform, - resolutionResponse: null, - entryModuleOnly: this._getBoolOptionFromQuery( - urlObj.query, - 'entryModuleOnly', - false, - ), - generateSourceMaps: - minify || !dev || this._getBoolOptionFromQuery(urlObj.query, 'babelSourcemap', false), - assetPlugins, - onProgress: null, - unbundle: false, - }; - } - - _getBoolOptionFromQuery(query: ?{}, opt: string, defaultVal: boolean): boolean { - /* $FlowFixMe: `query` could be empty when it comes from an invalid URL */ - if (query[opt] == null) { - return defaultVal; - } - - return query[opt] === 'true' || query[opt] === '1'; - } - - getNewBuildID(): string { - return (this._nextBundleBuildID++).toString(36); - } - - static DEFAULT_BUNDLE_OPTIONS; - -} - -Server.DEFAULT_BUNDLE_OPTIONS = { - assetPlugins: [], - dev: true, - entryModuleOnly: false, - generateSourceMaps: false, - hot: false, - inlineSourceMap: false, - isolateModuleIDs: false, - minify: false, - onProgress: null, - resolutionResponse: null, - runBeforeMainModule: defaults.runBeforeMainModule, - runModule: true, - sourceMapUrl: null, - unbundle: false, -}; - -function contentsEqual(array: Array, set: Set): boolean { - return array.length === set.size && array.every(set.has, set); -} - -function* zip(xs: Iterable, ys: Iterable): Iterable<[X, Y]> { - //$FlowIssue #9324959 - const ysIter: Iterator = ys[Symbol.iterator](); - for (const x of xs) { - const y = ysIter.next(); - if (y.done) { - return; - } - yield [x, y.value]; - } -} - -module.exports = Server; diff --git a/packager/src/Server/symbolicate/__tests__/symbolicate-test.js b/packager/src/Server/symbolicate/__tests__/symbolicate-test.js deleted file mode 100644 index d5dcc72a7f4c43..00000000000000 --- a/packager/src/Server/symbolicate/__tests__/symbolicate-test.js +++ /dev/null @@ -1,106 +0,0 @@ -/** - * 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'; - -jest.disableAutomock(); -jest.mock('child_process'); -jest.mock('net'); - -const EventEmitter = require('events'); -const {Readable} = require('stream'); -const {createWorker} = require('../'); - -let childProcess, socketResponse, socket, worker; - -beforeEach(() => { - childProcess = Object.assign(new EventEmitter(), {send: jest.fn()}); - require('child_process').fork.mockReturnValueOnce(childProcess); - setupCommunication(); - - socketResponse = '{"error": "no fake socket response set"}'; - socket = Object.assign(new Readable(), { - _read() { - this.push(socketResponse); - this.push(null); - }, - end: jest.fn(), - setEncoding: jest.fn(), - }); - require('net').createConnection.mockImplementation(() => socket); - - worker = createWorker(); -}); - -it('sends a socket path to the child process', () => { - socketResponse = '{}'; - return worker([], fakeSourceMaps()) - .then(() => expect(childProcess.send).toBeCalledWith(expect.any(String))); -}); - -it('fails if the child process emits an error', () => { - const error = new Error('Expected error'); - childProcess.send.mockImplementation(() => - childProcess.emit('error', error)); - - expect.assertions(1); - return worker([], fakeSourceMaps()) - .catch(e => expect(e).toBe(error)); -}); - -it('fails if the socket connection emits an error', () => { - const error = new Error('Expected error'); - socket._read = () => socket.emit('error', error); - - expect.assertions(1); - return worker([], fakeSourceMaps()) - .catch(e => expect(e).toBe(error)); -}); - -it('sends the passed in stack and maps over the socket', () => { - socketResponse = '{}'; - const stack = ['the', 'stack']; - return worker(stack, fakeSourceMaps()) - .then(() => - expect(socket.end).toBeCalledWith(JSON.stringify({ - maps: Array.from(fakeSourceMaps()), - stack, - }))); -}); - -it('resolves to the `result` property of the message returned over the socket', () => { - socketResponse = '{"result": {"the": "result"}}'; - return worker([], fakeSourceMaps()) - .then(response => expect(response).toEqual({the: 'result'})); -}); - -it('rejects with the `error` property of the message returned over the socket', () => { - socketResponse = '{"error": "the error message"}'; - - expect.assertions(1); - return worker([], fakeSourceMaps()) - .catch(error => expect(error).toEqual(new Error('the error message'))); -}); - -it('rejects if the socket response cannot be parsed as JSON', () => { - socketResponse = '{'; - - expect.assertions(1); - return worker([], fakeSourceMaps()) - .catch(error => expect(error).toBeInstanceOf(SyntaxError)); -}); - -function setupCommunication() { - childProcess.send.mockImplementation(() => - process.nextTick(() => childProcess.emit('message'))); -} - -function* fakeSourceMaps() { - yield [1, {}]; - yield [2, {}]; -} diff --git a/packager/src/Server/symbolicate/__tests__/util-test.js b/packager/src/Server/symbolicate/__tests__/util-test.js deleted file mode 100644 index 1311b920b85c17..00000000000000 --- a/packager/src/Server/symbolicate/__tests__/util-test.js +++ /dev/null @@ -1,157 +0,0 @@ -/** - * 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'; - -jest.disableAutomock(); - -const {LazyPromise, LockingPromise} = require('../util'); - -describe('Lazy Promise', () => { - let factory; - const value = {}; - - beforeEach(() => { - factory = jest.fn(); - factory.mockReturnValue(Promise.resolve(value)); - }); - - it('does not run the factory by default', () => { - new LazyPromise(factory); // eslint-disable-line no-new - expect(factory).not.toBeCalled(); - }); - - it('calling `.then()` returns a promise', () => { - expect(new LazyPromise(factory).then()).toBeInstanceOf(Promise); - }); - - it('does not invoke the factory twice', () => { - const p = new LazyPromise(factory); - p.then(x => x); - p.then(x => x); - expect(factory).toHaveBeenCalledTimes(1); - }); - - describe('value and error propagation', () => { - it('resolves to the value provided by the factory', () => { - expect.assertions(1); - return new LazyPromise(factory) - .then(v => expect(v).toBe(value)); - }); - - it('passes through errors if not handled', () => { - const error = new Error('Unhandled'); - factory.mockReturnValue(Promise.reject(error)); - - expect.assertions(1); - return new LazyPromise(factory) - .then() - .catch(e => expect(e).toBe(error)); - }); - - it('uses rejection handlers passed to `then()`', () => { - const error = new Error('Must be handled'); - factory.mockReturnValue(Promise.reject(error)); - - expect.assertions(1); - return new LazyPromise(factory) - .then(() => {}, e => expect(e).toBe(error)); - }); - - it('uses rejection handlers passed to `catch()`', () => { - const error = new Error('Must be handled'); - factory.mockReturnValue(Promise.reject(error)); - - expect.assertions(1); - return new LazyPromise(factory) - .catch(e => expect(e).toBe(error)); - }); - }); -}); - -describe('Locking Promise', () => { - it('resolves to the value of the passed-in promise', () => { - const value = {}; - - expect.assertions(1); - return new LockingPromise(Promise.resolve(value)) - .then(v => expect(v).toBe(value)); - }); - - it('passes through rejections', () => { - const error = new Error('Rejection'); - - expect.assertions(1); - return new LockingPromise(Promise.reject(error)) - .then() - .catch(e => expect(e).toBe(error)); - }); - - it('uses rejection handlers passed to `then()`', () => { - const error = new Error('Must be handled'); - - expect.assertions(1); - return new LockingPromise(Promise.reject(error)) - .then(x => x, e => expect(e).toBe(error)); - }); - - it('uses rejection handlers passed to `catch()`', () => { - const error = new Error('Must be handled'); - - expect.assertions(1); - return new LockingPromise(Promise.reject(error)) - .catch(e => expect(e).toBe(error)); - }); - - describe('locking', () => { - const value = Symbol; - let locking; - beforeEach(() => { - locking = new LockingPromise(Promise.resolve(value)); - }); - - - it('only allows one handler to access the promise value', () => { - const deferred = defer(); - const secondHandler = jest.fn(); - locking.then(() => deferred.promise); - locking.then(secondHandler); - return Promise.resolve() // wait for the next tick - .then(() => expect(secondHandler).not.toBeCalled()); - }); - - it('allows waiting handlers to access the value after the current handler resolves', () => { - let counter = 0; - - const deferred = defer(); - const x = locking.then(v => { - const result = [++counter, v]; - return deferred.promise.then(() => result); - }); - const y = locking.then(v => [++counter, v]); - const z = locking.then(v => [++counter, v]); - - deferred.resolve(); - - return Promise.all([x, y, z]) - .then(([first, second, third]) => { - expect(first).toEqual([1, value]); - expect(second).toEqual([2, value]); - expect(third).toEqual([3, value]); - }); - }); - }); -}); - - -function defer() { - let resolve; - const promise = new Promise(res => { resolve = res; }); - return {promise, resolve}; -} diff --git a/packager/src/Server/symbolicate/__tests__/worker-test.js b/packager/src/Server/symbolicate/__tests__/worker-test.js deleted file mode 100644 index efa77038d72598..00000000000000 --- a/packager/src/Server/symbolicate/__tests__/worker-test.js +++ /dev/null @@ -1,110 +0,0 @@ -/** - * 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. - * - * @format - */ -'use strict'; - -jest.disableAutomock(); - -const SourceMapGenerator = require('../../../Bundler/source-map/Generator'); -const {symbolicate} = require('../worker'); - -let connection; -beforeEach(() => { - connection = {end: jest.fn()}; -}); - -it('symbolicates stack frames', () => { - const mappings = [ - { - from: {file: 'bundle1.js', lineNumber: 1, column: 2}, - to: {file: 'apples.js', lineNumber: 12, column: 34}, - }, - { - from: {file: 'bundle2.js', lineNumber: 3, column: 4}, - to: {file: 'bananas.js', lineNumber: 56, column: 78}, - }, - { - from: {file: 'bundle1.js', lineNumber: 5, column: 6}, - to: {file: 'clementines.js', lineNumber: 90, column: 12}, - }, - ]; - - const stack = mappings.map(m => m.to); - const maps = Object.entries( - groupBy(mappings, m => m.from.file), - ).map(([file, ms]) => [file, sourceMap(file, ms)]); - - return symbolicate(connection, makeData(stack, maps)).then(() => - expect(connection.end).toBeCalledWith( - JSON.stringify({result: mappings.map(m => m.to)}), - ), - ); -}); - -it('ignores stack frames without corresponding map', () => { - const frame = { - file: 'arbitrary.js', - lineNumber: 123, - column: 456, - }; - - return symbolicate( - connection, - makeData([frame], [['other.js', emptyMap()]]), - ).then(() => - expect(connection.end).toBeCalledWith(JSON.stringify({result: [frame]})), - ); -}); - -it('ignores `/debuggerWorker.js` stack frames', () => { - const frame = { - file: 'http://localhost:8081/debuggerWorker.js', - lineNumber: 123, - column: 456, - }; - - return symbolicate(connection, makeData([frame])).then(() => - expect(connection.end).toBeCalledWith(JSON.stringify({result: [frame]})), - ); -}); - -function makeData(stack, maps = []) { - return JSON.stringify({maps, stack}); -} - -function sourceMap(file, mappings) { - const g = new SourceMapGenerator(); - g.startFile(file, null); - mappings.forEach(({from, to}) => - g.addSourceMapping(to.lineNumber, to.column, from.lineNumber, from.column), - ); - return g.toMap(); -} - -function groupBy(xs, key) { - const grouped = {}; - xs.forEach(x => { - const k = key(x); - if (k in grouped) { - grouped[k].push(x); - } else { - grouped[k] = [x]; - } - }); - return grouped; -} - -function emptyMap() { - return { - version: 3, - sources: [], - mappings: '', - }; -} diff --git a/packager/src/Server/symbolicate/package.json b/packager/src/Server/symbolicate/package.json deleted file mode 100644 index 901fd0b6a3f96e..00000000000000 --- a/packager/src/Server/symbolicate/package.json +++ /dev/null @@ -1 +0,0 @@ -{"main": "./symbolicate.js"} diff --git a/packager/src/Server/symbolicate/symbolicate.js b/packager/src/Server/symbolicate/symbolicate.js deleted file mode 100644 index f4fd96363e95ab..00000000000000 --- a/packager/src/Server/symbolicate/symbolicate.js +++ /dev/null @@ -1,81 +0,0 @@ -/** - * 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. - * - * @flow - */ -'use strict'; - -const concat = require('concat-stream'); -const debug = require('debug')('RNP:Symbolication'); -const net = require('net'); -const temp = require('temp'); -const xpipe = require('xpipe'); - -const {LazyPromise, LockingPromise} = require('./util'); -const {fork} = require('child_process'); - -export type {SourceMap as SourceMap}; -import type {SourceMap} from '../../lib/SourceMap'; - -export type Stack = Array<{file: string, lineNumber: number, column: number}>; -export type Symbolicate = - (Stack, Iterable<[string, SourceMap]>) => Promise; - -const affixes = {prefix: 'metro-bundler-symbolicate', suffix: '.sock'}; -const childPath = require.resolve('./worker'); - -exports.createWorker = (): Symbolicate => { - // There are issues with named sockets on windows that cause the connection to - // close too early so run the symbolicate server on a random localhost port. - const socket = process.platform === 'win32' - ? 34712 - : xpipe.eq(temp.path(affixes)); - const child = new LockingPromise(new LazyPromise(() => startupChild(socket))); - - return (stack, sourceMaps) => - child - .then(() => connectAndSendJob(socket, message(stack, sourceMaps))) - .then(JSON.parse) - .then(response => - 'error' in response - ? Promise.reject(new Error(response.error)) - : response.result - ); -}; - -function startupChild(socket) { - const child = fork(childPath); - return new Promise((resolve, reject) => { - child - .once('error', reject) - .once('message', () => { - child.removeAllListeners(); - resolve(child); - }); - // $FlowFixMe ChildProcess.send should accept any type. - child.send(socket); - }); -} - -function connectAndSendJob(socket, data) { - const job = new Promise((resolve, reject) => { - debug('Connecting to worker'); - const connection = net.createConnection(socket); - connection.setEncoding('utf8'); - connection.on('error', reject); - connection.pipe(concat(resolve)); - debug('Sending data to worker'); - connection.end(data); - }); - job.then(() => debug('Received response from worker')); - return job; -} - -function message(stack, sourceMaps) { - return JSON.stringify({maps: Array.from(sourceMaps), stack}); -} diff --git a/packager/src/Server/symbolicate/util.js b/packager/src/Server/symbolicate/util.js deleted file mode 100644 index 858c21823c7690..00000000000000 --- a/packager/src/Server/symbolicate/util.js +++ /dev/null @@ -1,86 +0,0 @@ -/** - * 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. - * - * @flow - */ - -'use strict'; - -type PromiseLike = { - catch(onReject?: (error: any) => ?Promise | U): Promise, - then( - fulfilled?: R => Promise | U, - rejected?: (error: any) => Promise | U, - ): Promise, -}; - -/** - * A promise-like object that only creates the underlying value lazily - * when requested. - */ -exports.LazyPromise = class LazyPromise { - _promise: PromiseLike; - - constructor(factory: () => PromiseLike) { - //$FlowIssue #16209141 - Object.defineProperty(this, '_promise', { - configurable: true, - enumerable: true, - get: () => (this._promise = factory()), - set: value => Object.defineProperty(this, '_promise', {value}), - }); - } - - then( - fulfilled?: (value: T) => Promise | U, - rejected?: (error: any) => Promise | U - ): Promise { - return this._promise.then(fulfilled, rejected); - } - - catch( - rejected?: (error: any) => ?Promise | U - ): Promise { - return this._promise.catch(rejected); - } -}; - -/** - * A promise-like object that allows only one `.then()` handler to access - * the wrapped value simultaneously. Can be used to lock resources that do - * asynchronous work. - */ -exports.LockingPromise = class LockingPromise { - _gate: PromiseLike - _promise: PromiseLike - - constructor(promise: PromiseLike) { - this._gate = this._promise = promise; - } - - then( - fulfilled?: (value: T) => Promise | U, - rejected?: (error: any) => Promise | U - ): Promise { - const whenUnlocked = () => { - const promise = this._promise.then(fulfilled, rejected); - this._gate = promise.then(empty); // avoid retaining the result of promise - return promise; - }; - - return this._gate.then(whenUnlocked, whenUnlocked); - } - - catch( - rejected?: (error: any) => ?Promise | U - ): Promise { - return this._promise.catch(rejected); - } -}; - -function empty() {} diff --git a/packager/src/Server/symbolicate/worker.js b/packager/src/Server/symbolicate/worker.js deleted file mode 100644 index 5384540342d246..00000000000000 --- a/packager/src/Server/symbolicate/worker.js +++ /dev/null @@ -1,77 +0,0 @@ -/** - * 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'; - -// RUNS UNTRANSFORMED IN NODE >= v4 -// NO FANCY FEATURES, E.G. DESTRUCTURING, PLEASE! - -const SourceMapConsumer = require('source-map').SourceMapConsumer; -const concat = require('concat-stream'); -const net = require('net'); - -process.once('message', socket => { - net.createServer({allowHalfOpen: true}, connection => { - connection.setEncoding('utf8'); - connection.pipe(concat(data => - symbolicate(connection, data) - .catch(console.error) // log the error as a last resort - )); - }).listen(socket, () => process.send(null)); -}); - -function symbolicate(connection, data) { - return Promise.resolve(data) - .then(JSON.parse) - .then(symbolicateStack) - .then(JSON.stringify) - .catch(makeErrorMessage) - .then(message => connection.end(message)); -} - -function symbolicateStack(data) { - const consumers = new Map(data.maps.map(mapToConsumer)); - return { - result: data.stack.map(frame => mapFrame(frame, consumers)), - }; -} - -function mapFrame(frame, consumers) { - const sourceUrl = frame.file; - const consumer = consumers.get(sourceUrl); - if (consumer == null) { - return frame; - } - const original = consumer.originalPositionFor({ - line: frame.lineNumber, - column: frame.column, - }); - if (!original) { - return frame; - } - return Object.assign({}, frame, { - file: original.source, - lineNumber: original.line, - column: original.column, - }); -} - -function makeErrorMessage(error) { - return JSON.stringify({ - error: String(error && error.message || error), - }); -} - -function mapToConsumer(tuple) { - tuple[1] = new SourceMapConsumer(tuple[1]); - return tuple; -} - -// for testing -exports.symbolicate = symbolicate; diff --git a/packager/src/__mocks__/debug.js b/packager/src/__mocks__/debug.js deleted file mode 100644 index 8739091797cadc..00000000000000 --- a/packager/src/__mocks__/debug.js +++ /dev/null @@ -1,11 +0,0 @@ -/** - * 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'; - -module.exports = () => () => {}; diff --git a/packager/src/babelRegisterOnly.js b/packager/src/babelRegisterOnly.js deleted file mode 100644 index fbb839da42c763..00000000000000 --- a/packager/src/babelRegisterOnly.js +++ /dev/null @@ -1,38 +0,0 @@ -/** - * 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'; - -require('./setupNodePolyfills'); - -var _only = []; - -function registerOnly(onlyList) { - require('babel-register')(config(onlyList)); -} - -function config(onlyList) { - _only = _only.concat(onlyList); - return { - presets: [require('babel-preset-es2015-node')], - plugins: [ - 'transform-flow-strip-types', - 'syntax-trailing-function-commas', - 'transform-object-rest-spread', - 'transform-async-to-generator', - 'transform-class-properties', - ].map(pluginName => require(`babel-plugin-${pluginName}`)), - only: _only, - retainLines: true, - sourceMaps: 'inline', - babelrc: false, - }; -} - -module.exports = exports = registerOnly; -exports.config = config; diff --git a/packager/src/blacklist.js b/packager/src/blacklist.js deleted file mode 100644 index 7c2cbb487d16ba..00000000000000 --- a/packager/src/blacklist.js +++ /dev/null @@ -1,44 +0,0 @@ -/** - * 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'; - -var path = require('path'); - -// Don't forget to everything listed here to `package.json` -// modulePathIgnorePatterns. -var sharedBlacklist = [ - /node_modules[/\\]react[/\\]dist[/\\].*/, - - /website\/node_modules\/.*/, - - /heapCapture\/bundle\.js/, -]; - -function escapeRegExp(pattern) { - if (Object.prototype.toString.call(pattern) === '[object RegExp]') { - return pattern.source.replace(/\//g, path.sep); - } else if (typeof pattern === 'string') { - var escaped = pattern.replace(/[\-\[\]\{\}\(\)\*\+\?\.\\\^\$\|]/g, '\\$&'); - // convert the '/' into an escaped local file separator - return escaped.replace(/\//g, '\\' + path.sep); - } else { - throw new Error('Unexpected packager blacklist pattern: ' + pattern); - } -} - -function blacklist(additionalBlacklist) { - return new RegExp('(' + - (additionalBlacklist || []).concat(sharedBlacklist) - .map(escapeRegExp) - .join('|') + - ')$' - ); -} - -module.exports = blacklist; diff --git a/packager/src/defaults.js b/packager/src/defaults.js deleted file mode 100644 index f5288937d7ebc4..00000000000000 --- a/packager/src/defaults.js +++ /dev/null @@ -1,46 +0,0 @@ -/** - * 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. - * - * @flow - */ -'use strict'; - -exports.assetExts = [ - 'bmp', 'gif', 'jpg', 'jpeg', 'png', 'psd', 'svg', 'webp', // Image formats - 'm4v', 'mov', 'mp4', 'mpeg', 'mpg', 'webm', // Video formats - 'aac', 'aiff', 'caf', 'm4a', 'mp3', 'wav', // Audio formats - 'html', 'pdf', // Document formats -]; - -exports.sourceExts = ['js', 'json']; - -exports.moduleSystem = require.resolve('./Resolver/polyfills/require.js'); - -exports.platforms = ['ios', 'android', 'windows', 'web']; - -exports.polyfills = [ - require.resolve('./Resolver/polyfills/Object.es6.js'), - require.resolve('./Resolver/polyfills/console.js'), - require.resolve('./Resolver/polyfills/error-guard.js'), - require.resolve('./Resolver/polyfills/Number.es6.js'), - require.resolve('./Resolver/polyfills/String.prototype.es6.js'), - require.resolve('./Resolver/polyfills/Array.prototype.es6.js'), - require.resolve('./Resolver/polyfills/Array.es6.js'), - require.resolve('./Resolver/polyfills/Object.es7.js'), - require.resolve('./Resolver/polyfills/babelHelpers.js'), -]; - -exports.providesModuleNodeModules = [ - 'react-native', - 'react-native-windows', -]; - -exports.runBeforeMainModule = [ - // Ensures essential globals are available and are patched correctly. - 'InitializeCore', -]; diff --git a/packager/src/index.js b/packager/src/index.js deleted file mode 100644 index 8a2c2e0ab2272a..00000000000000 --- a/packager/src/index.js +++ /dev/null @@ -1,149 +0,0 @@ -/** - * 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. - * - * @flow - */ - -'use strict'; - -const Logger = require('./Logger'); -const TransformCaching = require('./lib/TransformCaching'); - -const debug = require('debug'); -const invariant = require('fbjs/lib/invariant'); - -import type {PostProcessModules, PostMinifyProcess} from './Bundler'; -import type Server from './Server'; -import type {GlobalTransformCache} from './lib/GlobalTransformCache'; -import type {TransformCache} from './lib/TransformCaching'; -import type {Reporter} from './lib/reporting'; -import type {HasteImpl} from './node-haste/Module'; - -exports.createServer = createServer; -exports.Logger = Logger; - -type Options = { - hasteImpl?: HasteImpl, - globalTransformCache: ?GlobalTransformCache, - nonPersistent?: boolean, - postProcessModules?: PostProcessModules, - postMinifyProcess?: PostMinifyProcess, - projectRoots: $ReadOnlyArray, - reporter?: Reporter, - +sourceExts: ?Array, - +transformCache: TransformCache, - +transformModulePath: string, - watch?: boolean, - workerPath: ?string, -}; - -type StrictOptions = {...Options, reporter: Reporter}; - -type PublicBundleOptions = { - +dev?: boolean, - +entryFile: string, - +generateSourceMaps?: boolean, - +inlineSourceMap?: boolean, - +minify?: boolean, - +platform?: string, - +runModule?: boolean, - +sourceMapUrl?: string, -}; - -exports.TransformCaching = TransformCaching; - -/** - * This is a public API, so we don't trust the value and purposefully downgrade - * it as `mixed`. Because it understands `invariant`, Flow ensure that we - * refine these values completely. - */ -function assertPublicBundleOptions(bo: mixed): PublicBundleOptions { - invariant(typeof bo === 'object' && bo != null, 'bundle options must be an object'); - invariant(bo.dev === undefined || typeof bo.dev === 'boolean', 'bundle options field `dev` must be a boolean'); - const {entryFile} = bo; - invariant(typeof entryFile === 'string', 'bundle options must contain a string field `entryFile`'); - invariant(bo.generateSourceMaps === undefined || typeof bo.generateSourceMaps === 'boolean', 'bundle options field `generateSourceMaps` must be a boolean'); - invariant(bo.inlineSourceMap === undefined || typeof bo.inlineSourceMap === 'boolean', 'bundle options field `inlineSourceMap` must be a boolean'); - invariant(bo.minify === undefined || typeof bo.minify === 'boolean', 'bundle options field `minify` must be a boolean'); - invariant(bo.platform === undefined || typeof bo.platform === 'string', 'bundle options field `platform` must be a string'); - invariant(bo.runModule === undefined || typeof bo.runModule === 'boolean', 'bundle options field `runModule` must be a boolean'); - invariant(bo.sourceMapUrl === undefined || typeof bo.sourceMapUrl === 'string', 'bundle options field `sourceMapUrl` must be a boolean'); - return {entryFile, ...bo}; -} - -exports.buildBundle = function(options: Options, bundleOptions: PublicBundleOptions) { - var server = createNonPersistentServer(options); - const ServerClass = require('./Server'); - return server.buildBundle({ - ...ServerClass.DEFAULT_BUNDLE_OPTIONS, - ...assertPublicBundleOptions(bundleOptions), - }).then(p => { - server.end(); - return p; - }); -}; - -exports.getOrderedDependencyPaths = function(options: Options, depOptions: { - +entryFile: string, - +dev: boolean, - +platform: string, - +minify: boolean, - +generateSourceMaps: boolean, -}) { - var server = createNonPersistentServer(options); - return server.getOrderedDependencyPaths(depOptions) - .then(function(paths) { - server.end(); - return paths; - }); -}; - -function enableDebug() { - // react-packager logs debug messages using the 'debug' npm package, and uses - // the following prefix throughout. - // To enable debugging, we need to set our pattern or append it to any - // existing pre-configured pattern to avoid disabling logging for - // other packages - var debugPattern = 'RNP:*'; - var existingPattern = debug.load(); - if (existingPattern) { - debugPattern += ',' + existingPattern; - } - debug.enable(debugPattern); -} - -function createServer(options: StrictOptions): Server { - // the debug module is configured globally, we need to enable debugging - // *before* requiring any packages that use `debug` for logging - if (options.verbose) { - enableDebug(); - } - - // Some callsites may not be Flowified yet. - invariant(options.reporter != null, 'createServer() requires reporter'); - if (options.transformCache == null) { - options.transformCache = TransformCaching.useTempDir(); - } - const serverOptions = Object.assign({}, options); - delete serverOptions.verbose; - const ServerClass = require('./Server'); - return new ServerClass(serverOptions); -} - -function createNonPersistentServer(options: Options): Server { - const serverOptions = { - // It's unsound to set-up the reporter here, - // but this allows backward compatibility. - reporter: options.reporter == null - ? require('./lib/reporting').nullReporter - : options.reporter, - ...options, - watch: !options.nonPersistent, - }; - return createServer(serverOptions); -} diff --git a/packager/src/integration_tests/__tests__/__snapshots__/basic_bundle-test.js.snap b/packager/src/integration_tests/__tests__/__snapshots__/basic_bundle-test.js.snap deleted file mode 100644 index 7c332196923488..00000000000000 --- a/packager/src/integration_tests/__tests__/__snapshots__/basic_bundle-test.js.snap +++ /dev/null @@ -1,1275 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`basic_bundle bundles package as expected 1`] = ` -"(function(global) { - -global.__DEV__ = false; - -global.__BUNDLE_START_TIME__ = global.nativePerformanceNow ? global.nativePerformanceNow() : Date.now(); -})(typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : this); -(function(global) { - -'use strict'; - -global.require = _require; -global.__d = define; - -var modules = Object.create(null); -if (__DEV__) { - var verboseNamesToModuleIds = Object.create(null); -} - -function define(factory, moduleId, dependencyMap) { - if (moduleId in modules) { - return; - } - modules[moduleId] = { - dependencyMap: dependencyMap, - exports: undefined, - factory: factory, - hasError: false, - isInitialized: false - }; - if (__DEV__) { - modules[moduleId].hot = createHotReloadingObject(); - - var _verboseName = arguments[3]; - if (_verboseName) { - modules[moduleId].verboseName = _verboseName; - verboseNamesToModuleIds[_verboseName] = moduleId; - } - } -} - -function _require(moduleId) { - if (__DEV__ && typeof moduleId === 'string') { - var _verboseName2 = moduleId; - moduleId = verboseNamesToModuleIds[moduleId]; - if (moduleId == null) { - throw new Error('Unknown named module: \\\\'' + _verboseName2 + '\\\\''); - } else { - console.warn('Requiring module \\\\'' + _verboseName2 + '\\\\' by name is only supported for ' + 'debugging purposes and will BREAK IN PRODUCTION!'); - } - } - - var moduleIdReallyIsNumber = moduleId; - var module = modules[moduleIdReallyIsNumber]; - return module && module.isInitialized ? module.exports : guardedLoadModule(moduleIdReallyIsNumber, module); -} - -var inGuard = false; -function guardedLoadModule(moduleId, module) { - if (!inGuard && global.ErrorUtils) { - inGuard = true; - var returnValue = void 0; - try { - returnValue = loadModuleImplementation(moduleId, module); - } catch (e) { - global.ErrorUtils.reportFatalError(e); - } - inGuard = false; - return returnValue; - } else { - return loadModuleImplementation(moduleId, module); - } -} - -function loadModuleImplementation(moduleId, module) { - var nativeRequire = global.nativeRequire; - if (!module && nativeRequire) { - nativeRequire(moduleId); - module = modules[moduleId]; - } - - if (!module) { - throw unknownModuleError(moduleId); - } - - if (module.hasError) { - throw moduleThrewError(moduleId, module.error); - } - - if (__DEV__) { - var Systrace = _require.Systrace; - } - - module.isInitialized = true; - var exports = module.exports = {}; - var _module = module, - factory = _module.factory, - dependencyMap = _module.dependencyMap; - - try { - if (__DEV__) { - Systrace.beginEvent('JS_require_' + (module.verboseName || moduleId)); - } - - var _moduleObject = { exports: exports }; - if (__DEV__ && module.hot) { - _moduleObject.hot = module.hot; - } - - factory(global, _require, _moduleObject, exports, dependencyMap); - - if (!__DEV__) { - module.factory = undefined; - module.dependencyMap = undefined; - } - - if (__DEV__) { - Systrace.endEvent(); - } - return module.exports = _moduleObject.exports; - } catch (e) { - module.hasError = true; - module.error = e; - module.isInitialized = false; - module.exports = undefined; - throw e; - } -} - -function unknownModuleError(id) { - var message = 'Requiring unknown module \\"' + id + '\\".'; - if (__DEV__) { - message += 'If you are sure the module is there, try restarting the packager. ' + 'You may also want to run \`npm install\`, or \`yarn\` (depending on your environment).'; - } - return Error(message); -} - -function moduleThrewError(id, error) { - var displayName = __DEV__ && modules[id] && modules[id].verboseName || id; - return Error('Requiring module \\"' + displayName + '\\", which threw an exception: ' + error); -} - -if (__DEV__) { - _require.Systrace = { beginEvent: function beginEvent() {}, endEvent: function endEvent() {} }; - - var createHotReloadingObject = function createHotReloadingObject() { - var hot = { - acceptCallback: null, - accept: function accept(callback) { - hot.acceptCallback = callback; - } - }; - return hot; - }; - - var acceptAll = function acceptAll(dependentModules, inverseDependencies) { - if (!dependentModules || dependentModules.length === 0) { - return true; - } - - var notAccepted = dependentModules.filter(function (module) { - return !_accept(module, undefined, inverseDependencies); - }); - - var parents = []; - for (var i = 0; i < notAccepted.length; i++) { - if (inverseDependencies[notAccepted[i]].length === 0) { - return false; - } - - parents.push.apply(parents, babelHelpers.toConsumableArray(inverseDependencies[notAccepted[i]])); - } - - return acceptAll(parents, inverseDependencies); - }; - - var _accept = function _accept(id, factory, inverseDependencies) { - var mod = modules[id]; - - if (!mod && factory) { - define(factory, id); - return true; - } - - var hot = mod.hot; - - if (!hot) { - console.warn('Cannot accept module because Hot Module Replacement ' + 'API was not installed.'); - return false; - } - - if (factory) { - mod.factory = factory; - } - mod.hasError = false; - mod.isInitialized = false; - _require(id); - - if (hot.acceptCallback) { - hot.acceptCallback(); - return true; - } else { - if (!inverseDependencies) { - throw new Error('Undefined \`inverseDependencies\`'); - } - - return acceptAll(inverseDependencies[id], inverseDependencies); - } - }; - - global.__accept = _accept; -} -})(typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : this); -(function(global) { - -Object.assign = function (target, sources) { - if (__DEV__) { - if (target == null) { - throw new TypeError('Object.assign target cannot be null or undefined'); - } - if (typeof target !== 'object' && typeof target !== 'function') { - throw new TypeError('In this environment the target of assign MUST be an object.' + 'This error is a performance optimization and not spec compliant.'); - } - } - - for (var nextIndex = 1; nextIndex < arguments.length; nextIndex++) { - var nextSource = arguments[nextIndex]; - if (nextSource == null) { - continue; - } - - if (__DEV__) { - if (typeof nextSource !== 'object' && typeof nextSource !== 'function') { - throw new TypeError('In this environment the sources for assign MUST be an object.' + 'This error is a performance optimization and not spec compliant.'); - } - } - - for (var key in nextSource) { - if (__DEV__) { - var hasOwnProperty = Object.prototype.hasOwnProperty; - if (!hasOwnProperty.call(nextSource, key)) { - throw new TypeError('One of the sources for assign has an enumerable key on the ' + 'prototype chain. Are you trying to assign a prototype property? ' + 'We don\\\\'t allow it, as this is an edge case that we do not support. ' + 'This error is a performance optimization and not spec compliant.'); - } - } - target[key] = nextSource[key]; - } - } - - return target; -}; -})(typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : this); -(function(global) { -var inspect = function () { - - function inspect(obj, opts) { - var ctx = { - seen: [], - stylize: stylizeNoColor - }; - return formatValue(ctx, obj, opts.depth); - } - - function stylizeNoColor(str, styleType) { - return str; - } - - function arrayToHash(array) { - var hash = {}; - - array.forEach(function (val, idx) { - hash[val] = true; - }); - - return hash; - } - - function formatValue(ctx, value, recurseTimes) { - var primitive = formatPrimitive(ctx, value); - if (primitive) { - return primitive; - } - - var keys = Object.keys(value); - var visibleKeys = arrayToHash(keys); - - if (isError(value) && (keys.indexOf('message') >= 0 || keys.indexOf('description') >= 0)) { - return formatError(value); - } - - if (keys.length === 0) { - if (isFunction(value)) { - var name = value.name ? ': ' + value.name : ''; - return ctx.stylize('[Function' + name + ']', 'special'); - } - if (isRegExp(value)) { - return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); - } - if (isDate(value)) { - return ctx.stylize(Date.prototype.toString.call(value), 'date'); - } - if (isError(value)) { - return formatError(value); - } - } - - var base = '', - array = false, - braces = ['{', '}']; - - if (isArray(value)) { - array = true; - braces = ['[', ']']; - } - - if (isFunction(value)) { - var n = value.name ? ': ' + value.name : ''; - base = ' [Function' + n + ']'; - } - - if (isRegExp(value)) { - base = ' ' + RegExp.prototype.toString.call(value); - } - - if (isDate(value)) { - base = ' ' + Date.prototype.toUTCString.call(value); - } - - if (isError(value)) { - base = ' ' + formatError(value); - } - - if (keys.length === 0 && (!array || value.length == 0)) { - return braces[0] + base + braces[1]; - } - - if (recurseTimes < 0) { - if (isRegExp(value)) { - return ctx.stylize(RegExp.prototype.toString.call(value), 'regexp'); - } else { - return ctx.stylize('[Object]', 'special'); - } - } - - ctx.seen.push(value); - - var output; - if (array) { - output = formatArray(ctx, value, recurseTimes, visibleKeys, keys); - } else { - output = keys.map(function (key) { - return formatProperty(ctx, value, recurseTimes, visibleKeys, key, array); - }); - } - - ctx.seen.pop(); - - return reduceToSingleString(output, base, braces); - } - - function formatPrimitive(ctx, value) { - if (isUndefined(value)) return ctx.stylize('undefined', 'undefined'); - if (isString(value)) { - var simple = '\\\\'' + JSON.stringify(value).replace(/^\\"|\\"$/g, '').replace(/'/g, \\"\\\\\\\\'\\").replace(/\\\\\\\\\\"/g, '\\"') + '\\\\''; - return ctx.stylize(simple, 'string'); - } - if (isNumber(value)) return ctx.stylize('' + value, 'number'); - if (isBoolean(value)) return ctx.stylize('' + value, 'boolean'); - - if (isNull(value)) return ctx.stylize('null', 'null'); - } - - function formatError(value) { - return '[' + Error.prototype.toString.call(value) + ']'; - } - - function formatArray(ctx, value, recurseTimes, visibleKeys, keys) { - var output = []; - for (var i = 0, l = value.length; i < l; ++i) { - if (hasOwnProperty(value, String(i))) { - output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, String(i), true)); - } else { - output.push(''); - } - } - keys.forEach(function (key) { - if (!key.match(/^\\\\d+$/)) { - output.push(formatProperty(ctx, value, recurseTimes, visibleKeys, key, true)); - } - }); - return output; - } - - function formatProperty(ctx, value, recurseTimes, visibleKeys, key, array) { - var name, str, desc; - desc = Object.getOwnPropertyDescriptor(value, key) || { value: value[key] }; - if (desc.get) { - if (desc.set) { - str = ctx.stylize('[Getter/Setter]', 'special'); - } else { - str = ctx.stylize('[Getter]', 'special'); - } - } else { - if (desc.set) { - str = ctx.stylize('[Setter]', 'special'); - } - } - if (!hasOwnProperty(visibleKeys, key)) { - name = '[' + key + ']'; - } - if (!str) { - if (ctx.seen.indexOf(desc.value) < 0) { - if (isNull(recurseTimes)) { - str = formatValue(ctx, desc.value, null); - } else { - str = formatValue(ctx, desc.value, recurseTimes - 1); - } - if (str.indexOf('\\\\n') > -1) { - if (array) { - str = str.split('\\\\n').map(function (line) { - return ' ' + line; - }).join('\\\\n').substr(2); - } else { - str = '\\\\n' + str.split('\\\\n').map(function (line) { - return ' ' + line; - }).join('\\\\n'); - } - } - } else { - str = ctx.stylize('[Circular]', 'special'); - } - } - if (isUndefined(name)) { - if (array && key.match(/^\\\\d+$/)) { - return str; - } - name = JSON.stringify('' + key); - if (name.match(/^\\"([a-zA-Z_][a-zA-Z_0-9]*)\\"$/)) { - name = name.substr(1, name.length - 2); - name = ctx.stylize(name, 'name'); - } else { - name = name.replace(/'/g, \\"\\\\\\\\'\\").replace(/\\\\\\\\\\"/g, '\\"').replace(/(^\\"|\\"$)/g, \\"'\\"); - name = ctx.stylize(name, 'string'); - } - } - - return name + ': ' + str; - } - - function reduceToSingleString(output, base, braces) { - var numLinesEst = 0; - var length = output.reduce(function (prev, cur) { - numLinesEst++; - if (cur.indexOf('\\\\n') >= 0) numLinesEst++; - return prev + cur.replace(/\\\\u001b\\\\[\\\\d\\\\d?m/g, '').length + 1; - }, 0); - - if (length > 60) { - return braces[0] + (base === '' ? '' : base + '\\\\n ') + ' ' + output.join(',\\\\n ') + ' ' + braces[1]; - } - - return braces[0] + base + ' ' + output.join(', ') + ' ' + braces[1]; - } - - function isArray(ar) { - return Array.isArray(ar); - } - - function isBoolean(arg) { - return typeof arg === 'boolean'; - } - - function isNull(arg) { - return arg === null; - } - - function isNullOrUndefined(arg) { - return arg == null; - } - - function isNumber(arg) { - return typeof arg === 'number'; - } - - function isString(arg) { - return typeof arg === 'string'; - } - - function isSymbol(arg) { - return typeof arg === 'symbol'; - } - - function isUndefined(arg) { - return arg === void 0; - } - - function isRegExp(re) { - return isObject(re) && objectToString(re) === '[object RegExp]'; - } - - function isObject(arg) { - return typeof arg === 'object' && arg !== null; - } - - function isDate(d) { - return isObject(d) && objectToString(d) === '[object Date]'; - } - - function isError(e) { - return isObject(e) && (objectToString(e) === '[object Error]' || e instanceof Error); - } - - function isFunction(arg) { - return typeof arg === 'function'; - } - - function isPrimitive(arg) { - return arg === null || typeof arg === 'boolean' || typeof arg === 'number' || typeof arg === 'string' || typeof arg === 'symbol' || typeof arg === 'undefined'; - } - - function objectToString(o) { - return Object.prototype.toString.call(o); - } - - function hasOwnProperty(obj, prop) { - return Object.prototype.hasOwnProperty.call(obj, prop); - } - - return inspect; -}(); - -var OBJECT_COLUMN_NAME = '(index)'; -var LOG_LEVELS = { - trace: 0, - info: 1, - warn: 2, - error: 3 -}; -var INSPECTOR_LEVELS = []; -INSPECTOR_LEVELS[LOG_LEVELS.trace] = 'debug'; -INSPECTOR_LEVELS[LOG_LEVELS.info] = 'log'; -INSPECTOR_LEVELS[LOG_LEVELS.warn] = 'warning'; -INSPECTOR_LEVELS[LOG_LEVELS.error] = 'error'; - -var INSPECTOR_FRAMES_TO_SKIP = __DEV__ ? 2 : 1; - -function setupConsole(global) { - if (!global.nativeLoggingHook) { - return; - } - - function getNativeLogFunction(level) { - return function () { - var str = void 0; - if (arguments.length === 1 && typeof arguments[0] === 'string') { - str = arguments[0]; - } else { - str = Array.prototype.map.call(arguments, function (arg) { - return inspect(arg, { depth: 10 }); - }).join(', '); - } - - var logLevel = level; - if (str.slice(0, 9) === 'Warning: ' && logLevel >= LOG_LEVELS.error) { - logLevel = LOG_LEVELS.warn; - } - if (global.__inspectorLog) { - global.__inspectorLog(INSPECTOR_LEVELS[logLevel], str, [].slice.call(arguments), INSPECTOR_FRAMES_TO_SKIP); - } - global.nativeLoggingHook(str, logLevel); - }; - } - - function repeat(element, n) { - return Array.apply(null, Array(n)).map(function () { - return element; - }); - }; - - function consoleTablePolyfill(rows) { - if (!Array.isArray(rows)) { - var data = rows; - rows = []; - for (var key in data) { - if (data.hasOwnProperty(key)) { - var row = data[key]; - row[OBJECT_COLUMN_NAME] = key; - rows.push(row); - } - } - } - if (rows.length === 0) { - global.nativeLoggingHook('', LOG_LEVELS.info); - return; - } - - var columns = Object.keys(rows[0]).sort(); - var stringRows = []; - var columnWidths = []; - - columns.forEach(function (k, i) { - columnWidths[i] = k.length; - for (var j = 0; j < rows.length; j++) { - var cellStr = (rows[j][k] || '?').toString(); - stringRows[j] = stringRows[j] || []; - stringRows[j][i] = cellStr; - columnWidths[i] = Math.max(columnWidths[i], cellStr.length); - } - }); - - function joinRow(row, space) { - var cells = row.map(function (cell, i) { - var extraSpaces = repeat(' ', columnWidths[i] - cell.length).join(''); - return cell + extraSpaces; - }); - space = space || ' '; - return cells.join(space + '|' + space); - }; - - var separators = columnWidths.map(function (columnWidth) { - return repeat('-', columnWidth).join(''); - }); - var separatorRow = joinRow(separators, '-'); - var header = joinRow(columns); - var table = [header, separatorRow]; - - for (var i = 0; i < rows.length; i++) { - table.push(joinRow(stringRows[i])); - } - - global.nativeLoggingHook('\\\\n' + table.join('\\\\n'), LOG_LEVELS.info); - } - - var originalConsole = global.console; - var descriptor = Object.getOwnPropertyDescriptor(global, 'console'); - if (descriptor) { - Object.defineProperty(global, 'originalConsole', descriptor); - } - - global.console = { - error: getNativeLogFunction(LOG_LEVELS.error), - info: getNativeLogFunction(LOG_LEVELS.info), - log: getNativeLogFunction(LOG_LEVELS.info), - warn: getNativeLogFunction(LOG_LEVELS.warn), - trace: getNativeLogFunction(LOG_LEVELS.trace), - debug: getNativeLogFunction(LOG_LEVELS.trace), - table: consoleTablePolyfill - }; - - if (__DEV__ && originalConsole) { - Object.keys(console).forEach(function (methodName) { - var reactNativeMethod = console[methodName]; - if (originalConsole[methodName]) { - console[methodName] = function () { - originalConsole[methodName].apply(originalConsole, arguments); - reactNativeMethod.apply(console, arguments); - }; - } - }); - } -} - -if (typeof module !== 'undefined') { - module.exports = setupConsole; -} else { - setupConsole(global); -} -})(typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : this); -(function(global) { - -var _inGuard = 0; - -var _globalHandler = function onError(e) { - throw e; -}; - -var ErrorUtils = { - setGlobalHandler: function setGlobalHandler(fun) { - _globalHandler = fun; - }, - getGlobalHandler: function getGlobalHandler() { - return _globalHandler; - }, - reportError: function reportError(error) { - _globalHandler && _globalHandler(error); - }, - reportFatalError: function reportFatalError(error) { - _globalHandler && _globalHandler(error, true); - }, - applyWithGuard: function applyWithGuard(fun, context, args) { - try { - _inGuard++; - return fun.apply(context, args); - } catch (e) { - ErrorUtils.reportError(e); - } finally { - _inGuard--; - } - return null; - }, - applyWithGuardIfNeeded: function applyWithGuardIfNeeded(fun, context, args) { - if (ErrorUtils.inGuard()) { - return fun.apply(context, args); - } else { - ErrorUtils.applyWithGuard(fun, context, args); - } - return null; - }, - inGuard: function inGuard() { - return _inGuard; - }, - guard: function guard(fun, name, context) { - if (typeof fun !== 'function') { - console.warn('A function must be passed to ErrorUtils.guard, got ', fun); - return null; - } - name = name || fun.name || ''; - function guarded() { - return ErrorUtils.applyWithGuard(fun, context || this, arguments, null, name); - } - - return guarded; - } -}; - -global.ErrorUtils = ErrorUtils; -})(typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : this); -(function(global) { - -if (Number.EPSILON === undefined) { - Object.defineProperty(Number, 'EPSILON', { - value: Math.pow(2, -52) - }); -} -if (Number.MAX_SAFE_INTEGER === undefined) { - Object.defineProperty(Number, 'MAX_SAFE_INTEGER', { - value: Math.pow(2, 53) - 1 - }); -} -if (Number.MIN_SAFE_INTEGER === undefined) { - Object.defineProperty(Number, 'MIN_SAFE_INTEGER', { - value: -(Math.pow(2, 53) - 1) - }); -} -if (!Number.isNaN) { - var globalIsNaN = global.isNaN; - Object.defineProperty(Number, 'isNaN', { - configurable: true, - enumerable: false, - value: function isNaN(value) { - return typeof value === 'number' && globalIsNaN(value); - }, - writable: true - }); -} -})(typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : this); -(function(global) { - -if (!String.prototype.startsWith) { - String.prototype.startsWith = function (search) { - 'use strict'; - - if (this == null) { - throw TypeError(); - } - var string = String(this); - var pos = arguments.length > 1 ? Number(arguments[1]) || 0 : 0; - var start = Math.min(Math.max(pos, 0), string.length); - return string.indexOf(String(search), pos) === start; - }; -} - -if (!String.prototype.endsWith) { - String.prototype.endsWith = function (search) { - 'use strict'; - - if (this == null) { - throw TypeError(); - } - var string = String(this); - var stringLength = string.length; - var searchString = String(search); - var pos = arguments.length > 1 ? Number(arguments[1]) || 0 : stringLength; - var end = Math.min(Math.max(pos, 0), stringLength); - var start = end - searchString.length; - if (start < 0) { - return false; - } - return string.lastIndexOf(searchString, start) === start; - }; -} - -if (!String.prototype.repeat) { - String.prototype.repeat = function (count) { - 'use strict'; - - if (this == null) { - throw TypeError(); - } - var string = String(this); - count = Number(count) || 0; - if (count < 0 || count === Infinity) { - throw RangeError(); - } - if (count === 1) { - return string; - } - var result = ''; - while (count) { - if (count & 1) { - result += string; - } - if (count >>= 1) { - string += string; - } - } - return result; - }; -} - -if (!String.prototype.includes) { - String.prototype.includes = function (search, start) { - 'use strict'; - - if (typeof start !== 'number') { - start = 0; - } - - if (start + search.length > this.length) { - return false; - } else { - return this.indexOf(search, start) !== -1; - } - }; -} -})(typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : this); -(function(global) { -function findIndex(predicate, context) { - if (this == null) { - throw new TypeError('Array.prototype.findIndex called on null or undefined'); - } - if (typeof predicate !== 'function') { - throw new TypeError('predicate must be a function'); - } - var list = Object(this); - var length = list.length >>> 0; - for (var i = 0; i < length; i++) { - if (predicate.call(context, list[i], i, list)) { - return i; - } - } - return -1; -} - -if (!Array.prototype.findIndex) { - Object.defineProperty(Array.prototype, 'findIndex', { - enumerable: false, - writable: true, - configurable: true, - value: findIndex - }); -} - -if (!Array.prototype.find) { - Object.defineProperty(Array.prototype, 'find', { - enumerable: false, - writable: true, - configurable: true, - value: function value(predicate, context) { - if (this == null) { - throw new TypeError('Array.prototype.find called on null or undefined'); - } - var index = findIndex.call(this, predicate, context); - return index === -1 ? undefined : this[index]; - } - }); -} - -if (!Array.prototype.includes) { - Object.defineProperty(Array.prototype, 'includes', { - enumerable: false, - writable: true, - configurable: true, - value: function value(searchElement) { - var O = Object(this); - var len = parseInt(O.length) || 0; - if (len === 0) { - return false; - } - var n = parseInt(arguments[1]) || 0; - var k; - if (n >= 0) { - k = n; - } else { - k = len + n; - if (k < 0) { - k = 0; - } - } - var currentElement; - while (k < len) { - currentElement = O[k]; - if (searchElement === currentElement || searchElement !== searchElement && currentElement !== currentElement) { - return true; - } - k++; - } - return false; - } - }); -} -})(typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : this); -(function(global) { -if (!Array.from) { - Array.from = function (arrayLike) { - if (arrayLike == null) { - throw new TypeError('Object is null or undefined'); - } - - var mapFn = arguments[1]; - var thisArg = arguments[2]; - - var C = this; - var items = Object(arrayLike); - var symbolIterator = typeof Symbol === 'function' ? typeof Symbol === 'function' ? Symbol.iterator : '@@iterator' : '@@iterator'; - var mapping = typeof mapFn === 'function'; - var usingIterator = typeof items[symbolIterator] === 'function'; - var key = 0; - var ret; - var value; - - if (usingIterator) { - ret = typeof C === 'function' ? new C() : []; - var it = items[symbolIterator](); - var next; - - while (!(next = it.next()).done) { - value = next.value; - - if (mapping) { - value = mapFn.call(thisArg, value, key); - } - - ret[key] = value; - key += 1; - } - - ret.length = key; - return ret; - } - - var len = items.length; - if (isNaN(len) || len < 0) { - len = 0; - } - - ret = typeof C === 'function' ? new C(len) : new Array(len); - - while (key < len) { - value = items[key]; - - if (mapping) { - value = mapFn.call(thisArg, value, key); - } - - ret[key] = value; - - key += 1; - } - - ret.length = key; - return ret; - }; -} -})(typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : this); -(function(global) { - -(function () { - 'use strict'; - - var hasOwnProperty = Object.prototype.hasOwnProperty; - - if (typeof Object.entries !== 'function') { - Object.entries = function (object) { - if (object == null) { - throw new TypeError('Object.entries called on non-object'); - } - - var entries = []; - for (var key in object) { - if (hasOwnProperty.call(object, key)) { - entries.push([key, object[key]]); - } - } - return entries; - }; - } - - if (typeof Object.values !== 'function') { - Object.values = function (object) { - if (object == null) { - throw new TypeError('Object.values called on non-object'); - } - - var values = []; - for (var key in object) { - if (hasOwnProperty.call(object, key)) { - values.push(object[key]); - } - } - return values; - }; - } -})(); -})(typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : this); -(function(global) { - -var babelHelpers = global.babelHelpers = {}; - -babelHelpers.typeof = typeof Symbol === \\"function\\" && typeof (typeof Symbol === \\"function\\" ? Symbol.iterator : \\"@@iterator\\") === \\"symbol\\" ? function (obj) { - return typeof obj; -} : function (obj) { - return obj && typeof Symbol === \\"function\\" && obj.constructor === Symbol && obj !== (typeof Symbol === \\"function\\" ? Symbol.prototype : \\"@@prototype\\") ? \\"symbol\\" : typeof obj; -}; - -babelHelpers.createRawReactElement = function () { - var REACT_ELEMENT_TYPE = typeof Symbol === \\"function\\" && (typeof Symbol === \\"function\\" ? Symbol.for : \\"@@for\\") && (typeof Symbol === \\"function\\" ? Symbol.for : \\"@@for\\")(\\"react.element\\") || 0xeac7; - return function createRawReactElement(type, key, props) { - return { - $$typeof: REACT_ELEMENT_TYPE, - type: type, - key: key, - ref: null, - props: props, - _owner: null - }; - }; -}(); - -babelHelpers.classCallCheck = function (instance, Constructor) { - if (!(instance instanceof Constructor)) { - throw new TypeError(\\"Cannot call a class as a function\\"); - } -}; - -babelHelpers.createClass = function () { - function defineProperties(target, props) { - for (var i = 0; i < props.length; i++) { - var descriptor = props[i]; - descriptor.enumerable = descriptor.enumerable || false; - descriptor.configurable = true; - if (\\"value\\" in descriptor) descriptor.writable = true; - Object.defineProperty(target, descriptor.key, descriptor); - } - } - - return function (Constructor, protoProps, staticProps) { - if (protoProps) defineProperties(Constructor.prototype, protoProps); - if (staticProps) defineProperties(Constructor, staticProps); - return Constructor; - }; -}(); - -babelHelpers.defineEnumerableProperties = function (obj, descs) { - for (var key in descs) { - var desc = descs[key]; - desc.configurable = desc.enumerable = true; - if ('value' in desc) desc.writable = true; - Object.defineProperty(obj, key, desc); - } - return obj; -}; - -babelHelpers.defineProperty = function (obj, key, value) { - if (key in obj) { - Object.defineProperty(obj, key, { - value: value, - enumerable: true, - configurable: true, - writable: true - }); - } else { - obj[key] = value; - } - - return obj; -}; - -babelHelpers._extends = babelHelpers.extends = Object.assign || function (target) { - for (var i = 1; i < arguments.length; i++) { - var source = arguments[i]; - - for (var key in source) { - if (Object.prototype.hasOwnProperty.call(source, key)) { - target[key] = source[key]; - } - } - } - - return target; -}; - -babelHelpers.get = function get(object, property, receiver) { - if (object === null) object = Function.prototype; - var desc = Object.getOwnPropertyDescriptor(object, property); - - if (desc === undefined) { - var parent = Object.getPrototypeOf(object); - - if (parent === null) { - return undefined; - } else { - return get(parent, property, receiver); - } - } else if (\\"value\\" in desc) { - return desc.value; - } else { - var getter = desc.get; - - if (getter === undefined) { - return undefined; - } - - return getter.call(receiver); - } -}; - -babelHelpers.inherits = function (subClass, superClass) { - if (typeof superClass !== \\"function\\" && superClass !== null) { - throw new TypeError(\\"Super expression must either be null or a function, not \\" + typeof superClass); - } - - subClass.prototype = Object.create(superClass && superClass.prototype, { - constructor: { - value: subClass, - enumerable: false, - writable: true, - configurable: true - } - }); - if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; -}; - -babelHelpers.interopRequireDefault = function (obj) { - return obj && obj.__esModule ? obj : { - default: obj - }; -}; - -babelHelpers.interopRequireWildcard = function (obj) { - if (obj && obj.__esModule) { - return obj; - } else { - var newObj = {}; - - if (obj != null) { - for (var key in obj) { - if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; - } - } - - newObj.default = obj; - return newObj; - } -}; - -babelHelpers.objectWithoutProperties = function (obj, keys) { - var target = {}; - - for (var i in obj) { - if (keys.indexOf(i) >= 0) continue; - if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; - target[i] = obj[i]; - } - - return target; -}; - -babelHelpers.possibleConstructorReturn = function (self, call) { - if (!self) { - throw new ReferenceError(\\"this hasn't been initialised - super() hasn't been called\\"); - } - - return call && (typeof call === \\"object\\" || typeof call === \\"function\\") ? call : self; -}; - -babelHelpers.slicedToArray = function () { - function sliceIterator(arr, i) { - var _arr = []; - var _n = true; - var _d = false; - var _e = undefined; - - try { - for (var _i = arr[typeof Symbol === \\"function\\" ? Symbol.iterator : \\"@@iterator\\"](), _s; !(_n = (_s = _i.next()).done); _n = true) { - _arr.push(_s.value); - - if (i && _arr.length === i) break; - } - } catch (err) { - _d = true; - _e = err; - } finally { - try { - if (!_n && _i[\\"return\\"]) _i[\\"return\\"](); - } finally { - if (_d) throw _e; - } - } - - return _arr; - } - - return function (arr, i) { - if (Array.isArray(arr)) { - return arr; - } else if ((typeof Symbol === \\"function\\" ? Symbol.iterator : \\"@@iterator\\") in Object(arr)) { - return sliceIterator(arr, i); - } else { - throw new TypeError(\\"Invalid attempt to destructure non-iterable instance\\"); - } - }; -}(); - -babelHelpers.taggedTemplateLiteral = function (strings, raw) { - return Object.freeze(Object.defineProperties(strings, { - raw: { - value: Object.freeze(raw) - } - })); -}; - -babelHelpers.toArray = function (arr) { - return Array.isArray(arr) ? arr : Array.from(arr); -}; - -babelHelpers.toConsumableArray = function (arr) { - if (Array.isArray(arr)) { - for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { - arr2[i] = arr[i]; - }return arr2; - } else { - return Array.from(arr); - } -}; -})(typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : this); -__d(/* /TestBundle.js */function(global, require, module, exports) { - -'use strict'; - -module.exports = { Foo: require(12 ), Bar: require(15 ) }; // 15 = ./Bar // 12 = ./Foo -}, 0); -__d(/* /Foo.js */function(global, require, module, exports) { - -'use strict'; - -module.exports = { type: 'foo', asset: require(13 ) }; // 13 = ./test.png -}, 12); -__d(/* /test.png */function(global, require, module, exports) {module.exports=require(14 ).registerAsset({\\"__packager_asset\\":true,\\"httpServerLocation\\":\\"/assets\\",\\"width\\":8,\\"height\\":8,\\"scales\\":[1],\\"hash\\":\\"77d45c1f7fa73c0f6c444a830dc42f67\\",\\"name\\":\\"test\\",\\"type\\":\\"png\\"}); // 14 = react-native/Libraries/Image/AssetRegistry -}, 13); -__d(/* /node_modules/react-native/Libraries/Image/AssetRegistry.js */function(global, require, module, exports) { - -'use strict'; - -module.export = {}; -}, 14); -__d(/* /Bar.js */function(global, require, module, exports) { - -'use strict'; - -module.exports = { type: 'bar', foo: require(12 ).type }; // 12 = ./Foo -}, 15); -;require(0);" -`; diff --git a/packager/src/integration_tests/__tests__/basic_bundle-test.js b/packager/src/integration_tests/__tests__/basic_bundle-test.js deleted file mode 100644 index fce916d082cf9a..00000000000000 --- a/packager/src/integration_tests/__tests__/basic_bundle-test.js +++ /dev/null @@ -1,89 +0,0 @@ -/** - * 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. - * - * @format - */ - -'use strict'; - -jest.disableAutomock(); -jest.useRealTimers(); - -jest.dontMock('fs'); -jest.dontMock('graceful-fs'); - -/** - * Don't waste time creating a worker-farm from jest-haste-map, use the function - * directly instead. - */ -jest.mock('worker-farm', () => { - function workerFarm(opts, workerPath, methodNames) { - return require(workerPath); - } - workerFarm.end = () => {}; - return workerFarm; -}); - -/** - * We replace the farm by a simple require, so that the worker sources are - * transformed and managed by jest. - */ -jest.mock('../../worker-farm', () => { - let ended = false; - function workerFarm(opts, workerPath, methodNames) { - const {Readable} = require('stream'); - const methods = {}; - const worker = require(workerPath); - methodNames.forEach(name => { - methods[name] = function() { - if (ended) { - throw new Error('worker farm was ended'); - } - return worker[name].apply(null, arguments); - }; - }); - return { - stdout: new Readable({read() {}}), - stderr: new Readable({read() {}}), - methods, - }; - } - workerFarm.end = () => { - ended = true; - }; - return workerFarm; -}); - -const Packager = require('../..'); - -const path = require('path'); - -jasmine.DEFAULT_TIMEOUT_INTERVAL = 30 * 1000; - -const INPUT_PATH = path.resolve(__dirname, '../basic_bundle'); -const POLYFILLS_PATH = path.resolve(__dirname, '../../Resolver/polyfills'); - -describe('basic_bundle', () => { - it('bundles package as expected', async () => { - const bundle = await Packager.buildBundle( - { - projectRoots: [INPUT_PATH, POLYFILLS_PATH], - transformCache: Packager.TransformCaching.none(), - transformModulePath: require.resolve('../../transformer'), - nonPersistent: true, - }, - { - dev: false, - entryFile: path.join(INPUT_PATH, 'TestBundle.js'), - platform: 'ios', - }, - ); - const absPathRe = new RegExp(INPUT_PATH, 'g'); - expect(bundle.getSource().replace(absPathRe, '')).toMatchSnapshot(); - }); -}); diff --git a/packager/src/integration_tests/basic_bundle/Bar.js b/packager/src/integration_tests/basic_bundle/Bar.js deleted file mode 100644 index e8df83a32f77c5..00000000000000 --- a/packager/src/integration_tests/basic_bundle/Bar.js +++ /dev/null @@ -1,16 +0,0 @@ -/** - * 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. - * - * @format - */ - -'use strict'; - -const Foo = require('./Foo'); - -module.exports = {type: 'bar', foo: Foo.type}; diff --git a/packager/src/integration_tests/basic_bundle/Foo.js b/packager/src/integration_tests/basic_bundle/Foo.js deleted file mode 100644 index a4e685ec48ad0b..00000000000000 --- a/packager/src/integration_tests/basic_bundle/Foo.js +++ /dev/null @@ -1,16 +0,0 @@ -/** - * 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. - * - * @format - */ - -'use strict'; - -const asset = require('./test.png'); - -module.exports = {type: 'foo', asset}; diff --git a/packager/src/integration_tests/basic_bundle/TestBundle.js b/packager/src/integration_tests/basic_bundle/TestBundle.js deleted file mode 100644 index a6cbce5aa1be7f..00000000000000 --- a/packager/src/integration_tests/basic_bundle/TestBundle.js +++ /dev/null @@ -1,17 +0,0 @@ -/** - * 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. - * - * @format - */ - -'use strict'; - -const Bar = require('./Bar'); -const Foo = require('./Foo'); - -module.exports = {Foo, Bar}; diff --git a/packager/src/integration_tests/basic_bundle/test.png b/packager/src/integration_tests/basic_bundle/test.png deleted file mode 100644 index 6f7dbca20ffca8..00000000000000 Binary files a/packager/src/integration_tests/basic_bundle/test.png and /dev/null differ diff --git a/packager/src/lib/BatchProcessor.js b/packager/src/lib/BatchProcessor.js deleted file mode 100644 index c26b36d518aea5..00000000000000 --- a/packager/src/lib/BatchProcessor.js +++ /dev/null @@ -1,110 +0,0 @@ -/** - * Copyright (c) 2016-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. - * - * @flow - */ - -'use strict'; - -const invariant = require('fbjs/lib/invariant'); - -type ProcessBatch = (batch: Array) => Promise>; - -type BatchProcessorOptions = { - maximumDelayMs: number, - maximumItems: number, - concurrency: number, -}; - -type QueueItem = { - item: TItem, - reject: (error: mixed) => mixed, - resolve: (result: TResult) => mixed, -}; - -/** - * We batch items together trying to minimize their processing, for example as - * network queries. For that we wait a small moment before processing a batch. - * We limit also the number of items we try to process in a single batch so that - * if we have many items pending in a short amount of time, we can start - * processing right away. - */ -class BatchProcessor { - - _currentProcessCount: number; - _options: BatchProcessorOptions; - _processBatch: ProcessBatch; - _queue: Array>; - _timeoutHandle: ?number; - - constructor(options: BatchProcessorOptions, processBatch: ProcessBatch) { - this._options = options; - this._processBatch = processBatch; - this._queue = []; - this._timeoutHandle = null; - this._currentProcessCount = 0; - (this: any)._processQueue = this._processQueue.bind(this); - } - - _onBatchFinished() { - this._currentProcessCount--; - this._processQueueOnceReady(); - } - - _onBatchResults(jobs: Array>, results: Array) { - invariant(results.length === jobs.length, 'Not enough results returned.'); - for (let i = 0; i < jobs.length; ++i) { - jobs[i].resolve(results[i]); - } - this._onBatchFinished(); - } - - _onBatchError(jobs: Array>, error: mixed) { - for (let i = 0; i < jobs.length; ++i) { - jobs[i].reject(error); - } - this._onBatchFinished(); - } - - _processQueue() { - this._timeoutHandle = null; - const {concurrency} = this._options; - while (this._queue.length > 0 && this._currentProcessCount < concurrency) { - this._currentProcessCount++; - const jobs = this._queue.splice(0, this._options.maximumItems); - this._processBatch(jobs.map(job => job.item)).then( - this._onBatchResults.bind(this, jobs), - this._onBatchError.bind(this, jobs), - ); - } - } - - _processQueueOnceReady() { - if (this._queue.length >= this._options.maximumItems) { - clearTimeout(this._timeoutHandle); - process.nextTick(this._processQueue); - return; - } - if (this._timeoutHandle == null) { - this._timeoutHandle = setTimeout( - this._processQueue, - this._options.maximumDelayMs, - ); - } - } - - queue(item: TItem): Promise { - return new Promise((resolve, reject) => { - this._queue.push({item, resolve, reject}); - this._processQueueOnceReady(); - }); - } - -} - -module.exports = BatchProcessor; diff --git a/packager/src/lib/GlobalTransformCache.js b/packager/src/lib/GlobalTransformCache.js deleted file mode 100644 index e8798dd140beda..00000000000000 --- a/packager/src/lib/GlobalTransformCache.js +++ /dev/null @@ -1,430 +0,0 @@ -/** - * Copyright (c) 2016-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. - * - * @flow - */ - -'use strict'; - -/* global Buffer: true */ - -const BatchProcessor = require('./BatchProcessor'); -const FetchError = require('node-fetch/lib/fetch-error'); - -const crypto = require('crypto'); -const fetch = require('node-fetch'); -const jsonStableStringify = require('json-stable-stringify'); -const path = require('path'); -const throat = require('throat'); - -import type { - Options as TransformWorkerOptions, - TransformOptionsStrict, -} from '../JSTransformer/worker'; -import type {LocalPath} from '../node-haste/lib/toLocalPath'; -import type {CachedResult, GetTransformCacheKey} from './TransformCaching'; - -/** - * The API that a global transform cache must comply with. To implement a - * custom cache, implement this interface and pass it as argument to the - * application's top-level `Server` class. - */ -export type GlobalTransformCache = { - /** - * Synchronously determine if it is worth trying to fetch a result from the - * cache. This can be used, for instance, to exclude sets of options we know - * will never be cached. - */ - shouldFetch(props: FetchProps): boolean, - - /** - * Try to fetch a result. It doesn't actually need to fetch from a server, - * the global cache could be instantiated locally for example. - */ - fetch(props: FetchProps): Promise, - - /** - * Try to store a result, without waiting for the success or failure of the - * operation. Consequently, the actual storage operation could be done at a - * much later point if desired. It is recommended to actually have this - * function be a no-op in production, and only do the storage operation from - * a script running on your Continuous Integration platform. - */ - store(props: FetchProps, result: CachedResult): void, -}; - -type FetchResultURIs = (keys: Array) => Promise>; -type FetchResultFromURI = (uri: string) => Promise; -type StoreResults = (resultsByKey: Map) => Promise; - -export type FetchProps = { - +localPath: LocalPath, - +sourceCode: string, - +getTransformCacheKey: GetTransformCacheKey, - +transformOptions: TransformWorkerOptions, -}; - -type URI = string; - -/** - * We aggregate the requests to do a single request for many keys. It also - * ensures we do a single request at a time to avoid pressuring the I/O. - */ -class KeyURIFetcher { - - _batchProcessor: BatchProcessor; - _fetchResultURIs: FetchResultURIs; - - /** - * When a batch request fails for some reason, we process the error locally - * and we proceed as if there were no result for these keys instead. That way - * a build will not fail just because of the cache. - */ - async _processKeys(keys: Array): Promise> { - const URIsByKey = await this._fetchResultURIs(keys); - return keys.map(key => URIsByKey.get(key)); - } - - async fetch(key: string): Promise { - return await this._batchProcessor.queue(key); - } - - constructor(fetchResultURIs: FetchResultURIs) { - this._fetchResultURIs = fetchResultURIs; - this._batchProcessor = new BatchProcessor({ - maximumDelayMs: 10, - maximumItems: 500, - concurrency: 2, - }, this._processKeys.bind(this)); - } - -} - -type KeyedResult = {key: string, result: CachedResult}; - -class KeyResultStore { - - _storeResults: StoreResults; - _batchProcessor: BatchProcessor; - - async _processResults(keyResults: Array): Promise> { - const resultsByKey = new Map(keyResults.map(pair => [pair.key, pair.result])); - await this._storeResults(resultsByKey); - return new Array(keyResults.length); - } - - store(key: string, result: CachedResult) { - this._batchProcessor.queue({key, result}); - } - - constructor(storeResults: StoreResults) { - this._storeResults = storeResults; - this._batchProcessor = new BatchProcessor({ - maximumDelayMs: 1000, - maximumItems: 100, - concurrency: 10, - }, this._processResults.bind(this)); - } - -} - -export type TransformProfile = {+dev: boolean, +minify: boolean, +platform: ?string}; - -function profileKey({dev, minify, platform}: TransformProfile): string { - return jsonStableStringify({dev, minify, platform}); -} - -/** - * We avoid doing any request to the server if we know the server is not - * going to have any key at all for a particular set of transform options. - */ -class TransformProfileSet { - _profileKeys: Set; - constructor(profiles: Iterable) { - this._profileKeys = new Set(); - for (const profile of profiles) { - this._profileKeys.add(profileKey(profile)); - } - } - has(profile: TransformProfile): boolean { - return this._profileKeys.has(profileKey(profile)); - } -} - -type FetchFailedDetails = - {+type: 'unhandled_http_status', +statusCode: number} | {+type: 'unspecified'}; - -class FetchFailedError extends Error { - /** Separate object for details allows us to have a type union. */ - +details: FetchFailedDetails; - - constructor(message: string, details: FetchFailedDetails) { - super(); - this.message = message; - (this: any).details = details; - } -} - -/** - * For some reason the result stored by the server for a key might mismatch what - * we expect a result to be. So we need to verify carefully the data. - */ -function validateCachedResult(cachedResult: mixed): ?CachedResult { - if ( - cachedResult != null && - typeof cachedResult === 'object' && - typeof cachedResult.code === 'string' && - Array.isArray(cachedResult.dependencies) && - cachedResult.dependencies.every(dep => typeof dep === 'string') && - Array.isArray(cachedResult.dependencyOffsets) && - cachedResult.dependencyOffsets.every(offset => typeof offset === 'number') - ) { - return (cachedResult: any); - } - return null; -} - -class URIBasedGlobalTransformCache { - - _fetcher: KeyURIFetcher; - _fetchResultFromURI: FetchResultFromURI; - _profileSet: TransformProfileSet; - _optionsHasher: OptionsHasher; - _store: ?KeyResultStore; - - static FetchFailedError; - - /** - * For using the global cache one needs to have some kind of central key-value - * store that gets prefilled using keyOf() and the transformed results. The - * fetching function should provide a mapping of keys to URIs. The files - * referred by these URIs contains the transform results. Using URIs instead - * of returning the content directly allows for independent and parallel - * fetching of each result, that may be arbitrarily large JSON blobs. - */ - constructor(props: { - fetchResultFromURI: FetchResultFromURI, - fetchResultURIs: FetchResultURIs, - profiles: Iterable, - rootPath: string, - storeResults: StoreResults | null, - }) { - this._fetcher = new KeyURIFetcher(props.fetchResultURIs); - this._profileSet = new TransformProfileSet(props.profiles); - this._fetchResultFromURI = props.fetchResultFromURI; - this._optionsHasher = new OptionsHasher(props.rootPath); - if (props.storeResults != null) { - this._store = new KeyResultStore(props.storeResults); - } - } - - /** - * Return a key for identifying uniquely a source file. - */ - keyOf(props: FetchProps) { - const hash = crypto.createHash('sha1'); - const {sourceCode, localPath, transformOptions} = props; - hash.update(this._optionsHasher.getTransformWorkerOptionsDigest(transformOptions)); - const cacheKey = props.getTransformCacheKey(transformOptions); - hash.update(JSON.stringify(cacheKey)); - hash.update(JSON.stringify(localPath)); - hash.update(crypto.createHash('sha1').update(sourceCode).digest('hex')); - const digest = hash.digest('hex'); - return `${digest}-${path.basename(localPath)}`; - } - - /** - * We may want to improve that logic to return a stream instead of the whole - * blob of transformed results. However the results are generally only a few - * megabytes each. - */ - static async _fetchResultFromURI(uri: string): Promise { - const response = await fetch(uri, {method: 'GET', timeout: 8000}); - if (response.status !== 200) { - const msg = `Unexpected HTTP status: ${response.status} ${response.statusText} `; - throw new FetchFailedError(msg, { - type: 'unhandled_http_status', - statusCode: response.status, - }); - } - const unvalidatedResult = await response.json(); - const result = validateCachedResult(unvalidatedResult); - if (result == null) { - throw new FetchFailedError('Server returned invalid result.', {type: 'unspecified'}); - } - return result; - } - - /** - * It happens from time to time that a fetch fails, we want to try these again - * a second time if we expect them to be transient. We might even consider - * waiting a little time before retring if experience shows it's useful. - */ - static _fetchResultFromURIWithRetry(uri: string): Promise { - return URIBasedGlobalTransformCache._fetchResultFromURI(uri).catch(error => { - if (!URIBasedGlobalTransformCache.shouldRetryAfterThatError(error)) { - throw error; - } - return this._fetchResultFromURI(uri); - }); - } - - /** - * The exposed version uses throat() to limit concurrency, as making too many parallel requests - * is more likely to trigger server-side throttling and cause timeouts. - */ - static fetchResultFromURI: (uri: string) => Promise; - - /** - * We want to retry timeouts as they're likely temporary. We retry 503 - * (Service Unavailable) and 502 (Bad Gateway) because they may be caused by a - * some rogue server, or because of throttling. - * - * There may be other types of error we'd want to retry for, but these are - * the ones we experienced the most in practice. - */ - static shouldRetryAfterThatError(error: Error): boolean { - return ( - error instanceof FetchError && error.type === 'request-timeout' || ( - error instanceof FetchFailedError && - error.details.type === 'unhandled_http_status' && - (error.details.statusCode === 503 || error.details.statusCode === 502) - ) - ); - } - - shouldFetch(props: FetchProps): boolean { - return this._profileSet.has(props.transformOptions); - } - - /** - * This may return `null` if either the cache doesn't have a value for that - * key yet, or an error happened, processed separately. - */ - async fetch(props: FetchProps): Promise { - const uri = await this._fetcher.fetch(this.keyOf(props)); - if (uri == null) { - return null; - } - return await this._fetchResultFromURI(uri); - } - - store(props: FetchProps, result: CachedResult) { - if (this._store != null) { - this._store.store(this.keyOf(props), result); - } - } - -} - -URIBasedGlobalTransformCache.fetchResultFromURI = - throat(500, URIBasedGlobalTransformCache._fetchResultFromURIWithRetry); - -class OptionsHasher { - _rootPath: string; - _cache: WeakMap; - - constructor(rootPath: string) { - this._rootPath = rootPath; - this._cache = new WeakMap(); - } - - getTransformWorkerOptionsDigest(options: TransformWorkerOptions): string { - const digest = this._cache.get(options); - if (digest != null) { - return digest; - } - const hash = crypto.createHash('sha1'); - this.hashTransformWorkerOptions(hash, options); - const newDigest = hash.digest('hex'); - this._cache.set(options, newDigest); - return newDigest; - } - - /** - * This function is extra-conservative with how it hashes the transform - * options. In particular: - * - * * we need to hash paths as local paths, i.e. relative to the root, not - * the absolute paths, otherwise everyone would have a different cache, - * defeating the purpose of global cache; - * * we need to reject any additional field we do not know of, because - * they could contain absolute path, and we absolutely want to process - * these. - * - * Theorically, Flow could help us prevent any other field from being here by - * using *exact* object type. In practice, the transform options are a mix of - * many different fields including the optional Babel fields, and some serious - * cleanup will be necessary to enable rock-solid typing. - */ - hashTransformWorkerOptions(hash: crypto$Hash, options: TransformWorkerOptions): crypto$Hash { - const {dev, minify, platform, transform, ...unknowns} = options; - const unknownKeys = Object.keys(unknowns); - if (unknownKeys.length > 0) { - const message = `these worker option fields are unknown: ${JSON.stringify(unknownKeys)}`; - throw new CannotHashOptionsError(message); - } - // eslint-disable-next-line no-undef, no-bitwise - hash.update(new Buffer([+dev | +minify << 1])); - hash.update(JSON.stringify(platform)); - return this.hashTransformOptions(hash, transform); - } - - /** - * The transform options contain absolute paths. This can contain, for - * example, the username if someone works their home directory (very likely). - * We get rid of this local data for the global cache, otherwise nobody would - * share the same cache keys. The project roots should not be needed as part - * of the cache key as they should not affect the transformation of a single - * particular file. - */ - hashTransformOptions(hash: crypto$Hash, options: TransformOptionsStrict): crypto$Hash { - const { - generateSourceMaps, dev, hot, inlineRequires, platform, projectRoot, - ...unknowns - } = options; - const unknownKeys = Object.keys(unknowns); - if (unknownKeys.length > 0) { - const message = `these transform option fields are unknown: ${JSON.stringify(unknownKeys)}`; - throw new CannotHashOptionsError(message); - } - - hash.update(new Buffer([ - // eslint-disable-next-line no-bitwise - +dev | +generateSourceMaps << 1 | +hot << 2 | +!!inlineRequires << 3, - ])); - hash.update(JSON.stringify(platform)); - let blacklistWithLocalPaths = []; - if (typeof inlineRequires === 'object') { - blacklistWithLocalPaths = this.pathsToLocal(Object.keys(inlineRequires.blacklist)); - } - const localProjectRoot = this.toLocalPath(projectRoot); - const optionTuple = [blacklistWithLocalPaths, localProjectRoot]; - hash.update(JSON.stringify(optionTuple)); - return hash; - } - - pathsToLocal(filePaths: Array): Array { - return filePaths.map(this.toLocalPath, this); - } - - toLocalPath(filePath: string): string { - return path.relative(this._rootPath, filePath); - } -} - -class CannotHashOptionsError extends Error { - constructor(message: string) { - super(); - this.message = message; - } -} - -URIBasedGlobalTransformCache.FetchFailedError = FetchFailedError; - -module.exports = {URIBasedGlobalTransformCache, CannotHashOptionsError}; diff --git a/packager/src/lib/JsonReporter.js b/packager/src/lib/JsonReporter.js deleted file mode 100644 index 05c5549e2b0aec..00000000000000 --- a/packager/src/lib/JsonReporter.js +++ /dev/null @@ -1,43 +0,0 @@ -/** - * 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. - * - * @flow - */ - -'use strict'; - -import {Writable} from 'stream'; - -class JsonReporter { - - _stream: Writable; - - constructor(stream: Writable) { - this._stream = stream; - } - - /** - * There is a special case for errors because they have non-enumerable fields. - * (Perhaps we should switch in favor of plain object?) - */ - update(event: TEvent) { - /* $FlowFixMe: fine to call on `undefined`. */ - if (Object.prototype.toString.call(event.error) === '[object Error]') { - event = {...event}; - event.error = { - ...event.error, - message: event.error.message, - stack: event.error.stack, - }; - } - this._stream.write(JSON.stringify(event) + '\n'); - } - -} - -module.exports = JsonReporter; diff --git a/packager/src/lib/ModuleTransport.js b/packager/src/lib/ModuleTransport.js deleted file mode 100644 index 9e91ef3c7e4b51..00000000000000 --- a/packager/src/lib/ModuleTransport.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * 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. - * - * @flow - */ - -'use strict'; - -import type {RawMapping} from '../Bundler/source-map'; -import type Module from '../node-haste/Module'; -import type {SourceMap} from './SourceMap'; - -export type SourceMapOrMappings = SourceMap | Array; - -type Metadata = { - dependencies?: ?Array, - dependencyPairs?: Array<[string, Module]>, - preloaded: ?boolean, -}; - -class ModuleTransport { - - name: string; - id: number; - code: string; - sourceCode: string; - sourcePath: string; - virtual: boolean; - meta: ?Metadata; - polyfill: boolean; - map: ?SourceMapOrMappings; - - constructor(data: { - name: string, - id: number, - code: string, - sourceCode: string, - sourcePath: string, - virtual?: boolean, - meta?: ?Metadata, - polyfill?: boolean, - map?: ?SourceMapOrMappings, - }) { - this.name = data.name; - - assertExists(data, 'id'); - this.id = data.id; - - assertExists(data, 'code'); - this.code = data.code; - - assertExists(data, 'sourceCode'); - this.sourceCode = data.sourceCode; - - assertExists(data, 'sourcePath'); - this.sourcePath = data.sourcePath; - - this.virtual = !!data.virtual; - this.meta = data.meta; - this.polyfill = !!data.polyfill; - this.map = data.map; - - Object.freeze(this); - } - -} - -module.exports = ModuleTransport; - -function assertExists(obj, field) { - if (obj[field] == null) { - throw new Error('Modules must have `' + field + '`'); - } -} diff --git a/packager/src/lib/SourceMap.js b/packager/src/lib/SourceMap.js deleted file mode 100644 index 8efaddba5c19da..00000000000000 --- a/packager/src/lib/SourceMap.js +++ /dev/null @@ -1,39 +0,0 @@ -/** - * 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. - * - * @flow - */ - -'use strict'; - -import type {SourceMap as MappingsMap} from 'babel-core'; - -export type IndexMapSection = { - map: SourceMap, - offset: {line: number, column: number}, -}; - -type FBExtensions = {x_facebook_offsets: Array}; - -export type {MappingsMap}; -export type IndexMap = { - file?: string, - mappings?: void, // avoids SourceMap being a disjoint union - sections: Array, - version: number, -}; - -export type FBIndexMap = IndexMap & FBExtensions; -export type SourceMap = IndexMap | MappingsMap; -export type FBSourceMap = FBIndexMap | (MappingsMap & FBExtensions); - -function isMappingsMap(map: SourceMap)/*: %checks*/ { - return map.mappings !== undefined; -} - -exports.isMappingsMap = isMappingsMap; diff --git a/packager/src/lib/TerminalClass.js b/packager/src/lib/TerminalClass.js deleted file mode 100644 index 7f767fb9d17a54..00000000000000 --- a/packager/src/lib/TerminalClass.js +++ /dev/null @@ -1,166 +0,0 @@ -/** - * 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. - * - * @flow - * @format - */ - -'use strict'; - -const readline = require('readline'); -const throttle = require('lodash/throttle'); -const tty = require('tty'); -const util = require('util'); - -/** - * Clear some text that was previously printed on an interactive stream, - * without trailing newline character (so we have to move back to the - * beginning of the line). - */ -function clearStringBackwards(stream: tty.WriteStream, str: string): void { - readline.moveCursor(stream, -stream.columns, 0); - readline.clearLine(stream, 0); - let lineCount = (str.match(/\n/g) || []).length; - while (lineCount > 0) { - readline.moveCursor(stream, 0, -1); - readline.clearLine(stream, 0); - --lineCount; - } -} - -/** - * Cut a string into an array of string of the specific maximum size. A newline - * ends a chunk immediately (it's not included in the "." RexExp operator), and - * is not included in the result. - */ -function chunkString(str: string, size: number): Array { - return str.match(new RegExp(`.{1,${size}}`, 'g')) || []; -} - -/** - * Get the stream as a TTY if it effectively looks like a valid TTY. - */ -function getTTYStream(stream: net$Socket): ?tty.WriteStream { - if ( - stream instanceof tty.WriteStream && - stream.isTTY && - stream.columns >= 1 - ) { - return stream; - } - return null; -} - -/** - * We don't just print things to the console, sometimes we also want to show - * and update progress. This utility just ensures the output stays neat: no - * missing newlines, no mangled log lines. - * - * const terminal = Terminal.default; - * terminal.status('Updating... 38%'); - * terminal.log('warning: Something happened.'); - * terminal.status('Updating, done.'); - * terminal.persistStatus(); - * - * The final output: - * - * warning: Something happened. - * Updating, done. - * - * Without the status feature, we may get a mangled output: - * - * Updating... 38%warning: Something happened. - * Updating, done. - * - * This is meant to be user-readable and TTY-oriented. We use stdout by default - * because it's more about status information than diagnostics/errors (stderr). - * - * Do not add any higher-level functionality in this class such as "warning" and - * "error" printers, as it is not meant for formatting/reporting. It has the - * single responsibility of handling status messages. - */ -class Terminal { - _logLines: Array; - _nextStatusStr: string; - _scheduleUpdate: () => void; - _statusStr: string; - _stream: net$Socket; - - constructor(stream: net$Socket) { - this._logLines = []; - this._nextStatusStr = ''; - this._scheduleUpdate = throttle(this._update, 0); - this._statusStr = ''; - this._stream = stream; - } - - /** - * Clear and write the new status, logging in bulk in-between. Doing this in a - * throttled way (in a different tick than the calls to `log()` and - * `status()`) prevents us from repeatedly rewriting the status in case - * `terminal.log()` is called several times. - */ - _update(): void { - const {_statusStr, _stream} = this; - const ttyStream = getTTYStream(_stream); - if (_statusStr === this._nextStatusStr && this._logLines.length === 0) { - return; - } - if (ttyStream != null) { - clearStringBackwards(ttyStream, _statusStr); - } - this._logLines.forEach(line => { - _stream.write(line); - _stream.write('\n'); - }); - this._logLines = []; - if (ttyStream != null) { - this._nextStatusStr = chunkString( - this._nextStatusStr, - ttyStream.columns, - ).join('\n'); - _stream.write(this._nextStatusStr); - } - this._statusStr = this._nextStatusStr; - } - - /** - * Shows some text that is meant to be overriden later. Return the previous - * status that was shown and is no more. Calling `status()` with no argument - * removes the status altogether. The status is never shown in a - * non-interactive terminal: for example, if the output is redirected to a - * file, then we don't care too much about having a progress bar. - */ - status(format: string, ...args: Array): string { - const {_nextStatusStr} = this; - this._nextStatusStr = util.format(format, ...args); - this._scheduleUpdate(); - return _nextStatusStr; - } - - /** - * Similar to `console.log`, except it moves the status/progress text out of - * the way correctly. In non-interactive terminals this is the same as - * `console.log`. - */ - log(format: string, ...args: Array): void { - this._logLines.push(util.format(format, ...args)); - this._scheduleUpdate(); - } - - /** - * Log the current status and start from scratch. This is useful if the last - * status was the last one of a series of updates. - */ - persistStatus(): void { - this.log(this._nextStatusStr); - this._nextStatusStr = ''; - } -} - -module.exports = Terminal; diff --git a/packager/src/lib/TerminalReporter.js b/packager/src/lib/TerminalReporter.js deleted file mode 100644 index aa4ef26ca7bab0..00000000000000 --- a/packager/src/lib/TerminalReporter.js +++ /dev/null @@ -1,354 +0,0 @@ -/** - * 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. - * - * @flow - */ - -'use strict'; - -const chalk = require('chalk'); -const formatBanner = require('./formatBanner'); -const path = require('path'); -const reporting = require('./reporting'); -const throttle = require('lodash/throttle'); -const util = require('util'); - -import type Terminal from './TerminalClass'; -import type {ReportableEvent, GlobalCacheDisabledReason} from './reporting'; - -const DEP_GRAPH_MESSAGE = 'Loading dependency graph'; -const GLOBAL_CACHE_DISABLED_MESSAGE_FORMAT = - 'The global cache is now disabled because %s'; - -type BundleProgress = { - entryFilePath: string, - transformedFileCount: number, - totalFileCount: number, - ratio: number, -}; - -const DARK_BLOCK_CHAR = '\u2593'; -const LIGHT_BLOCK_CHAR = '\u2591'; - -function getProgressBar(ratio: number, length: number) { - const blockCount = Math.floor(ratio * length); - return ( - DARK_BLOCK_CHAR.repeat(blockCount) + - LIGHT_BLOCK_CHAR.repeat(length - blockCount) - ); -} - -export type TerminalReportableEvent = ReportableEvent | { - buildID: string, - type: 'bundle_transform_progressed_throttled', - transformedFileCount: number, - totalFileCount: number, -}; - -type BuildPhase = 'in_progress' | 'done' | 'failed'; - -/** - * We try to print useful information to the terminal for interactive builds. - * This implements the `Reporter` interface from the './reporting' module. - */ -class TerminalReporter { - - /** - * The bundle builds for which we are actively maintaining the status on the - * terminal, ie. showing a progress bar. There can be several bundles being - * built at the same time. - */ - _activeBundles: Map; - - _dependencyGraphHasLoaded: boolean; - _scheduleUpdateBundleProgress: (data: { - buildID: string, - transformedFileCount: number, - totalFileCount: number, - }) => void; - - +terminal: Terminal; - - constructor(terminal: Terminal) { - this._dependencyGraphHasLoaded = false; - this._activeBundles = new Map(); - this._scheduleUpdateBundleProgress = throttle(data => { - this.update({...data, type: 'bundle_transform_progressed_throttled'}); - }, 100); - (this: any).terminal = terminal; - } - - /** - * Construct a message that represents the progress of a - * single bundle build, for example: - * - * Bunding `foo.js` |#### | 34.2% (324/945) - */ - _getBundleStatusMessage( - {entryFilePath, totalFileCount, transformedFileCount, ratio}: BundleProgress, - phase: BuildPhase, - ): string { - const localPath = path.relative('.', entryFilePath); - return util.format( - 'Bundling `%s` %s%s% (%s/%s)%s', - localPath, - phase === 'in_progress' ? getProgressBar(ratio, 16) + ' ' : '', - (100 * ratio).toFixed(1), - transformedFileCount, - totalFileCount, - phase === 'done' ? ', done.' : (phase === 'failed' ? ', failed.' : ''), - ); - } - - _logCacheDisabled(reason: GlobalCacheDisabledReason): void { - const format = GLOBAL_CACHE_DISABLED_MESSAGE_FORMAT; - switch (reason) { - case 'too_many_errors': - reporting.logWarning( - this.terminal, - format, - 'it has been failing too many times.', - ); - break; - case 'too_many_misses': - reporting.logWarning( - this.terminal, - format, - 'it has been missing too many consecutive keys.', - ); - break; - } - } - - _logBundleBuildDone(buildID: string) { - const progress = this._activeBundles.get(buildID); - if (progress != null) { - const msg = this._getBundleStatusMessage({ - ...progress, - ratio: 1, - transformedFileCount: progress.totalFileCount, - }, 'done'); - this.terminal.log(msg); - } - } - - _logBundleBuildFailed(buildID: string) { - const progress = this._activeBundles.get(buildID); - if (progress != null) { - const msg = this._getBundleStatusMessage(progress, 'failed'); - this.terminal.log(msg); - } - } - - _logPackagerInitializing(port: number, projectRoots: $ReadOnlyArray) { - this.terminal.log( - formatBanner( - 'Running packager on port ' + - port + - '.\n\n' + - 'Keep this packager running while developing on any JS projects. ' + - 'Feel free to close this tab and run your own packager instance if you ' + - 'prefer.\n\n' + - 'https://github.com/facebook/react-native', - { - marginLeft: 1, - marginRight: 1, - paddingBottom: 1, - } - ) - ); - - this.terminal.log( - 'Looking for JS files in\n ', - chalk.dim(projectRoots.join('\n ')), - '\n' - ); - } - - _logPackagerInitializingFailed(port: number, error: Error) { - if (error.code === 'EADDRINUSE') { - this.terminal.log( - chalk.bgRed.bold(' ERROR '), - chalk.red("Packager can't listen on port", chalk.bold(port)) - ); - this.terminal.log('Most likely another process is already using this port'); - this.terminal.log('Run the following command to find out which process:'); - this.terminal.log('\n ', chalk.bold('lsof -i :' + port), '\n'); - this.terminal.log('Then, you can either shut down the other process:'); - this.terminal.log('\n ', chalk.bold('kill -9 '), '\n'); - this.terminal.log('or run packager on different port.'); - } else { - this.terminal.log(chalk.bgRed.bold(' ERROR '), chalk.red(error.message)); - const errorAttributes = JSON.stringify(error); - if (errorAttributes !== '{}') { - this.terminal.log(chalk.red(errorAttributes)); - } - this.terminal.log(chalk.red(error.stack)); - } - } - - /** - * This function is only concerned with logging and should not do state - * or terminal status updates. - */ - _log(event: TerminalReportableEvent): void { - switch (event.type) { - case 'initialize_packager_started': - this._logPackagerInitializing(event.port, event.projectRoots); - break; - case 'initialize_packager_done': - this.terminal.log('\nReact packager ready.\n'); - break; - case 'initialize_packager_failed': - this._logPackagerInitializingFailed(event.port, event.error); - break; - case 'bundle_build_done': - this._logBundleBuildDone(event.buildID); - break; - case 'bundle_build_failed': - this._logBundleBuildFailed(event.buildID); - break; - case 'bundling_error': - this._logBundlingError(event.error); - break; - case 'dep_graph_loaded': - this.terminal.log(`${DEP_GRAPH_MESSAGE}, done.`); - break; - case 'global_cache_disabled': - this._logCacheDisabled(event.reason); - break; - case 'transform_cache_reset': - reporting.logWarning(this.terminal, 'the transform cache was reset.'); - break; - case 'worker_stdout_chunk': - this._logWorkerChunk('stdout', event.chunk); - break; - case 'worker_stderr_chunk': - this._logWorkerChunk('stderr', event.chunk); - break; - } - } - - /** - * We do not want to log the whole stacktrace for bundling error, because - * these are operational errors, not programming errors, and the stacktrace - * is not actionable to end users. - */ - _logBundlingError(error: Error) { - const str = JSON.stringify(error.message); - reporting.logError(this.terminal, 'bundling failed: %s', str); - } - - _logWorkerChunk(origin: 'stdout' | 'stderr', chunk: string) { - const lines = chunk.split('\n'); - if (lines.length >= 1 && lines[lines.length - 1] === '') { - lines.splice(lines.length - 1, 1); - } - lines.forEach(line => { - this.terminal.log(`transform[${origin}]: ${line}`); - }); - } - - /** - * We use Math.pow(ratio, 2) to as a conservative measure of progress because - * we know the `totalCount` is going to progressively increase as well. We - * also prevent the ratio from going backwards. - */ - _updateBundleProgress( - {buildID, transformedFileCount, totalFileCount}: { - buildID: string, - transformedFileCount: number, - totalFileCount: number, - }, - ) { - const currentProgress = this._activeBundles.get(buildID); - if (currentProgress == null) { - return; - } - const rawRatio = transformedFileCount / totalFileCount; - const conservativeRatio = Math.pow(rawRatio, 2); - const ratio = Math.max(conservativeRatio, currentProgress.ratio); - Object.assign(currentProgress, { - ratio, - transformedFileCount, - totalFileCount, - }); - } - - /** - * This function is exclusively concerned with updating the internal state. - * No logging or status updates should be done at this point. - */ - _updateState(event: TerminalReportableEvent): void { - switch (event.type) { - case 'bundle_build_done': - case 'bundle_build_failed': - this._activeBundles.delete(event.buildID); - break; - case 'bundle_build_started': - this._activeBundles.set(event.buildID, { - entryFilePath: event.entryFilePath, - transformedFileCount: 0, - totalFileCount: 1, - ratio: 0, - }); - break; - case 'bundle_transform_progressed': - if (event.totalFileCount === event.transformedFileCount) { - this._scheduleUpdateBundleProgress.cancel(); - this._updateBundleProgress(event); - } else { - this._scheduleUpdateBundleProgress(event); - } - break; - case 'bundle_transform_progressed_throttled': - this._updateBundleProgress(event); - break; - case 'dep_graph_loading': - this._dependencyGraphHasLoaded = false; - break; - case 'dep_graph_loaded': - this._dependencyGraphHasLoaded = true; - break; - } - } - - _getDepGraphStatusMessage(): ?string { - if (!this._dependencyGraphHasLoaded) { - return `${DEP_GRAPH_MESSAGE}...`; - } - return null; - } - - /** - * Return a status message that is always consistent with the current state - * of the application. Having this single function ensures we don't have - * different callsites overriding each other status messages. - */ - _getStatusMessage(): string { - return [ - this._getDepGraphStatusMessage(), - ].concat(Array.from(this._activeBundles.entries()).map( - ([_, progress]) => - this._getBundleStatusMessage(progress, 'in_progress'), - )).filter(str => str != null).join('\n'); - } - - /** - * Everything that happens goes through the same 3 steps. This makes the - * output more reliable and consistent, because no matter what additional. - */ - update(event: TerminalReportableEvent) { - this._log(event); - this._updateState(event); - this.terminal.status(this._getStatusMessage()); - } - -} - -module.exports = TerminalReporter; diff --git a/packager/src/lib/TransformCaching.js b/packager/src/lib/TransformCaching.js deleted file mode 100644 index dfbc0b898882b4..00000000000000 --- a/packager/src/lib/TransformCaching.js +++ /dev/null @@ -1,457 +0,0 @@ -/** - * Copyright (c) 2016-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. - * - * @flow - * @format - */ - -'use strict'; - -const crypto = require('crypto'); -const debugRead = require('debug')('RNP:TransformCache:Read'); -const fs = require('fs'); -const invariant = require('fbjs/lib/invariant'); -const mkdirp = require('mkdirp'); -const path = require('path'); -const rimraf = require('rimraf'); -const writeFileAtomicSync = require('write-file-atomic').sync; - -import type {Options as WorkerOptions} from '../JSTransformer/worker'; -import type {MappingsMap} from './SourceMap'; -import type {Reporter} from './reporting'; -import type {LocalPath} from '../node-haste/lib/toLocalPath'; - -type CacheFilePaths = {transformedCode: string, metadata: string}; -export type GetTransformCacheKey = (options: {}) => string; - -const CACHE_SUB_DIR = 'cache'; - -export type CachedResult = { - code: string, - dependencies: Array, - dependencyOffsets: Array, - map?: ?MappingsMap, -}; - -export type TransformCacheResult = {| - +result: ?CachedResult, - +outdatedDependencies: $ReadOnlyArray, -|}; - -export type CacheOptions = { - reporter: Reporter, - resetCache?: boolean, -}; - -export type ReadTransformProps = { - filePath: string, - localPath: LocalPath, - sourceCode: string, - transformOptions: WorkerOptions, - transformOptionsKey: string, - getTransformCacheKey: GetTransformCacheKey, - cacheOptions: CacheOptions, -}; - -type WriteTransformProps = { - filePath: string, - sourceCode: string, - getTransformCacheKey: GetTransformCacheKey, - transformOptions: WorkerOptions, - transformOptionsKey: string, - result: CachedResult, -}; - -/** - * The API that should be exposed for a transform cache. - */ -export type TransformCache = { - writeSync(props: WriteTransformProps): void, - readSync(props: ReadTransformProps): TransformCacheResult, -}; - -const EMPTY_ARRAY = []; - -/* 1 day */ -const GARBAGE_COLLECTION_PERIOD = 24 * 60 * 60 * 1000; -/* 4 days */ -const CACHE_FILE_MAX_LAST_ACCESS_TIME = GARBAGE_COLLECTION_PERIOD * 4; - -class FileBasedCache { - _cacheWasReset: boolean; - _lastCollected: ?number; - _rootPath: string; - - /** - * The root path is where the data will be stored. It shouldn't contain - * other files other than the cache's own files, so it should start empty - * when the packager is first run. When doing a cache reset, it may be - * completely deleted. - */ - constructor(rootPath: string) { - this._cacheWasReset = false; - invariant( - path.isAbsolute(rootPath), - 'root path of the transform cache must be absolute', - ); - require('debug')('RNP:TransformCache:Dir')( - `transform cache directory: ${rootPath}`, - ); - this._rootPath = rootPath; - } - - /** - * We store the transformed JS because it is likely to be much bigger than the - * rest of the data JSON. Probably the map should be stored separately as - * well. - * - * We make the write operation as much atomic as possible: indeed, if another - * process is reading the cache at the same time, there would be a risk it - * reads new transformed code, but old metadata. This is avoided by removing - * the files first. - * - * There is still a risk of conflincting writes, that is mitigated by hashing - * the result code, that is verified at the end. In case of writes happening - * close to each others, one of the workers is going to loose its results no - * matter what. - */ - writeSync(props: { - filePath: string, - sourceCode: string, - getTransformCacheKey: GetTransformCacheKey, - transformOptions: WorkerOptions, - transformOptionsKey: string, - result: CachedResult, - }): void { - const cacheFilePath = this._getCacheFilePaths(props); - mkdirp.sync(path.dirname(cacheFilePath.transformedCode)); - const {result} = props; - unlinkIfExistsSync(cacheFilePath.transformedCode); - unlinkIfExistsSync(cacheFilePath.metadata); - writeFileAtomicSync(cacheFilePath.transformedCode, result.code); - writeFileAtomicSync( - cacheFilePath.metadata, - JSON.stringify([ - crypto.createHash('sha1').update(result.code).digest('hex'), - hashSourceCode(props), - result.dependencies, - result.dependencyOffsets, - result.map, - ]), - ); - } - - readSync(props: ReadTransformProps): TransformCacheResult { - const result = this._readSync(props); - const msg = result ? 'Cache hit: ' : 'Cache miss: '; - debugRead(msg + props.filePath); - return result; - } - - /** - * We verify the source hash matches to ensure we always favor rebuilding when - * source change (rather than just using fs.mtime(), a bit less robust). - * - * That means when the source changes, we override the old transformed code - * with the new one. This is, I believe, preferable, so as to avoid bloating - * the cache during development cycles, where people changes files all the - * time. If we implement a global cache ability at some point, we'll be able - * to store old artifacts as well. - * - * Meanwhile we store transforms with different options in different files so - * that it is fast to switch between ex. minified, or not. - */ - _readSync(props: ReadTransformProps): TransformCacheResult { - this._collectIfNecessarySync(props.cacheOptions); - try { - return this._readFilesSync(props); - } catch (error) { - if (error.code === 'ENOENT') { - return {result: null, outdatedDependencies: EMPTY_ARRAY}; - } - throw error; - } - } - - _readFilesSync(props: ReadTransformProps): TransformCacheResult { - const cacheFilePaths = this._getCacheFilePaths(props); - const metadata = readMetadataFileSync(cacheFilePaths.metadata); - if (metadata == null) { - return {result: null, outdatedDependencies: EMPTY_ARRAY}; - } - const sourceHash = hashSourceCode(props); - if (sourceHash !== metadata.cachedSourceHash) { - return {result: null, outdatedDependencies: metadata.dependencies}; - } - const transformedCode = fs.readFileSync( - cacheFilePaths.transformedCode, - 'utf8', - ); - const codeHash = crypto - .createHash('sha1') - .update(transformedCode) - .digest('hex'); - if (metadata.cachedResultHash !== codeHash) { - return {result: null, outdatedDependencies: metadata.dependencies}; - } - return { - result: { - code: transformedCode, - dependencies: metadata.dependencies, - dependencyOffsets: metadata.dependencyOffsets, - map: metadata.sourceMap, - }, - outdatedDependencies: EMPTY_ARRAY, - }; - } - - /** - * Temporary folder is never cleaned up automatically, we need to clean up old - * stuff ourselves. This code should be safe even if two different React - * Native projects are running at the same time. - */ - _collectIfNecessarySync(options: CacheOptions) { - if (options.resetCache && !this._cacheWasReset) { - this._resetCache(options.reporter); - return; - } - const lastCollected = this._lastCollected; - if ( - lastCollected == null || - Date.now() - lastCollected > GARBAGE_COLLECTION_PERIOD - ) { - this._collectSyncNoThrow(options.reporter); - } - } - - _resetCache(reporter: Reporter) { - rimraf.sync(this._rootPath); - reporter.update({type: 'transform_cache_reset'}); - this._cacheWasReset = true; - this._lastCollected = Date.now(); - } - - /** - * We want to avoid preventing tool use if the cleanup fails for some reason, - * but still provide some chance for people to report/fix things. - */ - _collectSyncNoThrow(reporter: Reporter) { - try { - this._collectCacheIfOldSync(); - } catch (error) { - // FIXME: $FlowFixMe: this is a hack, only works for TerminalReporter - const {terminal} = reporter; - if (terminal != null) { - terminal.log(error.stack); - terminal.log( - 'Error: Cleaning up the cache folder failed. Continuing anyway.', - ); - terminal.log('The cache folder is: %s', this._rootPath); - } - } - this._lastCollected = Date.now(); - } - - /** - * When restarting packager we want to avoid running the collection over - * again, so we store the last collection time in a file and we check that - * first. - */ - _collectCacheIfOldSync() { - const cacheDirPath = this._rootPath; - mkdirp.sync(cacheDirPath); - const cacheCollectionFilePath = path.join(cacheDirPath, 'last_collected'); - const lastCollected = Number.parseInt( - tryReadFileSync(cacheCollectionFilePath), - 10, - ); - if ( - Number.isInteger(lastCollected) && - Date.now() - lastCollected > GARBAGE_COLLECTION_PERIOD - ) { - return; - } - const effectiveCacheDirPath = path.join(cacheDirPath, CACHE_SUB_DIR); - mkdirp.sync(effectiveCacheDirPath); - collectCacheSync(effectiveCacheDirPath); - fs.writeFileSync(cacheCollectionFilePath, Date.now().toString()); - } - - /** - * The path, built as a hash, does not take the source code itself into - * account because it would generate lots of file during development. (The - * source hash is stored in the metadata instead). - */ - _getCacheFilePaths(props: { - filePath: string, - transformOptionsKey: string, - }): CacheFilePaths { - const hasher = crypto - .createHash('sha1') - .update(props.filePath) - .update(props.transformOptionsKey); - const hash = hasher.digest('hex'); - const prefix = hash.substr(0, 2); - const fileName = `${hash.substr(2)}`; - const base = path.join(this._rootPath, CACHE_SUB_DIR, prefix, fileName); - return {transformedCode: base, metadata: base + '.meta'}; - } -} - -/** - * Remove all the cache files from the specified folder that are older than a - * certain duration. - */ -function collectCacheSync(dirPath: string) { - const prefixDirs = fs.readdirSync(dirPath); - for (let i = 0; i < prefixDirs.length; ++i) { - const prefixDir = path.join(dirPath, prefixDirs[i]); - const cacheFileNames = fs.readdirSync(prefixDir); - for (let j = 0; j < cacheFileNames.length; ++j) { - const cacheFilePath = path.join(prefixDir, cacheFileNames[j]); - const stats = fs.lstatSync(cacheFilePath); - const timeSinceLastAccess = Date.now() - stats.atime.getTime(); - if ( - stats.isFile() && - timeSinceLastAccess > CACHE_FILE_MAX_LAST_ACCESS_TIME - ) { - fs.unlinkSync(cacheFilePath); - } - } - } -} - -function tryReadFileSync(filePath: string): string { - try { - return fs.readFileSync(filePath, 'utf8'); - } catch (error) { - if (error.code !== 'ENOENT') { - throw error; - } - return ''; - } -} - -function readMetadataFileSync( - metadataFilePath: string, -): ?{ - cachedResultHash: string, - cachedSourceHash: string, - dependencies: Array, - dependencyOffsets: Array, - sourceMap: ?MappingsMap, -} { - const metadataStr = fs.readFileSync(metadataFilePath, 'utf8'); - const metadata = tryParseJSON(metadataStr); - if (!Array.isArray(metadata)) { - return null; - } - const [ - cachedResultHash, - cachedSourceHash, - dependencies, - dependencyOffsets, - sourceMap, - ] = metadata; - if ( - typeof cachedResultHash !== 'string' || - typeof cachedSourceHash !== 'string' || - !(Array.isArray(dependencies) && - dependencies.every(dep => typeof dep === 'string')) || - !(Array.isArray(dependencyOffsets) && - dependencyOffsets.every(offset => typeof offset === 'number')) || - !(sourceMap == null || typeof sourceMap === 'object') - ) { - return null; - } - return { - cachedResultHash, - cachedSourceHash, - dependencies, - dependencyOffsets, - sourceMap, - }; -} - -function tryParseJSON(str: string): any { - try { - return JSON.parse(str); - } catch (error) { - if (error instanceof SyntaxError) { - return null; - } - throw error; - } -} - -function hashSourceCode(props: { - filePath: string, - sourceCode: string, - getTransformCacheKey: GetTransformCacheKey, - transformOptions: WorkerOptions, - transformOptionsKey: string, -}): string { - return crypto - .createHash('sha1') - .update(props.getTransformCacheKey(props.transformOptions)) - .update(props.sourceCode) - .digest('hex'); -} - -/** - * We want to unlink all cache files before writing, so that it is as much - * atomic as possible. - */ -function unlinkIfExistsSync(filePath: string) { - try { - fs.unlinkSync(filePath); - } catch (error) { - if (error.code === 'ENOENT') { - return; - } - throw error; - } -} - -/** - * In some context we want to build from scratch, that is what this cache - * implementation allows. - */ -function none(): TransformCache { - return { - writeSync: () => {}, - readSync: () => ({ - result: null, - outdatedDependencies: [], - }), - }; -} - -/** - * If packager is running for two different directories, we don't want the - * caches to conflict with each other. `__dirname` carries that because - * packager will be, for example, installed in a different `node_modules/` - * folder for different projects. - */ -function useTempDir(): TransformCache { - const hash = crypto.createHash('sha1').update(__dirname); - if (process.getuid != null) { - hash.update(process.getuid().toString()); - } - const tmpDir = require('os').tmpdir(); - const cacheName = 'react-native-packager-cache'; - const rootPath = path.join(tmpDir, cacheName + '-' + hash.digest('hex')); - return new FileBasedCache(rootPath); -} - -function useProjectDir(projectPath: string): TransformCache { - invariant(path.isAbsolute(projectPath), 'project path must be absolute'); - return new FileBasedCache(path.join(projectPath, '.metro-bundler')); -} - -module.exports = {FileBasedCache, none, useTempDir, useProjectDir}; diff --git a/packager/src/lib/__mocks__/GlobalTransformCache.js b/packager/src/lib/__mocks__/GlobalTransformCache.js deleted file mode 100644 index f741d77228ddc1..00000000000000 --- a/packager/src/lib/__mocks__/GlobalTransformCache.js +++ /dev/null @@ -1,18 +0,0 @@ -/** - * Copyright (c) 2016-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. - * - * @flow - */ - -'use strict'; - -function get() { - return null; -} - -module.exports = {get}; diff --git a/packager/src/lib/__mocks__/TransformCaching.js b/packager/src/lib/__mocks__/TransformCaching.js deleted file mode 100644 index 0fd89826d52790..00000000000000 --- a/packager/src/lib/__mocks__/TransformCaching.js +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright (c) 2016-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 crypto = require('crypto'); -const jsonStableStringify = require('json-stable-stringify'); - -const transformCache = new Map(); - -const transformCacheKeyOf = props => - props.filePath + '-' + crypto.createHash('md5') - .update(props.sourceCode) - .update(props.getTransformCacheKey(props.sourceCode, props.filePath, props.transformOptions)) - .update(jsonStableStringify(props.transformOptions || {})) - .digest('hex'); - -class TransformCacheMock { - - constructor() { - this.mock = { - lastWrite: null, - reset: () => { - transformCache.clear(); - this.mock.lastWrite = null; - }, - }; - } - - writeSync(props) { - transformCache.set(transformCacheKeyOf(props), props.result); - this.mock.lastWrite = props; - } - - readSync(props) { - return {result: transformCache.get(transformCacheKeyOf(props)), outdatedDependencies: []}; - } - -} - -module.exports = {mocked: () => new TransformCacheMock()}; diff --git a/packager/src/lib/__mocks__/declareOpts.js b/packager/src/lib/__mocks__/declareOpts.js deleted file mode 100644 index 3322005e7376b6..00000000000000 --- a/packager/src/lib/__mocks__/declareOpts.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * 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'; - -module.exports = function(declared) { - return function(opts) { - for (var p in declared) { - if (opts[p] == null && declared[p].default != null) { - opts[p] = declared[p].default; - } - } - return opts; - }; -}; diff --git a/packager/src/lib/__tests__/BatchProcessor-test.js b/packager/src/lib/__tests__/BatchProcessor-test.js deleted file mode 100644 index 856cd822557cf4..00000000000000 --- a/packager/src/lib/__tests__/BatchProcessor-test.js +++ /dev/null @@ -1,68 +0,0 @@ -/** - * Copyright (c) 2016-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'; - -jest - .useRealTimers() - .dontMock('../BatchProcessor'); - -const BatchProcessor = require('../BatchProcessor'); - -describe('BatchProcessor', () => { - - const options = { - maximumDelayMs: 500, - maximumItems: 3, - concurrency: 2, - }; - - it('aggregate items concurrently', async () => { - const input = [...Array(9).keys()].slice(1); - const transform = e => e * 10; - const batches = []; - let concurrency = 0; - let maxConcurrency = 0; - const bp = new BatchProcessor(options, items => new Promise(resolve => { - ++concurrency; - expect(concurrency).toBeLessThanOrEqual(options.concurrency); - maxConcurrency = Math.max(maxConcurrency, concurrency); - batches.push(items); - setTimeout(() => { - resolve(items.map(transform)); - --concurrency; - }, 0); - })); - const results = []; - await Promise.all(input.map(e => bp.queue(e).then( - res => results.push(res), - error => process.nextTick(() => { throw error; }), - ))); - expect(batches).toEqual([ - [1, 2, 3], - [4, 5, 6], - [7, 8], - ]); - expect(maxConcurrency).toEqual(options.concurrency); - expect(results).toEqual(input.map(transform)); - }); - - it('report errors', async () => { - const error = new Error('oh noes'); - const bp = new BatchProcessor(options, items => new Promise((_, reject) => { - setTimeout(reject.bind(null, error), 0); - })); - let receivedError; - await bp.queue('foo').catch( - err => { receivedError = err; }, - ); - expect(receivedError).toBe(error); - }); - -}); diff --git a/packager/src/lib/__tests__/GlobalTransformCache-test.js b/packager/src/lib/__tests__/GlobalTransformCache-test.js deleted file mode 100644 index c95a4e05421460..00000000000000 --- a/packager/src/lib/__tests__/GlobalTransformCache-test.js +++ /dev/null @@ -1,105 +0,0 @@ -/** - * Copyright (c) 2016-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'; - -jest.disableAutomock(); -jest.useRealTimers(); - -const mockFetch = jest.fn(); -jest.mock('node-fetch', () => mockFetch); - -const {URIBasedGlobalTransformCache} = require('../GlobalTransformCache'); -const FetchError = require('node-fetch/lib/fetch-error'); -const path = require('path'); - -async function fetchResultURIs(keys: Array): Promise> { - return new Map(keys.map(key => [key, `http://globalcache.com/${key}`])); -} - -async function fetchResultFromURI(uri: string): Promise { - return { - code: `/* code from ${uri} */`, - dependencies: [], - dependencyOffsets: [], - }; -} - -describe('GlobalTransformCache', () => { - - it('fetches results', async () => { - const cache = new URIBasedGlobalTransformCache({ - fetchResultFromURI, - fetchResultURIs, - profiles: [{dev: true, minify: false, platform: 'ios'}], - rootPath: __dirname, - storeResults: null, - }); - const transformOptions = { - dev: true, - minify: false, - platform: 'ios', - transform: { - generateSourceMaps: false, - dev: false, - hot: false, - inlineRequires: false, - platform: 'ios', - projectRoot: path.join(__dirname, 'root'), - }, - }; - const result = await Promise.all([cache.fetch({ - localPath: 'some/where/foo.js', - sourceCode: '/* beep */', - getTransformCacheKey: () => 'abcd', - transformOptions, - }), cache.fetch({ - localPath: 'some/where/else/bar.js', - sourceCode: '/* boop */', - getTransformCacheKey: () => 'abcd', - transformOptions, - })]); - expect(result).toMatchSnapshot(); - }); - - describe('fetchResultFromURI', () => { - - const defaultFetchMockImpl = async uri => ({ - status: 200, - json: async () => ({ - code: `/* code from ${uri} */`, - dependencies: [], - dependencyOffsets: [], - }), - }); - - beforeEach(() => { - mockFetch.mockReset(); - }); - - it('fetches result', async () => { - mockFetch.mockImplementation(defaultFetchMockImpl); - const result = await URIBasedGlobalTransformCache - .fetchResultFromURI('http://globalcache.com/foo'); - expect(result).toMatchSnapshot(); - }); - - it('retries once on timeout', async () => { - mockFetch.mockImplementation(async uri => { - mockFetch.mockImplementation(defaultFetchMockImpl); - throw new FetchError('timeout!', 'request-timeout'); - }); - const result = await URIBasedGlobalTransformCache - .fetchResultFromURI('http://globalcache.com/foo'); - expect(result).toMatchSnapshot(); - }); - - }); - -}); diff --git a/packager/src/lib/__tests__/TerminalClass-test.js b/packager/src/lib/__tests__/TerminalClass-test.js deleted file mode 100644 index 9c8470f967264f..00000000000000 --- a/packager/src/lib/__tests__/TerminalClass-test.js +++ /dev/null @@ -1,112 +0,0 @@ -/** - * 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'; - -jest.dontMock('../TerminalClass').dontMock('lodash/throttle'); - -jest.mock('readline', () => ({ - moveCursor: (stream, dx, dy) => { - const {cursor, columns} = stream; - stream.cursor = Math.max(cursor - cursor % columns, cursor + dx) + dy * columns; - }, - clearLine: (stream, dir) => { - if (dir !== 0) {throw new Error('unsupported');} - const {cursor, columns} = stream; - const curLine = cursor - cursor % columns; - const nextLine = curLine + columns; - for (var i = curLine; i < nextLine; ++i) { - stream.buffer[i] = ' '; - } - }, -})); - -describe('Terminal', () => { - - beforeEach(() => { - jest.resetModules(); - }); - - function prepare(isTTY) { - const Terminal = require('../TerminalClass'); - const lines = 10; - const columns = 10; - const stream = Object.create( - isTTY ? require('tty').WriteStream.prototype : require('net').Socket, - ); - Object.assign(stream, { - cursor: 0, - buffer: ' '.repeat(columns * lines).split(''), - columns, - lines, - write(str) { - for (let i = 0; i < str.length; ++i) { - if (str[i] === '\n') { - this.cursor = this.cursor - (this.cursor % columns) + columns; - } else { - this.buffer[this.cursor] = str[i]; - ++this.cursor; - } - } - }, - }); - return {stream, terminal: new Terminal(stream)}; - } - - it('is not printing status to non-interactive terminal', () => { - const {stream, terminal} = prepare(false); - terminal.log('foo %s', 'smth'); - terminal.status('status'); - terminal.log('bar'); - jest.runAllTimers(); - expect(stream.buffer.join('').trim()).toEqual('foo smth bar'); - }); - - it('print status', () => { - const {stream, terminal} = prepare(true); - terminal.log('foo'); - terminal.status('status'); - jest.runAllTimers(); - expect(stream.buffer.join('').trim()).toEqual('foo status'); - }); - - it('updates status when logging, single line', () => { - const {stream, terminal} = prepare(true); - terminal.log('foo'); - terminal.status('status'); - terminal.status('status2'); - terminal.log('bar'); - jest.runAllTimers(); - expect(stream.buffer.join('').trim()).toEqual('foo bar status2'); - terminal.log('beep'); - jest.runAllTimers(); - expect(stream.buffer.join('').trim()).toEqual('foo bar beep status2'); - }); - - it('updates status when logging, multi-line', () => { - const {stream, terminal} = prepare(true); - terminal.log('foo'); - terminal.status('status\nanother'); - terminal.log('bar'); - jest.runAllTimers(); - expect(stream.buffer.join('').trim()) - .toEqual('foo bar status another'); - }); - - it('persists status', () => { - const {stream, terminal} = prepare(true); - terminal.log('foo'); - terminal.status('status'); - terminal.persistStatus(); - terminal.log('bar'); - jest.runAllTimers(); - expect(stream.buffer.join('').trim()).toEqual('foo status bar'); - }); - -}); diff --git a/packager/src/lib/__tests__/TransformCaching-test.js b/packager/src/lib/__tests__/TransformCaching-test.js deleted file mode 100644 index 107503d1254d93..00000000000000 --- a/packager/src/lib/__tests__/TransformCaching-test.js +++ /dev/null @@ -1,139 +0,0 @@ -/** - * 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'; - -jest - .dontMock('json-stable-stringify') - .dontMock('../TransformCaching') - .dontMock('left-pad') - .dontMock('lodash/throttle') - .dontMock('crypto'); - -const crypto = require('crypto'); -const jsonStableStringify = require('json-stable-stringify'); - -const mockFS = new Map(); - -jest.mock('fs', () => ({ - readFileSync(filePath) { - return mockFS.get(filePath); - }, - unlinkSync(filePath) { - mockFS.delete(filePath); - }, - readdirSync(dirPath) { - // Not required for it to work. - return []; - }, -})); - -jest.mock('write-file-atomic', () => ({ - sync(filePath, data) { - mockFS.set(filePath, data.toString()); - }, -})); - -jest.mock('rimraf', () => () => {}); - -function cartesianProductOf(a1, a2) { - const product = []; - a1.forEach(e1 => a2.forEach(e2 => product.push([e1, e2]))); - return product; -} - -describe('TransformCaching.FileBasedCache', () => { - - let transformCache; - - beforeEach(() => { - jest.resetModules(); - mockFS.clear(); - transformCache = new (require('../TransformCaching').FileBasedCache)('/cache'); - }); - - it('is caching different files and options separately', () => { - const argsFor = ([filePath, transformOptions]) => { - const key = filePath + JSON.stringify(transformOptions); - return { - sourceCode: `/* source for ${key} */`, - getTransformCacheKey: () => 'abcdef', - filePath, - transformOptions, - transformOptionsKey: crypto.createHash('md5') - .update(jsonStableStringify(transformOptions)).digest('hex'), - result: { - code: `/* result for ${key} */`, - dependencies: ['foo', `dep of ${key}`], - dependencyOffsets: [12, 34], - map: {desc: `source map for ${key}`}, - }, - }; - }; - const allCases = cartesianProductOf( - ['/some/project/sub/dir/file.js', '/some/project/other.js'], - [{foo: 1}, {foo: 2}], - ); - allCases.forEach( - entry => transformCache.writeSync(argsFor(entry)), - ); - allCases.forEach(entry => { - const args = argsFor(entry); - const {result} = args; - const cachedResult = transformCache.readSync({ - ...args, - cacheOptions: {reporter: {}, resetCache: false}, - }); - expect(cachedResult.result).toEqual(result); - }); - }); - - it('is overriding cache when source code or transform key changes', () => { - const argsFor = ([sourceCode, transformCacheKey]) => { - const key = sourceCode + transformCacheKey; - return { - sourceCode, - getTransformCacheKey: () => transformCacheKey, - filePath: 'test.js', - transformOptions: {foo: 1}, - transformOptionsKey: 'boo!', - result: { - code: `/* result for ${key} */`, - dependencies: ['foo', 'bar'], - dependencyOffsets: [12, 34], - map: {desc: `source map for ${key}`}, - }, - }; - }; - const allCases = cartesianProductOf( - ['/* foo */', '/* bar */'], - ['abcd', 'efgh'], - ); - allCases.forEach(entry => { - transformCache.writeSync(argsFor(entry)); - const args = argsFor(entry); - const {result} = args; - const cachedResult = transformCache.readSync({ - ...args, - cacheOptions: {reporter: {}, resetCache: false}, - }); - expect(cachedResult.result).toEqual(result); - }); - allCases.pop(); - allCases.forEach(entry => { - const cachedResult = transformCache.readSync({ - ...argsFor(entry), - cacheOptions: {reporter: {}, resetCache: false}, - }); - expect(cachedResult.result).toBeNull(); - expect(cachedResult.outdatedDependencies).toEqual(['foo', 'bar']); - }); - }); - -}); diff --git a/packager/src/lib/__tests__/__snapshots__/GlobalTransformCache-test.js.snap b/packager/src/lib/__tests__/__snapshots__/GlobalTransformCache-test.js.snap deleted file mode 100644 index 88ce154729fea9..00000000000000 --- a/packager/src/lib/__tests__/__snapshots__/GlobalTransformCache-test.js.snap +++ /dev/null @@ -1,32 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`GlobalTransformCache fetchResultFromURI fetches result 1`] = ` -Object { - "code": "/* code from http://globalcache.com/foo */", - "dependencies": Array [], - "dependencyOffsets": Array [], -} -`; - -exports[`GlobalTransformCache fetchResultFromURI retries once on timeout 1`] = ` -Object { - "code": "/* code from http://globalcache.com/foo */", - "dependencies": Array [], - "dependencyOffsets": Array [], -} -`; - -exports[`GlobalTransformCache fetches results 1`] = ` -Array [ - Object { - "code": "/* code from http://globalcache.com/b23da8c74218e6155fcaf590a0fedbd1d117c2ae-foo.js */", - "dependencies": Array [], - "dependencyOffsets": Array [], - }, - Object { - "code": "/* code from http://globalcache.com/5e95f6c3e9bac0282480cda6f1a984ad8bc83e55-bar.js */", - "dependencies": Array [], - "dependencyOffsets": Array [], - }, -] -`; diff --git a/packager/src/lib/formatBanner.js b/packager/src/lib/formatBanner.js deleted file mode 100644 index 6d168abf6ae594..00000000000000 --- a/packager/src/lib/formatBanner.js +++ /dev/null @@ -1,108 +0,0 @@ -/** - * 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'; - -var _ = require('lodash'); -var wordwrap = require('wordwrap'); - -var HORIZONTAL_LINE = '\u2500'; -var VERTICAL_LINE = '\u2502'; -var TOP_LEFT = '\u250c'; -var TOP_RIGHT = '\u2510'; -var BOTTOM_LEFT = '\u2514'; -var BOTTOM_RIGHT = '\u2518'; - -/** - * Prints a banner with a border around it containing the given message. The - * following options are supported: - * - * type Options = { - * // A function to apply to each line of text to decorate it - * chalkFunction: (string: message) => string; - * // The total width (max line length) of the banner, including margin and - * // padding (default = 80) - * width: number; - * // How much leading space to prepend to each line (default = 0) - * marginLeft: number; - * // How much trailing space to append to each line (default = 0) - * marginRight: number; - * // Space between the top banner border and the text (default = 0) - * paddingTop: number; - * // Space between the bottom banner border and the text (default = 0) - * paddingBottom: number; - * // Space between the left banner border and the text (default = 2) - * paddingLeft: number; - * // Space between the right banner border and the text (default = 2) - * paddingRight: number; - * }; - */ -function formatBanner(message, options) { - options = options || {}; - _.defaults(options, { - chalkFunction: _.identity, - width: 80, - marginLeft: 0, - marginRight: 0, - paddingTop: 0, - paddingBottom: 0, - paddingLeft: 2, - paddingRight: 2, - }); - - var width = options.width; - var marginLeft = options.marginLeft; - var marginRight = options.marginRight; - var paddingTop = options.paddingTop; - var paddingBottom = options.paddingBottom; - var paddingLeft = options.paddingLeft; - var paddingRight = options.paddingRight; - - var horizSpacing = marginLeft + paddingLeft + paddingRight + marginRight; - // 2 for the banner borders - var maxLineWidth = width - horizSpacing - 2; - var wrap = wordwrap(maxLineWidth); - var body = wrap(message); - - var left = spaces(marginLeft) + VERTICAL_LINE + spaces(paddingLeft); - var right = spaces(paddingRight) + VERTICAL_LINE + spaces(marginRight); - var bodyLines = _.flattenDeep([ - arrayOf('', paddingTop), - body.split('\n'), - arrayOf('', paddingBottom), - ]).map(function(line) { - var padding = spaces(Math.max(0, maxLineWidth - line.length)); - return left + options.chalkFunction(line) + padding + right; - }); - - var horizontalBorderLine = repeatString( - HORIZONTAL_LINE, - width - marginLeft - marginRight - 2 - ); - var top = spaces(marginLeft) + TOP_LEFT + horizontalBorderLine + TOP_RIGHT + - spaces(marginRight); - var bottom = spaces(marginLeft) + BOTTOM_LEFT + horizontalBorderLine + - BOTTOM_RIGHT + spaces(marginRight); - return _.flattenDeep([top, bodyLines, bottom]).join('\n'); -} - -function spaces(number) { - return repeatString(' ', number); -} - -function repeatString(string, number) { - return new Array(number + 1).join(string); -} - -function arrayOf(value, number) { - return _.range(number).map(function() { - return value; - }); -} - -module.exports = formatBanner; diff --git a/packager/src/lib/relativizeSourceMap.js b/packager/src/lib/relativizeSourceMap.js deleted file mode 100644 index 28f35b49531c57..00000000000000 --- a/packager/src/lib/relativizeSourceMap.js +++ /dev/null @@ -1,40 +0,0 @@ -/** - * 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. - * - * @flow - */ - -'use strict'; - -const path = require('path'); - -const {isMappingsMap} = require('./SourceMap'); - -import type {SourceMap} from './SourceMap'; - -function relativizeSourceMapInternal(sourceMap: SourceMap, sourcesRoot: string) { - if (!isMappingsMap(sourceMap)) { - for (let i = 0; i < sourceMap.sections.length; i++) { - relativizeSourceMapInternal(sourceMap.sections[i].map, sourcesRoot); - } - } else { - for (let i = 0; i < sourceMap.sources.length; i++) { - sourceMap.sources[i] = path.relative(sourcesRoot, sourceMap.sources[i]); - } - } -} - -function relativizeSourceMap(sourceMap: SourceMap, sourcesRoot?: string): SourceMap { - if (!sourcesRoot) { - return sourceMap; - } - relativizeSourceMapInternal(sourceMap, sourcesRoot); - return sourceMap; -} - -module.exports = relativizeSourceMap; diff --git a/packager/src/lib/reporting.js b/packager/src/lib/reporting.js deleted file mode 100644 index e4e7c48284cf4a..00000000000000 --- a/packager/src/lib/reporting.js +++ /dev/null @@ -1,124 +0,0 @@ -/** - * 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. - * - * @flow - */ - -'use strict'; - -const chalk = require('chalk'); -const util = require('util'); - -import type Terminal from './TerminalClass'; - -export type GlobalCacheDisabledReason = 'too_many_errors' | 'too_many_misses'; - -/** - * A tagged union of all the actions that may happen and we may want to - * report to the tool user. - */ -export type ReportableEvent = { - port: number, - projectRoots: $ReadOnlyArray, - type: 'initialize_packager_started', -} | { - type: 'initialize_packager_done', -} | { - type: 'initialize_packager_failed', - port: number, - error: Error, -} | { - buildID: string, - type: 'bundle_build_done', -} | { - buildID: string, - type: 'bundle_build_failed', -} | { - buildID: string, - entryFilePath: string, - type: 'bundle_build_started', -} | { - error: Error, - type: 'bundling_error', -} | { - type: 'dep_graph_loading', -} | { - type: 'dep_graph_loaded', -} | { - buildID: string, - type: 'bundle_transform_progressed', - transformedFileCount: number, - totalFileCount: number, -} | { - type: 'global_cache_error', - error: Error, -} | { - type: 'global_cache_disabled', - reason: GlobalCacheDisabledReason, -} | { - type: 'transform_cache_reset', -} | { - type: 'worker_stdout_chunk', - chunk: string, -} | { - type: 'worker_stderr_chunk', - chunk: string, -}; - -/** - * Code across the application takes a reporter as an option and calls the - * update whenever one of the ReportableEvent happens. Code does not directly - * write to the standard output, because a build would be: - * - * 1. ad-hoc, embedded into another tool, in which case we do not want to - * pollute that tool's own output. The tool is free to present the - * warnings/progress we generate any way they want, by specifing a custom - * reporter. - * 2. run as a background process from another tool, in which case we want - * to expose updates in a way that is easily machine-readable, for example - * a JSON-stream. We don't want to pollute it with textual messages. - * - * We centralize terminal reporting into a single place because we want the - * output to be robust and consistent. The most common reporter is - * TerminalReporter, that should be the only place in the application should - * access the `terminal` module (nor the `console`). - */ -export type Reporter = { - update(event: ReportableEvent): void, -}; - -/** - * A standard way to log a warning to the terminal. This should not be called - * from some arbitrary packager logic, only from the reporters. Instead of - * calling this, add a new type of ReportableEvent instead, and implement a - * proper handler in the reporter(s). - */ -function logWarning(terminal: Terminal, format: string, ...args: Array): void { - const str = util.format(format, ...args); - terminal.log('%s: %s', chalk.yellow('warning'), str); -} - -/** - * Similar to `logWarning`, but for messages that require the user to act. - */ -function logError(terminal: Terminal, format: string, ...args: Array): void { - const str = util.format(format, ...args); - terminal.log('%s: %s', chalk.red('error'), str); -} - -/** - * A reporter that does nothing. Errors and warnings will be swallowed, that - * is generally not what you want. - */ -const nullReporter = {update() {}}; - -module.exports = { - logWarning, - logError, - nullReporter, -}; diff --git a/packager/src/node-haste/AssetModule.js b/packager/src/node-haste/AssetModule.js deleted file mode 100644 index 99c610bcfda0cb..00000000000000 --- a/packager/src/node-haste/AssetModule.js +++ /dev/null @@ -1,75 +0,0 @@ -/** - * 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. - * - * @flow - * @format - */ - -'use strict'; - -const AssetPaths = require('./lib/AssetPaths'); -const Module = require('./Module'); - -import type {CachedReadResult, ConstructorArgs, ReadResult} from './Module'; - -class AssetModule extends Module { - resolution: mixed; - _name: string; - _type: string; - _dependencies: Array; - - constructor( - args: ConstructorArgs & {dependencies: Array}, - platforms: Set, - ) { - super(args); - const {resolution, name, type} = AssetPaths.parse(this.path, platforms); - this.resolution = resolution; - this._name = name; - this._type = type; - this._dependencies = args.dependencies || []; - } - - isHaste() { - return false; - } - - readCached(): CachedReadResult { - return { - /** $FlowFixMe: improper OOP design. AssetModule, being different from a - * normal Module, shouldn't inherit it in the first place. */ - result: {dependencies: this._dependencies}, - outdatedDependencies: [], - }; - } - - /** $FlowFixMe: improper OOP design. */ - readFresh(): Promise { - return Promise.resolve({dependencies: this._dependencies}); - } - - getName() { - return super - .getName() - .then(id => id.replace(/\/[^\/]+$/, `/${this._name}.${this._type}`)); - } - - hash() { - return `AssetModule : ${this.path}`; - } - - isJSON() { - return false; - } - - isAsset() { - return true; - } -} - -module.exports = AssetModule; diff --git a/packager/src/node-haste/AssetResolutionCache.js b/packager/src/node-haste/AssetResolutionCache.js deleted file mode 100644 index afad46c6c4b66f..00000000000000 --- a/packager/src/node-haste/AssetResolutionCache.js +++ /dev/null @@ -1,134 +0,0 @@ -/** - * 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. - * - * @flow - * @format - */ - -'use strict'; - -const AssetPaths = require('./lib/AssetPaths'); -const MapWithDefaults = require('./lib/MapWithDefaults'); - -import type {AssetData} from './lib/AssetPaths'; - -type Options = {| - /** - * Files that don't match these extensions are discarded. Assets always need - * an extension. - */ - +assetExtensions: Set, - /** - * This should return all the files of the specified directory. - */ - +getDirFiles: (dirPath: string) => $ReadOnlyArray, - /** - * All the valid platforms so as to support platform extensions, ex. - * `foo.ios.png`. A platform that's no in this set will be considered part of - * the asset name. Ex. `foo.smth.png`, if `smth` is not a valid platform, will - * be resolved by its full name `foo.smth.png`. - */ - +platforms: Set, -|}; - -type AssetInfo = {|platform: ?string, fileName: string|}; -type InfoByAssetName = Map>; - -const EMPTY_ARRAY = []; - -/** - * Lazily build an index of assets for the directories in which we're looking - * for specific assets. For example if we're looking for `foo.png` in a `bar` - * directory, we'll look at all the files there and identify all the assets - * related to `foo.png`, for example `foo@2x.png` and `foo.ios.png`. - */ -class AssetResolutionCache { - _assetsByDirPath: MapWithDefaults; - _opts: Options; - - constructor(options: Options) { - this._assetsByDirPath = new MapWithDefaults(this._findAssets); - this._opts = options; - } - - /** - * The cache needs to be emptied if any file changes. This could be made more - * selective if performance demands it: for example, we could clear - * exclusively the directories in which files have changed. But that'd be - * more error-prone. - */ - clear() { - this._assetsByDirPath.clear(); - } - - /** - * Get the file paths of all the variants (resolutions, platforms, etc.) of a - * particular asset name, only looking at a specific directory. If needed this - * function could be changed to return pre-parsed information about the assets - * such as the resolution. - */ - resolve( - dirPath: string, - assetName: string, - platform: ?string, - ): $ReadOnlyArray { - const results = this._assetsByDirPath.get(dirPath); - const assets = results.get(assetName); - if (assets == null) { - return EMPTY_ARRAY; - } - return assets - .filter(asset => asset.platform == null || asset.platform === platform) - .map(asset => asset.fileName); - } - - /** - * Build an index of assets for a particular directory. Several file can - * fulfill a single asset name, for example the different resolutions or - * platforms: ex. `foo.png` could contain `foo@2x.png`, `foo.ios.js`, etc. - */ - _findAssets = (dirPath: string) => { - const results = new Map(); - const fileNames = this._opts.getDirFiles(dirPath); - for (let i = 0; i < fileNames.length; ++i) { - const fileName = fileNames[i]; - const assetData = AssetPaths.tryParse(fileName, this._opts.platforms); - if (assetData == null || !this._isValidAsset(assetData)) { - continue; - } - getWithDefaultArray(results, assetData.assetName).push({ - platform: assetData.platform, - fileName, - }); - } - return results; - }; - - _isValidAsset(assetData: AssetData): boolean { - return this._opts.assetExtensions.has(assetData.type); - } -} - -/** - * Used instead of `MapWithDefaults` so that we don't create empty arrays - * anymore once the index is built. - */ -function getWithDefaultArray( - map: Map>, - key: TK, -): Array { - let el = map.get(key); - if (el != null) { - return el; - } - el = []; - map.set(key, el); - return el; -} - -module.exports = AssetResolutionCache; diff --git a/packager/src/node-haste/DependencyGraph.js b/packager/src/node-haste/DependencyGraph.js deleted file mode 100644 index 5e1c115446bf44..00000000000000 --- a/packager/src/node-haste/DependencyGraph.js +++ /dev/null @@ -1,312 +0,0 @@ -/** - * 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. - * - * @flow - * @format - */ - -'use strict'; - -const AssetResolutionCache = require('./AssetResolutionCache'); -const DependencyGraphHelpers = require('./DependencyGraph/DependencyGraphHelpers'); -const FilesByDirNameIndex = require('./FilesByDirNameIndex'); -const JestHasteMap = require('jest-haste-map'); -const Module = require('./Module'); -const ModuleCache = require('./ModuleCache'); -const ResolutionRequest = require('./DependencyGraph/ResolutionRequest'); -const ResolutionResponse = require('./DependencyGraph/ResolutionResponse'); - -const fs = require('fs'); -const invariant = require('fbjs/lib/invariant'); -const isAbsolutePath = require('absolute-path'); -const parsePlatformFilePath = require('./lib/parsePlatformFilePath'); -const path = require('path'); -const util = require('util'); - -const { - createActionEndEntry, - createActionStartEntry, - log, -} = require('../Logger'); -const {EventEmitter} = require('events'); - -import type {Options as JSTransformerOptions} from '../JSTransformer/worker'; -import type {GlobalTransformCache} from '../lib/GlobalTransformCache'; -import type {GetTransformCacheKey} from '../lib/TransformCaching'; -import type {Reporter} from '../lib/reporting'; -import type {ModuleMap} from './DependencyGraph/ResolutionRequest'; -import type {Options as ModuleOptions, TransformCode} from './Module'; -import type {HasteFS} from './types'; - -type Options = {| - +assetDependencies: Array, - +assetExts: Array, - +extraNodeModules: ?{}, - +forceNodeFilesystemAPI: boolean, - +getTransformCacheKey: GetTransformCacheKey, - +globalTransformCache: ?GlobalTransformCache, - +ignoreFilePath: (filePath: string) => boolean, - +maxWorkerCount: number, - +moduleOptions: ModuleOptions, - +platforms: Set, - +preferNativePlatform: boolean, - +providesModuleNodeModules: Array, - +reporter: Reporter, - +resetCache: boolean, - +roots: $ReadOnlyArray, - +sourceExts: Array, - +transformCode: TransformCode, - +useWatchman: boolean, - +watch: boolean, -|}; - -const JEST_HASTE_MAP_CACHE_BREAKER = 1; - -class DependencyGraph extends EventEmitter { - _assetResolutionCache: AssetResolutionCache; - _filesByDirNameIndex: FilesByDirNameIndex; - _haste: JestHasteMap; - _hasteFS: HasteFS; - _helpers: DependencyGraphHelpers; - _moduleCache: ModuleCache; - _moduleMap: ModuleMap; - _opts: Options; - - constructor(config: {| - +opts: Options, - +haste: JestHasteMap, - +initialHasteFS: HasteFS, - +initialModuleMap: ModuleMap, - |}) { - super(); - invariant( - config.opts.maxWorkerCount >= 1, - 'worker count must be greater or equal to 1', - ); - this._opts = config.opts; - this._filesByDirNameIndex = new FilesByDirNameIndex( - config.initialHasteFS.getAllFiles(), - ); - this._assetResolutionCache = new AssetResolutionCache({ - assetExtensions: new Set(config.opts.assetExts), - getDirFiles: dirPath => this._filesByDirNameIndex.getAllFiles(dirPath), - platforms: config.opts.platforms, - }); - this._haste = config.haste; - this._hasteFS = config.initialHasteFS; - this._moduleMap = config.initialModuleMap; - this._helpers = new DependencyGraphHelpers(this._opts); - this._haste.on('change', this._onHasteChange.bind(this)); - this._moduleCache = this._createModuleCache(); - } - - static _createHaste(opts: Options): JestHasteMap { - return new JestHasteMap({ - extensions: opts.sourceExts.concat(opts.assetExts), - forceNodeFilesystemAPI: opts.forceNodeFilesystemAPI, - ignorePattern: opts.ignoreFilePath, - maxWorkers: opts.maxWorkerCount, - mocksPattern: '', - name: 'react-native-packager-' + JEST_HASTE_MAP_CACHE_BREAKER, - platforms: Array.from(opts.platforms), - providesModuleNodeModules: opts.providesModuleNodeModules, - resetCache: opts.resetCache, - retainAllFiles: true, - roots: opts.roots, - useWatchman: opts.useWatchman, - watch: opts.watch, - }); - } - - static async load(opts: Options): Promise { - const initializingPackagerLogEntry = log( - createActionStartEntry('Initializing Packager'), - ); - opts.reporter.update({type: 'dep_graph_loading'}); - const haste = DependencyGraph._createHaste(opts); - const {hasteFS, moduleMap} = await haste.build(); - log(createActionEndEntry(initializingPackagerLogEntry)); - opts.reporter.update({type: 'dep_graph_loaded'}); - return new DependencyGraph({ - haste, - initialHasteFS: hasteFS, - initialModuleMap: moduleMap, - opts, - }); - } - - _getClosestPackage(filePath: string): ?string { - const parsedPath = path.parse(filePath); - const root = parsedPath.root; - let dir = parsedPath.dir; - do { - const candidate = path.join(dir, 'package.json'); - if (this._hasteFS.exists(candidate)) { - return candidate; - } - dir = path.dirname(dir); - } while (dir !== '.' && dir !== root); - return null; - } - - _onHasteChange({eventsQueue, hasteFS, moduleMap}) { - this._hasteFS = hasteFS; - this._filesByDirNameIndex = new FilesByDirNameIndex(hasteFS.getAllFiles()); - this._assetResolutionCache.clear(); - this._moduleMap = moduleMap; - eventsQueue.forEach(({type, filePath}) => - this._moduleCache.processFileChange(type, filePath), - ); - this.emit('change'); - } - - _createModuleCache() { - const {_opts} = this; - return new ModuleCache( - { - assetDependencies: _opts.assetDependencies, - depGraphHelpers: this._helpers, - getClosestPackage: this._getClosestPackage.bind(this), - getTransformCacheKey: _opts.getTransformCacheKey, - globalTransformCache: _opts.globalTransformCache, - moduleOptions: _opts.moduleOptions, - reporter: _opts.reporter, - roots: _opts.roots, - transformCode: _opts.transformCode, - }, - _opts.platforms, - ); - } - - /** - * Returns a promise with the direct dependencies the module associated to - * the given entryPath has. - */ - getShallowDependencies( - entryPath: string, - transformOptions: JSTransformerOptions, - ): Promise> { - return this._moduleCache - .getModule(entryPath) - .getDependencies(transformOptions); - } - - getWatcher() { - return this._haste; - } - - /** - * Returns the module object for the given path. - */ - getModuleForPath(entryFile: string) { - return this._moduleCache.getModule(entryFile); - } - - getAllModules() { - return Promise.resolve(this._moduleCache.getAllModules()); - } - - getDependencies({ - entryPath, - options, - platform, - onProgress, - recursive = true, - }: { - entryPath: string, - options: T, - platform: ?string, - onProgress?: ?(finishedModules: number, totalModules: number) => mixed, - recursive: boolean, - }): Promise> { - platform = this._getRequestPlatform(entryPath, platform); - const absPath = this._getAbsolutePath(entryPath); - const dirExists = filePath => { - try { - return fs.lstatSync(filePath).isDirectory(); - } catch (e) {} - return false; - }; - const req = new ResolutionRequest({ - dirExists, - entryPath: absPath, - extraNodeModules: this._opts.extraNodeModules, - hasteFS: this._hasteFS, - helpers: this._helpers, - moduleCache: this._moduleCache, - moduleMap: this._moduleMap, - platform, - preferNativePlatform: this._opts.preferNativePlatform, - resolveAsset: (dirPath, assetName) => - this._assetResolutionCache.resolve(dirPath, assetName, platform), - sourceExts: this._opts.sourceExts, - }); - - const response = new ResolutionResponse(options); - - return req - .getOrderedDependencies({ - response, - transformOptions: options.transformer, - onProgress, - recursive, - }) - .then(() => response); - } - - matchFilesByPattern(pattern: RegExp) { - return Promise.resolve(this._hasteFS.matchFiles(pattern)); - } - - _getRequestPlatform(entryPath: string, platform: ?string): ?string { - if (platform == null) { - platform = parsePlatformFilePath(entryPath, this._opts.platforms) - .platform; - } else if (!this._opts.platforms.has(platform)) { - throw new Error('Unrecognized platform: ' + platform); - } - return platform; - } - - _getAbsolutePath(filePath: string) { - if (isAbsolutePath(filePath)) { - return path.resolve(filePath); - } - - for (let i = 0; i < this._opts.roots.length; i++) { - const root = this._opts.roots[i]; - const potentialAbsPath = path.join(root, filePath); - if (this._hasteFS.exists(potentialAbsPath)) { - return path.resolve(potentialAbsPath); - } - } - - throw new NotFoundError( - 'Cannot find entry file %s in any of the roots: %j', - filePath, - this._opts.roots, - ); - } - - createPolyfill(options: {file: string}) { - return this._moduleCache.createPolyfill(options); - } -} - -function NotFoundError(...args) { - /* $FlowFixMe: monkey-patching */ - Error.call(this); - Error.captureStackTrace(this, this.constructor); - var msg = util.format.apply(util, args); - this.message = msg; - this.type = this.name = 'NotFoundError'; - this.status = 404; -} -util.inherits(NotFoundError, Error); - -module.exports = DependencyGraph; diff --git a/packager/src/node-haste/DependencyGraph/DependencyGraphHelpers.js b/packager/src/node-haste/DependencyGraph/DependencyGraphHelpers.js deleted file mode 100644 index 83af23273a72d2..00000000000000 --- a/packager/src/node-haste/DependencyGraph/DependencyGraphHelpers.js +++ /dev/null @@ -1,57 +0,0 @@ - /** - * 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. - * - * @flow - */ - -'use strict'; - -const path = require('path'); - -const NODE_MODULES = path.sep + 'node_modules' + path.sep; - -class DependencyGraphHelpers { - - _providesModuleNodeModules: Array; - _assetExts: Array; - - constructor({providesModuleNodeModules, assetExts}: { - +providesModuleNodeModules: Array, - +assetExts: Array, - }) { - this._providesModuleNodeModules = providesModuleNodeModules; - this._assetExts = assetExts; - } - - isNodeModulesDir(file: string) { - const index = file.lastIndexOf(NODE_MODULES); - if (index === -1) { - return false; - } - - const parts = file.substr(index + 14).split(path.sep); - const dirs = this._providesModuleNodeModules; - for (let i = 0; i < dirs.length; i++) { - if (parts.indexOf(dirs[i]) > -1) { - return false; - } - } - - return true; - } - - isAssetFile(file: string) { - return this._assetExts.indexOf(this.extname(file)) !== -1; - } - - extname(name: string) { - return path.extname(name).substr(1); - } -} - -module.exports = DependencyGraphHelpers; diff --git a/packager/src/node-haste/DependencyGraph/FileNameResolver.js b/packager/src/node-haste/DependencyGraph/FileNameResolver.js deleted file mode 100644 index bf0ab3856258f2..00000000000000 --- a/packager/src/node-haste/DependencyGraph/FileNameResolver.js +++ /dev/null @@ -1,46 +0,0 @@ -/** - * 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. - * - * @flow - * @format - */ - -'use strict'; - -const path = require('path'); - -export type Options = {| - +dirPath: string, - +doesFileExist: (filePath: string) => boolean, -|}; - -/** - * When resolving a single module we want to keep track of the list of paths - * we tried to find. This class is a way to aggregate all the tries easily. - */ -class FileNameResolver { - _options: Options; - _tentativeFileNames: Array; - - constructor(options: Options) { - this._options = options; - this._tentativeFileNames = []; - } - - getTentativeFileNames(): $ReadOnlyArray { - return this._tentativeFileNames; - } - - tryToResolveFileName(fileName: string): boolean { - this._tentativeFileNames.push(fileName); - const filePath = path.join(this._options.dirPath, fileName); - return this._options.doesFileExist(filePath); - } -} - -module.exports = FileNameResolver; diff --git a/packager/src/node-haste/DependencyGraph/HasteMap.js b/packager/src/node-haste/DependencyGraph/HasteMap.js deleted file mode 100644 index df9e021c6ffee1..00000000000000 --- a/packager/src/node-haste/DependencyGraph/HasteMap.js +++ /dev/null @@ -1,216 +0,0 @@ - /** - * 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. - * - * @flow - * @format - */ - -'use strict'; - -const EventEmitter = require('events'); - -const parsePlatformFilePath = require('../lib/parsePlatformFilePath'); -const path = require('path'); -const throat = require('throat'); - -const GENERIC_PLATFORM = 'generic'; -const NATIVE_PLATFORM = 'native'; -const PACKAGE_JSON = path.sep + 'package.json'; - -import type {Moduleish, Packageish, ModuleishCache} from './ResolutionRequest'; -import type DependencyGraphHelpers from './DependencyGraphHelpers'; - -type Options = {| - extensions: Array, - files: Array, - helpers: DependencyGraphHelpers, - moduleCache: ModuleishCache, - platforms: Set, - preferNativePlatform: boolean, -|}; - -class HasteMap extends EventEmitter { - - _extensions: Array; - _files: Array; - _helpers: DependencyGraphHelpers; - _map: {}; - _moduleCache: ModuleishCache; - _packages: {}; - _platforms: Set; - _preferNativePlatform: boolean; - - constructor({ - extensions, - files, - helpers, - moduleCache, - platforms, - preferNativePlatform, - }: Options) { - super(); - this._extensions = extensions; - this._files = files; - this._helpers = helpers; - this._moduleCache = moduleCache; - this._platforms = platforms; - this._preferNativePlatform = preferNativePlatform; - - (this: any)._processHastePackage = throat(1, this._processHastePackage.bind(this)); - (this: any)._processHasteModule = throat(1, this._processHasteModule.bind(this)); - } - - build() { - this._map = Object.create(null); - this._packages = Object.create(null); - const promises = []; - this._files.forEach(filePath => { - if (!this._helpers.isNodeModulesDir(filePath)) { - if (this._extensions.indexOf(path.extname(filePath).substr(1)) !== -1) { - promises.push(this._processHasteModule(filePath)); - } - if (filePath.endsWith(PACKAGE_JSON)) { - promises.push(this._processHastePackage(filePath)); - } - } - }); - return Promise.all(promises).then(() => this._map); - } - - getAllFiles(): Array { - return this._files; - } - - processFileChange(type: string, absPath: string) { - return Promise.resolve().then(() => { - /*eslint no-labels: 0 */ - let invalidated; - if (type === 'delete' || type === 'change') { - loop: for (const name in this._map) { - const modulesMap = this._map[name]; - for (const platform in modulesMap) { - const module = modulesMap[platform]; - if (module.path === absPath) { - delete modulesMap[platform]; - invalidated = name; - break loop; - } - } - } - - if (type === 'delete') { - if (invalidated) { - this.emit('change'); - } - return null; - } - } - - if (type !== 'delete' && this._extensions.indexOf(this._helpers.extname(absPath)) !== -1) { - if (path.basename(absPath) === 'package.json') { - return this._processHastePackage(absPath, invalidated); - } else { - return this._processHasteModule(absPath, invalidated); - } - } - return null; - }); - } - - getModule(name: string, platform: ?string): ?TModule { - const modulesMap = this._map[name]; - if (modulesMap == null) { - return null; - } - - // If platform is 'ios', we prefer .ios.js to .native.js which we prefer to - // a plain .js file. - let module; - if (module == null && platform != null) { - module = modulesMap[platform]; - } - if (module == null && this._preferNativePlatform) { - module = modulesMap[NATIVE_PLATFORM]; - } - if (module == null) { - module = modulesMap[GENERIC_PLATFORM]; - } - return module; - } - - getPackage(name: string): TPackage { - return this._packages[name]; - } - - _processHasteModule(file: string, previousName: ?string) { - const module = this._moduleCache.getModule(file); - return Promise.resolve().then(() => { - const isHaste = module.isHaste(); - return isHaste && module.getName() - .then(name => { - const result = this._updateHasteMap(name, module); - if (previousName && name !== previousName) { - this.emit('change'); - } - return result; - }); - }); - } - - _processHastePackage(file: string, previousName: ?string) { - const p = this._moduleCache.getPackage(file); - return Promise.resolve().then(() => { - const isHaste = p.isHaste(); - return isHaste && p.getName() - .then(name => { - const result = this._updateHasteMap(name, p); - if (previousName && name !== previousName) { - this.emit('change'); - } - return result; - }); - }).catch(e => { - if (e instanceof SyntaxError) { - // Malformed package.json. - return; - } - throw e; - }); - } - - _updateHasteMap(name: string, mod: TModule | TPackage) { - let existingModule; - - if (mod.type === 'Package') { - existingModule = this._packages[name]; - this._packages[name] = mod; - } else { - if (this._map[name] == null) { - this._map[name] = Object.create(null); - } - const moduleMap = this._map[name]; - const modulePlatform = - parsePlatformFilePath(mod.path, this._platforms).platform || - GENERIC_PLATFORM; - existingModule = moduleMap[modulePlatform]; - moduleMap[modulePlatform] = mod; - } - - if (existingModule && existingModule.path !== mod.path) { - throw new Error( - `@providesModule naming collision:\n` + - ` Duplicate module name: ${name}\n` + - ` Paths: ${mod.path} collides with ${existingModule.path}\n\n` + - 'This error is caused by a @providesModule declaration ' + - 'with the same name across two different files.' - ); - } - } -} - -module.exports = HasteMap; diff --git a/packager/src/node-haste/DependencyGraph/ResolutionRequest.js b/packager/src/node-haste/DependencyGraph/ResolutionRequest.js deleted file mode 100644 index f2795679cd3a19..00000000000000 --- a/packager/src/node-haste/DependencyGraph/ResolutionRequest.js +++ /dev/null @@ -1,899 +0,0 @@ -/** - * 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. - * - * @flow - * @format - */ - -'use strict'; - -const AsyncTaskGroup = require('../lib/AsyncTaskGroup'); -const FileNameResolver = require('./FileNameResolver'); -const MapWithDefaults = require('../lib/MapWithDefaults'); - -const debug = require('debug')('RNP:DependencyGraph'); -const util = require('util'); -const path = require('path'); -const realPath = require('path'); -const invariant = require('fbjs/lib/invariant'); -const isAbsolutePath = require('absolute-path'); - -import type {HasteFS} from '../types'; -import type DependencyGraphHelpers from './DependencyGraphHelpers'; -import type ResolutionResponse from './ResolutionResponse'; -import type { - Options as TransformWorkerOptions, -} from '../../JSTransformer/worker'; -import type {ReadResult, CachedReadResult} from '../Module'; - -type DirExistsFn = (filePath: string) => boolean; - -/** - * `jest-haste-map`'s interface for ModuleMap. - */ -export type ModuleMap = { - getModule( - name: string, - platform: ?string, - supportsNativePlatform: boolean, - ): ?string, - getPackage( - name: string, - platform: ?string, - supportsNativePlatform: boolean, - ): ?string, -}; - -export type Packageish = { - isHaste(): boolean, - getName(): Promise, - path: string, - redirectRequire(toModuleName: string): string | false, - getMain(): string, - +root: string, -}; - -export type Moduleish = { - +path: string, - isHaste(): boolean, - getName(): Promise, - getPackage(): ?Packageish, - hash(): string, - readCached(transformOptions: TransformWorkerOptions): CachedReadResult, - readFresh(transformOptions: TransformWorkerOptions): Promise, -}; - -export type ModuleishCache = { - getPackage( - name: string, - platform?: string, - supportsNativePlatform?: boolean, - ): TPackage, - getModule(path: string): TModule, - getAssetModule(path: string): TModule, -}; - -type Options = {| - +dirExists: DirExistsFn, - +entryPath: string, - +extraNodeModules: ?Object, - +hasteFS: HasteFS, - +helpers: DependencyGraphHelpers, - +moduleCache: ModuleishCache, - +moduleMap: ModuleMap, - +platform: ?string, - +preferNativePlatform: boolean, - +resolveAsset: (dirPath: string, assetName: string) => $ReadOnlyArray, - +sourceExts: Array, -|}; - -/** - * This is a way to describe what files we tried to look for when resolving - * a module name as file. This is mainly used for error reporting, so that - * we can explain why we cannot resolve a module. - */ -type FileCandidates = - // We only tried to resolve a specific asset. - | {|+type: 'asset', +name: string|} - // We attempted to resolve a name as being a source file (ex. JavaScript, - // JSON...), in which case there can be several variants we tried, for - // example `foo.ios.js`, `foo.js`, etc. - | {|+type: 'sources', +fileNames: $ReadOnlyArray|}; - -/** - * This is a way to describe what files we tried to look for when resolving - * a module name as directory. - */ -type DirCandidates = - | {|+type: 'package', +dir: DirCandidates, +file: FileCandidates|} - | {|+type: 'index', +file: FileCandidates|}; - -type Resolution = - | {|+type: 'resolved', +module: TModule|} - | {|+type: 'failed', +candidates: TCandidates|}; - -/** - * It may not be a great pattern to leverage exception just for "trying" things - * out, notably for performance. We should consider replacing these functions - * to be nullable-returning, or being better stucture to the algorithm. - */ -function tryResolveSync(action: () => T, secondaryAction: () => T): T { - try { - return action(); - } catch (error) { - if (error.type !== 'UnableToResolveError') { - throw error; - } - return secondaryAction(); - } -} - -class ResolutionRequest { - _doesFileExist = filePath => this._options.hasteFS.exists(filePath); - _immediateResolutionCache: {[key: string]: TModule}; - _options: Options; - - static EMPTY_MODULE: string = require.resolve('./assets/empty-module.js'); - - constructor(options: Options) { - this._options = options; - this._resetResolutionCache(); - } - - _tryResolve( - action: () => Promise, - secondaryAction: () => ?Promise, - ): Promise { - return action().catch(error => { - if (error.type !== 'UnableToResolveError') { - throw error; - } - return secondaryAction(); - }); - } - - resolveDependency(fromModule: TModule, toModuleName: string): TModule { - const resHash = resolutionHash(fromModule.path, toModuleName); - - const immediateResolution = this._immediateResolutionCache[resHash]; - if (immediateResolution) { - return immediateResolution; - } - - const cacheResult = result => { - this._immediateResolutionCache[resHash] = result; - return result; - }; - - if ( - !this._options.helpers.isNodeModulesDir(fromModule.path) && - !(isRelativeImport(toModuleName) || isAbsolutePath(toModuleName)) - ) { - const result = tryResolveSync( - () => this._resolveHasteDependency(fromModule, toModuleName), - () => this._resolveNodeDependency(fromModule, toModuleName), - ); - return cacheResult(result); - } - - return cacheResult(this._resolveNodeDependency(fromModule, toModuleName)); - } - - resolveModuleDependencies( - module: TModule, - dependencyNames: $ReadOnlyArray, - ): [$ReadOnlyArray, $ReadOnlyArray] { - const dependencies = dependencyNames.map(name => - this.resolveDependency(module, name), - ); - return [dependencyNames, dependencies]; - } - - getOrderedDependencies({ - response, - transformOptions, - onProgress, - recursive = true, - }: { - response: ResolutionResponse, - transformOptions: TransformWorkerOptions, - onProgress?: ?(finishedModules: number, totalModules: number) => mixed, - recursive: boolean, - }) { - const entry = this._options.moduleCache.getModule(this._options.entryPath); - - response.pushDependency(entry); - let totalModules = 1; - let finishedModules = 0; - - let preprocessedModuleCount = 1; - if (recursive) { - this._preprocessPotentialDependencies(transformOptions, entry, count => { - if (count + 1 <= preprocessedModuleCount) { - return; - } - preprocessedModuleCount = count + 1; - if (onProgress != null) { - onProgress(finishedModules, preprocessedModuleCount); - } - }); - } - - const resolveDependencies = (module: TModule) => - Promise.resolve().then(() => { - const cached = module.readCached(transformOptions); - if (cached.result != null) { - return this.resolveModuleDependencies( - module, - cached.result.dependencies, - ); - } - return module - .readFresh(transformOptions) - .then(({dependencies}) => - this.resolveModuleDependencies(module, dependencies), - ); - }); - - const collectedDependencies: MapWithDefaults< - TModule, - Promise>, - > = new MapWithDefaults(module => collect(module)); - const crawlDependencies = (mod, [depNames, dependencies]) => { - const filteredPairs = []; - - dependencies.forEach((modDep, i) => { - const name = depNames[i]; - if (modDep == null) { - debug( - 'WARNING: Cannot find required module `%s` from module `%s`', - name, - mod.path, - ); - return false; - } - return filteredPairs.push([name, modDep]); - }); - - response.setResolvedDependencyPairs(mod, filteredPairs); - - const dependencyModules = filteredPairs.map(([, m]) => m); - const newDependencies = dependencyModules.filter( - m => !collectedDependencies.has(m), - ); - - if (onProgress) { - finishedModules += 1; - totalModules += newDependencies.length; - onProgress( - finishedModules, - Math.max(totalModules, preprocessedModuleCount), - ); - } - - if (recursive) { - // doesn't block the return of this function invocation, but defers - // the resulution of collectionsInProgress.done.then(...) - dependencyModules.forEach(dependency => - collectedDependencies.get(dependency), - ); - } - return dependencyModules; - }; - - const collectionsInProgress = new AsyncTaskGroup(); - function collect(module) { - collectionsInProgress.start(module); - const result = resolveDependencies(module).then(deps => - crawlDependencies(module, deps), - ); - const end = () => collectionsInProgress.end(module); - result.then(end, end); - return result; - } - - function resolveKeyWithPromise( - [key: TModule, promise: Promise>], - ): Promise<[TModule, Array]> { - return promise.then(value => [key, value]); - } - - return Promise.all([ - // kicks off recursive dependency discovery, but doesn't block until it's - // done - collectedDependencies.get(entry), - - // resolves when there are no more modules resolving dependencies - collectionsInProgress.done, - ]) - .then(([rootDependencies]) => { - return Promise.all( - Array.from(collectedDependencies, resolveKeyWithPromise), - ).then(moduleToDependenciesPairs => [ - rootDependencies, - new MapWithDefaults(() => [], moduleToDependenciesPairs), - ]); - }) - .then(([rootDependencies, moduleDependencies]) => { - // serialize dependencies, and make sure that every single one is only - // included once - const seen = new Set([entry]); - function traverse(dependencies) { - dependencies.forEach(dependency => { - if (seen.has(dependency)) { - return; - } - - seen.add(dependency); - response.pushDependency(dependency); - traverse(moduleDependencies.get(dependency)); - }); - } - - traverse(rootDependencies); - }); - } - - /** - * This synchronously look at all the specified modules and recursively kicks - * off global cache fetching or transforming (via `readFresh`). This is a hack - * that workaround the current structure, because we could do better. First - * off, the algorithm that resolves dependencies recursively should be - * synchronous itself until it cannot progress anymore (and needs to call - * `readFresh`), so that this algo would be integrated into it. - */ - _preprocessPotentialDependencies( - transformOptions: TransformWorkerOptions, - module: TModule, - onProgress: (moduleCount: number) => mixed, - ): void { - const visitedModulePaths = new Set(); - const pendingBatches = [ - this.preprocessModule(transformOptions, module, visitedModulePaths), - ]; - onProgress(visitedModulePaths.size); - while (pendingBatches.length > 0) { - const dependencyModules = pendingBatches.pop(); - while (dependencyModules.length > 0) { - const dependencyModule = dependencyModules.pop(); - const deps = this.preprocessModule( - transformOptions, - dependencyModule, - visitedModulePaths, - ); - pendingBatches.push(deps); - onProgress(visitedModulePaths.size); - } - } - } - - preprocessModule( - transformOptions: TransformWorkerOptions, - module: TModule, - visitedModulePaths: Set, - ): Array { - const cached = module.readCached(transformOptions); - if (cached.result == null) { - module.readFresh(transformOptions).catch(error => { - /* ignore errors, they'll be handled later if the dependency is actually - * not obsolete, and required from somewhere */ - }); - } - const dependencies = cached.result != null - ? cached.result.dependencies - : cached.outdatedDependencies; - return this.tryResolveModuleDependencies( - module, - dependencies, - visitedModulePaths, - ); - } - - tryResolveModuleDependencies( - module: TModule, - dependencyNames: $ReadOnlyArray, - visitedModulePaths: Set, - ): Array { - const result = []; - for (let i = 0; i < dependencyNames.length; ++i) { - try { - const depModule = this.resolveDependency(module, dependencyNames[i]); - if (!visitedModulePaths.has(depModule.path)) { - visitedModulePaths.add(depModule.path); - result.push(depModule); - } - } catch (error) { - if (!(error instanceof UnableToResolveError)) { - throw error; - } - } - } - return result; - } - - _resolveHasteDependency(fromModule: TModule, toModuleName: string): TModule { - toModuleName = normalizePath(toModuleName); - - const pck = fromModule.getPackage(); - let realModuleName; - if (pck) { - /* $FlowFixMe: redirectRequire can actually return `false` for - exclusions*/ - realModuleName = (pck.redirectRequire(toModuleName): string); - } else { - realModuleName = toModuleName; - } - - const modulePath = this._options.moduleMap.getModule( - realModuleName, - this._options.platform, - /* supportsNativePlatform */ true, - ); - if (modulePath != null) { - const module = this._options.moduleCache.getModule(modulePath); - /* temporary until we strengthen the typing */ - invariant(module.type === 'Module', 'expected Module type'); - return module; - } - - let packageName = realModuleName; - let packagePath; - while (packageName && packageName !== '.') { - packagePath = this._options.moduleMap.getPackage( - packageName, - this._options.platform, - /* supportsNativePlatform */ true, - ); - if (packagePath != null) { - break; - } - packageName = path.dirname(packageName); - } - - if (packagePath != null) { - const package_ = this._options.moduleCache.getPackage(packagePath); - /* temporary until we strengthen the typing */ - invariant(package_.type === 'Package', 'expected Package type'); - - const potentialModulePath = path.join( - package_.root, - path.relative(packageName, realModuleName), - ); - return tryResolveSync( - () => - this._loadAsFileOrThrow( - potentialModulePath, - fromModule, - toModuleName, - ), - () => - this._loadAsDirOrThrow(potentialModulePath, fromModule, toModuleName), - ); - } - - throw new UnableToResolveError( - fromModule, - toModuleName, - 'Unable to resolve dependency', - ); - } - - _redirectRequire(fromModule: TModule, modulePath: string): string | false { - const pck = fromModule.getPackage(); - if (pck) { - return pck.redirectRequire(modulePath); - } - return modulePath; - } - - _resolveFileOrDir(fromModule: TModule, toModuleName: string): TModule { - const potentialModulePath = isAbsolutePath(toModuleName) - ? resolveWindowsPath(toModuleName) - : path.join(path.dirname(fromModule.path), toModuleName); - - const realModuleName = this._redirectRequire( - fromModule, - potentialModulePath, - ); - if (realModuleName === false) { - return this._getEmptyModule(fromModule, toModuleName); - } - - return tryResolveSync( - () => this._loadAsFileOrThrow(realModuleName, fromModule, toModuleName), - () => this._loadAsDirOrThrow(realModuleName, fromModule, toModuleName), - ); - } - - _resolveNodeDependency(fromModule: TModule, toModuleName: string): TModule { - if (isRelativeImport(toModuleName) || isAbsolutePath(toModuleName)) { - return this._resolveFileOrDir(fromModule, toModuleName); - } - const realModuleName = this._redirectRequire(fromModule, toModuleName); - // exclude - if (realModuleName === false) { - return this._getEmptyModule(fromModule, toModuleName); - } - - if (isRelativeImport(realModuleName) || isAbsolutePath(realModuleName)) { - // derive absolute path /.../node_modules/fromModuleDir/realModuleName - const fromModuleParentIdx = - fromModule.path.lastIndexOf('node_modules' + path.sep) + 13; - const fromModuleDir = fromModule.path.slice( - 0, - fromModule.path.indexOf(path.sep, fromModuleParentIdx), - ); - const absPath = path.join(fromModuleDir, realModuleName); - return this._resolveFileOrDir(fromModule, absPath); - } - - const searchQueue = []; - for ( - let currDir = path.dirname(fromModule.path); - currDir !== '.' && currDir !== realPath.parse(fromModule.path).root; - currDir = path.dirname(currDir) - ) { - const searchPath = path.join(currDir, 'node_modules'); - searchQueue.push(path.join(searchPath, realModuleName)); - } - - const extraSearchQueue = []; - if (this._options.extraNodeModules) { - const {extraNodeModules} = this._options; - const bits = toModuleName.split(path.sep); - const packageName = bits[0]; - if (extraNodeModules[packageName]) { - bits[0] = extraNodeModules[packageName]; - extraSearchQueue.push(path.join.apply(path, bits)); - } - } - - const fullSearchQueue = searchQueue.concat(extraSearchQueue); - for (let i = 0; i < fullSearchQueue.length; ++i) { - const resolvedModule = this._tryResolveNodeDep( - fullSearchQueue[i], - fromModule, - toModuleName, - ); - if (resolvedModule != null) { - return resolvedModule; - } - } - - const displaySearchQueue = searchQueue - .filter(dirPath => this._options.dirExists(dirPath)) - .concat(extraSearchQueue); - - const hint = displaySearchQueue.length ? ' or in these directories:' : ''; - throw new UnableToResolveError( - fromModule, - toModuleName, - `Module does not exist in the module map${hint}\n` + - displaySearchQueue - .map(searchPath => ` ${path.dirname(searchPath)}\n`) - .join(', ') + - '\n' + - `This might be related to https://github.com/facebook/react-native/issues/4968\n` + - `To resolve try the following:\n` + - ` 1. Clear watchman watches: \`watchman watch-del-all\`.\n` + - ` 2. Delete the \`node_modules\` folder: \`rm -rf node_modules && npm install\`.\n` + - ' 3. Reset packager cache: `rm -fr $TMPDIR/react-*` or `npm start -- --reset-cache`.', - ); - } - - /** - * This is written as a separate function because "try..catch" blocks cause - * the entire surrounding function to be deoptimized. - */ - _tryResolveNodeDep( - searchPath: string, - fromModule: TModule, - toModuleName: string, - ): ?TModule { - try { - return tryResolveSync( - () => this._loadAsFileOrThrow(searchPath, fromModule, toModuleName), - () => this._loadAsDirOrThrow(searchPath, fromModule, toModuleName), - ); - } catch (error) { - if (error.type !== 'UnableToResolveError') { - throw error; - } - return null; - } - } - - /** - * Eventually we'd like to remove all the exception being throw in the middle - * of the resolution algorithm, instead keeping track of tentatives in a - * specific data structure, and building a proper error at the top-level. - * This function is meant to be a temporary proxy for _loadAsFile until - * the callsites switch to that tracking structure. - */ - _loadAsFileOrThrow( - basePath: string, - fromModule: TModule, - toModule: string, - ): TModule { - const dirPath = path.dirname(basePath); - const fileNameHint = path.basename(basePath); - const result = this._loadAsFile(dirPath, fileNameHint); - if (result.type === 'resolved') { - return result.module; - } - if (result.candidates.type === 'asset') { - const msg = - `Directory \`${dirPath}' doesn't contain asset ` + - `\`${result.candidates.name}'`; - throw new UnableToResolveError(fromModule, toModule, msg); - } - invariant(result.candidates.type === 'sources', 'invalid candidate type'); - const msg = - `Could not resolve the base path \`${basePath}' into a module. The ` + - `folder \`${dirPath}' was searched for one of these files: ` + - result.candidates.fileNames.map(filePath => `\`${filePath}'`).join(', ') + - '.'; - throw new UnableToResolveError(fromModule, toModule, msg); - } - - _loadAsFile( - dirPath: string, - fileNameHint: string, - ): Resolution { - if (this._options.helpers.isAssetFile(fileNameHint)) { - return this._loadAsAssetFile(dirPath, fileNameHint); - } - const doesFileExist = this._doesFileExist; - const resolver = new FileNameResolver({doesFileExist, dirPath}); - const fileName = this._tryToResolveAllFileNames(resolver, fileNameHint); - if (fileName != null) { - const filePath = path.join(dirPath, fileName); - const module = this._options.moduleCache.getModule(filePath); - return {type: 'resolved', module}; - } - const fileNames = resolver.getTentativeFileNames(); - return {type: 'failed', candidates: {type: 'sources', fileNames}}; - } - - _loadAsAssetFile( - dirPath: string, - fileNameHint: string, - ): Resolution { - const assetNames = this._options.resolveAsset(dirPath, fileNameHint); - const assetName = getArrayLowestItem(assetNames); - if (assetName != null) { - const assetPath = path.join(dirPath, assetName); - return { - type: 'resolved', - module: this._options.moduleCache.getAssetModule(assetPath), - }; - } - return { - type: 'failed', - candidates: {type: 'asset', name: fileNameHint}, - }; - } - - /** - * A particular 'base path' can resolve to a number of possibilities depending - * on the context. For example `foo/bar` could resolve to `foo/bar.ios.js`, or - * to `foo/bar.js`. If can also resolve to the bare path `foo/bar` itself, as - * supported by Node.js resolution. On the other hand it doesn't support - * `foo/bar.ios`, for historical reasons. - */ - _tryToResolveAllFileNames( - resolver: FileNameResolver, - fileNamePrefix: string, - ): ?string { - if (resolver.tryToResolveFileName(fileNamePrefix)) { - return fileNamePrefix; - } - const {sourceExts} = this._options; - for (let i = 0; i < sourceExts.length; i++) { - const fileName = this._tryToResolveFileNamesForExt( - fileNamePrefix, - resolver, - sourceExts[i], - ); - if (fileName != null) { - return fileName; - } - } - return null; - } - - /** - * For a particular extension, ex. `js`, we want to try a few possibilities, - * such as `foo.ios.js`, `foo.native.js`, and of course `foo.js`. - */ - _tryToResolveFileNamesForExt( - fileNamePrefix: string, - resolver: FileNameResolver, - ext: string, - ): ?string { - const {platform, preferNativePlatform} = this._options; - if (platform != null) { - const fileName = `${fileNamePrefix}.${platform}.${ext}`; - if (resolver.tryToResolveFileName(fileName)) { - return fileName; - } - } - if (preferNativePlatform) { - const fileName = `${fileNamePrefix}.native.${ext}`; - if (resolver.tryToResolveFileName(fileName)) { - return fileName; - } - } - const fileName = `${fileNamePrefix}.${ext}`; - return resolver.tryToResolveFileName(fileName) ? fileName : null; - } - - _getEmptyModule(fromModule: TModule, toModuleName: string): TModule { - const {moduleCache} = this._options; - const module = moduleCache.getModule(ResolutionRequest.EMPTY_MODULE); - if (module != null) { - return module; - } - throw new UnableToResolveError( - fromModule, - toModuleName, - "could not resolve `${ResolutionRequest.EMPTY_MODULE}'", - ); - } - - /** - * Same as `_loadAsDir`, but throws instead of returning candidates in case of - * failure. We want to migrate all the callsites to `_loadAsDir` eventually. - */ - _loadAsDirOrThrow( - potentialDirPath: string, - fromModule: TModule, - toModuleName: string, - ): TModule { - const result = this._loadAsDir(potentialDirPath); - if (result.type === 'resolved') { - return result.module; - } - if (result.candidates.type === 'package') { - throw new UnableToResolveError( - fromModule, - toModuleName, - `could not resolve \`${potentialDirPath}' as a folder: it contained ` + - 'a package, but its "main" could not be resolved', - ); - } - invariant(result.candidates.type === 'index', 'invalid candidate type'); - throw new UnableToResolveError( - fromModule, - toModuleName, - `could not resolve \`${potentialDirPath}' as a folder: it did not ` + - 'contain a package, nor an index file', - ); - } - - /** - * Try to resolve a potential path as if it was a directory-based module. - * Either this is a directory that contains a package, or that the directory - * contains an index file. If it fails to resolve these options, it returns - * `null` and fills the array of `candidates` that were tried. - * - * For example we could try to resolve `/foo/bar`, that would eventually - * resolve to `/foo/bar/lib/index.ios.js` if we're on platform iOS and that - * `bar` contains a package which entry point is `./lib/index` (or `./lib`). - */ - _loadAsDir(potentialDirPath: string): Resolution { - const packageJsonPath = path.join(potentialDirPath, 'package.json'); - if (this._options.hasteFS.exists(packageJsonPath)) { - return this._loadAsPackage(packageJsonPath); - } - const result = this._loadAsFile(potentialDirPath, 'index'); - if (result.type === 'resolved') { - return result; - } - return { - type: 'failed', - candidates: {type: 'index', file: result.candidates}, - }; - } - - /** - * Right now we just consider it a failure to resolve if we couldn't find the - * file corresponding to the `main` indicated by a package. Argument can be - * made this should be changed so that failing to find the `main` is not a - * resolution failure, but identified instead as a corrupted or invalid - * package (or that a package only supports a specific platform, etc.) - */ - _loadAsPackage(packageJsonPath: string): Resolution { - const package_ = this._options.moduleCache.getPackage(packageJsonPath); - const mainPrefixPath = package_.getMain(); - const dirPath = path.dirname(mainPrefixPath); - const prefixName = path.basename(mainPrefixPath); - const fileResult = this._loadAsFile(dirPath, prefixName); - if (fileResult.type === 'resolved') { - return fileResult; - } - const dirResult = this._loadAsDir(mainPrefixPath); - if (dirResult.type === 'resolved') { - return dirResult; - } - return { - type: 'failed', - candidates: { - type: 'package', - dir: dirResult.candidates, - file: fileResult.candidates, - }, - }; - } - - _resetResolutionCache() { - this._immediateResolutionCache = Object.create(null); - } -} - -function resolutionHash(modulePath, depName) { - return `${path.resolve(modulePath)}:${depName}`; -} - -class UnableToResolveError extends Error { - type: string; - from: string; - to: string; - - constructor(fromModule, toModule, message) { - super(); - this.from = fromModule.path; - this.to = toModule; - this.message = util.format( - 'Unable to resolve module `%s` from `%s`: %s', - toModule, - fromModule.path, - message, - ); - this.type = this.name = 'UnableToResolveError'; - } -} - -function normalizePath(modulePath) { - if (path.sep === '/') { - modulePath = path.normalize(modulePath); - } else if (path.posix) { - modulePath = path.posix.normalize(modulePath); - } - - return modulePath.replace(/\/$/, ''); -} - -// HasteFS stores paths with backslashes on Windows, this ensures the path is in -// the proper format. Will also add drive letter if not present so `/root` will -// resolve to `C:\root`. Noop on other platforms. -function resolveWindowsPath(modulePath) { - if (path.sep !== '\\') { - return modulePath; - } - return path.resolve(modulePath); -} - -function isRelativeImport(filePath) { - return /^[.][.]?(?:[/]|$)/.test(filePath); -} - -function getArrayLowestItem(a: $ReadOnlyArray): string | void { - if (a.length === 0) { - return undefined; - } - let lowest = a[0]; - for (let i = 1; i < a.length; ++i) { - if (a[i] < lowest) { - lowest = a[i]; - } - } - return lowest; -} - -module.exports = ResolutionRequest; diff --git a/packager/src/node-haste/DependencyGraph/ResolutionResponse.js b/packager/src/node-haste/DependencyGraph/ResolutionResponse.js deleted file mode 100644 index 1b3eca62579afe..00000000000000 --- a/packager/src/node-haste/DependencyGraph/ResolutionResponse.js +++ /dev/null @@ -1,128 +0,0 @@ - /** - * 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. - * - * @flow - */ - -'use strict'; - -const NO_OPTIONS = {}; - -class ResolutionResponse { - - dependencies: Array; - mainModuleId: ?(number | string); - mocks: mixed; - numPrependedDependencies: number; - options: TOptions; - - // This is monkey-patched from Resolver. - getModuleId: ?() => number; - - _mappings: {[hash: string]: Array<[string, TModule]>}; - _finalized: boolean; - _mainModule: ?TModule; - - constructor(options: TOptions) { - this.dependencies = []; - this.mainModuleId = null; - this.mocks = null; - this.numPrependedDependencies = 0; - this.options = options; - this._mappings = Object.create(null); - this._finalized = false; - } - - copy(properties: { - dependencies?: Array, - mainModuleId?: number, - mocks?: mixed, - }): ResolutionResponse { - const { - dependencies = this.dependencies, - mainModuleId = this.mainModuleId, - mocks = this.mocks, - } = properties; - - const numPrependedDependencies = dependencies === this.dependencies - ? this.numPrependedDependencies : 0; - - /* $FlowFixMe: Flow doesn't like Object.assign on class-made objects. */ - return Object.assign( - new this.constructor(this.options), - this, - { - dependencies, - mainModuleId, - mocks, - numPrependedDependencies, - }, - ); - } - - _assertNotFinalized() { - if (this._finalized) { - throw new Error('Attempted to mutate finalized response.'); - } - } - - _assertFinalized() { - if (!this._finalized) { - throw new Error('Attempted to access unfinalized response.'); - } - } - - finalize(): Promise { - /* $FlowFixMe: _mainModule is not initialized in the constructor. */ - return this._mainModule.getName().then(id => { - this.mainModuleId = id; - this._finalized = true; - return this; - }); - } - - pushDependency(module: TModule) { - this._assertNotFinalized(); - if (this.dependencies.length === 0) { - this._mainModule = module; - } - - this.dependencies.push(module); - } - - prependDependency(module: TModule) { - this._assertNotFinalized(); - this.dependencies.unshift(module); - this.numPrependedDependencies += 1; - } - - setResolvedDependencyPairs( - module: TModule, - pairs: Array<[string, TModule]>, - options: {ignoreFinalized?: boolean} = NO_OPTIONS, - ) { - if (!options.ignoreFinalized) { - this._assertNotFinalized(); - } - const hash = module.hash(); - if (this._mappings[hash] == null) { - this._mappings[hash] = pairs; - } - } - - setMocks(mocks: mixed) { - this.mocks = mocks; - } - - getResolvedDependencyPairs(module: TModule): $ReadOnlyArray<[string, TModule]> { - this._assertFinalized(); - return this._mappings[module.hash()]; - } -} - -module.exports = ResolutionResponse; diff --git a/packager/src/node-haste/DependencyGraph/assets/empty-module.js b/packager/src/node-haste/DependencyGraph/assets/empty-module.js deleted file mode 100644 index 1c98b33701bdb4..00000000000000 --- a/packager/src/node-haste/DependencyGraph/assets/empty-module.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * 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. - */ diff --git a/packager/src/node-haste/DependencyGraph/docblock.js b/packager/src/node-haste/DependencyGraph/docblock.js deleted file mode 100644 index 354e85176c248d..00000000000000 --- a/packager/src/node-haste/DependencyGraph/docblock.js +++ /dev/null @@ -1,83 +0,0 @@ -/** - * 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. - * - * @flow - * @format - */ - -'use strict'; - -var docblockRe = /^\s*(\/\*\*(.|\r?\n)*?\*\/)/; - -var ltrimRe = /^\s*/; -/** - * @param {String} contents - * @return {String} - */ -function extract(contents: string): string { - var match = contents.match(docblockRe); - if (match) { - return match[0].replace(ltrimRe, '') || ''; - } - return ''; -} - -var commentStartRe = /^\/\*\*/; -var commentEndRe = /\*\/$/; -var wsRe = /[\t ]+/g; -var stringStartRe = /(\r?\n|^) *\*/g; -var multilineRe = /(?:^|\r?\n) *(@[^\r\n]*?) *\r?\n *([^@\r\n\s][^@\r\n]+?) *\r?\n/g; -var propertyRe = /(?:^|\r?\n) *@(\S+) *([^\r\n]*)/g; - -/** - * @param {String} contents - * @return {Array} - */ -function parse(docblock: string): Array<[string, string]> { - docblock = docblock - .replace(commentStartRe, '') - .replace(commentEndRe, '') - .replace(wsRe, ' ') - .replace(stringStartRe, '$1'); - - // Normalize multi-line directives - var prev = ''; - while (prev !== docblock) { - prev = docblock; - docblock = docblock.replace(multilineRe, '\n$1 $2\n'); - } - docblock = docblock.trim(); - - var result = []; - var match; - while ((match = propertyRe.exec(docblock))) { - result.push([match[1], match[2]]); - } - - return result; -} - -/** - * Same as parse but returns an object of prop: value instead of array of paris - * If a property appers more than once the last one will be returned - * - * @param {String} contents - * @return {Object} - */ -function parseAsObject(docblock: string): {[string]: string} { - var pairs = parse(docblock); - var result = {}; - for (var i = 0; i < pairs.length; i++) { - result[pairs[i][0]] = pairs[i][1]; - } - return result; -} - -exports.extract = extract; -exports.parse = parse; -exports.parseAsObject = parseAsObject; diff --git a/packager/src/node-haste/FilesByDirNameIndex.js b/packager/src/node-haste/FilesByDirNameIndex.js deleted file mode 100644 index 440a8e09f67f14..00000000000000 --- a/packager/src/node-haste/FilesByDirNameIndex.js +++ /dev/null @@ -1,48 +0,0 @@ -/** - * 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. - * - * @flow - * @format - */ - -'use strict'; - -const path = require('path'); - -/** - * This is a way to find files quickly given a RegExp, in a specific directory. - * This is must faster than iterating over all the files and matching both - * directory and RegExp at the same time. - * - * This was first implemented to support finding assets fast, for which we know - * the directory, but we want to identify all variants (ex. @2x, @1x, for - * a picture's different definition levels). - */ -class FilesByDirNameIndex { - _filesByDirName: Map>; - - constructor(allFilePaths: Array) { - this._filesByDirName = new Map(); - for (let i = 0; i < allFilePaths.length; ++i) { - const filePath = allFilePaths[i]; - const dirName = path.dirname(filePath); - let dir = this._filesByDirName.get(dirName); - if (dir === undefined) { - dir = []; - this._filesByDirName.set(dirName, dir); - } - dir.push(path.basename(filePath)); - } - } - - getAllFiles(dirPath: string): $ReadOnlyArray { - return this._filesByDirName.get(dirPath) || []; - } -} - -module.exports = FilesByDirNameIndex; diff --git a/packager/src/node-haste/Module.js b/packager/src/node-haste/Module.js deleted file mode 100644 index fa11ef95df1b27..00000000000000 --- a/packager/src/node-haste/Module.js +++ /dev/null @@ -1,443 +0,0 @@ -/** - * 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. - * - * @flow - * @format - */ - -'use strict'; - -const crypto = require('crypto'); -const docblock = require('./DependencyGraph/docblock'); -const fs = require('fs'); -const invariant = require('fbjs/lib/invariant'); -const isAbsolutePath = require('absolute-path'); -const jsonStableStringify = require('json-stable-stringify'); - -const {join: joinPath, relative: relativePath, extname} = require('path'); - -import type { - TransformedCode, - Options as WorkerOptions, -} from '../JSTransformer/worker'; -import type {GlobalTransformCache} from '../lib/GlobalTransformCache'; -import type {MappingsMap} from '../lib/SourceMap'; -import type { - TransformCache, - GetTransformCacheKey, - ReadTransformProps, -} from '../lib/TransformCaching'; -import type {Reporter} from '../lib/reporting'; -import type DependencyGraphHelpers - from './DependencyGraph/DependencyGraphHelpers'; -import type ModuleCache from './ModuleCache'; -import type {LocalPath} from './lib/toLocalPath'; - -export type ReadResult = { - +code: string, - +dependencies: Array, - +dependencyOffsets?: ?Array, - +map?: ?MappingsMap, - +source: string, -}; - -export type CachedReadResult = {| - +result: ?ReadResult, - +outdatedDependencies: $ReadOnlyArray, -|}; - -export type TransformCode = ( - module: Module, - sourceCode: string, - transformOptions: WorkerOptions, -) => Promise; - -export type HasteImpl = { - getHasteName(filePath: string): string | void, - // This exists temporarily to enforce consistency while we deprecate - // @providesModule. - enforceHasteNameMatches?: ( - filePath: string, - expectedName: string | void, - ) => void, -}; - -export type Options = { - hasteImpl?: HasteImpl, - resetCache?: boolean, - transformCache: TransformCache, -}; - -export type ConstructorArgs = { - depGraphHelpers: DependencyGraphHelpers, - file: string, - getTransformCacheKey: GetTransformCacheKey, - globalTransformCache: ?GlobalTransformCache, - localPath: LocalPath, - moduleCache: ModuleCache, - options: Options, - reporter: Reporter, - transformCode: ?TransformCode, -}; - -type DocBlock = {+[key: string]: string}; - -class Module { - localPath: LocalPath; - path: string; - type: string; - - _moduleCache: ModuleCache; - _transformCode: ?TransformCode; - _getTransformCacheKey: GetTransformCacheKey; - _depGraphHelpers: DependencyGraphHelpers; - _options: Options; - _reporter: Reporter; - _globalCache: ?GlobalTransformCache; - - _docBlock: ?DocBlock; - _hasteNameCache: ?{+hasteName: ?string}; - _sourceCode: ?string; - _readPromises: Map>; - - _readResultsByOptionsKey: Map; - - static _transformCache: TransformCache; - - constructor({ - depGraphHelpers, - localPath, - file, - getTransformCacheKey, - globalTransformCache, - moduleCache, - options, - reporter, - transformCode, - }: ConstructorArgs) { - if (!isAbsolutePath(file)) { - throw new Error('Expected file to be absolute path but got ' + file); - } - - this.localPath = localPath; - this.path = file; - this.type = 'Module'; - - this._moduleCache = moduleCache; - this._transformCode = transformCode; - this._getTransformCacheKey = getTransformCacheKey; - this._depGraphHelpers = depGraphHelpers; - this._options = options || {}; - this._reporter = reporter; - this._globalCache = globalTransformCache; - - this._readPromises = new Map(); - this._readResultsByOptionsKey = new Map(); - } - - isHaste(): boolean { - return this._getHasteName() != null; - } - - getCode(transformOptions: WorkerOptions) { - return this.read(transformOptions).then(({code}) => code); - } - - getMap(transformOptions: WorkerOptions) { - return this.read(transformOptions).then(({map}) => map); - } - - getName(): Promise { - return Promise.resolve().then(() => { - const name = this._getHasteName(); - if (name != null) { - return name; - } - - const p = this.getPackage(); - - if (!p) { - // Name is full path - return this.path; - } - - return p.getName().then(packageName => { - if (!packageName) { - return this.path; - } - - return joinPath(packageName, relativePath(p.root, this.path)).replace( - /\\/g, - '/', - ); - }); - }); - } - - getPackage() { - return this._moduleCache.getPackageForModule(this); - } - - getDependencies(transformOptions: WorkerOptions) { - return this.read(transformOptions).then(({dependencies}) => dependencies); - } - - /** - * We don't need to invalidate the TranformCache itself because it guarantees - * itself that if a source code changed we won't return the cached transformed - * code. - */ - invalidate() { - this._readPromises.clear(); - this._readResultsByOptionsKey.clear(); - this._sourceCode = null; - this._docBlock = null; - this._hasteNameCache = null; - } - - _readSourceCode(): string { - if (this._sourceCode == null) { - this._sourceCode = fs.readFileSync(this.path, 'utf8'); - } - return this._sourceCode; - } - - _readDocBlock(): DocBlock { - if (this._docBlock == null) { - this._docBlock = docblock.parseAsObject(this._readSourceCode()); - } - return this._docBlock; - } - - _getHasteName(): ?string { - if (this._hasteNameCache == null) { - this._hasteNameCache = {hasteName: this._readHasteName()}; - } - return this._hasteNameCache.hasteName; - } - - /** - * If a custom Haste implementation is provided, then we use it to determine - * the actual Haste name instead of "@providesModule". - * `enforceHasteNameMatches` has been added to that it is easier to - * transition from a system using "@providesModule" to a system using another - * custom system, by throwing if inconsistencies are detected. For example, - * we could verify that the file's basename (ex. "bar/foo.js") is the same as - * the "@providesModule" name (ex. "foo"). - */ - _readHasteName(): ?string { - const hasteImpl = this._options.hasteImpl; - if (hasteImpl == null) { - return this._readHasteNameFromDocBlock(); - } - const {enforceHasteNameMatches} = hasteImpl; - if (enforceHasteNameMatches != null) { - const name = this._readHasteNameFromDocBlock(); - enforceHasteNameMatches(this.path, name || undefined); - } - return hasteImpl.getHasteName(this.path); - } - - /** - * We extract the Haste name from the `@providesModule` docbloc field. This is - * not allowed for modules living in `node_modules`, except if they are - * whitelisted. - */ - _readHasteNameFromDocBlock(): ?string { - const moduleDocBlock = this._readDocBlock(); - const {providesModule} = moduleDocBlock; - if (providesModule && !this._depGraphHelpers.isNodeModulesDir(this.path)) { - return /^\S+/.exec(providesModule)[0]; - } - return null; - } - - /** - * To what we read from the cache or worker, we need to add id and source. - */ - _finalizeReadResult(source: string, result: TransformedCode): ReadResult { - return {...result, id: this._getHasteName(), source}; - } - - async _transformCodeFor( - cacheProps: ReadTransformProps, - ): Promise { - const {_transformCode} = this; - invariant(_transformCode != null, 'missing code transform funtion'); - const {sourceCode, transformOptions} = cacheProps; - return await _transformCode(this, sourceCode, transformOptions); - } - - async _transformAndStoreCodeGlobally( - cacheProps: ReadTransformProps, - globalCache: GlobalTransformCache, - ): Promise { - const result = await this._transformCodeFor(cacheProps); - globalCache.store(cacheProps, result); - return result; - } - - async _getTransformedCode( - cacheProps: ReadTransformProps, - ): Promise { - const {_globalCache} = this; - if (_globalCache == null || !_globalCache.shouldFetch(cacheProps)) { - return await this._transformCodeFor(cacheProps); - } - const globalCachedResult = await _globalCache.fetch(cacheProps); - if (globalCachedResult != null) { - return globalCachedResult; - } - return await this._transformAndStoreCodeGlobally(cacheProps, _globalCache); - } - - async _getAndCacheTransformedCode( - cacheProps: ReadTransformProps, - ): Promise { - const result = await this._getTransformedCode(cacheProps); - this._options.transformCache.writeSync({...cacheProps, result}); - return result; - } - - /** - * Shorthand for reading both from cache or from fresh for all call sites that - * are asynchronous by default. - */ - read(transformOptions: WorkerOptions): Promise { - return Promise.resolve().then(() => { - const cached = this.readCached(transformOptions); - if (cached.result != null) { - return cached.result; - } - return this.readFresh(transformOptions); - }); - } - - /** - * Same as `readFresh`, but reads from the cache instead of transforming - * the file from source. This has the benefit of being synchronous. As a - * result it is possible to read many cached Module in a row, synchronously. - */ - readCached(transformOptions: WorkerOptions): CachedReadResult { - const key = stableObjectHash(transformOptions || {}); - let result = this._readResultsByOptionsKey.get(key); - if (result != null) { - return result; - } - result = this._readFromTransformCache(transformOptions, key); - this._readResultsByOptionsKey.set(key, result); - return result; - } - - /** - * Read again from the TransformCache, on disk. `readCached` should be favored - * so it's faster in case the results are already in memory. - */ - _readFromTransformCache( - transformOptions: WorkerOptions, - transformOptionsKey: string, - ): CachedReadResult { - const cacheProps = this._getCacheProps( - transformOptions, - transformOptionsKey, - ); - const cachedResult = this._options.transformCache.readSync(cacheProps); - if (cachedResult.result == null) { - return { - result: null, - outdatedDependencies: cachedResult.outdatedDependencies, - }; - } - return { - result: this._finalizeReadResult( - cacheProps.sourceCode, - cachedResult.result, - ), - outdatedDependencies: [], - }; - } - - /** - * Gathers relevant data about a module: source code, transformed code, - * dependencies, etc. This function reads and transforms the source from - * scratch. We don't repeat the same work as `readCached` because we assume - * call sites have called it already. - */ - readFresh(transformOptions: WorkerOptions): Promise { - const key = stableObjectHash(transformOptions || {}); - const promise = this._readPromises.get(key); - if (promise != null) { - return promise; - } - const freshPromise = (async () => { - const cacheProps = this._getCacheProps(transformOptions, key); - const freshResult = await this._getAndCacheTransformedCode(cacheProps); - const finalResult = this._finalizeReadResult( - cacheProps.sourceCode, - freshResult, - ); - this._readResultsByOptionsKey.set(key, { - result: finalResult, - outdatedDependencies: [], - }); - return finalResult; - })(); - this._readPromises.set(key, freshPromise); - return freshPromise; - } - - _getCacheProps(transformOptions: WorkerOptions, transformOptionsKey: string) { - const sourceCode = this._readSourceCode(); - const getTransformCacheKey = this._getTransformCacheKey; - return { - filePath: this.path, - localPath: this.localPath, - sourceCode, - getTransformCacheKey, - transformOptions, - transformOptionsKey, - cacheOptions: { - resetCache: this._options.resetCache, - reporter: this._reporter, - }, - }; - } - - hash() { - return `Module : ${this.path}`; - } - - isJSON() { - return extname(this.path) === '.json'; - } - - isAsset() { - return false; - } - - isPolyfill() { - return false; - } -} - -// use weak map to speed up hash creation of known objects -const knownHashes = new WeakMap(); -function stableObjectHash(object) { - let digest = knownHashes.get(object); - if (!digest) { - digest = crypto - .createHash('md5') - .update(jsonStableStringify(object)) - .digest('base64'); - knownHashes.set(object, digest); - } - - return digest; -} - -module.exports = Module; diff --git a/packager/src/node-haste/ModuleCache.js b/packager/src/node-haste/ModuleCache.js deleted file mode 100644 index 7d94509e833f47..00000000000000 --- a/packager/src/node-haste/ModuleCache.js +++ /dev/null @@ -1,186 +0,0 @@ -/** - * Copyright (c) 2013-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. - * - * @flow - * @format - */ - -'use strict'; - -const AssetModule = require('./AssetModule'); -const Module = require('./Module'); -const Package = require('./Package'); -const Polyfill = require('./Polyfill'); - -const toLocalPath = require('./lib/toLocalPath'); - -import type {GlobalTransformCache} from '../lib/GlobalTransformCache'; -import type {GetTransformCacheKey} from '../lib/TransformCaching'; -import type {Reporter} from '../lib/reporting'; -import type DependencyGraphHelpers - from './DependencyGraph/DependencyGraphHelpers'; -import type {TransformCode, Options as ModuleOptions} from './Module'; - -type GetClosestPackageFn = (filePath: string) => ?string; - -class ModuleCache { - _assetDependencies: Array; - _depGraphHelpers: DependencyGraphHelpers; - _getClosestPackage: GetClosestPackageFn; - _getTransformCacheKey: GetTransformCacheKey; - _globalTransformCache: ?GlobalTransformCache; - _moduleCache: {[filePath: string]: Module}; - _moduleOptions: ModuleOptions; - _packageCache: {[filePath: string]: Package}; - _packageModuleMap: WeakMap; - _platforms: Set; - _transformCode: TransformCode; - _reporter: Reporter; - _roots: $ReadOnlyArray; - - constructor( - { - assetDependencies, - depGraphHelpers, - extractRequires, - getClosestPackage, - getTransformCacheKey, - globalTransformCache, - moduleOptions, - roots, - reporter, - transformCode, - }: {| - assetDependencies: Array, - depGraphHelpers: DependencyGraphHelpers, - getClosestPackage: GetClosestPackageFn, - getTransformCacheKey: GetTransformCacheKey, - globalTransformCache: ?GlobalTransformCache, - moduleOptions: ModuleOptions, - roots: $ReadOnlyArray, - reporter: Reporter, - transformCode: TransformCode, - |}, - platforms: Set, - ) { - this._assetDependencies = assetDependencies; - this._getClosestPackage = getClosestPackage; - this._getTransformCacheKey = getTransformCacheKey; - this._globalTransformCache = globalTransformCache; - this._depGraphHelpers = depGraphHelpers; - this._moduleCache = Object.create(null); - this._moduleOptions = moduleOptions; - this._packageCache = Object.create(null); - this._packageModuleMap = new WeakMap(); - this._platforms = platforms; - this._transformCode = transformCode; - this._reporter = reporter; - this._roots = roots; - } - - getModule(filePath: string): Module { - if (!this._moduleCache[filePath]) { - this._moduleCache[filePath] = new Module({ - depGraphHelpers: this._depGraphHelpers, - file: filePath, - getTransformCacheKey: this._getTransformCacheKey, - globalTransformCache: this._globalTransformCache, - localPath: toLocalPath(this._roots, filePath), - moduleCache: this, - options: this._moduleOptions, - reporter: this._reporter, - transformCode: this._transformCode, - }); - } - return this._moduleCache[filePath]; - } - - getAllModules() { - return this._moduleCache; - } - - getAssetModule(filePath: string) { - if (!this._moduleCache[filePath]) { - /* FixMe: AssetModule does not need all these options. This is because - * this is an incorrect OOP design in the first place: AssetModule, being - * simpler than a normal Module, should not inherit the Module class. - */ - this._moduleCache[filePath] = new AssetModule( - { - depGraphHelpers: this._depGraphHelpers, - dependencies: this._assetDependencies, - file: filePath, - getTransformCacheKey: this._getTransformCacheKey, - globalTransformCache: null, - localPath: toLocalPath(this._roots, filePath), - moduleCache: this, - options: this._moduleOptions, - reporter: this._reporter, - transformCode: this._transformCode, - }, - this._platforms, - ); - } - return this._moduleCache[filePath]; - } - - getPackage(filePath: string): Package { - if (!this._packageCache[filePath]) { - this._packageCache[filePath] = new Package({ - file: filePath, - }); - } - return this._packageCache[filePath]; - } - - getPackageForModule(module: Module): ?Package { - if (this._packageModuleMap.has(module)) { - const packagePath = this._packageModuleMap.get(module); - // $FlowFixMe(>=0.37.0) - if (this._packageCache[packagePath]) { - return this._packageCache[packagePath]; - } else { - this._packageModuleMap.delete(module); - } - } - - const packagePath = this._getClosestPackage(module.path); - if (!packagePath) { - return null; - } - - this._packageModuleMap.set(module, packagePath); - return this.getPackage(packagePath); - } - - createPolyfill({file}: {file: string}) { - /* $FlowFixMe: there are missing arguments. */ - return new Polyfill({ - depGraphHelpers: this._depGraphHelpers, - file, - getTransformCacheKey: this._getTransformCacheKey, - localPath: toLocalPath(this._roots, file), - moduleCache: this, - options: this._moduleOptions, - transformCode: this._transformCode, - }); - } - - processFileChange(type: string, filePath: string) { - if (this._moduleCache[filePath]) { - this._moduleCache[filePath].invalidate(); - delete this._moduleCache[filePath]; - } - if (this._packageCache[filePath]) { - this._packageCache[filePath].invalidate(); - delete this._packageCache[filePath]; - } - } -} - -module.exports = ModuleCache; diff --git a/packager/src/node-haste/Package.js b/packager/src/node-haste/Package.js deleted file mode 100644 index 019047048fac1e..00000000000000 --- a/packager/src/node-haste/Package.js +++ /dev/null @@ -1,159 +0,0 @@ -/** - * Copyright (c) 2013-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. - * - * @flow - * @format - */ - -'use strict'; - -const fs = require('fs'); -const isAbsolutePath = require('absolute-path'); -const path = require('path'); - -type PackageContent = { - name: string, - 'react-native': mixed, - browser: mixed, - main: ?string, -}; - -class Package { - path: string; - root: string; - type: string; - - _content: ?PackageContent; - - constructor({file}: {file: string}) { - this.path = path.resolve(file); - this.root = path.dirname(this.path); - this.type = 'Package'; - this._content = null; - } - - getMain(): string { - const json = this.read(); - var replacements = getReplacements(json); - if (typeof replacements === 'string') { - return path.join(this.root, replacements); - } - - let main = json.main || 'index'; - - if (replacements && typeof replacements === 'object') { - main = - replacements[main] || - replacements[main + '.js'] || - replacements[main + '.json'] || - replacements[main.replace(/(\.js|\.json)$/, '')] || - main; - } - - /* $FlowFixMe: `getReplacements` doesn't validate the return value. */ - return path.join(this.root, main); - } - - isHaste(): boolean { - return !!this.read().name; - } - - getName(): Promise { - return Promise.resolve().then(() => this.read().name); - } - - invalidate() { - this._content = null; - } - - redirectRequire(name: string): string | false { - const json = this.read(); - const replacements = getReplacements(json); - - if (!replacements || typeof replacements !== 'object') { - return name; - } - - if (!isAbsolutePath(name)) { - const replacement = replacements[name]; - // support exclude with "someDependency": false - return replacement === false - ? false - : /* $FlowFixMe: type of replacements is not being validated */ - replacement || name; - } - - let relPath = './' + path.relative(this.root, name); - if (path.sep !== '/') { - relPath = relPath.replace(new RegExp('\\' + path.sep, 'g'), '/'); - } - - let redirect = replacements[relPath]; - - // false is a valid value - if (redirect == null) { - redirect = replacements[relPath + '.js']; - if (redirect == null) { - redirect = replacements[relPath + '.json']; - } - } - - // support exclude with "./someFile": false - if (redirect === false) { - return false; - } - - if (redirect) { - return path.join( - this.root, - /* $FlowFixMe: `getReplacements` doesn't validate the return value. */ - redirect, - ); - } - - return name; - } - - read(): PackageContent { - if (this._content == null) { - this._content = JSON.parse(fs.readFileSync(this.path, 'utf8')); - } - return this._content; - } -} - -function getReplacements(pkg: PackageContent): mixed { - let rn = pkg['react-native']; - let browser = pkg.browser; - if (rn == null) { - return browser; - } - - if (browser == null) { - return rn; - } - - if (typeof rn === 'string') { - /* $FlowFixMe: It is likely unsafe to assume all packages would - * contain a "main" */ - rn = {[pkg.main]: rn}; - } - - if (typeof browser === 'string') { - /* $FlowFixMe: It is likely unsafe to assume all packages would - * contain a "main" */ - browser = {[pkg.main]: browser}; - } - - // merge with "browser" as default, - // "react-native" as override - // $FlowFixMe(>=0.35.0) browser and rn should be objects - return {...browser, ...rn}; -} - -module.exports = Package; diff --git a/packager/src/node-haste/Polyfill.js b/packager/src/node-haste/Polyfill.js deleted file mode 100644 index 3024efde410e28..00000000000000 --- a/packager/src/node-haste/Polyfill.js +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Copyright (c) 2013-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. - * - * @flow - * @format - */ - -'use strict'; - -const Module = require('./Module'); - -import type {ConstructorArgs} from './Module'; - -class Polyfill extends Module { - _id: string; - _dependencies: Array; - - constructor( - options: ConstructorArgs & { - id: string, - dependencies: Array, - }, - ) { - super(options); - this._id = options.id; - this._dependencies = options.dependencies; - } - - isHaste() { - return false; - } - - getName() { - return Promise.resolve(this._id); - } - - getPackage() { - return null; - } - - getDependencies() { - return Promise.resolve(this._dependencies); - } - - isJSON() { - return false; - } - - isPolyfill() { - return true; - } -} - -module.exports = Polyfill; diff --git a/packager/src/node-haste/__mocks__/fs.js b/packager/src/node-haste/__mocks__/fs.js deleted file mode 100644 index 2036e6ea8f9483..00000000000000 --- a/packager/src/node-haste/__mocks__/fs.js +++ /dev/null @@ -1,11 +0,0 @@ -/** - * 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'; - -module.exports = require('graceful-fs'); diff --git a/packager/src/node-haste/__mocks__/graceful-fs.js b/packager/src/node-haste/__mocks__/graceful-fs.js deleted file mode 100644 index 41ad4f1c5d53fa..00000000000000 --- a/packager/src/node-haste/__mocks__/graceful-fs.js +++ /dev/null @@ -1,308 +0,0 @@ -/** - * 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 {EventEmitter} = require('events'); -const {dirname} = require.requireActual('path'); -const fs = jest.genMockFromModule('fs'); -const path = require('path'); -const stream = require.requireActual('stream'); - -const noop = () => {}; - -function asyncCallback(cb) { - return function() { - setImmediate(() => cb.apply(this, arguments)); - }; -} - -const mtime = { - getTime: () => Math.ceil(Math.random() * 10000000), -}; - -fs.realpath.mockImplementation((filepath, callback) => { - callback = asyncCallback(callback); - let node; - try { - node = getToNode(filepath); - } catch (e) { - return callback(e); - } - if (node && typeof node === 'object' && node.SYMLINK != null) { - return callback(null, node.SYMLINK); - } - return callback(null, filepath); -}); - -fs.readdirSync.mockImplementation(filepath => Object.keys(getToNode(filepath))); - -fs.readdir.mockImplementation((filepath, callback) => { - callback = asyncCallback(callback); - let node; - try { - node = getToNode(filepath); - if (node && typeof node === 'object' && node.SYMLINK != null) { - node = getToNode(node.SYMLINK); - } - } catch (e) { - return callback(e); - } - - if (!(node && typeof node === 'object' && node.SYMLINK == null)) { - return callback(new Error(filepath + ' is not a directory.')); - } - - return callback(null, Object.keys(node)); -}); - -fs.readFile.mockImplementation(function(filepath, encoding, callback) { - callback = asyncCallback(callback); - if (arguments.length === 2) { - callback = encoding; - encoding = null; - } - - let node; - try { - node = getToNode(filepath); - // dir check - if (node && typeof node === 'object' && node.SYMLINK == null) { - callback(new Error('Error readFile a dir: ' + filepath)); - } - if (node == null) { - return callback(Error('No such file: ' + filepath)); - } else { - return callback(null, node); - } - } catch (e) { - return callback(e); - } -}); - -fs.readFileSync.mockImplementation(function(filepath, encoding) { - const node = getToNode(filepath); - // dir check - if (node && typeof node === 'object' && node.SYMLINK == null) { - throw new Error('Error readFileSync a dir: ' + filepath); - } - return node; -}); - -function makeStatResult(node) { - const isSymlink = node != null && node.SYMLINK != null; - return { - isBlockDevice: () => false, - isCharacterDevice: () => false, - isDirectory: () => node != null && typeof node === 'object' && !isSymlink, - isFIFO: () => false, - isFile: () => node != null && typeof node === 'string', - isSocket: () => false, - isSymbolicLink: () => isSymlink, - mtime, - }; -} - -function statSync(filepath) { - const node = getToNode(filepath); - if (node != null && node.SYMLINK) { - return statSync(node.SYMLINK); - } - return makeStatResult(node); -} - -fs.stat.mockImplementation((filepath, callback) => { - callback = asyncCallback(callback); - let result; - try { - result = statSync(filepath); - } catch (e) { - callback(e); - return; - } - callback(null, result); -}); - -fs.statSync.mockImplementation(statSync); - -function lstatSync(filepath) { - const node = getToNode(filepath); - return makeStatResult(node); -} - -fs.lstat.mockImplementation((filepath, callback) => { - callback = asyncCallback(callback); - let result; - try { - result = lstatSync(filepath); - } catch (e) { - callback(e); - return; - } - callback(null, result); -}); - -fs.lstatSync.mockImplementation(lstatSync); - -fs.open.mockImplementation(function(filepath) { - const callback = arguments[arguments.length - 1] || noop; - let data, error, fd; - try { - data = getToNode(filepath); - } catch (e) { - error = e; - } - - if (error || data == null) { - error = Error(`ENOENT: no such file or directory, open ${filepath}`); - } - if (data != null) { - /* global Buffer: true */ - fd = {buffer: new Buffer(data, 'utf8'), position: 0}; - } - - callback(error, fd); -}); - -fs.read.mockImplementation((fd, buffer, writeOffset, length, position, callback = noop) => { - let bytesWritten; - try { - if (position == null || position < 0) { - ({position} = fd); - } - bytesWritten = fd.buffer.copy(buffer, writeOffset, position, position + length); - fd.position = position + bytesWritten; - } catch (e) { - callback(Error('invalid argument')); - return; - } - callback(null, bytesWritten, buffer); -}); - -fs.close.mockImplementation((fd, callback = noop) => { - try { - fd.buffer = fs.position = undefined; - } catch (e) { - callback(Error('invalid argument')); - return; - } - callback(null); -}); - -let filesystem; - -fs.createReadStream.mockImplementation(filepath => { - if (!filepath.startsWith('/')) { - throw Error('Cannot open file ' + filepath); - } - - const parts = filepath.split('/').slice(1); - let file = filesystem; - - for (const part of parts) { - file = file[part]; - if (!file) { - break; - } - } - - if (typeof file !== 'string') { - throw Error('Cannot open file ' + filepath); - } - - return new stream.Readable({ - read() { - this.push(file, 'utf8'); - this.push(null); - }, - }); -}); - -fs.createWriteStream.mockImplementation(file => { - let node; - try { - node = getToNode(dirname(file)); - } finally { - if (typeof node === 'object') { - const writeStream = new stream.Writable({ - write(chunk) { - this.__chunks.push(chunk); - }, - }); - writeStream.__file = file; - writeStream.__chunks = []; - writeStream.end = jest.fn(writeStream.end); - fs.createWriteStream.mock.returned.push(writeStream); - return writeStream; - } else { - throw new Error('Cannot open file ' + file); - } - } -}); -fs.createWriteStream.mock.returned = []; - -fs.__setMockFilesystem = object => (filesystem = object); - -const watcherListByPath = new Map(); - -fs.watch.mockImplementation((filename, options, listener) => { - if (options.recursive) { - throw new Error('recursive watch not implemented'); - } - let watcherList = watcherListByPath.get(filename); - if (watcherList == null) { - watcherList = []; - watcherListByPath.set(filename, watcherList); - } - const fsWatcher = new EventEmitter(); - fsWatcher.on('change', listener); - fsWatcher.close = () => { - watcherList.splice(watcherList.indexOf(fsWatcher), 1); - fsWatcher.close = () => { throw new Error('FSWatcher is already closed'); }; - }; - watcherList.push(fsWatcher); -}); - -fs.__triggerWatchEvent = (eventType, filename) => { - const directWatchers = watcherListByPath.get(filename) || []; - directWatchers.forEach(wtc => wtc.emit('change', eventType)); - const dirPath = path.dirname(filename); - const dirWatchers = watcherListByPath.get(dirPath) || []; - dirWatchers.forEach(wtc => wtc.emit('change', eventType, path.relative(dirPath, filename))); -}; - -function getToNode(filepath) { - // Ignore the drive for Windows paths. - if (filepath.match(/^[a-zA-Z]:\\/)) { - filepath = filepath.substring(2); - } - - if (filepath.endsWith(path.sep)) { - filepath = filepath.slice(0, -1); - } - const parts = filepath.split(/[\/\\]/); - if (parts[0] !== '') { - throw new Error('Make sure all paths are absolute.'); - } - let node = filesystem; - parts.slice(1).forEach(part => { - if (node && node.SYMLINK) { - node = getToNode(node.SYMLINK); - } - node = node[part]; - if (node == null) { - const err = new Error('ENOENT: no such file or directory'); - err.code = 'ENOENT'; - throw err; - } - }); - - return node; -} - -module.exports = fs; diff --git a/packager/src/node-haste/__tests__/AssetModule-test.js b/packager/src/node-haste/__tests__/AssetModule-test.js deleted file mode 100644 index 76d548cb62e8bc..00000000000000 --- a/packager/src/node-haste/__tests__/AssetModule-test.js +++ /dev/null @@ -1,28 +0,0 @@ -/** - * 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'; - -jest.autoMockOff(); - -const AssetModule = require('../AssetModule'); - -describe('AssetModule:', () => { - const defaults = {file: '/arbitrary.png'}; - - it('has no dependencies by default', () => { - return new AssetModule(defaults).getDependencies() - .then(deps => expect(deps).toEqual([])); - }); - - it('can be parametrized with dependencies', () => { - const dependencies = ['arbitrary', 'dependencies']; - return new AssetModule({...defaults, dependencies}).getDependencies() - .then(deps => expect(deps).toEqual(dependencies)); - }); -}); diff --git a/packager/src/node-haste/__tests__/AssetResolutionCache-test.js b/packager/src/node-haste/__tests__/AssetResolutionCache-test.js deleted file mode 100644 index a5396848e83812..00000000000000 --- a/packager/src/node-haste/__tests__/AssetResolutionCache-test.js +++ /dev/null @@ -1,53 +0,0 @@ -/** - * 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. - * - * @format - */ - -'use strict'; - -jest.disableAutomock(); - -const AssetResolutionCache = require('../AssetResolutionCache'); - -const MOCK_FILE_NAMES = [ - 'test@2x.ios.png', - 'test@1x.ios.png', - 'foo.jpg', - 'bar.ios.png', - 'test@1.5x.ios.png', - 'foo@2x.jpg', - 'test@3x.android.png', - 'test.android.png', -]; - -describe('AssetResolutionCache', () => { - let fileNames, cache; - - beforeEach(() => { - fileNames = [...MOCK_FILE_NAMES]; - cache = new AssetResolutionCache({ - assetExtensions: new Set(['png', 'jpg']), - getDirFiles: dirPath => (dirPath === '/assets' ? fileNames : []), - platforms: new Set(['ios', 'android']), - }); - }); - - it('finds the correct assets', () => { - const results = cache.resolve('/assets', 'test.png', 'ios'); - expect(results).toMatchSnapshot(); - }); - - it('correctly clears out', () => { - cache.resolve('/assets', 'test.png', 'ios'); - fileNames.push('test@3x.ios.png'); - cache.clear(); - const results = cache.resolve('/assets', 'test.png', 'ios'); - expect(results).toMatchSnapshot(); - }); -}); diff --git a/packager/src/node-haste/__tests__/DependencyGraph-test.js b/packager/src/node-haste/__tests__/DependencyGraph-test.js deleted file mode 100644 index 34b0cf4137ce3a..00000000000000 --- a/packager/src/node-haste/__tests__/DependencyGraph-test.js +++ /dev/null @@ -1,5824 +0,0 @@ -/** - * 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. - * - * @format - */ -'use strict'; - -jest.disableAutomock(); -jest.useRealTimers(); -jest - .mock('fs') - .mock('../../Logger') - .mock('../../lib/TransformCaching') - // It's noticeably faster to prevent running watchman from FileWatcher. - .mock('child_process', () => ({})); - -// This doesn't have state, and it's huge (Babel) so it's much faster to -// require it only once. The variable name is prefixed with "mock" as an escape-hatch -// for babel-plugin-jest-hoist. -let mockExtractDependencies; -jest.mock('../../JSTransformer/worker/extract-dependencies', () => { - if (!mockExtractDependencies) { - mockExtractDependencies = require.requireActual( - '../../JSTransformer/worker/extract-dependencies', - ); - } - return mockExtractDependencies; -}); - -jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000; - -beforeEach(() => { - jest.resetModules(); - jest.mock('path', () => require.requireActual('path')); -}); - -describe('DependencyGraph', function() { - let Module; - let ResolutionRequest; - let defaults; - let emptyTransformOptions; - - function getOrderedDependenciesAsJSON( - dgraphPromise, - entryPath, - platform, - recursive = true, - ) { - return Promise.resolve(dgraphPromise) - .then(dgraph => - dgraph.getDependencies({ - entryPath, - options: emptyTransformOptions, - platform, - recursive, - }), - ) - .then(response => response.finalize()) - .then(({dependencies}) => - Promise.all( - dependencies.map(dep => - Promise.all([ - dep.getName(), - dep.getDependencies(), - ]).then(([name, moduleDependencies]) => ({ - path: dep.path, - isJSON: dep.isJSON(), - isAsset: dep.isAsset(), - isPolyfill: dep.isPolyfill(), - resolution: dep.resolution, - id: name, - dependencies: moduleDependencies, - })), - ), - ), - ); - } - - beforeEach(function() { - jest.resetModules(); - - Module = require('../Module'); - ResolutionRequest = require('../DependencyGraph/ResolutionRequest'); - - emptyTransformOptions = {transformer: {transform: {}}}; - defaults = { - assetExts: ['png', 'jpg'], - forceNodeFilesystemAPI: true, - providesModuleNodeModules: ['haste-fbjs', 'react-haste', 'react-native'], - platforms: new Set(['ios', 'android']), - useWatchman: false, - ignoreFilePath: () => false, - maxWorkerCount: 1, - moduleOptions: {transformCache: require('TransformCaching').mocked()}, - resetCache: true, - transformCode: (module, sourceCode, transformOptions) => { - return new Promise(resolve => { - let deps = {dependencies: [], dependencyOffsets: []}; - if (!module.path.endsWith('.json')) { - if (!mockExtractDependencies) { - mockExtractDependencies = require.requireActual( - '../../JSTransformer/worker/extract-dependencies', - ); - } - deps = mockExtractDependencies(sourceCode); - } - resolve({...deps, code: sourceCode}); - }); - }, - getTransformCacheKey: () => 'abcdef', - reporter: require('../../lib/reporting').nullReporter, - sourceExts: ['js', 'json'], - watch: true, - }; - }); - - describe('get sync dependencies (posix)', function() { - let DependencyGraph; - const consoleWarn = console.warn; - const realPlatform = process.platform; - beforeEach(function() { - process.platform = 'linux'; - DependencyGraph = require('../DependencyGraph'); - }); - - afterEach(function() { - console.warn = consoleWarn; - process.platform = realPlatform; - }); - - it('should get dependencies', function() { - var root = '/root'; - setMockFileSystem({ - root: { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("a")', - ].join('\n'), - 'a.js': ['/**', ' * @providesModule a', ' */', 'require("b")'].join( - '\n', - ), - 'b.js': ['/**', ' * @providesModule b', ' */'].join('\n'), - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.js', - ).then(function(deps) { - expect(deps).toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['a'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'a', - path: '/root/a.js', - dependencies: ['b'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'b', - path: '/root/b.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - }); - }); - - it('should resolve relative entry path', function() { - var root = '/root'; - setMockFileSystem({ - root: { - 'index.js': ['/**', ' * @providesModule index', ' */'].join('\n'), - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, 'index.js').then(function( - deps, - ) { - expect(deps).toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - it('should get shallow dependencies', function() { - var root = '/root'; - setMockFileSystem({ - root: { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("a")', - ].join('\n'), - 'a.js': ['/**', ' * @providesModule a', ' */', 'require("b")'].join( - '\n', - ), - 'b.js': ['/**', ' * @providesModule b', ' */'].join('\n'), - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.js', - null, - false, - ).then(deps => { - expect(deps).toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['a'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'a', - path: '/root/a.js', - dependencies: ['b'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - it('should get dependencies with the correct extensions', function() { - var root = '/root'; - setMockFileSystem({ - root: { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("a")', - ].join('\n'), - 'a.js': ['/**', ' * @providesModule a', ' */'].join('\n'), - 'a.js.orig': ['/**', ' * @providesModule a', ' */'].join('\n'), - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.js', - ).then(function(deps) { - expect(deps).toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['a'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'a', - path: '/root/a.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - it('should get json dependencies', function() { - var root = '/root'; - setMockFileSystem({ - root: { - 'package.json': JSON.stringify({ - name: 'package', - }), - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("./a.json")', - 'require("./b")', - ].join('\n'), - 'a.json': JSON.stringify({}), - 'b.json': JSON.stringify({}), - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.js', - ).then(function(deps) { - expect(deps).toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['./a.json', './b'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'package/a.json', - isJSON: true, - path: '/root/a.json', - dependencies: [], - isAsset: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'package/b.json', - isJSON: true, - path: '/root/b.json', - dependencies: [], - isAsset: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - it('should get package json as a dep', () => { - var root = '/root'; - setMockFileSystem({ - root: { - 'package.json': JSON.stringify({ - name: 'package', - }), - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("./package.json")', - ].join('\n'), - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.js', - ).then(deps => { - expect(deps).toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['./package.json'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'package/package.json', - isJSON: true, - path: '/root/package.json', - dependencies: [], - isAsset: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - it('should get dependencies with relative assets', function() { - var root = '/root'; - setMockFileSystem({ - root: { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("./imgs/a.png")', - ].join('\n'), - imgs: { - 'a.png': '', - }, - 'package.json': JSON.stringify({ - name: 'rootPackage', - }), - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.js', - ).then(function(deps) { - expect(deps).toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['./imgs/a.png'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'rootPackage/imgs/a.png', - path: '/root/imgs/a.png', - dependencies: [], - isAsset: true, - resolution: 1, - isJSON: false, - isPolyfill: false, - }, - ]); - }); - }); - - it('should get dependencies with assets and resolution', function() { - var root = '/root'; - setMockFileSystem({ - root: { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("./imgs/a.png");', - 'require("./imgs/b.png");', - 'require("./imgs/c.png");', - ].join('\n'), - imgs: { - 'a@1.5x.png': '', - 'b@.7x.png': '', - 'c.png': '', - 'c@2x.png': '', - }, - 'package.json': JSON.stringify({ - name: 'rootPackage', - }), - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.js', - ).then(function(deps) { - expect(deps).toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['./imgs/a.png', './imgs/b.png', './imgs/c.png'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'rootPackage/imgs/a.png', - path: '/root/imgs/a@1.5x.png', - resolution: 1.5, - dependencies: [], - isAsset: true, - isJSON: false, - isPolyfill: false, - }, - { - id: 'rootPackage/imgs/b.png', - path: '/root/imgs/b@.7x.png', - resolution: 0.7, - dependencies: [], - isAsset: true, - isJSON: false, - isPolyfill: false, - }, - { - id: 'rootPackage/imgs/c.png', - path: '/root/imgs/c.png', - resolution: 1, - dependencies: [], - isAsset: true, - isJSON: false, - isPolyfill: false, - }, - ]); - }); - }); - - it('should respect platform extension in assets', function() { - var root = '/root'; - setMockFileSystem({ - root: { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("./imgs/a.png");', - 'require("./imgs/b.png");', - 'require("./imgs/c.png");', - ].join('\n'), - imgs: { - 'a@1.5x.ios.png': '', - 'b@.7x.ios.png': '', - 'c.ios.png': '', - 'c@2x.ios.png': '', - }, - 'package.json': JSON.stringify({ - name: 'rootPackage', - }), - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.js', - 'ios', - ).then(function(deps) { - expect(deps).toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['./imgs/a.png', './imgs/b.png', './imgs/c.png'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'rootPackage/imgs/a.png', - path: '/root/imgs/a@1.5x.ios.png', - resolution: 1.5, - dependencies: [], - isAsset: true, - isJSON: false, - isPolyfill: false, - }, - { - id: 'rootPackage/imgs/b.png', - path: '/root/imgs/b@.7x.ios.png', - resolution: 0.7, - dependencies: [], - isAsset: true, - isJSON: false, - isPolyfill: false, - }, - { - id: 'rootPackage/imgs/c.png', - path: '/root/imgs/c.ios.png', - resolution: 1, - dependencies: [], - isAsset: true, - isJSON: false, - isPolyfill: false, - }, - ]); - }); - }); - - it('should get recursive dependencies', function() { - var root = '/root'; - setMockFileSystem({ - root: { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("a")', - ].join('\n'), - 'a.js': [ - '/**', - ' * @providesModule a', - ' */', - 'require("index")', - ].join('\n'), - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.js', - ).then(function(deps) { - expect(deps).toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['a'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'a', - path: '/root/a.js', - dependencies: ['index'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - it('should work with packages', function() { - var root = '/root'; - setMockFileSystem({ - root: { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - aPackage: { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js', - }), - 'main.js': 'lol', - }, - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.js', - ).then(function(deps) { - expect(deps).toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'aPackage/main.js', - path: '/root/aPackage/main.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - it('should work with packages with a trailing slash', function() { - var root = '/root'; - setMockFileSystem({ - root: { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage/")', - ].join('\n'), - aPackage: { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js', - }), - 'main.js': 'lol', - }, - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.js', - ).then(function(deps) { - expect(deps).toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['aPackage/'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'aPackage/main.js', - path: '/root/aPackage/main.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - it('should work with packages with a dot in the name', function() { - var root = '/root'; - setMockFileSystem({ - root: { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("sha.js")', - 'require("x.y.z")', - ].join('\n'), - 'sha.js': { - 'package.json': JSON.stringify({ - name: 'sha.js', - main: 'main.js', - }), - 'main.js': 'lol', - }, - 'x.y.z': { - 'package.json': JSON.stringify({ - name: 'x.y.z', - main: 'main.js', - }), - 'main.js': 'lol', - }, - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.js', - ).then(function(deps) { - expect(deps).toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['sha.js', 'x.y.z'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'sha.js/main.js', - path: '/root/sha.js/main.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'x.y.z/main.js', - path: '/root/x.y.z/main.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - it('should default main package to index.js', function() { - var root = '/root'; - setMockFileSystem({ - root: { - 'index.js': 'require("aPackage")', - aPackage: { - 'package.json': JSON.stringify({ - name: 'aPackage', - }), - 'index.js': 'lol', - }, - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.js', - ).then(function(deps) { - expect(deps).toEqual([ - { - id: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'aPackage/index.js', - path: '/root/aPackage/index.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - it('should resolve using alternative ids', () => { - var root = '/root'; - setMockFileSystem({ - root: { - 'index.js': 'require("aPackage")', - aPackage: { - 'package.json': JSON.stringify({ - name: 'aPackage', - }), - 'index.js': ['/**', ' * @providesModule EpicModule', ' */'].join( - '\n', - ), - }, - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.js', - ).then(function(deps) { - expect(deps).toEqual([ - { - id: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'EpicModule', - path: '/root/aPackage/index.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - it('should default use index.js if main is a dir', function() { - var root = '/root'; - setMockFileSystem({ - root: { - 'index.js': 'require("aPackage")', - aPackage: { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'lib', - }), - lib: { - 'index.js': 'lol', - }, - }, - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.js', - ).then(function(deps) { - expect(deps).toEqual([ - { - id: '/root/index.js', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'aPackage/lib/index.js', - path: '/root/aPackage/lib/index.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - it('should resolve require to index if it is a dir', function() { - var root = '/root'; - setMockFileSystem({ - root: { - 'package.json': JSON.stringify({ - name: 'test', - }), - 'index.js': 'require("./lib/")', - lib: { - 'index.js': 'lol', - }, - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.js', - ).then(function(deps) { - expect(deps).toEqual([ - { - id: 'test/index.js', - path: '/root/index.js', - dependencies: ['./lib/'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'test/lib/index.js', - path: '/root/lib/index.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - it('should resolve require to main if it is a dir w/ a package.json', function() { - var root = '/root'; - setMockFileSystem({ - root: { - 'package.json': JSON.stringify({ - name: 'test', - }), - 'index.js': 'require("./lib/")', - lib: { - 'package.json': JSON.stringify({ - main: 'main.js', - }), - 'index.js': 'lol', - 'main.js': 'lol', - }, - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.js', - ).then(function(deps) { - expect(deps).toEqual([ - { - id: 'test/index.js', - path: '/root/index.js', - dependencies: ['./lib/'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: '/root/lib/main.js', - path: '/root/lib/main.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - it('should ignore malformed packages', function() { - var root = '/root'; - setMockFileSystem({ - root: { - 'index.js': ['/**', ' * @providesModule index', ' */'].join('\n'), - aPackage: { - 'package.json': 'lol', - 'main.js': 'lol', - }, - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.js', - ).then(function(deps) { - expect(deps).toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - it('should fatal on multiple modules with the same name', function() { - const root = '/root'; - console.warn = jest.fn(); - setMockFileSystem({ - root: { - 'index.js': ['/**', ' * @providesModule index', ' */'].join('\n'), - 'b.js': ['/**', ' * @providesModule index', ' */'].join('\n'), - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - - return dgraph.catch(err => { - expect(err.message).toEqual( - `Failed to build DependencyGraph: @providesModule naming collision:\n` + - ` Duplicate module name: index\n` + - ` Paths: /root/b.js collides with /root/index.js\n\n` + - 'This error is caused by a @providesModule declaration ' + - 'with the same name across two different files.', - ); - expect(err.type).toEqual('DependencyGraphError'); - expect(console.warn).toBeCalled(); - }); - }); - - it('throws when a module is missing', function() { - var root = '/root'; - setMockFileSystem({ - root: { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("lolomg")', - ].join('\n'), - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.js', - ).catch(error => { - expect(error.type).toEqual('UnableToResolveError'); - }); - }); - - it('should work with packages with subdirs', function() { - var root = '/root'; - setMockFileSystem({ - root: { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage/subdir/lolynot")', - ].join('\n'), - aPackage: { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js', - }), - 'main.js': 'lol', - subdir: { - 'lolynot.js': 'lolynot', - }, - }, - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.js', - ).then(function(deps) { - expect(deps).toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['aPackage/subdir/lolynot'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'aPackage/subdir/lolynot.js', - path: '/root/aPackage/subdir/lolynot.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - }); - }); - - it('should work with relative modules in packages', function() { - var root = '/root'; - setMockFileSystem({ - root: { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - aPackage: { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js', - }), - 'main.js': 'require("./subdir/lolynot")', - subdir: { - 'lolynot.js': 'require("../other")', - }, - 'other.js': '/* some code */', - }, - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.js', - ).then(function(deps) { - expect(deps).toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'aPackage/main.js', - path: '/root/aPackage/main.js', - dependencies: ['./subdir/lolynot'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'aPackage/subdir/lolynot.js', - path: '/root/aPackage/subdir/lolynot.js', - dependencies: ['../other'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'aPackage/other.js', - path: '/root/aPackage/other.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - }); - }); - - testBrowserField('browser'); - testBrowserField('react-native'); - - function replaceBrowserField(json, fieldName) { - if (fieldName !== 'browser') { - json[fieldName] = json.browser; - delete json.browser; - } - - return json; - } - - function testBrowserField(fieldName) { - it( - 'should support simple browser field in packages ("' + fieldName + '")', - function() { - var root = '/root'; - setMockFileSystem({ - root: { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - aPackage: { - 'package.json': JSON.stringify( - replaceBrowserField( - { - name: 'aPackage', - main: 'main.js', - browser: 'client.js', - }, - fieldName, - ), - ), - 'main.js': 'some other code', - 'client.js': '/* some code */', - }, - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.js', - ).then(function(deps) { - expect(deps).toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'aPackage/client.js', - path: '/root/aPackage/client.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - }); - }, - ); - - it( - 'should support browser field in packages w/o .js ext ("' + - fieldName + - '")', - function() { - var root = '/root'; - setMockFileSystem({ - root: { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - aPackage: { - 'package.json': JSON.stringify( - replaceBrowserField( - { - name: 'aPackage', - main: 'main.js', - browser: 'client', - }, - fieldName, - ), - ), - 'main.js': 'some other code', - 'client.js': '/* some code */', - }, - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.js', - ).then(function(deps) { - expect(deps).toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'aPackage/client.js', - path: '/root/aPackage/client.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }, - ); - - it( - 'should support mapping main in browser field json ("' + - fieldName + - '")', - function() { - var root = '/root'; - setMockFileSystem({ - root: { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - aPackage: { - 'package.json': JSON.stringify( - replaceBrowserField( - { - name: 'aPackage', - main: './main.js', - browser: { - './main.js': './client.js', - }, - }, - fieldName, - ), - ), - 'main.js': 'some other code', - 'client.js': '/* some code */', - }, - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - assetExts: ['png', 'jpg'], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.js', - ).then(function(deps) { - expect(deps).toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'aPackage/client.js', - path: '/root/aPackage/client.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - }); - }, - ); - - it( - 'should work do correct browser mapping w/o js ext ("' + - fieldName + - '")', - function() { - var root = '/root'; - setMockFileSystem({ - root: { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - aPackage: { - 'package.json': JSON.stringify( - replaceBrowserField( - { - name: 'aPackage', - main: './main.js', - browser: { - './main': './client.js', - }, - }, - fieldName, - ), - ), - 'main.js': 'some other code', - 'client.js': '/* some code */', - }, - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - assetExts: ['png', 'jpg'], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.js', - ).then(function(deps) { - expect(deps).toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'aPackage/client.js', - path: '/root/aPackage/client.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - }); - }, - ); - - it( - 'should support browser mapping of files ("' + fieldName + '")', - function() { - var root = '/root'; - setMockFileSystem({ - root: { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - aPackage: { - 'package.json': JSON.stringify( - replaceBrowserField( - { - name: 'aPackage', - main: './main.js', - browser: { - './main': './client.js', - './node.js': './not-node.js', - './not-browser': './browser.js', - './dir/server.js': './dir/client', - './hello.js': './bye.js', - }, - }, - fieldName, - ), - ), - 'main.js': '/* some other code */', - 'client.js': 'require("./node")\nrequire("./dir/server.js")', - 'not-node.js': 'require("./not-browser")', - 'not-browser.js': 'require("./dir/server")', - 'browser.js': '/* some browser code */', - dir: { - 'server.js': '/* some node code */', - 'client.js': 'require("../hello")', - }, - 'hello.js': '/* hello */', - 'bye.js': '/* bye */', - }, - }, - }); - - const dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.js', - ).then(function(deps) { - expect(deps).toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'aPackage/client.js', - path: '/root/aPackage/client.js', - dependencies: ['./node', './dir/server.js'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'aPackage/not-node.js', - path: '/root/aPackage/not-node.js', - dependencies: ['./not-browser'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'aPackage/browser.js', - path: '/root/aPackage/browser.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'aPackage/dir/client.js', - path: '/root/aPackage/dir/client.js', - dependencies: ['../hello'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'aPackage/bye.js', - path: '/root/aPackage/bye.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }, - ); - - it( - 'should support browser mapping for packages ("' + fieldName + '")', - function() { - var root = '/root'; - setMockFileSystem({ - root: { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - aPackage: { - 'package.json': JSON.stringify( - replaceBrowserField( - { - name: 'aPackage', - browser: { - 'node-package': 'browser-package', - }, - }, - fieldName, - ), - ), - 'index.js': 'require("node-package")', - 'node-package': { - 'package.json': JSON.stringify({ - name: 'node-package', - }), - 'index.js': '/* some node code */', - }, - 'browser-package': { - 'package.json': JSON.stringify({ - name: 'browser-package', - }), - 'index.js': '/* some browser code */', - }, - }, - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.js', - ).then(function(deps) { - expect(deps).toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'aPackage/index.js', - path: '/root/aPackage/index.js', - dependencies: ['node-package'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'browser-package/index.js', - path: '/root/aPackage/browser-package/index.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }, - ); - - it( - 'should support browser mapping of a package to a file ("' + - fieldName + - '")', - () => { - var root = '/root'; - setMockFileSystem({ - root: { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - aPackage: { - 'package.json': JSON.stringify( - replaceBrowserField( - { - name: 'aPackage', - browser: { - 'node-package': './dir/browser.js', - }, - }, - fieldName, - ), - ), - 'index.js': 'require("./dir/ooga")', - dir: { - 'ooga.js': 'require("node-package")', - 'browser.js': '/* some browser code */', - }, - 'node-package': { - 'package.json': JSON.stringify({ - name: 'node-package', - }), - 'index.js': '/* some node code */', - }, - }, - }, - }); - - const dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.js', - ).then(function(deps) { - expect(deps).toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'aPackage/index.js', - path: '/root/aPackage/index.js', - dependencies: ['./dir/ooga'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'aPackage/dir/ooga.js', - path: '/root/aPackage/dir/ooga.js', - dependencies: ['node-package'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'aPackage/dir/browser.js', - path: '/root/aPackage/dir/browser.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }, - ); - - it( - 'should support browser mapping for packages ("' + fieldName + '")', - function() { - var root = '/root'; - setMockFileSystem({ - root: { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - aPackage: { - 'package.json': JSON.stringify( - replaceBrowserField( - { - name: 'aPackage', - browser: { - 'node-package': 'browser-package', - }, - }, - fieldName, - ), - ), - 'index.js': 'require("node-package")', - 'node-package': { - 'package.json': JSON.stringify({ - name: 'node-package', - }), - 'index.js': '/* some node code */', - }, - 'browser-package': { - 'package.json': JSON.stringify({ - name: 'browser-package', - }), - 'index.js': '/* some browser code */', - }, - }, - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.js', - ).then(function(deps) { - expect(deps).toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'aPackage/index.js', - path: '/root/aPackage/index.js', - dependencies: ['node-package'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'browser-package/index.js', - path: '/root/aPackage/browser-package/index.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }, - ); - - it( - 'should support browser exclude of a package ("' + fieldName + '")', - function() { - ResolutionRequest.EMPTY_MODULE = '/root/emptyModule.js'; - var root = '/root'; - setMockFileSystem({ - root: { - 'emptyModule.js': '', - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - aPackage: { - 'package.json': JSON.stringify( - replaceBrowserField( - { - name: 'aPackage', - browser: { - booga: false, - }, - }, - fieldName, - ), - ), - 'index.js': 'require("booga")', - booga: { - 'package.json': JSON.stringify({ - name: 'booga', - }), - 'index.js': '/* some node code */', - }, - }, - }, - }); - - const dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.js', - ).then(function(deps) { - expect(deps).toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'aPackage/index.js', - path: '/root/aPackage/index.js', - dependencies: ['booga'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - dependencies: [], - id: '/root/emptyModule.js', - isAsset: false, - isJSON: false, - isPolyfill: false, - path: '/root/emptyModule.js', - resolution: undefined, - }, - ]); - }); - }, - ); - - it( - 'should support browser exclude of a file ("' + fieldName + '")', - function() { - ResolutionRequest.EMPTY_MODULE = '/root/emptyModule.js'; - - var root = '/root'; - setMockFileSystem({ - root: { - 'emptyModule.js': '', - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - aPackage: { - 'package.json': JSON.stringify( - replaceBrowserField( - { - name: 'aPackage', - browser: { - './booga.js': false, - }, - }, - fieldName, - ), - ), - 'index.js': 'require("./booga")', - 'booga.js': '/* some node code */', - }, - }, - }); - - const dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.js', - ).then(function(deps) { - expect(deps).toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'aPackage/index.js', - path: '/root/aPackage/index.js', - dependencies: ['./booga'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - dependencies: [], - id: '/root/emptyModule.js', - isAsset: false, - isJSON: false, - isPolyfill: false, - path: '/root/emptyModule.js', - resolution: undefined, - }, - ]); - }); - }, - ); - } - - it('should fall back to browser mapping from react-native mapping', function() { - var root = '/root'; - setMockFileSystem({ - root: { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - aPackage: { - 'package.json': JSON.stringify({ - name: 'aPackage', - 'react-native': { - 'node-package': 'rn-package', - }, - }), - 'index.js': 'require("node-package")', - node_modules: { - 'node-package': { - 'package.json': JSON.stringify({ - name: 'node-package', - }), - 'index.js': '/* some node code */', - }, - 'rn-package': { - 'package.json': JSON.stringify({ - name: 'rn-package', - browser: { - 'nested-package': 'nested-browser-package', - }, - }), - 'index.js': 'require("nested-package")', - }, - 'nested-browser-package': { - 'package.json': JSON.stringify({ - name: 'nested-browser-package', - }), - 'index.js': '/* some code */', - }, - }, - }, - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.js', - ).then(function(deps) { - expect(deps).toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'aPackage/index.js', - path: '/root/aPackage/index.js', - dependencies: ['node-package'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'rn-package/index.js', - path: '/root/aPackage/node_modules/rn-package/index.js', - dependencies: ['nested-package'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'nested-browser-package/index.js', - path: '/root/aPackage/node_modules/nested-browser-package/index.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - it('should work with absolute paths', () => { - const root = '/root'; - setMockFileSystem({ - [root.slice(1)]: { - 'index.js': 'require("/root/apple.js");', - 'apple.js': '', - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.js', - ).then(function(deps) { - expect(deps).toEqual([ - { - id: '/root/index.js', - path: '/root/index.js', - dependencies: ['/root/apple.js'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: '/root/apple.js', - path: '/root/apple.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - it('should merge browser mapping with react-native mapping', function() { - var root = '/root'; - setMockFileSystem({ - root: { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - aPackage: { - 'package.json': JSON.stringify({ - name: 'aPackage', - 'react-native': { - // should see this: - 'node-package-a': 'rn-package-a', - // should see this: - 'node-package-c': 'rn-package-d', - }, - browser: { - // should see this: - 'node-package-b': 'rn-package-b', - // should NOT see this: - 'node-package-c': 'rn-package-c', - }, - }), - 'index.js': 'require("node-package-a"); require("node-package-b"); require("node-package-c");', - node_modules: { - 'node-package-a': { - 'package.json': JSON.stringify({ - name: 'node-package-a', - }), - 'index.js': '/* some node code */', - }, - 'node-package-b': { - 'package.json': JSON.stringify({ - name: 'node-package-b', - }), - 'index.js': '/* some node code */', - }, - 'node-package-c': { - 'package.json': JSON.stringify({ - name: 'node-package-c', - }), - 'index.js': '/* some node code */', - }, - 'node-package-d': { - 'package.json': JSON.stringify({ - name: 'node-package-d', - }), - 'index.js': '/* some node code */', - }, - 'rn-package-a': { - 'package.json': JSON.stringify({ - name: 'rn-package-a', - }), - 'index.js': '/* some rn code */', - }, - 'rn-package-b': { - 'package.json': JSON.stringify({ - name: 'rn-package-b', - }), - 'index.js': '/* some rn code */', - }, - 'rn-package-c': { - 'package.json': JSON.stringify({ - name: 'rn-package-c', - }), - 'index.js': '/* some rn code */', - }, - 'rn-package-d': { - 'package.json': JSON.stringify({ - name: 'rn-package-d', - }), - 'index.js': '/* some rn code */', - }, - }, - }, - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.js', - ).then(function(deps) { - expect(deps).toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'aPackage/index.js', - path: '/root/aPackage/index.js', - dependencies: [ - 'node-package-a', - 'node-package-b', - 'node-package-c', - ], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'rn-package-a/index.js', - path: '/root/aPackage/node_modules/rn-package-a/index.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'rn-package-b/index.js', - path: '/root/aPackage/node_modules/rn-package-b/index.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'rn-package-d/index.js', - path: '/root/aPackage/node_modules/rn-package-d/index.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - it('should fall back to `extraNodeModules`', () => { - const root = '/root'; - setMockFileSystem({ - [root.slice(1)]: { - 'index.js': 'require("./foo")', - foo: { - 'index.js': 'require("bar")', - }, - 'provides-bar': { - 'package.json': '{"main": "lib/bar.js"}', - lib: { - 'bar.js': '', - }, - }, - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - extraNodeModules: { - bar: root + '/provides-bar', - }, - }); - - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.js', - ).then(deps => { - expect(deps).toEqual([ - { - id: '/root/index.js', - path: '/root/index.js', - dependencies: ['./foo'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: '/root/foo/index.js', - path: '/root/foo/index.js', - dependencies: ['bar'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: '/root/provides-bar/lib/bar.js', - path: '/root/provides-bar/lib/bar.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - it('should only use `extraNodeModules` after checking all possible filesystem locations', () => { - const root = '/root'; - setMockFileSystem({ - [root.slice(1)]: { - 'index.js': 'require("bar")', - node_modules: {'bar.js': ''}, - 'provides-bar': {'index.js': ''}, - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - extraNodeModules: { - bar: root + '/provides-bar', - }, - }); - - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.js', - ).then(deps => { - expect(deps).toEqual([ - { - id: '/root/index.js', - path: '/root/index.js', - dependencies: ['bar'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: '/root/node_modules/bar.js', - path: '/root/node_modules/bar.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - it('should be able to resolve paths within `extraNodeModules`', () => { - const root = '/root'; - setMockFileSystem({ - [root.slice(1)]: { - 'index.js': 'require("bar/lib/foo")', - 'provides-bar': { - 'package.json': '{}', - lib: {'foo.js': ''}, - }, - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - extraNodeModules: { - bar: root + '/provides-bar', - }, - }); - - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.js', - ).then(deps => { - expect(deps).toEqual([ - { - id: '/root/index.js', - path: '/root/index.js', - dependencies: ['bar/lib/foo'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: '/root/provides-bar/lib/foo.js', - path: '/root/provides-bar/lib/foo.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - }); - - describe('get sync dependencies (win32)', function() { - const realPlatform = process.platform; - let DependencyGraph; - beforeEach(function() { - process.platform = 'win32'; - - // reload path module - jest.resetModules(); - jest.mock('path', () => require.requireActual('path').win32); - DependencyGraph = require('../DependencyGraph'); - }); - - afterEach(function() { - process.platform = realPlatform; - }); - - it('should get dependencies', function() { - const root = 'C:\\root'; - setMockFileSystem({ - root: { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("a")', - ].join('\n'), - 'a.js': ['/**', ' * @providesModule a', ' */', 'require("b")'].join( - '\n', - ), - 'b.js': ['/**', ' * @providesModule b', ' */'].join('\n'), - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - 'C:\\root\\index.js', - ).then(deps => { - expect(deps).toEqual([ - { - id: 'index', - path: 'C:\\root\\index.js', - dependencies: ['a'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'a', - path: 'C:\\root\\a.js', - dependencies: ['b'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'b', - path: 'C:\\root\\b.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - }); - }); - - it('should work with absolute paths', () => { - const root = 'C:\\root'; - setMockFileSystem({ - root: { - 'index.js': 'require("C:/root/apple.js");', - 'apple.js': '', - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - 'C:\\root\\index.js', - ).then(function(deps) { - expect(deps).toEqual([ - { - id: 'C:\\root\\index.js', - path: 'C:\\root\\index.js', - dependencies: ['C:/root/apple.js'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'C:\\root\\apple.js', - path: 'C:\\root\\apple.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - it('should get dependencies with assets and resolution', function() { - const root = 'C:\\root'; - setMockFileSystem({ - root: { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("./imgs/a.png");', - 'require("./imgs/b.png");', - 'require("./imgs/c.png");', - ].join('\n'), - imgs: { - 'a@1.5x.png': '', - 'b@.7x.png': '', - 'c.png': '', - 'c@2x.png': '', - }, - 'package.json': JSON.stringify({ - name: 'rootPackage', - }), - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - 'C:\\root\\index.js', - ).then(function(deps) { - expect(deps).toEqual([ - { - id: 'index', - path: 'C:\\root\\index.js', - dependencies: ['./imgs/a.png', './imgs/b.png', './imgs/c.png'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'rootPackage/imgs/a.png', - path: 'C:\\root\\imgs\\a@1.5x.png', - resolution: 1.5, - dependencies: [], - isAsset: true, - isJSON: false, - isPolyfill: false, - }, - { - id: 'rootPackage/imgs/b.png', - path: 'C:\\root\\imgs\\b@.7x.png', - resolution: 0.7, - dependencies: [], - isAsset: true, - isJSON: false, - isPolyfill: false, - }, - { - id: 'rootPackage/imgs/c.png', - path: 'C:\\root\\imgs\\c.png', - resolution: 1, - dependencies: [], - isAsset: true, - isJSON: false, - isPolyfill: false, - }, - ]); - }); - }); - }); - - describe('node_modules (posix)', function() { - const realPlatform = process.platform; - let DependencyGraph; - beforeEach(function() { - process.platform = 'linux'; - DependencyGraph = require('../DependencyGraph'); - }); - - afterEach(function() { - process.platform = realPlatform; - }); - - it('should work with nested node_modules', function() { - var root = '/root'; - setMockFileSystem({ - root: { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("foo");', - 'require("bar");', - ].join('\n'), - node_modules: { - foo: { - 'package.json': JSON.stringify({ - name: 'foo', - main: 'main.js', - }), - 'main.js': 'require("bar");\n/* foo module */', - node_modules: { - bar: { - 'package.json': JSON.stringify({ - name: 'bar', - main: 'main.js', - }), - 'main.js': '/* bar 1 module */', - }, - }, - }, - bar: { - 'package.json': JSON.stringify({ - name: 'bar', - main: 'main.js', - }), - 'main.js': '/* bar 2 module */', - }, - }, - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.js', - ).then(function(deps) { - expect(deps).toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['foo', 'bar'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'foo/main.js', - path: '/root/node_modules/foo/main.js', - dependencies: ['bar'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'bar/main.js', - path: '/root/node_modules/foo/node_modules/bar/main.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'bar/main.js', - path: '/root/node_modules/bar/main.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - it('platform should work with node_modules', function() { - var root = '/root'; - setMockFileSystem({ - root: { - 'index.ios.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("foo");', - 'require("bar");', - ].join('\n'), - node_modules: { - foo: { - 'package.json': JSON.stringify({ - name: 'foo', - }), - 'index.ios.js': '', - }, - bar: { - 'package.json': JSON.stringify({ - name: 'bar', - main: 'main', - }), - 'main.ios.js': '', - }, - }, - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.ios.js', - ).then(function(deps) { - expect(deps).toEqual([ - { - id: 'index', - path: '/root/index.ios.js', - dependencies: ['foo', 'bar'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'foo/index.ios.js', - path: '/root/node_modules/foo/index.ios.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'bar/main.ios.js', - path: '/root/node_modules/bar/main.ios.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - it('nested node_modules with specific paths', function() { - var root = '/root'; - setMockFileSystem({ - root: { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("foo");', - 'require("bar/");', - ].join('\n'), - node_modules: { - foo: { - 'package.json': JSON.stringify({ - name: 'foo', - main: 'main.js', - }), - 'main.js': 'require("bar/lol");\n/* foo module */', - node_modules: { - bar: { - 'package.json': JSON.stringify({ - name: 'bar', - main: 'main.js', - }), - 'main.js': '/* bar 1 module */', - 'lol.js': '', - }, - }, - }, - bar: { - 'package.json': JSON.stringify({ - name: 'bar', - main: 'main.js', - }), - 'main.js': '/* bar 2 module */', - }, - }, - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.js', - ).then(function(deps) { - expect(deps).toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['foo', 'bar/'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'foo/main.js', - path: '/root/node_modules/foo/main.js', - dependencies: ['bar/lol'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'bar/lol.js', - path: '/root/node_modules/foo/node_modules/bar/lol.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'bar/main.js', - path: '/root/node_modules/bar/main.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - it('nested node_modules with browser field', function() { - var root = '/root'; - setMockFileSystem({ - root: { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("foo");', - 'require("bar");', - ].join('\n'), - node_modules: { - foo: { - 'package.json': JSON.stringify({ - name: 'foo', - main: 'main.js', - }), - 'main.js': 'require("bar/lol");\n/* foo module */', - node_modules: { - bar: { - 'package.json': JSON.stringify({ - name: 'bar', - main: 'main.js', - browser: { - './lol': './wow', - }, - }), - 'main.js': '/* bar 1 module */', - 'lol.js': '', - 'wow.js': '', - }, - }, - }, - bar: { - 'package.json': JSON.stringify({ - name: 'bar', - browser: './main2', - }), - 'main2.js': '/* bar 2 module */', - }, - }, - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.js', - ).then(function(deps) { - expect(deps).toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['foo', 'bar'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'foo/main.js', - path: '/root/node_modules/foo/main.js', - dependencies: ['bar/lol'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'bar/lol.js', - path: '/root/node_modules/foo/node_modules/bar/lol.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'bar/main2.js', - path: '/root/node_modules/bar/main2.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - it('node_modules should support multi level', function() { - var root = '/root'; - setMockFileSystem({ - root: { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("bar");', - ].join('\n'), - node_modules: { - foo: { - 'package.json': JSON.stringify({ - name: 'foo', - main: 'main.js', - }), - 'main.js': '', - }, - }, - path: { - to: { - 'bar.js': [ - '/**', - ' * @providesModule bar', - ' */', - 'require("foo")', - ].join('\n'), - }, - node_modules: {}, - }, - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.js', - ).then(function(deps) { - expect(deps).toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['bar'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'bar', - path: '/root/path/to/bar.js', - dependencies: ['foo'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'foo/main.js', - path: '/root/node_modules/foo/main.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - it('should selectively ignore providesModule in node_modules', function() { - var root = '/root'; - var otherRoot = '/anotherRoot'; - const filesystem = { - root: { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("shouldWork");', - 'require("dontWork");', - 'require("wontWork");', - 'require("ember");', - 'require("internalVendoredPackage");', - 'require("anotherIndex");', - ].join('\n'), - node_modules: { - 'react-haste': { - 'package.json': JSON.stringify({ - name: 'react-haste', - main: 'main.js', - }), - // @providesModule should not be ignored here, because react-haste is whitelisted - 'main.js': [ - '/**', - ' * @providesModule shouldWork', - ' */', - 'require("submodule");', - ].join('\n'), - node_modules: { - bar: { - 'package.json': JSON.stringify({ - name: 'bar', - main: 'main.js', - }), - // @providesModule should be ignored here, because it's not whitelisted - 'main.js': [ - '/**', - ' * @providesModule dontWork', - ' */', - 'hi();', - ].join('\n'), - }, - submodule: { - 'package.json': JSON.stringify({ - name: 'submodule', - main: 'main.js', - }), - 'main.js': 'log()', - }, - }, - }, - ember: { - 'package.json': JSON.stringify({ - name: 'ember', - main: 'main.js', - }), - // @providesModule should be ignored here, because it's not whitelisted, - // and also, the modules "id" should be ember/main.js, not it's haste name - 'main.js': [ - '/**', - ' * @providesModule wontWork', - ' */', - 'hi();', - ].join('\n'), - }, - }, - // This part of the dep graph is meant to emulate internal facebook infra. - // By whitelisting `vendored_modules`, haste should still work. - vendored_modules: { - 'a-vendored-package': { - 'package.json': JSON.stringify({ - name: 'a-vendored-package', - main: 'main.js', - }), - // @providesModule should _not_ be ignored here, because it's whitelisted. - 'main.js': [ - '/**', - ' * @providesModule internalVendoredPackage', - ' */', - 'hiFromInternalPackage();', - ].join('\n'), - }, - }, - }, - // we need to support multiple roots and using haste between them - anotherRoot: { - 'index.js': [ - '/**', - ' * @providesModule anotherIndex', - ' */', - 'wazup()', - ].join('\n'), - }, - }; - setMockFileSystem(filesystem); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root, otherRoot], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js') - .catch(error => { - expect(error.type).toEqual('UnableToResolveError'); - }) - .then(() => { - filesystem.root['index.js'] = filesystem.root['index.js'] - .replace('require("dontWork")', '') - .replace('require("wontWork")', ''); - return triggerAndProcessWatchEvent( - dgraph, - 'change', - root + '/index.js', - ) - .then(() => getOrderedDependenciesAsJSON(dgraph, '/root/index.js')) - .then(deps => { - expect(deps).toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: [ - 'shouldWork', - 'ember', - 'internalVendoredPackage', - 'anotherIndex', - ], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'shouldWork', - path: '/root/node_modules/react-haste/main.js', - dependencies: ['submodule'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'submodule/main.js', - path: '/root/node_modules/react-haste/node_modules/submodule/main.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'ember/main.js', - path: '/root/node_modules/ember/main.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'internalVendoredPackage', - path: '/root/vendored_modules/a-vendored-package/main.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'anotherIndex', - path: '/anotherRoot/index.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - }); - - it('should not be confused by prev occuring whitelisted names', function() { - var root = '/react-haste'; - setMockFileSystem({ - 'react-haste': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("shouldWork");', - ].join('\n'), - node_modules: { - 'react-haste': { - 'package.json': JSON.stringify({ - name: 'react-haste', - main: 'main.js', - }), - 'main.js': ['/**', ' * @providesModule shouldWork', ' */'].join( - '\n', - ), - }, - }, - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/react-haste/index.js', - ).then(function(deps) { - expect(deps).toEqual([ - { - id: 'index', - path: '/react-haste/index.js', - dependencies: ['shouldWork'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'shouldWork', - path: '/react-haste/node_modules/react-haste/main.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - it('should work with node packages with a .js in the name', function() { - var root = '/root'; - setMockFileSystem({ - root: { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("sha.js")', - ].join('\n'), - node_modules: { - 'sha.js': { - 'package.json': JSON.stringify({ - name: 'sha.js', - main: 'main.js', - }), - 'main.js': 'lol', - }, - }, - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.js', - ).then(function(deps) { - expect(deps).toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['sha.js'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'sha.js/main.js', - path: '/root/node_modules/sha.js/main.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - it('should work with multiple platforms (haste)', function() { - var root = '/root'; - setMockFileSystem({ - root: { - 'index.ios.js': ` - /** - * @providesModule index - */ - require('a'); - `, - 'a.ios.js': ` - /** - * @providesModule a - */ - `, - 'a.android.js': ` - /** - * @providesModule a - */ - `, - 'a.js': ` - /** - * @providesModule a - */ - `, - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.ios.js', - ).then(function(deps) { - expect(deps).toEqual([ - { - id: 'index', - path: '/root/index.ios.js', - dependencies: ['a'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'a', - path: '/root/a.ios.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - it('should pick the generic file', function() { - var root = '/root'; - setMockFileSystem({ - root: { - 'index.ios.js': ` - /** - * @providesModule index - */ - require('a'); - `, - 'a.android.js': ` - /** - * @providesModule a - */ - `, - 'a.js': ` - /** - * @providesModule a - */ - `, - 'a.web.js': ` - /** - * @providesModule a - */ - `, - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - platforms: new Set(['ios', 'android', 'web']), - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.ios.js', - ).then(function(deps) { - expect(deps).toEqual([ - { - id: 'index', - path: '/root/index.ios.js', - dependencies: ['a'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'a', - path: '/root/a.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - it('should work with multiple platforms (node)', function() { - var root = '/root'; - setMockFileSystem({ - root: { - 'index.ios.js': ` - /** - * @providesModule index - */ - require('./a'); - `, - 'a.ios.js': '', - 'a.android.js': '', - 'a.js': '', - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.ios.js', - ).then(function(deps) { - expect(deps).toEqual([ - { - id: 'index', - path: '/root/index.ios.js', - dependencies: ['./a'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: '/root/a.ios.js', - path: '/root/a.ios.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - it('should require package.json', () => { - var root = '/root'; - setMockFileSystem({ - root: { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("foo/package.json");', - 'require("bar");', - ].join('\n'), - node_modules: { - foo: { - 'package.json': JSON.stringify({ - name: 'foo', - main: 'main.js', - }), - }, - bar: { - 'package.json': JSON.stringify({ - name: 'bar', - main: 'main.js', - }), - 'main.js': 'require("./package.json")', - }, - }, - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.js', - ).then(deps => { - expect(deps).toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['foo/package.json', 'bar'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'foo/package.json', - path: '/root/node_modules/foo/package.json', - dependencies: [], - isAsset: false, - isJSON: true, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'bar/main.js', - path: '/root/node_modules/bar/main.js', - dependencies: ['./package.json'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'bar/package.json', - path: '/root/node_modules/bar/package.json', - dependencies: [], - isAsset: false, - isJSON: true, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - it('should work with one-character node_modules', () => { - const root = '/root'; - setMockFileSystem({ - [root.slice(1)]: { - 'index.js': 'require("a/index.js");', - node_modules: { - a: { - 'package.json': '{"name": "a", "version": "1.2.3"}', - 'index.js': '', - }, - }, - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.js', - ).then(function(deps) { - expect(deps).toEqual([ - { - id: '/root/index.js', - path: '/root/index.js', - dependencies: ['a/index.js'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'a/index.js', - path: '/root/node_modules/a/index.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - }); - - describe('node_modules (win32)', function() { - const realPlatform = process.platform; - - // these tests will not work in a simulated way on linux testing VMs - // due to the drive letter expectation - if (realPlatform !== 'win32') { - return; - } - - const DependencyGraph = require('../DependencyGraph'); - - it('should work with nested node_modules', function() { - var root = '/root'; - setMockFileSystem({ - root: { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("foo");', - 'require("bar");', - ].join('\n'), - node_modules: { - foo: { - 'package.json': JSON.stringify({ - name: 'foo', - main: 'main.js', - }), - 'main.js': 'require("bar");\n/* foo module */', - node_modules: { - bar: { - 'package.json': JSON.stringify({ - name: 'bar', - main: 'main.js', - }), - 'main.js': '/* bar 1 module */', - }, - }, - }, - bar: { - 'package.json': JSON.stringify({ - name: 'bar', - main: 'main.js', - }), - 'main.js': '/* bar 2 module */', - }, - }, - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.js', - ).then(function(deps) { - expect(deps).toEqual([ - { - id: 'index', - path: 'C:\\root\\index.js', - dependencies: ['foo', 'bar'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'foo/main.js', - path: 'C:\\root\\node_modules\\foo\\main.js', - dependencies: ['bar'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'bar/main.js', - path: 'C:\\root\\node_modules\\foo\\node_modules\\bar\\main.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'bar/main.js', - path: 'C:\\root\\node_modules\\bar\\main.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - it('platform should work with node_modules', function() { - var root = '/root'; - setMockFileSystem({ - root: { - 'index.ios.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("foo");', - 'require("bar");', - ].join('\n'), - node_modules: { - foo: { - 'package.json': JSON.stringify({ - name: 'foo', - }), - 'index.ios.js': '', - }, - bar: { - 'package.json': JSON.stringify({ - name: 'bar', - main: 'main', - }), - 'main.ios.js': '', - }, - }, - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.ios.js', - ).then(function(deps) { - expect(deps).toEqual([ - { - id: 'index', - path: 'C:\\root\\index.ios.js', - dependencies: ['foo', 'bar'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'foo/index.ios.js', - path: 'C:\\root\\node_modules\\foo\\index.ios.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'bar/main.ios.js', - path: 'C:\\root\\node_modules\\bar\\main.ios.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - it('nested node_modules with specific paths', function() { - var root = '/root'; - setMockFileSystem({ - root: { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("foo");', - 'require("bar/");', - ].join('\n'), - node_modules: { - foo: { - 'package.json': JSON.stringify({ - name: 'foo', - main: 'main.js', - }), - 'main.js': 'require("bar/lol");\n/* foo module */', - node_modules: { - bar: { - 'package.json': JSON.stringify({ - name: 'bar', - main: 'main.js', - }), - 'main.js': '/* bar 1 module */', - 'lol.js': '', - }, - }, - }, - bar: { - 'package.json': JSON.stringify({ - name: 'bar', - main: 'main.js', - }), - 'main.js': '/* bar 2 module */', - }, - }, - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.js', - ).then(function(deps) { - expect(deps).toEqual([ - { - id: 'index', - path: 'C:\\root\\index.js', - dependencies: ['foo', 'bar/'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'foo/main.js', - path: 'C:\\root\\node_modules\\foo\\main.js', - dependencies: ['bar/lol'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'bar/lol.js', - path: 'C:\\root\\node_modules\\foo\\node_modules\\bar\\lol.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'bar/main.js', - path: 'C:\\root\\node_modules\\bar\\main.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - it('nested node_modules with browser field', function() { - var root = '/root'; - setMockFileSystem({ - root: { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("foo");', - 'require("bar");', - ].join('\n'), - node_modules: { - foo: { - 'package.json': JSON.stringify({ - name: 'foo', - main: 'main.js', - }), - 'main.js': 'require("bar/lol");\n/* foo module */', - node_modules: { - bar: { - 'package.json': JSON.stringify({ - name: 'bar', - main: 'main.js', - browser: { - './lol': './wow', - }, - }), - 'main.js': '/* bar 1 module */', - 'lol.js': '', - 'wow.js': '', - }, - }, - }, - bar: { - 'package.json': JSON.stringify({ - name: 'bar', - browser: './main2', - }), - 'main2.js': '/* bar 2 module */', - }, - }, - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.js', - ).then(function(deps) { - expect(deps).toEqual([ - { - id: 'index', - path: 'C:\\root\\index.js', - dependencies: ['foo', 'bar'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'foo/main.js', - path: 'C:\\root\\node_modules\\foo\\main.js', - dependencies: ['bar/lol'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'bar/lol.js', - path: 'C:\\root\\node_modules\\foo\\node_modules\\bar\\lol.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'bar/main2.js', - path: 'C:\\root\\node_modules\\bar\\main2.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - it('node_modules should support multi level', function() { - var root = '/root'; - setMockFileSystem({ - root: { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("bar");', - ].join('\n'), - node_modules: { - foo: { - 'package.json': JSON.stringify({ - name: 'foo', - main: 'main.js', - }), - 'main.js': '', - }, - }, - path: { - to: { - 'bar.js': [ - '/**', - ' * @providesModule bar', - ' */', - 'require("foo")', - ].join('\n'), - }, - node_modules: {}, - }, - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.js', - ).then(function(deps) { - expect(deps).toEqual([ - { - id: 'index', - path: 'C:\\root\\index.js', - dependencies: ['bar'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'bar', - path: 'C:\\root\\path\\to\\bar.js', - dependencies: ['foo'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'foo/main.js', - path: 'C:\\root\\node_modules\\foo\\main.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - it('should selectively ignore providesModule in node_modules', function() { - var root = '/root'; - var otherRoot = '/anotherRoot'; - setMockFileSystem({ - root: { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("shouldWork");', - 'require("dontWork");', - 'require("wontWork");', - 'require("ember");', - 'require("internalVendoredPackage");', - 'require("anotherIndex");', - ].join('\n'), - node_modules: { - 'react-haste': { - 'package.json': JSON.stringify({ - name: 'react-haste', - main: 'main.js', - }), - // @providesModule should not be ignored here, because react-haste is whitelisted - 'main.js': [ - '/**', - ' * @providesModule shouldWork', - ' */', - 'require("submodule");', - ].join('\n'), - node_modules: { - bar: { - 'package.json': JSON.stringify({ - name: 'bar', - main: 'main.js', - }), - // @providesModule should be ignored here, because it's not whitelisted - 'main.js': [ - '/**', - ' * @providesModule dontWork', - ' */', - 'hi();', - ].join('\n'), - }, - submodule: { - 'package.json': JSON.stringify({ - name: 'submodule', - main: 'main.js', - }), - 'main.js': 'log()', - }, - }, - }, - ember: { - 'package.json': JSON.stringify({ - name: 'ember', - main: 'main.js', - }), - // @providesModule should be ignored here, because it's not whitelisted, - // and also, the modules "id" should be ember/main.js, not it's haste name - 'main.js': [ - '/**', - ' * @providesModule wontWork', - ' */', - 'hi();', - ].join('\n'), - }, - }, - // This part of the dep graph is meant to emulate internal facebook infra. - // By whitelisting `vendored_modules`, haste should still work. - vendored_modules: { - 'a-vendored-package': { - 'package.json': JSON.stringify({ - name: 'a-vendored-package', - main: 'main.js', - }), - // @providesModule should _not_ be ignored here, because it's whitelisted. - 'main.js': [ - '/**', - ' * @providesModule internalVendoredPackage', - ' */', - 'hiFromInternalPackage();', - ].join('\n'), - }, - }, - }, - // we need to support multiple roots and using haste between them - anotherRoot: { - 'index.js': [ - '/**', - ' * @providesModule anotherIndex', - ' */', - 'wazup()', - ].join('\n'), - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root, otherRoot], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.js', - ).then(function(deps) { - expect(deps).toEqual([ - { - id: 'index', - path: 'C:\\root\\index.js', - dependencies: [ - 'shouldWork', - 'dontWork', - 'wontWork', - 'ember', - 'internalVendoredPackage', - 'anotherIndex', - ], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'shouldWork', - path: 'C:\\root\\node_modules\\react-haste\\main.js', - dependencies: ['submodule'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'submodule/main.js', - path: 'C:\\root\\node_modules\\react-haste\\node_modules\\submodule\\main.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'ember/main.js', - path: 'C:\\root\\node_modules\\ember\\main.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'internalVendoredPackage', - path: 'C:\\root\\vendored_modules\\a-vendored-package\\main.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'anotherIndex', - path: 'C:\\anotherRoot\\index.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - it('should not be confused by prev occuring whitelisted names', function() { - var root = '/react-haste'; - setMockFileSystem({ - 'react-haste': { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("shouldWork");', - ].join('\n'), - node_modules: { - 'react-haste': { - 'package.json': JSON.stringify({ - name: 'react-haste', - main: 'main.js', - }), - 'main.js': ['/**', ' * @providesModule shouldWork', ' */'].join( - '\n', - ), - }, - }, - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/react-haste/index.js', - ).then(function(deps) { - expect(deps).toEqual([ - { - id: 'index', - path: 'C:\\react-haste\\index.js', - dependencies: ['shouldWork'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'shouldWork', - path: 'C:\\react-haste\\node_modules\\react-haste\\main.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - it('should ignore modules it cant find (assumes own require system)', function() { - // For example SourceMap.js implements it's own require system. - var root = '/root'; - setMockFileSystem({ - root: { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("foo/lol");', - ].join('\n'), - node_modules: { - foo: { - 'package.json': JSON.stringify({ - name: 'foo', - main: 'main.js', - }), - 'main.js': '/* foo module */', - }, - }, - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.js', - ).then(function(deps) { - expect(deps).toEqual([ - { - id: 'index', - path: 'C:\\root\\index.js', - dependencies: ['foo/lol'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - it('should work with node packages with a .js in the name', function() { - var root = '/root'; - setMockFileSystem({ - root: { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("sha.js")', - ].join('\n'), - node_modules: { - 'sha.js': { - 'package.json': JSON.stringify({ - name: 'sha.js', - main: 'main.js', - }), - 'main.js': 'lol', - }, - }, - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.js', - ).then(function(deps) { - expect(deps).toEqual([ - { - id: 'index', - path: 'C:\\root\\index.js', - dependencies: ['sha.js'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'sha.js/main.js', - path: 'C:\\root\\node_modules\\sha.js\\main.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - it('should work with multiple platforms (haste)', function() { - var root = '/root'; - setMockFileSystem({ - root: { - 'index.ios.js': ` - /** - * @providesModule index - */ - require('a'); - `, - 'a.ios.js': ` - /** - * @providesModule a - */ - `, - 'a.android.js': ` - /** - * @providesModule a - */ - `, - 'a.js': ` - /** - * @providesModule a - */ - `, - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.ios.js', - ).then(function(deps) { - expect(deps).toEqual([ - { - id: 'index', - path: 'C:\\root\\index.ios.js', - dependencies: ['a'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'a', - path: 'C:\\root\\a.ios.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - it('should pick the generic file', function() { - var root = '/root'; - setMockFileSystem({ - root: { - 'index.ios.js': ` - /** - * @providesModule index - */ - require('a'); - `, - 'a.android.js': ` - /** - * @providesModule a - */ - `, - 'a.js': ` - /** - * @providesModule a - */ - `, - 'a.web.js': ` - /** - * @providesModule a - */ - `, - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.ios.js', - ).then(function(deps) { - expect(deps).toEqual([ - { - id: 'index', - path: 'C:\\root\\index.ios.js', - dependencies: ['a'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'a', - path: 'C:\\root\\a.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - it('should work with multiple platforms (node)', function() { - var root = '/root'; - setMockFileSystem({ - root: { - 'index.ios.js': ` - /** - * @providesModule index - */ - require('./a'); - `, - 'a.ios.js': '', - 'a.android.js': '', - 'a.js': '', - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.ios.js', - ).then(function(deps) { - expect(deps).toEqual([ - { - id: 'index', - path: 'C:\\root\\index.ios.js', - dependencies: ['./a'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'C:\\root\\a.ios.js', - path: 'C:\\root\\a.ios.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - - it('should require package.json', () => { - var root = '/root'; - setMockFileSystem({ - root: { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("foo/package.json");', - 'require("bar");', - ].join('\n'), - node_modules: { - foo: { - 'package.json': JSON.stringify({ - name: 'foo', - main: 'main.js', - }), - }, - bar: { - 'package.json': JSON.stringify({ - name: 'bar', - main: 'main.js', - }), - 'main.js': 'require("./package.json")', - }, - }, - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.js', - ).then(deps => { - expect(deps).toEqual([ - { - id: 'index', - path: 'C:\\root\\index.js', - dependencies: ['foo/package.json', 'bar'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'foo/package.json', - path: 'C:\\root\\node_modules\\foo\\package.json', - dependencies: [], - isAsset: false, - isJSON: true, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'bar/main.js', - path: 'C:\\root\\node_modules\\bar\\main.js', - dependencies: ['./package.json'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'bar/package.json', - path: 'C:\\root\\node_modules\\bar\\package.json', - dependencies: [], - isAsset: false, - isJSON: true, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - }); - - describe('file watch updating', function() { - const realPlatform = process.platform; - let DependencyGraph; - - beforeEach(function() { - process.platform = 'linux'; - DependencyGraph = require('../DependencyGraph'); - }); - - afterEach(function() { - process.platform = realPlatform; - }); - - it('updates module dependencies', function() { - var root = '/root'; - var filesystem = setMockFileSystem({ - root: { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - 'require("foo")', - ].join('\n'), - 'foo.js': [ - '/**', - ' * @providesModule foo', - ' */', - 'require("aPackage")', - ].join('\n'), - aPackage: { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js', - }), - 'main.js': 'main', - }, - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.js', - ).then(function() { - filesystem.root['index.js'] = filesystem.root['index.js'].replace( - 'require("foo")', - '', - ); - return triggerAndProcessWatchEvent(dgraph, 'change', root + '/index.js') - .then(() => getOrderedDependenciesAsJSON(dgraph, '/root/index.js')) - .then(function(deps) { - expect(deps).toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'aPackage/main.js', - path: '/root/aPackage/main.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - }); - - it('updates module dependencies on file change', function() { - var root = '/root'; - var filesystem = setMockFileSystem({ - root: { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - 'require("foo")', - ].join('\n'), - 'foo.js': [ - '/**', - ' * @providesModule foo', - ' */', - 'require("aPackage")', - ].join('\n'), - aPackage: { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js', - }), - 'main.js': 'main', - }, - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.js', - ).then(function() { - filesystem.root['index.js'] = filesystem.root['index.js'].replace( - 'require("foo")', - '', - ); - return triggerAndProcessWatchEvent(dgraph, 'change', root + '/index.js') - .then(() => getOrderedDependenciesAsJSON(dgraph, '/root/index.js')) - .then(function(deps) { - expect(deps).toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'aPackage/main.js', - path: '/root/aPackage/main.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - ]); - }); - }); - }); - - it('updates module dependencies on file delete', function() { - expect.assertions(1); - var root = '/root'; - var filesystem = setMockFileSystem({ - root: { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - 'require("foo")', - ].join('\n'), - 'foo.js': [ - '/**', - ' * @providesModule foo', - ' */', - 'require("aPackage")', - ].join('\n'), - aPackage: { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js', - }), - 'main.js': 'main', - }, - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON( - dgraph, - '/root/index.js', - ).then(function() { - delete filesystem.root['foo.js']; - return triggerAndProcessWatchEvent(dgraph, 'change', root + '/foo.js') - .then(() => getOrderedDependenciesAsJSON(dgraph, '/root/index.js')) - .catch(error => expect(error.type).toEqual('UnableToResolveError')); - }); - }); - - it('updates module dependencies on file add', function() { - expect.assertions(1); - var root = '/root'; - var filesystem = setMockFileSystem({ - root: { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - 'require("foo")', - ].join('\n'), - 'foo.js': [ - '/**', - ' * @providesModule foo', - ' */', - 'require("aPackage")', - ].join('\n'), - aPackage: { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js', - }), - 'main.js': 'main', - }, - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js') - .then(function() { - filesystem.root['bar.js'] = [ - '/**', - ' * @providesModule bar', - ' */', - 'require("foo")', - ].join('\n'); - return triggerAndProcessWatchEvent( - dgraph, - 'change', - root + '/bar.js', - ); - }) - .then(() => { - filesystem.root.aPackage['main.js'] = 'require("bar")'; - return triggerAndProcessWatchEvent( - dgraph, - 'change', - root + '/aPackage/main.js', - ); - }) - .then(() => getOrderedDependenciesAsJSON(dgraph, '/root/index.js')) - .then(function(deps) { - expect(deps).toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['aPackage', 'foo'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'aPackage/main.js', - path: '/root/aPackage/main.js', - dependencies: ['bar'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - }, - { - id: 'bar', - path: '/root/bar.js', - dependencies: ['foo'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'foo', - path: '/root/foo.js', - dependencies: ['aPackage'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - }); - }); - - it('updates module dependencies on relative asset add', function() { - var root = '/root'; - var filesystem = setMockFileSystem({ - root: { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("./foo.png")', - ].join('\n'), - 'package.json': JSON.stringify({ - name: 'aPackage', - }), - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - assetExts: ['png'], - }); - - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js') - .catch(error => { - expect(error.type).toEqual('UnableToResolveError'); - }) - .then(() => { - filesystem.root['foo.png'] = ''; - return triggerAndProcessWatchEvent( - dgraph, - 'change', - root + '/foo.png', - ); - }) - .then(() => getOrderedDependenciesAsJSON(dgraph, '/root/index.js')) - .then(function(deps2) { - expect(deps2).toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['./foo.png'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'aPackage/foo.png', - path: '/root/foo.png', - dependencies: [], - isAsset: true, - resolution: 1, - isJSON: false, - isPolyfill: false, - resolveDependency: undefined, - }, - ]); - }); - }); - - it('changes to browser field', function() { - expect.assertions(1); - var root = '/root'; - var filesystem = setMockFileSystem({ - root: { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - aPackage: { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js', - }), - 'main.js': 'main', - 'browser.js': 'browser', - }, - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js') - .then(function() { - filesystem.root.aPackage['package.json'] = JSON.stringify({ - name: 'aPackage', - main: 'main.js', - browser: 'browser.js', - }); - return triggerAndProcessWatchEvent( - dgraph, - 'change', - root + '/aPackage/package.json', - ); - }) - .then(() => getOrderedDependenciesAsJSON(dgraph, '/root/index.js')) - .then(function(deps) { - expect(deps).toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['aPackage'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'aPackage/browser.js', - path: '/root/aPackage/browser.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - }); - }); - - it('removes old package from cache', function() { - var root = '/root'; - var filesystem = setMockFileSystem({ - root: { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("aPackage")', - ].join('\n'), - aPackage: { - 'package.json': JSON.stringify({ - name: 'aPackage', - main: 'main.js', - }), - 'main.js': 'main', - 'browser.js': 'browser', - }, - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js') - .then(function() { - filesystem.root['index.js'] = [ - '/**', - ' * @providesModule index', - ' */', - 'require("bPackage")', - ].join('\n'); - filesystem.root.aPackage['package.json'] = JSON.stringify({ - name: 'bPackage', - main: 'main.js', - }); - return dgraph.then( - dg => - new Promise(resolve => { - dg.once('change', () => resolve()); - triggerWatchEvent('change', root + '/index.js'); - triggerWatchEvent('change', root + '/aPackage/package.json'); - }), - ); - }) - .then(() => getOrderedDependenciesAsJSON(dgraph, '/root/index.js')) - .then(function(deps) { - expect(deps).toEqual([ - { - dependencies: ['bPackage'], - id: 'index', - isAsset: false, - isJSON: false, - isPolyfill: false, - path: '/root/index.js', - resolution: undefined, - resolveDependency: undefined, - }, - { - dependencies: [], - id: 'bPackage/main.js', - isAsset: false, - isJSON: false, - isPolyfill: false, - path: '/root/aPackage/main.js', - resolution: undefined, - }, - ]); - }); - }); - - it('should update node package changes', function() { - expect.assertions(2); - var root = '/root'; - var filesystem = setMockFileSystem({ - root: { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("foo");', - ].join('\n'), - node_modules: { - foo: { - 'package.json': JSON.stringify({ - name: 'foo', - main: 'main.js', - }), - 'main.js': 'require("bar");\n/* foo module */', - node_modules: { - bar: { - 'package.json': JSON.stringify({ - name: 'bar', - main: 'main.js', - }), - 'main.js': '/* bar 1 module */', - }, - }, - }, - }, - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js') - .then(function(deps) { - expect(deps).toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['foo'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'foo/main.js', - path: '/root/node_modules/foo/main.js', - dependencies: ['bar'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'bar/main.js', - path: '/root/node_modules/foo/node_modules/bar/main.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - - filesystem.root.node_modules.foo['main.js'] = 'lol'; - return triggerAndProcessWatchEvent( - dgraph, - 'change', - root + '/node_modules/foo/main.js', - ); - }) - .then(() => getOrderedDependenciesAsJSON(dgraph, '/root/index.js')) - .then(function(deps2) { - expect(deps2).toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['foo'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'foo/main.js', - path: '/root/node_modules/foo/main.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - }); - }); - - it('should update node package main changes', function() { - expect.assertions(1); - var root = '/root'; - var filesystem = setMockFileSystem({ - root: { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'require("foo");', - ].join('\n'), - node_modules: { - foo: { - 'package.json': JSON.stringify({ - name: 'foo', - main: 'main.js', - }), - 'main.js': '/* foo module */', - 'browser.js': '/* foo module */', - }, - }, - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js') - .then(function(deps) { - filesystem.root.node_modules.foo['package.json'] = JSON.stringify({ - name: 'foo', - main: 'main.js', - browser: 'browser.js', - }); - return triggerAndProcessWatchEvent( - dgraph, - 'change', - root + '/node_modules/foo/package.json', - ); - }) - .then(() => getOrderedDependenciesAsJSON(dgraph, '/root/index.js')) - .then(function(deps2) { - expect(deps2).toEqual([ - { - id: 'index', - path: '/root/index.js', - dependencies: ['foo'], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - { - id: 'foo/browser.js', - path: '/root/node_modules/foo/browser.js', - dependencies: [], - isAsset: false, - isJSON: false, - isPolyfill: false, - resolution: undefined, - resolveDependency: undefined, - }, - ]); - }); - }); - - it('should not error when the watcher reports a known file as added', function() { - expect.assertions(1); - var root = '/root'; - setMockFileSystem({ - root: { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - 'var b = require("b");', - ].join('\n'), - 'b.js': [ - '/**', - ' * @providesModule b', - ' */', - 'module.exports = function() {};', - ].join('\n'), - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - }); - - return getOrderedDependenciesAsJSON(dgraph, '/root/index.js') - .then(() => - triggerAndProcessWatchEvent(dgraph, 'change', root + '/index.js'), - ) - .then(() => getOrderedDependenciesAsJSON(dgraph, '/root/index.js')) - .then(deps => { - expect(deps).toBeDefined(); - }); - }); - - it('should recover from multiple modules with the same name', async () => { - const root = '/root'; - console.warn = jest.fn(); - const filesystem = setMockFileSystem({ - root: { - 'index.js': [ - '/**', - ' * @providesModule index', - ' */', - "require('a')", - "require('b')", - ].join('\n'), - 'a.js': ['/**', ' * @providesModule a', ' */'].join('\n'), - 'b.js': ['/**', ' * @providesModule b', ' */'].join('\n'), - }, - }); - - const dgraph = DependencyGraph.load({...defaults, roots: [root]}); - await getOrderedDependenciesAsJSON(dgraph, root + '/index.js'); - filesystem.root['b.js'] = ['/**', ' * @providesModule a', ' */'].join( - '\n', - ); - await triggerAndProcessWatchEvent(dgraph, 'change', root + '/b.js'); - try { - await getOrderedDependenciesAsJSON(dgraph, root + '/index.js'); - throw new Error('expected `getOrderedDependenciesAsJSON` to fail'); - } catch (error) { - if (error.type !== 'UnableToResolveError') { - throw error; - } - expect(console.warn).toBeCalled(); - filesystem.root['b.js'] = ['/**', ' * @providesModule b', ' */'].join( - '\n', - ); - await triggerAndProcessWatchEvent(dgraph, 'change', root + '/b.js'); - } - - const deps = await getOrderedDependenciesAsJSON( - dgraph, - root + '/index.js', - ); - expect(deps).toMatchSnapshot(); - }); - }); - - describe('Extensions', () => { - const realPlatform = process.platform; - let DependencyGraph; - beforeEach(function() { - process.platform = 'linux'; - DependencyGraph = require('../DependencyGraph'); - }); - - afterEach(function() { - process.platform = realPlatform; - }); - - it('supports custom file extensions', () => { - var root = '/root'; - setMockFileSystem({ - root: { - 'index.jsx': [ - '/**', - ' * @providesModule index', - ' */', - 'require("a")', - ].join('\n'), - 'a.coffee': ['/**', ' * @providesModule a', ' */'].join('\n'), - 'X.js': '', - }, - }); - - var dgraph = DependencyGraph.load({ - ...defaults, - roots: [root], - sourceExts: ['jsx', 'coffee'], - }); - - return dgraph - .then(dg => dg.matchFilesByPattern('.*')) - .then(files => { - expect(files).toEqual(['/root/index.jsx', '/root/a.coffee']); - }) - .then(() => getOrderedDependenciesAsJSON(dgraph, '/root/index.jsx')) - .then(deps => { - expect(deps).toEqual([ - { - dependencies: ['a'], - id: 'index', - isAsset: false, - isJSON: false, - isPolyfill: false, - path: '/root/index.jsx', - resolution: undefined, - }, - { - dependencies: [], - id: 'a', - isAsset: false, - isJSON: false, - isPolyfill: false, - path: '/root/a.coffee', - resolution: undefined, - }, - ]); - }); - }); - - it('supports custom file extensions with relative paths', async () => { - const root = '/root'; - setMockFileSystem({ - root: { - 'index.jsx': ['require("./a")'].join('\n'), - 'a.coffee': [].join('\n'), - 'X.js': '', - }, - }); - - const dgraph = await DependencyGraph.load({ - ...defaults, - roots: [root], - sourceExts: ['jsx', 'coffee'], - }); - const files = await dgraph.matchFilesByPattern('.*'); - expect(files).toEqual(['/root/index.jsx', '/root/a.coffee']); - - const deps = await getOrderedDependenciesAsJSON( - dgraph, - '/root/index.jsx', - ); - expect(deps).toEqual([ - { - dependencies: ['./a'], - id: '/root/index.jsx', - isAsset: false, - isJSON: false, - isPolyfill: false, - path: '/root/index.jsx', - resolution: undefined, - }, - { - dependencies: [], - id: '/root/a.coffee', - isAsset: false, - isJSON: false, - isPolyfill: false, - path: '/root/a.coffee', - resolution: undefined, - }, - ]); - }); - - it('does not include extensions that are not specified explicitely', async () => { - const root = '/root'; - setMockFileSystem({ - root: { - 'index.jsx': ['require("./a")'].join('\n'), - 'a.coffee': [].join('\n'), - 'X.js': '', - }, - }); - - const dgraph = await DependencyGraph.load({ - ...defaults, - roots: [root], - }); - const files = await dgraph.matchFilesByPattern('.*'); - expect(files).toEqual(['/root/X.js']); - - try { - await getOrderedDependenciesAsJSON(dgraph, '/root/index.jsx'); - throw Error('should not reach this line'); - } catch (error) { - expect(error.type).toEqual('UnableToResolveError'); - } - }); - }); - - describe('Progress updates', () => { - let dependencyGraph, onProgress; - - function makeModule(id, dependencies = []) { - return ( - ` - /** - * @providesModule ${id} - */\n` + - dependencies.map(d => `require(${JSON.stringify(d)});`).join('\n') - ); - } - - function getDependencies() { - return dependencyGraph.getDependencies({ - entryPath: '/root/index.js', - onProgress, - options: emptyTransformOptions, - }); - } - - beforeEach(function() { - onProgress = jest.genMockFn(); - setMockFileSystem({ - root: { - 'index.js': makeModule('index', ['a', 'b']), - 'a.js': makeModule('a', ['c', 'd']), - 'b.js': makeModule('b', ['d', 'e']), - 'c.js': makeModule('c'), - 'd.js': makeModule('d', ['f']), - 'e.js': makeModule('e', ['f']), - 'f.js': makeModule('f', ['g']), - 'g.js': makeModule('g'), - }, - }); - const DependencyGraph = require('../DependencyGraph'); - return DependencyGraph.load({ - ...defaults, - roots: ['/root'], - }).then(dg => { - dependencyGraph = dg; - }); - }); - - it('calls back for each finished module', () => { - return getDependencies().then(() => - expect(onProgress.mock.calls.length).toBe(8), - ); - }); - - it('increases the number of finished modules in steps of one', () => { - return getDependencies().then(() => { - const increments = onProgress.mock.calls.map(([finished]) => finished); - expect(increments).toEqual([1, 2, 3, 4, 5, 6, 7, 8]); - }); - }); - - it('adds the number of discovered modules to the number of total modules', () => { - return getDependencies().then(() => { - const increments = onProgress.mock.calls.map(([, total]) => total); - expect(increments).toEqual([3, 5, 6, 6, 7, 7, 8, 8]); - }); - }); - }); - - describe('Asset module dependencies', () => { - let DependencyGraph; - beforeEach(() => { - DependencyGraph = require('../DependencyGraph'); - }); - - it('allows setting dependencies for asset modules', () => { - const assetDependencies = ['/root/apple.png', '/root/banana.png']; - - setMockFileSystem({ - root: { - 'index.js': 'require("./a.png")', - 'a.png': '', - 'apple.png': '', - 'banana.png': '', - }, - }); - - DependencyGraph.load({ - ...defaults, - assetDependencies, - roots: ['/root'], - }) - .then(dependencyGraph => - dependencyGraph.getDependencies({ - entryPath: '/root/index.js', - }), - ) - .then(({dependencies}) => { - const [, assetModule] = dependencies; - return assetModule - .getDependencies() - .then(deps => expect(deps).toBe(assetDependencies)); - }); - }); - }); - - describe('Deterministic order of dependencies', () => { - let callDeferreds, dependencyGraph, moduleReadDeferreds; - let moduleRead; - let DependencyGraph; - - beforeEach(() => { - moduleRead = Module.prototype.read; - DependencyGraph = require('../DependencyGraph'); - setMockFileSystem({ - root: { - 'index.js': ` - require('./a'); - require('./b'); - `, - 'a.js': ` - require('./c'); - require('./d'); - `, - 'b.js': ` - require('./c'); - require('./d'); - `, - 'c.js': 'require("./e");', - 'd.js': '', - 'e.js': 'require("./f");', - 'f.js': 'require("./c");', // circular dependency - }, - }); - dependencyGraph = DependencyGraph.load({ - ...defaults, - roots: ['/root'], - }); - moduleReadDeferreds = {}; - callDeferreds = [defer(), defer()]; // [a.js, b.js] - - Module.prototype.read = jest.genMockFn().mockImplementation(function() { - const returnValue = moduleRead.apply(this, arguments); - if (/\/[ab]\.js$/.test(this.path)) { - let deferred = moduleReadDeferreds[this.path]; - if (!deferred) { - deferred = moduleReadDeferreds[this.path] = defer(returnValue); - const index = Number(this.path.endsWith('b.js')); // 0 or 1 - callDeferreds[index].resolve(); - } - return deferred.promise; - } - - return returnValue; - }); - }); - - afterEach(() => { - Module.prototype.read = moduleRead; - }); - - it('produces a deterministic tree if the "a" module resolves first', () => { - const dependenciesPromise = getOrderedDependenciesAsJSON( - dependencyGraph, - 'index.js', - ); - - return Promise.all(callDeferreds.map(deferred => deferred.promise)) - .then(() => { - const main = moduleReadDeferreds['/root/a.js']; - main.promise.then(() => { - moduleReadDeferreds['/root/b.js'].resolve(); - }); - main.resolve(); - return dependenciesPromise; - }) - .then(result => { - const names = result.map(({path: resultPath}) => - resultPath.split('/').pop(), - ); - expect(names).toEqual([ - 'index.js', - 'a.js', - 'c.js', - 'e.js', - 'f.js', - 'd.js', - 'b.js', - ]); - }); - }); - - it('produces a deterministic tree if the "b" module resolves first', () => { - const dependenciesPromise = getOrderedDependenciesAsJSON( - dependencyGraph, - 'index.js', - ); - - return Promise.all(callDeferreds.map(deferred => deferred.promise)) - .then(() => { - const main = moduleReadDeferreds['/root/b.js']; - main.promise.then(() => { - moduleReadDeferreds['/root/a.js'].resolve(); - }); - main.resolve(); - return dependenciesPromise; - }) - .then(result => { - const names = result.map(({path: resultPath}) => - resultPath.split('/').pop(), - ); - expect(names).toEqual([ - 'index.js', - 'a.js', - 'c.js', - 'e.js', - 'f.js', - 'd.js', - 'b.js', - ]); - }); - }); - }); - - function defer(value) { - let resolve; - const promise = new Promise(r => { - resolve = r; - }); - return {promise, resolve: () => resolve(value)}; - } - - function setMockFileSystem(object) { - return require('graceful-fs').__setMockFilesystem(object); - } - - function triggerAndProcessWatchEvent(dgraphPromise, eventType, filename) { - return dgraphPromise.then( - dgraph => - new Promise(resolve => { - dgraph.once('change', () => resolve()); - triggerWatchEvent(eventType, filename); - }), - ); - } - - function triggerWatchEvent(eventType, filename) { - return require('graceful-fs').__triggerWatchEvent(eventType, filename); - } -}); diff --git a/packager/src/node-haste/__tests__/Module-test.js b/packager/src/node-haste/__tests__/Module-test.js deleted file mode 100644 index 1c92071d708d94..00000000000000 --- a/packager/src/node-haste/__tests__/Module-test.js +++ /dev/null @@ -1,343 +0,0 @@ -/** - * 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'; - -jest - .dontMock('absolute-path') - .dontMock('json-stable-stringify') - .dontMock('crypto') - .dontMock('../DependencyGraph/docblock') - .dontMock('../Module'); - -jest - .mock('fs'); - -const Module = require('../Module'); -const ModuleCache = require('../ModuleCache'); -const DependencyGraphHelpers = require('../DependencyGraph/DependencyGraphHelpers'); -const TransformCaching = require('../../lib/TransformCaching'); -const fs = require('graceful-fs'); - -const packageJson = - JSON.stringify({ - name: 'arbitrary', - version: '1.0.0', - description: "A require('foo') story", - }); - -function mockFS(rootChildren) { - fs.__setMockFilesystem({root: rootChildren}); -} - -function mockPackageFile() { - mockFS({'package.json': packageJson}); -} - -function mockIndexFile(indexJs) { - mockFS({'index.js': indexJs}); -} - -describe('Module', () => { - const fileName = '/root/index.js'; - - let cache; - const transformCache = TransformCaching.mocked(); - - const createCache = () => ({ - get: jest.genMockFn().mockImplementation( - (filepath, field, cb) => cb(filepath) - ), - invalidate: jest.genMockFn(), - end: jest.genMockFn(), - }); - - let transformCacheKey; - const createModule = options => - new Module({ - options: {transformCache}, - transformCode: (module, sourceCode, transformOptions) => { - return Promise.resolve({code: sourceCode}); - }, - ...options, - cache, - file: options && options.file || fileName, - depGraphHelpers: new DependencyGraphHelpers(), - moduleCache: new ModuleCache({cache}), - getTransformCacheKey: () => transformCacheKey, - }); - - const createJSONModule = - options => createModule({...options, file: '/root/package.json'}); - - beforeEach(function() { - process.platform = 'linux'; - cache = createCache(); - transformCacheKey = 'abcdef'; - transformCache.mock.reset(); - }); - - describe('Module ID', () => { - const moduleId = 'arbitraryModule'; - const source = - `/** - * @providesModule ${moduleId} - */ - `; - - let module; - beforeEach(() => { - module = createModule(); - }); - - describe('@providesModule annotations', () => { - beforeEach(() => { - mockIndexFile(source); - }); - - it('extracts the module name from the header', () => - module.getName().then(name => expect(name).toEqual(moduleId)) - ); - - it('identifies the module as haste module', () => - expect(module.isHaste()).toBe(true) - ); - - it('does not transform the file in order to access the name', () => { - const transformCode = - jest.genMockFn().mockReturnValue(Promise.resolve()); - return createModule({transformCode}).getName() - .then(() => expect(transformCode).not.toBeCalled()); - }); - - it('does not transform the file in order to access the haste status', () => { - const transformCode = - jest.genMockFn().mockReturnValue(Promise.resolve()); - createModule({transformCode}).isHaste(); - expect(transformCode).not.toBeCalled(); - }); - }); - - describe('no annotation', () => { - beforeEach(() => { - mockIndexFile('arbitrary(code);'); - }); - - it('uses the file name as module name', () => - module.getName().then(name => expect(name).toEqual(fileName)) - ); - - it('does not identify the module as haste module', () => - expect(module.isHaste()).toBe(false) - ); - - it('does not transform the file in order to access the name', () => { - const transformCode = - jest.genMockFn().mockReturnValue(Promise.resolve()); - return createModule({transformCode}).getName() - .then(() => expect(transformCode).not.toBeCalled()); - }); - - it('does not transform the file in order to access the haste status', () => { - const transformCode = - jest.genMockFn().mockReturnValue(Promise.resolve()); - createModule({transformCode}).isHaste(); - expect(transformCode).not.toBeCalled(); - }); - }); - }); - - describe('Code', () => { - const fileContents = 'arbitrary(code)'; - beforeEach(function() { - mockIndexFile(fileContents); - }); - - it('exposes file contents as `code` property on the data exposed by `read()`', () => - createModule().read().then(({code}) => - expect(code).toBe(fileContents)) - ); - - it('exposes file contents via the `getCode()` method', () => - createModule().getCode().then(code => - expect(code).toBe(fileContents)) - ); - }); - - describe('Custom Code Transform', () => { - let transformCode; - let transformResult; - const fileContents = 'arbitrary(code);'; - const exampleCode = ` - ${'require'}('a'); - ${'System.import'}('b'); - ${'require'}('c');`; - - beforeEach(function() { - transformResult = {code: ''}; - transformCode = jest.genMockFn() - .mockImplementation((module, sourceCode, options) => { - transformCache.writeSync({ - filePath: module.path, - sourceCode, - transformOptions: options, - getTransformCacheKey: () => transformCacheKey, - result: transformResult, - }); - return Promise.resolve(transformResult); - }); - mockIndexFile(fileContents); - }); - - it('passes the module and file contents to the transform function when reading', () => { - const module = createModule({transformCode}); - return module.read() - .then(() => { - expect(transformCode).toBeCalledWith(module, fileContents, undefined); - }); - }); - - it('passes any additional options to the transform function when reading', () => { - const module = createModule({transformCode}); - const transformOptions = {arbitrary: Object()}; - return module.read(transformOptions) - .then(() => - expect(transformCode.mock.calls[0][2]).toBe(transformOptions) - ); - }); - - it('passes the module and file contents to the transform for JSON files', () => { - mockPackageFile(); - const module = createJSONModule({transformCode}); - return module.read().then(() => { - expect(transformCode).toBeCalledWith(module, packageJson, undefined); - }); - }); - - it('does not extend the passed options object for JSON files', () => { - mockPackageFile(); - const module = createJSONModule({transformCode}); - const options = {arbitrary: 'foo'}; - return module.read(options).then(() => { - expect(transformCode).toBeCalledWith(module, packageJson, options); - }); - }); - - it('uses dependencies that `transformCode` resolves to, instead of extracting them', () => { - const mockedDependencies = ['foo', 'bar']; - transformResult = { - code: exampleCode, - dependencies: mockedDependencies, - }; - const module = createModule({transformCode}); - - return module.getDependencies().then(dependencies => { - expect(dependencies).toEqual(mockedDependencies); - }); - }); - - it('forwards all additional properties of the result provided by `transformCode`', () => { - transformResult = { - code: exampleCode, - arbitrary: 'arbitrary', - dependencyOffsets: [12, 764], - map: {version: 3}, - subObject: {foo: 'bar'}, - }; - const module = createModule({transformCode}); - - return module.read().then(result => { - expect(result).toEqual(jasmine.objectContaining(transformResult)); - }); - }); - - it('exposes the transformed code rather than the raw file contents', () => { - transformResult = {code: exampleCode}; - const module = createModule({transformCode}); - return Promise.all([module.read(), module.getCode()]) - .then(([data, code]) => { - expect(data.code).toBe(exampleCode); - expect(code).toBe(exampleCode); - }); - }); - - it('exposes the raw file contents as `source` property', () => { - const module = createModule({transformCode}); - return module.read() - .then(data => expect(data.source).toBe(fileContents)); - }); - - it('exposes a source map returned by the transform', () => { - const map = {version: 3}; - transformResult = {map, code: exampleCode}; - const module = createModule({transformCode}); - return Promise.all([module.read(), module.getMap()]) - .then(([data, sourceMap]) => { - expect(data.map).toBe(map); - expect(sourceMap).toBe(map); - }); - }); - - it('caches the transform result for the same transform options', () => { - let module = createModule({transformCode}); - return module.read() - .then(() => { - expect(transformCode).toHaveBeenCalledTimes(1); - // We want to check transform caching rather than shallow caching of - // Promises returned by read(). - module = createModule({transformCode}); - return module.read() - .then(() => { - expect(transformCode).toHaveBeenCalledTimes(1); - }); - }); - }); - - it('triggers a new transform for different transform options', () => { - const module = createModule({transformCode}); - return module.read({foo: 1}) - .then(() => { - expect(transformCode).toHaveBeenCalledTimes(1); - return module.read({foo: 2}) - .then(() => { - expect(transformCode).toHaveBeenCalledTimes(2); - }); - }); - }); - - it('triggers a new transform for different source code', () => { - let module = createModule({transformCode}); - return module.read() - .then(() => { - expect(transformCode).toHaveBeenCalledTimes(1); - cache = createCache(); - mockIndexFile('test'); - module = createModule({transformCode}); - return module.read() - .then(() => { - expect(transformCode).toHaveBeenCalledTimes(2); - }); - }); - }); - - it('triggers a new transform for different transform cache key', () => { - let module = createModule({transformCode}); - return module.read() - .then(() => { - expect(transformCode).toHaveBeenCalledTimes(1); - transformCacheKey = 'other'; - module = createModule({transformCode}); - return module.read() - .then(() => { - expect(transformCode).toHaveBeenCalledTimes(2); - }); - }); - }); - - }); -}); diff --git a/packager/src/node-haste/__tests__/__snapshots__/AssetResolutionCache-test.js.snap b/packager/src/node-haste/__tests__/__snapshots__/AssetResolutionCache-test.js.snap deleted file mode 100644 index 1f6708713c4415..00000000000000 --- a/packager/src/node-haste/__tests__/__snapshots__/AssetResolutionCache-test.js.snap +++ /dev/null @@ -1,18 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`AssetResolutionCache correctly clears out 1`] = ` -Array [ - "test@2x.ios.png", - "test@1x.ios.png", - "test@1.5x.ios.png", - "test@3x.ios.png", -] -`; - -exports[`AssetResolutionCache finds the correct assets 1`] = ` -Array [ - "test@2x.ios.png", - "test@1x.ios.png", - "test@1.5x.ios.png", -] -`; diff --git a/packager/src/node-haste/__tests__/__snapshots__/DependencyGraph-test.js.snap b/packager/src/node-haste/__tests__/__snapshots__/DependencyGraph-test.js.snap deleted file mode 100644 index dbd9ae1f45e0cf..00000000000000 --- a/packager/src/node-haste/__tests__/__snapshots__/DependencyGraph-test.js.snap +++ /dev/null @@ -1,36 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`DependencyGraph file watch updating should recover from multiple modules with the same name 1`] = ` -Array [ - Object { - "dependencies": Array [ - "a", - "b", - ], - "id": "index", - "isAsset": false, - "isJSON": false, - "isPolyfill": false, - "path": "/root/index.js", - "resolution": undefined, - }, - Object { - "dependencies": Array [], - "id": "a", - "isAsset": false, - "isJSON": false, - "isPolyfill": false, - "path": "/root/a.js", - "resolution": undefined, - }, - Object { - "dependencies": Array [], - "id": "b", - "isAsset": false, - "isJSON": false, - "isPolyfill": false, - "path": "/root/b.js", - "resolution": undefined, - }, -] -`; diff --git a/packager/src/node-haste/lib/AssetPaths.js b/packager/src/node-haste/lib/AssetPaths.js deleted file mode 100644 index 35083431ef5673..00000000000000 --- a/packager/src/node-haste/lib/AssetPaths.js +++ /dev/null @@ -1,80 +0,0 @@ - /** - * 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. - * - * @flow - * @format - */ - -'use strict'; - -const parsePlatformFilePath = require('./parsePlatformFilePath'); -const path = require('path'); - -export type AssetData = {| - // TODO: rename to "assetPath", what it actually is. - assetName: string, - name: string, - platform: ?string, - resolution: number, - type: string, -|}; - -const ASSET_BASE_NAME_RE = /(.+?)(@([\d.]+)x)?$/; - -function parseBaseName( - baseName: string, -): {rootName: string, resolution: number} { - const match = baseName.match(ASSET_BASE_NAME_RE); - if (!match) { - throw new Error(`invalid asset name: \`${baseName}'`); - } - const rootName = match[1]; - if (match[3] != null) { - const resolution = parseFloat(match[3]); - if (!Number.isNaN(resolution)) { - return {rootName, resolution}; - } - } - return {rootName, resolution: 1}; -} - -/** - * Return `null` if the `filePath` doesn't have a valid extension, required - * to describe the type of an asset. - */ -function tryParse( - filePath: string, - platforms: Set, -): ?AssetData { - const result = parsePlatformFilePath(filePath, platforms); - const {dirPath, baseName, platform, extension} = result; - if (extension == null) { - return null; - } - const {rootName, resolution} = parseBaseName(baseName); - return { - assetName: path.join(dirPath, `${rootName}.${extension}`), - name: rootName, - platform, - resolution, - type: extension, - }; -} - -function parse( - filePath: string, - platforms: Set, -): AssetData { - const result = tryParse(filePath, platforms); - if (result == null) { - throw new Error('invalid asset file path: \`${filePath}'); - } - return result; -} - -module.exports = {parse, tryParse}; diff --git a/packager/src/node-haste/lib/AsyncTaskGroup.js b/packager/src/node-haste/lib/AsyncTaskGroup.js deleted file mode 100644 index 8123559581e93b..00000000000000 --- a/packager/src/node-haste/lib/AsyncTaskGroup.js +++ /dev/null @@ -1,36 +0,0 @@ - /** - * Copyright (c) 2016-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. - * - * @flow - */ - -'use strict'; - -module.exports = class AsyncTaskGroup { - _runningTasks: Set; - _resolve: ?() => void; - done: Promise; - - constructor() { - this._runningTasks = new Set(); - this._resolve = null; - this.done = new Promise(resolve => this._resolve = resolve); - } - - start(taskHandle: TTaskHandle) { - this._runningTasks.add(taskHandle); - } - - end(taskHandle: TTaskHandle) { - const runningTasks = this._runningTasks; - if (runningTasks.delete(taskHandle) && runningTasks.size === 0) { - /* $FlowFixMe: could be null */ - this._resolve(); - } - } -}; diff --git a/packager/src/node-haste/lib/MapWithDefaults.js b/packager/src/node-haste/lib/MapWithDefaults.js deleted file mode 100644 index ce79e045051dd2..00000000000000 --- a/packager/src/node-haste/lib/MapWithDefaults.js +++ /dev/null @@ -1,35 +0,0 @@ -/** - * Copyright (c) 2016-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. - * - * @flow - * @format - */ - -'use strict'; - -class MapWithDefaults extends Map { - _factory: TK => TV; - - constructor(factory: TK => TV, iterable?: Iterable<[TK, TV]>) { - super(iterable); - this._factory = factory; - } - - get(key: TK): TV { - if (this.has(key)) { - /* $FlowFixMe: can never be `undefined` since we tested with `has` - * (except if `TV` includes `void` as subtype, ex. is nullable) */ - return Map.prototype.get.call(this, key); - } - const value = this._factory(key); - this.set(key, value); - return value; - } -} - -module.exports = MapWithDefaults; diff --git a/packager/src/node-haste/lib/__tests__/AssetPaths-test.js b/packager/src/node-haste/lib/__tests__/AssetPaths-test.js deleted file mode 100644 index bdf3987e8e4d35..00000000000000 --- a/packager/src/node-haste/lib/__tests__/AssetPaths-test.js +++ /dev/null @@ -1,129 +0,0 @@ -/** - * 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'; - -jest.dontMock('../parsePlatformFilePath') - .dontMock('../AssetPaths'); - -var AssetPaths = require('../AssetPaths'); - -const TEST_PLATFORMS = new Set(['ios', 'android']); - -describe('AssetPaths', () => { - it('should get data from name', () => { - expect(AssetPaths.parse('a/b/c.png', TEST_PLATFORMS)).toEqual({ - resolution: 1, - assetName: 'a/b/c.png', - type: 'png', - name: 'c', - platform: null, - }); - - expect(AssetPaths.parse('a/b/c@1x.png', TEST_PLATFORMS)).toEqual({ - resolution: 1, - assetName: 'a/b/c.png', - type: 'png', - name: 'c', - platform: null, - }); - - expect(AssetPaths.parse('a/b/c@2.5x.png', TEST_PLATFORMS)).toEqual({ - resolution: 2.5, - assetName: 'a/b/c.png', - type: 'png', - name: 'c', - platform: null, - }); - - expect(AssetPaths.parse('a/b/c.ios.png', TEST_PLATFORMS)).toEqual({ - resolution: 1, - assetName: 'a/b/c.png', - type: 'png', - name: 'c', - platform: 'ios', - }); - - expect(AssetPaths.parse('a/b/c@1x.ios.png', TEST_PLATFORMS)).toEqual({ - resolution: 1, - assetName: 'a/b/c.png', - type: 'png', - name: 'c', - platform: 'ios', - }); - - expect(AssetPaths.parse('a/b/c@2.5x.ios.png', TEST_PLATFORMS)).toEqual({ - resolution: 2.5, - assetName: 'a/b/c.png', - type: 'png', - name: 'c', - platform: 'ios', - }); - - expect(AssetPaths.parse('a/b /c.png', TEST_PLATFORMS)).toEqual({ - resolution: 1, - assetName: 'a/b /c.png', - type: 'png', - name: 'c', - platform: null, - }); - }); - - describe('resolution extraction', () => { - it('should extract resolution simple case', () => { - var data = AssetPaths.parse('test@2x.png', TEST_PLATFORMS); - expect(data).toEqual({ - assetName: 'test.png', - resolution: 2, - type: 'png', - name: 'test', - platform: null, - }); - }); - - it('should default resolution to 1', () => { - var data = AssetPaths.parse('test.png', TEST_PLATFORMS); - expect(data).toEqual({ - assetName: 'test.png', - resolution: 1, - type: 'png', - name: 'test', - platform: null, - }); - }); - - it('should support float', () => { - var data = AssetPaths.parse('test@1.1x.png', TEST_PLATFORMS); - expect(data).toEqual({ - assetName: 'test.png', - resolution: 1.1, - type: 'png', - name: 'test', - platform: null, - }); - - data = AssetPaths.parse('test@.1x.png', TEST_PLATFORMS); - expect(data).toEqual({ - assetName: 'test.png', - resolution: 0.1, - type: 'png', - name: 'test', - platform: null, - }); - - data = AssetPaths.parse('test@0.2x.png', TEST_PLATFORMS); - expect(data).toEqual({ - assetName: 'test.png', - resolution: 0.2, - type: 'png', - name: 'test', - platform: null, - }); - }); - }); -}); diff --git a/packager/src/node-haste/lib/__tests__/MapWithDefaults-test.js b/packager/src/node-haste/lib/__tests__/MapWithDefaults-test.js deleted file mode 100644 index efb122b3d7bd61..00000000000000 --- a/packager/src/node-haste/lib/__tests__/MapWithDefaults-test.js +++ /dev/null @@ -1,24 +0,0 @@ -/** - * 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. - * - * @format - */ - -'use strict'; - -jest.disableAutomock(); - -const MapWithDefaults = require('../MapWithDefaults'); - -describe('MapWithDefaults', function() { - it('works', () => { - const map = new MapWithDefaults(() => ['bar']); - map.get('foo').push('baz'); - expect(map.get('foo')).toEqual(['bar', 'baz']); - }); -}); diff --git a/packager/src/node-haste/lib/__tests__/parsePlatformFilePath-test.js b/packager/src/node-haste/lib/__tests__/parsePlatformFilePath-test.js deleted file mode 100644 index 967cdecc6a9fa4..00000000000000 --- a/packager/src/node-haste/lib/__tests__/parsePlatformFilePath-test.js +++ /dev/null @@ -1,33 +0,0 @@ -/** - * 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'; - -jest.dontMock('../parsePlatformFilePath'); - -var parsePlatformFilePath = require('../parsePlatformFilePath'); - -const TEST_PLATFORMS = new Set(['ios', 'android']); - -describe('parsePlatformFilePath', function() { - it('should get platform ext', function() { - const get = name => parsePlatformFilePath(name, TEST_PLATFORMS).platform; - expect(get('a.js')).toBe(null); - expect(get('a.ios.js')).toBe('ios'); - expect(get('a.android.js')).toBe('android'); - expect(get('/b/c/a.ios.js')).toBe('ios'); - expect(get('/b/c.android/a.ios.js')).toBe('ios'); - expect(get('/b/c/a@1.5x.ios.png')).toBe('ios'); - expect(get('/b/c/a@1.5x.lol.png')).toBe(null); - expect(get('/b/c/a.lol.png')).toBe(null); - expect(parsePlatformFilePath('a.ios.js', new Set(['ios'])).platform).toBe('ios'); - expect(parsePlatformFilePath('a.android.js', new Set(['android'])).platform).toBe('android'); - expect(parsePlatformFilePath('a.ios.js', new Set(['ubuntu'])).platform).toBe(null); - expect(parsePlatformFilePath('a.ubuntu.js', new Set(['ubuntu'])).platform).toBe('ubuntu'); - }); -}); diff --git a/packager/src/node-haste/lib/parsePlatformFilePath.js b/packager/src/node-haste/lib/parsePlatformFilePath.js deleted file mode 100644 index bd540427be5c93..00000000000000 --- a/packager/src/node-haste/lib/parsePlatformFilePath.js +++ /dev/null @@ -1,49 +0,0 @@ -/** - * 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. - * - * @flow - * @format - */ - -'use strict'; - -const path = require('path'); - -type PlatformFilePathParts = {| - dirPath: string, - baseName: string, - platform: ?string, - extension: ?string, -|}; - -const PATH_RE = /^(.+?)(\.([^.]+))?\.([^.]+)$/; - -/** - * Extract the components of a file path that can have a platform specifier: Ex. - * `index.ios.js` is specific to the `ios` platform and has the extension `js`. - */ -function parsePlatformFilePath( - filePath: string, - platforms: Set, -): PlatformFilePathParts { - const dirPath = path.dirname(filePath); - const fileName = path.basename(filePath); - const match = fileName.match(PATH_RE); - if (!match) { - return {dirPath, baseName: fileName, platform: null, extension: null}; - } - const extension = match[4] || null; - const platform = match[3] || null; - if (platform == null || platforms.has(platform)) { - return {dirPath, baseName: match[1], platform, extension}; - } - const baseName = `${match[1]}.${platform}`; - return {dirPath, baseName, platform: null, extension}; -} - -module.exports = parsePlatformFilePath; diff --git a/packager/src/node-haste/lib/toLocalPath.js b/packager/src/node-haste/lib/toLocalPath.js deleted file mode 100644 index 2b8ed3311a5839..00000000000000 --- a/packager/src/node-haste/lib/toLocalPath.js +++ /dev/null @@ -1,34 +0,0 @@ -/** - * 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. - * - * @flow - */ - -'use strict'; - -const {relative} = require('path'); - -declare class OpaqueLocalPath {} -export type LocalPath = OpaqueLocalPath & string; - -// FIXME: This function has the shortcoming of potentially returning identical -// paths for two files in different roots. -function toLocalPath(roots: $ReadOnlyArray, absolutePath: string): LocalPath { - for (let i = 0; i < roots.length; i++) { - const localPath = relative(roots[i], absolutePath); - if (localPath[0] !== '.') { - return (localPath: any); - } - } - - throw new Error( - 'Expected root module to be relative to one of the project roots' - ); -} - -module.exports = toLocalPath; diff --git a/packager/src/node-haste/types.js b/packager/src/node-haste/types.js deleted file mode 100644 index b7598ccd5d4489..00000000000000 --- a/packager/src/node-haste/types.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * Copyright (c) 2013-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. - * - * @flow - * @format - */ - -'use strict'; - -// TODO(cpojer): Create a jest-types repo. -export type HasteFS = { - exists(filePath: string): boolean, - getAllFiles(): Array, - matchFiles(pattern: RegExp | string): Array, -}; diff --git a/packager/src/rn-cli.config.js b/packager/src/rn-cli.config.js deleted file mode 100644 index 3b398c7d1fc1b8..00000000000000 --- a/packager/src/rn-cli.config.js +++ /dev/null @@ -1,50 +0,0 @@ -/** - * 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. - * - * React Native CLI configuration file - */ -'use strict'; - -const blacklist = require('./blacklist'); -const path = require('path'); - -module.exports = { - getProjectRoots() { - return this._getRoots(); - }, - - getAssetExts() { - return []; - }, - - getSourceExts() { - return []; - }, - - getBlacklistRE() { - return blacklist(); - }, - - _getRoots() { - // match on either path separator - if (__dirname.match(/node_modules[\/\\]react-native[\/\\]packager$/)) { - // packager is running from node_modules of another project - return [path.resolve(__dirname, '../../..')]; - } else if (__dirname.match(/Pods\/React\/packager$/)) { - // packager is running from node_modules of another project - return [path.resolve(__dirname, '../../..')]; - } else { - return [path.resolve(__dirname, '..')]; - } - }, - - getTransformModulePath() { - return require.resolve('./transformer'); - }, - -}; diff --git a/packager/src/setupNodePolyfills.js b/packager/src/setupNodePolyfills.js deleted file mode 100644 index ac1514309a770d..00000000000000 --- a/packager/src/setupNodePolyfills.js +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Copyright (c) 2017-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'; - -Array.prototype.values || require('core-js/fn/array/values'); -Object.entries || require('core-js/fn/object/entries'); -Object.values || require('core-js/fn/object/values'); diff --git a/packager/src/shared/output/bundle.js b/packager/src/shared/output/bundle.js deleted file mode 100644 index bf5eb73e253f5e..00000000000000 --- a/packager/src/shared/output/bundle.js +++ /dev/null @@ -1,83 +0,0 @@ -/** - * 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. - * - * @flow - */ - -'use strict'; - -const Server = require('../../Server'); - -const meta = require('./meta'); -const relativizeSourceMap = require('../../lib/relativizeSourceMap'); -const writeFile = require('./writeFile'); - -import type Bundle from '../../Bundler/Bundle'; -import type {SourceMap} from '../../lib/SourceMap'; -import type {OutputOptions, RequestOptions} from '../types.flow'; - -function buildBundle(packagerClient: Server, requestOptions: RequestOptions) { - return packagerClient.buildBundle({ - ...Server.DEFAULT_BUNDLE_OPTIONS, - ...requestOptions, - isolateModuleIDs: true, - }); -} - -function createCodeWithMap(bundle: Bundle, dev: boolean, sourceMapSourcesRoot?: string): * { - const map = bundle.getSourceMap({dev}); - const sourceMap = relativizeSourceMap( - typeof map === 'string' ? (JSON.parse(map): SourceMap) : map, - sourceMapSourcesRoot); - return { - code: bundle.getSource({dev}), - map: JSON.stringify(sourceMap), - }; -} - -function saveBundleAndMap( - bundle: Bundle, - options: OutputOptions, - log: (...args: Array) => {}, -): Promise<> { - const { - bundleOutput, - bundleEncoding: encoding, - dev, - sourcemapOutput, - sourcemapSourcesRoot, - } = options; - - log('start'); - const codeWithMap = createCodeWithMap(bundle, !!dev, sourcemapSourcesRoot); - log('finish'); - - log('Writing bundle output to:', bundleOutput); - - const {code} = codeWithMap; - const writeBundle = writeFile(bundleOutput, code, encoding); - const writeMetadata = writeFile( - bundleOutput + '.meta', - meta(code, encoding), - 'binary'); - Promise.all([writeBundle, writeMetadata]) - .then(() => log('Done writing bundle output')); - - if (sourcemapOutput) { - log('Writing sourcemap output to:', sourcemapOutput); - const writeMap = writeFile(sourcemapOutput, codeWithMap.map, null); - writeMap.then(() => log('Done writing sourcemap output')); - return Promise.all([writeBundle, writeMetadata, writeMap]); - } else { - return writeBundle; - } -} - -exports.build = buildBundle; -exports.save = saveBundleAndMap; -exports.formatName = 'bundle'; diff --git a/packager/src/shared/output/meta.js b/packager/src/shared/output/meta.js deleted file mode 100644 index 4c110ada722636..00000000000000 --- a/packager/src/shared/output/meta.js +++ /dev/null @@ -1,46 +0,0 @@ -/** - * 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. - * - * @flow - */ -'use strict'; - -/* global Buffer: true */ - -const crypto = require('crypto'); - -const isUTF8 = encoding => /^utf-?8$/i.test(encoding); - -const constantFor = encoding => - /^ascii$/i.test(encoding) ? 1 : - isUTF8(encoding) ? 2 : - /^(?:utf-?16(?:le)?|ucs-?2)$/.test(encoding) ? 3 : 0; - -module.exports = function( - code: string, - encoding: 'ascii' | 'utf8' | 'utf16le' = 'utf8', -): Buffer { - const hash = crypto.createHash('sha1'); - // remove `new Buffer` calls when RN drops support for Node 4 - hash.update(Buffer.from ? Buffer.from(code, encoding) : new Buffer(code, encoding)); - const digest = hash.digest(); - const signature = Buffer.alloc ? Buffer.alloc(digest.length + 1) : new Buffer(digest.length + 1); - digest.copy(signature); - signature.writeUInt8( - constantFor(tryAsciiPromotion(code, encoding)), - signature.length - 1); - return signature; -}; - -function tryAsciiPromotion(string, encoding) { - if (!isUTF8(encoding)) { return encoding; } - for (let i = 0, n = string.length; i < n; i++) { - if (string.charCodeAt(i) > 0x7f) { return encoding; } - } - return 'ascii'; -} diff --git a/packager/src/shared/output/unbundle/as-assets.js b/packager/src/shared/output/unbundle/as-assets.js deleted file mode 100644 index 8ca1652df933d0..00000000000000 --- a/packager/src/shared/output/unbundle/as-assets.js +++ /dev/null @@ -1,108 +0,0 @@ -/** - * 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. - * - * @flow - */ -'use strict'; - -const MAGIC_UNBUNDLE_NUMBER = require('./magic-number'); - -const buildSourceMapWithMetaData = require('./build-unbundle-sourcemap-with-metadata'); -const mkdirp = require('mkdirp'); -const path = require('path'); -const relativizeSourceMap = require('../../../lib/relativizeSourceMap'); -const writeFile = require('../writeFile'); -const writeSourceMap = require('./write-sourcemap'); - -const {joinModules} = require('./util'); - -import type Bundle from '../../../Bundler/Bundle'; -import type {OutputOptions} from '../../types.flow'; - -// must not start with a dot, as that won't go into the apk -const MAGIC_UNBUNDLE_FILENAME = 'UNBUNDLE'; -const MODULES_DIR = 'js-modules'; - -/** - * Saves all JS modules of an app as single files - * The startup code (prelude, polyfills etc.) are written to the file - * designated by the `bundleOuput` option. - * All other modules go into a 'js-modules' folder that in the same parent - * directory as the startup file. - */ -function saveAsAssets( - bundle: Bundle, - options: OutputOptions, - log: (...args: Array) => void, -): Promise { - const { - bundleOutput, - bundleEncoding: encoding, - sourcemapOutput, - sourcemapSourcesRoot, - } = options; - - log('start'); - const {startupModules, lazyModules} = bundle.getUnbundle(); - log('finish'); - const startupCode = joinModules(startupModules); - - log('Writing bundle output to:', bundleOutput); - const modulesDir = path.join(path.dirname(bundleOutput), MODULES_DIR); - const writeUnbundle = - createDir(modulesDir).then( // create the modules directory first - () => Promise.all([ - writeModules(lazyModules, modulesDir, encoding), - writeFile(bundleOutput, startupCode, encoding), - writeMagicFlagFile(modulesDir), - ]) - ); - writeUnbundle.then(() => log('Done writing unbundle output')); - - const sourceMap = - relativizeSourceMap( - buildSourceMapWithMetaData({ - fixWrapperOffset: true, - lazyModules: lazyModules.concat(), - moduleGroups: null, - startupModules: startupModules.concat(), - }), - sourcemapSourcesRoot - ); - - - return Promise.all([ - writeUnbundle, - sourcemapOutput && writeSourceMap(sourcemapOutput, JSON.stringify(sourceMap), log), - ]); -} - -function createDir(dirName) { - return new Promise((resolve, reject) => - mkdirp(dirName, error => error ? reject(error) : resolve())); -} - -function writeModuleFile(module, modulesDir, encoding) { - const {code, id} = module; - return writeFile(path.join(modulesDir, id + '.js'), code, encoding); -} - -function writeModules(modules, modulesDir, encoding) { - const writeFiles = - modules.map(module => writeModuleFile(module, modulesDir, encoding)); - return Promise.all(writeFiles); -} - -function writeMagicFlagFile(outputDir) { - /* global Buffer: true */ - const buffer = new Buffer(4); - buffer.writeUInt32LE(MAGIC_UNBUNDLE_NUMBER, 0); - return writeFile(path.join(outputDir, MAGIC_UNBUNDLE_FILENAME), buffer); -} - -module.exports = saveAsAssets; diff --git a/packager/src/shared/output/unbundle/as-indexed-file.js b/packager/src/shared/output/unbundle/as-indexed-file.js deleted file mode 100644 index d636455bc66370..00000000000000 --- a/packager/src/shared/output/unbundle/as-indexed-file.js +++ /dev/null @@ -1,216 +0,0 @@ -/** - * 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. - * - * @flow - */ -'use strict'; - -const MAGIC_UNBUNDLE_FILE_HEADER = require('./magic-number'); - -const buildSourceMapWithMetaData = require('./build-unbundle-sourcemap-with-metadata'); -const fs = require('fs'); -const relativizeSourceMap = require('../../../lib/relativizeSourceMap'); -const writeSourceMap = require('./write-sourcemap'); - -const {joinModules} = require('./util'); - -import type Bundle from '../../../Bundler/Bundle'; -import type {ModuleGroups, ModuleTransportLike, OutputOptions} from '../../types.flow'; - -const SIZEOF_UINT32 = 4; - -/** - * Saves all JS modules of an app as a single file, separated with null bytes. - * The file begins with an offset table that contains module ids and their - * lengths/offsets. - * The module id for the startup code (prelude, polyfills etc.) is the - * empty string. - */ -function saveAsIndexedFile( - bundle: Bundle, - options: OutputOptions, - log: (...args: Array) => void, -): Promise<> { - const { - bundleOutput, - bundleEncoding: encoding, - sourcemapOutput, - sourcemapSourcesRoot, - } = options; - - log('start'); - const {startupModules, lazyModules, groups} = bundle.getUnbundle(); - log('finish'); - - const moduleGroups = createModuleGroups(groups, lazyModules); - const startupCode = joinModules(startupModules); - - log('Writing unbundle output to:', bundleOutput); - const writeUnbundle = writeBuffers( - fs.createWriteStream(bundleOutput), - buildTableAndContents(startupCode, lazyModules, moduleGroups, encoding) - ).then(() => log('Done writing unbundle output')); - - const sourceMap = - relativizeSourceMap( - buildSourceMapWithMetaData({ - startupModules: startupModules.concat(), - lazyModules: lazyModules.concat(), - moduleGroups, - fixWrapperOffset: true, - }), - sourcemapSourcesRoot - ); - - return Promise.all([ - writeUnbundle, - sourcemapOutput && writeSourceMap(sourcemapOutput, JSON.stringify(sourceMap), log), - ]); -} - -/* global Buffer: true */ - -const fileHeader = new Buffer(4); -fileHeader.writeUInt32LE(MAGIC_UNBUNDLE_FILE_HEADER, 0); -const nullByteBuffer: Buffer = new Buffer(1).fill(0); - -function writeBuffers(stream, buffers: Array) { - buffers.forEach(buffer => stream.write(buffer)); - return new Promise((resolve, reject) => { - stream.on('error', reject); - stream.on('finish', () => resolve()); - stream.end(); - }); -} - -function nullTerminatedBuffer(contents, encoding) { - return Buffer.concat([new Buffer(contents, encoding), nullByteBuffer]); -} - -function moduleToBuffer(id, code, encoding) { - return { - id, - buffer: nullTerminatedBuffer(code, encoding), - }; -} - -function entryOffset(n) { - // 2: num_entries + startup_code_len - // n * 2: each entry consists of two uint32s - return (2 + n * 2) * SIZEOF_UINT32; -} - -function buildModuleTable(startupCode, moduleBuffers, moduleGroups) { - // table format: - // - num_entries: uint_32 number of entries - // - startup_code_len: uint_32 length of the startup section - // - entries: entry... - // - // entry: - // - module_offset: uint_32 offset into the modules blob - // - module_length: uint_32 length of the module code in bytes - - const moduleIds = Array.from(moduleGroups.modulesById.keys()); - const maxId = moduleIds.reduce((max, id) => Math.max(max, id)); - const numEntries = maxId + 1; - const table: Buffer = new Buffer(entryOffset(numEntries)).fill(0); - - // num_entries - table.writeUInt32LE(numEntries, 0); - - // startup_code_len - table.writeUInt32LE(startupCode.length, SIZEOF_UINT32); - - // entries - let codeOffset = startupCode.length; - moduleBuffers.forEach(({id, buffer}) => { - const group = moduleGroups.groups.get(id); - const idsInGroup = group ? [id].concat(Array.from(group)) : [id]; - - idsInGroup.forEach(moduleId => { - const offset = entryOffset(moduleId); - // module_offset - table.writeUInt32LE(codeOffset, offset); - // module_length - table.writeUInt32LE(buffer.length, offset + SIZEOF_UINT32); - }); - codeOffset += buffer.length; - }); - - return table; -} - -function groupCode(rootCode, moduleGroup, modulesById) { - if (!moduleGroup || !moduleGroup.size) { - return rootCode; - } - const code = [rootCode]; - for (const id of moduleGroup) { - code.push((modulesById.get(id) || {}).code); - } - - return code.join('\n'); -} - -function buildModuleBuffers(modules, moduleGroups, encoding) { - return modules - .filter(m => !moduleGroups.modulesInGroups.has(m.id)) - .map(({id, code}) => moduleToBuffer( - id, - groupCode( - code, - moduleGroups.groups.get(id), - moduleGroups.modulesById, - ), - encoding - )); -} - -function buildTableAndContents( - startupCode: string, - modules: $ReadOnlyArray, - moduleGroups: ModuleGroups, - encoding?: 'utf8' | 'utf16le' | 'ascii', -) { - // file contents layout: - // - magic number char[4] 0xE5 0xD1 0x0B 0xFB (0xFB0BD1E5 uint32 LE) - // - offset table table see `buildModuleTables` - // - code blob char[] null-terminated code strings, starting with - // the startup code - - const startupCodeBuffer = nullTerminatedBuffer(startupCode, encoding); - const moduleBuffers = buildModuleBuffers(modules, moduleGroups, encoding); - const table = buildModuleTable(startupCodeBuffer, moduleBuffers, moduleGroups); - - return [ - fileHeader, - table, - startupCodeBuffer, - ].concat(moduleBuffers.map(({buffer}) => buffer)); -} - -function createModuleGroups( - groups: Map>, - modules: $ReadOnlyArray, -): ModuleGroups { - return { - groups, - modulesById: new Map(modules.map(m => [m.id, m])), - modulesInGroups: new Set(concat(groups.values())), - }; -} - -function * concat(iterators) { - for (const it of iterators) { - yield * it; - } -} - -exports.save = saveAsIndexedFile; -exports.buildTableAndContents = buildTableAndContents; -exports.createModuleGroups = createModuleGroups; diff --git a/packager/src/shared/output/unbundle/build-unbundle-sourcemap-with-metadata.js b/packager/src/shared/output/unbundle/build-unbundle-sourcemap-with-metadata.js deleted file mode 100644 index 6b500db9a411ee..00000000000000 --- a/packager/src/shared/output/unbundle/build-unbundle-sourcemap-with-metadata.js +++ /dev/null @@ -1,40 +0,0 @@ -/** - * 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. - * - * @flow - */ -'use strict'; - -const {combineSourceMaps, combineSourceMapsAddingOffsets, joinModules} = require('./util'); - -import type {ModuleGroups, ModuleTransportLike} from '../../types.flow'; - -type Params = {| - fixWrapperOffset: boolean, - lazyModules: $ReadOnlyArray, - moduleGroups: ?ModuleGroups, - startupModules: $ReadOnlyArray, -|}; - -module.exports = ({fixWrapperOffset, lazyModules, moduleGroups, startupModules}: Params) => { - const options = fixWrapperOffset ? {fixWrapperOffset: true} : undefined; - const startupModule: ModuleTransportLike = { - code: joinModules(startupModules), - id: Number.MIN_SAFE_INTEGER, - map: combineSourceMaps(startupModules, undefined, options), - sourcePath: '', - }; - - const map = combineSourceMapsAddingOffsets( - [startupModule].concat(lazyModules), - moduleGroups, - options, - ); - delete map.x_facebook_offsets[Number.MIN_SAFE_INTEGER]; - return map; -}; diff --git a/packager/src/shared/output/unbundle/index.js b/packager/src/shared/output/unbundle/index.js deleted file mode 100644 index 27b8d320bd1ba5..00000000000000 --- a/packager/src/shared/output/unbundle/index.js +++ /dev/null @@ -1,46 +0,0 @@ -/** - * 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. - * - * @flow - */ - -'use strict'; - -const Server = require('../../../Server'); - -const asAssets = require('./as-assets'); -const asIndexedFile = require('./as-indexed-file').save; - -import type Bundle from '../../../Bundler/Bundle'; -import type {OutputOptions, RequestOptions} from '../../types.flow'; - -function buildBundle(packagerClient: Server, requestOptions: RequestOptions) { - return packagerClient.buildBundle({ - ...Server.DEFAULT_BUNDLE_OPTIONS, - ...requestOptions, - unbundle: true, - isolateModuleIDs: true, - }); -} - -function saveUnbundle( - bundle: Bundle, - options: OutputOptions, - log: (x: string) => void, -): Promise { - // we fork here depending on the platform: - // while android is pretty good at loading individual assets, ios has a large - // overhead when reading hundreds pf assets from disk - return options.platform === 'android' && !options.indexedUnbundle ? - asAssets(bundle, options, log) : - asIndexedFile(bundle, options, log); -} - -exports.build = buildBundle; -exports.save = saveUnbundle; -exports.formatName = 'bundle'; diff --git a/packager/src/shared/output/unbundle/magic-number.js b/packager/src/shared/output/unbundle/magic-number.js deleted file mode 100644 index 64293f8762aa52..00000000000000 --- a/packager/src/shared/output/unbundle/magic-number.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * 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. - * - * @flow - */ -'use strict'; - -module.exports = 0xFB0BD1E5; diff --git a/packager/src/shared/output/unbundle/util.js b/packager/src/shared/output/unbundle/util.js deleted file mode 100644 index cc1dd00dea0011..00000000000000 --- a/packager/src/shared/output/unbundle/util.js +++ /dev/null @@ -1,128 +0,0 @@ -/** - * Copyright (c) 2016-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. - * - * @flow - */ -'use strict'; - -const invariant = require('fbjs/lib/invariant'); - -import type {FBIndexMap, IndexMap, MappingsMap, SourceMap} from '../../../lib/SourceMap'; -import type {ModuleGroups, ModuleTransportLike} from '../../types.flow'; - -const newline = /\r\n?|\n|\u2028|\u2029/g; -// fastest implementation -const countLines = (string: string) => (string.match(newline) || []).length + 1; - - -function lineToLineSourceMap(source: string, filename: string = ''): MappingsMap { - // The first line mapping in our package is the base64vlq code for zeros (A). - const firstLine = 'AAAA;'; - - // Most other lines in our mappings are all zeros (for module, column etc) - // except for the lineno mapping: curLineno - prevLineno = 1; Which is C. - const line = 'AACA;'; - - return { - file: filename, - mappings: firstLine + Array(countLines(source)).join(line), - sources: [filename], - names: [], - version: 3, - }; -} - -const wrapperEnd = wrappedCode => wrappedCode.indexOf('{') + 1; - -const Section = - (line: number, column: number, map: SourceMap) => - ({map, offset: {line, column}}); - -type CombineOptions = {fixWrapperOffset: boolean}; - -function combineSourceMaps( - modules: $ReadOnlyArray, - moduleGroups?: ModuleGroups, - options?: ?CombineOptions, -): IndexMap { - const sections = combineMaps(modules, null, moduleGroups, options); - return {sections, version: 3}; -} - -function combineSourceMapsAddingOffsets( - modules: $ReadOnlyArray, - moduleGroups?: ?ModuleGroups, - options?: ?CombineOptions, -): FBIndexMap { - const x_facebook_offsets = []; - const sections = combineMaps(modules, x_facebook_offsets, moduleGroups, options); - return {sections, version: 3, x_facebook_offsets}; -} - -function combineMaps(modules, offsets: ?Array, moduleGroups, options) { - const sections = []; - - let line = 0; - modules.forEach(moduleTransport => { - const {code, id, name} = moduleTransport; - let column = 0; - let group; - let groupLines = 0; - let {map} = moduleTransport; - - if (moduleGroups && moduleGroups.modulesInGroups.has(id)) { - // this is a module appended to another module - return; - } - - - if (offsets != null) { - group = moduleGroups && moduleGroups.groups.get(id); - if (group && moduleGroups) { - const {modulesById} = moduleGroups; - const otherModules: $ReadOnlyArray = - Array.from(group || []) - .map(moduleId => modulesById.get(moduleId)) - .filter(Boolean); // needed to appease flow - otherModules.forEach(m => { - groupLines += countLines(m.code); - }); - map = combineSourceMaps([moduleTransport].concat(otherModules)); - } - - column = options && options.fixWrapperOffset ? wrapperEnd(code) : 0; - } - - invariant( - !Array.isArray(map), - 'Random Access Bundle source maps cannot be built from raw mappings', - ); - sections.push(Section(line, column, map || lineToLineSourceMap(code, name))); - if (offsets != null && id != null) { - offsets[id] = line; - for (const moduleId of group || []) { - offsets[moduleId] = line; - } - } - line += countLines(code) + groupLines; - }); - - return sections; -} - -const joinModules = - (modules: $ReadOnlyArray<{+code: string}>): string => - modules.map(m => m.code).join('\n'); - -module.exports = { - combineSourceMaps, - combineSourceMapsAddingOffsets, - countLines, - joinModules, - lineToLineSourceMap, -}; diff --git a/packager/src/shared/output/unbundle/write-sourcemap.js b/packager/src/shared/output/unbundle/write-sourcemap.js deleted file mode 100644 index 18c05b2af5de5a..00000000000000 --- a/packager/src/shared/output/unbundle/write-sourcemap.js +++ /dev/null @@ -1,29 +0,0 @@ -/** - * 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. - * - * @flow - */ -'use strict'; - -const writeFile = require('../writeFile'); - -function writeSourcemap( - fileName: string, - contents: string, - log: (...args: Array) => void, -): Promise<> { - if (!fileName) { - return Promise.resolve(); - } - log('Writing sourcemap output to:', fileName); - const writeMap = writeFile(fileName, contents, null); - writeMap.then(() => log('Done writing sourcemap output')); - return writeMap; -} - -module.exports = writeSourcemap; diff --git a/packager/src/shared/output/writeFile.js b/packager/src/shared/output/writeFile.js deleted file mode 100644 index 445ccef2259331..00000000000000 --- a/packager/src/shared/output/writeFile.js +++ /dev/null @@ -1,20 +0,0 @@ -/** - * 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. - * - * @flow - */ -'use strict'; - -const denodeify = require('denodeify'); -const fs = require('fs'); - -type WriteFn = - (file: string, data: string | Buffer, encoding?: ?string) => Promise; -const writeFile: WriteFn = denodeify(fs.writeFile); - -module.exports = writeFile; diff --git a/packager/src/shared/types.flow.js b/packager/src/shared/types.flow.js deleted file mode 100644 index 0e6a4caaab81e6..00000000000000 --- a/packager/src/shared/types.flow.js +++ /dev/null @@ -1,44 +0,0 @@ -/** - * 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. - * - * @flow - */ -'use strict'; -import type {SourceMapOrMappings} from '../lib/ModuleTransport'; - -export type ModuleGroups = {| - groups: Map>, - modulesById: Map, - modulesInGroups: Set, -|}; - -export type ModuleTransportLike = { - +code: string, - +id: number, - +map: ?SourceMapOrMappings, - +name?: string, - +sourcePath: string, -}; - -export type OutputOptions = { - bundleOutput: string, - bundleEncoding?: 'utf8' | 'utf16le' | 'ascii', - dev?: boolean, - platform: string, - sourcemapOutput?: string, - sourcemapSourcesRoot?: string, - sourcemapUseAbsolutePath?: boolean, -}; - -export type RequestOptions = {| - entryFile: string, - sourceMapUrl?: string, - dev?: boolean, - minify: boolean, - platform: string, -|}; diff --git a/packager/src/transformer.js b/packager/src/transformer.js deleted file mode 100644 index d70b41aa08aefc..00000000000000 --- a/packager/src/transformer.js +++ /dev/null @@ -1,169 +0,0 @@ -/** - * 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. - * - * Note: This is a fork of the fb-specific transform.js - * - * @flow - */ -'use strict'; - -const babel = require('babel-core'); -const crypto = require('crypto'); -const externalHelpersPlugin = require('babel-plugin-external-helpers'); -const fs = require('fs'); -const generate = require('babel-generator').default; -const inlineRequiresPlugin = require('babel-preset-fbjs/plugins/inline-requires'); -const json5 = require('json5'); -const makeHMRConfig = require('babel-preset-react-native/configs/hmr'); -const path = require('path'); -const resolvePlugins = require('babel-preset-react-native/lib/resolvePlugins'); - -const {compactMapping} = require('./Bundler/source-map'); - -import type {Plugins as BabelPlugins} from 'babel-core'; -import type {Transformer, TransformOptions} from './JSTransformer/worker'; - -const cacheKeyParts = [ - fs.readFileSync(__filename), - require('babel-plugin-external-helpers/package.json').version, - require('babel-preset-fbjs/package.json').version, - require('babel-preset-react-native/package.json').version, -]; - -/** - * Return a memoized function that checks for the existence of a - * project level .babelrc file, and if it doesn't exist, reads the - * default RN babelrc file and uses that. - */ -const getBabelRC = (function() { - let babelRC: ?{extends?: string, plugins: BabelPlugins} = null; - - return function _getBabelRC(projectRoot) { - if (babelRC !== null) { - return babelRC; - } - - babelRC = {plugins: []}; - - // Let's look for the .babelrc in the project root. - // In the future let's look into adding a command line option to specify - // this location. - let projectBabelRCPath; - if (projectRoot) { - projectBabelRCPath = path.resolve(projectRoot, '.babelrc'); - } - - // If a .babelrc file doesn't exist in the project, - // use the Babel config provided with react-native. - if (!projectBabelRCPath || !fs.existsSync(projectBabelRCPath)) { - babelRC = json5.parse( - fs.readFileSync( - path.resolve(__dirname, '..', 'rn-babelrc.json')) - ); - - // Require the babel-preset's listed in the default babel config - // $FlowFixMe: dynamic require can't be avoided - babelRC.presets = babelRC.presets.map(preset => require('babel-preset-' + preset)); - babelRC.plugins = resolvePlugins(babelRC.plugins); - } else { - // if we find a .babelrc file we tell babel to use it - babelRC.extends = projectBabelRCPath; - } - - return babelRC; - }; -})(); - -/** - * Given a filename and options, build a Babel - * config object with the appropriate plugins. - */ -function buildBabelConfig(filename, options) { - const babelRC = getBabelRC(options.projectRoot); - - const extraConfig = { - code: false, - filename, - }; - - let config = Object.assign({}, babelRC, extraConfig); - - // Add extra plugins - const extraPlugins = [externalHelpersPlugin]; - - var inlineRequires = options.inlineRequires; - var blacklist = typeof inlineRequires === 'object' ? inlineRequires.blacklist : null; - if (inlineRequires && !(blacklist && filename in blacklist)) { - extraPlugins.push(inlineRequiresPlugin); - } - - config.plugins = extraPlugins.concat(config.plugins); - - if (options.hot) { - const hmrConfig = makeHMRConfig(options, filename); - config = Object.assign({}, config, hmrConfig); - } - - return Object.assign({}, babelRC, config); -} - -type Params = { - filename: string, - options: TransformOptions, - plugins?: BabelPlugins, - src: string, -}; - -function transform({filename, options, src}: Params) { - options = options || {}; - - const OLD_BABEL_ENV = process.env.BABEL_ENV; - process.env.BABEL_ENV = options.dev ? 'development' : 'production'; - - try { - const babelConfig = buildBabelConfig(filename, options); - const {ast, ignored} = babel.transform(src, babelConfig); - - if (ignored) { - return { - ast: null, - code: src, - filename, - map: null, - }; - } else { - const result = generate(ast, { - comments: false, - compact: false, - filename, - sourceFileName: filename, - sourceMaps: true, - }, src); - - return { - ast, - code: result.code, - filename, - map: options.generateSourceMaps ? result.map : result.rawMappings.map(compactMapping), - }; - } - } finally { - process.env.BABEL_ENV = OLD_BABEL_ENV; - } -} - -function getCacheKey() { - var key = crypto.createHash('md5'); - cacheKeyParts.forEach(part => key.update(part)); - return key.digest('hex'); -} - -module.exports = ({ - transform, - getCacheKey, -}: Transformer<>); diff --git a/packager/src/worker-farm/.npmignore b/packager/src/worker-farm/.npmignore deleted file mode 100644 index 3c3629e647f5dd..00000000000000 --- a/packager/src/worker-farm/.npmignore +++ /dev/null @@ -1 +0,0 @@ -node_modules diff --git a/packager/src/worker-farm/.travis.yml b/packager/src/worker-farm/.travis.yml deleted file mode 100644 index 7ed0d1982e2777..00000000000000 --- a/packager/src/worker-farm/.travis.yml +++ /dev/null @@ -1,9 +0,0 @@ -language: node_js -node_js: - - "0.10" -branches: - only: - - master -notifications: - email: - - rod@vagg.org diff --git a/packager/src/worker-farm/LICENSE.md b/packager/src/worker-farm/LICENSE.md deleted file mode 100644 index 274c9b46079443..00000000000000 --- a/packager/src/worker-farm/LICENSE.md +++ /dev/null @@ -1,13 +0,0 @@ -The MIT License (MIT) -===================== - -Copyright (c) 2014 LevelUP contributors ---------------------------------------- - -*LevelUP contributors listed at * - -Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/packager/src/worker-farm/README.md b/packager/src/worker-farm/README.md deleted file mode 100644 index 086a545a891d24..00000000000000 --- a/packager/src/worker-farm/README.md +++ /dev/null @@ -1,143 +0,0 @@ -# Worker Farm - -NOTE: this was forked from npm module `worker-farm`. Below is the original documentation, that may not be up-to-date. - - ---- - -Distribute processing tasks to child processes with an ΓΌber-simple API and baked-in durability & custom concurrency options. *Available in npm as worker-farm*. - -## Example - -Given a file, *child.js*: - -```js -module.exports = function (inp, callback) { - callback(null, inp + ' BAR (' + process.pid + ')') -} -``` - -And a main file: - -```js -var workerFarm = require('worker-farm') - , workers = workerFarm(require.resolve('./child')) - , ret = 0 - -for (var i = 0; i < 10; i++) { - workers('#' + i + ' FOO', function (err, outp) { - console.log(outp) - if (++ret == 10) - workerFarm.end(workers) - }) -} -``` - -We'll get an output something like the following: - -``` -#1 FOO BAR (8546) -#0 FOO BAR (8545) -#8 FOO BAR (8545) -#9 FOO BAR (8546) -#2 FOO BAR (8548) -#4 FOO BAR (8551) -#3 FOO BAR (8549) -#6 FOO BAR (8555) -#5 FOO BAR (8553) -#7 FOO BAR (8557) -``` - -This example is contained in the *[examples/basic](https://github.com/rvagg/node-worker-farm/tree/master/examples/basic/)* directory. - -### Example #1: Estimating Ο€ using child workers - -You will also find a more complex example in *[examples/pi](https://github.com/rvagg/node-worker-farm/tree/master/examples/pi/)* that estimates the value of **Ο€** by using a Monte Carlo *area-under-the-curve* method and compares the speed of doing it all in-process vs using child workers to complete separate portions. - -Running `node examples/pi` will give you something like: - -``` -Doing it the slow (single-process) way... -Ο€ β‰ˆ 3.1416269360000006 (0.0000342824102075312 away from actual!) -took 8341 milliseconds -Doing it the fast (multi-process) way... -Ο€ β‰ˆ 3.1416233600000036 (0.00003070641021052367 away from actual!) -took 1985 milliseconds -``` - -## Durability - -An important feature of Worker Farm is **call durability**. If a child process dies for any reason during the execution of call(s), those calls will be re-queued and taken care of by other child processes. In this way, when you ask for something to be done, unless there is something *seriously* wrong with what you're doing, you should get a result on your callback function. - -## My use-case - -There are other libraries for managing worker processes available but my use-case was fairly specific: I need to make heavy use of the [node-java](https://github.com/nearinfinity/node-java) library to interact with JVM code. Unfortunately, because the JVM garbage collector is so difficult to interact with, it's prone to killing your Node process when the GC kicks under heavy load. For safety I needed a durable way to make calls so that (1) it wouldn't kill my main process and (2) any calls that weren't successful would be resubmitted for processing. - -Worker Farm allows me to spin up multiple JVMs to be controlled by Node, and have a single, uncomplicated API that acts the same way as an in-process API and the calls will be taken care of by a child process even if an error kills a child process while it is working as the call will simply be passed to a new child process. - -**But**, don't think that Worker Farm is specific to that use-case, it's designed to be very generic and simple to adapt to anything requiring the use of child Node processes. - -## API - -Worker Farm exports a main function an an `end()` method. The main function sets up a "farm" of coordinated child-process workers and it can be used to instantiate multiple farms, all operating independently. - -### workerFarm([options, ]pathToModule[, exportedMethods]) - -In its most basic form, you call `workerFarm()` with the path to a module file to be invoked by the child process. You should use an **absolute path** to the module file, the best way to obtain the path is with `require.resolve('./path/to/module')`, this function can be used in exactly the same way as `require('./path/to/module')` but it returns an absolute path. - -#### `exportedMethods` - -If your module exports a single function on `module.exports` then you should omit the final parameter. However, if you are exporting multiple functions on `module.exports` then you should list them in an Array of Strings: - -```js -var workers = workerFarm(require.resolve('./mod'), [ 'doSomething', 'doSomethingElse' ]) -workers.doSomething(function () {}) -workers.doSomethingElse(function () {}) -``` - -Listing the available methods will instruct Worker Farm what API to provide you with on the returned object. If you don't list a `exportedMethods` Array then you'll get a single callable function to use; but if you list the available methods then you'll get an object with callable functions by those names. - -**It is assumed that each function you call on your child module will take a `callback` function as the last argument.** - -#### `options` - -If you don't provide an `options` object then the following defaults will be used: - -```js -{ - maxCallsPerWorker : Infinity - , maxConcurrentWorkers : require('os').cpus().length - , maxConcurrentCallsPerWorker : 10 - , maxConcurrentCalls : Infinity - , maxCallTime : Infinity - , maxRetries : Infinity - , autoStart : false -} -``` - - * **maxCallsPerWorker** allows you to control the lifespan of your child processes. A positive number will indicate that you only want each child to accept that many calls before it is terminated. This may be useful if you need to control memory leaks or similar in child processes. - - * **maxConcurrentWorkers** will set the number of child processes to maintain concurrently. By default it is set to the number of CPUs available on the current system, but it can be any reasonable number, including `1`. - - * **maxConcurrentCallsPerWorker** allows you to control the *concurrency* of individual child processes. Calls are placed into a queue and farmed out to child processes according to the number of calls they are allowed to handle concurrently. It is arbitrarily set to 10 by default so that calls are shared relatively evenly across workers, however if your calls predictably take a similar amount of time then you could set it to `Infinity` and Worker Farm won't queue any calls but spread them evenly across child processes and let them go at it. If your calls aren't I/O bound then it won't matter what value you use here as the individual workers won't be able to execute more than a single call at a time. - - * **maxConcurrentCalls** allows you to control the maximum number of calls in the queue—either actively being processed or waiting for a worker to be processed. `Infinity` indicates no limit but if you have conditions that may endlessly queue jobs and you need to set a limit then provide a `>0` value and any calls that push the limit will return on their callback with a `MaxConcurrentCallsError` error (check `err.type == 'MaxConcurrentCallsError'`). - - * **maxCallTime** *(use with caution, understand what this does before you use it!)* when `!== Infinity`, will cap a time, in milliseconds, that *any single call* can take to execute in a worker. If this time limit is exceeded by just a single call then the worker running that call will be killed and any calls running on that worker will have their callbacks returned with a `TimeoutError` (check `err.type == 'TimeoutError'`). If you are running with `maxConcurrentCallsPerWorker` value greater than `1` then **all calls currently executing** will fail and will be automatically resubmitted uless you've changed the `maxRetries` option. Use this if you have jobs that may potentially end in infinite loops that you can't programatically end with your child code. Preferably run this with a `maxConcurrentCallsPerWorker` so you don't interrupt other calls when you have a timeout. This timeout operates on a per-call basis but will interrupt a whole worker. - - * **maxRetries** allows you to control the max number of call requeues after worker termination (unexpected or timeout). By default this option is set to `Infinity` which means that each call of each terminated worker will always be auto requeued. When the number of retries exceeds `maxRetries` value, the job callback will be executed with a `ProcessTerminatedError`. Note that if you are running with finite `maxCallTime` and `maxConcurrentCallsPerWorkers` greater than `1` then any `TimeoutError` will increase the retries counter *for each* concurrent call of the terminated worker. - - * **autoStart** when set to `true` will start the workers as early as possible. Use this when your workers have to do expensive initialization. That way they'll be ready when the first request comes through. - -### workerFarm.end(farm) - -Child processes stay alive waiting for jobs indefinitely and your farm manager will stay alive managing its workers, so if you need it to stop then you have to do so explicitly. If you send your farm API to `workerFarm.end()` then it'll cleanly end your worker processes. Note though that it's a *soft* ending so it'll wait for child processes to finish what they are working on before asking them to die. - -Any calls that are queued and not yet being handled by a child process will be discarded. `end()` only waits for those currently in progress. - -Once you end a farm, it won't handle any more calls, so don't even try! - - -## License - -Worker Farm is Copyright (c) 2014 Rod Vagg [@rvagg](https://twitter.com/rvagg) and licensed under the MIT license. All rights not explicitly granted in the MIT license are reserved. See the included LICENSE.md file for more details. diff --git a/packager/src/worker-farm/examples/basic/child.js b/packager/src/worker-farm/examples/basic/child.js deleted file mode 100644 index a5eda7b5402d8c..00000000000000 --- a/packager/src/worker-farm/examples/basic/child.js +++ /dev/null @@ -1,13 +0,0 @@ -/** - * Copyright (c) 2013-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. - */ - -/* eslint-disable */ -module.exports = function (inp, callback) { - callback(null, inp + ' BAR (' + process.pid + ')') -} diff --git a/packager/src/worker-farm/examples/basic/index.js b/packager/src/worker-farm/examples/basic/index.js deleted file mode 100644 index 0e6d446da72a28..00000000000000 --- a/packager/src/worker-farm/examples/basic/index.js +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Copyright (c) 2013-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. - */ - -/* eslint-disable */ -var workerFarm = require('../../') - , workers = workerFarm(require.resolve('./child')) - , ret = 0 - -for (var i = 0; i < 10; i++) { - workers('#' + i + ' FOO', function (err, outp) { - console.log(outp) - if (++ret == 10) - workerFarm.end(workers) - }) -} diff --git a/packager/src/worker-farm/examples/pi/calc.js b/packager/src/worker-farm/examples/pi/calc.js deleted file mode 100644 index 6596442de94a2b..00000000000000 --- a/packager/src/worker-farm/examples/pi/calc.js +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright (c) 2013-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. - */ - -/* eslint-disable */ -/* A simple PI estimation function using a Monte Carlo method - * For 0 to `points`, take 2 random numbers < 1, square and add them to - * find the area under that point in a 1x1 square. If that area is <= 1 - * then it's *within* a quarter-circle, otherwise it's outside. - * Take the number of points <= 1 and multiply it by 4 and you have an - * estimate! - * Do this across multiple processes and average the results to - * increase accuracy. - */ - -module.exports = function (points, callback) { - var inside = 0 - , i = points - - while (i--) - if (Math.pow(Math.random(), 2) + Math.pow(Math.random(), 2) <= 1) - inside++ - - callback(null, (inside / points) * 4) -} diff --git a/packager/src/worker-farm/examples/pi/index.js b/packager/src/worker-farm/examples/pi/index.js deleted file mode 100644 index 6cdfe55335a0d4..00000000000000 --- a/packager/src/worker-farm/examples/pi/index.js +++ /dev/null @@ -1,49 +0,0 @@ -/** - * Copyright (c) 2013-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. - */ - -/* eslint-disable */ -const CHILDREN = 500 - , POINTS_PER_CHILD = 1000000 - , FARM_OPTIONS = { - maxConcurrentWorkers : require('os').cpus().length - , maxCallsPerWorker : Infinity - , maxConcurrentCallsPerWorker : 1 - } - -var workerFarm = require('../../') - , calcDirect = require('./calc') - , calcWorker = workerFarm(FARM_OPTIONS, require.resolve('./calc')) - - , ret - , start - - , tally = function (finish, err, avg) { - ret.push(avg) - if (ret.length == CHILDREN) { - var pi = ret.reduce(function (a, b) { return a + b }) / ret.length - , end = +new Date() - console.log('PI ~=', pi, '\t(' + Math.abs(pi - Math.PI), 'away from actual!)') - console.log('took', end - start, 'milliseconds') - if (finish) - finish() - } - } - - , calc = function (method, callback) { - ret = [] - start = +new Date() - for (var i = 0; i < CHILDREN; i++) - method(POINTS_PER_CHILD, tally.bind(null, callback)) - } - -console.log('Doing it the slow (single-process) way...') -calc(calcDirect, function () { - console.log('Doing it the fast (multi-process) way...') - calc(calcWorker, process.exit) -}) diff --git a/packager/src/worker-farm/lib/child/index.js b/packager/src/worker-farm/lib/child/index.js deleted file mode 100644 index a20dcc2ba4cf33..00000000000000 --- a/packager/src/worker-farm/lib/child/index.js +++ /dev/null @@ -1,58 +0,0 @@ -/** - * Copyright (c) 2013-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. - */ - -/* eslint-disable */ -var $module - -/* - var contextProto = this.context; - while (contextProto = Object.getPrototypeOf(contextProto)) { - completionGroups.push(Object.getOwnPropertyNames(contextProto)); - } -*/ - -function handle (data) { - var idx = data.idx - , child = data.child - , method = data.method - , args = data.args - , callback = function () { - var _args = Array.prototype.slice.call(arguments) - if (_args[0] instanceof Error) { - var e = _args[0] - _args[0] = { - '$error' : '$error' - , 'type' : e.constructor.name - , 'message' : e.message - , 'stack' : e.stack - } - Object.keys(e).forEach(function(key) { - _args[0][key] = e[key] - }) - } - process.send({ idx: idx, child: child, args: _args }) - } - , exec - - if (method == null && typeof $module == 'function') - exec = $module - else if (typeof $module[method] == 'function') - exec = $module[method] - - if (!exec) - return console.error('NO SUCH METHOD:', method) - - exec.apply(null, args.concat([ callback ])) -} - -process.on('message', function (data) { - if (!$module) return $module = require(data.module) - if (data == 'die') return process.exit(0) - handle(data) -}) diff --git a/packager/src/worker-farm/lib/farm.js b/packager/src/worker-farm/lib/farm.js deleted file mode 100644 index 14c3d12c3a5417..00000000000000 --- a/packager/src/worker-farm/lib/farm.js +++ /dev/null @@ -1,342 +0,0 @@ -/** - * Copyright (c) 2013-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. - * - * @flow - */ - -/* eslint-disable */ -const DEFAULT_OPTIONS = { - maxCallsPerWorker : Infinity - , maxConcurrentWorkers : require('os').cpus().length - , maxConcurrentCallsPerWorker : 10 - , maxConcurrentCalls : Infinity - , maxCallTime : Infinity // exceed this and the whole worker is terminated - , maxRetries : Infinity - , forcedKillTime : 100 - , autoStart : false - } - -const extend = require('xtend') - , fork = require('./fork') - , TimeoutError = require('errno').create('TimeoutError') - , ProcessTerminatedError = require('errno').create('ProcessTerminatedError') - , MaxConcurrentCallsError = require('errno').create('MaxConcurrentCallsError') - -const mergeStream = require('merge-stream'); - -function Farm (options: {+execArgv: Array}, path: string) { - this.options = extend(DEFAULT_OPTIONS, options) - this.path = path - this.activeCalls = 0 - this.stdout = mergeStream(); - this.stderr = mergeStream(); -} - -// make a handle to pass back in the form of an external API -Farm.prototype.mkhandle = function (method) { - return function () { - var args = Array.prototype.slice.call(arguments) - if (this.activeCalls >= this.options.maxConcurrentCalls) { - var err = new MaxConcurrentCallsError('Too many concurrent calls (' + this.activeCalls + ')') - if (typeof args[args.length - 1] == 'function') - return process.nextTick(args[args.length - 1].bind(null, err)) - throw err - } - this.addCall({ - method : method - , callback : args.pop() - , args : args - , retries : 0 - }) - }.bind(this) -} - -// a constructor of sorts -Farm.prototype.setup = function (methods) { - var iface - if (!methods) { // single-function export - iface = this.mkhandle() - } else { // multiple functions on the export - iface = {} - methods.forEach(function (m) { - iface[m] = this.mkhandle(m) - }.bind(this)) - } - - this.searchStart = -1 - this.childId = -1 - this.children = {} - this.activeChildren = 0 - this.callQueue = [] - - if (this.options.autoStart) { - while (this.activeChildren < this.options.maxConcurrentWorkers) - this.startChild() - } - - return iface -} - -// when a child exits, check if there are any outstanding jobs and requeue them -Farm.prototype.onExit = function (childId) { - // delay this to give any sends a chance to finish - setTimeout(function () { - var doQueue = false - if (this.children[childId] && this.children[childId].activeCalls) { - this.children[childId].calls.forEach(function (call, i) { - if (!call) return - else if (call.retries >= this.options.maxRetries) { - this.receive({ - idx : i - , child : childId - , args : [ new ProcessTerminatedError('cancel after ' + call.retries + ' retries!') ] - }) - } else { - call.retries++ - this.callQueue.unshift(call) - doQueue = true - } - }.bind(this)) - } - this.stopChild(childId) - doQueue && this.processQueue() - }.bind(this), 10) -} - -// start a new worker -Farm.prototype.startChild = function () { - this.childId++ - - var forked = fork(this.path, {execArgv: this.options.execArgv}) - , id = this.childId - , c = { - send : forked.send - , child : forked.child - , calls : [] - , activeCalls : 0 - , exitCode : null - } - - this.stdout.add(forked.child.stdout); - this.stderr.add(forked.child.stderr); - - forked.child.on('message', this.receive.bind(this)) - forked.child.once('exit', function (code) { - c.exitCode = code - this.onExit(id) - }.bind(this)) - - this.activeChildren++ - this.children[id] = c -} - -// stop a worker, identified by id -Farm.prototype.stopChild = function (childId) { - var child = this.children[childId] - if (child) { - child.send('die') - setTimeout(function () { - if (child.exitCode === null) - child.child.kill('SIGKILL') - }, this.options.forcedKillTime) - ;delete this.children[childId] - this.activeChildren-- - } -} - -// called from a child process, the data contains information needed to -// look up the child and the original call so we can invoke the callback -Farm.prototype.receive = function (data) { - var idx = data.idx - , childId = data.child - , args = data.args - , child = this.children[childId] - , call - - if (!child) { - return console.error( - 'Worker Farm: Received message for unknown child. ' - + 'This is likely as a result of premature child death, ' - + 'the operation will have been re-queued.' - ) - } - - call = child.calls[idx] - if (!call) { - return console.error( - 'Worker Farm: Received message for unknown index for existing child. ' - + 'This should not happen!' - ) - } - - if (this.options.maxCallTime !== Infinity) - clearTimeout(call.timer) - - if (args[0] && args[0].$error == '$error') { - var e = args[0] - switch (e.type) { - case 'TypeError': args[0] = new TypeError(e.message); break - case 'RangeError': args[0] = new RangeError(e.message); break - case 'EvalError': args[0] = new EvalError(e.message); break - case 'ReferenceError': args[0] = new ReferenceError(e.message); break - case 'SyntaxError': args[0] = new SyntaxError(e.message); break - case 'URIError': args[0] = new URIError(e.message); break - default: args[0] = new Error(e.message) - } - args[0].type = e.type - args[0].stack = e.stack - - // Copy any custom properties to pass it on. - Object.keys(e).forEach(function(key) { - args[0][key] = e[key]; - }); - } - - process.nextTick(function () { - call.callback.apply(null, args) - }) - - ;delete child.calls[idx] - child.activeCalls-- - this.activeCalls-- - - if (child.calls.length >= this.options.maxCallsPerWorker - && !Object.keys(child.calls).length) { - // this child has finished its run, kill it - this.stopChild(childId) - } - - // allow any outstanding calls to be processed - this.processQueue() -} - -Farm.prototype.childTimeout = function (childId) { - var child = this.children[childId] - , i - - if (!child) - return - - for (i in child.calls) { - this.receive({ - idx : i - , child : childId - , args : [ new TimeoutError('worker call timed out!') ] - }) - } - this.stopChild(childId) -} - -// send a call to a worker, identified by id -Farm.prototype.send = function (childId, call) { - var child = this.children[childId] - , idx = child.calls.length - - child.calls.push(call) - child.activeCalls++ - this.activeCalls++ - - child.send({ - idx : idx - , child : childId - , method : call.method - , args : call.args - }) - - if (this.options.maxCallTime !== Infinity) { - call.timer = - setTimeout(this.childTimeout.bind(this, childId), this.options.maxCallTime) - } -} - -// a list of active worker ids, in order, but the starting offset is -// shifted each time this method is called, so we work our way through -// all workers when handing out jobs -Farm.prototype.childKeys = function () { - var cka = Object.keys(this.children) - , cks - - if (this.searchStart >= cka.length - 1) - this.searchStart = 0 - else - this.searchStart++ - - cks = cka.splice(0, this.searchStart) - - return cka.concat(cks) -} - -// Calls are added to a queue, this processes the queue and is called -// whenever there might be a chance to send more calls to the workers. -// The various options all impact on when we're able to send calls, -// they may need to be kept in a queue until a worker is ready. -Farm.prototype.processQueue = function () { - var cka, i = 0, childId - - if (!this.callQueue.length) - return this.ending && this.end() - - if (this.activeChildren < this.options.maxConcurrentWorkers) - this.startChild() - - for (cka = this.childKeys(); i < cka.length; i++) { - childId = +cka[i] - if (this.children[childId].activeCalls < this.options.maxConcurrentCallsPerWorker - && this.children[childId].calls.length < this.options.maxCallsPerWorker) { - - this.send(childId, this.callQueue.shift()) - if (!this.callQueue.length) - return this.ending && this.end() - } /*else { - console.log( - , this.children[childId].activeCalls < this.options.maxConcurrentCallsPerWorker - , this.children[childId].calls.length < this.options.maxCallsPerWorker - , this.children[childId].calls.length , this.options.maxCallsPerWorker) - }*/ - } - - if (this.ending) - this.end() -} - -// add a new call to the call queue, then trigger a process of the queue -Farm.prototype.addCall = function (call) { - if (this.ending) - return this.end() // don't add anything new to the queue - this.callQueue.push(call) - this.processQueue() -} - -// kills child workers when they're all done -Farm.prototype.end = function (callback) { - var complete = true - if (this.ending === false) - return - if (callback) - this.ending = callback - else if (this.ending == null) - this.ending = true - Object.keys(this.children).forEach(function (child) { - if (!this.children[child]) - return - if (!this.children[child].activeCalls) - this.stopChild(child) - else - complete = false - }.bind(this)) - - if (complete && typeof this.ending == 'function') { - process.nextTick(function () { - this.ending() - this.ending = false - }.bind(this)) - } -} - -module.exports = Farm -module.exports.TimeoutError = TimeoutError diff --git a/packager/src/worker-farm/lib/fork.js b/packager/src/worker-farm/lib/fork.js deleted file mode 100644 index f3ea06c3e39e7f..00000000000000 --- a/packager/src/worker-farm/lib/fork.js +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright (c) 2013-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. - * - * @flow - */ - -'use strict'; - -const childProcess = require('child_process'); -const childModule = require.resolve('./child/index'); - -function fork(forkModule: string, options: {|+execArgv: Array|}) { - const child = childProcess.fork(childModule, { - cwd: process.cwd(), - env: process.env, - execArgv: options.execArgv, - silent: true, - }); - - child.send({module: forkModule}); - - // return a send() function for this child - return { - send(data: {}) { - try { - child.send(data); - } catch (e) { - // this *should* be picked up by onExit and the operation requeued - } - }, - child, - }; -} - -module.exports = fork; diff --git a/packager/src/worker-farm/lib/index.js b/packager/src/worker-farm/lib/index.js deleted file mode 100644 index 8857b6abfa73dd..00000000000000 --- a/packager/src/worker-farm/lib/index.js +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Copyright (c) 2013-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. - * - * @flow - */ - -/* eslint-disable */ -const Farm = require('./farm') - -import type {Readable} from 'stream'; - -var farms = [] // keep record of farms so we can end() them if required - -export type FarmAPI = {| - methods: {[name: string]: Function}, - stdout: Readable, - stderr: Readable, -|}; - -function farm( - options: {+execArgv: Array}, - path: string, - methods: Array, -): FarmAPI { - var f = new Farm(options, path) - , api = f.setup(methods) - - farms.push({ farm: f, api: api }) - - // $FlowFixMe: gotta type the Farm class. - const {stdout, stderr} = f; - - // return the public API - return {methods: (api: any), stdout, stderr}; -} - -function end (api, callback) { - for (var i = 0; i < farms.length; i++) - if (farms[i] && farms[i].api === api) - return farms[i].farm.end(callback) - process.nextTick(callback.bind(null, 'Worker farm not found!')) -} - -module.exports = farm -module.exports.end = end diff --git a/packager/src/worker-farm/package.json b/packager/src/worker-farm/package.json deleted file mode 100644 index 17d82a4507cd2e..00000000000000 --- a/packager/src/worker-farm/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "private": true, - "authors": [ - "Rod Vagg @rvagg (https://github.com/rvagg)" - ], - "main": "./lib/index.js", - "dependencies": { - "errno": ">=0.1.1 <0.2.0-0", - "xtend": ">=4.0.0 <4.1.0-0" - }, - "devDependencies": { - "tape": ">=3.0.3 <3.1.0-0" - }, - "scripts": { - "test": "node ./tests/" - }, - "license": "MIT" -} diff --git a/packager/src/worker-farm/tests/child.js b/packager/src/worker-farm/tests/child.js deleted file mode 100644 index 4cc96195287cf2..00000000000000 --- a/packager/src/worker-farm/tests/child.js +++ /dev/null @@ -1,79 +0,0 @@ -/** - * Copyright (c) 2013-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. - */ - -/* eslint-disable */ -var fs = require('fs') - -module.exports = function (timeout, callback) { - callback = callback.bind(null, null, process.pid, Math.random(), timeout) - if (timeout) - return setTimeout(callback, timeout) - callback() -} - -module.exports.run0 = function (callback) { - module.exports(0, callback) -} - -module.exports.killable = function (id, callback) { - if (Math.random() < 0.5) - return process.exit(-1) - callback(null, id, process.pid) -} - -module.exports.err = function (type, message, data, callback) { - if (typeof data == 'function') { - callback = data - data = null - } else { - var err = new Error(message) - Object.keys(data).forEach(function(key) { - err[key] = data[key] - }) - callback(err) - return - } - - if (type == 'TypeError') - return callback(new TypeError(message)) - callback(new Error(message)) -} - -module.exports.block = function () { - while (true); -} - -// use provided file path to save retries count among terminated workers -module.exports.stubborn = function (path, callback) { - function isOutdated(path) { - return ((new Date).getTime() - fs.statSync(path).mtime.getTime()) > 2000 - } - - // file may not be properly deleted, check if modified no earler than two seconds ago - if (!fs.existsSync(path) || isOutdated(path)) { - fs.writeFileSync(path, '1') - process.exit(-1) - } - - var retry = parseInt(fs.readFileSync(path, 'utf8')) - if (Number.isNaN(retry)) - return callback(new Error('file contents is not a number')) - - if (retry > 4) { - callback(null, 12) - } else { - fs.writeFileSync(path, String(retry + 1)) - process.exit(-1) - } -} - -var started = Date.now() -module.exports.uptime = function(callback) { - callback(null, Date.now() - started) -} diff --git a/packager/src/worker-farm/tests/index.js b/packager/src/worker-farm/tests/index.js deleted file mode 100644 index b34dcb7c8a2c77..00000000000000 --- a/packager/src/worker-farm/tests/index.js +++ /dev/null @@ -1,466 +0,0 @@ -/** - * Copyright (c) 2013-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. - */ - -/* eslint-disable */ -var tape = require('tape') - , workerFarm = require('../') - , childPath = require.resolve('./child') - , fs = require('fs') - - , uniq = function (ar) { - var a = [], i, j - o: for (i = 0; i < ar.length; ++i) { - for (j = 0; j < a.length; ++j) if (a[j] == ar[i]) continue o - a[a.length] = ar[i] - } - return a - } - -// a child where module.exports = function ... -tape('simple, exports=function test', function (t) { - t.plan(4) - - var child = workerFarm(childPath) - child(0, function (err, pid, rnd) { - t.ok(pid > process.pid, 'pid makes sense') - t.ok(pid < process.pid + 500, 'pid makes sense') - t.ok(rnd >= 0 && rnd < 1, 'rnd result makes sense') - }) - - workerFarm.end(child, function () { - t.ok(true, 'workerFarm ended') - }) -}) - -// a child where we have module.exports.fn = function ... -tape('simple, exports.fn test', function (t) { - t.plan(4) - - var child = workerFarm(childPath, [ 'run0' ]) - child.run0(function (err, pid, rnd) { - t.ok(pid > process.pid, 'pid makes sense') - t.ok(pid < process.pid + 500, 'pid makes sense') - t.ok(rnd >= 0 && rnd < 1, 'rnd result makes sense') - }) - - workerFarm.end(child, function () { - t.ok(true, 'workerFarm ended') - }) -}) - -// use the returned pids to check that we're using a single child process -// when maxConcurrentWorkers = 1 -tape('single worker', function (t) { - t.plan(2) - - var child = workerFarm({ maxConcurrentWorkers: 1 }, childPath) - - - , pids = [] - , i = 10 - - while (i--) { - child(0, function (err, pid) { - pids.push(pid) - if (pids.length == 10) { - t.equal(1, uniq(pids).length, 'only a single process (by pid)') - } else if (pids.length > 10) - t.fail('too many callbacks!') - }) - } - - workerFarm.end(child, function () { - t.ok(true, 'workerFarm ended') - }) -}) - -// use the returned pids to check that we're using two child processes -// when maxConcurrentWorkers = 2 -tape('two workers', function (t) { - t.plan(2) - - var child = workerFarm({ maxConcurrentWorkers: 2 }, childPath) - , pids = [] - , i = 10 - - while (i--) { - child(0, function (err, pid) { - pids.push(pid) - if (pids.length == 10) { - t.equal(2, uniq(pids).length, 'only two child processes (by pid)') - } else if (pids.length > 10) - t.fail('too many callbacks!') - }) - } - - workerFarm.end(child, function () { - t.ok(true, 'workerFarm ended') - }) -}) - -// use the returned pids to check that we're using a child process per -// call when maxConcurrentWorkers = 10 -tape('many workers', function (t) { - t.plan(2) - - var child = workerFarm({ maxConcurrentWorkers: 10 }, childPath) - , pids = [] - , i = 10 - - while (i--) { - child(1, function (err, pid) { - pids.push(pid) - if (pids.length == 10) { - t.equal(10, uniq(pids).length, 'pids are all the same (by pid)') - } else if (pids.length > 10) - t.fail('too many callbacks!') - }) - } - - workerFarm.end(child, function () { - t.ok(true, 'workerFarm ended') - }) -}) - -tape('auto start workers', function (t) { - t.plan(4) - - var child = workerFarm({ maxConcurrentWorkers: 3, autoStart: true }, childPath, ['uptime']) - , pids = [] - , i = 3 - , delay = 150 - - setTimeout(function() { - while (i--) - child.uptime(function (err, uptime) { - t.ok(uptime > 10, 'child has been up before the request') - }) - - workerFarm.end(child, function () { - t.ok(true, 'workerFarm ended') - }) - }, delay) -}) - -// use the returned pids to check that we're using a child process per -// call when we set maxCallsPerWorker = 1 even when we have maxConcurrentWorkers = 1 -tape('single call per worker', function (t) { - t.plan(2) - - var child = workerFarm({ maxConcurrentWorkers: 1, maxCallsPerWorker: 1 }, childPath) - , pids = [] - , i = 10 - - while (i--) { - child(0, function (err, pid) { - pids.push(pid) - if (pids.length == 10) { - t.equal(10, uniq(pids).length, 'one process for each call (by pid)') - workerFarm.end(child, function () { - t.ok(true, 'workerFarm ended') - }) - } else if (pids.length > 10) - t.fail('too many callbacks!') - }) - } -}) - -// use the returned pids to check that we're using a child process per -// two-calls when we set maxCallsPerWorker = 2 even when we have maxConcurrentWorkers = 1 -tape('two calls per worker', function (t) { - t.plan(2) - - var child = workerFarm({ maxConcurrentWorkers: 1, maxCallsPerWorker: 2 }, childPath) - , pids = [] - , i = 10 - - while (i--) { - child(0, function (err, pid) { - pids.push(pid) - if (pids.length == 10) { - t.equal(5, uniq(pids).length, 'one process for each call (by pid)') - workerFarm.end(child, function () { - t.ok(true, 'workerFarm ended') - }) - } else if (pids.length > 10) - t.fail('too many callbacks!') - }) - } -}) - -// use timing to confirm that one worker will process calls sequentially -tape('many concurrent calls', function (t) { - t.plan(2) - - var child = workerFarm({ maxConcurrentWorkers: 1 }, childPath) - , i = 10 - , cbc = 0 - , start = Date.now() - - while (i--) { - child(100, function () { - if (++cbc == 10) { - var time = Date.now() - start - t.ok(time > 100 && time < 200, 'processed tasks concurrently (' + time + 'ms)') - workerFarm.end(child, function () { - t.ok(true, 'workerFarm ended') - }) - } else if (cbc > 10) - t.fail('too many callbacks!') - }) - } -}) - -// use timing to confirm that one child processes calls sequentially with -// maxConcurrentCallsPerWorker = 1 -tape('single concurrent call', function (t) { - t.plan(2) - - var child = workerFarm( - { maxConcurrentWorkers: 1, maxConcurrentCallsPerWorker: 1 } - , childPath - ) - , i = 10 - , cbc = 0 - , start = Date.now() - - while (i--) { - child(10, function () { - if (++cbc == 10) { - var time = Date.now() - start - t.ok(time > 100 && time < 190, 'processed tasks sequentially (' + time + 'ms)') - workerFarm.end(child, function () { - t.ok(true, 'workerFarm ended') - }) - } else if (cbc > 10) - t.fail('too many callbacks!') - }) - } -}) - -// use timing to confirm that one child processes *only* 5 calls concurrently -tape('multiple concurrent calls', function (t) { - t.plan(2) - - var child = workerFarm({ maxConcurrentWorkers: 1, maxConcurrentCallsPerWorker: 5 }, childPath) - , i = 10 - , cbc = 0 - , start = Date.now() - - while (i--) { - child(50, function () { - if (++cbc == 10) { - var time = Date.now() - start - t.ok(time > 100 && time < 200, 'processed tasks concurrently (' + time + 'ms)') - workerFarm.end(child, function () { - t.ok(true, 'workerFarm ended') - }) - } else if (cbc > 10) - t.fail('too many callbacks!') - }) - } -}) - -// call a method that will die with a probability of 0.5 but expect that -// we'll get results for each of our calls anyway -tape('durability', function (t) { - t.plan(3) - - var child = workerFarm({ maxConcurrentWorkers: 2 }, childPath, [ 'killable' ]) - , ids = [] - , pids = [] - , i = 10 - - while (i--) { - child.killable(i, function (err, id, pid) { - ids.push(id) - pids.push(pid) - if (ids.length == 10) { - t.ok(uniq(pids).length > 2, 'processed by many (' + uniq(pids).length + ') workers, but got there in the end!') - t.ok(uniq(ids).length == 10, 'received a single result for each unique call') - workerFarm.end(child, function () { - t.ok(true, 'workerFarm ended') - }) - } else if (ids.length > 10) - t.fail('too many callbacks!') - }) - } -}) - -// a callback provided to .end() can and will be called (uses "simple, exports=function test" to create a child) -tape('simple, end callback', function (t) { - t.plan(4) - - var child = workerFarm(childPath) - child(0, function (err, pid, rnd) { - t.ok(pid > process.pid, 'pid makes sense ' + pid + ' vs ' + process.pid) - t.ok(pid < process.pid + 500, 'pid makes sense ' + pid + ' vs ' + process.pid) - t.ok(rnd >= 0 && rnd < 1, 'rnd result makes sense') - }) - - workerFarm.end(child, function() { - t.pass('an .end() callback was successfully called') - }) -}) - -tape('call timeout test', function (t) { - t.plan(3 + 3 + 4 + 4 + 4 + 3 + 1) - - var child = workerFarm({ maxCallTime: 250, maxConcurrentWorkers: 1 }, childPath) - - // should come back ok - child(50, function (err, pid, rnd) { - t.ok(pid > process.pid, 'pid makes sense ' + pid + ' vs ' + process.pid) - t.ok(pid < process.pid + 500, 'pid makes sense ' + pid + ' vs ' + process.pid) - t.ok(rnd > 0 && rnd < 1, 'rnd result makes sense ' + rnd) - }) - - // should come back ok - child(50, function (err, pid, rnd) { - t.ok(pid > process.pid, 'pid makes sense ' + pid + ' vs ' + process.pid) - t.ok(pid < process.pid + 500, 'pid makes sense ' + pid + ' vs ' + process.pid) - t.ok(rnd > 0 && rnd < 1, 'rnd result makes sense ' + rnd) - }) - - // should die - child(500, function (err, pid, rnd) { - t.ok(err, 'got an error') - t.equal(err.type, 'TimeoutError', 'correct error type') - t.ok(pid === undefined, 'no pid') - t.ok(rnd === undefined, 'no rnd') - }) - - // should die - child(1000, function (err, pid, rnd) { - t.ok(err, 'got an error') - t.equal(err.type, 'TimeoutError', 'correct error type') - t.ok(pid === undefined, 'no pid') - t.ok(rnd === undefined, 'no rnd') - }) - - // should die even though it is only a 100ms task, it'll get caught up - // in a dying worker - setTimeout(function () { - child(100, function (err, pid, rnd) { - t.ok(err, 'got an error') - t.equal(err.type, 'TimeoutError', 'correct error type') - t.ok(pid === undefined, 'no pid') - t.ok(rnd === undefined, 'no rnd') - }) - }, 200) - - // should be ok, new worker - setTimeout(function () { - child(50, function (err, pid, rnd) { - t.ok(pid > process.pid, 'pid makes sense ' + pid + ' vs ' + process.pid) - t.ok(pid < process.pid + 500, 'pid makes sense ' + pid + ' vs ' + process.pid) - t.ok(rnd > 0 && rnd < 1, 'rnd result makes sense ' + rnd) - }) - workerFarm.end(child, function () { - t.ok(true, 'workerFarm ended') - }) - }, 400) -}) - -tape('test error passing', function (t) { - t.plan(10) - - var child = workerFarm(childPath, [ 'err' ]) - child.err('Error', 'this is an Error', function (err) { - t.ok(err instanceof Error, 'is an Error object') - t.equal('Error', err.type, 'correct type') - t.equal('this is an Error', err.message, 'correct message') - }) - child.err('TypeError', 'this is a TypeError', function (err) { - t.ok(err instanceof Error, 'is a TypeError object') - t.equal('TypeError', err.type, 'correct type') - t.equal('this is a TypeError', err.message, 'correct message') - }) - child.err('Error', 'this is an Error with custom props', {foo: 'bar', 'baz': 1}, function (err) { - t.ok(err instanceof Error, 'is an Error object') - t.equal(err.foo, 'bar', 'passes data') - t.equal(err.baz, 1, 'passes data') - }) - - workerFarm.end(child, function () { - t.ok(true, 'workerFarm ended') - }) -}) - - -tape('test maxConcurrentCalls', function (t) { - t.plan(10) - - var child = workerFarm({ maxConcurrentCalls: 5 }, childPath) - - child(50, function (err) { t.notOk(err, 'no error') }) - child(50, function (err) { t.notOk(err, 'no error') }) - child(50, function (err) { t.notOk(err, 'no error') }) - child(50, function (err) { t.notOk(err, 'no error') }) - child(50, function (err) { t.notOk(err, 'no error') }) - child(50, function (err) { - t.ok(err) - t.equal(err.type, 'MaxConcurrentCallsError', 'correct error type') - }) - child(50, function (err) { - t.ok(err) - t.equal(err.type, 'MaxConcurrentCallsError', 'correct error type') - }) - - workerFarm.end(child, function () { - t.ok(true, 'workerFarm ended') - }) -}) - -// this test should not keep the process running! if the test process -// doesn't die then the problem is here -tape('test timeout kill', function (t) { - t.plan(3) - - var child = workerFarm({ maxCallTime: 250, maxConcurrentWorkers: 1 }, childPath, [ 'block' ]) - child.block(function (err) { - t.ok(err, 'got an error') - t.equal(err.type, 'TimeoutError', 'correct error type') - }) - - workerFarm.end(child, function () { - t.ok(true, 'workerFarm ended') - }) -}) - - -tape('test max retries after process terminate', function (t) { - t.plan(7) - - // temporary file is used to store the number of retries among terminating workers - var filepath1 = '.retries1' - var child1 = workerFarm({ maxConcurrentWorkers: 1, maxRetries: 5}, childPath, [ 'stubborn' ]) - child1.stubborn(filepath1, function (err, result) { - t.notOk(err, 'no error') - t.equal(result, 12, 'correct result') - }) - - workerFarm.end(child1, function () { - fs.unlinkSync(filepath1) - t.ok(true, 'workerFarm ended') - }) - - var filepath2 = '.retries2' - var child2 = workerFarm({ maxConcurrentWorkers: 1, maxRetries: 3}, childPath, [ 'stubborn' ]) - child2.stubborn(filepath2, function (err, result) { - t.ok(err, 'got an error') - t.equal(err.type, 'ProcessTerminatedError', 'correct error type') - t.equal(err.message, 'cancel after 3 retries!', 'correct message and number of retries') - }) - - workerFarm.end(child2, function () { - fs.unlinkSync(filepath2) - t.ok(true, 'workerFarm ended') - }) -})