mirror of https://github.com/status-im/metro.git
Create integration types and code to use module resolution logic from node-haste
Summary: This creates flow types for the module resolution code in node-haste, and implementations of types used by that code. The idea is to use that code until we can replace it with something leaner, and provide objects that don’t touch the file system. These objects will be initialized with the static data provided by ModuleGraph/worker. Reviewed By: cpojer Differential Revision: D4037372 fbshipit-source-id: 2698dbb630f4122fc1d839d06e414d0963bd6ff2
This commit is contained in:
parent
977e7209ac
commit
c16465a582
|
@ -0,0 +1,68 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the BSD-style license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
*
|
||||||
|
* @flow
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const {dirname, parse} = require('path');
|
||||||
|
|
||||||
|
module.exports = class FastFS {
|
||||||
|
directories: Set<string>;
|
||||||
|
directoryEntries: Map<string, Array<string>>;
|
||||||
|
files: Set<string>;
|
||||||
|
|
||||||
|
constructor(files: Array<string>) {
|
||||||
|
this.directories = buildDirectorySet(files);
|
||||||
|
this.directoryEntries = buildDirectoryEntries(files.map(parse));
|
||||||
|
this.files = new Set(files);
|
||||||
|
}
|
||||||
|
|
||||||
|
dirExists(path: string) {
|
||||||
|
return this.directories.has(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
fileExists(path: string) {
|
||||||
|
return this.files.has(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
getAllFiles() {
|
||||||
|
return Array.from(this.files.keys());
|
||||||
|
}
|
||||||
|
|
||||||
|
matches(directory: string, pattern: RegExp) {
|
||||||
|
const entries = this.directoryEntries.get(directory);
|
||||||
|
return entries ? entries.filter(pattern.test, pattern) : [];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function buildDirectorySet(files) {
|
||||||
|
const directories = new Set();
|
||||||
|
files.forEach(path => {
|
||||||
|
let {dir, root} = parse(path);
|
||||||
|
while (dir !== '.' && dir !== root && !directories.has(dir)) {
|
||||||
|
directories.add(path);
|
||||||
|
dir = dirname(path);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return directories;
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildDirectoryEntries(files) {
|
||||||
|
const directoryEntries = new Map();
|
||||||
|
files.forEach(({base, dir}) => {
|
||||||
|
const entries = directoryEntries.get(dir);
|
||||||
|
if (entries) {
|
||||||
|
entries.push(base);
|
||||||
|
} else {
|
||||||
|
directoryEntries.set(dir, [base]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return directoryEntries;
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the BSD-style license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
*
|
||||||
|
* @flow
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import type {TransformedFile} from '../types.flow';
|
||||||
|
|
||||||
|
module.exports = class Module {
|
||||||
|
hasteID: Promise<?string>;
|
||||||
|
name: Promise<string>;
|
||||||
|
path: string;
|
||||||
|
type: 'Module';
|
||||||
|
|
||||||
|
constructor(path: string, info: Promise<TransformedFile>) {
|
||||||
|
this.hasteID = info.then(({hasteID}) => hasteID);
|
||||||
|
this.name = this.hasteID.then(name => name || getName(path));
|
||||||
|
this.path = path;
|
||||||
|
this.type = 'Module';
|
||||||
|
}
|
||||||
|
|
||||||
|
getName() {
|
||||||
|
return this.name;
|
||||||
|
}
|
||||||
|
|
||||||
|
isHaste() {
|
||||||
|
return this.hasteID.then(Boolean);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function getName(path) {
|
||||||
|
return path.replace(/^.*[\/\\]node_modules[\///]/, '');
|
||||||
|
}
|
|
@ -0,0 +1,57 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the BSD-style license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
*
|
||||||
|
* @flow
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const Module = require('./Module');
|
||||||
|
const Package = require('./Package');
|
||||||
|
|
||||||
|
import type {PackageData, TransformedFile} from '../types.flow';
|
||||||
|
|
||||||
|
type GetFn<T> = (path: string) => Promise<T>;
|
||||||
|
|
||||||
|
module.exports = class ModuleCache {
|
||||||
|
getPackageData: GetFn<PackageData>;
|
||||||
|
getTransformedFile: GetFn<TransformedFile>;
|
||||||
|
modules: Map<string, Module>;
|
||||||
|
packages: Map<string, Package>;
|
||||||
|
|
||||||
|
constructor(getTransformedFile: GetFn<TransformedFile>) {
|
||||||
|
this.getTransformedFile = getTransformedFile;
|
||||||
|
this.getPackageData = path => getTransformedFile(path).then(
|
||||||
|
f => f.package || Promise.reject(new Error(`"${path}" does not exist`))
|
||||||
|
);
|
||||||
|
this.modules = new Map();
|
||||||
|
this.packages = new Map();
|
||||||
|
}
|
||||||
|
|
||||||
|
getAssetModule(path: string) {
|
||||||
|
return this.getModule(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
getModule(path: string) {
|
||||||
|
let m = this.modules.get(path);
|
||||||
|
if (!m) {
|
||||||
|
m = new Module(path, this.getTransformedFile(path));
|
||||||
|
this.modules.set(path, m);
|
||||||
|
}
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
getPackage(path: string) {
|
||||||
|
let p = this.packages.get(path);
|
||||||
|
if (!p) {
|
||||||
|
p = new Package(path, this.getPackageData(path));
|
||||||
|
this.packages.set(path, p);
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,132 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the BSD-style license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
*
|
||||||
|
* @flow
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
import type {PackageData} from '../types.flow';
|
||||||
|
|
||||||
|
module.exports = class Package {
|
||||||
|
data: Promise<PackageData>;
|
||||||
|
root: string;
|
||||||
|
type: 'Package';
|
||||||
|
|
||||||
|
constructor(packagePath: string, data: Promise<PackageData>) {
|
||||||
|
this.data = data;
|
||||||
|
this.root = path.dirname(packagePath);
|
||||||
|
this.type = 'Package';
|
||||||
|
}
|
||||||
|
|
||||||
|
getMain() {
|
||||||
|
// Copied from node-haste/Package.js
|
||||||
|
return this.data.then(data => {
|
||||||
|
const replacements = getReplacements(data);
|
||||||
|
if (typeof replacements === 'string') {
|
||||||
|
return path.join(this.root, replacements);
|
||||||
|
}
|
||||||
|
|
||||||
|
let main = getMain(data);
|
||||||
|
|
||||||
|
if (replacements && typeof replacements === 'object') {
|
||||||
|
main = replacements[main] ||
|
||||||
|
replacements[main + '.js'] ||
|
||||||
|
replacements[main + '.json'] ||
|
||||||
|
replacements[main.replace(/(\.js|\.json)$/, '')] ||
|
||||||
|
main;
|
||||||
|
}
|
||||||
|
|
||||||
|
return path.join(this.root, main);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
getName() {
|
||||||
|
return this.data.then(p => p.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
redirectRequire(name: string) {
|
||||||
|
// Copied from node-haste/Package.js
|
||||||
|
return this.data.then(data => {
|
||||||
|
const replacements = getReplacements(data);
|
||||||
|
|
||||||
|
if (!replacements || typeof replacements !== 'object') {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!path.isAbsolute(name)) {
|
||||||
|
const replacement = replacements[name];
|
||||||
|
// support exclude with "someDependency": false
|
||||||
|
return replacement === false
|
||||||
|
? false
|
||||||
|
: replacement || name;
|
||||||
|
}
|
||||||
|
|
||||||
|
let relPath = './' + path.relative(this.root, name);
|
||||||
|
if (path.sep !== '/') {
|
||||||
|
relPath = relPath.replace(path.sep, '/');
|
||||||
|
}
|
||||||
|
|
||||||
|
let redirect = replacements[relPath];
|
||||||
|
|
||||||
|
// false is a valid value
|
||||||
|
if (redirect == null) {
|
||||||
|
redirect = replacements[relPath + '.js'];
|
||||||
|
if (redirect == null) {
|
||||||
|
redirect = replacements[relPath + '.json'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// support exclude with "./someFile": false
|
||||||
|
if (redirect === false) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (redirect) {
|
||||||
|
return path.join(
|
||||||
|
this.root,
|
||||||
|
redirect
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return name;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
function getMain(pkg) {
|
||||||
|
return pkg.main || 'index';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copied from node-haste/Package.js
|
||||||
|
function getReplacements(pkg) {
|
||||||
|
let rn = pkg['react-native'];
|
||||||
|
let browser = pkg.browser;
|
||||||
|
if (rn == null) {
|
||||||
|
return browser;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (browser == null) {
|
||||||
|
return rn;
|
||||||
|
}
|
||||||
|
|
||||||
|
const main = getMain(pkg);
|
||||||
|
if (typeof rn !== 'object') {
|
||||||
|
rn = { [main]: rn };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof browser !== 'object') {
|
||||||
|
browser = { [main]: browser };
|
||||||
|
}
|
||||||
|
|
||||||
|
// merge with "browser" as default,
|
||||||
|
// "react-native" as override
|
||||||
|
return { ...browser, ...rn };
|
||||||
|
}
|
|
@ -0,0 +1,105 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the BSD-style license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
*
|
||||||
|
* @flow
|
||||||
|
*/
|
||||||
|
|
||||||
|
type ModuleID = string;
|
||||||
|
export type Path = string;
|
||||||
|
type Platform = string;
|
||||||
|
type Platforms = Set<Platform>;
|
||||||
|
|
||||||
|
export type Extensions = Array<string>;
|
||||||
|
|
||||||
|
export type Module = {
|
||||||
|
path: Path,
|
||||||
|
type: 'Module',
|
||||||
|
getName(): Promise<ModuleID>,
|
||||||
|
isHaste(): Promise<boolean>,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type Package = {
|
||||||
|
type: 'Package',
|
||||||
|
root: Path,
|
||||||
|
getMain(): Promise<Path>,
|
||||||
|
getName(): Promise<ModuleID>,
|
||||||
|
redirectRequire(id: ModuleID): Promise<Path | false>,
|
||||||
|
};
|
||||||
|
|
||||||
|
// when changing this to `type`, the code does not typecheck any more
|
||||||
|
export interface ModuleCache {
|
||||||
|
getAssetModule(path: Path): Module,
|
||||||
|
getModule(path: Path): Module,
|
||||||
|
getPackage(path: Path): Package,
|
||||||
|
}
|
||||||
|
|
||||||
|
export type FastFS = {
|
||||||
|
dirExists(path: Path): boolean,
|
||||||
|
fileExists(path: Path): boolean,
|
||||||
|
getAllFiles(): Array<Path>,
|
||||||
|
matches(directory: Path, pattern: RegExp): Array<Path>,
|
||||||
|
};
|
||||||
|
|
||||||
|
type HelpersOptions = {|
|
||||||
|
assetExts: Extensions,
|
||||||
|
providesModuleNodeModules: Array<string>,
|
||||||
|
|};
|
||||||
|
|
||||||
|
declare class Helpers {
|
||||||
|
// node-haste/DependencyGraph/DependencyGraphHelpers.js
|
||||||
|
constructor(options: HelpersOptions): void,
|
||||||
|
}
|
||||||
|
export type HelpersT = Helpers;
|
||||||
|
|
||||||
|
type DeprecatedAssetMapOptions = {|
|
||||||
|
assetExts: Extensions,
|
||||||
|
files: Array<Path>,
|
||||||
|
helpers: Helpers,
|
||||||
|
platforms: Platforms,
|
||||||
|
|};
|
||||||
|
|
||||||
|
declare class DeprecatedAssetMap {
|
||||||
|
// node-haste/DependencyGraph/DeprecatedAssetMap.js
|
||||||
|
constructor(options: DeprecatedAssetMapOptions): void,
|
||||||
|
}
|
||||||
|
export type DeprecatedAssetMapT = DeprecatedAssetMap;
|
||||||
|
|
||||||
|
type HasteMapOptions = {|
|
||||||
|
extensions: Extensions,
|
||||||
|
fastfs: FastFS,
|
||||||
|
moduleCache: ModuleCache,
|
||||||
|
preferNativePlatform: true,
|
||||||
|
helpers: Helpers,
|
||||||
|
platforms: Platforms,
|
||||||
|
|};
|
||||||
|
|
||||||
|
declare class HasteMap {
|
||||||
|
// node-haste/DependencyGraph/HasteMap.js
|
||||||
|
constructor(options: HasteMapOptions): void,
|
||||||
|
}
|
||||||
|
export type HasteMapT = HasteMap;
|
||||||
|
|
||||||
|
type ResolutionRequestOptions = {|
|
||||||
|
platform: Platform,
|
||||||
|
platforms: Platforms,
|
||||||
|
preferNativePlatform: true,
|
||||||
|
hasteMap: HasteMap,
|
||||||
|
deprecatedAssetMap: DeprecatedAssetMap,
|
||||||
|
helpers: Helpers,
|
||||||
|
moduleCache: ModuleCache,
|
||||||
|
fastfs: FastFS,
|
||||||
|
shouldThrowOnUnresolvedErrors: () => true,
|
||||||
|
extraNodeModules: {[id: ModuleID]: Path},
|
||||||
|
|};
|
||||||
|
|
||||||
|
declare class ResolutionRequest {
|
||||||
|
// node-haste/DependencyGraph/ResolutionRequest.js
|
||||||
|
constructor(options: ResolutionRequestOptions): void,
|
||||||
|
resolveDependency(from: Module, to: ModuleID): Promise<Module>,
|
||||||
|
}
|
||||||
|
export type ResolutionRequestT = ResolutionRequest;
|
|
@ -0,0 +1,106 @@
|
||||||
|
/**
|
||||||
|
* Copyright (c) 2015-present, Facebook, Inc.
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This source code is licensed under the BSD-style license found in the
|
||||||
|
* LICENSE file in the root directory of this source tree. An additional grant
|
||||||
|
* of patent rights can be found in the PATENTS file in the same directory.
|
||||||
|
*
|
||||||
|
* @flow
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
import type { // eslint-disable-line sort-requires
|
||||||
|
DeprecatedAssetMapT,
|
||||||
|
Extensions,
|
||||||
|
HasteMapT,
|
||||||
|
HelpersT,
|
||||||
|
Path,
|
||||||
|
ResolutionRequestT,
|
||||||
|
} from './node-haste.flow';
|
||||||
|
|
||||||
|
import type {
|
||||||
|
ResolveFn,
|
||||||
|
TransformedFile,
|
||||||
|
} from '../types.flow';
|
||||||
|
|
||||||
|
const DependencyGraphHelpers: Class<HelpersT> = require('../../node-haste/DependencyGraph/DependencyGraphHelpers');
|
||||||
|
const DeprecatedAssetMap: Class<DeprecatedAssetMapT> = require('../../node-haste/DependencyGraph/DeprecatedAssetMap');
|
||||||
|
const FastFS = require('./FastFS');
|
||||||
|
const HasteMap: Class<HasteMapT> = require('../../node-haste/DependencyGraph/HasteMap');
|
||||||
|
const Module = require('./Module');
|
||||||
|
const ModuleCache = require('./ModuleCache');
|
||||||
|
const ResolutionRequest: Class<ResolutionRequestT> = require('../../node-haste/DependencyGraph/ResolutionRequest');
|
||||||
|
|
||||||
|
type ResolveOptions = {
|
||||||
|
assetExts: Extensions,
|
||||||
|
extraNodeModules: {[id: string]: string},
|
||||||
|
providesModuleNodeModules: Array<string>,
|
||||||
|
transformedFiles: {[path: Path]: TransformedFile},
|
||||||
|
};
|
||||||
|
|
||||||
|
const platforms = new Set(['android', 'ios']);
|
||||||
|
const returnTrue = () => true;
|
||||||
|
|
||||||
|
exports.createResolveFn = function(options: ResolveOptions): ResolveFn {
|
||||||
|
const {
|
||||||
|
assetExts,
|
||||||
|
extraNodeModules,
|
||||||
|
providesModuleNodeModules,
|
||||||
|
transformedFiles,
|
||||||
|
} = options;
|
||||||
|
const files = Object.keys(transformedFiles);
|
||||||
|
const getTransformedFile =
|
||||||
|
path => Promise.resolve(
|
||||||
|
transformedFiles[path] || Promise.reject(new Error(`"${path} does not exist`))
|
||||||
|
);
|
||||||
|
|
||||||
|
const helpers = new DependencyGraphHelpers({
|
||||||
|
assetExts,
|
||||||
|
providesModuleNodeModules,
|
||||||
|
});
|
||||||
|
const deprecatedAssetMap = new DeprecatedAssetMap({
|
||||||
|
assetExts,
|
||||||
|
files,
|
||||||
|
helpers,
|
||||||
|
platforms,
|
||||||
|
});
|
||||||
|
|
||||||
|
const fastfs = new FastFS(files);
|
||||||
|
const moduleCache = new ModuleCache(getTransformedFile);
|
||||||
|
const hasteMap = new HasteMap({
|
||||||
|
extensions: ['js', 'json'],
|
||||||
|
fastfs,
|
||||||
|
helpers,
|
||||||
|
moduleCache,
|
||||||
|
platforms,
|
||||||
|
preferNativePlatform: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const resolutionRequests = {};
|
||||||
|
return (id, source, platform, _, callback) => {
|
||||||
|
let resolutionRequest = resolutionRequests[platform];
|
||||||
|
if (!resolutionRequest) {
|
||||||
|
resolutionRequest = resolutionRequests[platform] = new ResolutionRequest({
|
||||||
|
deprecatedAssetMap,
|
||||||
|
extraNodeModules,
|
||||||
|
fastfs,
|
||||||
|
hasteMap,
|
||||||
|
helpers,
|
||||||
|
moduleCache,
|
||||||
|
platform,
|
||||||
|
platforms,
|
||||||
|
preferNativePlatform: true,
|
||||||
|
shouldThrowOnUnresolvedErrors: returnTrue,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const from = new Module(source, getTransformedFile(source));
|
||||||
|
resolutionRequest.resolveDependency(from, id).then(
|
||||||
|
// nextTick to escape promise error handling
|
||||||
|
module => process.nextTick(callback, null, module.path),
|
||||||
|
error => process.nextTick(callback, error),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
};
|
|
@ -0,0 +1 @@
|
||||||
|
{"main":"node-haste.js"}
|
|
@ -68,3 +68,24 @@ export type LoadFn = (
|
||||||
options: LoadOptions,
|
options: LoadOptions,
|
||||||
callback: callback2<File, Array<string>>,
|
callback: callback2<File, Array<string>>,
|
||||||
) => void;
|
) => void;
|
||||||
|
|
||||||
|
type TransformResult = {
|
||||||
|
code: string,
|
||||||
|
map: ?Object,
|
||||||
|
dependencies: Array<String>,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TransformedFile = {
|
||||||
|
file: string,
|
||||||
|
code: string,
|
||||||
|
transformed: {[variant: string]: TransformResult},
|
||||||
|
hasteID: ?string,
|
||||||
|
package?: PackageData,
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PackageData = {
|
||||||
|
name?: string,
|
||||||
|
main?: string,
|
||||||
|
browser?: Object | string,
|
||||||
|
'react-native'?: Object | string,
|
||||||
|
};
|
||||||
|
|
|
@ -65,6 +65,7 @@ function transformJSON(infile, options, outfile, callback) {
|
||||||
if (basename(filename) === 'package.json') {
|
if (basename(filename) === 'package.json') {
|
||||||
result.package = {
|
result.package = {
|
||||||
name: value.name,
|
name: value.name,
|
||||||
|
main: value.main,
|
||||||
browser: value.browser,
|
browser: value.browser,
|
||||||
'react-native': value['react-native'],
|
'react-native': value['react-native'],
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue