Skip to content

Commit

Permalink
Implements the PnP resolver into Metro
Browse files Browse the repository at this point in the history
Reviewed By: pvdz

Differential Revision: D13097265

fbshipit-source-id: ee2daed2256843448bc2e070c394800fee829790
  • Loading branch information
Maël Nison authored and facebook-github-bot committed Nov 20, 2018
1 parent 1517f12 commit 60bad94
Show file tree
Hide file tree
Showing 12 changed files with 116 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Object {
"reporter": null,
"resetCache": false,
"resolver": Object {
"allowPnp": true,
"assetExts": Array [
"bmp",
"gif",
Expand Down Expand Up @@ -132,6 +133,7 @@ Object {
"reporter": null,
"resetCache": false,
"resolver": Object {
"allowPnp": true,
"assetExts": Array [
"bmp",
"gif",
Expand Down
2 changes: 2 additions & 0 deletions packages/metro-config/src/configTypes.flow.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ export type Middleware = (
) => mixed;

export type OldConfigT = {
allowPnp: boolean,
assetRegistryPath: string,
cacheStores: Array<CacheStore<TransformResult<>>>,
cacheVersion: string,
Expand Down Expand Up @@ -102,6 +103,7 @@ export type OldConfigT = {
};

type ResolverConfigT = {|
allowPnp: boolean,
assetExts: $ReadOnlyArray<string>,
blacklistRE: RegExp,
extraNodeModules: {[name: string]: string},
Expand Down
2 changes: 2 additions & 0 deletions packages/metro-config/src/convertConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ async function convertOldToNew({
reporter = new TerminalReporter(new Terminal(process.stdout)),
}: PublicMetroOptions): Promise<ConfigT> {
const {
allowPnp,
getBlacklistRE,
cacheStores,
createModuleIdFactory,
Expand Down Expand Up @@ -97,6 +98,7 @@ async function convertOldToNew({

return {
resolver: {
allowPnp,
assetExts,
platforms,
providesModuleNodeModules,
Expand Down
1 change: 1 addition & 0 deletions packages/metro-config/src/defaults/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import type {ConfigT} from '../configTypes.flow';

const getDefaultValues = (projectRoot: ?string): ConfigT => ({
resolver: {
allowPnp: true,
assetExts,
platforms,
sourceExts,
Expand Down
1 change: 1 addition & 0 deletions packages/metro-config/src/oldConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const {FileStore} = require('metro-cache');
import type {OldConfigT as ConfigT} from './configTypes.flow.js';

const DEFAULT = ({
allowPnp: true,
assetRegistryPath: 'missing-asset-registry-path',
enhanceMiddleware: middleware => middleware,
extraNodeModules: {},
Expand Down
1 change: 1 addition & 0 deletions packages/metro-resolver/src/__tests__/index-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ const CONTEXT: ResolutionContext = (() => {
);
return {
allowHaste: true,
allowPnp: false,
doesFileExist: filePath => fileSet.has(filePath),
extraNodeModules: null,
getPackageMainPath: dirPath => path.join(path.dirname(dirPath), 'main'),
Expand Down
88 changes: 88 additions & 0 deletions packages/metro-resolver/src/makePnpResolver.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow
* @format
*/

'use strict';

// $FlowFixMe it exists!
const Module = require('module');

const path = require('path');

import type {ResolutionContext} from './types';

const builtinModules = new Set(
// $FlowFixMe "process.binding" exists
Module.builtinModules || Object.keys(process.binding('natives')),
);

module.exports = (pnp: any) => (
context: ResolutionContext,
request: string,
platform: string | null,
) => {
// We don't support builtin modules, so we force pnp to resolve those
// modules as regular npm packages by appending a `/` character
if (builtinModules.has(request)) {
request += '/';
}

const unqualifiedPath = pnp.resolveToUnqualified(
request,
context.originModulePath,
);

const baseExtensions = context.sourceExts.map(extension => `.${extension}`);
let finalExtensions = [...baseExtensions];

if (context.preferNativePlatform) {
finalExtensions = [
...baseExtensions.map(extension => `.native${extension}`),
...finalExtensions,
];
}

if (platform) {
// We must keep a const reference to make Flow happy
const p = platform;

finalExtensions = [
...baseExtensions.map(extension => `.${p}${extension}`),
...finalExtensions,
];
}

try {
return {
type: 'sourceFile',
filePath: pnp.resolveUnqualified(unqualifiedPath, {
extensions: finalExtensions,
}),
};
} catch (error) {
// Only catch the error if it was caused by the resolution process
if (error.code !== 'QUALIFIED_PATH_RESOLUTION_FAILED') {
throw error;
}

const dirname = path.dirname(unqualifiedPath);
const basename = path.basename(unqualifiedPath);

const assetResolutions = context.resolveAsset(dirname, basename, platform);

if (assetResolutions) {
return {
type: 'assetFiles',
filePaths: assetResolutions.map<string>(name => `${dirname}/${name}`),
};
} else {
throw error;
}
}
};
21 changes: 13 additions & 8 deletions packages/metro-resolver/src/resolve.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const InvalidPackageError = require('./InvalidPackageError');

const formatFileCandidates = require('./formatFileCandidates');
const isAbsolutePath = require('absolute-path');
const makePnpResolver = require('./makePnpResolver');
const path = require('path');

import type {
Expand All @@ -39,8 +40,16 @@ function resolve(
moduleName: string,
platform: string | null,
): Resolution {
let resolveRequest = context.resolveRequest;

if (!resolveRequest && context.allowPnp && process.versions.pnp) {
// $FlowFixMe `pnpapi` is a builtin under PnP environments
const pnp = require('pnpapi');
resolveRequest = makePnpResolver(pnp);
}

if (
!context.resolveRequest &&
!resolveRequest &&
(isRelativeImport(moduleName) || isAbsolutePath(moduleName))
) {
return resolveModulePath(context, moduleName, platform);
Expand All @@ -59,7 +68,7 @@ function resolve(
isRelativeImport(realModuleName) || isAbsolutePath(realModuleName);

// We disable the direct file loading to let the custom resolvers deal with it
if (!context.resolveRequest && isDirectImport) {
if (!resolveRequest && isDirectImport) {
// derive absolute path /.../node_modules/originModuleDir/realModuleName
const fromModuleParentIdx =
originModulePath.lastIndexOf('node_modules' + path.sep) + 13;
Expand All @@ -82,13 +91,9 @@ function resolve(
}
}

if (context.resolveRequest) {
if (resolveRequest) {
try {
const resolution = context.resolveRequest(
context,
realModuleName,
platform,
);
const resolution = resolveRequest(context, realModuleName, platform);
if (resolution) {
return resolution;
}
Expand Down
1 change: 1 addition & 0 deletions packages/metro-resolver/src/types.js
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ export type ModulePathContext = FileOrDirContext & {

export type ResolutionContext = ModulePathContext &
HasteContext & {
allowPnp: boolean,
allowHaste: boolean,
extraNodeModules: ?{[string]: string},
originModulePath: string,
Expand Down
3 changes: 3 additions & 0 deletions packages/metro/src/ModuleGraph/node-haste/node-haste.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import type {Extensions, Path} from './node-haste.flow';
import type {CustomResolver} from 'metro-resolver';

type ResolveOptions = {|
allowPnp: boolean,
assetExts: Extensions,
extraNodeModules: {[id: string]: string},
mainFields: $ReadOnlyArray<string>,
Expand Down Expand Up @@ -107,6 +108,7 @@ const createModuleMap = ({files, helpers, moduleCache, sourceExts}) => {

exports.createResolveFn = function(options: ResolveOptions): ResolveFn {
const {
allowPnp,
assetExts,
extraNodeModules,
transformedFiles,
Expand Down Expand Up @@ -140,6 +142,7 @@ exports.createResolveFn = function(options: ResolveOptions): ResolveFn {
platforms,
});
const moduleResolver = new ModuleResolver({
allowPnp,
dirExists: filePath => hasteFS.dirExists(filePath),
doesFileExist: filePath => hasteFS.exists(filePath),
extraNodeModules,
Expand Down
1 change: 1 addition & 0 deletions packages/metro/src/node-haste/DependencyGraph.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,7 @@ class DependencyGraph extends EventEmitter {

_createModuleResolver() {
this._moduleResolver = new ModuleResolver({
allowPnp: this._config.resolver.allowPnp,
dirExists: filePath => {
try {
return fs.lstatSync(filePath).isDirectory();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ export type ModuleishCache<TModule, TPackage> = {
};

type Options<TModule, TPackage> = {|
+allowPnp: boolean,
+dirExists: DirExistsFn,
+doesFileExist: DoesFileExist,
+extraNodeModules: ?Object,
Expand Down

0 comments on commit 60bad94

Please sign in to comment.