diff --git a/packages/metro-minify-uglify/src/minifier.js b/packages/metro-minify-uglify/src/minifier.js index 8e6faf00..b2320435 100644 --- a/packages/metro-minify-uglify/src/minifier.js +++ b/packages/metro-minify-uglify/src/minifier.js @@ -34,7 +34,7 @@ function withSourceMap( function minify(inputCode: string, inputMap: ?BabelSourceMap) { const result = uglify.minify(inputCode, { - mangle: {toplevel: true}, + mangle: {toplevel: false}, output: { ascii_only: true, quote_style: 3, @@ -44,7 +44,7 @@ function minify(inputCode: string, inputMap: ?BabelSourceMap) { content: inputMap, includeSources: false, }, - toplevel: true, + toplevel: false, compress: { // reduce_funcs inlines single-use function, which cause perf regressions. reduce_funcs: false, diff --git a/packages/metro/src/DeltaBundler/DeltaTransformer.js b/packages/metro/src/DeltaBundler/DeltaTransformer.js index e796c38d..197a7bb1 100644 --- a/packages/metro/src/DeltaBundler/DeltaTransformer.js +++ b/packages/metro/src/DeltaBundler/DeltaTransformer.js @@ -444,7 +444,7 @@ class DeltaTransformer extends EventEmitter { const name = this._dependencyGraph.getHasteName(edge.path); const wrappedCode = wrapModule(edge, { - createModuleIdFn: this._getModuleId, + createModuleId: this._getModuleId, dev: transformOptions.dev, }); diff --git a/packages/metro/src/DeltaBundler/Serializers/__tests__/plainJSBundle-test.js b/packages/metro/src/DeltaBundler/Serializers/__tests__/plainJSBundle-test.js index 7088e9d2..971502cc 100644 --- a/packages/metro/src/DeltaBundler/Serializers/__tests__/plainJSBundle-test.js +++ b/packages/metro/src/DeltaBundler/Serializers/__tests__/plainJSBundle-test.js @@ -42,7 +42,7 @@ it('should serialize a very simple bundle', () => { entryPoints: ['foo'], }, { - createModuleIdFn: path => path, + createModuleId: path => path, dev: true, runBeforeMainModule: [], runModule: true, @@ -70,7 +70,7 @@ it('should add runBeforeMainModule statements if found in the graph', () => { entryPoints: ['foo'], }, { - createModuleIdFn: path => path, + createModuleId: path => path, dev: true, runBeforeMainModule: ['bar', 'non-existant'], runModule: true, @@ -99,7 +99,7 @@ it('should handle numeric module ids', () => { entryPoints: ['foo'], }, { - createModuleIdFn: createModuleIdFactory(), + createModuleId: createModuleIdFactory(), dev: true, runBeforeMainModule: ['bar', 'non-existant'], runModule: true, diff --git a/packages/metro/src/DeltaBundler/Serializers/helpers/__tests__/js-test.js b/packages/metro/src/DeltaBundler/Serializers/helpers/__tests__/js-test.js index 8fdfce65..f6899119 100644 --- a/packages/metro/src/DeltaBundler/Serializers/helpers/__tests__/js-test.js +++ b/packages/metro/src/DeltaBundler/Serializers/helpers/__tests__/js-test.js @@ -35,7 +35,7 @@ describe('wrapModule()', () => { it('Should wrap a module in nondev mode', () => { expect( wrapModule(myModule, { - createModuleIdFn: createModuleIdFactory(), + createModuleId: createModuleIdFactory(), dev: false, }), ).toEqual('__d(function() { console.log("foo") },0,[1,2]);'); @@ -44,7 +44,7 @@ describe('wrapModule()', () => { it('Should wrap a module in dev mode', () => { expect( wrapModule(myModule, { - createModuleIdFn: createModuleIdFactory(), + createModuleId: createModuleIdFactory(), dev: true, }), ).toEqual('__d(function() { console.log("foo") },0,[1,2],"foo.js");'); @@ -55,17 +55,17 @@ describe('wrapModule()', () => { expect( wrapModule(myModule, { - createModuleIdFn: createModuleIdFactory(), + createModuleId: createModuleIdFactory(), dev: true, }), ).toEqual(myModule.output.code); }); - it('should use custom createModuleIdFn param', () => { - // Just use a createModuleIdFn that returns the same path. + it('should use custom createModuleId param', () => { + // Just use a createModuleId that returns the same path. expect( wrapModule(myModule, { - createModuleIdFn: path => path, + createModuleId: path => path, dev: false, }), ).toEqual( diff --git a/packages/metro/src/DeltaBundler/Serializers/helpers/js.js b/packages/metro/src/DeltaBundler/Serializers/helpers/js.js index 067602e9..96bfedc3 100644 --- a/packages/metro/src/DeltaBundler/Serializers/helpers/js.js +++ b/packages/metro/src/DeltaBundler/Serializers/helpers/js.js @@ -16,7 +16,7 @@ const path = require('path'); import type {DependencyEdge} from '../../traverseDependencies'; export type Options = { - +createModuleIdFn: string => number | string, + +createModuleId: string => number | string, +dev: boolean, }; @@ -25,10 +25,10 @@ function wrapModule(module: DependencyEdge, options: Options) { return module.output.code; } - const moduleId = options.createModuleIdFn(module.path); + const moduleId = options.createModuleId(module.path); const params = [ moduleId, - Array.from(module.dependencies.values()).map(options.createModuleIdFn), + Array.from(module.dependencies.values()).map(options.createModuleId), ]; // Add the module name as the last parameter (to make it easier to do diff --git a/packages/metro/src/DeltaBundler/Serializers/plainJSBundle.js b/packages/metro/src/DeltaBundler/Serializers/plainJSBundle.js index a76aa425..9d601d9f 100644 --- a/packages/metro/src/DeltaBundler/Serializers/plainJSBundle.js +++ b/packages/metro/src/DeltaBundler/Serializers/plainJSBundle.js @@ -10,13 +10,15 @@ 'use strict'; +const getAppendScripts = require('../../lib/getAppendScripts'); + const {wrapModule} = require('./helpers/js'); import type {Graph} from '../DeltaCalculator'; import type {DependencyEdge} from '../traverseDependencies'; type Options = {| - createModuleIdFn: string => number | string, + createModuleId: string => number | string, +dev: boolean, +runBeforeMainModule: $ReadOnlyArray, +runModule: boolean, @@ -29,35 +31,17 @@ function plainJSBundle( graph: Graph, options: Options, ): string { - const output = []; - - for (const module of pre) { - output.push(wrapModule(module, options)); - } - for (const module of graph.dependencies.values()) { - output.push(wrapModule(module, options)); + options.createModuleId(module.path); } - for (const path of options.runBeforeMainModule) { - if (graph.dependencies.has(path)) { - output.push( - `require(${JSON.stringify(options.createModuleIdFn(path))});`, - ); - } - } - - if (options.runModule && graph.dependencies.has(entryPoint)) { - output.push( - `require(${JSON.stringify(options.createModuleIdFn(entryPoint))});`, - ); - } - - if (options.sourceMapUrl) { - output.push(`//# sourceMappingURL=${options.sourceMapUrl}`); - } - - return output.join('\n'); + return [ + ...pre, + ...graph.dependencies.values(), + ...getAppendScripts(entryPoint, graph, options), + ] + .map(module => wrapModule(module, options)) + .join('\n'); } module.exports = plainJSBundle; diff --git a/packages/metro/src/DeltaBundler/index.js b/packages/metro/src/DeltaBundler/index.js index 5e8f31e9..98e66932 100644 --- a/packages/metro/src/DeltaBundler/index.js +++ b/packages/metro/src/DeltaBundler/index.js @@ -10,10 +10,16 @@ 'use strict'; +const DeltaCalculator = require('./DeltaCalculator'); const DeltaTransformer = require('./DeltaTransformer'); import type Bundler from '../Bundler'; import type {BundleOptions} from '../shared/types.flow'; +import type { + DeltaResult, + Graph as CalculatorGraph, + Options, +} from './DeltaCalculator'; import type {DeltaEntry} from './DeltaTransformer'; export type PostProcessModules = ( @@ -27,6 +33,9 @@ export type MainOptions = {| postProcessModules?: PostProcessModules, |}; +export type Delta = DeltaResult; +export type Graph = CalculatorGraph; + /** * `DeltaBundler` uses the `DeltaTransformer` to build bundle deltas. This * module handles all the transformer instances so it can support multiple @@ -38,6 +47,7 @@ class DeltaBundler { _options: MainOptions; _deltaTransformers: Map = new Map(); _currentId: number = 0; + _deltaCalculators: Map = new Map(); constructor(bundler: Bundler, options: MainOptions) { this._bundler = bundler; @@ -47,6 +57,9 @@ class DeltaBundler { end() { this._deltaTransformers.forEach(DeltaTransformer => DeltaTransformer.end()); this._deltaTransformers = new Map(); + + this._deltaCalculators.forEach(deltaCalculator => deltaCalculator.end()); + this._deltaCalculators = new Map(); } endTransformer(clientId: string) { @@ -78,6 +91,55 @@ class DeltaBundler { return deltaTransformer; } + async buildGraph(options: Options): Promise { + const depGraph = await this._bundler.getDependencyGraph(); + + const deltaCalculator = new DeltaCalculator( + this._bundler, + depGraph, + options, + ); + + await deltaCalculator.getDelta({reset: true}); + const graph = deltaCalculator.getGraph(); + + this._deltaCalculators.set(graph, deltaCalculator); + + return graph; + } + + async getDelta(graph: Graph, {reset}: {reset: boolean}): Promise { + const deltaCalculator = this._deltaCalculators.get(graph); + + if (!deltaCalculator) { + throw new Error('Graph not found'); + } + + return await deltaCalculator.getDelta({reset}); + } + + listen(graph: Graph, callback: () => mixed) { + const deltaCalculator = this._deltaCalculators.get(graph); + + if (!deltaCalculator) { + throw new Error('Graph not found'); + } + + deltaCalculator.on('change', callback); + } + + endGraph(graph: Graph) { + const deltaCalculator = this._deltaCalculators.get(graph); + + if (!deltaCalculator) { + throw new Error('Graph not found'); + } + + deltaCalculator.end(); + + this._deltaCalculators.delete(graph); + } + getPostProcessModulesFn( entryPoint: string, ): (modules: $ReadOnlyArray) => $ReadOnlyArray { diff --git a/packages/metro/src/Server/__tests__/Server-test.js b/packages/metro/src/Server/__tests__/Server-test.js index c9d2f3c5..0d677a85 100644 --- a/packages/metro/src/Server/__tests__/Server-test.js +++ b/packages/metro/src/Server/__tests__/Server-test.js @@ -19,6 +19,7 @@ jest })) .mock('../../Bundler') .mock('../../Assets') + .mock('../../lib/getPrependedScripts') .mock('../../node-haste/DependencyGraph') .mock('metro-core/src/Logger') .mock('../../lib/getAbsolutePath') @@ -29,6 +30,7 @@ describe('processRequest', () => { let Bundler; let Server; let getAsset; + let getPrependedScripts; let symbolicate; let Serializers; let DeltaBundler; @@ -40,6 +42,7 @@ describe('processRequest', () => { Bundler = require('../../Bundler'); Server = require('../'); getAsset = require('../../Assets').getAsset; + getPrependedScripts = require('../../lib/getPrependedScripts'); symbolicate = require('../symbolicate/symbolicate'); Serializers = require('../../DeltaBundler/Serializers/Serializers'); DeltaBundler = require('../../DeltaBundler'); @@ -85,6 +88,15 @@ describe('processRequest', () => { let requestHandler; beforeEach(() => { + DeltaBundler.prototype.buildGraph = jest.fn().mockReturnValue( + Promise.resolve({ + entryPoints: [''], + dependencies: new Map(), + }), + ); + + getPrependedScripts.mockReturnValue(Promise.resolve([])); + Serializers.fullBundle.mockReturnValue( Promise.resolve({ bundle: 'this is the source', @@ -445,28 +457,16 @@ describe('processRequest', () => { entryFile: 'foo file', }) .then(() => - expect(Serializers.fullBundle).toBeCalledWith( - expect.any(DeltaBundler), - { - assetPlugins: [], - customTransformOptions: {}, - dev: true, - entryFile: '/root/foo file', - entryModuleOnly: false, - excludeSource: false, - hot: false, - inlineSourceMap: false, - isolateModuleIDs: false, - minify: false, - onProgress: null, - platform: undefined, - resolutionResponse: null, - runBeforeMainModule: ['InitializeCore'], - runModule: true, - sourceMapUrl: null, - unbundle: false, - }, - ), + expect(DeltaBundler.prototype.buildGraph).toBeCalledWith({ + assetPlugins: [], + customTransformOptions: {}, + dev: true, + entryPoints: ['/root/foo file'], + hot: false, + minify: false, + onProgress: null, + platform: undefined, + }), ); }); }); diff --git a/packages/metro/src/Server/index.js b/packages/metro/src/Server/index.js index 6df66feb..821e34ba 100644 --- a/packages/metro/src/Server/index.js +++ b/packages/metro/src/Server/index.js @@ -14,13 +14,19 @@ const Bundler = require('../Bundler'); const DeltaBundler = require('../DeltaBundler'); const MultipartResponse = require('./MultipartResponse'); const Serializers = require('../DeltaBundler/Serializers/Serializers'); + +const defaultCreateModuleIdFactory = require('../lib/createModuleIdFactory'); +const plainJSBundle = require('../DeltaBundler/Serializers/plainJSBundle'); +const sourceMapString = require('../DeltaBundler/Serializers/sourceMapString'); 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 getPrependedScripts = require('../lib/getPrependedScripts'); const mime = require('mime-types'); +const mapGraph = require('../lib/mapGraph'); const nullthrows = require('fbjs/lib/nullthrows'); const parseCustomTransformOptions = require('../lib/parseCustomTransformOptions'); const parsePlatformFilePath = require('../node-haste/lib/parsePlatformFilePath'); @@ -32,6 +38,7 @@ const {getAsset} = require('../Assets'); const resolveSync: ResolveSync = require('resolve').sync; import type {CustomError} from '../lib/formatBundlingError'; +import type {DependencyEdge} from '../DeltaBundler/traverseDependencies'; import type {IncomingMessage, ServerResponse} from 'http'; import type {Reporter} from '../lib/reporting'; import type { @@ -73,7 +80,7 @@ class Server { blacklistRE: void | RegExp, cacheStores: $ReadOnlyArray>, cacheVersion: string, - createModuleIdFactory?: () => (path: string) => number, + createModuleId: (path: string) => number, enableBabelRCLookup: boolean, extraNodeModules: {}, getPolyfills: ({platform: ?string}) => $ReadOnlyArray, @@ -119,6 +126,9 @@ class Server { const assetExts = options.assetExts || defaults.assetExts; const sourceExts = options.sourceExts || defaults.sourceExts; + const _createModuleId = + options.createModuleId || defaultCreateModuleIdFactory(); + this._opts = { assetExts: options.assetTransforms ? [] : assetExts, assetRegistryPath: options.assetRegistryPath, @@ -126,7 +136,7 @@ class Server { cacheStores: options.cacheStores, cacheVersion: options.cacheVersion, dynamicDepsInPackages: options.dynamicDepsInPackages || 'throwAtRuntime', - createModuleIdFactory: options.createModuleIdFactory, + createModuleId: _createModuleId, enableBabelRCLookup: options.enableBabelRCLookup != null ? options.enableBabelRCLookup @@ -175,7 +185,7 @@ class Server { // This slices out options that are not part of the strict BundlerOptions /* eslint-disable no-unused-vars */ const { - createModuleIdFactory, + createModuleId, getModulesRunBeforeMainModule, moduleFormat, silent, @@ -230,27 +240,48 @@ class Server { } async build(options: BundleOptions): Promise<{code: string, map: string}> { - options = { - ...options, - runBeforeMainModule: this._opts.getModulesRunBeforeMainModule( - options.entryFile, - ), - entryFile: getAbsolutePath(options.entryFile, this._opts.projectRoots), - }; - - const fullBundle = await Serializers.fullBundle( - this._deltaBundler, - options, + const entryPoint = getAbsolutePath( + options.entryFile, + this._opts.projectRoots, ); - const fullMap = await Serializers.fullSourceMap( - this._deltaBundler, + let graph = await this._deltaBundler.buildGraph({ + assetPlugins: options.assetPlugins, + customTransformOptions: options.customTransformOptions, + dev: options.dev, + entryPoints: [entryPoint], + hot: options.hot, + minify: options.minify, + onProgress: options.onProgress, + platform: options.platform, + }); + let prependScripts = await getPrependedScripts( + this._opts, options, + this._bundler, ); + if (options.minify) { + prependScripts = await Promise.all( + prependScripts.map(script => this._minifyModule(script)), + ); + + graph = await mapGraph(graph, module => this._minifyModule(module)); + } + return { - code: fullBundle.bundle, - map: fullMap, + code: plainJSBundle(entryPoint, prependScripts, graph, { + createModuleId: this._opts.createModuleId, + dev: options.dev, + runBeforeMainModule: this._opts.getModulesRunBeforeMainModule( + options.entryFile, + ), + runModule: options.runModule, + sourceMapUrl: options.sourceMapUrl, + }), + map: sourceMapString(prependScripts, graph, { + excludeSource: options.excludeSource, + }), }; } @@ -299,6 +330,24 @@ class Server { return await getOrderedDependencyPaths(this._deltaBundler, bundleOptions); } + async _minifyModule(module: DependencyEdge): Promise { + const {code, map} = await this._bundler.minifyModule( + module.path, + module.output.code, + module.output.map, + ); + + // $FlowIssue #16581373 spread of an exact object should be exact + return { + ...module, + output: { + ...module.output, + code, + map, + }, + }; + } + onFileChange(type: string, filePath: string) { Promise.all( this._fileChangeListeners.map(listener => listener(filePath)), diff --git a/packages/metro/src/integration_tests/__tests__/__snapshots__/basic_bundle-test.js.snap b/packages/metro/src/integration_tests/__tests__/__snapshots__/basic_bundle-test.js.snap index fb41e30d..98228e6f 100644 --- a/packages/metro/src/integration_tests/__tests__/__snapshots__/basic_bundle-test.js.snap +++ b/packages/metro/src/integration_tests/__tests__/__snapshots__/basic_bundle-test.js.snap @@ -149,7 +149,7 @@ __d(function (global, _require, module, exports, _dependencyMap) { Foo: Foo, Bar: Bar }; -},4,[5,6]); +},0,[1,2]); __d(function (global, _require, module, exports, _dependencyMap) { 'use strict'; @@ -159,7 +159,7 @@ __d(function (global, _require, module, exports, _dependencyMap) { type: 'bar', foo: Foo.type }; -},5,[6]); +},1,[2]); __d(function (global, _require, module, exports, _dependencyMap) { 'use strict'; @@ -169,7 +169,7 @@ __d(function (global, _require, module, exports, _dependencyMap) { type: 'foo', asset: asset }; -},6,[7]); +},2,[3]); __d(function (global, _require, module, exports, _dependencyMap) { module.exports = _require(_dependencyMap[0]).registerAsset({ \\"__packager_asset\\": true, @@ -181,11 +181,11 @@ __d(function (global, _require, module, exports, _dependencyMap) { \\"name\\": \\"test\\", \\"type\\": \\"png\\" }); -},7,[8]); +},3,[4]); __d(function (global, _require, module, exports, _dependencyMap) { 'use strict'; -},8,[]); -require(4);" +},4,[]); +require(0);" `; exports[`basic_bundle bundles package without polyfills 1`] = ` @@ -323,7 +323,7 @@ __d(function (global, _require, module, exports, _dependencyMap) { Foo: Foo, Bar: Bar }; -},2,[3,4]); +},0,[1,2]); __d(function (global, _require, module, exports, _dependencyMap) { 'use strict'; @@ -333,7 +333,7 @@ __d(function (global, _require, module, exports, _dependencyMap) { type: 'bar', foo: Foo.type }; -},3,[4]); +},1,[2]); __d(function (global, _require, module, exports, _dependencyMap) { 'use strict'; @@ -343,7 +343,7 @@ __d(function (global, _require, module, exports, _dependencyMap) { type: 'foo', asset: asset }; -},4,[5]); +},2,[3]); __d(function (global, _require, module, exports, _dependencyMap) { module.exports = _require(_dependencyMap[0]).registerAsset({ \\"__packager_asset\\": true, @@ -355,9 +355,9 @@ __d(function (global, _require, module, exports, _dependencyMap) { \\"name\\": \\"test\\", \\"type\\": \\"png\\" }); -},5,[6]); +},3,[4]); __d(function (global, _require, module, exports, _dependencyMap) { 'use strict'; -},6,[]); -require(2);" +},4,[]); +require(0);" `; diff --git a/packages/metro/src/lib/__tests__/mapGraph-test.js b/packages/metro/src/lib/__tests__/mapGraph-test.js new file mode 100644 index 00000000..1376b8e9 --- /dev/null +++ b/packages/metro/src/lib/__tests__/mapGraph-test.js @@ -0,0 +1,58 @@ +/** + * 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. + * + * @emails oncall+javascript_foundation + * @format + */ + +'use strict'; + +const mapGraph = require('../mapGraph'); + +let graph; + +beforeEach(() => { + graph = { + dependencies: new Map([ + ['/entryPoint', {name: 'entryPoint', id: '1'}], + ['/foo', {name: 'foo', id: '2'}], + ['/baz', {name: 'baz', id: '3'}], + ]), + entryPoints: ['/entryPoint'], + }; +}); + +it('should map the passed graph when a sync function is passed', async () => { + const mapped = await mapGraph(graph, element => ({ + name: '-' + element.name + '-', + id: parseInt(element.id, 10), + })); + + expect(mapped.dependencies).toEqual( + new Map([ + ['/entryPoint', {name: '-entryPoint-', id: 1}], + ['/foo', {name: '-foo-', id: 2}], + ['/baz', {name: '-baz-', id: 3}], + ]), + ); + expect(mapped.entryPoints).toEqual(['/entryPoint']); +}); + +it('should map the passed graph when an async function is passed', async () => { + const mapped = await mapGraph(graph, async element => ({ + name: '-' + element.name + '-', + id: parseInt(element.id, 10), + })); + + expect(mapped.dependencies).toEqual( + new Map([ + ['/entryPoint', {name: '-entryPoint-', id: 1}], + ['/foo', {name: '-foo-', id: 2}], + ['/baz', {name: '-baz-', id: 3}], + ]), + ); + expect(mapped.entryPoints).toEqual(['/entryPoint']); +}); diff --git a/packages/metro/src/lib/getAppendScripts.js b/packages/metro/src/lib/getAppendScripts.js new file mode 100644 index 00000000..2f45834f --- /dev/null +++ b/packages/metro/src/lib/getAppendScripts.js @@ -0,0 +1,67 @@ +/** + * 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'; + +import type {Graph} from '../DeltaBundler/DeltaCalculator'; +import type {DependencyEdge} from '../DeltaBundler/traverseDependencies'; + +type Options = { + +createModuleId: string => number | string, + +runBeforeMainModule: $ReadOnlyArray, + +runModule: boolean, + +sourceMapUrl: ?string, +}; + +function getAppendScripts( + entryPoint: string, + graph: Graph, + options: Options, +): $ReadOnlyArray { + const output = []; + + if (options.runModule) { + const paths = [...options.runBeforeMainModule, entryPoint]; + + for (const path of paths) { + if (graph.dependencies.has(path)) { + output.push({ + path: `require-${path}`, + dependencies: new Map(), + inverseDependencies: new Set(), + output: { + code: `require(${JSON.stringify(options.createModuleId(path))});`, + source: '', + map: [], + type: 'script', + }, + }); + } + } + } + + if (options.sourceMapUrl) { + output.push({ + path: 'source-map', + dependencies: new Map(), + inverseDependencies: new Set(), + output: { + code: `//# sourceMappingURL=${options.sourceMapUrl}`, + source: '', + map: [], + type: 'script', + }, + }); + } + + return output; +} + +module.exports = getAppendScripts; diff --git a/packages/metro/src/lib/getPrependedScripts.js b/packages/metro/src/lib/getPrependedScripts.js new file mode 100644 index 00000000..36085197 --- /dev/null +++ b/packages/metro/src/lib/getPrependedScripts.js @@ -0,0 +1,125 @@ +/** + * 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 defaults = require('../defaults'); +const getPreludeCode = require('./getPreludeCode'); + +import type Bundler from '../Bundler'; +import type {DependencyEdge} from '../DeltaBundler/traverseDependencies'; +import type Module from '../node-haste/Module'; + +type Options = { + enableBabelRCLookup: boolean, + getPolyfills: ({platform: ?string}) => $ReadOnlyArray, + polyfillModuleNames: Array, + projectRoots: $ReadOnlyArray, +}; + +type BundleOptions = { + +dev: boolean, + +hot: boolean, + +platform: ?string, +}; + +async function getPrependedScripts( + options: Options, + bundleOptions: BundleOptions, + bundler: Bundler, +): Promise> { + // Get all the polyfills from the relevant option params (the + // `getPolyfills()` method and the `polyfillModuleNames` variable). + const polyfillModuleNames = options + .getPolyfills({ + platform: bundleOptions.platform, + }) + .concat(options.polyfillModuleNames); + + const dependencyGraph = await bundler.getDependencyGraph(); + + // Build the module system dependencies (scripts that need to + // be included at the very beginning of the bundle) + any polifyll. + const modules = [defaults.moduleSystem] + .concat(polyfillModuleNames) + .map(polyfillModuleName => + dependencyGraph.createPolyfill({ + file: polyfillModuleName, + }), + ); + + const transformOptions = { + dev: bundleOptions.dev, + enableBabelRCLookup: options.enableBabelRCLookup, + hot: bundleOptions.hot, + projectRoot: options.projectRoots[0], + }; + + const out = await Promise.all( + modules.map(module => _createEdgeFromScript(module, transformOptions)), + ); + + out.unshift(_getPrelude({dev: bundleOptions.dev})); + + return out; +} + +function _getPrelude({dev}: {dev: boolean}): DependencyEdge { + const code = getPreludeCode({isDev: dev}); + const name = '__prelude__'; + + return { + dependencies: new Map(), + inverseDependencies: new Set(), + path: name, + output: { + code, + map: [], + source: code, + type: 'script', + }, + }; +} + +async function _createEdgeFromScript( + module: Module, + options: { + dev: boolean, + enableBabelRCLookup: boolean, + hot: boolean, + projectRoot: string, + }, +): Promise { + const result = await module.read({ + assetDataPlugins: [], + customTransformOptions: {}, + dev: options.dev, + enableBabelRCLookup: options.enableBabelRCLookup, + hot: options.hot, + inlineRequires: false, + minify: false, + platform: undefined, + projectRoot: options.projectRoot, + }); + + return { + dependencies: new Map(), + inverseDependencies: new Set(), + path: module.path, + output: { + code: result.code, + map: result.map, + source: result.source, + type: 'script', + }, + }; +} + +module.exports = getPrependedScripts; diff --git a/packages/metro/src/lib/mapGraph.js b/packages/metro/src/lib/mapGraph.js new file mode 100644 index 00000000..96a81a3c --- /dev/null +++ b/packages/metro/src/lib/mapGraph.js @@ -0,0 +1,40 @@ +/** + * 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'; + +import type {DependencyEdge} from '../DeltaBundler/traverseDependencies'; +import type {Graph} from '../DeltaBundler'; + +/** + * Generates a new Graph object, which has all the dependencies returned by the + * mapping function (similar to Array.prototype.map). + **/ +async function mapGraph( + graph: Graph, + mappingFn: DependencyEdge => Promise, +): Promise { + const dependencies = new Map( + await Promise.all( + Array.from(graph.dependencies.entries()).map(async ([path, module]) => { + const mutated = await mappingFn(module); + + return [path, mutated]; + }), + ), + ); + + return { + dependencies, + entryPoints: graph.entryPoints, + }; +} + +module.exports = mapGraph;