Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions packages/metro-babel-transformer/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,20 @@ export type BabelFileFunctionMapMetadata = $ReadOnly<{

export type BabelFileImportLocsMetadata = $ReadOnlySet<string>;

export type VirtualModule = $ReadOnly<{
absolutePath: string,
code: string,
type: 'sourceFile',
}>;

export type VirtualModulesRawMap = Map<string, VirtualModule>;

export type MetroBabelFileMetadata = {
...BabelFileMetadata,
metro?: ?{
functionMap?: ?BabelFileFunctionMapMetadata,
unstable_importDeclarationLocs?: ?BabelFileImportLocsMetadata,
virtualModulesRawMap?: VirtualModulesRawMap,
...
},
...
Expand Down
13 changes: 13 additions & 0 deletions packages/metro-resolver/src/resolve.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,19 @@ export default function resolve(
);
}

if (
context.dependency?.data.isVirtualModule &&
context.dependency?.data.absolutePath &&
context.dependency?.data.type
) {
// $FlowFixMe[incompatible-type] fix it
return {
type: context.dependency.data.type,
filePath: context.dependency.data.absolutePath,
isVirtualModule: true,
};
}

if (isRelativeImport(moduleName) || path.isAbsolute(moduleName)) {
const result = resolveModulePath(context, moduleName, platform);
if (result.type === 'failed') {
Expand Down
12 changes: 12 additions & 0 deletions packages/metro-transform-worker/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import {
toSegmentTuple,
} from 'metro-source-map';
import metroTransformPlugins from 'metro-transform-plugins';
import {VirtualModules} from 'metro/private/DeltaBundler/VirtualModules';
import collectDependencies from 'metro/private/ModuleGraph/worker/collectDependencies';
import generateImportNames from 'metro/private/ModuleGraph/worker/generateImportNames';
import {
Expand Down Expand Up @@ -151,6 +152,7 @@ type JSFile = $ReadOnly<{
type: JSFileType,
functionMap: FBSourceFunctionMap | null,
unstable_importDeclarationLocs?: ?$ReadOnlySet<string>,
virtualModules?: ?VirtualModules,
}>;

type JSONFile = {
Expand All @@ -177,6 +179,7 @@ export type JsOutput = $ReadOnly<{
type TransformResponse = $ReadOnly<{
dependencies: $ReadOnlyArray<TransformResultDependency>,
output: $ReadOnlyArray<JsOutput>,
virtualModules?: ?VirtualModules,
}>;

function getDynamicDepsBehavior(
Expand Down Expand Up @@ -405,6 +408,7 @@ async function transformJS(
? (loc: BabelSourceLocation) =>
importDeclarationLocs.has(locToKey(loc))
: null,
virtualModules: file.virtualModules,
};
({ast, dependencies, dependencyMapName} = collectDependencies(ast, opts));
} catch (error) {
Expand Down Expand Up @@ -501,9 +505,12 @@ async function transformJS(
},
];

const {virtualModules} = file;

return {
dependencies,
output,
virtualModules,
};
}

Expand Down Expand Up @@ -563,6 +570,11 @@ async function transformJSWithBabel(
null,
unstable_importDeclarationLocs:
transformResult.metadata?.metro?.unstable_importDeclarationLocs,
virtualModules: new VirtualModules(
// TODO: use raw map here
// $FlowFixMe[prop-missing] we need to update the type of metadata.metro.virtualModules
transformResult.metadata?.metro?.virtualModules,
),
};

return await transformJS(jsFile, context);
Expand Down
9 changes: 7 additions & 2 deletions packages/metro/src/Bundler.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
*/

import type {TransformResultWithSource} from './DeltaBundler';
import type {VirtualModules} from './DeltaBundler/VirtualModules';
import type {TransformOptions} from './DeltaBundler/Worker';
import type EventEmitter from 'events';
import type {ConfigT} from 'metro-config';
Expand All @@ -35,8 +36,10 @@ export default class Bundler {
.then(() => {
config.reporter.update({type: 'transformer_load_started'});
this._transformer = new Transformer(config, {
getOrComputeSha1: filePath =>
this._depGraph.getOrComputeSha1(filePath),
getOrComputeSha1: (
filePath: string,
virtualModule?: ?VirtualModules,
) => this._depGraph.getOrComputeSha1(filePath, virtualModule),
});
config.reporter.update({type: 'transformer_load_done'});
})
Expand Down Expand Up @@ -71,6 +74,7 @@ export default class Bundler {
transformOptions: TransformOptions,
/** Optionally provide the file contents, this can be used to provide virtual contents for a file. */
fileBuffer?: Buffer,
virtualModules?: ?VirtualModules,
): Promise<TransformResultWithSource<>> {
// We need to be sure that the DependencyGraph has been initialized.
// TODO: Remove this ugly hack!
Expand All @@ -80,6 +84,7 @@ export default class Bundler {
filePath,
transformOptions,
fileBuffer,
virtualModules,
);
}

Expand Down
10 changes: 10 additions & 0 deletions packages/metro/src/DeltaBundler.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import type {
import type EventEmitter from 'events';

import DeltaCalculator from './DeltaBundler/DeltaCalculator';
import {VirtualModules} from './DeltaBundler/VirtualModules';

export type {
DeltaResult,
Expand All @@ -43,9 +44,11 @@ export type {
export default class DeltaBundler<T = MixedOutput> {
_changeEventSource: EventEmitter;
_deltaCalculators: Map<Graph<T>, DeltaCalculator<T>> = new Map();
_virtualModules: VirtualModules;

constructor(changeEventSource: EventEmitter) {
this._changeEventSource = changeEventSource;
this._virtualModules = new VirtualModules();
}

end(): void {
Expand All @@ -68,6 +71,13 @@ export default class DeltaBundler<T = MixedOutput> {
await deltaCalculator.getDelta({reset: true, shallow: options.shallow});
const graph = deltaCalculator.getGraph();

this._virtualModules.addRawMap(graph.virtualModules.toRawMap());

graph.dependencies.forEach((value, key) => {
// $FlowFixMe[cannot-write] We need to mark the module as virtual
value.isVirtualModule = graph.virtualModules.get(key) != null;
});

deltaCalculator.end();
return graph.dependencies;
}
Expand Down
39 changes: 25 additions & 14 deletions packages/metro/src/DeltaBundler/Graph.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ import {fileMatchesContext} from '../lib/contextModule';
import CountingSet from '../lib/CountingSet';
import {isResolvedDependency} from '../lib/isResolvedDependency';
import {buildSubgraph} from './buildSubgraph';
import {VirtualModules} from './VirtualModules';
import invariant from 'invariant';
import nullthrows from 'nullthrows';

Expand Down Expand Up @@ -133,6 +134,7 @@ export class Graph<T = MixedOutput> {
+entryPoints: $ReadOnlySet<string>;
+transformOptions: TransformInputOptions;
+dependencies: Dependencies<T> = new Map();
+virtualModules: VirtualModules = new VirtualModules();
+#importBundleNodes: Map<
string,
$ReadOnly<{
Expand Down Expand Up @@ -348,21 +350,30 @@ export class Graph<T = MixedOutput> {
options: InternalOptions<T>,
moduleFilter?: (path: string) => boolean,
): Promise<Delta<T>> {
const subGraph = await buildSubgraph(pathsToVisit, this.#resolvedContexts, {
resolve: options.resolve,
transform: async (absolutePath, requireContext) => {
options.onDependencyAdd();
const result = await options.transform(absolutePath, requireContext);
options.onDependencyAdded();
return result;
},
shouldTraverse: (dependency: ResolvedDependency) => {
if (options.shallow || isWeakOrLazy(dependency, options)) {
return false;
}
return moduleFilter == null || moduleFilter(dependency.absolutePath);
const subGraph = await buildSubgraph(
pathsToVisit,
this.#resolvedContexts,
{
resolve: options.resolve,
transform: async (absolutePath, requireContext, virtualModules) => {
options.onDependencyAdd();
const result = await options.transform(
absolutePath,
requireContext,
virtualModules,
);
options.onDependencyAdded();
return result;
},
shouldTraverse: (dependency: ResolvedDependency) => {
if (options.shallow || isWeakOrLazy(dependency, options)) {
return false;
}
return moduleFilter == null || moduleFilter(dependency.absolutePath);
},
},
});
this.virtualModules,
);

return {
added: new Set(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,11 @@ export default function getSourceMapInfo(
...getJsOutput(module).data,
isIgnored: options.shouldAddToIgnoreList(module),
path: options?.getSourceUrl?.(module) ?? module.path,
source: options.excludeSource ? '' : getModuleSource(module),
source:
// TODO: Figure out sourceMaps for virtual modules.
options.excludeSource || module.isVirtualModule === true
? ''
: getModuleSource(module),
};
}

Expand Down
12 changes: 8 additions & 4 deletions packages/metro/src/DeltaBundler/Transformer.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
*/

import type {TransformResult, TransformResultWithSource} from '../DeltaBundler';
import type {VirtualModules} from './VirtualModules';
import type {TransformerConfig, TransformOptions} from './Worker';
import type {ConfigT} from 'metro-config';

Expand All @@ -25,9 +26,10 @@ import path from 'path';
// eslint-disable-next-line import/no-commonjs
const debug = require('debug')('Metro:Transformer');

type GetOrComputeSha1Fn = string => Promise<
$ReadOnly<{content?: Buffer, sha1: string}>,
>;
type GetOrComputeSha1Fn = (
path: string,
virtualModules?: ?VirtualModules,
) => Promise<$ReadOnly<{content?: Buffer, sha1: string}>>;

export default class Transformer {
_config: ConfigT;
Expand Down Expand Up @@ -80,6 +82,7 @@ export default class Transformer {
filePath: string,
transformerOptions: TransformOptions,
fileBuffer?: Buffer,
virtualModules?: ?VirtualModules,
): Promise<TransformResultWithSource<>> {
const cache = this._cache;

Expand Down Expand Up @@ -139,7 +142,7 @@ export default class Transformer {
sha1 = crypto.createHash('sha1').update(fileBuffer).digest('hex');
content = fileBuffer;
} else {
const result = await this._getSha1(filePath);
const result = await this._getSha1(filePath, virtualModules);
sha1 = result.sha1;
if (result.content) {
content = result.content;
Expand Down Expand Up @@ -169,6 +172,7 @@ export default class Transformer {
localPath,
transformerOptions,
content,
virtualModules,
);

// Only re-compute the full key if the SHA-1 changed. This is because
Expand Down
48 changes: 48 additions & 0 deletions packages/metro/src/DeltaBundler/VirtualModules.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* Copyright (c) Meta Platforms, Inc. and 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
* @oncall react_native
*/

import type {VirtualModule, VirtualModulesRawMap} from './types';

export class VirtualModules {
#map_: VirtualModulesRawMap;

constructor(initialMap?: ?VirtualModulesRawMap) {
this.#map_ = new Map(initialMap ?? []);
}

toRawMap(): VirtualModulesRawMap {
return this.#map_;
}

addRawMap(other: ?VirtualModulesRawMap) {
other?.forEach((value, key) => this.#map_.set(key, value));
}

get(mixedPath: string): ?VirtualModule {
if (this.#map_.has(mixedPath)) {
return this.#map_.get(mixedPath);
}

const key = this.#map_
.keys()
.find(relativePath => mixedPath.endsWith(relativePath));

if (key == null) {
return null;
}

return this.#map_.get(key);
}

set(relativePath: string, vModule: VirtualModule): void {
this.#map_.set(relativePath, vModule);
}
}
Loading