feat(tools-react-native): add function to get available platforms (#974)

This commit is contained in:
Tommy Nguyen 2022-01-04 23:25:22 +01:00 коммит произвёл GitHub
Родитель be7347b674
Коммит adf6feb85d
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
41 изменённых файлов: 401 добавлений и 31 удалений

Просмотреть файл

@ -0,0 +1,5 @@
---
"@rnx-kit/metro-resolver-symlinks": patch
---
Get available platforms from disk instead of using a hard-coded list

Просмотреть файл

@ -0,0 +1,5 @@
---
"@rnx-kit/tools-react-native": patch
---
Added a function to get available React Native platforms

Просмотреть файл

@ -0,0 +1,5 @@
---
"@rnx-kit/typescript-react-native-resolver": patch
---
Get available platforms from disk instead of using a hard-coded list

Просмотреть файл

@ -1,5 +1,5 @@
import { isFileModuleRef, parseModuleRef } from "@rnx-kit/tools-node";
import { AVAILABLE_PLATFORMS } from "@rnx-kit/tools-react-native";
import { getAvailablePlatforms } from "@rnx-kit/tools-react-native";
import * as path from "path";
import type { MetroResolver, ModuleResolver } from "./types";
@ -33,7 +33,7 @@ export const remapReactNativeModule: ModuleResolver = (
moduleName,
platform
) => {
const platformImpl = AVAILABLE_PLATFORMS[platform];
const platformImpl = getAvailablePlatforms()[platform];
if (platformImpl) {
if (moduleName === "react-native") {
return platformImpl;

Просмотреть файл

@ -0,0 +1,4 @@
{
"name": "@office-iss/react-native-win32",
"version": "0.0.0-dev"
}

Просмотреть файл

@ -0,0 +1,10 @@
module.exports = {
platforms: {
win32: {
linkConfig: () => null,
projectConfig: (projectRoot, projectParams) => null,
dependencyConfig: (projectRoot, dependencyParams) => null,
npmPackageName: "@office-iss/react-native-win32",
},
},
};

Просмотреть файл

@ -0,0 +1,4 @@
{
"name": "react-native-macos",
"version": "0.0.0-dev"
}

Просмотреть файл

@ -0,0 +1,11 @@
module.exports = {
platforms: {
ios: {},
android: {},
macos: {
projectConfig: () => null,
dependencyConfig: () => null,
npmPackageName: "react-native-macos",
},
},
};

Просмотреть файл

@ -0,0 +1,4 @@
{
"name": "react-native-windows",
"version": "0.0.0-dev"
}

Просмотреть файл

@ -0,0 +1,8 @@
module.exports = {
platforms: {
windows: {
linkConfig: () => null,
npmPackageName: "react-native-windows",
},
},
};

Просмотреть файл

@ -0,0 +1,10 @@
{
"name": "@rnx-kit/metro-resolver-symlinks/remap-tests",
"version": "0.0.0-dev",
"dependencies": {
"@office-iss/react-native-win32": "0.0.0-dev",
"react-native": "0.0.0-dev",
"react-native-macos": "0.0.0-dev",
"react-native-windows": "0.0.0-dev"
}
}

Просмотреть файл

@ -1,4 +1,3 @@
import { AVAILABLE_PLATFORMS } from "@rnx-kit/tools-react-native";
import * as path from "path";
import {
getMetroResolver,
@ -6,6 +5,12 @@ import {
resolveModulePath,
} from "../src/resolver";
const AVAILABLE_PLATFORMS = {
macos: "react-native-macos",
win32: "@office-iss/react-native-win32",
windows: "react-native-windows",
};
function useFixture(name: string): string {
return path.join(__dirname, "__fixtures__", name);
}
@ -32,7 +37,7 @@ describe("getMetroResolver", () => {
test("throws if `metro-resolver` cannot be found", () => {
const cwd = process.cwd();
const root = cwd.substr(0, cwd.indexOf(path.sep) + 1);
const root = cwd.substring(0, cwd.indexOf(path.sep) + 1);
expect(() => getMetroResolver(root)(context, "", null)).toThrowError(
"Cannot find module"
);
@ -44,6 +49,16 @@ describe("remapReactNativeModule", () => {
originModulePath: "",
};
const currentDir = process.cwd();
beforeAll(() => {
process.chdir(useFixture("remap-platforms"));
});
afterAll(() => {
process.chdir(currentDir);
});
test("remaps `react-native` if platform is supported", () => {
expect(remapReactNativeModule(context, "terminator", "macos")).toBe(
"terminator"

Просмотреть файл

@ -22,10 +22,12 @@ import * from "@rnx-kit/tools-react-native/platform";
| -------- | ------------ | ----------------------------------------- |
| platform | AllPlatforms | List of supported react-native platforms. |
| Category | Function | Description |
| -------- | ------------------------------------------------ | ----------------------------------------------------------------------------------------------- |
| platform | `expandPlatformExtensions(platform, extensions)` | Returns a list of extensions that should be tried for the target platform in prioritized order. |
| platform | `parsePlatform(val)` | Parse a string to ensure it maps to a valid react-native platform. |
| platform | `platformExtensions(platform)` | Returns file extensions that can be mapped to the target platform. |
| Category | Function | Description |
| -------- | ------------------------------------------------------ | ----------------------------------------------------------------------------------------------- |
| platform | `expandPlatformExtensions(platform, extensions)` | Returns a list of extensions that should be tried for the target platform in prioritized order. |
| platform | `getAvailablePlatforms(startDir)` | Returns a map of available React Native platforms. The result is cached. |
| platform | `getAvailablePlatformsUncached(startDir, platformMap)` | Returns a map of available React Native platforms. The result is NOT cached. |
| platform | `parsePlatform(val)` | Parse a string to ensure it maps to a valid react-native platform. |
| platform | `platformExtensions(platform)` | Returns file extensions that can be mapped to the target platform. |
<!-- @rnx-kit/api end -->

Просмотреть файл

@ -1,6 +1,7 @@
export {
AVAILABLE_PLATFORMS,
expandPlatformExtensions,
getAvailablePlatforms,
getAvailablePlatformsUncached,
parsePlatform,
platformExtensions,
} from "./platform";

Просмотреть файл

@ -1,17 +1,11 @@
import * as fs from "fs";
import * as path from "path";
/**
* List of supported react-native platforms.
*/
export type AllPlatforms = "ios" | "android" | "windows" | "win32" | "macos";
// TODO: `react-native config` is too slow. Hard-coding this list until we can
// figure out a better solution.
// See https://github.com/microsoft/rnx-kit/issues/925
export const AVAILABLE_PLATFORMS: Record<string, string> = {
macos: "react-native-macos",
win32: "@office-iss/react-native-win32",
windows: "react-native-windows",
};
/**
* Returns a list of extensions that should be tried for the target platform in
* prioritized order.
@ -34,6 +28,69 @@ export function expandPlatformExtensions(
return expanded;
}
/**
* Returns a map of available React Native platforms. The result is cached.
* @privateRemarks is-arrow-function
* @param startDir The directory to look for react-native platforms from
* @returns A platform-to-npm-package map, excluding "core" platforms.
*/
export const getAvailablePlatforms = (() => {
let platformMap: Record<string, string> | undefined = undefined;
return (startDir: string = process.cwd()) => {
if (!platformMap) {
platformMap = getAvailablePlatformsUncached(startDir);
}
return platformMap;
};
})();
/**
* Returns a map of available React Native platforms. The result is NOT cached.
* @param startDir The directory to look for react-native platforms from
* @param platformMap A platform-to-npm-package map of known packages
* @returns A platform-to-npm-package map, excluding "core" platforms.
*/
export function getAvailablePlatformsUncached(
startDir = process.cwd(),
platformMap: Record<string, string> = { android: "", ios: "" }
): Record<string, string> {
const packageJson = path.join(startDir, "package.json");
if (!fs.existsSync(packageJson)) {
const parent = path.dirname(startDir);
return parent === startDir
? platformMap
: getAvailablePlatformsUncached(path.dirname(startDir), platformMap);
}
const resolveOptions = { paths: [startDir] };
const { dependencies, devDependencies } = require(packageJson);
[
...(dependencies ? Object.keys(dependencies) : []),
...(devDependencies ? Object.keys(devDependencies) : []),
].forEach((pkgName) => {
const pkgPath = path.dirname(
require.resolve(`${pkgName}/package.json`, resolveOptions)
);
const configPath = path.join(pkgPath, "react-native.config.js");
if (fs.existsSync(configPath)) {
const { platforms } = require(configPath);
if (platforms) {
Object.keys(platforms).forEach((platform) => {
if (typeof platformMap[platform] === "undefined") {
const { npmPackageName } = platforms[platform];
if (npmPackageName) {
platformMap[platform] = npmPackageName;
}
}
});
}
}
});
return platformMap;
}
/**
* Returns file extensions that can be mapped to the target platform.
* @param platform The platform to retrieve extensions for

Просмотреть файл

@ -0,0 +1,4 @@
{
"name": "@office-iss/react-native-win32",
"version": "0.0.0-dev"
}

Просмотреть файл

@ -0,0 +1,10 @@
module.exports = {
platforms: {
win32: {
linkConfig: () => null,
projectConfig: (projectRoot, projectParams) => null,
dependencyConfig: (projectRoot, dependencyParams) => null,
npmPackageName: "@office-iss/react-native-win32",
},
},
};

Просмотреть файл

@ -0,0 +1,4 @@
{
"name": "react-native-codegen",
"version": "0.0.0-dev"
}

Просмотреть файл

@ -0,0 +1,4 @@
{
"name": "react-native-macos",
"version": "0.0.0-dev"
}

Просмотреть файл

@ -0,0 +1,11 @@
module.exports = {
platforms: {
ios: {},
android: {},
macos: {
projectConfig: () => null,
dependencyConfig: () => null,
npmPackageName: "react-native-macos",
},
},
};

Просмотреть файл

@ -0,0 +1,4 @@
{
"name": "react-native-tvos",
"version": "0.0.0-dev"
}

Просмотреть файл

@ -0,0 +1,6 @@
module.exports = {
platforms: {
ios: {},
android: {},
},
};

Просмотреть файл

@ -0,0 +1,4 @@
{
"name": "react-native-windows",
"version": "0.0.0-dev"
}

Просмотреть файл

@ -0,0 +1,8 @@
module.exports = {
platforms: {
windows: {
linkConfig: () => null,
npmPackageName: "react-native-windows",
},
},
};

Просмотреть файл

@ -0,0 +1,12 @@
{
"name": "@rnx-kit/tools-react-native/available-platform-tests",
"version": "0.0.0-dev",
"dependencies": {
"@office-iss/react-native-win32": "0.0.0-dev",
"react-native": "0.0.0-dev",
"react-native-codegen": "0.0.0-dev",
"react-native-macos": "0.0.0-dev",
"react-native-tvos": "0.0.0-dev",
"react-native-windows": "0.0.0-dev"
}
}

Просмотреть файл

@ -1,5 +1,7 @@
import * as path from "path";
import {
expandPlatformExtensions,
getAvailablePlatformsUncached,
parsePlatform,
platformExtensions,
} from "../src/platform";
@ -26,6 +28,47 @@ describe("React Native > Platform", () => {
]);
});
test("getAvailablePlatformsUncached() returns available platforms", () => {
const fixture = path.join(__dirname, "__fixtures__", "available-platforms");
expect(getAvailablePlatformsUncached(fixture)).toMatchInlineSnapshot(`
Object {
"android": "",
"ios": "",
"macos": "react-native-macos",
"win32": "@office-iss/react-native-win32",
"windows": "react-native-windows",
}
`);
});
test("getAvailablePlatformsUncached() finds package root", () => {
const fixture = path.join(
__dirname,
"__fixtures__",
"available-platforms",
"node_modules",
"react-native"
);
expect(getAvailablePlatformsUncached(fixture)).toMatchInlineSnapshot(`
Object {
"android": "",
"ios": "",
"macos": "react-native-macos",
"win32": "@office-iss/react-native-win32",
"windows": "react-native-windows",
}
`);
});
test("getAvailablePlatformsUncached() handles 'missing' package root", () => {
expect(getAvailablePlatformsUncached()).toMatchInlineSnapshot(`
Object {
"android": "",
"ios": "",
}
`);
});
test("parsePlatform() succeeds for all known platforms", () => {
expect(parsePlatform("ios")).toEqual("ios");
expect(parsePlatform("android")).toEqual("android");

Просмотреть файл

@ -87,6 +87,7 @@ export function changeHostToUseReactNativeResolver({
(e) => `.${e}` // prepend a '.' to each name to make it a file extension
),
replaceReactNativePackageName: createReactNativePackageNameReplacer(
host.getCurrentDirectory(),
platform,
disableReactNativePackageSubstitution,
log

Просмотреть файл

@ -1,4 +1,4 @@
import { AVAILABLE_PLATFORMS } from "@rnx-kit/tools-react-native";
import { getAvailablePlatforms } from "@rnx-kit/tools-react-native";
import type { ResolverLog } from "./log";
const DEFAULT_PACKAGE_NAME = "react-native";
@ -8,17 +8,19 @@ const DEFAULT_PACKAGE_NAME = "react-native";
* a reference to the target platform's react-native package. This only
* happens when targeting an out-of-tree platform like Windows or MacOS.
*
* @param currentDirectory Current directory - used to find available React Native platforms
* @param platform Target platform
* @param disableReactNativePackageSubstitution Flag controling whether or not the returned function has an effect
* @param log Resolver log
* @returns Function which replaces a 'react-native' module reference, or a function which has no effect if module replacement is not needed or disabled
*/
export function createReactNativePackageNameReplacer(
currentDirectory: string,
platform: string,
disableReactNativePackageSubstitution: boolean,
log: ResolverLog
): (m: string) => string {
const platformPackageName = AVAILABLE_PLATFORMS[platform];
const platformPackageName = getAvailablePlatforms(currentDirectory)[platform];
if (!platformPackageName || disableReactNativePackageSubstitution) {
return (m: string) => m;
}

Просмотреть файл

@ -0,0 +1,4 @@
{
"name": "@office-iss/react-native-win32",
"version": "0.0.0-dev"
}

Просмотреть файл

@ -0,0 +1,10 @@
module.exports = {
platforms: {
win32: {
linkConfig: () => null,
projectConfig: (projectRoot, projectParams) => null,
dependencyConfig: (projectRoot, dependencyParams) => null,
npmPackageName: "@office-iss/react-native-win32",
},
},
};

Просмотреть файл

@ -0,0 +1,4 @@
{
"name": "react-native-codegen",
"version": "0.0.0-dev"
}

Просмотреть файл

@ -0,0 +1,4 @@
{
"name": "react-native-macos",
"version": "0.0.0-dev"
}

Просмотреть файл

@ -0,0 +1,11 @@
module.exports = {
platforms: {
ios: {},
android: {},
macos: {
projectConfig: () => null,
dependencyConfig: () => null,
npmPackageName: "react-native-macos",
},
},
};

Просмотреть файл

@ -0,0 +1,4 @@
{
"name": "react-native-tvos",
"version": "0.0.0-dev"
}

Просмотреть файл

@ -0,0 +1,6 @@
module.exports = {
platforms: {
ios: {},
android: {},
},
};

Просмотреть файл

@ -0,0 +1,4 @@
{
"name": "react-native-windows",
"version": "0.0.0-dev"
}

Просмотреть файл

@ -0,0 +1,8 @@
module.exports = {
platforms: {
windows: {
linkConfig: () => null,
npmPackageName: "react-native-windows",
},
},
};

Просмотреть файл

@ -0,0 +1,12 @@
{
"name": "@rnx-kit/tools-react-native/available-platform-tests",
"version": "0.0.0-dev",
"dependencies": {
"@office-iss/react-native-win32": "0.0.0-dev",
"react-native": "0.0.0-dev",
"react-native-codegen": "0.0.0-dev",
"react-native-macos": "0.0.0-dev",
"react-native-tvos": "0.0.0-dev",
"react-native-windows": "0.0.0-dev"
}
}

Просмотреть файл

@ -19,17 +19,19 @@ import {
} from "../src/extension";
describe("Host > changeHostToUseReactNativeResolver", () => {
const mockFileExists = jest.fn();
const mockReadFile = jest.fn();
const mockDirectoryExists = jest.fn();
const mockRealpath = jest.fn();
const mockFileExists = jest.fn();
const mockGetCurrentDirectory = jest.fn();
const mockGetDirectories = jest.fn();
const mockReadFile = jest.fn();
const mockRealpath = jest.fn();
const host = {
fileExists: mockFileExists,
readFile: mockReadFile,
directoryExists: mockDirectoryExists,
realpath: mockRealpath,
fileExists: mockFileExists,
getCurrentDirectory: mockGetCurrentDirectory,
getDirectories: mockGetDirectories,
readFile: mockReadFile,
realpath: mockRealpath,
} as unknown as ts.CompilerHost;
afterEach(() => {

Просмотреть файл

@ -1,9 +1,17 @@
import * as path from "path";
import { ResolverLog, ResolverLogMode } from "../src/log";
import { createReactNativePackageNameReplacer } from "../src/react-native-package-name";
const fixture = path.join(
__dirname,
"__fixtures__",
"react-native-package-name-test"
);
describe("React-Native Package Name > createReactNativePackageNameReplacer > Disabled", () => {
test("returns the input module without substitution", () => {
const replacer = createReactNativePackageNameReplacer(
fixture,
"windows",
true,
new ResolverLog(ResolverLogMode.Never)
@ -16,6 +24,7 @@ describe("React-Native Package Name > createReactNativePackageNameReplacer > Dis
describe("React-Native Package Name > createReactNativePackageNameReplacer > In-tree Platform", () => {
test("returns the input module without substitution", () => {
const replacer = createReactNativePackageNameReplacer(
fixture,
"ios",
false,
new ResolverLog(ResolverLogMode.Never)
@ -32,6 +41,7 @@ describe("React-Native Package Name > createReactNativePackageNameReplacer > Out
};
const replacer = createReactNativePackageNameReplacer(
fixture,
"windows",
false,
resolverLog as ResolverLog

Просмотреть файл

@ -73,20 +73,46 @@ function findSourceFiles() {
* @returns {string}
*/
function getExportedName(node) {
switch (node.declaration?.type) {
const declaration = node.declaration;
switch (declaration?.type) {
case "FunctionDeclaration":
case "TSInterfaceDeclaration":
case "TSTypeAliasDeclaration":
if (!isIdentifier(node.declaration.id)) {
if (!isIdentifier(declaration.id)) {
// TODO: Unnamed functions are currently unsupported
return "";
}
return node.declaration.id.name;
return declaration.id.name;
case "VariableDeclaration": {
if (isArrowFunction(node)) {
const decl = declaration.declarations[0];
if (isIdentifier(decl.id)) {
return decl.id.name;
}
}
return "";
}
default:
return "";
}
}
/**
* Returns whether the node represents a memoized function.
* @param {import("@babel/types").ExportNamedDeclaration} node
* @returns {boolean}
*/
function isArrowFunction(node) {
return Boolean(
node.declaration?.type === "VariableDeclaration" &&
findLastBlockComment(node.leadingComments)?.value?.includes(
"@privateRemarks is-arrow-function"
)
);
}
/**
* @param {import("@microsoft/tsdoc").DocNode} docNode
* @returns {string}
@ -213,6 +239,14 @@ function updateApiReadme() {
.map(renderParamNode)
.join(", ")})\``;
}
if (isArrowFunction(node)) {
const comment = findLastBlockComment(node.leadingComments);
return `\`${name}(${comment?.value
?.split("@param ")
?.map((part) => part.substring(0, part.indexOf(" ")))
?.slice(1)
?.join(", ")})\``;
}
return name;
})();
@ -232,7 +266,10 @@ function updateApiReadme() {
const summary = renderDocNode(result.docComment.summarySection);
const description = extractBrief(summary);
if (isFunctionDeclaration(node.declaration)) {
if (
isFunctionDeclaration(node.declaration) ||
isArrowFunction(node)
) {
exportedFunctions.push([category, identifier, description]);
} else {
exportedTypes.push([category, identifier, description]);