From 40cce3b12017d7a7d1e4a796c1b47819795a944a Mon Sep 17 00:00:00 2001 From: Rafael Oleza Date: Mon, 19 Mar 2018 10:04:49 -0700 Subject: [PATCH] Convert entryPoint paths to absolute in the Server Reviewed By: davidaurelio Differential Revision: D7196392 fbshipit-source-id: a1927d66fd67e35382a5ac418896dc5a902a5a5c --- packages/metro-core/src/Logger/Logger.js | 11 +++- .../metro/src/DeltaBundler/DeltaCalculator.js | 6 +- .../src/DeltaBundler/DeltaTransformer.js | 9 +-- .../src/HmrServer/__tests__/HmrServer-test.js | 7 ++- packages/metro/src/HmrServer/index.js | 7 ++- .../metro/src/Server/__tests__/Server-test.js | 17 +++--- .../__snapshots__/Server-test.js.snap | 2 +- packages/metro/src/Server/index.js | 24 +++++++- .../src/lib/__mocks__/getAbsolutePath.js | 16 ++++++ .../src/lib/__tests__/getAbsolutePath-test.js | 57 +++++++++++++++++++ packages/metro/src/lib/getAbsolutePath.js | 51 +++++++++++++++++ .../metro/src/node-haste/DependencyGraph.js | 45 --------------- 12 files changed, 179 insertions(+), 73 deletions(-) create mode 100644 packages/metro/src/lib/__mocks__/getAbsolutePath.js create mode 100644 packages/metro/src/lib/__tests__/getAbsolutePath-test.js create mode 100644 packages/metro/src/lib/getAbsolutePath.js diff --git a/packages/metro-core/src/Logger/Logger.js b/packages/metro-core/src/Logger/Logger.js index 7a6f667d..9fdab68d 100644 --- a/packages/metro-core/src/Logger/Logger.js +++ b/packages/metro-core/src/Logger/Logger.js @@ -11,6 +11,8 @@ 'use strict'; const os = require('os'); +const path = require('path'); +const process = require('process'); const {EventEmitter} = require('events'); @@ -33,6 +35,7 @@ export type LogEntry = { action_name?: string, action_phase?: string, duration_ms?: number, + entry_point?: string, log_entry_label: string, log_session?: string, start_timestamp?: [number, number], @@ -46,7 +49,13 @@ function on(event: string, handler: (logEntry: LogEntry) => void): void { } function createEntry(data: LogEntry | string): LogEntry { - const logEntry = typeof data === 'string' ? {log_entry_label: data} : data; + const logEntry: LogEntry = + typeof data === 'string' ? {log_entry_label: data} : data; + + const entryPoint = logEntry.entry_point; + if (entryPoint) { + logEntry.entry_point = path.relative(process.cwd(), entryPoint); + } return { ...logEntry, diff --git a/packages/metro/src/DeltaBundler/DeltaCalculator.js b/packages/metro/src/DeltaBundler/DeltaCalculator.js index 2dd10a18..b1f18b2f 100644 --- a/packages/metro/src/DeltaBundler/DeltaCalculator.js +++ b/packages/metro/src/DeltaBundler/DeltaCalculator.js @@ -73,13 +73,9 @@ class DeltaCalculator extends EventEmitter { this._options = options; this._dependencyGraph = dependencyGraph; - const entryPoints = this._options.entryPoints.map(entryPoint => - this._dependencyGraph.getAbsolutePath(entryPoint), - ); - this._graph = { dependencies: new Map(), - entryPoints, + entryPoints: this._options.entryPoints, }; this._dependencyGraph diff --git a/packages/metro/src/DeltaBundler/DeltaTransformer.js b/packages/metro/src/DeltaBundler/DeltaTransformer.js index c5658aa8..2627ab68 100644 --- a/packages/metro/src/DeltaBundler/DeltaTransformer.js +++ b/packages/metro/src/DeltaBundler/DeltaTransformer.js @@ -356,19 +356,12 @@ class DeltaTransformer extends EventEmitter { } async _getAppend(dependencyEdges: DependencyEdges): Promise { - // Get the absolute path of the entry file, in order to be able to get the - // actual correspondant module (and its moduleId) to be able to add the - // correct require(); call at the very end of the bundle. - const entryPointModulePath = this._dependencyGraph.getAbsolutePath( - this._bundleOptions.entryFile, - ); - // First, get the modules correspondant to all the module names defined in // the `runBeforeMainModule` config variable. Then, append the entry point // module so the last thing that gets required is the entry point. const append = new Map( this._bundleOptions.runBeforeMainModule - .concat(entryPointModulePath) + .concat(this._bundleOptions.entryFile) .filter(path => dependencyEdges.has(path)) .map(this._getModuleId) .map(moduleId => { diff --git a/packages/metro/src/HmrServer/__tests__/HmrServer-test.js b/packages/metro/src/HmrServer/__tests__/HmrServer-test.js index 58aac42e..4ddee6e3 100644 --- a/packages/metro/src/HmrServer/__tests__/HmrServer-test.js +++ b/packages/metro/src/HmrServer/__tests__/HmrServer-test.js @@ -9,6 +9,8 @@ */ 'use strict'; +jest.mock('../../lib/getAbsolutePath'); + const HmrServer = require('..'); const {EventEmitter} = require('events'); @@ -41,6 +43,9 @@ describe('HmrServer', () => { update: jest.fn(), }; }, + getProjectRoots() { + return ['/root']; + }, }; hmrServer = new HmrServer(serverMock); @@ -57,7 +62,7 @@ describe('HmrServer', () => { expect.objectContaining({ deltaBundleId: null, dev: true, - entryFile: 'EntryPoint.js', + entryFile: '/root/EntryPoint.js', minify: false, platform: 'ios', }), diff --git a/packages/metro/src/HmrServer/index.js b/packages/metro/src/HmrServer/index.js index 4a42724c..fdf40189 100644 --- a/packages/metro/src/HmrServer/index.js +++ b/packages/metro/src/HmrServer/index.js @@ -12,6 +12,7 @@ const addParamsToDefineCall = require('../lib/addParamsToDefineCall'); const formatBundlingError = require('../lib/formatBundlingError'); +const getAbsolutePath = require('../lib/getAbsolutePath'); const getBundlingOptionsForHmr = require('./getBundlingOptionsForHmr'); const nullthrows = require('fbjs/lib/nullthrows'); const parseCustomTransformOptions = require('../lib/parseCustomTransformOptions'); @@ -66,7 +67,11 @@ class HmrServer { const deltaBundler = this._packagerServer.getDeltaBundler(); const deltaTransformer = await deltaBundler.getDeltaTransformer( clientUrl, - getBundlingOptionsForHmr(bundleEntry, platform, customTransformOptions), + getBundlingOptionsForHmr( + getAbsolutePath(bundleEntry, this._packagerServer.getProjectRoots()), + platform, + customTransformOptions, + ), ); // Trigger an initial build to start up the DeltaTransformer. diff --git a/packages/metro/src/Server/__tests__/Server-test.js b/packages/metro/src/Server/__tests__/Server-test.js index 37875786..c9d2f3c5 100644 --- a/packages/metro/src/Server/__tests__/Server-test.js +++ b/packages/metro/src/Server/__tests__/Server-test.js @@ -21,6 +21,7 @@ jest .mock('../../Assets') .mock('../../node-haste/DependencyGraph') .mock('metro-core/src/Logger') + .mock('../../lib/getAbsolutePath') .mock('../../lib/GlobalTransformCache') .mock('../../DeltaBundler/Serializers/Serializers'); @@ -47,7 +48,7 @@ describe('processRequest', () => { let server; const options = { - projectRoots: ['root'], + projectRoots: ['/root'], blacklistRE: null, cacheVersion: null, polyfillModuleNames: null, @@ -183,7 +184,7 @@ describe('processRequest', () => { bundleType: 'bundle', customTransformOptions: {}, dev: true, - entryFile: 'index.ios.js', + entryFile: '/root/index.ios.js', entryModuleOnly: false, excludeSource: false, hot: true, @@ -214,7 +215,7 @@ describe('processRequest', () => { bundleType: 'bundle', customTransformOptions: {}, dev: true, - entryFile: 'index.js', + entryFile: '/root/index.js', entryModuleOnly: false, excludeSource: false, hot: true, @@ -245,7 +246,7 @@ describe('processRequest', () => { bundleType: 'bundle', customTransformOptions: {}, dev: true, - entryFile: 'index.js', + entryFile: '/root/index.js', entryModuleOnly: false, excludeSource: false, hot: true, @@ -391,7 +392,7 @@ describe('processRequest', () => { server.processRequest(req, res); res.end.mockImplementation(value => { - expect(getAsset).toBeCalledWith('imgs/a.png', ['root'], 'ios'); + expect(getAsset).toBeCalledWith('imgs/a.png', ['/root'], 'ios'); expect(value).toBe('i am image'); done(); }); @@ -409,7 +410,7 @@ describe('processRequest', () => { server.processRequest(req, res); res.end.mockImplementation(value => { - expect(getAsset).toBeCalledWith('imgs/a.png', ['root'], 'ios'); + expect(getAsset).toBeCalledWith('imgs/a.png', ['/root'], 'ios'); expect(value).toBe(mockData.slice(0, 4)); done(); }); @@ -427,7 +428,7 @@ describe('processRequest', () => { res.end.mockImplementation(value => { expect(getAsset).toBeCalledWith( 'imgs/\u{4E3B}\u{9875}/logo.png', - ['root'], + ['/root'], undefined, ); expect(value).toBe('i am image'); @@ -450,7 +451,7 @@ describe('processRequest', () => { assetPlugins: [], customTransformOptions: {}, dev: true, - entryFile: 'foo file', + entryFile: '/root/foo file', entryModuleOnly: false, excludeSource: false, hot: false, diff --git a/packages/metro/src/Server/__tests__/__snapshots__/Server-test.js.snap b/packages/metro/src/Server/__tests__/__snapshots__/Server-test.js.snap index 82f51698..527e6c91 100644 --- a/packages/metro/src/Server/__tests__/__snapshots__/Server-test.js.snap +++ b/packages/metro/src/Server/__tests__/__snapshots__/Server-test.js.snap @@ -1,3 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`processRequest Generate delta bundle endpoint should send the correct deltaBundlerId to the bundler 1`] = `"{\\"sourceMapUrl\\":null,\\"bundleType\\":\\"delta\\",\\"customTransformOptions\\":{},\\"entryFile\\":\\"index.js\\",\\"deltaBundleId\\":null,\\"dev\\":true,\\"minify\\":false,\\"excludeSource\\":null,\\"hot\\":true,\\"runBeforeMainModule\\":[\\"InitializeCore\\"],\\"runModule\\":true,\\"inlineSourceMap\\":false,\\"isolateModuleIDs\\":false,\\"platform\\":\\"ios\\",\\"resolutionResponse\\":null,\\"entryModuleOnly\\":false,\\"assetPlugins\\":[],\\"onProgress\\":null,\\"unbundle\\":false}"`; +exports[`processRequest Generate delta bundle endpoint should send the correct deltaBundlerId to the bundler 1`] = `"{\\"sourceMapUrl\\":null,\\"bundleType\\":\\"delta\\",\\"customTransformOptions\\":{},\\"entryFile\\":\\"/root/index.js\\",\\"deltaBundleId\\":null,\\"dev\\":true,\\"minify\\":false,\\"excludeSource\\":null,\\"hot\\":true,\\"runBeforeMainModule\\":[\\"InitializeCore\\"],\\"runModule\\":true,\\"inlineSourceMap\\":false,\\"isolateModuleIDs\\":false,\\"platform\\":\\"ios\\",\\"resolutionResponse\\":null,\\"entryModuleOnly\\":false,\\"assetPlugins\\":[],\\"onProgress\\":null,\\"unbundle\\":false}"`; diff --git a/packages/metro/src/Server/index.js b/packages/metro/src/Server/index.js index fe4399e3..6df66feb 100644 --- a/packages/metro/src/Server/index.js +++ b/packages/metro/src/Server/index.js @@ -17,6 +17,7 @@ const Serializers = require('../DeltaBundler/Serializers/Serializers'); const debug = require('debug')('Metro:Server'); const defaults = require('../defaults'); const formatBundlingError = require('../lib/formatBundlingError'); +const getAbsolutePath = require('../lib/getAbsolutePath'); const getMaxWorkers = require('../lib/getMaxWorkers'); const getOrderedDependencyPaths = require('../lib/getOrderedDependencyPaths'); const mime = require('mime-types'); @@ -234,6 +235,7 @@ class Server { runBeforeMainModule: this._opts.getModulesRunBeforeMainModule( options.entryFile, ), + entryFile: getAbsolutePath(options.entryFile, this._opts.projectRoots), }; const fullBundle = await Serializers.fullBundle( @@ -255,7 +257,10 @@ class Server { async getRamBundleInfo(options: BundleOptions): Promise { return await Serializers.getRamBundleInfo( this._deltaBundler, - options, + { + ...options, + entryFile: getAbsolutePath(options.entryFile, this._opts.projectRoots), + }, this._opts.getTransformOptions, ); } @@ -263,7 +268,10 @@ class Server { async getAssets(options: BundleOptions): Promise<$ReadOnlyArray> { return await Serializers.getAssets( this._deltaBundler, - options, + { + ...options, + entryFile: getAbsolutePath(options.entryFile, this._opts.projectRoots), + }, this._opts.projectRoots, ); } @@ -277,6 +285,7 @@ class Server { const bundleOptions = { ...Server.DEFAULT_BUNDLE_OPTIONS, ...options, + entryFile: getAbsolutePath(options.entryFile, this._opts.projectRoots), bundleType: 'delta', }; @@ -813,6 +822,11 @@ class Server { }) .join('.') + '.js'; + const absoluteEntryFile = getAbsolutePath( + entryFile, + this._opts.projectRoots, + ); + // try to get the platform from the url const platform = urlQuery.platform || @@ -847,7 +861,7 @@ class Server { }), bundleType: isMap ? 'map' : deltaBundleId ? 'delta' : 'bundle', customTransformOptions, - entryFile, + entryFile: absoluteEntryFile, deltaBundleId, dev, minify, @@ -891,6 +905,10 @@ class Server { return this._reporter; } + getProjectRoots(): $ReadOnlyArray { + return this._opts.projectRoots; + } + static DEFAULT_BUNDLE_OPTIONS = { assetPlugins: [], customTransformOptions: Object.create(null), diff --git a/packages/metro/src/lib/__mocks__/getAbsolutePath.js b/packages/metro/src/lib/__mocks__/getAbsolutePath.js new file mode 100644 index 00000000..24a4a8a6 --- /dev/null +++ b/packages/metro/src/lib/__mocks__/getAbsolutePath.js @@ -0,0 +1,16 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + * @format + */ + +'use strict'; + +const path = require('path'); + +module.exports = (file: string, roots: $ReadOnlyArray): string => + path.resolve(roots[0], file); diff --git a/packages/metro/src/lib/__tests__/getAbsolutePath-test.js b/packages/metro/src/lib/__tests__/getAbsolutePath-test.js new file mode 100644 index 00000000..e914f578 --- /dev/null +++ b/packages/metro/src/lib/__tests__/getAbsolutePath-test.js @@ -0,0 +1,57 @@ +/** + * Copyright (c) 2013-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @emails oncall+javascript_foundation + * @format + */ + +'use strict'; + +jest.mock('fs', () => new (require('metro-memory-fs'))()); + +const fs = require('fs'); +const getAbsolutePath = require('../getAbsolutePath'); +const mkdirp = require('mkdirp'); + +beforeEach(() => { + fs.reset(); + + mkdirp.sync('/root/a'); + mkdirp.sync('/root/b'); + mkdirp.sync('/root/a/d'); +}); + +it('should work for a simple case with a single project root', () => { + fs.writeFileSync('/root/a/entryPoint.js', ''); + + expect(getAbsolutePath('entryPoint.js', ['/root/a'])).toEqual( + '/root/a/entryPoint.js', + ); +}); + +it('should resolve from the first defined project root', () => { + fs.writeFileSync('/root/a/entryPoint.js', ''); + fs.writeFileSync('/root/b/entryPoint.js', ''); + + expect(getAbsolutePath('entryPoint.js', ['/root/a', '/root/c'])).toEqual( + '/root/a/entryPoint.js', + ); +}); + +it('should resolve from sub-folders', () => { + fs.writeFileSync('/root/a/d/entryPoint.js', ''); + fs.writeFileSync('/root/b/entryPoint.js', ''); + + expect(getAbsolutePath('d/entryPoint.js', ['/root/a', '/root/d'])).toEqual( + '/root/a/d/entryPoint.js', + ); +}); + +it('should throw an error if not found', () => { + expect(() => + getAbsolutePath('entryPoint.js', ['/root/a', '/root/d']), + ).toThrow(); +}); diff --git a/packages/metro/src/lib/getAbsolutePath.js b/packages/metro/src/lib/getAbsolutePath.js new file mode 100644 index 00000000..b3c0bbd3 --- /dev/null +++ b/packages/metro/src/lib/getAbsolutePath.js @@ -0,0 +1,51 @@ +/** + * Copyright (c) 2015-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + * @format + */ + +'use strict'; + +const fs = require('fs'); +const isAbsolutePath = require('absolute-path'); +const path = require('path'); + +function getAbsolutePath( + filePath: string, + projectRoots: $ReadOnlyArray, +): string { + if (isAbsolutePath(filePath)) { + return path.resolve(filePath); + } + + for (let i = 0; i < projectRoots.length; i++) { + const potentialAbsPath = path.resolve(projectRoots[i], filePath); + if (fs.existsSync(potentialAbsPath)) { + return potentialAbsPath; + } + } + + throw new NotFoundError(filePath, projectRoots); +} + +class NotFoundError extends Error { + status: number; + type: string; + + constructor(relativePath: string, projectRoots: $ReadOnlyArray) { + super( + `File not found: ${relativePath} in any of the project roots (${projectRoots.join( + ', ', + )})`, + ); + + this.type = 'NotFoundError'; + this.status = 404; + } +} + +module.exports = getAbsolutePath; diff --git a/packages/metro/src/node-haste/DependencyGraph.js b/packages/metro/src/node-haste/DependencyGraph.js index 077a3319..aa940ffb 100644 --- a/packages/metro/src/node-haste/DependencyGraph.js +++ b/packages/metro/src/node-haste/DependencyGraph.js @@ -19,11 +19,9 @@ const ModuleCache = require('./ModuleCache'); const ResolutionRequest = require('./DependencyGraph/ResolutionRequest'); const fs = require('fs'); -const isAbsolutePath = require('absolute-path'); const parsePlatformFilePath = require('./lib/parsePlatformFilePath'); const path = require('path'); const toLocalPath = require('../node-haste/lib/toLocalPath'); -const util = require('util'); const {ModuleResolver} = require('./DependencyGraph/ModuleResolution'); const {EventEmitter} = require('events'); @@ -288,52 +286,9 @@ class DependencyGraph extends EventEmitter { return toLocalPath(this._opts.projectRoots, filePath); } - getAbsolutePath(filePath: string) { - if (isAbsolutePath(filePath)) { - return path.resolve(filePath); - } - - for (let i = 0; i < this._opts.projectRoots.length; i++) { - const root = this._opts.projectRoots[i]; - const potentialAbsPath = path.join(root, filePath); - if (this._hasteFS.exists(potentialAbsPath)) { - return path.resolve(potentialAbsPath); - } - } - - // If we failed to find a file, maybe this is just a Haste name so try that - // TODO: We should prefer Haste name resolution first ideally since it is faster - // TODO: Ideally, we should not do any `path.parse().name` here and just use the - // name, but in `metro/src/Server/index.js` we append `'.js'` to all names - // so until that changes, we have to do this. - const potentialPath = this._moduleMap.getModule( - path.parse(filePath).name, - null, - ); - if (potentialPath) { - return potentialPath; - } - - throw new NotFoundError( - 'Cannot find entry file %s in any of the roots: %j', - filePath, - this._opts.projectRoots, - ); - } - createPolyfill(options: {file: string}) { return this._moduleCache.createPolyfill(options); } } -function NotFoundError(...args) { - 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;