/** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * * @flow strict-local * @format */ 'use strict'; const addParamsToDefineCall = require('../../lib/addParamsToDefineCall'); const generate = require('../worker/generate'); const mergeSourceMaps = require('../worker/mergeSourceMaps'); const reverseDependencyMapReferences = require('./reverse-dependency-map-references'); const virtualModule = require('../module').virtual; const {transformSync} = require('@babel/core'); import type {IdsForPathFn, Module} from '../types.flow'; import type {MetroSourceMap} from 'metro-source-map'; // 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. function addModuleIdsToModuleWrapper( module: Module, idForPath: ({path: string}) => number, ): string { const {dependencies, file} = module; const {code} = file; // 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); const paramsToAdd = [fileId]; if (dependencies.length) { paramsToAdd.push(dependencies.map(idForPath)); } return addParamsToDefineCall(code, ...paramsToAdd); } exports.addModuleIdsToModuleWrapper = addModuleIdsToModuleWrapper; function inlineModuleIds( module: Module, idForPath: ({path: string}) => number, ): { moduleCode: string, moduleMap: ?MetroSourceMap, } { const {dependencies, file} = module; const {code, map, path} = file; // 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); const dependencyIds = dependencies.map(idForPath); const {ast} = transformSync(code, { ast: true, babelrc: false, code: false, configFile: false, plugins: [[reverseDependencyMapReferences, {dependencyIds}]], }); const {code: generatedCode, map: generatedMap} = generate( ast, path, '', true, ); return { moduleCode: addParamsToDefineCall(generatedCode, fileId), moduleMap: map && generatedMap && mergeSourceMaps(path, map, generatedMap), }; } exports.inlineModuleIds = inlineModuleIds; type IdForPathFn = ({path: string}) => number; // Adds the module ids to a file if the file is a module. If it's not (e.g. a // script) it just keeps it as-is. function getModuleCodeAndMap( module: Module, idForPath: IdForPathFn, options: $ReadOnly<{enableIDInlining: boolean}>, ) { const {file} = module; if (file.type !== 'module') { return {moduleCode: file.code, moduleMap: file.map}; } if (!options.enableIDInlining) { return { moduleCode: addModuleIdsToModuleWrapper(module, idForPath), moduleMap: file.map, }; } return inlineModuleIds(module, idForPath); } exports.getModuleCodeAndMap = getModuleCodeAndMap; // Concatenates many iterables, by calling them sequentially. exports.concat = function* concat<T>( ...iterables: Array<Iterable<T>> ): Iterable<T> { 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<Module>, idForPath: IdForPathFn, getRunModuleStatement: (id: number | string) => string, ): Iterable<Module> { for (const module of modules) { const id = idForPath(module.file); yield virtualModule( getRunModuleStatement(id), `/<generated>/require-${id}.js`, ); } }; // Divides the modules into two types: the ones that are loaded at startup, and // the ones loaded deferredly (lazy loaded). exports.partition = ( modules: Iterable<Module>, preloadedModules: Set<string>, ): Array<Array<Module>> => { const startup = []; const deferred = []; for (const module of modules) { (preloadedModules.has(module.file.path) ? startup : deferred).push(module); } return [startup, deferred]; }; // Transforms a new Module object into an old one, so that it can be passed // around code. exports.toModuleTransport = (module: Module, idsForPath: IdsForPathFn) => { const {dependencies, file} = module; const {moduleCode, moduleMap} = getModuleCodeAndMap( module, x => idsForPath(x).moduleId, {enableIDInlining: true}, ); return { code: moduleCode, dependencies, // ID is required but we provide an invalid one for "script"s. id: file.type === 'module' ? idsForPath(file).localId : -1, map: moduleMap, name: file.path, sourcePath: file.path, }; };