Skip to content

Commit

Permalink
feat(polyfills): introduce @rnx-kit/polyfills
Browse files Browse the repository at this point in the history
  • Loading branch information
tido64 committed Aug 4, 2023
1 parent 7e17bac commit a53da8e
Show file tree
Hide file tree
Showing 9 changed files with 271 additions and 0 deletions.
47 changes: 47 additions & 0 deletions incubator/polyfills/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# @rnx-kit/polyfills

[![Build](https://github.com/microsoft/rnx-kit/actions/workflows/build.yml/badge.svg)](https://github.com/microsoft/rnx-kit/actions/workflows/build.yml)
[![npm version](https://img.shields.io/npm/v/@rnx-kit/polyfills)](https://www.npmjs.com/package/@rnx-kit/polyfills)

🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧

### THIS TOOL IS EXPERIMENTAL — USE WITH CAUTION

🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧🚧

This is a polyfills "autolinker" for Metro. It works like native module
autolinking, but gathers polyfills from dependencies instead.

> **Note**
>
> This package is temporary. Ideally, this should be upstreamed to
> `@react-native-community/cli`.
## Motivation

This package is part of
[React Native WebAPIs](https://github.com/microsoft/rnx-kit/pull/2504).

## Installation

```sh
yarn add @rnx-kit/polyfills --dev
```

or if you're using npm

```sh
npm add --save-dev @rnx-kit/polyfills
```

## Usage

```diff
const { makeMetroConfig } = require("@rnx-kit/metro-config");
const { getPolyfills } = require("@rnx-kit/polyfills");
module.exports = makeMetroConfig({
+ serializer: {
+ getPolyfills,
+ },
});
```
74 changes: 74 additions & 0 deletions incubator/polyfills/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
{
"private": true,
"name": "@rnx-kit/polyfills",
"version": "0.0.1",
"description": "EXPERIMENTAL - USE WITH CAUTION - New package called polyfills",
"homepage": "https://github.com/microsoft/rnx-kit/tree/main/incubator/polyfills#readme",
"license": "MIT",
"author": {
"name": "Microsoft Open Source",
"email": "microsoftopensource@users.noreply.github.com"
},
"files": [
"lib/*"
],
"main": "lib/index.js",
"types": "lib/index.d.ts",
"repository": {
"type": "git",
"url": "https://github.com/microsoft/rnx-kit",
"directory": "incubator/polyfills"
},
"engines": {
"node": ">=14.15"
},
"scripts": {
"build": "rnx-kit-scripts build",
"format": "rnx-kit-scripts format",
"lint": "rnx-kit-scripts lint",
"test": "rnx-kit-scripts test"
},
"dependencies": {
"@rnx-kit/console": "^1.0.0",
"@rnx-kit/tools-node": "^2.0.0",
"@rnx-kit/tools-react-native": "^1.3.1"
},
"peerDependencies": {
"@react-native/js-polyfills": "*",
"react-native": ">=0.71.0"
},
"peerDependenciesMeta": {
"@react-native/js-polyfills": {
"optional": true
}
},
"devDependencies": {
"@rnx-kit/scripts": "*",
"eslint": "^8.0.0",
"jest": "^29.2.1",
"metro-config": "^0.73.7",
"prettier": "^3.0.0",
"typescript": "^5.0.0"
},
"eslintConfig": {
"extends": "@rnx-kit/eslint-config"
},
"jest": {
"preset": "@rnx-kit/scripts"
},
"rnx-kit": {
"alignDeps": {
"presets": [
"microsoft/react-native",
"@rnx-kit/scripts/align-deps-preset.js"
],
"requirements": [
"react-native@0.71"
],
"capabilities": [
"metro-config"
]
}
},
"experimental": true
}
34 changes: 34 additions & 0 deletions incubator/polyfills/src/defaultPolyfills.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { error } from "@rnx-kit/console";
import { getAvailablePlatforms } from "@rnx-kit/tools-react-native";
import type { Context } from "./types";

function getDefaultPolyfillsPath({
platform,
projectRoot,
}: Context): string | null {
const options = { paths: [projectRoot] };

try {
return require.resolve("@react-native/js-polyfills", options);
} catch (_) {
// `@react-native/js-polyfills` is available from 0.72+
}

const platforms = getAvailablePlatforms(projectRoot);
const reactNativePath = platforms[platform || "ios"] || "react-native";

try {
return require.resolve(`${reactNativePath}/rn-get-polyfills`, options);
} catch (_) {
error(
`Could not find polyfills for '${platform}' — if this is expected, you can ignore this error message`
);
}

return null;
}

export function getDefaultPolyfills(context: Context): string[] {
const defaultPolyfillsPath = getDefaultPolyfillsPath(context);
return defaultPolyfillsPath ? require(defaultPolyfillsPath)() : [];
}
53 changes: 53 additions & 0 deletions incubator/polyfills/src/dependency.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { error } from "@rnx-kit/console";
import { readPackage } from "@rnx-kit/tools-node";
import * as path from "path";
import type { Context } from "./types";

function getDependencies({ projectRoot }: Context): string[] {
const manifest = readPackage(projectRoot);

const dependencies = new Set<string>();
for (const section of ["dependencies", "devDependencies"] as const) {
const names = manifest[section];
if (names) {
Object.keys(names).forEach((name) => dependencies.add(name));
}
}

return Array.from(dependencies);
}

function isValidPath(p: string): boolean {
return (
Boolean(p) &&
!p.startsWith("..") &&
!p.startsWith("/") &&
!/^[A-Za-z]:/.test(p)
);
}

export function getDependencyPolyfills(context: Context): string[] {
const polyfills: string[] = [];

const options = { paths: [context.projectRoot] };
const dependencies = getDependencies(context);

for (const name of dependencies) {
try {
const config = require.resolve(`${name}/react-native.config.js`, options);
const polyfill = require(config).dependency?.api?.polyfill;
if (typeof polyfill === "string") {
if (!isValidPath(polyfill)) {
error(`${name}: invalid polyfill path: ${polyfill}`);
continue;
}

polyfills.push(path.resolve(path.dirname(config), polyfill));
}
} catch (_) {
// ignore
}
}

return polyfills;
}
12 changes: 12 additions & 0 deletions incubator/polyfills/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { getDefaultPolyfills } from "./defaultPolyfills";
import { getDependencyPolyfills } from "./dependency";
import type { GetPolyfills } from "./types";

export const getPolyfills: GetPolyfills = ({ platform }) => {
const context = { platform, projectRoot: process.cwd() };
const polyfills = getDefaultPolyfills(context);
const dependencyPolyfills = getDependencyPolyfills(context);
return polyfills.concat(dependencyPolyfills);
};

export default getPolyfills;
8 changes: 8 additions & 0 deletions incubator/polyfills/src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import type { ConfigT } from "metro-config";

export type Context = {
platform: string | null;
projectRoot: string;
};

export type GetPolyfills = ConfigT["serializer"]["getPolyfills"];
17 changes: 17 additions & 0 deletions incubator/polyfills/test/defaultPolyfills.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { getDefaultPolyfills } from "../src/defaultPolyfills";

describe("getDefaultPolyfills", () => {
const context = {
platform: "ios",
projectRoot: process.cwd(),
};

it("should return default polyfills", () => {
const defaultPolyfills = getDefaultPolyfills(context).sort();
expect(defaultPolyfills).toEqual([
expect.stringMatching(/[/\\]@react-native[/\\]polyfills[/\\]Object.es8.js$/),
expect.stringMatching(/[/\\]@react-native[/\\]polyfills[/\\]console.js$/),
expect.stringMatching(/[/\\]@react-native[/\\]polyfills[/\\]error-guard.js$/),
]);
});
});
4 changes: 4 additions & 0 deletions incubator/polyfills/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"extends": "@rnx-kit/scripts/tsconfig-shared.json",
"include": ["src"]
}
22 changes: 22 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3866,6 +3866,28 @@ __metadata:
languageName: unknown
linkType: soft

"@rnx-kit/polyfills@workspace:incubator/polyfills":
version: 0.0.0-use.local
resolution: "@rnx-kit/polyfills@workspace:incubator/polyfills"
dependencies:
"@rnx-kit/console": ^1.0.0
"@rnx-kit/scripts": "*"
"@rnx-kit/tools-node": ^2.0.0
"@rnx-kit/tools-react-native": ^1.3.1
eslint: ^8.0.0
jest: ^29.2.1
metro-config: ^0.73.7
prettier: ^3.0.0
typescript: ^5.0.0
peerDependencies:
"@react-native/js-polyfills": "*"
react-native: ">=0.71.0"
peerDependenciesMeta:
"@react-native/js-polyfills":
optional: true
languageName: unknown
linkType: soft

"@rnx-kit/react-native-auth@*, @rnx-kit/react-native-auth@workspace:packages/react-native-auth":
version: 0.0.0-use.local
resolution: "@rnx-kit/react-native-auth@workspace:packages/react-native-auth"
Expand Down

0 comments on commit a53da8e

Please sign in to comment.