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
2 changes: 1 addition & 1 deletion packages/metro-file-map/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ export class HastePlugin implements HasteMap, FileMapPlugin<null, string | null>
getModuleNameByPath(mixedPath: Path): null | undefined | string;
getPackage(name: string, platform: null | undefined | string, _supportsNativePlatform?: null | undefined | boolean): null | undefined | Path;
getSerializableSnapshot(): null;
getWorker(): FileMapPluginWorker;
getWorker(): null | undefined | FileMapPluginWorker;
initialize(opts: FileMapPluginInitOptions<null, string | null>): Promise<void>;
readonly name: 'haste';
onChanged(delta: ReadonlyFileSystemChanges<null | undefined | string>): void;
Expand Down
11 changes: 7 additions & 4 deletions packages/metro-file-map/src/__tests__/worker-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {HastePlugin} from '..';
import {Worker} from '../worker';
import * as path from 'node:path';
import * as vm from 'node:vm';
import nullthrows from 'nullthrows';

jest.mock('node:fs', () => {
const path = require('node:path');
Expand Down Expand Up @@ -98,10 +99,12 @@ function workerWithHaste(
) {
return new Worker({
plugins: [
new HastePlugin({
...defaultHasteConfig,
...hasteOverrides,
}).getWorker().worker,
nullthrows(
new HastePlugin({
...defaultHasteConfig,
...hasteOverrides,
}).getWorker(),
).worker,
],
}).processFile({
...message,
Expand Down
2 changes: 1 addition & 1 deletion packages/metro-file-map/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ export type {
// This should be bumped whenever a code change to `metro-file-map` itself
// would cause a change to the cache data structure and/or content (for a given
// filesystem state and build parameters).
const CACHE_BREAKER = '15';
const CACHE_BREAKER = '16';

const CHANGE_INTERVAL = 30;

Expand Down
22 changes: 16 additions & 6 deletions packages/metro-file-map/src/lib/FileProcessor.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ const NODE_MODULES_SEP = 'node_modules' + sep;
const MAX_FILES_PER_WORKER = 100;

export class FileProcessor {
// Whether any plugin's worker runs on a batch, rather than on demand.
#hasEagerPluginWorker: boolean;
#maxFilesPerWorker: number;
#maxWorkers: number;
#perfLogger: ?PerfLogger;
Expand All @@ -72,6 +74,9 @@ export class FileProcessor {
this.#maxFilesPerWorker = opts.maxFilesPerWorker ?? MAX_FILES_PER_WORKER;
this.#maxWorkers = opts.maxWorkers;
this.#pluginWorkers = opts.pluginWorkers ?? [];
this.#hasEagerPluginWorker = this.#pluginWorkers.some(
plugin => plugin.lazy !== true,
);
this.#inBandWorker = new Worker({
plugins: this.#pluginWorkers.map(plugin => plugin.worker),
});
Expand All @@ -90,6 +95,17 @@ export class FileProcessor {
}> {
const errors = [];

if (req.maybeReturnContent) {
throw new Error(
'Batch processing does not support returning file contents',
);
}

if (!req.computeSha1 && !this.#hasEagerPluginWorker) {
// No file needs a worker.
return {errors};
}

const workerJobs = files
.map(([normalFilePath, fileMetadata]) => {
const maybeWorkerInput = this.#getWorkerInput(
Expand All @@ -110,12 +126,6 @@ export class FileProcessor {
);
const batchWorker = this.#getBatchWorker(numWorkers);

if (req.maybeReturnContent) {
throw new Error(
'Batch processing does not support returning file contents',
);
}

await Promise.all(
workerJobs.map(([workerInput, fileMetadata]) => {
return batchWorker
Expand Down
55 changes: 48 additions & 7 deletions packages/metro-file-map/src/lib/__tests__/FileProcessor-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import H from '../../constants';
import DependencyPlugin from '../../plugins/DependencyPlugin';
import HastePlugin from '../../plugins/HastePlugin';
import path from 'node:path';
import nullthrows from 'nullthrows';

const MockJestWorker = jest.fn().mockImplementation(() => ({
processFile: async () => ({}),
Expand Down Expand Up @@ -276,6 +277,43 @@ describe('processBatch', () => {
expect(mockWorkerFn).not.toHaveBeenCalled();
});

test.each([
['no plugins', [] as ReadonlyArray<FileMapPluginWorker>],
[
'only lazy plugins',
[
{
worker: {modulePath: 'mock-lazy-plugin', setupArgs: {}},
filter: () => true,
lazy: true,
},
],
],
])(
'reads no file without a SHA-1 to compute and with %s',
async (_, pluginWorkers) => {
const readKeys: Array<string | symbol> = [];
const fileMetadata = new Proxy<FileMetadata>(
[123, 234, 0, null, 0, null],
{
get(target, key, receiver) {
readKeys.push(key);
return Reflect.get(target, key, receiver);
},
},
);
const processor = new FileProcessor({...defaultOptions, pluginWorkers});

await processor.processBatch([[p('src/index.js'), fileMetadata]], {
computeSha1: false,
maybeReturnContent: false,
});

expect(readKeys).toEqual([]);
expect(mockWorkerFn).not.toHaveBeenCalled();
},
);

describe('node_modules files', () => {
const nodeModulesFiles = (): Array<[string, FileMetadata]> => [
[p('node_modules/pkg/index.js'), [123, 234, 0, null, 0, null]],
Expand Down Expand Up @@ -335,13 +373,16 @@ describe('processBatch', () => {

test('built-in plugins only run on them when a SHA-1 is needed', async () => {
const pluginWorkers = [
new HastePlugin({
enableHastePackages: true,
failValidationOnConflicts: false,
hasteImplModulePath: require.resolve('../../__tests__/haste_impl.js'),
platforms: new Set(),
rootDir: defaultOptions.rootDir,
}).getWorker(),
nullthrows(
new HastePlugin({
enableHastePackages: true,
failValidationOnConflicts: false,
hasteImplModulePath:
require.resolve('../../__tests__/haste_impl.js'),
platforms: new Set(),
rootDir: defaultOptions.rootDir,
}).getWorker(),
),
new DependencyPlugin({
computeDependencies: true,
dependencyExtractor: null,
Expand Down
30 changes: 21 additions & 9 deletions packages/metro-file-map/src/plugins/HastePlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,14 +108,16 @@ export default class HastePlugin
await new Promise(setImmediate);
}
}
this.#getModuleNameByPath = mixedPath => {
const result = opts.getOrComputeFileData(mixedPath);
return result.exists &&
result.type === 'f' &&
typeof result.data === 'string'
? result.data
: null;
};
this.#getModuleNameByPath = this.#hasHasteNames()
? mixedPath => {
const result = opts.getOrComputeFileData(mixedPath);
return result.exists &&
result.type === 'f' &&
typeof result.data === 'string'
? result.data
: null;
}
: () => null;
this.#perfLogger?.point('constructHasteMap_end');
this.#perfLogger?.annotate({int: {hasteFiles}});
}
Expand Down Expand Up @@ -491,7 +493,17 @@ export default class HastePlugin
]);
}

getWorker(): FileMapPluginWorker {
// Files have Haste names only through `hasteImplModulePath` or, for
// `package.json`, Haste packages.
#hasHasteNames(): boolean {
return this.#hasteImplModulePath != null || this.#enableHastePackages;
}

getWorker(): ?FileMapPluginWorker {
if (!this.#hasHasteNames()) {
// Nothing to compute, so no file needs reading for this plugin.
return null;
}
return {
worker: {
modulePath: require.resolve('./haste/worker.js'),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ describe.each([['win32'], ['posix']])('HastePlugin on %s', platform => {
let HasteMap: Class<HasteMapType>;
let DuplicateHasteCandidatesError;

const hasteImplModulePath =
require.resolve('../../../__tests__/haste_impl.js');

const opts = {
enableHastePackages: false,
hasteImplModulePath: null,
Expand Down Expand Up @@ -95,6 +98,29 @@ describe.each([['win32'], ['posix']])('HastePlugin on %s', platform => {
expect(hasteMap.getModule('NameForFoo')).toEqual(p('/root/project/Foo.js'));
});

test.each([
['a hasteImplModulePath', {hasteImplModulePath}],
['Haste packages', {enableHastePackages: true}],
])('has a worker with %s', (_, overrides) => {
expect(new HasteMap({...opts, ...overrides}).getWorker()).not.toBeNull();
});

test('has no worker or module names without a hasteImplModulePath or Haste packages', async () => {
const hasteMap = new HasteMap(opts);
expect(hasteMap.getWorker()).toBeNull();
const getOrComputeFileData = jest.fn();
await hasteMap.initialize({
files: {
fileIterator: jest.fn().mockReturnValue([]),
lookup: jest.fn(),
},
getOrComputeFileData,
pluginState: null,
});
expect(hasteMap.getModuleNameByPath(p('/root/Foo.js'))).toBeNull();
expect(getOrComputeFileData).not.toHaveBeenCalled();
});

describe('remove file', () => {
let hasteMap: HasteMapType;
let removeFile: (path: CanonicalPath, name: ?string) => void;
Expand Down Expand Up @@ -184,7 +210,7 @@ describe.each([['win32'], ['posix']])('HastePlugin on %s', platform => {
let getOrComputeFileData;

beforeEach(async () => {
hasteMap = new HasteMap(opts);
hasteMap = new HasteMap({...opts, hasteImplModulePath});
getOrComputeFileData = jest.fn().mockReturnValue(null);

await hasteMap.initialize({
Expand Down
Loading