/**
* 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,
};
};