Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(polyfills): introduce @rnx-kit/polyfills #2580

Merged
merged 4 commits into from
Aug 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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.
tido64 marked this conversation as resolved.
Show resolved Hide resolved

> **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