From 0c7bc9801c2e5bf8100be6c975235734f6ccb309 Mon Sep 17 00:00:00 2001 From: David Aurelio Date: Wed, 19 Oct 2016 06:48:45 -0700 Subject: [PATCH] Create integration types and code to use module resolution logic from node-haste MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../src/ModuleGraph/node-haste/FastFS.js | 68 +++++++++ .../src/ModuleGraph/node-haste/Module.js | 40 ++++++ .../src/ModuleGraph/node-haste/ModuleCache.js | 57 ++++++++ .../src/ModuleGraph/node-haste/Package.js | 132 ++++++++++++++++++ .../ModuleGraph/node-haste/node-haste.flow.js | 105 ++++++++++++++ .../src/ModuleGraph/node-haste/node-haste.js | 106 ++++++++++++++ .../src/ModuleGraph/node-haste/package.json | 1 + .../src/ModuleGraph/types.flow.js | 21 +++ .../react-packager/src/ModuleGraph/worker.js | 1 + 9 files changed, 531 insertions(+) create mode 100644 packager/react-packager/src/ModuleGraph/node-haste/FastFS.js create mode 100644 packager/react-packager/src/ModuleGraph/node-haste/Module.js create mode 100644 packager/react-packager/src/ModuleGraph/node-haste/ModuleCache.js create mode 100644 packager/react-packager/src/ModuleGraph/node-haste/Package.js create mode 100644 packager/react-packager/src/ModuleGraph/node-haste/node-haste.flow.js create mode 100644 packager/react-packager/src/ModuleGraph/node-haste/node-haste.js create mode 100644 packager/react-packager/src/ModuleGraph/node-haste/package.json diff --git a/packager/react-packager/src/ModuleGraph/node-haste/FastFS.js b/packager/react-packager/src/ModuleGraph/node-haste/FastFS.js new file mode 100644 index 000000000..1378debfb --- /dev/null +++ b/packager/react-packager/src/ModuleGraph/node-haste/FastFS.js @@ -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; + directoryEntries: Map>; + files: Set; + + constructor(files: Array) { + 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; +} diff --git a/packager/react-packager/src/ModuleGraph/node-haste/Module.js b/packager/react-packager/src/ModuleGraph/node-haste/Module.js new file mode 100644 index 000000000..fac654d94 --- /dev/null +++ b/packager/react-packager/src/ModuleGraph/node-haste/Module.js @@ -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; + name: Promise; + path: string; + type: 'Module'; + + constructor(path: string, info: Promise) { + 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[\///]/, ''); +} diff --git a/packager/react-packager/src/ModuleGraph/node-haste/ModuleCache.js b/packager/react-packager/src/ModuleGraph/node-haste/ModuleCache.js new file mode 100644 index 000000000..91de8307d --- /dev/null +++ b/packager/react-packager/src/ModuleGraph/node-haste/ModuleCache.js @@ -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 = (path: string) => Promise; + +module.exports = class ModuleCache { + getPackageData: GetFn; + getTransformedFile: GetFn; + modules: Map; + packages: Map; + + constructor(getTransformedFile: GetFn) { + 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; + } +}; diff --git a/packager/react-packager/src/ModuleGraph/node-haste/Package.js b/packager/react-packager/src/ModuleGraph/node-haste/Package.js new file mode 100644 index 000000000..a96393612 --- /dev/null +++ b/packager/react-packager/src/ModuleGraph/node-haste/Package.js @@ -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; + root: string; + type: 'Package'; + + constructor(packagePath: string, data: Promise) { + 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 }; +} diff --git a/packager/react-packager/src/ModuleGraph/node-haste/node-haste.flow.js b/packager/react-packager/src/ModuleGraph/node-haste/node-haste.flow.js new file mode 100644 index 000000000..cae82416e --- /dev/null +++ b/packager/react-packager/src/ModuleGraph/node-haste/node-haste.flow.js @@ -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; + +export type Extensions = Array; + +export type Module = { + path: Path, + type: 'Module', + getName(): Promise, + isHaste(): Promise, +}; + +export type Package = { + type: 'Package', + root: Path, + getMain(): Promise, + getName(): Promise, + redirectRequire(id: ModuleID): Promise, +}; + +// 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, + matches(directory: Path, pattern: RegExp): Array, +}; + +type HelpersOptions = {| + assetExts: Extensions, + providesModuleNodeModules: Array, +|}; + +declare class Helpers { + // node-haste/DependencyGraph/DependencyGraphHelpers.js + constructor(options: HelpersOptions): void, +} +export type HelpersT = Helpers; + +type DeprecatedAssetMapOptions = {| + assetExts: Extensions, + files: Array, + 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, +} +export type ResolutionRequestT = ResolutionRequest; diff --git a/packager/react-packager/src/ModuleGraph/node-haste/node-haste.js b/packager/react-packager/src/ModuleGraph/node-haste/node-haste.js new file mode 100644 index 000000000..6013e6891 --- /dev/null +++ b/packager/react-packager/src/ModuleGraph/node-haste/node-haste.js @@ -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 = require('../../node-haste/DependencyGraph/DependencyGraphHelpers'); +const DeprecatedAssetMap: Class = require('../../node-haste/DependencyGraph/DeprecatedAssetMap'); +const FastFS = require('./FastFS'); +const HasteMap: Class = require('../../node-haste/DependencyGraph/HasteMap'); +const Module = require('./Module'); +const ModuleCache = require('./ModuleCache'); +const ResolutionRequest: Class = require('../../node-haste/DependencyGraph/ResolutionRequest'); + +type ResolveOptions = { + assetExts: Extensions, + extraNodeModules: {[id: string]: string}, + providesModuleNodeModules: Array, + 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), + ); + }; +}; diff --git a/packager/react-packager/src/ModuleGraph/node-haste/package.json b/packager/react-packager/src/ModuleGraph/node-haste/package.json new file mode 100644 index 000000000..f642436d9 --- /dev/null +++ b/packager/react-packager/src/ModuleGraph/node-haste/package.json @@ -0,0 +1 @@ +{"main":"node-haste.js"} diff --git a/packager/react-packager/src/ModuleGraph/types.flow.js b/packager/react-packager/src/ModuleGraph/types.flow.js index ae62209f1..f168e9ad3 100644 --- a/packager/react-packager/src/ModuleGraph/types.flow.js +++ b/packager/react-packager/src/ModuleGraph/types.flow.js @@ -68,3 +68,24 @@ export type LoadFn = ( options: LoadOptions, callback: callback2>, ) => void; + +type TransformResult = { + code: string, + map: ?Object, + dependencies: Array, +}; + +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, +}; diff --git a/packager/react-packager/src/ModuleGraph/worker.js b/packager/react-packager/src/ModuleGraph/worker.js index 20b97a1da..c604d5fba 100644 --- a/packager/react-packager/src/ModuleGraph/worker.js +++ b/packager/react-packager/src/ModuleGraph/worker.js @@ -65,6 +65,7 @@ function transformJSON(infile, options, outfile, callback) { if (basename(filename) === 'package.json') { result.package = { name: value.name, + main: value.main, browser: value.browser, 'react-native': value['react-native'], };