Skip to content

Commit

Permalink
feat(polyfills): introduce @rnx-kit/polyfills (#2580)
Browse files Browse the repository at this point in the history
  • Loading branch information
tido64 authored Aug 8, 2023
1 parent 18c757b commit 7609d4a
Show file tree
Hide file tree
Showing 8 changed files with 249 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .changeset/angry-impalas-sneeze.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
51 changes: 51 additions & 0 deletions incubator/polyfills/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# @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

Please read the
[Modularity](https://github.com/microsoft/rnx-kit/blob/tido/react-native-standard-api/text/0002-react-native-webapis.md#modularity)
section of the
[React Native WebAPIs RFC](https://github.com/microsoft/rnx-kit/pull/2504) for
its raison d'être.

## 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 { getPreludeModules } = require("@rnx-kit/polyfills");

module.exports = makeMetroConfig({
+ serializer: {
+ getModulesRunBeforeMainModule: getPreludeModules,
+ },
});
```
71 changes: 71 additions & 0 deletions incubator/polyfills/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
{
"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"
},
"dependencies": {
"@rnx-kit/console": "^1.0.0",
"@rnx-kit/tools-node": "^2.0.0"
},
"peerDependencies": {
"@react-native/js-polyfills": "*"
},
"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
}
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;
}
40 changes: 40 additions & 0 deletions incubator/polyfills/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { getDependencyPolyfills } from "./dependency";
import type { Context, GetPreludeModules } from "./types";

/**
* Ideally, we'd need something between `serializer.getPolyfills` and
* `serializer.getModulesRunBeforeMainModule`. The former does not have access
* to `require`, while the latter requires that the listed modules are
* explicitly used in the bundle itself (see
* https://github.com/facebook/metro/issues/850). For now, we will use this fact
* to simply list all prelude modules.
*/
function defaultModules({ projectRoot }: Context): string[] {
const platforms = [
"react-native",
"react-native-macos",
"react-native-windows",
];
const options = { paths: [projectRoot] };

const modules = [];
for (const platform of platforms) {
const core = `${platform}/Libraries/Core/InitializeCore`;
try {
modules.push(require.resolve(core, options));
} catch (_) {
// ignore
}
}

return modules;
}

export const getPreludeModules: GetPreludeModules = () => {
const context = { projectRoot: process.cwd() };
const modules = defaultModules(context);
const dependencyPolyfills = getDependencyPolyfills(context);
return modules.concat(dependencyPolyfills);
};

export default getPreludeModules;
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 = {
projectRoot: string;
};

export type GetPreludeModules =
ConfigT["serializer"]["getModulesRunBeforeMainModule"];
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"]
}
20 changes: 20 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3867,6 +3867,26 @@ __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
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": "*"
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 7609d4a

Please sign in to comment.