Skip to content
Merged
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 .flowconfig
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,4 @@ untyped-import
untyped-type-import

[version]
^0.309.0
^0.311.0
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-relay": "^1.8.3",
"flow-api-translator": "0.35.0",
"flow-bin": "^0.309.0",
"flow-bin": "^0.311.0",
"hermes-eslint": "0.35.0",
"invariant": "^2.2.4",
"istanbul-api": "3.0.0",
Expand All @@ -42,7 +42,7 @@
"metro-babel-register": "*",
"micromatch": "^4.0.4",
"prettier": "3.6.2",
"prettier-plugin-hermes-parser": "0.34.1",
"prettier-plugin-hermes-parser": "0.35.0",
"progress": "^2.0.0",
"signedsource": "^2.0.0",
"tinyglobby": "^0.2.15",
Expand Down
1 change: 0 additions & 1 deletion packages/metro-file-map/src/__tests__/index-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,6 @@ describe('FileMap', () => {
const dependencyPlugin = new DependencyPlugin({
dependencyExtractor: dependencyOverrides.dependencyExtractor ?? null,
computeDependencies: true,
rootDir: defaultConfig.rootDir,
});
const hasteMap = new (require('../plugins/HastePlugin').default)({
...defaultHasteConfig,
Expand Down
107 changes: 22 additions & 85 deletions packages/metro-file-map/src/plugins/DependencyPlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,121 +9,58 @@
* @oncall react_native
*/

import type {
FileMapPlugin,
FileMapPluginInitOptions,
FileMapPluginWorker,
Path,
} from '../flow-types';
import type {Path} from '../flow-types';

import excludedExtensions from '../workerExclusionList';
import FileDataPlugin from './FileDataPlugin';

export type DependencyPluginOptions = Readonly<{
/** Path to custom dependency extractor module */
dependencyExtractor: ?string,
/** Whether to compute dependencies (performance optimization) */
computeDependencies: boolean,
rootDir: Path,
}>;

export default class DependencyPlugin
implements FileMapPlugin<null, ReadonlyArray<string> | null>
{
+name: 'dependencies' = 'dependencies';

#dependencyExtractor: ?string;
#computeDependencies: boolean;
#getDependencies: Path => ?ReadonlyArray<string>;
#rootDir: Path;

export default class DependencyPlugin extends FileDataPlugin<ReadonlyArray<string> | null> {
constructor(options: DependencyPluginOptions) {
this.#dependencyExtractor = options.dependencyExtractor;
this.#computeDependencies = options.computeDependencies;
this.#rootDir = options.rootDir;
}

async initialize(
initOptions: FileMapPluginInitOptions<null, ReadonlyArray<string> | null>,
): Promise<void> {
const {files} = initOptions;
// Create closure to access dependencies from file metadata plugin data
this.#getDependencies = (mixedPath: Path) => {
const result = files.lookup(mixedPath);
if (result.exists && result.type === 'f') {
// Backwards compatibility: distinguish an extant file that we've not
// run the worker on (probably because it fails the extension filter)
// from a missing file. Non-source files are expected to have empty
// dependencies.
return result.pluginData ?? [];
}
return null;
};
}

getSerializableSnapshot(): null {
// Dependencies stored in plugin data, no separate serialization needed
return null;
}
const {dependencyExtractor, computeDependencies} = options;

onChanged(): void {
// No-op: Worker already populated plugin data
// Plugin data is write-only from worker
}

assertValid(): void {
// No validation needed
}

getCacheKey(): string {
if (this.#dependencyExtractor != null) {
// Dynamic require to get extractor's cache key
let cacheKey: string;
if (dependencyExtractor != null) {
// $FlowFixMe[unsupported-syntax] - dynamic require
const extractor = require(this.#dependencyExtractor);
return JSON.stringify({
extractorKey: extractor.getCacheKey?.() ?? null,
extractorPath: this.#dependencyExtractor,
});
const extractor = require(dependencyExtractor);
cacheKey = extractor.getCacheKey?.() ?? dependencyExtractor;
} else {
cacheKey = 'default-dependency-extractor';
}
return 'default-dependency-extractor';
}

getWorker(): FileMapPluginWorker {
const excludedExtensions = require('../workerExclusionList');

return {
super({
name: 'dependencies',
cacheKey,
worker: {
modulePath: require.resolve('./dependencies/worker.js'),
setupArgs: {
dependencyExtractor: this.#dependencyExtractor ?? null,
dependencyExtractor: dependencyExtractor ?? null,
},
},
filter: ({normalPath, isNodeModules}) => {
// Respect computeDependencies flag
if (!this.#computeDependencies) {
if (!computeDependencies) {
return false;
}

// Never process node_modules
if (isNodeModules) {
return false;
}

// Skip excluded extensions
const ext = normalPath.substr(normalPath.lastIndexOf('.'));
return !excludedExtensions.has(ext);
},
};
});
}

/**
* Get the list of dependencies for a given file.
* @param mixedPath Absolute or project-relative path to the file
* @returns Array of dependency module names, or null if the file doesn't exist
*/
getDependencies(mixedPath: Path): ?ReadonlyArray<string> {
if (this.#getDependencies == null) {
throw new Error(
'DependencyPlugin has not been initialized before getDependencies',
);
const result = this.getFileSystem().lookup(mixedPath);
if (result.exists && result.type === 'f') {
return result.pluginData ?? [];
}
return this.#getDependencies(mixedPath);
return null;
}
}
76 changes: 76 additions & 0 deletions packages/metro-file-map/src/plugins/FileDataPlugin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/**
* 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 {
FileMapPlugin,
FileMapPluginInitOptions,
FileMapPluginWorker,
ReadonlyFileSystemChanges,
V8Serializable,
} from '../flow-types';

export type FileDataPluginOptions = Readonly<{
...FileMapPluginWorker,
name: string,
cacheKey: string,
}>;

/**
* Base class for FileMap plugins that store per-file data via a worker and
* have no separate serializable state. Provides default no-op implementations
* of lifecycle methods that subclasses can override as needed.
*/
export default class FileDataPlugin<
-PerFileData extends void | V8Serializable = void | V8Serializable,
> implements FileMapPlugin<null, PerFileData>
{
+name: string;

#worker: FileMapPluginWorker;
#cacheKey: string;
#files: ?FileMapPluginInitOptions<null, PerFileData>['files'];

constructor({name, worker, filter, cacheKey}: FileDataPluginOptions) {
this.name = name;
this.#worker = {worker, filter};
this.#cacheKey = cacheKey;
}

async initialize(
initOptions: FileMapPluginInitOptions<null, PerFileData>,
): Promise<void> {
this.#files = initOptions.files;
}

getFileSystem(): FileMapPluginInitOptions<null, PerFileData>['files'] {
const files = this.#files;
if (files == null) {
throw new Error(`${this.name} plugin has not been initialized`);
}
return files;
}

onChanged(_changes: ReadonlyFileSystemChanges<?PerFileData>): void {}

assertValid(): void {}

getSerializableSnapshot(): null {
return null;
}

getCacheKey(): string {
return this.#cacheKey;
}

getWorker(): FileMapPluginWorker {
return this.#worker;
}
}
Loading
Loading