first commit

This commit is contained in:
2026-03-10 16:18:05 +00:00
commit 11f9c069b5
31635 changed files with 3187747 additions and 0 deletions

9
node_modules/metro/README.md generated vendored Normal file
View File

@@ -0,0 +1,9 @@
# Metro
🚇 The JavaScript bundler for React Native.
- **🚅 Fast**: We aim for sub-second reload cycles, fast startup and quick bundling speeds.
- **⚖️ Scalable**: Works with thousands of modules in a single application.
- **⚛️ Integrated**: Supports every React Native project out of the box.
This project was previously part of the [react-native](https://github.com/facebook/react-native) repository. In this smaller repository it is easier for the team working on Metro to respond to both issues and pull requests. See [react-native#13976](https://github.com/facebook/react-native/issues/13976) for the initial announcement.

78
node_modules/metro/node_modules/ci-info/CHANGELOG.md generated vendored Normal file
View File

@@ -0,0 +1,78 @@
# Changelog
## v2.0.0
Breaking changes:
* Drop support for Node.js end-of-life versions: 0.10, 0.12, 4, 5, 7,
and 9
* Team Foundation Server will now be detected as Azure Pipelines. The
constant `ci.TFS` no longer exists - use `ci.AZURE_PIPELINES` instead
* Remove deprecated `ci.TDDIUM` constant - use `ci.SOLANDO` instead
New features:
* feat: support Azure Pipelines ([#23](https://github.com/watson/ci-info/pull/23))
* feat: support Netlify CI ([#26](https://github.com/watson/ci-info/pull/26))
* feat: support Bitbucket pipelines PR detection ([#27](https://github.com/watson/ci-info/pull/27))
## v1.6.0
* feat: add Sail CI support
* feat: add Buddy support
* feat: add Bitrise support
* feat: detect Jenkins PRs
* feat: detect Drone PRs
## v1.5.1
* fix: use full path to vendors.json
## v1.5.0
* feat: add dsari detection ([#15](https://github.com/watson/ci-info/pull/15))
* feat: add ci.isPR ([#16](https://github.com/watson/ci-info/pull/16))
## v1.4.0
* feat: add Cirrus CI detection ([#13](https://github.com/watson/ci-info/pull/13))
* feat: add Shippable CI detection ([#14](https://github.com/watson/ci-info/pull/14))
## v1.3.1
* chore: reduce npm package size by not including `.github` folder content ([#11](https://github.com/watson/ci-info/pull/11))
## v1.3.0
* feat: add support for Strider CD
* chore: deprecate vendor constant `TDDIUM` in favor of `SOLANO`
* docs: add missing vendor constant to docs
## v1.2.0
* feat: detect solano-ci ([#9](https://github.com/watson/ci-info/pull/9))
## v1.1.3
* fix: fix spelling of Hunson in `ci.name`
## v1.1.2
* fix: no more false positive matches for Jenkins
## v1.1.1
* docs: sort lists of CI servers in README.md
* docs: add missing AWS CodeBuild to the docs
## v1.1.0
* feat: add AWS CodeBuild to CI detection ([#2](https://github.com/watson/ci-info/pull/2))
## v1.0.1
* chore: reduce npm package size by using an `.npmignore` file ([#3](https://github.com/watson/ci-info/pull/3))
## v1.0.0
* Initial release

21
node_modules/metro/node_modules/ci-info/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016-2018 Thomas Watson Steen
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

108
node_modules/metro/node_modules/ci-info/README.md generated vendored Normal file
View File

@@ -0,0 +1,108 @@
# ci-info
Get details about the current Continuous Integration environment.
Please [open an
issue](https://github.com/watson/ci-info/issues/new?template=ci-server-not-detected.md)
if your CI server isn't properly detected :)
[![npm](https://img.shields.io/npm/v/ci-info.svg)](https://www.npmjs.com/package/ci-info)
[![Build status](https://travis-ci.org/watson/ci-info.svg?branch=master)](https://travis-ci.org/watson/ci-info)
[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](https://github.com/feross/standard)
## Installation
```bash
npm install ci-info --save
```
## Usage
```js
var ci = require('ci-info')
if (ci.isCI) {
console.log('The name of the CI server is:', ci.name)
} else {
console.log('This program is not running on a CI server')
}
```
## Supported CI tools
Officially supported CI servers:
| Name | Constant | isPR |
|------|----------|------|
| [AWS CodeBuild](https://aws.amazon.com/codebuild/) | `ci.CODEBUILD` | 🚫 |
| [AppVeyor](http://www.appveyor.com) | `ci.APPVEYOR` | ✅ |
| [Azure Pipelines](https://azure.microsoft.com/en-us/services/devops/pipelines/) | `ci.AZURE_PIPELINES` | ✅ |
| [Bamboo](https://www.atlassian.com/software/bamboo) by Atlassian | `ci.BAMBOO` | 🚫 |
| [Bitbucket Pipelines](https://bitbucket.org/product/features/pipelines) | `ci.BITBUCKET` | ✅ |
| [Bitrise](https://www.bitrise.io/) | `ci.BITRISE` | ✅ |
| [Buddy](https://buddy.works/) | `ci.BUDDY` | ✅ |
| [Buildkite](https://buildkite.com) | `ci.BUILDKITE` | ✅ |
| [CircleCI](http://circleci.com) | `ci.CIRCLE` | ✅ |
| [Cirrus CI](https://cirrus-ci.org) | `ci.CIRRUS` | ✅ |
| [Codeship](https://codeship.com) | `ci.CODESHIP` | 🚫 |
| [Drone](https://drone.io) | `ci.DRONE` | ✅ |
| [dsari](https://github.com/rfinnie/dsari) | `ci.DSARI` | 🚫 |
| [GitLab CI](https://about.gitlab.com/gitlab-ci/) | `ci.GITLAB` | 🚫 |
| [GoCD](https://www.go.cd/) | `ci.GOCD` | 🚫 |
| [Hudson](http://hudson-ci.org) | `ci.HUDSON` | 🚫 |
| [Jenkins CI](https://jenkins-ci.org) | `ci.JENKINS` | ✅ |
| [Magnum CI](https://magnum-ci.com) | `ci.MAGNUM` | 🚫 |
| [Netlify CI](https://www.netlify.com/) | `ci.NETLIFY` | ✅ |
| [Sail CI](https://sail.ci/) | `ci.SAIL` | ✅ |
| [Semaphore](https://semaphoreci.com) | `ci.SEMAPHORE` | ✅ |
| [Shippable](https://www.shippable.com/) | `ci.SHIPPABLE` | ✅ |
| [Solano CI](https://www.solanolabs.com/) | `ci.SOLANO` | ✅ |
| [Strider CD](https://strider-cd.github.io/) | `ci.STRIDER` | 🚫 |
| [TaskCluster](http://docs.taskcluster.net) | `ci.TASKCLUSTER` | 🚫 |
| [TeamCity](https://www.jetbrains.com/teamcity/) by JetBrains | `ci.TEAMCITY` | 🚫 |
| [Travis CI](http://travis-ci.org) | `ci.TRAVIS` | ✅ |
## API
### `ci.name`
Returns a string containing name of the CI server the code is running on.
If CI server is not detected, it returns `null`.
Don't depend on the value of this string not to change for a specific
vendor. If you find your self writing `ci.name === 'Travis CI'`, you
most likely want to use `ci.TRAVIS` instead.
### `ci.isCI`
Returns a boolean. Will be `true` if the code is running on a CI server,
otherwise `false`.
Some CI servers not listed here might still trigger the `ci.isCI`
boolean to be set to `true` if they use certain vendor neutral
environment variables. In those cases `ci.name` will be `null` and no
vendor specific boolean will be set to `true`.
### `ci.isPR`
Returns a boolean if PR detection is supported for the current CI server. Will
be `true` if a PR is being tested, otherwise `false`. If PR detection is
not supported for the current CI server, the value will be `null`.
### `ci.<VENDOR-CONSTANT>`
A vendor specific boolean constant is exposed for each support CI
vendor. A constant will be `true` if the code is determined to run on
the given CI server, otherwise `false`.
Examples of vendor constants are `ci.TRAVIS` or `ci.APPVEYOR`. For a
complete list, see the support table above.
Deprecated vendor constants that will be removed in the next major
release:
- `ci.TDDIUM` (Solano CI) This have been renamed `ci.SOLANO`
## License
[MIT](LICENSE)

66
node_modules/metro/node_modules/ci-info/index.js generated vendored Normal file
View File

@@ -0,0 +1,66 @@
'use strict'
var vendors = require('./vendors.json')
var env = process.env
// Used for testing only
Object.defineProperty(exports, '_vendors', {
value: vendors.map(function (v) { return v.constant })
})
exports.name = null
exports.isPR = null
vendors.forEach(function (vendor) {
var envs = Array.isArray(vendor.env) ? vendor.env : [vendor.env]
var isCI = envs.every(function (obj) {
return checkEnv(obj)
})
exports[vendor.constant] = isCI
if (isCI) {
exports.name = vendor.name
switch (typeof vendor.pr) {
case 'string':
// "pr": "CIRRUS_PR"
exports.isPR = !!env[vendor.pr]
break
case 'object':
if ('env' in vendor.pr) {
// "pr": { "env": "BUILDKITE_PULL_REQUEST", "ne": "false" }
exports.isPR = vendor.pr.env in env && env[vendor.pr.env] !== vendor.pr.ne
} else if ('any' in vendor.pr) {
// "pr": { "any": ["ghprbPullId", "CHANGE_ID"] }
exports.isPR = vendor.pr.any.some(function (key) {
return !!env[key]
})
} else {
// "pr": { "DRONE_BUILD_EVENT": "pull_request" }
exports.isPR = checkEnv(vendor.pr)
}
break
default:
// PR detection not supported for this vendor
exports.isPR = null
}
}
})
exports.isCI = !!(
env.CI || // Travis CI, CircleCI, Cirrus CI, Gitlab CI, Appveyor, CodeShip, dsari
env.CONTINUOUS_INTEGRATION || // Travis CI, Cirrus CI
env.BUILD_NUMBER || // Jenkins, TeamCity
env.RUN_ID || // TaskCluster, dsari
exports.name ||
false
)
function checkEnv (obj) {
if (typeof obj === 'string') return !!env[obj]
return Object.keys(obj).every(function (k) {
return env[k] === obj[k]
})
}

36
node_modules/metro/node_modules/ci-info/package.json generated vendored Normal file
View File

@@ -0,0 +1,36 @@
{
"name": "ci-info",
"version": "2.0.0",
"description": "Get details about the current Continuous Integration environment",
"main": "index.js",
"dependencies": {},
"devDependencies": {
"clear-require": "^1.0.1",
"standard": "^12.0.1",
"tape": "^4.9.1"
},
"scripts": {
"test": "standard && node test.js"
},
"repository": {
"type": "git",
"url": "https://github.com/watson/ci-info.git"
},
"keywords": [
"ci",
"continuous",
"integration",
"test",
"detect"
],
"author": "Thomas Watson Steen <w@tson.dk> (https://twitter.com/wa7son)",
"license": "MIT",
"bugs": {
"url": "https://github.com/watson/ci-info/issues"
},
"homepage": "https://github.com/watson/ci-info",
"coordinates": [
55.778231,
12.593179
]
}

153
node_modules/metro/node_modules/ci-info/vendors.json generated vendored Normal file
View File

@@ -0,0 +1,153 @@
[
{
"name": "AppVeyor",
"constant": "APPVEYOR",
"env": "APPVEYOR",
"pr": "APPVEYOR_PULL_REQUEST_NUMBER"
},
{
"name": "Azure Pipelines",
"constant": "AZURE_PIPELINES",
"env": "SYSTEM_TEAMFOUNDATIONCOLLECTIONURI",
"pr": "SYSTEM_PULLREQUEST_PULLREQUESTID"
},
{
"name": "Bamboo",
"constant": "BAMBOO",
"env": "bamboo_planKey"
},
{
"name": "Bitbucket Pipelines",
"constant": "BITBUCKET",
"env": "BITBUCKET_COMMIT",
"pr": "BITBUCKET_PR_ID"
},
{
"name": "Bitrise",
"constant": "BITRISE",
"env": "BITRISE_IO",
"pr": "BITRISE_PULL_REQUEST"
},
{
"name": "Buddy",
"constant": "BUDDY",
"env": "BUDDY_WORKSPACE_ID",
"pr": "BUDDY_EXECUTION_PULL_REQUEST_ID"
},
{
"name": "Buildkite",
"constant": "BUILDKITE",
"env": "BUILDKITE",
"pr": { "env": "BUILDKITE_PULL_REQUEST", "ne": "false" }
},
{
"name": "CircleCI",
"constant": "CIRCLE",
"env": "CIRCLECI",
"pr": "CIRCLE_PULL_REQUEST"
},
{
"name": "Cirrus CI",
"constant": "CIRRUS",
"env": "CIRRUS_CI",
"pr": "CIRRUS_PR"
},
{
"name": "AWS CodeBuild",
"constant": "CODEBUILD",
"env": "CODEBUILD_BUILD_ARN"
},
{
"name": "Codeship",
"constant": "CODESHIP",
"env": { "CI_NAME": "codeship" }
},
{
"name": "Drone",
"constant": "DRONE",
"env": "DRONE",
"pr": { "DRONE_BUILD_EVENT": "pull_request" }
},
{
"name": "dsari",
"constant": "DSARI",
"env": "DSARI"
},
{
"name": "GitLab CI",
"constant": "GITLAB",
"env": "GITLAB_CI"
},
{
"name": "GoCD",
"constant": "GOCD",
"env": "GO_PIPELINE_LABEL"
},
{
"name": "Hudson",
"constant": "HUDSON",
"env": "HUDSON_URL"
},
{
"name": "Jenkins",
"constant": "JENKINS",
"env": ["JENKINS_URL", "BUILD_ID"],
"pr": { "any": ["ghprbPullId", "CHANGE_ID"] }
},
{
"name": "Magnum CI",
"constant": "MAGNUM",
"env": "MAGNUM"
},
{
"name": "Netlify CI",
"constant": "NETLIFY",
"env": "NETLIFY_BUILD_BASE",
"pr": { "env": "PULL_REQUEST", "ne": "false" }
},
{
"name": "Sail CI",
"constant": "SAIL",
"env": "SAILCI",
"pr": "SAIL_PULL_REQUEST_NUMBER"
},
{
"name": "Semaphore",
"constant": "SEMAPHORE",
"env": "SEMAPHORE",
"pr": "PULL_REQUEST_NUMBER"
},
{
"name": "Shippable",
"constant": "SHIPPABLE",
"env": "SHIPPABLE",
"pr": { "IS_PULL_REQUEST": "true" }
},
{
"name": "Solano CI",
"constant": "SOLANO",
"env": "TDDIUM",
"pr": "TDDIUM_PR_ID"
},
{
"name": "Strider CD",
"constant": "STRIDER",
"env": "STRIDER"
},
{
"name": "TaskCluster",
"constant": "TASKCLUSTER",
"env": ["TASK_ID", "RUN_ID"]
},
{
"name": "TeamCity",
"constant": "TEAMCITY",
"env": "TEAMCITY_VERSION"
},
{
"name": "Travis CI",
"constant": "TRAVIS",
"env": "TRAVIS",
"pr": { "env": "TRAVIS_PULL_REQUEST", "ne": "false" }
}
]

82
node_modules/metro/package.json generated vendored Normal file
View File

@@ -0,0 +1,82 @@
{
"name": "metro",
"version": "0.83.3",
"description": "🚇 The JavaScript bundler for React Native.",
"main": "src/index.js",
"bin": "src/cli.js",
"exports": {
".": "./src/index.js",
"./package.json": "./package.json",
"./private/*": "./src/*.js"
},
"repository": {
"type": "git",
"url": "git@github.com:facebook/metro.git"
},
"scripts": {
"prepare-release": "test -d build && rm -rf src.real && mv src src.real && mv build src",
"cleanup-release": "test ! -e build && mv src build && mv src.real src"
},
"dependencies": {
"@babel/code-frame": "^7.24.7",
"@babel/core": "^7.25.2",
"@babel/generator": "^7.25.0",
"@babel/parser": "^7.25.3",
"@babel/template": "^7.25.0",
"@babel/traverse": "^7.25.3",
"@babel/types": "^7.25.2",
"accepts": "^1.3.7",
"chalk": "^4.0.0",
"ci-info": "^2.0.0",
"connect": "^3.6.5",
"debug": "^4.4.0",
"error-stack-parser": "^2.0.6",
"flow-enums-runtime": "^0.0.6",
"graceful-fs": "^4.2.4",
"hermes-parser": "0.32.0",
"image-size": "^1.0.2",
"invariant": "^2.2.4",
"jest-worker": "^29.7.0",
"jsc-safe-url": "^0.2.2",
"lodash.throttle": "^4.1.1",
"metro-babel-transformer": "0.83.3",
"metro-cache": "0.83.3",
"metro-cache-key": "0.83.3",
"metro-config": "0.83.3",
"metro-core": "0.83.3",
"metro-file-map": "0.83.3",
"metro-resolver": "0.83.3",
"metro-runtime": "0.83.3",
"metro-source-map": "0.83.3",
"metro-symbolicate": "0.83.3",
"metro-transform-plugins": "0.83.3",
"metro-transform-worker": "0.83.3",
"mime-types": "^2.1.27",
"nullthrows": "^1.1.1",
"serialize-error": "^2.1.0",
"source-map": "^0.5.6",
"throat": "^5.0.0",
"ws": "^7.5.10",
"yargs": "^17.6.2"
},
"devDependencies": {
"@babel/plugin-transform-flow-strip-types": "^7.25.2",
"@babel/plugin-transform-modules-commonjs": "^7.24.8",
"@babel/plugin-transform-runtime": "^7.24.7",
"@react-native/babel-preset": "0.78.0",
"@react-native/metro-babel-transformer": "0.78.0",
"babel-jest": "^29.7.0",
"dedent": "^0.7.0",
"jest-snapshot": "^29.7.0",
"jest-snapshot-serializer-raw": "^1.2.0",
"metro-babel-register": "0.83.3",
"metro-memory-fs": "*",
"mock-req": "^0.2.0",
"mock-res": "^0.6.0",
"stack-trace": "^0.0.10"
},
"license": "MIT",
"engines": {
"node": ">=20.19.4"
}
}

25
node_modules/metro/src/Asset.d.ts generated vendored Normal file
View File

@@ -0,0 +1,25 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @oncall react_native
*/
export interface AssetDataWithoutFiles {
readonly __packager_asset: boolean;
readonly fileSystemLocation: string;
readonly hash: string;
readonly height?: number;
readonly httpServerLocation: string;
readonly name: string;
readonly scales: number[];
readonly type: string;
readonly width?: number;
}
export interface AssetData extends AssetDataWithoutFiles {
readonly files: string[];
}

239
node_modules/metro/src/Assets.js generated vendored Normal file
View File

@@ -0,0 +1,239 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.getAsset = getAsset;
exports.getAssetData = getAssetData;
exports.getAssetFiles = getAssetFiles;
exports.getAssetSize = getAssetSize;
exports.isAssetTypeAnImage = isAssetTypeAnImage;
var _pathUtils = require("./lib/pathUtils");
var AssetPaths = _interopRequireWildcard(
require("./node-haste/lib/AssetPaths"),
);
var _crypto = _interopRequireDefault(require("crypto"));
var _fs = _interopRequireDefault(require("fs"));
var _imageSize = _interopRequireDefault(require("image-size"));
var _path = _interopRequireDefault(require("path"));
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
function _getRequireWildcardCache(e) {
if ("function" != typeof WeakMap) return null;
var r = new WeakMap(),
t = new WeakMap();
return (_getRequireWildcardCache = function (e) {
return e ? t : r;
})(e);
}
function _interopRequireWildcard(e, r) {
if (!r && e && e.__esModule) return e;
if (null === e || ("object" != typeof e && "function" != typeof e))
return { default: e };
var t = _getRequireWildcardCache(r);
if (t && t.has(e)) return t.get(e);
var n = { __proto__: null },
a = Object.defineProperty && Object.getOwnPropertyDescriptor;
for (var u in e)
if ("default" !== u && {}.hasOwnProperty.call(e, u)) {
var i = a ? Object.getOwnPropertyDescriptor(e, u) : null;
i && (i.get || i.set) ? Object.defineProperty(n, u, i) : (n[u] = e[u]);
}
return ((n.default = e), t && t.set(e, n), n);
}
function isAssetTypeAnImage(type) {
return (
[
"png",
"jpg",
"jpeg",
"bmp",
"gif",
"webp",
"psd",
"svg",
"tiff",
"ktx",
].indexOf(type) !== -1
);
}
function getAssetSize(type, content, filePath) {
if (!isAssetTypeAnImage(type)) {
return null;
}
if (content.length === 0) {
throw new Error(`Image asset \`${filePath}\` cannot be an empty file.`);
}
const { width, height } = (0, _imageSize.default)(content);
return {
width,
height,
};
}
function buildAssetMap(dir, files, platform) {
const platforms = new Set(platform != null ? [platform] : []);
const assets = files.map((file) => AssetPaths.tryParse(file, platforms));
const map = new Map();
assets.forEach(function (asset, i) {
if (asset == null) {
return;
}
const file = files[i];
const assetKey = getAssetKey(asset.assetName, asset.platform);
let record = map.get(assetKey);
if (!record) {
record = {
scales: [],
files: [],
};
map.set(assetKey, record);
}
let insertIndex;
const length = record.scales.length;
for (insertIndex = 0; insertIndex < length; insertIndex++) {
if (asset.resolution < record.scales[insertIndex]) {
break;
}
}
record.scales.splice(insertIndex, 0, asset.resolution);
record.files.splice(insertIndex, 0, _path.default.join(dir, file));
});
return map;
}
function getAssetKey(assetName, platform) {
if (platform != null) {
return `${assetName} : ${platform}`;
} else {
return assetName;
}
}
async function getAbsoluteAssetRecord(assetPath, platform = null) {
const filename = _path.default.basename(assetPath);
const dir = _path.default.dirname(assetPath);
const files = await _fs.default.promises.readdir(dir);
const assetData = AssetPaths.parse(
filename,
new Set(platform != null ? [platform] : []),
);
const map = buildAssetMap(dir, files, platform);
let record;
if (platform != null) {
record =
map.get(getAssetKey(assetData.assetName, platform)) ||
map.get(assetData.assetName);
} else {
record = map.get(assetData.assetName);
}
if (!record) {
throw new Error(
`Asset not found: ${assetPath} for platform: ${platform ?? "(unspecified)"}`,
);
}
return record;
}
async function getAbsoluteAssetInfo(assetPath, platform = null) {
const nameData = AssetPaths.parse(
assetPath,
new Set(platform != null ? [platform] : []),
);
const { name, type } = nameData;
const { scales, files } = await getAbsoluteAssetRecord(assetPath, platform);
const hasher = _crypto.default.createHash("md5");
const fileData = await Promise.all(
files.map((file) => _fs.default.promises.readFile(file)),
);
for (const data of fileData) {
hasher.update(data);
}
return {
files,
hash: hasher.digest("hex"),
name,
scales,
type,
};
}
async function getAssetData(
assetPath,
localPath,
assetDataPlugins,
platform = null,
publicPath,
) {
let assetUrlPath = localPath.startsWith("..")
? publicPath.replace(/\/$/, "") + "/" + _path.default.dirname(localPath)
: _path.default.join(publicPath, _path.default.dirname(localPath));
assetUrlPath = (0, _pathUtils.normalizePathSeparatorsToPosix)(assetUrlPath);
const isImage = isAssetTypeAnImage(_path.default.extname(assetPath).slice(1));
const assetInfo = await getAbsoluteAssetInfo(assetPath, platform);
const isImageInput = assetInfo.files[0].includes(".zip/")
? _fs.default.readFileSync(assetInfo.files[0])
: assetInfo.files[0];
const dimensions = isImage ? (0, _imageSize.default)(isImageInput) : null;
const scale = assetInfo.scales[0];
const assetData = {
__packager_asset: true,
fileSystemLocation: _path.default.dirname(assetPath),
httpServerLocation: assetUrlPath,
width: dimensions ? dimensions.width / scale : undefined,
height: dimensions ? dimensions.height / scale : undefined,
scales: assetInfo.scales,
files: assetInfo.files,
hash: assetInfo.hash,
name: assetInfo.name,
type: assetInfo.type,
};
return await applyAssetDataPlugins(assetDataPlugins, assetData);
}
async function applyAssetDataPlugins(assetDataPlugins, assetData) {
if (!assetDataPlugins.length) {
return assetData;
}
const [currentAssetPlugin, ...remainingAssetPlugins] = assetDataPlugins;
const assetPluginFunction = require(currentAssetPlugin);
const resultAssetData = await assetPluginFunction(assetData);
return await applyAssetDataPlugins(remainingAssetPlugins, resultAssetData);
}
async function getAssetFiles(assetPath, platform = null) {
const assetData = await getAbsoluteAssetRecord(assetPath, platform);
return assetData.files;
}
async function getAsset(
relativePath,
projectRoot,
watchFolders,
platform = null,
assetExts,
) {
const assetData = AssetPaths.parse(
relativePath,
new Set(platform != null ? [platform] : []),
);
const absolutePath = _path.default.resolve(projectRoot, relativePath);
if (!assetExts.includes(assetData.type)) {
throw new Error(
`'${relativePath}' cannot be loaded as its extension is not registered in assetExts`,
);
}
if (!pathBelongsToRoots(absolutePath, [projectRoot, ...watchFolders])) {
throw new Error(
`'${relativePath}' could not be found, because it cannot be found in the project root or any watch folder`,
);
}
const record = await getAbsoluteAssetRecord(absolutePath, platform);
for (let i = 0; i < record.scales.length; i++) {
if (record.scales[i] >= assetData.resolution) {
return _fs.default.promises.readFile(record.files[i]);
}
}
return _fs.default.promises.readFile(record.files[record.files.length - 1]);
}
function pathBelongsToRoots(pathToCheck, roots) {
for (const rootFolder of roots) {
if (pathToCheck.startsWith(_path.default.resolve(rootFolder))) {
return true;
}
}
return false;
}

327
node_modules/metro/src/Assets.js.flow generated vendored Normal file
View File

@@ -0,0 +1,327 @@
/**
* Copyright (c) Meta Platforms, Inc. and 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
* @oncall react_native
*/
import type {AssetPath} from './node-haste/lib/AssetPaths';
import {normalizePathSeparatorsToPosix} from './lib/pathUtils';
import * as AssetPaths from './node-haste/lib/AssetPaths';
import crypto from 'crypto';
import fs from 'fs';
import getImageSize from 'image-size';
import path from 'path';
export type AssetInfo = {
+files: Array<string>,
+hash: string,
+name: string,
+scales: Array<number>,
+type: string,
};
export type AssetDataWithoutFiles = {
+__packager_asset: boolean,
+fileSystemLocation: string,
+hash: string,
+height: ?number,
+httpServerLocation: string,
+name: string,
+scales: Array<number>,
+type: string,
+width: ?number,
...
};
export type AssetDataFiltered = {
+__packager_asset: boolean,
+hash: string,
+height: ?number,
+httpServerLocation: string,
+name: string,
+scales: Array<number>,
+type: string,
+width: ?number,
...
};
// Test extension against all types supported by image-size module.
// If it's not one of these, we won't treat it as an image.
export function isAssetTypeAnImage(type: string): boolean {
return (
[
'png',
'jpg',
'jpeg',
'bmp',
'gif',
'webp',
'psd',
'svg',
'tiff',
'ktx',
].indexOf(type) !== -1
);
}
export function getAssetSize(
type: string,
content: Buffer,
filePath: string,
): ?{+width: number, +height: number} {
if (!isAssetTypeAnImage(type)) {
return null;
}
if (content.length === 0) {
throw new Error(`Image asset \`${filePath}\` cannot be an empty file.`);
}
const {width, height} = getImageSize(content);
return {width, height};
}
export type AssetData = AssetDataWithoutFiles & {+files: Array<string>, ...};
export type AssetDataPlugin = (
assetData: AssetData,
) => AssetData | Promise<AssetData>;
function buildAssetMap(
dir: string,
files: $ReadOnlyArray<string>,
platform: ?string,
): Map<string, {files: Array<string>, scales: Array<number>}> {
const platforms = new Set(platform != null ? [platform] : []);
const assets = files.map((file: string) =>
AssetPaths.tryParse(file, platforms),
);
const map = new Map<string, {files: Array<string>, scales: Array<number>}>();
assets.forEach(function (asset: ?AssetPath, i: number) {
if (asset == null) {
return;
}
const file = files[i];
const assetKey = getAssetKey(asset.assetName, asset.platform);
let record = map.get(assetKey);
if (!record) {
record = {
scales: [],
files: [],
};
map.set(assetKey, record);
}
let insertIndex;
const length = record.scales.length;
for (insertIndex = 0; insertIndex < length; insertIndex++) {
if (asset.resolution < record.scales[insertIndex]) {
break;
}
}
record.scales.splice(insertIndex, 0, asset.resolution);
record.files.splice(insertIndex, 0, path.join(dir, file));
});
return map;
}
function getAssetKey(assetName: string, platform: ?string): string {
if (platform != null) {
return `${assetName} : ${platform}`;
} else {
return assetName;
}
}
async function getAbsoluteAssetRecord(
assetPath: string,
platform: ?string = null,
): Promise<{files: Array<string>, scales: Array<number>}> {
const filename = path.basename(assetPath);
const dir = path.dirname(assetPath);
const files = await fs.promises.readdir(dir);
const assetData = AssetPaths.parse(
filename,
new Set(platform != null ? [platform] : []),
);
const map = buildAssetMap(dir, files, platform);
let record;
if (platform != null) {
record =
map.get(getAssetKey(assetData.assetName, platform)) ||
map.get(assetData.assetName);
} else {
record = map.get(assetData.assetName);
}
if (!record) {
throw new Error(
`Asset not found: ${assetPath} for platform: ${
platform ?? '(unspecified)'
}`,
);
}
return record;
}
async function getAbsoluteAssetInfo(
assetPath: string,
platform: ?string = null,
): Promise<AssetInfo> {
const nameData = AssetPaths.parse(
assetPath,
new Set(platform != null ? [platform] : []),
);
const {name, type} = nameData;
const {scales, files} = await getAbsoluteAssetRecord(assetPath, platform);
const hasher = crypto.createHash('md5');
const fileData = await Promise.all(
files.map(file => fs.promises.readFile(file)),
);
for (const data of fileData) {
hasher.update(data);
}
return {files, hash: hasher.digest('hex'), name, scales, type};
}
export async function getAssetData(
assetPath: string,
localPath: string,
assetDataPlugins: $ReadOnlyArray<string>,
platform: ?string = null,
publicPath: string,
): Promise<AssetData> {
// If the path of the asset is outside of the projectRoot, we don't want to
// use `path.join` since this will generate an incorrect URL path. In that
// case we just concatenate the publicPath with the relative path.
let assetUrlPath = localPath.startsWith('..')
? publicPath.replace(/\/$/, '') + '/' + path.dirname(localPath)
: path.join(publicPath, path.dirname(localPath));
// On Windows, change backslashes to slashes to get proper URL path from file path.
assetUrlPath = normalizePathSeparatorsToPosix(assetUrlPath);
const isImage = isAssetTypeAnImage(path.extname(assetPath).slice(1));
const assetInfo = await getAbsoluteAssetInfo(assetPath, platform);
const isImageInput = assetInfo.files[0].includes('.zip/')
? fs.readFileSync(assetInfo.files[0])
: assetInfo.files[0];
const dimensions = isImage ? getImageSize(isImageInput) : null;
const scale = assetInfo.scales[0];
const assetData = {
__packager_asset: true,
fileSystemLocation: path.dirname(assetPath),
httpServerLocation: assetUrlPath,
width: dimensions ? dimensions.width / scale : undefined,
height: dimensions ? dimensions.height / scale : undefined,
scales: assetInfo.scales,
files: assetInfo.files,
hash: assetInfo.hash,
name: assetInfo.name,
type: assetInfo.type,
};
return await applyAssetDataPlugins(assetDataPlugins, assetData);
}
async function applyAssetDataPlugins(
assetDataPlugins: $ReadOnlyArray<string>,
assetData: AssetData,
): Promise<AssetData> {
if (!assetDataPlugins.length) {
return assetData;
}
const [currentAssetPlugin, ...remainingAssetPlugins] = assetDataPlugins;
// $FlowFixMe[unsupported-syntax]: impossible to type a dynamic require.
const assetPluginFunction: AssetDataPlugin = require(currentAssetPlugin);
const resultAssetData = await assetPluginFunction(assetData);
return await applyAssetDataPlugins(remainingAssetPlugins, resultAssetData);
}
/**
* Returns all the associated files (for different resolutions) of an asset.
**/
export async function getAssetFiles(
assetPath: string,
platform: ?string = null,
): Promise<Array<string>> {
const assetData = await getAbsoluteAssetRecord(assetPath, platform);
return assetData.files;
}
/**
* Return a buffer with the actual image given a request for an image by path.
* The relativePath can contain a resolution postfix, in this case we need to
* find that image (or the closest one to it's resolution) in one of the
* project roots:
*
* 1. We first parse the directory of the asset
* 2. We then build a map of all assets and their scales in this directory
* 3. Then try to pick platform-specific asset records
* 4. Then pick the closest resolution (rounding up) to the requested one
*/
export async function getAsset(
relativePath: string,
projectRoot: string,
watchFolders: $ReadOnlyArray<string>,
platform: ?string = null,
assetExts: $ReadOnlyArray<string>,
): Promise<Buffer> {
const assetData = AssetPaths.parse(
relativePath,
new Set(platform != null ? [platform] : []),
);
const absolutePath = path.resolve(projectRoot, relativePath);
if (!assetExts.includes(assetData.type)) {
throw new Error(
`'${relativePath}' cannot be loaded as its extension is not registered in assetExts`,
);
}
if (!pathBelongsToRoots(absolutePath, [projectRoot, ...watchFolders])) {
throw new Error(
`'${relativePath}' could not be found, because it cannot be found in the project root or any watch folder`,
);
}
const record = await getAbsoluteAssetRecord(absolutePath, platform);
for (let i = 0; i < record.scales.length; i++) {
if (record.scales[i] >= assetData.resolution) {
return fs.promises.readFile(record.files[i]);
}
}
return fs.promises.readFile(record.files[record.files.length - 1]);
}
function pathBelongsToRoots(
pathToCheck: string,
roots: $ReadOnlyArray<string>,
): boolean {
for (const rootFolder of roots) {
if (pathToCheck.startsWith(path.resolve(rootFolder))) {
return true;
}
}
return false;
}

39
node_modules/metro/src/Bundler.d.ts generated vendored Normal file
View File

@@ -0,0 +1,39 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @oncall react_native
*/
import type {TransformResultWithSource} from './DeltaBundler';
import type {TransformOptions} from './DeltaBundler/Worker';
import type DependencyGraph from './node-haste/DependencyGraph';
import type {EventEmitter} from 'events';
import type {ConfigT} from 'metro-config';
export interface BundlerOptions {
readonly hasReducedPerformance?: boolean;
readonly watch?: boolean;
}
export default class Bundler {
constructor(config: ConfigT, options?: BundlerOptions);
getWatcher(): EventEmitter;
end(): Promise<void>;
getDependencyGraph(): Promise<DependencyGraph>;
transformFile(
filePath: string,
transformOptions: TransformOptions,
/** Optionally provide the file contents, this can be used to provide virtual contents for a file. */
fileBuffer?: Buffer,
): Promise<TransformResultWithSource<void>>;
ready(): Promise<void>;
}

65
node_modules/metro/src/Bundler.js generated vendored Normal file
View File

@@ -0,0 +1,65 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
var _Transformer = _interopRequireDefault(
require("./DeltaBundler/Transformer"),
);
var _DependencyGraph = _interopRequireDefault(
require("./node-haste/DependencyGraph"),
);
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
class Bundler {
constructor(config, options) {
this._depGraph = new _DependencyGraph.default(config, options);
this._initializedPromise = this._depGraph
.ready()
.then(() => {
config.reporter.update({
type: "transformer_load_started",
});
this._transformer = new _Transformer.default(config, {
getOrComputeSha1: (filePath) =>
this._depGraph.getOrComputeSha1(filePath),
});
config.reporter.update({
type: "transformer_load_done",
});
})
.catch((error) => {
console.error("Failed to construct transformer: ", error);
config.reporter.update({
type: "transformer_load_failed",
error,
});
});
}
getWatcher() {
return this._depGraph.getWatcher();
}
async end() {
await this.ready();
await this._transformer.end();
await this._depGraph.end();
}
async getDependencyGraph() {
await this.ready();
return this._depGraph;
}
async transformFile(filePath, transformOptions, fileBuffer) {
await this.ready();
return this._transformer.transformFile(
filePath,
transformOptions,
fileBuffer,
);
}
async ready() {
await this._initializedPromise;
}
}
exports.default = Bundler;

90
node_modules/metro/src/Bundler.js.flow generated vendored Normal file
View File

@@ -0,0 +1,90 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/
import type {TransformResultWithSource} from './DeltaBundler';
import type {TransformOptions} from './DeltaBundler/Worker';
import type EventEmitter from 'events';
import type {ConfigT} from 'metro-config';
import Transformer from './DeltaBundler/Transformer';
import DependencyGraph from './node-haste/DependencyGraph';
export type BundlerOptions = $ReadOnly<{
hasReducedPerformance?: boolean,
watch?: boolean,
}>;
export default class Bundler {
_depGraph: DependencyGraph;
_initializedPromise: Promise<void>;
_transformer: Transformer;
constructor(config: ConfigT, options?: BundlerOptions) {
this._depGraph = new DependencyGraph(config, options);
this._initializedPromise = this._depGraph
.ready()
.then(() => {
config.reporter.update({type: 'transformer_load_started'});
this._transformer = new Transformer(config, {
getOrComputeSha1: filePath =>
this._depGraph.getOrComputeSha1(filePath),
});
config.reporter.update({type: 'transformer_load_done'});
})
.catch(error => {
console.error('Failed to construct transformer: ', error);
config.reporter.update({
type: 'transformer_load_failed',
error,
});
});
}
getWatcher(): EventEmitter {
return this._depGraph.getWatcher();
}
async end(): Promise<void> {
await this.ready();
await this._transformer.end();
await this._depGraph.end();
}
async getDependencyGraph(): Promise<DependencyGraph> {
await this.ready();
return this._depGraph;
}
async transformFile(
filePath: string,
transformOptions: TransformOptions,
/** Optionally provide the file contents, this can be used to provide virtual contents for a file. */
fileBuffer?: Buffer,
): Promise<TransformResultWithSource<>> {
// We need to be sure that the DependencyGraph has been initialized.
// TODO: Remove this ugly hack!
await this.ready();
return this._transformer.transformFile(
filePath,
transformOptions,
fileBuffer,
);
}
// Waits for the bundler to become ready.
async ready(): Promise<void> {
await this._initializedPromise;
}
}

118
node_modules/metro/src/Bundler/util.js generated vendored Normal file
View File

@@ -0,0 +1,118 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.createRamBundleGroups = createRamBundleGroups;
exports.generateAssetCodeFileAst = generateAssetCodeFileAst;
var babylon = _interopRequireWildcard(require("@babel/parser"));
var _template = _interopRequireDefault(require("@babel/template"));
var babelTypes = _interopRequireWildcard(require("@babel/types"));
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
function _getRequireWildcardCache(e) {
if ("function" != typeof WeakMap) return null;
var r = new WeakMap(),
t = new WeakMap();
return (_getRequireWildcardCache = function (e) {
return e ? t : r;
})(e);
}
function _interopRequireWildcard(e, r) {
if (!r && e && e.__esModule) return e;
if (null === e || ("object" != typeof e && "function" != typeof e))
return { default: e };
var t = _getRequireWildcardCache(r);
if (t && t.has(e)) return t.get(e);
var n = { __proto__: null },
a = Object.defineProperty && Object.getOwnPropertyDescriptor;
for (var u in e)
if ("default" !== u && {}.hasOwnProperty.call(e, u)) {
var i = a ? Object.getOwnPropertyDescriptor(e, u) : null;
i && (i.get || i.set) ? Object.defineProperty(n, u, i) : (n[u] = e[u]);
}
return ((n.default = e), t && t.set(e, n), n);
}
const assetPropertyBlockList = new Set(["files", "fileSystemLocation", "path"]);
function generateAssetCodeFileAst(assetRegistryPath, assetDescriptor) {
const properDescriptor = filterObject(
assetDescriptor,
assetPropertyBlockList,
);
const descriptorAst = babylon.parseExpression(
JSON.stringify(properDescriptor),
);
const t = babelTypes;
const buildRequire = _template.default.statement(`
module.exports = require(ASSET_REGISTRY_PATH).registerAsset(DESCRIPTOR_AST)
`);
return t.file(
t.program([
buildRequire({
ASSET_REGISTRY_PATH: t.stringLiteral(assetRegistryPath),
DESCRIPTOR_AST: descriptorAst,
}),
]),
);
}
function filterObject(object, blockList) {
const copied = {
...object,
};
for (const key of blockList) {
delete copied[key];
}
return copied;
}
function createRamBundleGroups(ramGroups, groupableModules, subtree) {
const byPath = new Map();
const byId = new Map();
groupableModules.forEach((m) => {
byPath.set(m.sourcePath, m);
byId.set(m.id, m.sourcePath);
});
const result = new Map(
ramGroups.map((modulePath) => {
const root = byPath.get(modulePath);
if (root == null) {
throw Error(`Group root ${modulePath} is not part of the bundle`);
}
return [root.id, new Set(subtree(root, byPath))];
}),
);
if (ramGroups.length > 1) {
const all = new ArrayMap();
for (const [parent, children] of result) {
for (const module of children) {
all.get(module).push(parent);
}
}
const doubles = filter(all, ([, parents]) => parents.length > 1);
for (const [moduleId, parents] of doubles) {
const parentNames = parents.map(byId.get, byId);
const lastName = parentNames.pop();
throw new Error(
`Module ${byId.get(moduleId) || moduleId} belongs to groups ${parentNames.join(", ")}, and ${String(lastName)}. Ensure that each module is only part of one group.`,
);
}
}
return result;
}
function* filter(iterator, predicate) {
for (const value of iterator) {
if (predicate(value)) {
yield value;
}
}
}
class ArrayMap extends Map {
get(key) {
let array = super.get(key);
if (!array) {
array = [];
this.set(key, array);
}
return array;
}
}

146
node_modules/metro/src/Bundler/util.js.flow generated vendored Normal file
View File

@@ -0,0 +1,146 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/
import type {AssetDataFiltered, AssetDataWithoutFiles} from '../Assets';
import type {ModuleTransportLike} from '../shared/types';
import type {File} from '@babel/types';
import * as babylon from '@babel/parser';
import template from '@babel/template';
import * as babelTypes from '@babel/types';
type SubTree<T: ModuleTransportLike> = (
moduleTransport: T,
moduleTransportsByPath: Map<string, T>,
) => Iterable<number>;
const assetPropertyBlockList = new Set(['files', 'fileSystemLocation', 'path']);
export function generateAssetCodeFileAst(
assetRegistryPath: string,
assetDescriptor: AssetDataWithoutFiles,
): File {
const properDescriptor = filterObject(
assetDescriptor,
assetPropertyBlockList,
);
// {...}
const descriptorAst = babylon.parseExpression(
JSON.stringify(properDescriptor),
);
const t = babelTypes;
// require('AssetRegistry').registerAsset({...})
const buildRequire = template.statement(`
module.exports = require(ASSET_REGISTRY_PATH).registerAsset(DESCRIPTOR_AST)
`);
return t.file(
t.program([
buildRequire({
ASSET_REGISTRY_PATH: t.stringLiteral(assetRegistryPath),
DESCRIPTOR_AST: descriptorAst,
}),
]),
);
}
function filterObject(
object: AssetDataWithoutFiles,
blockList: Set<string>,
): AssetDataFiltered {
const copied = {...object};
for (const key of blockList) {
// $FlowFixMe[prop-missing]
delete copied[key];
}
return copied;
}
export function createRamBundleGroups<T: ModuleTransportLike>(
ramGroups: $ReadOnlyArray<string>,
groupableModules: $ReadOnlyArray<T>,
subtree: SubTree<T>,
): Map<number, Set<number>> {
// build two maps that allow to lookup module data
// by path or (numeric) module id;
const byPath: Map<string, T> = new Map();
const byId: Map<number, string> = new Map();
groupableModules.forEach((m: T) => {
byPath.set(m.sourcePath, m);
byId.set(m.id, m.sourcePath);
});
// build a map of group root IDs to an array of module IDs in the group
const result: Map<number, Set<number>> = new Map(
ramGroups.map((modulePath: string) => {
const root = byPath.get(modulePath);
if (root == null) {
throw Error(`Group root ${modulePath} is not part of the bundle`);
}
return [
root.id,
// `subtree` yields the IDs of all transitive dependencies of a module
new Set(subtree(root, byPath)),
];
}),
);
if (ramGroups.length > 1) {
// build a map of all grouped module IDs to an array of group root IDs
const all = new ArrayMap<number, number>();
for (const [parent, children] of result) {
for (const module of children) {
all.get(module).push(parent);
}
}
// find all module IDs that are part of more than one group
const doubles = filter(all, ([, parents]) => parents.length > 1);
for (const [moduleId, parents] of doubles) {
// $FlowFixMe[method-unbinding] added when improving typing for this parameters
const parentNames = parents.map(byId.get, byId);
const lastName = parentNames.pop();
throw new Error(
`Module ${
byId.get(moduleId) || moduleId
} belongs to groups ${parentNames.join(', ')}, and ${String(
lastName,
)}. Ensure that each module is only part of one group.`,
);
}
}
return result;
}
function* filter<A: number, B: number>(
iterator: ArrayMap<A, B>,
predicate: ([A, Array<B>]) => boolean,
): Generator<[A, Array<B>], void, void> {
for (const value of iterator) {
if (predicate(value)) {
yield value;
}
}
}
class ArrayMap<K, V> extends Map<K, Array<V>> {
get(key: K): Array<V> {
let array = super.get(key);
if (!array) {
array = [];
this.set(key, array);
}
return array;
}
}

58
node_modules/metro/src/DeltaBundler.d.ts generated vendored Normal file
View File

@@ -0,0 +1,58 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @oncall react_native
*/
import type {
DeltaResult,
Graph,
MixedOutput,
Options,
ReadOnlyGraph,
} from './DeltaBundler/types';
import type {EventEmitter} from 'events';
export type {
DeltaResult,
Graph,
Dependencies,
MixedOutput,
Module,
ReadOnlyGraph,
TransformFn,
TransformResult,
TransformResultDependency,
TransformResultWithSource,
} from './DeltaBundler/types';
export default class DeltaBundler<T = MixedOutput> {
constructor(changeEventSource: EventEmitter);
end(): void;
getDependencies(
entryPoints: ReadonlyArray<string>,
options: Options<T>,
): Promise<ReadOnlyGraph<T>['dependencies']>;
buildGraph(
entryPoints: ReadonlyArray<string>,
options: Options<T>,
): Promise<Graph<T>>;
getDelta(
graph: Graph<T>,
{
reset,
shallow,
}: {
reset: boolean;
shallow: boolean;
},
): Promise<DeltaResult<T>>;
listen(graph: Graph<T>, callback: () => Promise<void>): () => void;
endGraph(graph: Graph<T>): void;
}

79
node_modules/metro/src/DeltaBundler.js generated vendored Normal file
View File

@@ -0,0 +1,79 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
var _DeltaCalculator = _interopRequireDefault(
require("./DeltaBundler/DeltaCalculator"),
);
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
class DeltaBundler {
_deltaCalculators = new Map();
constructor(changeEventSource) {
this._changeEventSource = changeEventSource;
}
end() {
this._deltaCalculators.forEach((deltaCalculator) => deltaCalculator.end());
this._deltaCalculators = new Map();
}
async getDependencies(entryPoints, options) {
const deltaCalculator = new _DeltaCalculator.default(
new Set(entryPoints),
this._changeEventSource,
options,
);
await deltaCalculator.getDelta({
reset: true,
shallow: options.shallow,
});
const graph = deltaCalculator.getGraph();
deltaCalculator.end();
return graph.dependencies;
}
async buildGraph(entryPoints, options) {
const deltaCalculator = new _DeltaCalculator.default(
new Set(entryPoints),
this._changeEventSource,
options,
);
await deltaCalculator.getDelta({
reset: true,
shallow: options.shallow,
});
const graph = deltaCalculator.getGraph();
this._deltaCalculators.set(graph, deltaCalculator);
return graph;
}
async getDelta(graph, { reset, shallow }) {
const deltaCalculator = this._deltaCalculators.get(graph);
if (!deltaCalculator) {
throw new Error("Graph not found");
}
return await deltaCalculator.getDelta({
reset,
shallow,
});
}
listen(graph, callback) {
const deltaCalculator = this._deltaCalculators.get(graph);
if (!deltaCalculator) {
throw new Error("Graph not found");
}
deltaCalculator.on("change", callback);
return () => {
deltaCalculator.removeListener("change", callback);
};
}
endGraph(graph) {
const deltaCalculator = this._deltaCalculators.get(graph);
if (!deltaCalculator) {
throw new Error("Graph not found");
}
deltaCalculator.end();
this._deltaCalculators.delete(graph);
}
}
exports.default = DeltaBundler;

140
node_modules/metro/src/DeltaBundler.js.flow generated vendored Normal file
View File

@@ -0,0 +1,140 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/
import type {
DeltaResult,
Graph,
// eslint-disable-next-line no-unused-vars
MixedOutput,
Options,
ReadOnlyGraph,
} from './DeltaBundler/types';
import type EventEmitter from 'events';
import DeltaCalculator from './DeltaBundler/DeltaCalculator';
export type {
DeltaResult,
Graph,
Dependencies,
MixedOutput,
Module,
ReadOnlyGraph,
TransformFn,
TransformResult,
TransformResultDependency,
TransformResultWithSource,
} from './DeltaBundler/types';
/**
* `DeltaBundler` uses the `DeltaTransformer` to build bundle deltas. This
* module handles all the transformer instances so it can support multiple
* concurrent clients requesting their own deltas. This is done through the
* `clientId` param (which maps a client to a specific delta transformer).
*/
export default class DeltaBundler<T = MixedOutput> {
_changeEventSource: EventEmitter;
_deltaCalculators: Map<Graph<T>, DeltaCalculator<T>> = new Map();
constructor(changeEventSource: EventEmitter) {
this._changeEventSource = changeEventSource;
}
end(): void {
this._deltaCalculators.forEach((deltaCalculator: DeltaCalculator<T>) =>
deltaCalculator.end(),
);
this._deltaCalculators = new Map();
}
async getDependencies(
entryPoints: $ReadOnlyArray<string>,
options: Options<T>,
): Promise<ReadOnlyGraph<T>['dependencies']> {
const deltaCalculator = new DeltaCalculator(
new Set(entryPoints),
this._changeEventSource,
options,
);
await deltaCalculator.getDelta({reset: true, shallow: options.shallow});
const graph = deltaCalculator.getGraph();
deltaCalculator.end();
return graph.dependencies;
}
// Note: the graph returned by this function needs to be ended when finished
// so that we don't leak graphs that are not reachable.
// To get just the dependencies, use getDependencies which will not leak graphs.
async buildGraph(
entryPoints: $ReadOnlyArray<string>,
options: Options<T>,
): Promise<Graph<T>> {
const deltaCalculator = new DeltaCalculator(
new Set(entryPoints),
this._changeEventSource,
options,
);
await deltaCalculator.getDelta({reset: true, shallow: options.shallow});
const graph = deltaCalculator.getGraph();
this._deltaCalculators.set(graph, deltaCalculator);
return graph;
}
async getDelta(
graph: Graph<T>,
{
reset,
shallow,
}: {
reset: boolean,
shallow: boolean,
...
},
): Promise<DeltaResult<T>> {
const deltaCalculator = this._deltaCalculators.get(graph);
if (!deltaCalculator) {
throw new Error('Graph not found');
}
return await deltaCalculator.getDelta({reset, shallow});
}
listen(graph: Graph<T>, callback: () => Promise<void>): () => void {
const deltaCalculator = this._deltaCalculators.get(graph);
if (!deltaCalculator) {
throw new Error('Graph not found');
}
deltaCalculator.on('change', callback);
return () => {
deltaCalculator.removeListener('change', callback);
};
}
endGraph(graph: Graph<T>): void {
const deltaCalculator = this._deltaCalculators.get(graph);
if (!deltaCalculator) {
throw new Error('Graph not found');
}
deltaCalculator.end();
this._deltaCalculators.delete(graph);
}
}

209
node_modules/metro/src/DeltaBundler/DeltaCalculator.js generated vendored Normal file
View File

@@ -0,0 +1,209 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
var _Graph = require("./Graph");
var _events = _interopRequireDefault(require("events"));
var _path = _interopRequireDefault(require("path"));
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
const debug = require("debug")("Metro:DeltaCalculator");
class DeltaCalculator extends _events.default {
_deletedFiles = new Set();
_modifiedFiles = new Set();
_addedFiles = new Set();
_requiresReset = false;
constructor(entryPoints, changeEventSource, options) {
super();
this._options = options;
this._changeEventSource = changeEventSource;
this._graph = new _Graph.Graph({
entryPoints,
transformOptions: this._options.transformOptions,
});
this._changeEventSource.on("change", this._handleMultipleFileChanges);
}
end() {
this._changeEventSource.removeListener(
"change",
this._handleMultipleFileChanges,
);
this.removeAllListeners();
this._graph = new _Graph.Graph({
entryPoints: this._graph.entryPoints,
transformOptions: this._options.transformOptions,
});
this._modifiedFiles = new Set();
this._deletedFiles = new Set();
this._addedFiles = new Set();
}
async getDelta({ reset, shallow }) {
debug("Calculating delta (reset: %s, shallow: %s)", reset, shallow);
if (this._currentBuildPromise) {
await this._currentBuildPromise;
}
const modifiedFiles = this._modifiedFiles;
this._modifiedFiles = new Set();
const deletedFiles = this._deletedFiles;
this._deletedFiles = new Set();
const addedFiles = this._addedFiles;
this._addedFiles = new Set();
const requiresReset = this._requiresReset;
this._requiresReset = false;
if (requiresReset) {
const markModified = (file) => {
if (!addedFiles.has(file) && !deletedFiles.has(file)) {
modifiedFiles.add(file);
}
};
this._graph.dependencies.forEach((_, key) => markModified(key));
this._graph.entryPoints.forEach(markModified);
}
this._currentBuildPromise = this._getChangedDependencies(
modifiedFiles,
deletedFiles,
addedFiles,
);
let result;
try {
result = await this._currentBuildPromise;
} catch (error) {
modifiedFiles.forEach((file) => this._modifiedFiles.add(file));
deletedFiles.forEach((file) => this._deletedFiles.add(file));
addedFiles.forEach((file) => this._addedFiles.add(file));
throw error;
} finally {
this._currentBuildPromise = null;
}
if (reset) {
this._graph.reorderGraph({
shallow,
});
return {
added: this._graph.dependencies,
modified: new Map(),
deleted: new Set(),
reset: true,
};
}
return result;
}
getGraph() {
return this._graph;
}
_handleMultipleFileChanges = (changeEvent) => {
changeEvent.eventsQueue.forEach((eventInfo) => {
this._handleFileChange(eventInfo, changeEvent.logger);
});
};
_handleFileChange = ({ type, filePath, metadata }, logger) => {
debug("Handling %s: %s (type: %s)", type, filePath, metadata.type);
if (
metadata.type === "l" ||
(this._options.unstable_enablePackageExports &&
filePath.endsWith(_path.default.sep + "package.json"))
) {
this._requiresReset = true;
this.emit("change", {
logger,
});
}
let state;
if (this._deletedFiles.has(filePath)) {
state = "deleted";
} else if (this._modifiedFiles.has(filePath)) {
state = "modified";
} else if (this._addedFiles.has(filePath)) {
state = "added";
}
let nextState;
if (type === "delete") {
nextState = "deleted";
} else if (type === "add") {
nextState = state === "deleted" ? "modified" : "added";
} else {
nextState = state === "added" ? "added" : "modified";
}
switch (nextState) {
case "deleted":
this._deletedFiles.add(filePath);
this._modifiedFiles.delete(filePath);
this._addedFiles.delete(filePath);
break;
case "added":
this._addedFiles.add(filePath);
this._deletedFiles.delete(filePath);
this._modifiedFiles.delete(filePath);
break;
case "modified":
this._modifiedFiles.add(filePath);
this._deletedFiles.delete(filePath);
this._addedFiles.delete(filePath);
break;
default:
nextState;
}
this.emit("change", {
logger,
});
};
async _getChangedDependencies(modifiedFiles, deletedFiles, addedFiles) {
if (!this._graph.dependencies.size) {
const { added } = await this._graph.initialTraverseDependencies(
this._options,
);
return {
added,
modified: new Map(),
deleted: new Set(),
reset: true,
};
}
deletedFiles.forEach((filePath) => {
for (const modifiedModulePath of this._graph.getModifiedModulesForDeletedPath(
filePath,
)) {
if (!deletedFiles.has(modifiedModulePath)) {
modifiedFiles.add(modifiedModulePath);
}
}
});
if (this._options.unstable_allowRequireContext) {
addedFiles.forEach((filePath) => {
this._graph.markModifiedContextModules(filePath, modifiedFiles);
});
}
const modifiedDependencies = Array.from(modifiedFiles).filter((filePath) =>
this._graph.dependencies.has(filePath),
);
if (modifiedDependencies.length === 0) {
return {
added: new Map(),
modified: new Map(),
deleted: new Set(),
reset: false,
};
}
debug("Traversing dependencies for %s paths", modifiedDependencies.length);
const { added, modified, deleted } = await this._graph.traverseDependencies(
modifiedDependencies,
this._options,
);
debug(
"Calculated graph delta {added: %s, modified: %d, deleted: %d}",
added.size,
modified.size,
deleted.size,
);
return {
added,
modified,
deleted,
reset: false,
};
}
}
exports.default = DeltaCalculator;

View File

@@ -0,0 +1,325 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/
import type {DeltaResult, Options} from './types';
import type {RootPerfLogger} from 'metro-config';
import type {ChangeEvent} from 'metro-file-map';
import {Graph} from './Graph';
import EventEmitter from 'events';
import path from 'path';
// eslint-disable-next-line import/no-commonjs
const debug = require('debug')('Metro:DeltaCalculator');
/**
* This class is in charge of calculating the delta of changed modules that
* happen between calls. To do so, it subscribes to file changes, so it can
* traverse the files that have been changed between calls and avoid having to
* traverse the whole dependency tree for trivial small changes.
*/
export default class DeltaCalculator<T> extends EventEmitter {
_changeEventSource: EventEmitter;
_options: Options<T>;
_currentBuildPromise: ?Promise<DeltaResult<T>>;
_deletedFiles: Set<string> = new Set();
_modifiedFiles: Set<string> = new Set();
_addedFiles: Set<string> = new Set();
_requiresReset = false;
_graph: Graph<T>;
constructor(
entryPoints: $ReadOnlySet<string>,
changeEventSource: EventEmitter,
options: Options<T>,
) {
super();
this._options = options;
this._changeEventSource = changeEventSource;
this._graph = new Graph({
entryPoints,
transformOptions: this._options.transformOptions,
});
this._changeEventSource.on('change', this._handleMultipleFileChanges);
}
/**
* Stops listening for file changes and clears all the caches.
*/
end(): void {
this._changeEventSource.removeListener(
'change',
this._handleMultipleFileChanges,
);
this.removeAllListeners();
// Clean up all the cache data structures to deallocate memory.
this._graph = new Graph({
entryPoints: this._graph.entryPoints,
transformOptions: this._options.transformOptions,
});
this._modifiedFiles = new Set();
this._deletedFiles = new Set();
this._addedFiles = new Set();
}
/**
* Main method to calculate the delta of modules. It returns a DeltaResult,
* which contain the modified/added modules and the removed modules.
*/
async getDelta({
reset,
shallow,
}: {
reset: boolean,
shallow: boolean,
...
}): Promise<DeltaResult<T>> {
debug('Calculating delta (reset: %s, shallow: %s)', reset, shallow);
// If there is already a build in progress, wait until it finish to start
// processing a new one (delta server doesn't support concurrent builds).
if (this._currentBuildPromise) {
await this._currentBuildPromise;
}
// We don't want the modified files Set to be modified while building the
// bundle, so we isolate them by using the current instance for the bundling
// and creating a new instance for the file watcher.
const modifiedFiles = this._modifiedFiles;
this._modifiedFiles = new Set();
const deletedFiles = this._deletedFiles;
this._deletedFiles = new Set();
const addedFiles = this._addedFiles;
this._addedFiles = new Set();
const requiresReset = this._requiresReset;
this._requiresReset = false;
// Revisit all files if changes require a graph reset - resolutions may be
// invalidated but we don't yet know which. This should be optimized in the
// future.
if (requiresReset) {
const markModified = (file: string) => {
if (!addedFiles.has(file) && !deletedFiles.has(file)) {
modifiedFiles.add(file);
}
};
this._graph.dependencies.forEach((_, key) => markModified(key));
this._graph.entryPoints.forEach(markModified);
}
// Concurrent requests should reuse the same bundling process. To do so,
// this method stores the promise as an instance variable, and then it's
// removed after it gets resolved.
this._currentBuildPromise = this._getChangedDependencies(
modifiedFiles,
deletedFiles,
addedFiles,
);
let result;
try {
result = await this._currentBuildPromise;
} catch (error) {
// In case of error, we don't want to mark the modified files as
// processed (since we haven't actually created any delta). If we do not
// do so, asking for a delta after an error will produce an empty Delta,
// which is not correct.
modifiedFiles.forEach((file: string) => this._modifiedFiles.add(file));
deletedFiles.forEach((file: string) => this._deletedFiles.add(file));
addedFiles.forEach((file: string) => this._addedFiles.add(file));
throw error;
} finally {
this._currentBuildPromise = null;
}
// Return all the modules if the client requested a reset delta.
if (reset) {
this._graph.reorderGraph({shallow});
return {
added: this._graph.dependencies,
modified: new Map(),
deleted: new Set(),
reset: true,
};
}
return result;
}
/**
* Returns the graph with all the dependencies. Each module contains the
* needed information to do the traversing (dependencies, inverseDependencies)
* plus some metadata.
*/
getGraph(): Graph<T> {
return this._graph;
}
_handleMultipleFileChanges = (changeEvent: ChangeEvent) => {
changeEvent.eventsQueue.forEach(eventInfo => {
this._handleFileChange(eventInfo, changeEvent.logger);
});
};
/**
* Handles a single file change. To avoid doing any work before it's needed,
* the listener only stores the modified file, which will then be used later
* when the delta needs to be calculated.
*/
_handleFileChange = (
{type, filePath, metadata}: ChangeEvent['eventsQueue'][number],
logger: ?RootPerfLogger,
): mixed => {
debug('Handling %s: %s (type: %s)', type, filePath, metadata.type);
if (
metadata.type === 'l' ||
(this._options.unstable_enablePackageExports &&
filePath.endsWith(path.sep + 'package.json'))
) {
this._requiresReset = true;
this.emit('change', {logger});
}
let state: void | 'deleted' | 'modified' | 'added';
if (this._deletedFiles.has(filePath)) {
state = 'deleted';
} else if (this._modifiedFiles.has(filePath)) {
state = 'modified';
} else if (this._addedFiles.has(filePath)) {
state = 'added';
}
let nextState: 'deleted' | 'modified' | 'added';
if (type === 'delete') {
nextState = 'deleted';
} else if (type === 'add') {
// A deleted+added file is modified
nextState = state === 'deleted' ? 'modified' : 'added';
} else {
// type === 'change'
// An added+modified file is added
nextState = state === 'added' ? 'added' : 'modified';
}
switch (nextState) {
case 'deleted':
this._deletedFiles.add(filePath);
this._modifiedFiles.delete(filePath);
this._addedFiles.delete(filePath);
break;
case 'added':
this._addedFiles.add(filePath);
this._deletedFiles.delete(filePath);
this._modifiedFiles.delete(filePath);
break;
case 'modified':
this._modifiedFiles.add(filePath);
this._deletedFiles.delete(filePath);
this._addedFiles.delete(filePath);
break;
default:
(nextState: empty);
}
// Notify users that there is a change in some of the bundle files. This
// way the client can choose to refetch the bundle.
this.emit('change', {
logger,
});
};
async _getChangedDependencies(
modifiedFiles: Set<string>,
deletedFiles: Set<string>,
addedFiles: Set<string>,
): Promise<DeltaResult<T>> {
if (!this._graph.dependencies.size) {
const {added} = await this._graph.initialTraverseDependencies(
this._options,
);
return {
added,
modified: new Map(),
deleted: new Set(),
reset: true,
};
}
// If a file has been deleted, we want to invalidate any other file that
// depends on it, so we can process it and correctly return an error.
deletedFiles.forEach((filePath: string) => {
for (const modifiedModulePath of this._graph.getModifiedModulesForDeletedPath(
filePath,
)) {
// Only mark the inverse dependency as modified if it's not already
// marked as deleted (in that case we can just ignore it).
if (!deletedFiles.has(modifiedModulePath)) {
modifiedFiles.add(modifiedModulePath);
}
}
});
// NOTE(EvanBacon): This check adds extra complexity so we feature gate it
// to enable users to opt out.
if (this._options.unstable_allowRequireContext) {
// Check if any added or removed files are matched in a context module.
// We only need to do this for added files because (1) deleted files will have a context
// module as an inverse dependency, (2) modified files don't invalidate the contents
// of the context module.
addedFiles.forEach(filePath => {
this._graph.markModifiedContextModules(filePath, modifiedFiles);
});
}
// We only want to process files that are in the bundle.
const modifiedDependencies = Array.from(modifiedFiles).filter(
(filePath: string) => this._graph.dependencies.has(filePath),
);
// No changes happened. Return empty delta.
if (modifiedDependencies.length === 0) {
return {
added: new Map(),
modified: new Map(),
deleted: new Set(),
reset: false,
};
}
debug('Traversing dependencies for %s paths', modifiedDependencies.length);
const {added, modified, deleted} = await this._graph.traverseDependencies(
modifiedDependencies,
this._options,
);
debug(
'Calculated graph delta {added: %s, modified: %d, deleted: %d}',
added.size,
modified.size,
deleted.size,
);
return {
added,
modified,
deleted,
reset: false,
};
}
}

40
node_modules/metro/src/DeltaBundler/Graph.d.ts generated vendored Normal file
View File

@@ -0,0 +1,40 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @oncall react_native
*/
import type {
Dependencies,
GraphInputOptions,
MixedOutput,
Module,
Options,
TransformInputOptions,
} from './types';
export interface Result<T> {
added: Map<string, Module<T>>;
modified: Map<string, Module<T>>;
deleted: Set<string>;
}
export class Graph<T = MixedOutput> {
entryPoints: ReadonlySet<string>;
transformOptions: TransformInputOptions;
dependencies: Dependencies<T>;
constructor(options: GraphInputOptions);
traverseDependencies(
paths: ReadonlyArray<string>,
options: Options<T>,
): Promise<Result<T>>;
initialTraverseDependencies(options: Options<T>): Promise<Result<T>>;
markModifiedContextModules(
filePath: string,
modifiedPaths: Set<string>,
): void;
}

612
node_modules/metro/src/DeltaBundler/Graph.js generated vendored Normal file
View File

@@ -0,0 +1,612 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.Graph = void 0;
var _contextModule = require("../lib/contextModule");
var _CountingSet = _interopRequireDefault(require("../lib/CountingSet"));
var _isResolvedDependency = require("../lib/isResolvedDependency");
var _buildSubgraph = require("./buildSubgraph");
var _invariant = _interopRequireDefault(require("invariant"));
var _nullthrows = _interopRequireDefault(require("nullthrows"));
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
function getInternalOptions({ transform, resolve, onProgress, lazy, shallow }) {
let numProcessed = 0;
let total = 0;
return {
lazy,
transform,
resolve,
onDependencyAdd: () => onProgress && onProgress(numProcessed, ++total),
onDependencyAdded: () => onProgress && onProgress(++numProcessed, total),
shallow,
};
}
function isWeakOrLazy(dependency, options) {
const asyncType = dependency.data.data.asyncType;
return asyncType === "weak" || (asyncType != null && options.lazy);
}
class Graph {
dependencies = new Map();
#importBundleNodes = new Map();
#gc = {
color: new Map(),
possibleCycleRoots: new Set(),
};
#resolvedContexts = new Map();
constructor(options) {
this.entryPoints = options.entryPoints;
this.transformOptions = options.transformOptions;
}
async traverseDependencies(paths, options) {
const internalOptions = getInternalOptions(options);
const modifiedPathsInBaseGraph = new Set(
paths.filter((path) => this.dependencies.has(path)),
);
const allModifiedPaths = new Set(paths);
const delta = await this._buildDelta(
modifiedPathsInBaseGraph,
internalOptions,
(absolutePath) =>
!this.dependencies.has(absolutePath) ||
allModifiedPaths.has(absolutePath),
);
if (delta.errors.size > 0) {
for (const modified of modifiedPathsInBaseGraph) {
delta.baseModuleData.set(
modified,
this._moduleSnapshot(
(0, _nullthrows.default)(this.dependencies.get(modified)),
),
);
}
}
for (const modified of modifiedPathsInBaseGraph) {
if (delta.errors.has(modified)) {
continue;
}
const module = this.dependencies.get(modified);
if (module == null) {
continue;
}
this._recursivelyCommitModule(modified, delta, internalOptions, {
onlyRemove: true,
});
}
this._collectCycles(delta, internalOptions);
try {
for (const modified of modifiedPathsInBaseGraph) {
const module = this.dependencies.get(modified);
if (module == null) {
continue;
}
this._recursivelyCommitModule(modified, delta, internalOptions);
}
} catch (error) {
const rollbackDelta = {
added: delta.added,
deleted: delta.deleted,
touched: new Set(),
updatedModuleData: delta.baseModuleData,
baseModuleData: new Map(),
errors: new Map(),
};
for (const modified of modifiedPathsInBaseGraph) {
const module = this.dependencies.get(modified);
if (module == null) {
continue;
}
this._recursivelyCommitModule(modified, rollbackDelta, internalOptions);
}
this._collectCycles(delta, internalOptions);
(0, _invariant.default)(
rollbackDelta.added.size === 0 && rollbackDelta.deleted.size === 0,
"attempted to roll back a graph commit but there were still changes",
);
throw error;
}
const added = new Map();
for (const path of delta.added) {
added.set(path, (0, _nullthrows.default)(this.dependencies.get(path)));
}
const modified = new Map();
for (const path of modifiedPathsInBaseGraph) {
if (
delta.touched.has(path) &&
!delta.deleted.has(path) &&
!delta.added.has(path)
) {
modified.set(
path,
(0, _nullthrows.default)(this.dependencies.get(path)),
);
}
}
return {
added,
modified,
deleted: delta.deleted,
};
}
async initialTraverseDependencies(options) {
const internalOptions = getInternalOptions(options);
(0, _invariant.default)(
this.dependencies.size === 0,
"initialTraverseDependencies called on nonempty graph",
);
this.#gc.color.clear();
this.#gc.possibleCycleRoots.clear();
this.#importBundleNodes.clear();
for (const path of this.entryPoints) {
this.#gc.color.set(path, "black");
}
const delta = await this._buildDelta(this.entryPoints, internalOptions);
if (delta.errors.size > 0) {
throw delta.errors.values().next().value;
}
for (const path of this.entryPoints) {
this._recursivelyCommitModule(path, delta, internalOptions);
}
this.reorderGraph({
shallow: options.shallow,
});
return {
added: this.dependencies,
modified: new Map(),
deleted: new Set(),
};
}
async _buildDelta(pathsToVisit, options, moduleFilter) {
const subGraph = await (0, _buildSubgraph.buildSubgraph)(
pathsToVisit,
this.#resolvedContexts,
{
resolve: options.resolve,
transform: async (absolutePath, requireContext) => {
options.onDependencyAdd();
const result = await options.transform(absolutePath, requireContext);
options.onDependencyAdded();
return result;
},
shouldTraverse: (dependency) => {
if (options.shallow || isWeakOrLazy(dependency, options)) {
return false;
}
return moduleFilter == null || moduleFilter(dependency.absolutePath);
},
},
);
return {
added: new Set(),
touched: new Set(),
deleted: new Set(),
updatedModuleData: subGraph.moduleData,
baseModuleData: new Map(),
errors: subGraph.errors,
};
}
_recursivelyCommitModule(
path,
delta,
options,
commitOptions = {
onlyRemove: false,
},
) {
if (delta.errors.has(path)) {
throw delta.errors.get(path);
}
const previousModule = this.dependencies.get(path);
const currentModule = (0, _nullthrows.default)(
delta.updatedModuleData.get(path) ?? delta.baseModuleData.get(path),
);
const previousDependencies = previousModule?.dependencies ?? new Map();
const {
dependencies: currentDependencies,
resolvedContexts,
...transformResult
} = currentModule;
const nextModule = {
...(previousModule ?? {
inverseDependencies: new _CountingSet.default(),
path,
}),
...transformResult,
dependencies: new Map(previousDependencies),
};
this.dependencies.set(nextModule.path, nextModule);
if (previousModule == null) {
if (delta.deleted.has(path)) {
delta.deleted.delete(path);
} else {
delta.added.add(path);
}
}
let dependenciesRemoved = false;
for (const [key, prevDependency] of previousDependencies) {
const curDependency = currentDependencies.get(key);
if (
!curDependency ||
!dependenciesEqual(prevDependency, curDependency, options)
) {
dependenciesRemoved = true;
this._removeDependency(nextModule, key, prevDependency, delta, options);
}
}
let dependenciesAdded = false;
if (!commitOptions.onlyRemove) {
for (const [key, curDependency] of currentDependencies) {
const prevDependency = previousDependencies.get(key);
if (
!prevDependency ||
!dependenciesEqual(prevDependency, curDependency, options)
) {
dependenciesAdded = true;
this._addDependency(
nextModule,
key,
curDependency,
resolvedContexts.get(key),
delta,
options,
);
}
}
}
const previousDependencyKeys = [...previousDependencies.keys()];
const dependencyKeysChangedOrReordered =
currentDependencies.size !== previousDependencies.size ||
[...currentDependencies.keys()].some(
(currentKey, index) => currentKey !== previousDependencyKeys[index],
);
if (
previousModule != null &&
!transformOutputMayDiffer(previousModule, nextModule) &&
!dependenciesRemoved &&
!dependenciesAdded &&
!dependencyKeysChangedOrReordered
) {
this.dependencies.set(previousModule.path, previousModule);
return previousModule;
}
delta.touched.add(path);
if (commitOptions.onlyRemove) {
return nextModule;
}
(0, _invariant.default)(
nextModule.dependencies.size === currentDependencies.size,
"Failed to add the correct dependencies",
);
nextModule.dependencies = new Map(currentDependencies);
return nextModule;
}
_addDependency(
parentModule,
key,
dependency,
requireContext,
delta,
options,
) {
if (options.shallow) {
} else if (!(0, _isResolvedDependency.isResolvedDependency)(dependency)) {
} else if (dependency.data.data.asyncType === "weak") {
} else if (options.lazy && dependency.data.data.asyncType != null) {
this._incrementImportBundleReference(dependency, parentModule);
} else {
const path = dependency.absolutePath;
let module = this.dependencies.get(path);
if (!module) {
try {
module = this._recursivelyCommitModule(path, delta, options);
} catch (error) {
const module = this.dependencies.get(path);
if (module) {
if (module.inverseDependencies.size > 0) {
this._markAsPossibleCycleRoot(module);
} else {
this._releaseModule(module, delta, options);
}
}
throw error;
}
}
module.inverseDependencies.add(parentModule.path);
this._markModuleInUse(module);
}
if ((0, _isResolvedDependency.isResolvedDependency)(dependency)) {
const path = dependency.absolutePath;
if (requireContext) {
this.#resolvedContexts.set(path, requireContext);
} else {
this.#resolvedContexts.delete(path);
}
}
parentModule.dependencies.set(key, dependency);
}
_removeDependency(parentModule, key, dependency, delta, options) {
parentModule.dependencies.delete(key);
if (
!(0, _isResolvedDependency.isResolvedDependency)(dependency) ||
dependency.data.data.asyncType === "weak"
) {
return;
}
const { absolutePath } = dependency;
const module = this.dependencies.get(absolutePath);
if (options.lazy && dependency.data.data.asyncType != null) {
this._decrementImportBundleReference(dependency, parentModule);
} else if (module) {
module.inverseDependencies.delete(parentModule.path);
}
if (!module) {
return;
}
if (
module.inverseDependencies.size > 0 ||
this.entryPoints.has(absolutePath)
) {
this._markAsPossibleCycleRoot(module);
} else {
this._releaseModule(module, delta, options);
}
}
markModifiedContextModules(filePath, modifiedPaths) {
for (const [absolutePath, context] of this.#resolvedContexts) {
if (
!modifiedPaths.has(absolutePath) &&
(0, _contextModule.fileMatchesContext)(filePath, context)
) {
modifiedPaths.add(absolutePath);
}
}
}
*getModifiedModulesForDeletedPath(filePath) {
yield* this.dependencies.get(filePath)?.inverseDependencies ?? [];
yield* this.#importBundleNodes.get(filePath)?.inverseDependencies ?? [];
}
reorderGraph(options) {
const orderedDependencies = new Map();
this.entryPoints.forEach((entryPoint) => {
const mainModule = this.dependencies.get(entryPoint);
if (!mainModule) {
throw new ReferenceError(
"Module not registered in graph: " + entryPoint,
);
}
this._reorderDependencies(mainModule, orderedDependencies, options);
});
this.dependencies.clear();
for (const [key, dep] of orderedDependencies) {
this.dependencies.set(key, dep);
}
}
_reorderDependencies(module, orderedDependencies, options) {
if (module.path) {
if (orderedDependencies.has(module.path)) {
return;
}
orderedDependencies.set(module.path, module);
}
module.dependencies.forEach((dependency) => {
const path = dependency.absolutePath;
if (path == null) {
return;
}
const childModule = this.dependencies.get(path);
if (!childModule) {
if (dependency.data.data.asyncType != null || options.shallow) {
return;
} else {
throw new ReferenceError("Module not registered in graph: " + path);
}
}
this._reorderDependencies(childModule, orderedDependencies, options);
});
}
_incrementImportBundleReference(dependency, parentModule) {
const { absolutePath } = dependency;
const importBundleNode = this.#importBundleNodes.get(absolutePath) ?? {
inverseDependencies: new _CountingSet.default(),
};
importBundleNode.inverseDependencies.add(parentModule.path);
this.#importBundleNodes.set(absolutePath, importBundleNode);
}
_decrementImportBundleReference(dependency, parentModule) {
const { absolutePath } = dependency;
const importBundleNode = (0, _nullthrows.default)(
this.#importBundleNodes.get(absolutePath),
);
(0, _invariant.default)(
importBundleNode.inverseDependencies.has(parentModule.path),
"lazy: import bundle inverse references",
);
importBundleNode.inverseDependencies.delete(parentModule.path);
if (importBundleNode.inverseDependencies.size === 0) {
this.#importBundleNodes.delete(absolutePath);
}
}
_markModuleInUse(module) {
this.#gc.color.set(module.path, "black");
}
*_children(module, options) {
for (const dependency of module.dependencies.values()) {
if (
!(0, _isResolvedDependency.isResolvedDependency)(dependency) ||
isWeakOrLazy(dependency, options)
) {
continue;
}
yield (0, _nullthrows.default)(
this.dependencies.get(dependency.absolutePath),
);
}
}
_moduleSnapshot(module) {
const { dependencies, getSource, output, unstable_transformResultKey } =
module;
const resolvedContexts = new Map();
for (const [key, dependency] of dependencies) {
if (!(0, _isResolvedDependency.isResolvedDependency)(dependency)) {
continue;
}
const resolvedContext = this.#resolvedContexts.get(
dependency.absolutePath,
);
if (resolvedContext != null) {
resolvedContexts.set(key, resolvedContext);
}
}
return {
dependencies: new Map(dependencies),
resolvedContexts,
getSource,
output,
unstable_transformResultKey,
};
}
_releaseModule(module, delta, options) {
if (
!delta.updatedModuleData.has(module.path) &&
!delta.baseModuleData.has(module.path)
) {
delta.baseModuleData.set(module.path, this._moduleSnapshot(module));
}
for (const [key, dependency] of module.dependencies) {
if (!(0, _isResolvedDependency.isResolvedDependency)(dependency)) {
continue;
}
this._removeDependency(module, key, dependency, delta, options);
}
this.#gc.color.set(module.path, "black");
this._freeModule(module, delta);
}
_freeModule(module, delta) {
if (delta.added.has(module.path)) {
delta.added.delete(module.path);
} else {
delta.deleted.add(module.path);
}
this.dependencies.delete(module.path);
this.#gc.possibleCycleRoots.delete(module.path);
this.#gc.color.delete(module.path);
this.#resolvedContexts.delete(module.path);
}
_markAsPossibleCycleRoot(module) {
if (this.#gc.color.get(module.path) !== "purple") {
this.#gc.color.set(module.path, "purple");
this.#gc.possibleCycleRoots.add(module.path);
}
}
_collectCycles(delta, options) {
for (const path of this.#gc.possibleCycleRoots) {
const module = (0, _nullthrows.default)(this.dependencies.get(path));
const color = (0, _nullthrows.default)(this.#gc.color.get(path));
if (color === "purple") {
this._markGray(module, options);
} else {
this.#gc.possibleCycleRoots.delete(path);
if (
color === "black" &&
module.inverseDependencies.size === 0 &&
!this.entryPoints.has(path)
) {
this._freeModule(module, delta);
}
}
}
for (const path of this.#gc.possibleCycleRoots) {
const module = (0, _nullthrows.default)(this.dependencies.get(path));
this._scan(module, options);
}
for (const path of this.#gc.possibleCycleRoots) {
this.#gc.possibleCycleRoots.delete(path);
const module = (0, _nullthrows.default)(this.dependencies.get(path));
this._collectWhite(module, delta);
}
}
_markGray(module, options) {
const color = (0, _nullthrows.default)(this.#gc.color.get(module.path));
if (color !== "gray") {
this.#gc.color.set(module.path, "gray");
for (const childModule of this._children(module, options)) {
childModule.inverseDependencies.delete(module.path);
this._markGray(childModule, options);
}
}
}
_scan(module, options) {
const color = (0, _nullthrows.default)(this.#gc.color.get(module.path));
if (color === "gray") {
if (
module.inverseDependencies.size > 0 ||
this.entryPoints.has(module.path)
) {
this._scanBlack(module, options);
} else {
this.#gc.color.set(module.path, "white");
for (const childModule of this._children(module, options)) {
this._scan(childModule, options);
}
}
}
}
_scanBlack(module, options) {
this.#gc.color.set(module.path, "black");
for (const childModule of this._children(module, options)) {
childModule.inverseDependencies.add(module.path);
const childColor = (0, _nullthrows.default)(
this.#gc.color.get(childModule.path),
);
if (childColor !== "black") {
this._scanBlack(childModule, options);
}
}
}
_collectWhite(module, delta) {
const color = (0, _nullthrows.default)(this.#gc.color.get(module.path));
if (color === "white" && !this.#gc.possibleCycleRoots.has(module.path)) {
this.#gc.color.set(module.path, "black");
for (const dependency of module.dependencies.values()) {
if (!(0, _isResolvedDependency.isResolvedDependency)(dependency)) {
continue;
}
const childModule = this.dependencies.get(dependency.absolutePath);
if (childModule) {
this._collectWhite(childModule, delta);
}
}
this._freeModule(module, delta);
}
}
}
exports.Graph = Graph;
function dependenciesEqual(a, b, options) {
return (
a === b ||
(a.absolutePath === b.absolutePath &&
(!options.lazy || a.data.data.asyncType === b.data.data.asyncType) &&
contextParamsEqual(a.data.data.contextParams, b.data.data.contextParams))
);
}
function contextParamsEqual(a, b) {
return (
a === b ||
(a == null && b == null) ||
(a != null &&
b != null &&
a.recursive === b.recursive &&
a.filter.pattern === b.filter.pattern &&
a.filter.flags === b.filter.flags &&
a.mode === b.mode)
);
}
function transformOutputMayDiffer(a, b) {
return (
a.unstable_transformResultKey == null ||
a.unstable_transformResultKey !== b.unstable_transformResultKey
);
}

970
node_modules/metro/src/DeltaBundler/Graph.js.flow generated vendored Normal file
View File

@@ -0,0 +1,970 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/
/**
* Portions of this code are based on the Synchronous Cycle Collection
* algorithm described in:
*
* David F. Bacon and V. T. Rajan. 2001. Concurrent Cycle Collection in
* Reference Counted Systems. In Proceedings of the 15th European Conference on
* Object-Oriented Programming (ECOOP '01). Springer-Verlag, Berlin,
* Heidelberg, 207235.
*
* Notable differences from the algorithm in the paper:
* 1. Our implementation uses the inverseDependencies set (which we already
* have to maintain) instead of a separate refcount variable. A module's
* reference count is equal to the size of its inverseDependencies set, plus
* 1 if it's an entry point of the graph.
* 2. We keep the "root buffer" (possibleCycleRoots) free of duplicates by
* making it a Set, instead of storing a "buffered" flag on each node.
* 3. On top of tracking edges between nodes, we also count references between
* nodes and entries in the importBundleNodes set.
*/
import type {RequireContext} from '../lib/contextModule';
import type {RequireContextParams} from '../ModuleGraph/worker/collectDependencies';
import type {
Dependencies,
Dependency,
GraphInputOptions,
MixedOutput,
Module,
ModuleData,
Options,
ResolvedDependency,
TransformInputOptions,
} from './types';
import {fileMatchesContext} from '../lib/contextModule';
import CountingSet from '../lib/CountingSet';
import {isResolvedDependency} from '../lib/isResolvedDependency';
import {buildSubgraph} from './buildSubgraph';
import invariant from 'invariant';
import nullthrows from 'nullthrows';
// TODO: Convert to a Flow enum
type NodeColor =
// In use or free
| 'black'
// Possible member of cycle
| 'gray'
// Member of garbage cycle
| 'white'
// Possible root of cycle
| 'purple'
// Inherently acyclic node (Not currently used)
| 'green';
export type Result<T> = {
added: Map<string, Module<T>>,
modified: Map<string, Module<T>>,
deleted: Set<string>,
};
/**
* Internal data structure that the traversal logic uses to know which of the
* files have been modified. This allows to return the added modules before the
* modified ones (which is useful for things like Hot Module Reloading).
**/
type Delta<T> = $ReadOnly<{
// `added` and `deleted` are mutually exclusive.
// Internally, a module can be in both `touched` and (either) `added` or
// `deleted`. Before returning the result, we'll calculate
// modified = touched - added - deleted.
added: Set<string>,
touched: Set<string>,
deleted: Set<string>,
updatedModuleData: $ReadOnlyMap<string, ModuleData<T>>,
baseModuleData: Map<string, ModuleData<T>>,
errors: $ReadOnlyMap<string, Error>,
}>;
type InternalOptions<T> = $ReadOnly<{
lazy: boolean,
onDependencyAdd: () => mixed,
onDependencyAdded: () => mixed,
resolve: Options<T>['resolve'],
transform: Options<T>['transform'],
shallow: boolean,
}>;
function getInternalOptions<T>({
transform,
resolve,
onProgress,
lazy,
shallow,
}: Options<T>): InternalOptions<T> {
let numProcessed = 0;
let total = 0;
return {
lazy,
transform,
resolve,
onDependencyAdd: () => onProgress && onProgress(numProcessed, ++total),
onDependencyAdded: () => onProgress && onProgress(++numProcessed, total),
shallow,
};
}
function isWeakOrLazy<T>(
dependency: ResolvedDependency,
options: InternalOptions<T>,
): boolean {
const asyncType = dependency.data.data.asyncType;
return asyncType === 'weak' || (asyncType != null && options.lazy);
}
export class Graph<T = MixedOutput> {
+entryPoints: $ReadOnlySet<string>;
+transformOptions: TransformInputOptions;
+dependencies: Dependencies<T> = new Map();
+#importBundleNodes: Map<
string,
$ReadOnly<{
inverseDependencies: CountingSet<string>,
}>,
> = new Map();
/// GC state for nodes in the graph (this.dependencies)
+#gc: {
+color: Map<string, NodeColor>,
+possibleCycleRoots: Set<string>,
} = {
color: new Map(),
possibleCycleRoots: new Set(),
};
/** Resolved context parameters from `require.context`. */
#resolvedContexts: Map<string, RequireContext> = new Map();
constructor(options: GraphInputOptions) {
this.entryPoints = options.entryPoints;
this.transformOptions = options.transformOptions;
}
/**
* Dependency Traversal logic for the Delta Bundler. This method calculates
* the modules that should be included in the bundle by traversing the
* dependency graph.
* Instead of traversing the whole graph each time, it just calculates the
* difference between runs by only traversing the added/removed dependencies.
* To do so, it uses the passed graph dependencies and it mutates it.
* The paths parameter contains the absolute paths of the root files that the
* method should traverse. Normally, these paths should be the modified files
* since the last traversal.
*/
async traverseDependencies(
paths: $ReadOnlyArray<string>,
options: Options<T>,
): Promise<Result<T>> {
const internalOptions = getInternalOptions(options);
const modifiedPathsInBaseGraph = new Set(
paths.filter(path => this.dependencies.has(path)),
);
const allModifiedPaths = new Set(paths);
const delta = await this._buildDelta(
modifiedPathsInBaseGraph,
internalOptions,
// Traverse new or modified paths
absolutePath =>
!this.dependencies.has(absolutePath) ||
allModifiedPaths.has(absolutePath),
);
// If we have errors we might need to roll back any changes - take
// snapshots of all modified modules at the base state. We'll also snapshot
// unmodified modules that become unreachable as they are released, so that
// we have everything we need to restore the graph to base.
if (delta.errors.size > 0) {
for (const modified of modifiedPathsInBaseGraph) {
delta.baseModuleData.set(
modified,
this._moduleSnapshot(nullthrows(this.dependencies.get(modified))),
);
}
}
// Commit changes in a subtractive pass and then an additive pass - this
// ensures that any errors encountered on the additive pass would also be
// encountered on a fresh build (implying legitimate errors in the graph,
// rather than an error in a module that's no longer reachable).
for (const modified of modifiedPathsInBaseGraph) {
// Skip this module if it has errors. Hopefully it will be removed -
// if not, we'll throw during the additive pass.
if (delta.errors.has(modified)) {
continue;
}
const module = this.dependencies.get(modified);
// The module may have already been released from the graph - we'll readd
// it if necessary later.
if (module == null) {
continue;
}
// Process the transform result and dependency removals. This should
// never encounter an error.
this._recursivelyCommitModule(modified, delta, internalOptions, {
onlyRemove: true,
});
}
// Ensure we have released any unreachable modules before the additive
// pass.
this._collectCycles(delta, internalOptions);
// Additive pass - any errors we encounter here should be thrown after
// rolling back the commit.
try {
for (const modified of modifiedPathsInBaseGraph) {
const module = this.dependencies.get(modified);
// The module may have already been released from the graph (it may yet
// be readded via another dependency).
if (module == null) {
continue;
}
this._recursivelyCommitModule(modified, delta, internalOptions);
}
} catch (error) {
// Roll back to base before re-throwing.
const rollbackDelta: Delta<T> = {
added: delta.added,
deleted: delta.deleted,
touched: new Set(),
updatedModuleData: delta.baseModuleData,
baseModuleData: new Map(),
errors: new Map(),
};
for (const modified of modifiedPathsInBaseGraph) {
const module = this.dependencies.get(modified);
// The module may have already been released from the graph (it may yet
// be readded via another dependency).
if (module == null) {
continue;
}
// Set the module and descendants back to base state.
this._recursivelyCommitModule(modified, rollbackDelta, internalOptions);
}
// Collect cycles again after rolling back. There's no need if we're
// not rolling back, because we have not removed any edges.
this._collectCycles(delta, internalOptions);
// Cheap check to validate the rollback.
invariant(
rollbackDelta.added.size === 0 && rollbackDelta.deleted.size === 0,
'attempted to roll back a graph commit but there were still changes',
);
// Re-throw the transform or resolution error originally seen by
// `buildSubgraph`.
throw error;
}
const added = new Map<string, Module<T>>();
for (const path of delta.added) {
added.set(path, nullthrows(this.dependencies.get(path)));
}
const modified = new Map<string, Module<T>>();
for (const path of modifiedPathsInBaseGraph) {
if (
delta.touched.has(path) &&
!delta.deleted.has(path) &&
!delta.added.has(path)
) {
modified.set(path, nullthrows(this.dependencies.get(path)));
}
}
return {
added,
modified,
deleted: delta.deleted,
};
}
async initialTraverseDependencies(options: Options<T>): Promise<Result<T>> {
const internalOptions = getInternalOptions(options);
invariant(
this.dependencies.size === 0,
'initialTraverseDependencies called on nonempty graph',
);
this.#gc.color.clear();
this.#gc.possibleCycleRoots.clear();
this.#importBundleNodes.clear();
for (const path of this.entryPoints) {
// Each entry point implicitly has a refcount of 1, so mark them all black.
this.#gc.color.set(path, 'black');
}
const delta = await this._buildDelta(this.entryPoints, internalOptions);
if (delta.errors.size > 0) {
// If we encountered any errors during traversal, throw one of them.
// Since errors are encountered in a non-deterministic order, even on
// fresh builds, it's valid to arbitrarily pick the first.
throw delta.errors.values().next().value;
}
for (const path of this.entryPoints) {
// We have already thrown on userland errors in the delta, so any error
// encountered here would be exceptional and fatal.
this._recursivelyCommitModule(path, delta, internalOptions);
}
this.reorderGraph({
shallow: options.shallow,
});
return {
added: this.dependencies,
modified: new Map(),
deleted: new Set(),
};
}
async _buildDelta(
pathsToVisit: $ReadOnlySet<string>,
options: InternalOptions<T>,
moduleFilter?: (path: string) => boolean,
): Promise<Delta<T>> {
const subGraph = await buildSubgraph(pathsToVisit, this.#resolvedContexts, {
resolve: options.resolve,
transform: async (absolutePath, requireContext) => {
options.onDependencyAdd();
const result = await options.transform(absolutePath, requireContext);
options.onDependencyAdded();
return result;
},
shouldTraverse: (dependency: ResolvedDependency) => {
if (options.shallow || isWeakOrLazy(dependency, options)) {
return false;
}
return moduleFilter == null || moduleFilter(dependency.absolutePath);
},
});
return {
added: new Set(),
touched: new Set(),
deleted: new Set(),
updatedModuleData: subGraph.moduleData,
baseModuleData: new Map(),
errors: subGraph.errors,
};
}
_recursivelyCommitModule(
path: string,
delta: Delta<T>,
options: InternalOptions<T>,
commitOptions: $ReadOnly<{
onlyRemove: boolean,
}> = {onlyRemove: false},
): Module<T> {
if (delta.errors.has(path)) {
throw delta.errors.get(path);
}
const previousModule = this.dependencies.get(path);
const currentModule: ModuleData<T> = nullthrows(
delta.updatedModuleData.get(path) ?? delta.baseModuleData.get(path),
);
const previousDependencies = previousModule?.dependencies ?? new Map();
const {
dependencies: currentDependencies,
resolvedContexts,
...transformResult
} = currentModule;
const nextModule = {
...(previousModule ?? {
inverseDependencies: new CountingSet(),
path,
}),
...transformResult,
dependencies: new Map(previousDependencies),
};
// Update the module information.
this.dependencies.set(nextModule.path, nextModule);
if (previousModule == null) {
// If the module is not currently in the graph, it is either new or was
// released earlier in the commit.
if (delta.deleted.has(path)) {
// Mark the addition by clearing a prior deletion.
delta.deleted.delete(path);
} else {
// Mark the addition in the added set.
delta.added.add(path);
}
}
// Diff dependencies (1/3): remove dependencies that have changed or been removed.
let dependenciesRemoved = false;
for (const [key, prevDependency] of previousDependencies) {
const curDependency = currentDependencies.get(key);
if (
!curDependency ||
!dependenciesEqual(prevDependency, curDependency, options)
) {
dependenciesRemoved = true;
this._removeDependency(nextModule, key, prevDependency, delta, options);
}
}
// Diff dependencies (2/3): add dependencies that have changed or been added.
let dependenciesAdded = false;
if (!commitOptions.onlyRemove) {
for (const [key, curDependency] of currentDependencies) {
const prevDependency = previousDependencies.get(key);
if (
!prevDependency ||
!dependenciesEqual(prevDependency, curDependency, options)
) {
dependenciesAdded = true;
this._addDependency(
nextModule,
key,
curDependency,
resolvedContexts.get(key),
delta,
options,
);
}
}
}
// Diff dependencies (3/3): detect changes in the ordering of dependency
// keys, which must be committed even if no other changes were made.
const previousDependencyKeys = [...previousDependencies.keys()];
const dependencyKeysChangedOrReordered =
currentDependencies.size !== previousDependencies.size ||
[...currentDependencies.keys()].some(
(currentKey, index) => currentKey !== previousDependencyKeys[index],
);
if (
previousModule != null &&
!transformOutputMayDiffer(previousModule, nextModule) &&
!dependenciesRemoved &&
!dependenciesAdded &&
!dependencyKeysChangedOrReordered
) {
// We have not operated on nextModule, so restore previousModule
// to aid diffing. Don't add this path to delta.touched.
this.dependencies.set(previousModule.path, previousModule);
return previousModule;
}
delta.touched.add(path);
// Replace dependencies with the correctly-ordered version, matching the
// transform output. Because this assignment does not add or remove edges,
// it does NOT invalidate any of the garbage collection state.
// A subtractive pass only partially commits modules, so our dependencies
// are not generally complete yet. We'll address ordering in the next pass
// after processing additions.
if (commitOptions.onlyRemove) {
return nextModule;
}
// Catch obvious errors with a cheap assertion.
invariant(
nextModule.dependencies.size === currentDependencies.size,
'Failed to add the correct dependencies',
);
nextModule.dependencies = new Map(currentDependencies);
return nextModule;
}
_addDependency(
parentModule: Module<T>,
key: string,
dependency: Dependency,
requireContext: ?RequireContext,
delta: Delta<T>,
options: InternalOptions<T>,
): void {
if (options.shallow) {
// Don't add a node for the module if the graph is shallow (single-module).
} else if (!isResolvedDependency(dependency)) {
// If the dependency is a missing optional dependency, it has no node of
// its own. We just need to add it to the parent's dependency map.
} else if (dependency.data.data.asyncType === 'weak') {
// Exclude weak dependencies from the bundle.
} else if (options.lazy && dependency.data.data.asyncType != null) {
// Don't add a node for the module if we are traversing async dependencies
// lazily (and this is an async dependency). Instead, record it in
// importBundleNodes.
this._incrementImportBundleReference(dependency, parentModule);
} else {
// The module may already exist, in which case we just need to update some
// bookkeeping instead of adding a new node to the graph.
const path = dependency.absolutePath;
let module = this.dependencies.get(path);
if (!module) {
try {
module = this._recursivelyCommitModule(path, delta, options);
} catch (error) {
// If we couldn't add this module but it was added to the graph
// before failing on a sub-dependency, it may be orphaned. Mark it as
// a possible garbage root.
const module = this.dependencies.get(path);
if (module) {
if (module.inverseDependencies.size > 0) {
this._markAsPossibleCycleRoot(module);
} else {
this._releaseModule(module, delta, options);
}
}
throw error;
}
}
// We either added a new node to the graph, or we're updating an existing one.
module.inverseDependencies.add(parentModule.path);
this._markModuleInUse(module);
}
if (isResolvedDependency(dependency)) {
const path = dependency.absolutePath;
if (requireContext) {
this.#resolvedContexts.set(path, requireContext);
} else {
// This dependency may have existed previously as a require.context -
// clean it up.
this.#resolvedContexts.delete(path);
}
}
// Update the parent's dependency map unless we failed to add a dependency.
// This means the parent's dependencies can get desynced from
// inverseDependencies and the other fields in the case of lazy edges.
// Not an optimal representation :(
parentModule.dependencies.set(key, dependency);
}
_removeDependency(
parentModule: Module<T>,
key: string,
dependency: Dependency,
delta: Delta<T>,
options: InternalOptions<T>,
): void {
parentModule.dependencies.delete(key);
if (
!isResolvedDependency(dependency) ||
dependency.data.data.asyncType === 'weak'
) {
// Weak and unresolved dependencies are excluded from the bundle.
return;
}
const {absolutePath} = dependency;
const module = this.dependencies.get(absolutePath);
if (options.lazy && dependency.data.data.asyncType != null) {
this._decrementImportBundleReference(dependency, parentModule);
} else if (module) {
// Decrement inverseDependencies only if the dependency is not async,
// mirroring the increment conditions in _addDependency.
module.inverseDependencies.delete(parentModule.path);
}
if (!module) {
return;
}
if (
module.inverseDependencies.size > 0 ||
this.entryPoints.has(absolutePath)
) {
// The reference count has decreased, but not to zero.
// NOTE: Each entry point implicitly has a refcount of 1.
this._markAsPossibleCycleRoot(module);
} else {
// The reference count has decreased to zero.
this._releaseModule(module, delta, options);
}
}
/**
* Collect a list of context modules which include a given file.
*/
markModifiedContextModules(
filePath: string,
modifiedPaths: Set<string> | CountingSet<string>,
) {
for (const [absolutePath, context] of this.#resolvedContexts) {
if (
!modifiedPaths.has(absolutePath) &&
fileMatchesContext(filePath, context)
) {
modifiedPaths.add(absolutePath);
}
}
}
/**
* Gets the list of modules affected by the deletion of a given file. The
* caller is expected to mark these modules as modified in the next call to
* traverseDependencies. Note that the list may contain duplicates.
*/
*getModifiedModulesForDeletedPath(filePath: string): Iterable<string> {
yield* this.dependencies.get(filePath)?.inverseDependencies ?? [];
yield* this.#importBundleNodes.get(filePath)?.inverseDependencies ?? [];
}
/**
* Re-traverse the dependency graph in DFS order to reorder the modules and
* guarantee the same order between runs. This method mutates the passed graph.
*/
reorderGraph(options: {shallow: boolean, ...}): void {
const orderedDependencies = new Map<string, Module<T>>();
this.entryPoints.forEach((entryPoint: string) => {
const mainModule = this.dependencies.get(entryPoint);
if (!mainModule) {
throw new ReferenceError(
'Module not registered in graph: ' + entryPoint,
);
}
this._reorderDependencies(mainModule, orderedDependencies, options);
});
this.dependencies.clear();
for (const [key, dep] of orderedDependencies) {
this.dependencies.set(key, dep);
}
}
_reorderDependencies(
module: Module<T>,
orderedDependencies: Map<string, Module<T>>,
options: {shallow: boolean, ...},
): void {
if (module.path) {
if (orderedDependencies.has(module.path)) {
return;
}
orderedDependencies.set(module.path, module);
}
module.dependencies.forEach(dependency => {
const path = dependency.absolutePath;
if (path == null) {
// If the dependency is not a missing optional dependency, it has no children to reorder.
return;
}
const childModule = this.dependencies.get(path);
if (!childModule) {
if (dependency.data.data.asyncType != null || options.shallow) {
return;
} else {
throw new ReferenceError('Module not registered in graph: ' + path);
}
}
this._reorderDependencies(childModule, orderedDependencies, options);
});
}
/** Garbage collection functions */
// Add an entry to importBundleNodes (or record an inverse dependency of an existing one)
_incrementImportBundleReference(
dependency: ResolvedDependency,
parentModule: Module<T>,
) {
const {absolutePath} = dependency;
const importBundleNode = this.#importBundleNodes.get(absolutePath) ?? {
inverseDependencies: new CountingSet(),
};
importBundleNode.inverseDependencies.add(parentModule.path);
this.#importBundleNodes.set(absolutePath, importBundleNode);
}
// Decrease the reference count of an entry in importBundleNodes (and delete it if necessary)
_decrementImportBundleReference(
dependency: ResolvedDependency,
parentModule: Module<T>,
) {
const {absolutePath} = dependency;
const importBundleNode = nullthrows(
this.#importBundleNodes.get(absolutePath),
);
invariant(
importBundleNode.inverseDependencies.has(parentModule.path),
'lazy: import bundle inverse references',
);
importBundleNode.inverseDependencies.delete(parentModule.path);
if (importBundleNode.inverseDependencies.size === 0) {
this.#importBundleNodes.delete(absolutePath);
}
}
// Mark a module as in use (ref count >= 1)
_markModuleInUse(module: Module<T>) {
this.#gc.color.set(module.path, 'black');
}
// Iterate "children" of the given module - i.e. non-weak / async
// dependencies having a corresponding inverse dependency.
*_children(
module: Module<T>,
options: InternalOptions<T>,
): Iterator<Module<T>> {
for (const dependency of module.dependencies.values()) {
if (
!isResolvedDependency(dependency) ||
isWeakOrLazy(dependency, options)
) {
continue;
}
yield nullthrows(this.dependencies.get(dependency.absolutePath));
}
}
_moduleSnapshot(module: Module<T>): ModuleData<T> {
const {dependencies, getSource, output, unstable_transformResultKey} =
module;
const resolvedContexts: Map<string, RequireContext> = new Map();
for (const [key, dependency] of dependencies) {
if (!isResolvedDependency(dependency)) {
continue;
}
const resolvedContext = this.#resolvedContexts.get(
dependency.absolutePath,
);
if (resolvedContext != null) {
resolvedContexts.set(key, resolvedContext);
}
}
return {
dependencies: new Map(dependencies),
resolvedContexts,
getSource,
output,
unstable_transformResultKey,
};
}
// Delete an unreachable module (and its outbound edges) from the graph
// immediately.
// Called when the reference count of a module has reached 0.
_releaseModule(
module: Module<T>,
delta: Delta<T>,
options: InternalOptions<T>,
) {
if (
!delta.updatedModuleData.has(module.path) &&
!delta.baseModuleData.has(module.path)
) {
// Before releasing a module, take a snapshot of the data we might need
// to reintroduce it to the graph later in this commit. As it is not
// already present in updatedModuleData we can infer it has not been modified,
// so the transform output and dependencies we copy here are current.
delta.baseModuleData.set(module.path, this._moduleSnapshot(module));
}
for (const [key, dependency] of module.dependencies) {
if (!isResolvedDependency(dependency)) {
// If the dependency is not a missing optional dependency, it has no children to remove.
continue;
}
this._removeDependency(module, key, dependency, delta, options);
}
this.#gc.color.set(module.path, 'black');
this._freeModule(module, delta);
}
// Delete an unreachable module from the graph.
_freeModule(module: Module<T>, delta: Delta<T>) {
if (delta.added.has(module.path)) {
// Mark the deletion by clearing a prior addition.
delta.added.delete(module.path);
} else {
// Mark the deletion in the deleted set.
delta.deleted.add(module.path);
}
// This module is not used anywhere else! We can clear it from the bundle.
// Clean up all the state associated with this module in order to correctly
// re-add it if we encounter it again.
this.dependencies.delete(module.path);
this.#gc.possibleCycleRoots.delete(module.path);
this.#gc.color.delete(module.path);
this.#resolvedContexts.delete(module.path);
}
// Mark a module as a possible cycle root
_markAsPossibleCycleRoot(module: Module<T>) {
if (this.#gc.color.get(module.path) !== 'purple') {
this.#gc.color.set(module.path, 'purple');
this.#gc.possibleCycleRoots.add(module.path);
}
}
// Collect any unreachable cycles in the graph.
_collectCycles(delta: Delta<T>, options: InternalOptions<T>) {
// Mark recursively from roots (trial deletion)
for (const path of this.#gc.possibleCycleRoots) {
const module = nullthrows(this.dependencies.get(path));
const color = nullthrows(this.#gc.color.get(path));
if (color === 'purple') {
this._markGray(module, options);
} else {
this.#gc.possibleCycleRoots.delete(path);
if (
color === 'black' &&
module.inverseDependencies.size === 0 &&
!this.entryPoints.has(path)
) {
this._freeModule(module, delta);
}
}
}
// Scan recursively from roots (undo unsuccessful trial deletions)
for (const path of this.#gc.possibleCycleRoots) {
const module = nullthrows(this.dependencies.get(path));
this._scan(module, options);
}
// Collect recursively from roots (free unreachable cycles)
for (const path of this.#gc.possibleCycleRoots) {
this.#gc.possibleCycleRoots.delete(path);
const module = nullthrows(this.dependencies.get(path));
this._collectWhite(module, delta);
}
}
_markGray(module: Module<T>, options: InternalOptions<T>) {
const color = nullthrows(this.#gc.color.get(module.path));
if (color !== 'gray') {
this.#gc.color.set(module.path, 'gray');
for (const childModule of this._children(module, options)) {
// The inverse dependency will be restored during the scan phase if this module remains live.
childModule.inverseDependencies.delete(module.path);
this._markGray(childModule, options);
}
}
}
_scan(module: Module<T>, options: InternalOptions<T>) {
const color = nullthrows(this.#gc.color.get(module.path));
if (color === 'gray') {
if (
module.inverseDependencies.size > 0 ||
this.entryPoints.has(module.path)
) {
this._scanBlack(module, options);
} else {
this.#gc.color.set(module.path, 'white');
for (const childModule of this._children(module, options)) {
this._scan(childModule, options);
}
}
}
}
_scanBlack(module: Module<T>, options: InternalOptions<T>) {
this.#gc.color.set(module.path, 'black');
for (const childModule of this._children(module, options)) {
// The inverse dependency must have been deleted during the mark phase.
childModule.inverseDependencies.add(module.path);
const childColor = nullthrows(this.#gc.color.get(childModule.path));
if (childColor !== 'black') {
this._scanBlack(childModule, options);
}
}
}
_collectWhite(module: Module<T>, delta: Delta<T>) {
const color = nullthrows(this.#gc.color.get(module.path));
if (color === 'white' && !this.#gc.possibleCycleRoots.has(module.path)) {
this.#gc.color.set(module.path, 'black');
for (const dependency of module.dependencies.values()) {
if (!isResolvedDependency(dependency)) {
continue;
}
const childModule = this.dependencies.get(dependency.absolutePath);
// The child may already have been collected.
if (childModule) {
this._collectWhite(childModule, delta);
}
}
this._freeModule(module, delta);
}
}
/** End of garbage collection functions */
}
function dependenciesEqual(
a: Dependency,
b: Dependency,
options: $ReadOnly<{lazy: boolean, ...}>,
): boolean {
return (
a === b ||
(a.absolutePath === b.absolutePath &&
(!options.lazy || a.data.data.asyncType === b.data.data.asyncType) &&
contextParamsEqual(a.data.data.contextParams, b.data.data.contextParams))
);
}
function contextParamsEqual(
a: ?RequireContextParams,
b: ?RequireContextParams,
): boolean {
return (
a === b ||
(a == null && b == null) ||
(a != null &&
b != null &&
a.recursive === b.recursive &&
a.filter.pattern === b.filter.pattern &&
a.filter.flags === b.filter.flags &&
a.mode === b.mode)
);
}
function transformOutputMayDiffer<T>(a: Module<T>, b: Module<T>): boolean {
return (
a.unstable_transformResultKey == null ||
a.unstable_transformResultKey !== b.unstable_transformResultKey
);
}

View File

@@ -0,0 +1,67 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = baseJSBundle;
var _getAppendScripts = _interopRequireDefault(
require("../../lib/getAppendScripts"),
);
var _processModules = _interopRequireDefault(
require("./helpers/processModules"),
);
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
function baseJSBundle(entryPoint, preModules, graph, options) {
for (const module of graph.dependencies.values()) {
options.createModuleId(module.path);
}
const processModulesOptions = {
filter: options.processModuleFilter,
createModuleId: options.createModuleId,
dev: options.dev,
includeAsyncPaths: options.includeAsyncPaths,
projectRoot: options.projectRoot,
serverRoot: options.serverRoot,
sourceUrl: options.sourceUrl,
};
if (options.modulesOnly) {
preModules = [];
}
const preCode = (0, _processModules.default)(
preModules,
processModulesOptions,
)
.map(([_, code]) => code)
.join("\n");
const modules = [...graph.dependencies.values()].sort(
(a, b) => options.createModuleId(a.path) - options.createModuleId(b.path),
);
const postCode = (0, _processModules.default)(
(0, _getAppendScripts.default)(entryPoint, [...preModules, ...modules], {
asyncRequireModulePath: options.asyncRequireModulePath,
createModuleId: options.createModuleId,
getRunModuleStatement: options.getRunModuleStatement,
globalPrefix: options.globalPrefix,
inlineSourceMap: options.inlineSourceMap,
runBeforeMainModule: options.runBeforeMainModule,
runModule: options.runModule,
shouldAddToIgnoreList: options.shouldAddToIgnoreList,
sourceMapUrl: options.sourceMapUrl,
sourceUrl: options.sourceUrl,
getSourceUrl: options.getSourceUrl,
}),
processModulesOptions,
)
.map(([_, code]) => code)
.join("\n");
return {
pre: preCode,
post: postCode,
modules: (0, _processModules.default)(
[...graph.dependencies.values()],
processModulesOptions,
).map(([module, code]) => [options.createModuleId(module.path), code]),
};
}

View File

@@ -0,0 +1,84 @@
/**
* Copyright (c) Meta Platforms, Inc. and 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
* @oncall react_native
*/
import type {
MixedOutput,
Module,
ReadOnlyGraph,
SerializerOptions,
} from '../types';
import type {Bundle} from 'metro-runtime/src/modules/types';
import getAppendScripts from '../../lib/getAppendScripts';
import processModules from './helpers/processModules';
export default function baseJSBundle(
entryPoint: string,
preModules: $ReadOnlyArray<Module<>>,
graph: ReadOnlyGraph<>,
options: SerializerOptions,
): Bundle {
for (const module of graph.dependencies.values()) {
options.createModuleId(module.path);
}
const processModulesOptions = {
filter: options.processModuleFilter,
createModuleId: options.createModuleId,
dev: options.dev,
includeAsyncPaths: options.includeAsyncPaths,
projectRoot: options.projectRoot,
serverRoot: options.serverRoot,
sourceUrl: options.sourceUrl,
};
// Do not prepend polyfills or the require runtime when only modules are requested
if (options.modulesOnly) {
preModules = [];
}
const preCode = processModules(preModules, processModulesOptions)
.map(([_, code]) => code)
.join('\n');
const modules = [...graph.dependencies.values()].sort(
(a: Module<MixedOutput>, b: Module<MixedOutput>) =>
options.createModuleId(a.path) - options.createModuleId(b.path),
);
const postCode = processModules(
getAppendScripts(entryPoint, [...preModules, ...modules], {
asyncRequireModulePath: options.asyncRequireModulePath,
createModuleId: options.createModuleId,
getRunModuleStatement: options.getRunModuleStatement,
globalPrefix: options.globalPrefix,
inlineSourceMap: options.inlineSourceMap,
runBeforeMainModule: options.runBeforeMainModule,
runModule: options.runModule,
shouldAddToIgnoreList: options.shouldAddToIgnoreList,
sourceMapUrl: options.sourceMapUrl,
sourceUrl: options.sourceUrl,
getSourceUrl: options.getSourceUrl,
}),
processModulesOptions,
)
.map(([_, code]) => code)
.join('\n');
return {
pre: preCode,
post: postCode,
modules: processModules(
[...graph.dependencies.values()],
processModulesOptions,
).map(([module, code]) => [options.createModuleId(module.path), code]),
};
}

View File

@@ -0,0 +1,34 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = getAllFiles;
var _Assets = require("../../Assets");
var _js = require("./helpers/js");
async function getAllFiles(pre, graph, options) {
const modules = graph.dependencies;
const { processModuleFilter } = options;
const promises = [];
for (const module of pre) {
if (processModuleFilter(module)) {
promises.push([module.path]);
}
}
for (const module of modules.values()) {
if (!(0, _js.isJsModule)(module) || !processModuleFilter(module)) {
continue;
}
if ((0, _js.getJsOutput)(module).type === "js/module/asset") {
promises.push((0, _Assets.getAssetFiles)(module.path, options.platform));
} else {
promises.push([module.path]);
}
}
const dependencies = await Promise.all(promises);
const output = [];
for (const dependencyArray of dependencies) {
output.push(...dependencyArray);
}
return output;
}

View File

@@ -0,0 +1,58 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/
import type {Module, ReadOnlyGraph} from '../types';
import {getAssetFiles} from '../../Assets';
import {getJsOutput, isJsModule} from './helpers/js';
type Options = {
platform: ?string,
+processModuleFilter: (module: Module<>) => boolean,
};
export default async function getAllFiles(
pre: $ReadOnlyArray<Module<>>,
graph: ReadOnlyGraph<>,
options: Options,
): Promise<$ReadOnlyArray<string>> {
const modules = graph.dependencies;
const {processModuleFilter} = options;
const promises: Array<Promise<Array<string>> | Array<string>> = [];
for (const module of pre) {
if (processModuleFilter(module)) {
promises.push([module.path]);
}
}
for (const module of modules.values()) {
if (!isJsModule(module) || !processModuleFilter(module)) {
continue;
}
if (getJsOutput(module).type === 'js/module/asset') {
promises.push(getAssetFiles(module.path, options.platform));
} else {
promises.push([module.path]);
}
}
const dependencies = await Promise.all(promises);
const output: Array<string> = [];
for (const dependencyArray of dependencies) {
output.push(...dependencyArray);
}
return output;
}

View File

@@ -0,0 +1,36 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = getAssets;
var _Assets = require("../../Assets");
var _js = require("./helpers/js");
var _path = _interopRequireDefault(require("path"));
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
async function getAssets(dependencies, options) {
const promises = [];
const { processModuleFilter } = options;
for (const module of dependencies.values()) {
if (
(0, _js.isJsModule)(module) &&
processModuleFilter(module) &&
(0, _js.getJsOutput)(module).type === "js/module/asset" &&
_path.default.relative(options.projectRoot, module.path) !==
"package.json"
) {
promises.push(
(0, _Assets.getAssetData)(
module.path,
_path.default.relative(options.projectRoot, module.path),
options.assetPlugins,
options.platform,
options.publicPath,
),
);
}
}
return await Promise.all(promises);
}

View File

@@ -0,0 +1,54 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/
import type {AssetData} from '../../Assets';
import type {Module, ReadOnlyDependencies} from '../types';
import {getAssetData} from '../../Assets';
import {getJsOutput, isJsModule} from './helpers/js';
import path from 'path';
type Options = {
+processModuleFilter: (module: Module<>) => boolean,
assetPlugins: $ReadOnlyArray<string>,
platform: ?string,
projectRoot: string,
publicPath: string,
};
export default async function getAssets(
dependencies: ReadOnlyDependencies<>,
options: Options,
): Promise<$ReadOnlyArray<AssetData>> {
const promises = [];
const {processModuleFilter} = options;
for (const module of dependencies.values()) {
if (
isJsModule(module) &&
processModuleFilter(module) &&
getJsOutput(module).type === 'js/module/asset' &&
path.relative(options.projectRoot, module.path) !== 'package.json'
) {
promises.push(
getAssetData(
module.path,
path.relative(options.projectRoot, module.path),
options.assetPlugins,
options.platform,
options.publicPath,
),
);
}
}
return await Promise.all(promises);
}

View File

@@ -0,0 +1,26 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @oncall react_native
*/
import type {Module} from '../types';
import type {
FBSourceFunctionMap,
MetroSourceMapSegmentTuple,
} from 'metro-source-map';
export type ExplodedSourceMap = ReadonlyArray<{
readonly map: Array<MetroSourceMapSegmentTuple>;
readonly firstLine1Based: number;
readonly functionMap: null | undefined | FBSourceFunctionMap;
readonly path: string;
}>;
export declare function getExplodedSourceMap(
modules: ReadonlyArray<Module>,
options: {readonly processModuleFilter: (module: Module) => boolean},
): ExplodedSourceMap;

View File

@@ -0,0 +1,26 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.getExplodedSourceMap = getExplodedSourceMap;
var _js = require("./helpers/js");
function getExplodedSourceMap(modules, options) {
const modulesToProcess = modules
.filter(_js.isJsModule)
.filter(options.processModuleFilter);
const result = [];
let firstLine1Based = 1;
for (const module of modulesToProcess) {
const { path } = module;
const { lineCount, functionMap, map } = (0, _js.getJsOutput)(module).data;
result.push({
firstLine1Based,
functionMap,
path,
map,
});
firstLine1Based += lineCount;
}
return result;
}

View File

@@ -0,0 +1,47 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/
import type {Module} from '../types';
import type {
FBSourceFunctionMap,
MetroSourceMapSegmentTuple,
} from 'metro-source-map';
import {getJsOutput, isJsModule} from './helpers/js';
export type ExplodedSourceMap = $ReadOnlyArray<{
+map: Array<MetroSourceMapSegmentTuple>,
+firstLine1Based: number,
+functionMap: ?FBSourceFunctionMap,
+path: string,
}>;
export function getExplodedSourceMap(
modules: $ReadOnlyArray<Module<>>,
options: {
+processModuleFilter: (module: Module<>) => boolean,
},
): ExplodedSourceMap {
const modulesToProcess = modules
.filter(isJsModule)
.filter(options.processModuleFilter);
const result = [];
let firstLine1Based = 1;
for (const module of modulesToProcess) {
const {path} = module;
const {lineCount, functionMap, map} = getJsOutput(module).data;
result.push({firstLine1Based, functionMap, path, map});
firstLine1Based += lineCount;
}
return result;
}

View File

@@ -0,0 +1,18 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @oncall react_native
*/
import {ModuleTransportLike} from '../../shared/types';
export interface RamBundleInfo {
getDependencies: (filePath: string) => Set<string>;
startupModules: Readonly<ModuleTransportLike>;
lazyModules: Readonly<ModuleTransportLike>;
groups: Map<number, Set<number>>;
}

View File

@@ -0,0 +1,121 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = getRamBundleInfo;
var _util = require("../../Bundler/util");
var _getAppendScripts = _interopRequireDefault(
require("../../lib/getAppendScripts"),
);
var _getTransitiveDependencies = _interopRequireDefault(
require("./helpers/getTransitiveDependencies"),
);
var _js = require("./helpers/js");
var _sourceMapObject = require("./sourceMapObject");
var _nullthrows = _interopRequireDefault(require("nullthrows"));
var _path = _interopRequireDefault(require("path"));
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
async function getRamBundleInfo(entryPoint, pre, graph, options) {
let modules = [...pre, ...graph.dependencies.values()];
modules = modules.concat(
(0, _getAppendScripts.default)(entryPoint, modules, options),
);
modules.forEach((module) => options.createModuleId(module.path));
const ramModules = modules
.filter(_js.isJsModule)
.filter(options.processModuleFilter)
.map((module) => ({
id: options.createModuleId(module.path),
code: (0, _js.wrapModule)(module, options),
map: (0, _sourceMapObject.sourceMapObject)([module], {
excludeSource: options.excludeSource,
processModuleFilter: options.processModuleFilter,
shouldAddToIgnoreList: options.shouldAddToIgnoreList,
getSourceUrl: options.getSourceUrl,
}),
name: _path.default.basename(module.path),
sourcePath: module.path,
source: module.getSource().toString(),
type: (0, _nullthrows.default)(
module.output.find(({ type }) => type.startsWith("js")),
).type,
}));
const { preloadedModules, ramGroups } = await _getRamOptions(
entryPoint,
{
dev: options.dev,
platform: options.platform,
},
(filePath) => (0, _getTransitiveDependencies.default)(filePath, graph),
options.getTransformOptions,
);
const startupModules = [];
const lazyModules = [];
ramModules.forEach((module) => {
if (preloadedModules.hasOwnProperty(module.sourcePath)) {
startupModules.push(module);
return;
}
if (module.type.startsWith("js/script")) {
startupModules.push(module);
return;
}
if (module.type.startsWith("js/module")) {
lazyModules.push(module);
}
});
const groups = (0, _util.createRamBundleGroups)(
ramGroups,
lazyModules,
(module, dependenciesByPath) => {
const deps = (0, _getTransitiveDependencies.default)(
module.sourcePath,
graph,
);
const output = new Set();
for (const dependency of deps) {
const module = dependenciesByPath.get(dependency);
if (module) {
output.add(module.id);
}
}
return output;
},
);
return {
getDependencies: (filePath) =>
(0, _getTransitiveDependencies.default)(filePath, graph),
groups,
lazyModules,
startupModules,
};
}
async function _getRamOptions(
entryFile,
options,
getDependencies,
getTransformOptions,
) {
if (getTransformOptions == null) {
return {
preloadedModules: {},
ramGroups: [],
};
}
const { preloadedModules, ramGroups } = await getTransformOptions(
[entryFile],
{
dev: options.dev,
hot: true,
platform: options.platform,
},
async (x) => Array.from(getDependencies),
);
return {
preloadedModules: preloadedModules || {},
ramGroups: ramGroups || [],
};
}

View File

@@ -0,0 +1,171 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/
import type {ModuleTransportLike, RamModuleTransport} from '../../shared/types';
import type {Module, ReadOnlyGraph, SerializerOptions} from '../types';
import type {SourceMapGeneratorOptions} from './sourceMapGenerator';
import type {GetTransformOptions} from 'metro-config';
import {createRamBundleGroups} from '../../Bundler/util';
import getAppendScripts from '../../lib/getAppendScripts';
import getTransitiveDependencies from './helpers/getTransitiveDependencies';
import {isJsModule, wrapModule} from './helpers/js';
import {sourceMapObject} from './sourceMapObject';
import nullthrows from 'nullthrows';
import path from 'path';
type Options = $ReadOnly<{
...SerializerOptions,
...SourceMapGeneratorOptions,
getTransformOptions: ?GetTransformOptions,
platform: ?string,
}>;
export type RamBundleInfo = {
getDependencies: string => Set<string>,
startupModules: $ReadOnlyArray<ModuleTransportLike>,
lazyModules: $ReadOnlyArray<ModuleTransportLike>,
groups: Map<number, Set<number>>,
};
export default async function getRamBundleInfo(
entryPoint: string,
pre: $ReadOnlyArray<Module<>>,
graph: ReadOnlyGraph<>,
options: Options,
): Promise<RamBundleInfo> {
let modules: $ReadOnlyArray<Module<>> = [
...pre,
...graph.dependencies.values(),
];
modules = modules.concat(getAppendScripts(entryPoint, modules, options));
modules.forEach((module: Module<>) => options.createModuleId(module.path));
const ramModules: Array<RamModuleTransport> = modules
.filter(isJsModule)
.filter(options.processModuleFilter)
.map((module: Module<>): RamModuleTransport => ({
id: options.createModuleId(module.path),
code: wrapModule(module, options),
map: sourceMapObject([module], {
excludeSource: options.excludeSource,
processModuleFilter: options.processModuleFilter,
shouldAddToIgnoreList: options.shouldAddToIgnoreList,
getSourceUrl: options.getSourceUrl,
}),
name: path.basename(module.path),
sourcePath: module.path,
source: module.getSource().toString(),
type: nullthrows(module.output.find(({type}) => type.startsWith('js')))
.type,
}));
const {preloadedModules, ramGroups} = await _getRamOptions(
entryPoint,
{
dev: options.dev,
platform: options.platform,
},
(filePath: string) => getTransitiveDependencies(filePath, graph),
options.getTransformOptions,
);
const startupModules = [];
const lazyModules = [];
ramModules.forEach((module: RamModuleTransport) => {
if (preloadedModules.hasOwnProperty(module.sourcePath)) {
startupModules.push(module);
return;
}
if (module.type.startsWith('js/script')) {
startupModules.push(module);
return;
}
if (module.type.startsWith('js/module')) {
lazyModules.push(module);
}
});
const groups = createRamBundleGroups(
ramGroups,
lazyModules,
(
module: ModuleTransportLike,
dependenciesByPath: Map<string, ModuleTransportLike>,
): Set<number> => {
const deps = getTransitiveDependencies(module.sourcePath, graph);
const output = new Set<number>();
for (const dependency of deps) {
const module = dependenciesByPath.get(dependency);
if (module) {
output.add(module.id);
}
}
return output;
},
);
return {
getDependencies: (filePath: string): Set<string> =>
getTransitiveDependencies(filePath, graph),
groups,
lazyModules,
startupModules,
};
}
/**
* Returns the options needed to create a RAM bundle.
*/
async function _getRamOptions(
entryFile: string,
options: {
dev: boolean,
platform: ?string,
...
},
getDependencies: string => Iterable<string>,
getTransformOptions: ?GetTransformOptions,
): Promise<
$ReadOnly<{
preloadedModules: $ReadOnly<{[string]: true, ...}>,
ramGroups: $ReadOnlyArray<string>,
}>,
> {
if (getTransformOptions == null) {
return {
preloadedModules: {},
ramGroups: [],
};
}
const {preloadedModules, ramGroups} = await getTransformOptions(
[entryFile],
{dev: options.dev, hot: true, platform: options.platform},
/* $FlowFixMe[incompatible-type](>=0.99.0 site=react_native_fb) This comment suppresses an
* error found when Flow v0.99 was deployed. To see the error, delete this
* comment and run Flow. */
async (x: string) => Array.from(getDependencies),
);
return {
// $FlowFixMe[sketchy-null-bool]
preloadedModules: preloadedModules || {},
ramGroups: ramGroups || [],
};
}

View File

@@ -0,0 +1,10 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = getInlineSourceMappingURL;
function getInlineSourceMappingURL(sourceMap) {
const base64 = Buffer.from(sourceMap).toString("base64");
return `data:application/json;charset=utf-8;base64,${base64}`;
}

View File

@@ -0,0 +1,15 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/
export default function getInlineSourceMappingURL(sourceMap: string): string {
const base64 = Buffer.from(sourceMap).toString('base64');
return `data:application/json;charset=utf-8;base64,${base64}`;
}

View File

@@ -0,0 +1,21 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = getSourceMapInfo;
var _js = require("./js");
function getSourceMapInfo(module, options) {
return {
...(0, _js.getJsOutput)(module).data,
isIgnored: options.shouldAddToIgnoreList(module),
path: options?.getSourceUrl?.(module) ?? module.path,
source: options.excludeSource ? "" : getModuleSource(module),
};
}
function getModuleSource(module) {
if ((0, _js.getJsOutput)(module).type === "js/module/asset") {
return "";
}
return module.getSource().toString();
}

View File

@@ -0,0 +1,50 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/
import type {Module} from '../../types';
import type {
FBSourceFunctionMap,
MetroSourceMapSegmentTuple,
} from 'metro-source-map';
import {getJsOutput} from './js';
export default function getSourceMapInfo(
module: Module<>,
options: {
+excludeSource: boolean,
+shouldAddToIgnoreList: (Module<>) => boolean,
getSourceUrl: ?(module: Module<>) => string,
},
): {
+map: Array<MetroSourceMapSegmentTuple>,
+functionMap: ?FBSourceFunctionMap,
+code: string,
+path: string,
+source: string,
+lineCount: number,
+isIgnored: boolean,
} {
return {
...getJsOutput(module).data,
isIgnored: options.shouldAddToIgnoreList(module),
path: options?.getSourceUrl?.(module) ?? module.path,
source: options.excludeSource ? '' : getModuleSource(module),
};
}
function getModuleSource(module: Module<>): string {
if (getJsOutput(module).type === 'js/module/asset') {
return '';
}
return module.getSource().toString();
}

View File

@@ -0,0 +1,28 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = getTransitiveDependencies;
var _isResolvedDependency = require("../../../lib/isResolvedDependency");
function getTransitiveDependencies(path, graph) {
const dependencies = _getDeps(path, graph, new Set());
dependencies.delete(path);
return dependencies;
}
function _getDeps(path, graph, deps) {
if (deps.has(path)) {
return deps;
}
const module = graph.dependencies.get(path);
if (!module) {
return deps;
}
deps.add(path);
for (const dependency of module.dependencies.values()) {
if ((0, _isResolvedDependency.isResolvedDependency)(dependency)) {
_getDeps(dependency.absolutePath, graph, deps);
}
}
return deps;
}

View File

@@ -0,0 +1,53 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/
import type {ReadOnlyGraph} from '../../types';
import {isResolvedDependency} from '../../../lib/isResolvedDependency';
export default function getTransitiveDependencies<T>(
path: string,
graph: ReadOnlyGraph<T>,
): Set<string> {
const dependencies = _getDeps(path, graph, new Set());
// Remove the main entry point, since this method only returns the
// dependencies.
dependencies.delete(path);
return dependencies;
}
function _getDeps<T>(
path: string,
graph: ReadOnlyGraph<T>,
deps: Set<string>,
): Set<string> {
if (deps.has(path)) {
return deps;
}
const module = graph.dependencies.get(path);
if (!module) {
return deps;
}
deps.add(path);
for (const dependency of module.dependencies.values()) {
if (isResolvedDependency(dependency)) {
_getDeps(dependency.absolutePath, graph, deps);
}
}
return deps;
}

View File

@@ -0,0 +1,129 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.getJsOutput = getJsOutput;
exports.getModuleParams = getModuleParams;
exports.isJsModule = isJsModule;
exports.wrapModule = wrapModule;
var _isResolvedDependency = require("../../../lib/isResolvedDependency");
var _pathUtils = require("../../../lib/pathUtils");
var _invariant = _interopRequireDefault(require("invariant"));
var jscSafeUrl = _interopRequireWildcard(require("jsc-safe-url"));
var _metroTransformPlugins = require("metro-transform-plugins");
var _path = _interopRequireDefault(require("path"));
function _getRequireWildcardCache(e) {
if ("function" != typeof WeakMap) return null;
var r = new WeakMap(),
t = new WeakMap();
return (_getRequireWildcardCache = function (e) {
return e ? t : r;
})(e);
}
function _interopRequireWildcard(e, r) {
if (!r && e && e.__esModule) return e;
if (null === e || ("object" != typeof e && "function" != typeof e))
return { default: e };
var t = _getRequireWildcardCache(r);
if (t && t.has(e)) return t.get(e);
var n = { __proto__: null },
a = Object.defineProperty && Object.getOwnPropertyDescriptor;
for (var u in e)
if ("default" !== u && {}.hasOwnProperty.call(e, u)) {
var i = a ? Object.getOwnPropertyDescriptor(e, u) : null;
i && (i.get || i.set) ? Object.defineProperty(n, u, i) : (n[u] = e[u]);
}
return ((n.default = e), t && t.set(e, n), n);
}
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
function wrapModule(module, options) {
const output = getJsOutput(module);
if (output.type.startsWith("js/script")) {
return output.data.code;
}
const params = getModuleParams(module, options);
return (0, _metroTransformPlugins.addParamsToDefineCall)(
output.data.code,
...params,
);
}
function getModuleParams(module, options) {
const moduleId = options.createModuleId(module.path);
const paths = {};
let hasPaths = false;
const dependencyMapArray = Array.from(module.dependencies.values()).map(
(dependency) => {
if (!(0, _isResolvedDependency.isResolvedDependency)(dependency)) {
return null;
}
const id = options.createModuleId(dependency.absolutePath);
if (options.includeAsyncPaths && dependency.data.data.asyncType != null) {
hasPaths = true;
(0, _invariant.default)(
options.sourceUrl != null,
"sourceUrl is required when includeAsyncPaths is true",
);
const { searchParams } = new URL(
jscSafeUrl.toNormalUrl(options.sourceUrl),
);
searchParams.set("modulesOnly", "true");
searchParams.set("runModule", "false");
const bundlePath = _path.default.relative(
options.serverRoot,
dependency.absolutePath,
);
paths[id] =
"/" +
_path.default.join(
_path.default.dirname(bundlePath),
_path.default.basename(
bundlePath,
_path.default.extname(bundlePath),
),
) +
".bundle?" +
searchParams.toString();
}
return id;
},
);
const params = [
moduleId,
hasPaths
? {
...dependencyMapArray,
paths,
}
: dependencyMapArray,
];
if (options.dev) {
params.push(
(0, _pathUtils.normalizePathSeparatorsToPosix)(
_path.default.relative(options.projectRoot, module.path),
),
);
}
return params;
}
function getJsOutput(module) {
const jsModules = module.output.filter(({ type }) => type.startsWith("js/"));
(0, _invariant.default)(
jsModules.length === 1,
`Modules must have exactly one JS output, but ${module.path ?? "unknown module"} has ${jsModules.length} JS outputs.`,
);
const jsOutput = jsModules[0];
(0, _invariant.default)(
Number.isFinite(jsOutput.data.lineCount),
`JS output must populate lineCount, but ${module.path ?? "unknown module"} has ${jsOutput.type} output with lineCount '${jsOutput.data.lineCount}'`,
);
return jsOutput;
}
function isJsModule(module) {
return module.output.filter(isJsOutput).length > 0;
}
function isJsOutput(output) {
return output.type.startsWith("js/");
}

View File

@@ -0,0 +1,154 @@
/**
* Copyright (c) Meta Platforms, Inc. and 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
* @oncall react_native
*/
import type {MixedOutput, Module} from '../../types';
import type {JsOutput} from 'metro-transform-worker';
import {isResolvedDependency} from '../../../lib/isResolvedDependency';
import {normalizePathSeparatorsToPosix} from '../../../lib/pathUtils';
import invariant from 'invariant';
import * as jscSafeUrl from 'jsc-safe-url';
import {addParamsToDefineCall} from 'metro-transform-plugins';
import path from 'path';
export type Options = $ReadOnly<{
createModuleId: string => number | string,
dev: boolean,
includeAsyncPaths: boolean,
projectRoot: string,
serverRoot: string,
sourceUrl: ?string,
...
}>;
export function wrapModule(module: Module<>, options: Options): string {
const output = getJsOutput(module);
if (output.type.startsWith('js/script')) {
return output.data.code;
}
const params = getModuleParams(module, options);
return addParamsToDefineCall(output.data.code, ...params);
}
export function getModuleParams(
module: Module<>,
options: Options,
): Array<mixed> {
const moduleId = options.createModuleId(module.path);
const paths: {[moduleID: number | string]: mixed} = {};
let hasPaths = false;
const dependencyMapArray = Array.from(module.dependencies.values()).map(
dependency => {
if (!isResolvedDependency(dependency)) {
// An unresolved dependency, which should cause a runtime error
// when required.
return null;
}
const id = options.createModuleId(dependency.absolutePath);
if (options.includeAsyncPaths && dependency.data.data.asyncType != null) {
hasPaths = true;
invariant(
options.sourceUrl != null,
'sourceUrl is required when includeAsyncPaths is true',
);
// TODO: Only include path if the target is not in the bundle
// Construct a server-relative URL for the split bundle, propagating
// most parameters from the main bundle's URL.
const {searchParams} = new URL(
jscSafeUrl.toNormalUrl(options.sourceUrl),
);
searchParams.set('modulesOnly', 'true');
searchParams.set('runModule', 'false');
const bundlePath = path.relative(
options.serverRoot,
dependency.absolutePath,
);
paths[id] =
'/' +
path.join(
// TODO: This is not the proper Metro URL encoding of a file path
path.dirname(bundlePath),
// Strip the file extension
path.basename(bundlePath, path.extname(bundlePath)),
) +
'.bundle?' +
searchParams.toString();
}
return id;
},
);
const params = [
moduleId,
hasPaths
? {
// $FlowFixMe[not-an-object] Intentionally spreading an array into an object
...dependencyMapArray,
paths,
}
: dependencyMapArray,
];
if (options.dev) {
// Add the relative path of the module to make debugging easier.
// This is mapped to `module.verboseName` in `require.js`.
params.push(
normalizePathSeparatorsToPosix(
path.relative(options.projectRoot, module.path),
),
);
}
return params;
}
export function getJsOutput(
module: $ReadOnly<{
output: $ReadOnlyArray<MixedOutput>,
path?: string,
...
}>,
): JsOutput {
const jsModules = module.output.filter(({type}) => type.startsWith('js/'));
invariant(
jsModules.length === 1,
`Modules must have exactly one JS output, but ${
module.path ?? 'unknown module'
} has ${jsModules.length} JS outputs.`,
);
const jsOutput: JsOutput = (jsModules[0]: any);
invariant(
Number.isFinite(jsOutput.data.lineCount),
`JS output must populate lineCount, but ${
module.path ?? 'unknown module'
} has ${jsOutput.type} output with lineCount '${jsOutput.data.lineCount}'`,
);
return jsOutput;
}
export function isJsModule(module: Module<>): boolean {
return module.output.filter(isJsOutput).length > 0;
}
function isJsOutput(output: MixedOutput): boolean {
return output.type.startsWith('js/');
}

View File

@@ -0,0 +1,34 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = processModules;
var _js = require("./js");
function processModules(
modules,
{
filter = () => true,
createModuleId,
dev,
includeAsyncPaths,
projectRoot,
serverRoot,
sourceUrl,
},
) {
return [...modules]
.filter(_js.isJsModule)
.filter(filter)
.map((module) => [
module,
(0, _js.wrapModule)(module, {
createModuleId,
dev,
includeAsyncPaths,
projectRoot,
serverRoot,
sourceUrl,
}),
]);
}

View File

@@ -0,0 +1,50 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/
import type {Module} from '../../types';
import {isJsModule, wrapModule} from './js';
export default function processModules(
modules: $ReadOnlyArray<Module<>>,
{
filter = () => true,
createModuleId,
dev,
includeAsyncPaths,
projectRoot,
serverRoot,
sourceUrl,
}: $ReadOnly<{
filter?: (module: Module<>) => boolean,
createModuleId: string => number,
dev: boolean,
includeAsyncPaths: boolean,
projectRoot: string,
serverRoot: string,
sourceUrl: ?string,
}>,
): $ReadOnlyArray<[Module<>, string]> {
return [...modules]
.filter(isJsModule)
.filter(filter)
.map((module: Module<>) => [
module,
wrapModule(module, {
createModuleId,
dev,
includeAsyncPaths,
projectRoot,
serverRoot,
sourceUrl,
}),
]);
}

View File

@@ -0,0 +1,124 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = hmrJSBundle;
var _js = require("./helpers/js");
var jscSafeUrl = _interopRequireWildcard(require("jsc-safe-url"));
var _metroTransformPlugins = require("metro-transform-plugins");
var _path = _interopRequireDefault(require("path"));
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
function _getRequireWildcardCache(e) {
if ("function" != typeof WeakMap) return null;
var r = new WeakMap(),
t = new WeakMap();
return (_getRequireWildcardCache = function (e) {
return e ? t : r;
})(e);
}
function _interopRequireWildcard(e, r) {
if (!r && e && e.__esModule) return e;
if (null === e || ("object" != typeof e && "function" != typeof e))
return { default: e };
var t = _getRequireWildcardCache(r);
if (t && t.has(e)) return t.get(e);
var n = { __proto__: null },
a = Object.defineProperty && Object.getOwnPropertyDescriptor;
for (var u in e)
if ("default" !== u && {}.hasOwnProperty.call(e, u)) {
var i = a ? Object.getOwnPropertyDescriptor(e, u) : null;
i && (i.get || i.set) ? Object.defineProperty(n, u, i) : (n[u] = e[u]);
}
return ((n.default = e), t && t.set(e, n), n);
}
const debug = require("debug")("Metro:HMR");
function generateModules(sourceModules, graph, options) {
const modules = [];
for (const module of sourceModules) {
if ((0, _js.isJsModule)(module)) {
const getPathname = (extension) => {
return _path.default
.relative(
options.serverRoot ?? options.projectRoot,
_path.default.join(
_path.default.dirname(module.path),
_path.default.basename(
module.path,
_path.default.extname(module.path),
) +
"." +
extension,
),
)
.split(_path.default.sep)
.map((segment) => encodeURIComponent(segment))
.join("/");
};
const clientUrl = new URL(options.clientUrl);
clientUrl.searchParams.delete("excludeSource");
clientUrl.pathname = getPathname("map");
const sourceMappingURL = clientUrl.toString();
clientUrl.pathname = getPathname("bundle");
const sourceURL = jscSafeUrl.toJscSafeUrl(clientUrl.toString());
debug(
"got sourceMappingURL: %s\nand sourceURL: %s\nfor module: %s",
sourceMappingURL,
sourceURL,
module.path,
);
const code =
prepareModule(module, graph, options) +
`\n//# sourceMappingURL=${sourceMappingURL}\n` +
`//# sourceURL=${sourceURL}\n`;
modules.push({
module: [options.createModuleId(module.path), code],
sourceMappingURL,
sourceURL,
});
}
}
return modules;
}
function prepareModule(module, graph, options) {
const code = (0, _js.wrapModule)(module, {
...options,
sourceUrl: options.clientUrl.toString(),
dev: true,
});
const inverseDependencies = getInverseDependencies(module.path, graph);
const inverseDependenciesById = Object.create(null);
Object.keys(inverseDependencies).forEach((path) => {
inverseDependenciesById[options.createModuleId(path)] = inverseDependencies[
path
].map(options.createModuleId);
});
return (0, _metroTransformPlugins.addParamsToDefineCall)(
code,
inverseDependenciesById,
);
}
function getInverseDependencies(path, graph, inverseDependencies = {}) {
if (path in inverseDependencies) {
return inverseDependencies;
}
const module = graph.dependencies.get(path);
if (!module) {
return inverseDependencies;
}
inverseDependencies[path] = [];
for (const inverse of module.inverseDependencies) {
inverseDependencies[path].push(inverse);
getInverseDependencies(inverse, graph, inverseDependencies);
}
return inverseDependencies;
}
function hmrJSBundle(delta, graph, options) {
return {
added: generateModules(delta.added.values(), graph, options),
modified: generateModules(delta.modified.values(), graph, options),
deleted: [...delta.deleted].map((path) => options.createModuleId(path)),
};
}

View File

@@ -0,0 +1,162 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/
import type {DeltaResult, Module, ReadOnlyGraph} from '../types';
import type {HmrModule} from 'metro-runtime/src/modules/types';
import {isJsModule, wrapModule} from './helpers/js';
import * as jscSafeUrl from 'jsc-safe-url';
import {addParamsToDefineCall} from 'metro-transform-plugins';
import path from 'path';
// eslint-disable-next-line import/no-commonjs
const debug = require('debug')('Metro:HMR');
type Options = $ReadOnly<{
clientUrl: URL,
createModuleId: string => number,
includeAsyncPaths: boolean,
projectRoot: string,
serverRoot: string,
...
}>;
function generateModules(
sourceModules: Iterable<Module<>>,
graph: ReadOnlyGraph<>,
options: Options,
): $ReadOnlyArray<HmrModule> {
const modules = [];
for (const module of sourceModules) {
if (isJsModule(module)) {
const getPathname = (extension: 'bundle' | 'map') => {
return (
path
.relative(
options.serverRoot ?? options.projectRoot,
path.join(
path.dirname(module.path),
path.basename(module.path, path.extname(module.path)) +
'.' +
extension,
),
)
.split(path.sep)
// using this Metro particular convention for encoding file paths as URL paths.
.map(segment => encodeURIComponent(segment))
.join('/')
);
};
const clientUrl = new URL(options.clientUrl);
clientUrl.searchParams.delete('excludeSource');
clientUrl.pathname = getPathname('map');
const sourceMappingURL = clientUrl.toString();
clientUrl.pathname = getPathname('bundle');
const sourceURL = jscSafeUrl.toJscSafeUrl(clientUrl.toString());
debug(
'got sourceMappingURL: %s\nand sourceURL: %s\nfor module: %s',
sourceMappingURL,
sourceURL,
module.path,
);
const code =
prepareModule(module, graph, options) +
`\n//# sourceMappingURL=${sourceMappingURL}\n` +
`//# sourceURL=${sourceURL}\n`;
modules.push({
module: [options.createModuleId(module.path), code],
sourceMappingURL,
sourceURL,
});
}
}
return modules;
}
function prepareModule(
module: Module<>,
graph: ReadOnlyGraph<>,
options: Options,
): string {
const code = wrapModule(module, {
...options,
sourceUrl: options.clientUrl.toString(),
dev: true,
});
const inverseDependencies = getInverseDependencies(module.path, graph);
// Transform the inverse dependency paths to ids.
const inverseDependenciesById = Object.create(null);
Object.keys(inverseDependencies).forEach((path: string) => {
// $FlowFixMe[prop-missing]
// $FlowFixMe[invalid-computed-prop]
inverseDependenciesById[options.createModuleId(path)] = inverseDependencies[
path
].map(options.createModuleId);
});
return addParamsToDefineCall(code, inverseDependenciesById);
}
/**
* Instead of adding the whole inverseDependncies object into each changed
* module (which can be really huge if the dependency graph is big), we only
* add the needed inverseDependencies for each changed module (we do this by
* traversing upwards the dependency graph).
*/
function getInverseDependencies(
path: string,
graph: ReadOnlyGraph<>,
inverseDependencies: {[key: string]: Array<string>, ...} = {},
): {[key: string]: Array<string>, ...} {
// Dependency alredy traversed.
if (path in inverseDependencies) {
return inverseDependencies;
}
const module = graph.dependencies.get(path);
if (!module) {
return inverseDependencies;
}
inverseDependencies[path] = [];
for (const inverse of module.inverseDependencies) {
inverseDependencies[path].push(inverse);
getInverseDependencies(inverse, graph, inverseDependencies);
}
return inverseDependencies;
}
export default function hmrJSBundle(
delta: DeltaResult<>,
graph: ReadOnlyGraph<>,
options: Options,
): {
+added: $ReadOnlyArray<HmrModule>,
+deleted: $ReadOnlyArray<number>,
+modified: $ReadOnlyArray<HmrModule>,
} {
return {
added: generateModules(delta.added.values(), graph, options),
modified: generateModules(delta.modified.values(), graph, options),
deleted: [...delta.deleted].map((path: string) =>
options.createModuleId(path),
),
};
}

View File

@@ -0,0 +1,76 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.sourceMapGenerator = sourceMapGenerator;
exports.sourceMapGeneratorNonBlocking = sourceMapGeneratorNonBlocking;
var _getSourceMapInfo = _interopRequireDefault(
require("./helpers/getSourceMapInfo"),
);
var _js = require("./helpers/js");
var _metroSourceMap = require("metro-source-map");
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
function getSourceMapInfosImpl(isBlocking, onDone, modules, options) {
const sourceMapInfos = [];
const modulesToProcess = modules
.filter(_js.isJsModule)
.filter(options.processModuleFilter);
function processNextModule() {
if (modulesToProcess.length === 0) {
return true;
}
const mod = modulesToProcess.shift();
const info = (0, _getSourceMapInfo.default)(mod, {
excludeSource: options.excludeSource,
shouldAddToIgnoreList: options.shouldAddToIgnoreList,
getSourceUrl: options.getSourceUrl,
});
sourceMapInfos.push(info);
return false;
}
function workLoop() {
const time = process.hrtime();
while (true) {
const isDone = processNextModule();
if (isDone) {
onDone(sourceMapInfos);
break;
}
if (!isBlocking) {
const diff = process.hrtime(time);
const NS_IN_MS = 1000000;
if (diff[1] > 50 * NS_IN_MS) {
setImmediate(workLoop);
break;
}
}
}
}
workLoop();
}
function sourceMapGenerator(modules, options) {
let sourceMapInfos;
getSourceMapInfosImpl(
true,
(infos) => {
sourceMapInfos = infos;
},
modules,
options,
);
if (sourceMapInfos == null) {
throw new Error(
"Expected getSourceMapInfosImpl() to finish synchronously.",
);
}
return (0, _metroSourceMap.fromRawMappings)(sourceMapInfos);
}
async function sourceMapGeneratorNonBlocking(modules, options) {
const sourceMapInfos = await new Promise((resolve) => {
getSourceMapInfosImpl(false, resolve, modules, options);
});
return (0, _metroSourceMap.fromRawMappingsNonBlocking)(sourceMapInfos);
}

View File

@@ -0,0 +1,111 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/
import type {Module} from '../types';
import getSourceMapInfo from './helpers/getSourceMapInfo';
import {isJsModule} from './helpers/js';
import {fromRawMappings, fromRawMappingsNonBlocking} from 'metro-source-map';
export type SourceMapGeneratorOptions = $ReadOnly<{
excludeSource: boolean,
processModuleFilter: (module: Module<>) => boolean,
shouldAddToIgnoreList: (module: Module<>) => boolean,
getSourceUrl: ?(module: Module<>) => string,
}>;
function getSourceMapInfosImpl(
isBlocking: boolean,
onDone: ($ReadOnlyArray<ReturnType<typeof getSourceMapInfo>>) => void,
modules: $ReadOnlyArray<Module<>>,
options: SourceMapGeneratorOptions,
): void {
const sourceMapInfos = [];
const modulesToProcess = modules
.filter(isJsModule)
.filter(options.processModuleFilter);
function processNextModule() {
if (modulesToProcess.length === 0) {
return true;
}
const mod = modulesToProcess.shift();
// $FlowFixMe[incompatible-type]
const info = getSourceMapInfo(mod, {
excludeSource: options.excludeSource,
shouldAddToIgnoreList: options.shouldAddToIgnoreList,
getSourceUrl: options.getSourceUrl,
});
sourceMapInfos.push(info);
return false;
}
function workLoop() {
const time = process.hrtime();
while (true) {
const isDone = processNextModule();
if (isDone) {
onDone(sourceMapInfos);
break;
}
if (!isBlocking) {
// Keep the loop running but try to avoid blocking
// for too long because this is not in a worker yet.
const diff = process.hrtime(time);
const NS_IN_MS = 1000000;
if (diff[1] > 50 * NS_IN_MS) {
// We've blocked for more than 50ms.
// This code currently runs on the main thread,
// so let's give Metro an opportunity to handle requests.
setImmediate(workLoop);
break;
}
}
}
}
workLoop();
}
function sourceMapGenerator(
modules: $ReadOnlyArray<Module<>>,
options: SourceMapGeneratorOptions,
): ReturnType<typeof fromRawMappings> {
let sourceMapInfos;
getSourceMapInfosImpl(
true,
infos => {
sourceMapInfos = infos;
},
modules,
options,
);
if (sourceMapInfos == null) {
throw new Error(
'Expected getSourceMapInfosImpl() to finish synchronously.',
);
}
return fromRawMappings(sourceMapInfos);
}
async function sourceMapGeneratorNonBlocking(
modules: $ReadOnlyArray<Module<>>,
options: SourceMapGeneratorOptions,
): ReturnType<typeof fromRawMappingsNonBlocking> {
const sourceMapInfos = await new Promise<
$ReadOnlyArray<ReturnType<typeof getSourceMapInfo>>,
>(resolve => {
getSourceMapInfosImpl(false, resolve, modules, options);
});
return fromRawMappingsNonBlocking(sourceMapInfos);
}
export {sourceMapGenerator, sourceMapGeneratorNonBlocking};

View File

@@ -0,0 +1,24 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.sourceMapObject = sourceMapObject;
exports.sourceMapObjectNonBlocking = sourceMapObjectNonBlocking;
var _sourceMapGenerator = require("./sourceMapGenerator");
function sourceMapObject(modules, options) {
const generator = (0, _sourceMapGenerator.sourceMapGenerator)(
modules,
options,
);
return generator.toMap(undefined, {
excludeSource: options.excludeSource,
});
}
async function sourceMapObjectNonBlocking(modules, options) {
const generator = await (0,
_sourceMapGenerator.sourceMapGeneratorNonBlocking)(modules, options);
return generator.toMap(undefined, {
excludeSource: options.excludeSource,
});
}

View File

@@ -0,0 +1,41 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/
import type {Module} from '../types';
import type {SourceMapGeneratorOptions} from './sourceMapGenerator';
import type {MixedSourceMap} from 'metro-source-map';
import {
sourceMapGenerator,
sourceMapGeneratorNonBlocking,
} from './sourceMapGenerator';
function sourceMapObject(
modules: $ReadOnlyArray<Module<>>,
options: SourceMapGeneratorOptions,
): MixedSourceMap {
const generator = sourceMapGenerator(modules, options);
return generator.toMap(undefined, {
excludeSource: options.excludeSource,
});
}
async function sourceMapObjectNonBlocking(
modules: $ReadOnlyArray<Module<>>,
options: SourceMapGeneratorOptions,
): Promise<MixedSourceMap> {
const generator = await sourceMapGeneratorNonBlocking(modules, options);
return generator.toMap(undefined, {
excludeSource: options.excludeSource,
});
}
export {sourceMapObject, sourceMapObjectNonBlocking};

View File

@@ -0,0 +1,23 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.sourceMapString = sourceMapString;
exports.sourceMapStringNonBlocking = sourceMapStringNonBlocking;
var _sourceMapGenerator = require("./sourceMapGenerator");
function sourceMapString(modules, options) {
return (0, _sourceMapGenerator.sourceMapGenerator)(modules, options).toString(
undefined,
{
excludeSource: options.excludeSource,
},
);
}
async function sourceMapStringNonBlocking(modules, options) {
const generator = await (0,
_sourceMapGenerator.sourceMapGeneratorNonBlocking)(modules, options);
return generator.toString(undefined, {
excludeSource: options.excludeSource,
});
}

View File

@@ -0,0 +1,39 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/
import type {Module} from '../types';
import type {SourceMapGeneratorOptions} from './sourceMapGenerator';
import {
sourceMapGenerator,
sourceMapGeneratorNonBlocking,
} from './sourceMapGenerator';
function sourceMapString(
modules: $ReadOnlyArray<Module<>>,
options: SourceMapGeneratorOptions,
): string {
return sourceMapGenerator(modules, options).toString(undefined, {
excludeSource: options.excludeSource,
});
}
async function sourceMapStringNonBlocking(
modules: $ReadOnlyArray<Module<>>,
options: SourceMapGeneratorOptions,
): Promise<string> {
const generator = await sourceMapGeneratorNonBlocking(modules, options);
return generator.toString(undefined, {
excludeSource: options.excludeSource,
});
}
export {sourceMapString, sourceMapStringNonBlocking};

159
node_modules/metro/src/DeltaBundler/Transformer.js generated vendored Normal file
View File

@@ -0,0 +1,159 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
var _pathUtils = require("../lib/pathUtils");
var _getTransformCacheKey = _interopRequireDefault(
require("./getTransformCacheKey"),
);
var _WorkerFarm = _interopRequireDefault(require("./WorkerFarm"));
var _assert = _interopRequireDefault(require("assert"));
var _crypto = _interopRequireDefault(require("crypto"));
var _fs = _interopRequireDefault(require("fs"));
var _metroCache = require("metro-cache");
var _path = _interopRequireDefault(require("path"));
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
const debug = require("debug")("Metro:Transformer");
class Transformer {
constructor(config, opts) {
this._config = config;
this._config.watchFolders.forEach(verifyRootExists);
this._cache = new _metroCache.Cache(config.cacheStores);
this._getSha1 = opts.getOrComputeSha1;
const {
getTransformOptions: _getTransformOptions,
transformVariants: _transformVariants,
unstable_workerThreads: _workerThreads,
...transformerConfig
} = this._config.transformer;
const transformerOptions = {
transformerPath: this._config.transformerPath,
transformerConfig,
};
this._workerFarm = new _WorkerFarm.default(config, transformerOptions);
const globalCacheKey = this._cache.isDisabled
? ""
: (0, _getTransformCacheKey.default)({
cacheVersion: this._config.cacheVersion,
projectRoot: this._config.projectRoot,
transformerConfig: transformerOptions,
});
const baseHashBuffer = (0, _metroCache.stableHash)([globalCacheKey]);
this._baseHash = baseHashBuffer.toString("binary");
debug("Base hash: %s", baseHashBuffer.toString("hex"));
}
async transformFile(filePath, transformerOptions, fileBuffer) {
const cache = this._cache;
const {
customTransformOptions,
dev,
experimentalImportSupport,
inlinePlatform,
inlineRequires,
minify,
nonInlinedRequires,
platform,
type,
unstable_transformProfile,
unstable_memoizeInlineRequires,
unstable_nonMemoizedInlineRequires,
...extra
} = transformerOptions;
for (const key in extra) {
if (hasOwnProperty.call(extra, key)) {
throw new Error(
"Extra keys detected: " + Object.keys(extra).join(", "),
);
}
}
const localPath = _path.default.relative(
this._config.projectRoot,
filePath,
);
const partialKey = (0, _metroCache.stableHash)([
this._baseHash,
(0, _pathUtils.normalizePathSeparatorsToPosix)(localPath),
customTransformOptions,
dev,
experimentalImportSupport,
inlinePlatform,
inlineRequires,
minify,
nonInlinedRequires,
platform,
type,
unstable_memoizeInlineRequires,
unstable_nonMemoizedInlineRequires,
unstable_transformProfile,
]);
let sha1;
let content;
if (fileBuffer) {
sha1 = _crypto.default
.createHash("sha1")
.update(fileBuffer)
.digest("hex");
content = fileBuffer;
} else {
const result = await this._getSha1(filePath);
sha1 = result.sha1;
if (result.content) {
content = result.content;
}
}
let fullKey = Buffer.concat([partialKey, Buffer.from(sha1, "hex")]);
let result;
try {
result = await cache.get(fullKey);
} catch (error) {
this._config.reporter.update({
type: "cache_read_error",
error,
});
throw error;
}
const data = result
? {
result,
sha1,
}
: await this._workerFarm.transform(
localPath,
transformerOptions,
content,
);
if (sha1 !== data.sha1) {
fullKey = Buffer.concat([partialKey, Buffer.from(data.sha1, "hex")]);
}
cache.set(fullKey, data.result).catch((error) => {
this._config.reporter.update({
type: "cache_write_error",
error,
});
});
return {
...data.result,
unstable_transformResultKey: fullKey.toString(),
getSource() {
if (fileBuffer) {
return fileBuffer;
}
return _fs.default.readFileSync(filePath);
},
};
}
async end() {
await this._workerFarm.kill();
}
}
exports.default = Transformer;
function verifyRootExists(root) {
(0, _assert.default)(
_fs.default.statSync(root).isDirectory(),
"Root has to be a valid directory",
);
}

209
node_modules/metro/src/DeltaBundler/Transformer.js.flow generated vendored Normal file
View File

@@ -0,0 +1,209 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/
import type {TransformResult, TransformResultWithSource} from '../DeltaBundler';
import type {TransformerConfig, TransformOptions} from './Worker';
import type {ConfigT} from 'metro-config';
import {normalizePathSeparatorsToPosix} from '../lib/pathUtils';
import getTransformCacheKey from './getTransformCacheKey';
import WorkerFarm from './WorkerFarm';
import assert from 'assert';
import crypto from 'crypto';
import fs from 'fs';
import {Cache, stableHash} from 'metro-cache';
import path from 'path';
// eslint-disable-next-line import/no-commonjs
const debug = require('debug')('Metro:Transformer');
type GetOrComputeSha1Fn = string => Promise<
$ReadOnly<{content?: Buffer, sha1: string}>,
>;
export default class Transformer {
_config: ConfigT;
_cache: Cache<TransformResult<>>;
_baseHash: string;
_getSha1: GetOrComputeSha1Fn;
_workerFarm: WorkerFarm;
constructor(
config: ConfigT,
opts: $ReadOnly<{getOrComputeSha1: GetOrComputeSha1Fn}>,
) {
this._config = config;
this._config.watchFolders.forEach(verifyRootExists);
this._cache = new Cache(config.cacheStores);
this._getSha1 = opts.getOrComputeSha1;
// Remove the transformer config params that we don't want to pass to the
// transformer. We should change the config object and move them away so we
// can treat the transformer config params as opaque.
const {
getTransformOptions: _getTransformOptions,
transformVariants: _transformVariants,
unstable_workerThreads: _workerThreads,
...transformerConfig
} = this._config.transformer;
const transformerOptions: TransformerConfig = {
transformerPath: this._config.transformerPath,
transformerConfig,
};
this._workerFarm = new WorkerFarm(config, transformerOptions);
const globalCacheKey = this._cache.isDisabled
? ''
: getTransformCacheKey({
cacheVersion: this._config.cacheVersion,
projectRoot: this._config.projectRoot,
transformerConfig: transformerOptions,
});
const baseHashBuffer = stableHash([globalCacheKey]);
this._baseHash = baseHashBuffer.toString('binary');
debug('Base hash: %s', baseHashBuffer.toString('hex'));
}
async transformFile(
filePath: string,
transformerOptions: TransformOptions,
fileBuffer?: Buffer,
): Promise<TransformResultWithSource<>> {
const cache = this._cache;
const {
customTransformOptions,
dev,
experimentalImportSupport,
inlinePlatform,
inlineRequires,
minify,
nonInlinedRequires,
platform,
type,
unstable_transformProfile,
unstable_memoizeInlineRequires,
unstable_nonMemoizedInlineRequires,
...extra
} = transformerOptions;
for (const key in extra) {
// $FlowFixMe[cannot-resolve-name]
if (hasOwnProperty.call(extra, key)) {
throw new Error(
'Extra keys detected: ' + Object.keys(extra).join(', '),
);
}
}
const localPath = path.relative(this._config.projectRoot, filePath);
const partialKey = stableHash([
// This is the hash related to the global Bundler config.
this._baseHash,
// Project-relative, posix-separated path for portability. Necessary in
// addition to content hash because transformers receive path as an
// input, and may apply e.g. extension-based logic.
normalizePathSeparatorsToPosix(localPath),
customTransformOptions,
dev,
experimentalImportSupport,
inlinePlatform,
inlineRequires,
minify,
nonInlinedRequires,
platform,
type,
unstable_memoizeInlineRequires,
unstable_nonMemoizedInlineRequires,
unstable_transformProfile,
]);
let sha1: string;
let content: ?Buffer;
if (fileBuffer) {
// Shortcut for virtual modules which provide the contents with the filename.
sha1 = crypto.createHash('sha1').update(fileBuffer).digest('hex');
content = fileBuffer;
} else {
const result = await this._getSha1(filePath);
sha1 = result.sha1;
if (result.content) {
content = result.content;
}
}
let fullKey = Buffer.concat([partialKey, Buffer.from(sha1, 'hex')]);
let result;
try {
result = await cache.get(fullKey);
} catch (error) {
this._config.reporter.update({
type: 'cache_read_error',
error,
});
throw error;
}
// A valid result from the cache is used directly; otherwise we call into
// the transformer to computed the corresponding result.
const data: $ReadOnly<{
result: TransformResult<>,
sha1: string,
}> = result
? {result, sha1}
: await this._workerFarm.transform(
localPath,
transformerOptions,
content,
);
// Only re-compute the full key if the SHA-1 changed. This is because
// references are used by the cache implementation in a weak map to keep
// track of the cache that returned the result.
if (sha1 !== data.sha1) {
fullKey = Buffer.concat([partialKey, Buffer.from(data.sha1, 'hex')]);
}
// Fire-and-forget cache set promise.
cache.set(fullKey, data.result).catch(error => {
this._config.reporter.update({
type: 'cache_write_error',
error,
});
});
return {
...data.result,
unstable_transformResultKey: fullKey.toString(),
getSource(): Buffer {
if (fileBuffer) {
return fileBuffer;
}
return fs.readFileSync(filePath);
},
};
}
async end(): Promise<void> {
await this._workerFarm.kill();
}
}
function verifyRootExists(root: string): void {
// Verify that the root exists.
assert(fs.statSync(root).isDirectory(), 'Root has to be a valid directory');
}

45
node_modules/metro/src/DeltaBundler/Worker.d.ts generated vendored Normal file
View File

@@ -0,0 +1,45 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @oncall react_native
*/
import type {TransformResult} from './types';
import type {
JsTransformerConfig,
JsTransformOptions,
} from 'metro-transform-worker';
type LogEntry = unknown;
export type TransformOptions = JsTransformOptions;
export interface Worker {
readonly transform: (
filename: string,
transformOptions: JsTransformOptions,
projectRoot: string,
transformerConfig: TransformerConfig,
fileBuffer?: Buffer,
) => Promise<Data>;
}
export interface TransformerConfig {
transformerPath: string;
transformerConfig: JsTransformerConfig;
}
interface Data {
readonly result: TransformResult<void>;
readonly sha1: string;
readonly transformFileStartLogEntry: LogEntry;
readonly transformFileEndLogEntry: LogEntry;
}
declare const worker: Worker;
export default worker;

96
node_modules/metro/src/DeltaBundler/Worker.flow.js generated vendored Normal file
View File

@@ -0,0 +1,96 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.transform = void 0;
var _traverse = _interopRequireDefault(require("@babel/traverse"));
var _crypto = _interopRequireDefault(require("crypto"));
var _fs = _interopRequireDefault(require("fs"));
var _path = _interopRequireDefault(require("path"));
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
function asDeserializedBuffer(value) {
if (Buffer.isBuffer(value)) {
return value;
}
if (value && value.type === "Buffer") {
return Buffer.from(value.data);
}
if (ArrayBuffer.isView(value)) {
return Buffer.from(value.buffer, value.byteOffset, value.byteLength);
}
return null;
}
const transform = (
filename,
transformOptions,
projectRoot,
transformerConfig,
fileBuffer,
) => {
let data;
const fileBufferObject = asDeserializedBuffer(fileBuffer);
if (fileBufferObject) {
data = fileBufferObject;
} else {
data = _fs.default.readFileSync(
_path.default.resolve(projectRoot, filename),
);
}
return transformFile(
filename,
data,
transformOptions,
projectRoot,
transformerConfig,
);
};
exports.transform = transform;
async function transformFile(
filename,
data,
transformOptions,
projectRoot,
transformerConfig,
) {
const Transformer = require.call(null, transformerConfig.transformerPath);
const transformFileStartLogEntry = {
action_name: "Transforming file",
action_phase: "start",
file_name: filename,
log_entry_label: "Transforming file",
start_timestamp: process.hrtime(),
};
const sha1 = _crypto.default.createHash("sha1").update(data).digest("hex");
const result = await Transformer.transform(
transformerConfig.transformerConfig,
projectRoot,
filename,
data,
transformOptions,
);
_traverse.default.cache.clear();
const transformFileEndLogEntry = getEndLogEntry(
transformFileStartLogEntry,
filename,
);
return {
result,
sha1,
transformFileStartLogEntry,
transformFileEndLogEntry,
};
}
function getEndLogEntry(startLogEntry, filename) {
const timeDelta = process.hrtime(startLogEntry.start_timestamp);
const duration_ms = Math.round((timeDelta[0] * 1e9 + timeDelta[1]) / 1e6);
return {
action_name: "Transforming file",
action_phase: "end",
file_name: filename,
duration_ms,
log_entry_label: "Transforming file",
};
}

160
node_modules/metro/src/DeltaBundler/Worker.flow.js.flow generated vendored Normal file
View File

@@ -0,0 +1,160 @@
/**
* Copyright (c) Meta Platforms, Inc. and 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
* @oncall react_native
*/
import type {TransformResult} from './types';
import type {LogEntry} from 'metro-core/private/Logger';
import type {
JsTransformerConfig,
JsTransformOptions,
} from 'metro-transform-worker';
import traverse from '@babel/traverse';
import crypto from 'crypto';
import fs from 'fs';
import path from 'path';
export type {JsTransformOptions as TransformOptions} from 'metro-transform-worker';
type TransformerInterface = {
transform(
JsTransformerConfig,
string,
string,
Buffer,
JsTransformOptions,
): Promise<TransformResult<>>,
};
export type TransformerConfig = {
transformerPath: string,
transformerConfig: JsTransformerConfig,
...
};
type Data = $ReadOnly<{
result: TransformResult<>,
sha1: string,
transformFileStartLogEntry: LogEntry,
transformFileEndLogEntry: LogEntry,
}>;
/**
* When the `Buffer` is sent over the worker thread it gets serialized into a JSON object.
* This helper method will deserialize it if needed.
*
* @returns `Buffer` representation of the JSON object.
* @returns `null` if the given object is nullish or not a serialized `Buffer` object.
*/
function asDeserializedBuffer(value: any): Buffer | null {
if (Buffer.isBuffer(value)) {
return value;
}
if (value && value.type === 'Buffer') {
return Buffer.from(value.data);
}
if (ArrayBuffer.isView(value)) {
return Buffer.from(value.buffer, value.byteOffset, value.byteLength);
}
return null;
}
export const transform = (
filename: string,
transformOptions: JsTransformOptions,
projectRoot: string,
transformerConfig: TransformerConfig,
fileBuffer?: Buffer,
): Promise<Data> => {
let data;
const fileBufferObject = asDeserializedBuffer(fileBuffer);
if (fileBufferObject) {
data = fileBufferObject;
} else {
data = fs.readFileSync(path.resolve(projectRoot, filename));
}
return transformFile(
filename,
data,
transformOptions,
projectRoot,
transformerConfig,
);
};
export type Worker = {
+transform: typeof transform,
};
async function transformFile(
filename: string,
data: Buffer,
transformOptions: JsTransformOptions,
projectRoot: string,
transformerConfig: TransformerConfig,
): Promise<Data> {
// eslint-disable-next-line no-useless-call
const Transformer: TransformerInterface = require.call(
null,
transformerConfig.transformerPath,
);
const transformFileStartLogEntry: LogEntry = {
action_name: 'Transforming file',
action_phase: 'start',
file_name: filename,
log_entry_label: 'Transforming file',
start_timestamp: process.hrtime(),
};
const sha1 = crypto.createHash('sha1').update(data).digest('hex');
const result = await Transformer.transform(
transformerConfig.transformerConfig,
projectRoot,
filename,
data,
transformOptions,
);
// The babel cache caches scopes and pathes for already traversed AST nodes.
// Clearing the cache here since the nodes of the transformed file are no longer referenced.
// This isn't stritcly necessary since the cache uses a WeakMap. However, WeakMap only permit
// that unreferenced keys are collected but the values still hold references to the Scope and NodePaths.
// Manually clearing the cache allows the GC to collect the Scope and NodePaths without checking if there
// exist any other references to the keys.
traverse.cache.clear();
const transformFileEndLogEntry = getEndLogEntry(
transformFileStartLogEntry,
filename,
);
return {
result,
sha1,
transformFileStartLogEntry,
transformFileEndLogEntry,
};
}
function getEndLogEntry(startLogEntry: LogEntry, filename: string): LogEntry {
const timeDelta = process.hrtime(startLogEntry.start_timestamp);
const duration_ms = Math.round((timeDelta[0] * 1e9 + timeDelta[1]) / 1e6);
return {
action_name: 'Transforming file',
action_phase: 'end',
file_name: filename,
duration_ms,
log_entry_label: 'Transforming file',
};
}

6
node_modules/metro/src/DeltaBundler/Worker.js generated vendored Normal file
View File

@@ -0,0 +1,6 @@
"use strict";
try {
require("metro-babel-register").unstable_registerForMetroMonorepo();
} catch {}
module.exports = require("./Worker.flow");

24
node_modules/metro/src/DeltaBundler/Worker.js.flow generated vendored Normal file
View File

@@ -0,0 +1,24 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/
/* eslint-disable import/no-commonjs */
'use strict';
/*::
export type * from './Worker.flow';
*/
try {
require('metro-babel-register').unstable_registerForMetroMonorepo();
} catch {}
module.exports = require('./Worker.flow');

116
node_modules/metro/src/DeltaBundler/WorkerFarm.js generated vendored Normal file
View File

@@ -0,0 +1,116 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
var _jestWorker = require("jest-worker");
var _metroCore = require("metro-core");
class WorkerFarm {
constructor(config, transformerConfig) {
this._config = config;
this._transformerConfig = transformerConfig;
const absoluteWorkerPath = require.resolve("./Worker.js");
if (this._config.maxWorkers > 1) {
const worker = this._makeFarm(
absoluteWorkerPath,
["transform"],
this._config.maxWorkers,
);
worker.getStdout().on("data", (chunk) => {
this._config.reporter.update({
type: "worker_stdout_chunk",
chunk: chunk.toString("utf8"),
});
});
worker.getStderr().on("data", (chunk) => {
this._config.reporter.update({
type: "worker_stderr_chunk",
chunk: chunk.toString("utf8"),
});
});
this._worker = worker;
} else {
this._worker = require("./Worker");
}
}
async kill() {
if (this._worker && typeof this._worker.end === "function") {
await this._worker.end();
}
}
async transform(filename, options, fileBuffer) {
try {
const data = await this._worker.transform(
filename,
options,
this._config.projectRoot,
this._transformerConfig,
fileBuffer,
);
_metroCore.Logger.log(data.transformFileStartLogEntry);
_metroCore.Logger.log(data.transformFileEndLogEntry);
return {
result: data.result,
sha1: data.sha1,
};
} catch (err) {
if (err.loc) {
throw this._formatBabelError(err, filename);
} else {
throw this._formatGenericError(err, filename);
}
}
}
_makeFarm(absoluteWorkerPath, exposedMethods, numWorkers) {
const env = {
...process.env,
FORCE_COLOR: 1,
};
return new _jestWorker.Worker(absoluteWorkerPath, {
computeWorkerKey: this._config.stickyWorkers
? this._computeWorkerKey
: undefined,
exposedMethods,
enableWorkerThreads: this._config.transformer.unstable_workerThreads,
forkOptions: {
env,
},
numWorkers,
workerSchedulingPolicy: "in-order",
});
}
_computeWorkerKey(method, filename) {
if (method === "transform") {
return filename;
}
return null;
}
_formatGenericError(err, filename) {
const error = new TransformError(`${filename}: ${err.message}`);
return Object.assign(error, {
stack: (err.stack || "").split("\n").slice(0, -1).join("\n"),
lineNumber: 0,
});
}
_formatBabelError(err, filename) {
const error = new TransformError(
`${err.type || "Error"}${err.message.includes(filename) ? "" : " in " + filename}: ${err.message}`,
);
return Object.assign(error, {
stack: err.stack,
snippet: err.codeFrame,
lineNumber: err.loc.line,
column: err.loc.column,
filename,
});
}
}
exports.default = WorkerFarm;
class TransformError extends SyntaxError {
type = "TransformError";
constructor(message) {
super(message);
Error.captureStackTrace && Error.captureStackTrace(this, TransformError);
}
}

182
node_modules/metro/src/DeltaBundler/WorkerFarm.js.flow generated vendored Normal file
View File

@@ -0,0 +1,182 @@
/**
* Copyright (c) Meta Platforms, Inc. and 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
* @oncall react_native
*/
import type {TransformResult} from '../DeltaBundler';
import type {TransformerConfig, TransformOptions, Worker} from './Worker';
import type {ConfigT} from 'metro-config';
import type {Readable} from 'stream';
import {Worker as JestWorker} from 'jest-worker';
import {Logger} from 'metro-core';
type WorkerInterface = {
getStdout(): Readable,
getStderr(): Readable,
end(): void,
...Worker,
};
type TransformerResult = $ReadOnly<{
result: TransformResult<>,
sha1: string,
}>;
export default class WorkerFarm {
_config: ConfigT;
_transformerConfig: TransformerConfig;
_worker: WorkerInterface | Worker;
constructor(config: ConfigT, transformerConfig: TransformerConfig) {
this._config = config;
this._transformerConfig = transformerConfig;
const absoluteWorkerPath = require.resolve('./Worker.js');
if (this._config.maxWorkers > 1) {
const worker = this._makeFarm(
absoluteWorkerPath,
['transform'],
this._config.maxWorkers,
);
worker.getStdout().on('data', chunk => {
this._config.reporter.update({
type: 'worker_stdout_chunk',
chunk: chunk.toString('utf8'),
});
});
worker.getStderr().on('data', chunk => {
this._config.reporter.update({
type: 'worker_stderr_chunk',
chunk: chunk.toString('utf8'),
});
});
this._worker = worker;
} else {
// eslint-disable-next-line import/no-commonjs
this._worker = (require('./Worker'): Worker);
}
}
async kill(): Promise<void> {
if (this._worker && typeof this._worker.end === 'function') {
await this._worker.end();
}
}
async transform(
filename: string,
options: TransformOptions,
fileBuffer?: Buffer,
): Promise<TransformerResult> {
try {
const data = await this._worker.transform(
filename,
options,
this._config.projectRoot,
this._transformerConfig,
fileBuffer,
);
Logger.log(data.transformFileStartLogEntry);
Logger.log(data.transformFileEndLogEntry);
return {
result: data.result,
sha1: data.sha1,
};
} catch (err) {
if (err.loc) {
throw this._formatBabelError(err, filename);
} else {
throw this._formatGenericError(err, filename);
}
}
}
_makeFarm(
absoluteWorkerPath: string,
exposedMethods: $ReadOnlyArray<string>,
numWorkers: number,
): any {
const env = {
...process.env,
// Force color to print syntax highlighted code frames.
FORCE_COLOR: 1,
};
return new JestWorker(absoluteWorkerPath, {
computeWorkerKey: this._config.stickyWorkers
? // $FlowFixMe[method-unbinding] added when improving typing for this parameters
// $FlowFixMe[incompatible-type]
this._computeWorkerKey
: undefined,
exposedMethods,
enableWorkerThreads: this._config.transformer.unstable_workerThreads,
forkOptions: {env},
numWorkers,
// Prefer using lower numbered available workers repeatedly rather than
// spreading jobs over the whole pool evenly (round-robin). This is more
// likely to use faster, hot workers earlier in graph traversal, when
// we want to be able to fan out as quickly as possible, and therefore
// gets us to full speed faster.
workerSchedulingPolicy: 'in-order',
});
}
_computeWorkerKey(method: string, filename: string): ?string {
// Only when transforming a file we want to stick to the same worker; and
// we'll shard by file path. If not; we return null, which tells the worker
// to pick the first available one.
if (method === 'transform') {
return filename;
}
return null;
}
_formatGenericError(err: any, filename: string): TransformError {
const error = new TransformError(`${filename}: ${err.message}`);
// $FlowFixMe[unsafe-object-assign]
return Object.assign(error, {
stack: (err.stack || '').split('\n').slice(0, -1).join('\n'),
lineNumber: 0,
});
}
_formatBabelError(err: any, filename: string): TransformError {
const error = new TransformError(
`${err.type || 'Error'}${
err.message.includes(filename) ? '' : ' in ' + filename
}: ${err.message}`,
);
// $FlowFixMe[prop-missing]
// $FlowExpectedError[unsafe-object-assign] : TODO(t67543470): Change this to properly extend the error.
return Object.assign(error, {
stack: err.stack,
snippet: err.codeFrame,
lineNumber: err.loc.line,
column: err.loc.column,
filename,
});
}
}
class TransformError extends SyntaxError {
type: string = 'TransformError';
constructor(message: string) {
super(message);
Error.captureStackTrace && Error.captureStackTrace(this, TransformError);
}
}

View File

@@ -0,0 +1,17 @@
"use strict";
const fs = require("fs");
module.exports = {
getHasteName(filename) {
const matches = fs
.readFileSync(filename, "utf8")
.match(/@providesModule ([^\n]+)/);
if (!matches) {
return undefined;
}
return matches[1];
},
getCacheKey() {
return "hasteImplFixture";
},
};

116
node_modules/metro/src/DeltaBundler/buildSubgraph.js generated vendored Normal file
View File

@@ -0,0 +1,116 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.buildSubgraph = buildSubgraph;
var _contextModule = require("../lib/contextModule");
var _isResolvedDependency = require("../lib/isResolvedDependency");
var _path = _interopRequireDefault(require("path"));
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
function resolveDependencies(parentPath, dependencies, resolve) {
const maybeResolvedDeps = new Map();
const resolvedContexts = new Map();
for (const dep of dependencies) {
let maybeResolvedDep;
const key = dep.data.key;
const { contextParams } = dep.data;
if (contextParams) {
const from = _path.default.join(parentPath, "..", dep.name);
const absolutePath = (0, _contextModule.deriveAbsolutePathFromContext)(
from,
contextParams,
);
const resolvedContext = {
from,
mode: contextParams.mode,
recursive: contextParams.recursive,
filter: new RegExp(
contextParams.filter.pattern,
contextParams.filter.flags,
),
};
resolvedContexts.set(key, resolvedContext);
maybeResolvedDep = {
absolutePath,
data: dep,
};
} else {
try {
maybeResolvedDep = {
absolutePath: resolve(parentPath, dep).filePath,
data: dep,
};
} catch (error) {
if (dep.data.isOptional !== true) {
throw error;
}
maybeResolvedDep = {
data: dep,
};
}
}
if (maybeResolvedDeps.has(key)) {
throw new Error(
`resolveDependencies: Found duplicate dependency key '${key}' in ${parentPath}`,
);
}
maybeResolvedDeps.set(key, maybeResolvedDep);
}
return {
dependencies: maybeResolvedDeps,
resolvedContexts,
};
}
async function buildSubgraph(
entryPaths,
resolvedContexts,
{ resolve, transform, shouldTraverse },
) {
const moduleData = new Map();
const errors = new Map();
const visitedPaths = new Set();
async function visit(absolutePath, requireContext) {
if (visitedPaths.has(absolutePath)) {
return;
}
visitedPaths.add(absolutePath);
const transformResult = await transform(absolutePath, requireContext);
const resolutionResult = resolveDependencies(
absolutePath,
transformResult.dependencies,
resolve,
);
moduleData.set(absolutePath, {
...transformResult,
...resolutionResult,
});
await Promise.all(
[...resolutionResult.dependencies.values()]
.filter(
(dependency) =>
(0, _isResolvedDependency.isResolvedDependency)(dependency) &&
shouldTraverse(dependency),
)
.map((dependency) =>
visit(
dependency.absolutePath,
resolutionResult.resolvedContexts.get(dependency.data.data.key),
).catch((error) => errors.set(dependency.absolutePath, error)),
),
);
}
await Promise.all(
[...entryPaths].map((absolutePath) =>
visit(absolutePath, resolvedContexts.get(absolutePath)).catch((error) =>
errors.set(absolutePath, error),
),
),
);
return {
moduleData,
errors,
};
}

View File

@@ -0,0 +1,161 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
*/
import type {RequireContext} from '../lib/contextModule';
import type {
Dependency,
ModuleData,
ResolvedDependency,
ResolveFn,
TransformFn,
TransformResultDependency,
} from './types';
import {deriveAbsolutePathFromContext} from '../lib/contextModule';
import {isResolvedDependency} from '../lib/isResolvedDependency';
import path from 'path';
type Parameters<T> = $ReadOnly<{
resolve: ResolveFn,
transform: TransformFn<T>,
shouldTraverse: ResolvedDependency => boolean,
}>;
function resolveDependencies(
parentPath: string,
dependencies: $ReadOnlyArray<TransformResultDependency>,
resolve: ResolveFn,
): {
dependencies: Map<string, Dependency>,
resolvedContexts: Map<string, RequireContext>,
} {
const maybeResolvedDeps = new Map<string, Dependency>();
const resolvedContexts = new Map<string, RequireContext>();
for (const dep of dependencies) {
let maybeResolvedDep: Dependency;
const key = dep.data.key;
// `require.context`
const {contextParams} = dep.data;
if (contextParams) {
// Ensure the filepath has uniqueness applied to ensure multiple `require.context`
// statements can be used to target the same file with different properties.
const from = path.join(parentPath, '..', dep.name);
const absolutePath = deriveAbsolutePathFromContext(from, contextParams);
const resolvedContext: RequireContext = {
from,
mode: contextParams.mode,
recursive: contextParams.recursive,
filter: new RegExp(
contextParams.filter.pattern,
contextParams.filter.flags,
),
};
resolvedContexts.set(key, resolvedContext);
maybeResolvedDep = {
absolutePath,
data: dep,
};
} else {
try {
maybeResolvedDep = {
absolutePath: resolve(parentPath, dep).filePath,
data: dep,
};
} catch (error) {
// Ignore unavailable optional dependencies. They are guarded
// with a try-catch block and will be handled during runtime.
if (dep.data.isOptional !== true) {
throw error;
}
maybeResolvedDep = {
data: dep,
};
}
}
if (maybeResolvedDeps.has(key)) {
throw new Error(
`resolveDependencies: Found duplicate dependency key '${key}' in ${parentPath}`,
);
}
maybeResolvedDeps.set(key, maybeResolvedDep);
}
return {
dependencies: maybeResolvedDeps,
resolvedContexts,
};
}
export async function buildSubgraph<T>(
entryPaths: $ReadOnlySet<string>,
resolvedContexts: $ReadOnlyMap<string, ?RequireContext>,
{resolve, transform, shouldTraverse}: Parameters<T>,
): Promise<{
moduleData: Map<string, ModuleData<T>>,
errors: Map<string, Error>,
}> {
const moduleData: Map<string, ModuleData<T>> = new Map();
const errors: Map<string, Error> = new Map();
const visitedPaths: Set<string> = new Set();
async function visit(
absolutePath: string,
requireContext: ?RequireContext,
): Promise<void> {
if (visitedPaths.has(absolutePath)) {
return;
}
visitedPaths.add(absolutePath);
const transformResult = await transform(absolutePath, requireContext);
// Get the absolute path of all sub-dependencies (some of them could have been
// moved but maintain the same relative path).
const resolutionResult = resolveDependencies(
absolutePath,
transformResult.dependencies,
resolve,
);
moduleData.set(absolutePath, {
...transformResult,
...resolutionResult,
});
await Promise.all(
[...resolutionResult.dependencies.values()]
.filter(
dependency =>
isResolvedDependency(dependency) && shouldTraverse(dependency),
)
.map(dependency =>
visit(
dependency.absolutePath,
resolutionResult.resolvedContexts.get(dependency.data.data.key),
).catch(error => errors.set(dependency.absolutePath, error)),
),
);
}
await Promise.all(
[...entryPaths].map(absolutePath =>
visit(absolutePath, resolvedContexts.get(absolutePath)).catch(error =>
errors.set(absolutePath, error),
),
),
);
return {moduleData, errors};
}

View File

@@ -0,0 +1,32 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = getTransformCacheKey;
var _crypto = _interopRequireDefault(require("crypto"));
var _metroCacheKey = require("metro-cache-key");
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
const VERSION = require("../../package.json").version;
function getTransformCacheKey(opts) {
const { transformerPath, transformerConfig } = opts.transformerConfig;
const Transformer = require.call(null, transformerPath);
const transformerKey = Transformer.getCacheKey
? Transformer.getCacheKey(transformerConfig)
: "";
return _crypto.default
.createHash("sha1")
.update(
[
"metro-cache",
VERSION,
opts.cacheVersion,
(0, _metroCacheKey.getCacheKey)([require.resolve(transformerPath)]),
transformerKey,
transformerConfig.globalPrefix,
].join("$"),
)
.digest("hex");
}

View File

@@ -0,0 +1,51 @@
/**
* Copyright (c) Meta Platforms, Inc. and 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
* @oncall react_native
*/
import type {TransformerConfig} from './Worker';
import type {JsTransformerConfig} from 'metro-transform-worker';
import crypto from 'crypto';
import {getCacheKey} from 'metro-cache-key';
// eslint-disable-next-line import/no-commonjs
const VERSION = require('../../package.json').version;
type CacheKeyProvider = {
getCacheKey?: JsTransformerConfig => string,
};
export default function getTransformCacheKey(opts: {
+cacheVersion: string,
+projectRoot: string,
+transformerConfig: TransformerConfig,
}): string {
const {transformerPath, transformerConfig} = opts.transformerConfig;
// eslint-disable-next-line no-useless-call
const Transformer: CacheKeyProvider = require.call(null, transformerPath);
const transformerKey = Transformer.getCacheKey
? Transformer.getCacheKey(transformerConfig)
: '';
return crypto
.createHash('sha1')
.update(
[
'metro-cache',
VERSION,
opts.cacheVersion,
getCacheKey([require.resolve(transformerPath)]),
transformerKey,
transformerConfig.globalPrefix,
].join('$'),
)
.digest('hex');
}

56
node_modules/metro/src/DeltaBundler/mergeDeltas.js generated vendored Normal file
View File

@@ -0,0 +1,56 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = mergeDeltas;
function mergeDeltas(delta1, delta2) {
const added1 = new Map(delta1.added);
const modified1 = new Map(delta1.modified);
const deleted1 = new Set(delta1.deleted);
const added2 = new Map(delta2.added);
const modified2 = new Map(delta2.modified);
const deleted2 = new Set(delta2.deleted);
const added = new Map();
const modified = new Map();
const deleted = new Set();
for (const [id, code] of added1) {
if (!deleted2.has(id) && !modified2.has(id)) {
added.set(id, code);
}
}
for (const [id, code] of modified1) {
if (!deleted2.has(id) && !modified2.has(id)) {
modified.set(id, code);
}
}
for (const id of deleted1) {
if (!added2.has(id)) {
deleted.add(id);
}
}
for (const [id, code] of added2) {
if (deleted1.has(id)) {
modified.set(id, code);
} else {
added.set(id, code);
}
}
for (const [id, code] of modified2) {
if (added1.has(id)) {
added.set(id, code);
} else {
modified.set(id, code);
}
}
for (const id of deleted2) {
if (!added1.has(id)) {
deleted.add(id);
}
}
return {
added: [...added.entries()],
modified: [...modified.entries()],
deleted: [...deleted],
};
}

View File

@@ -0,0 +1,73 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/
import type {DeltaBundle} from 'metro-runtime/src/modules/types';
export default function mergeDeltas(
delta1: DeltaBundle,
delta2: DeltaBundle,
): DeltaBundle {
const added1 = new Map(delta1.added);
const modified1 = new Map(delta1.modified);
const deleted1 = new Set(delta1.deleted);
const added2 = new Map(delta2.added);
const modified2 = new Map(delta2.modified);
const deleted2 = new Set(delta2.deleted);
const added = new Map<number, string>();
const modified = new Map<number, string>();
const deleted = new Set<number>();
for (const [id, code] of added1) {
if (!deleted2.has(id) && !modified2.has(id)) {
added.set(id, code);
}
}
for (const [id, code] of modified1) {
if (!deleted2.has(id) && !modified2.has(id)) {
modified.set(id, code);
}
}
for (const id of deleted1) {
if (!added2.has(id)) {
deleted.add(id);
}
}
for (const [id, code] of added2) {
if (deleted1.has(id)) {
modified.set(id, code);
} else {
added.set(id, code);
}
}
for (const [id, code] of modified2) {
if (added1.has(id)) {
added.set(id, code);
} else {
modified.set(id, code);
}
}
for (const id of deleted2) {
if (!added1.has(id)) {
deleted.add(id);
}
}
return {
added: [...added.entries()],
modified: [...modified.entries()],
deleted: [...deleted],
};
}

166
node_modules/metro/src/DeltaBundler/types.d.ts generated vendored Normal file
View File

@@ -0,0 +1,166 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @oncall react_native
*/
import type {RequireContext} from '../lib/contextModule';
import type CountingSet from '../lib/CountingSet';
import type {RequireContextParams} from '../ModuleGraph/worker/collectDependencies';
import type {Graph} from './Graph';
import type {JsTransformOptions} from 'metro-transform-worker';
export interface MixedOutput {
readonly data: {code: string};
readonly type: string;
}
export type AsyncDependencyType = 'async' | 'prefetch' | 'weak';
export interface TransformResultDependency {
/**
* The literal name provided to a require or import call. For example 'foo' in
* case of `require('foo')`.
*/
readonly name: string;
/**
* Extra data returned by the dependency extractor.
*/
readonly data: {
/**
* A locally unique key for this dependency within the current module.
*/
readonly key: string;
/**
* If not null, this dependency is due to a dynamic `import()` or `__prefetchImport()` call.
*/
readonly asyncType: AsyncDependencyType | null;
/**
* The dependency is enclosed in a try/catch block.
*/
readonly isOptional?: boolean;
readonly locs: ReadonlyArray<{
readonly start: {readonly line: number; readonly column: number};
readonly end: {readonly line: number; readonly column: number};
}>;
/** Context for requiring a collection of modules. */
readonly contextParams?: RequireContextParams;
};
}
export interface Dependency {
readonly absolutePath: string;
readonly data: TransformResultDependency;
[key: string]: unknown;
}
export interface Module<T = MixedOutput> {
readonly dependencies: Map<string, Dependency>;
readonly inverseDependencies: CountingSet<string>;
readonly output: ReadonlyArray<T>;
readonly path: string;
readonly getSource: () => Buffer;
}
export type Dependencies<T = MixedOutput> = Map<string, Module<T>>;
export type ReadOnlyDependencies<T = MixedOutput> = ReadonlyMap<
string,
Module<T>
>;
export type TransformInputOptions = Omit<
JsTransformOptions,
'inlinePlatform' | 'inlineRequires'
>;
export type GraphInputOptions = Readonly<{
entryPoints: ReadonlySet<string>;
// Unused in core but useful for custom serializers / experimentalSerializerHook
transformOptions: TransformInputOptions;
}>;
export interface ReadOnlyGraph<T = MixedOutput> {
readonly entryPoints: ReadonlySet<string>;
// Unused in core but useful for custom serializers / experimentalSerializerHook
readonly transformOptions: Readonly<TransformInputOptions>;
readonly dependencies: ReadOnlyDependencies<T>;
}
export type {Graph};
export interface TransformResult<T = MixedOutput> {
dependencies: ReadonlyArray<TransformResultDependency>;
output: ReadonlyArray<T>;
}
export interface TransformResultWithSource<T = MixedOutput>
extends TransformResult<T> {
getSource: () => Buffer;
}
export type TransformFn<T = MixedOutput> = (
modulePath: string,
requireContext: RequireContext | null,
) => Promise<TransformResultWithSource<T>>;
export interface AllowOptionalDependenciesWithOptions {
readonly exclude: string[];
}
export type AllowOptionalDependencies =
| boolean
| AllowOptionalDependenciesWithOptions;
export interface BundlerResolution {
readonly type: 'sourceFile';
readonly filePath: string;
}
export interface Options<T = MixedOutput> {
readonly resolve: (from: string, to: TransformResultDependency) => string;
readonly transform: TransformFn<T>;
readonly transformOptions: TransformInputOptions;
readonly onProgress:
| ((numProcessed: number, total: number) => unknown)
| null;
readonly lazy: boolean;
readonly unstable_allowRequireContext: boolean;
readonly shallow: boolean;
}
export interface DeltaResult<T = MixedOutput> {
readonly added: Map<string, Module<T>>;
readonly modified: Map<string, Module<T>>;
readonly deleted: Set<string>;
readonly reset: boolean;
}
export interface SerializerOptions<T = MixedOutput> {
readonly asyncRequireModulePath: string;
readonly createModuleId: (filePath: string) => number;
readonly dev: boolean;
readonly getRunModuleStatement: (
moduleId: number | string,
globalPrefix: string,
) => string;
readonly includeAsyncPaths: boolean;
readonly inlineSourceMap?: boolean;
readonly modulesOnly: boolean;
readonly processModuleFilter: (module: Module<T>) => boolean;
readonly projectRoot: string;
readonly runBeforeMainModule: ReadonlyArray<string>;
readonly runModule: boolean;
readonly serverRoot: string;
readonly shouldAddToIgnoreList: (module: Module<T>) => boolean;
readonly sourceMapUrl?: string;
readonly sourceUrl?: string;
}

6
node_modules/metro/src/DeltaBundler/types.js generated vendored Normal file
View File

@@ -0,0 +1,6 @@
"use strict";
var _CountingSet = _interopRequireDefault(require("../lib/CountingSet"));
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}

188
node_modules/metro/src/DeltaBundler/types.js.flow generated vendored Normal file
View File

@@ -0,0 +1,188 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/
import type {RequireContext} from '../lib/contextModule';
import type {RequireContextParams} from '../ModuleGraph/worker/collectDependencies';
import type {Graph} from './Graph';
import type {JsTransformOptions} from 'metro-transform-worker';
import CountingSet from '../lib/CountingSet';
export type MixedOutput = {
+data: mixed,
+type: string,
};
export type AsyncDependencyType = 'async' | 'maybeSync' | 'prefetch' | 'weak';
export type TransformResultDependency = $ReadOnly<{
/**
* The literal name provided to a require or import call. For example 'foo' in
* case of `require('foo')`.
*/
name: string,
/**
* Extra data returned by the dependency extractor.
*/
data: $ReadOnly<{
/**
* A locally unique key for this dependency within the current module.
*/
key: string,
/**
* If not null, this dependency is due to a dynamic `import()` or `__prefetchImport()` call.
*/
asyncType: AsyncDependencyType | null,
/**
* True if the dependency is declared with a static "import x from 'y'" or
* an import() call.
*/
isESMImport: boolean,
/**
* The dependency is enclosed in a try/catch block.
*/
isOptional?: boolean,
locs: $ReadOnlyArray<BabelSourceLocation>,
/** Context for requiring a collection of modules. */
contextParams?: RequireContextParams,
}>,
}>;
export type ResolvedDependency = $ReadOnly<{
absolutePath: string,
data: TransformResultDependency,
}>;
export type Dependency =
| ResolvedDependency
| $ReadOnly<{
data: TransformResultDependency,
}>;
export type Module<T = MixedOutput> = $ReadOnly<{
dependencies: Map<string, Dependency>,
inverseDependencies: CountingSet<string>,
output: $ReadOnlyArray<T>,
path: string,
getSource: () => Buffer,
unstable_transformResultKey?: ?string,
}>;
export type ModuleData<T = MixedOutput> = $ReadOnly<{
dependencies: $ReadOnlyMap<string, Dependency>,
resolvedContexts: $ReadOnlyMap<string, RequireContext>,
output: $ReadOnlyArray<T>,
getSource: () => Buffer,
unstable_transformResultKey?: ?string,
}>;
export type Dependencies<T = MixedOutput> = Map<string, Module<T>>;
export type ReadOnlyDependencies<T = MixedOutput> = $ReadOnlyMap<
string,
Module<T>,
>;
export type TransformInputOptions = Omit<
JsTransformOptions,
'inlinePlatform' | 'inlineRequires',
>;
export type GraphInputOptions = $ReadOnly<{
entryPoints: $ReadOnlySet<string>,
// Unused in core but useful for custom serializers / experimentalSerializerHook
transformOptions: TransformInputOptions,
}>;
export interface ReadOnlyGraph<T = MixedOutput> {
+entryPoints: $ReadOnlySet<string>;
// Unused in core but useful for custom serializers / experimentalSerializerHook
+transformOptions: $ReadOnly<TransformInputOptions>;
+dependencies: ReadOnlyDependencies<T>;
}
export type {Graph};
export type TransformResult<T = MixedOutput> = $ReadOnly<{
dependencies: $ReadOnlyArray<TransformResultDependency>,
output: $ReadOnlyArray<T>,
unstable_transformResultKey?: ?string,
}>;
export type TransformResultWithSource<T = MixedOutput> = $ReadOnly<{
...TransformResult<T>,
getSource: () => Buffer,
}>;
export type TransformFn<T = MixedOutput> = (
string,
?RequireContext,
) => Promise<TransformResultWithSource<T>>;
export type ResolveFn = (
from: string,
dependency: TransformResultDependency,
) => BundlerResolution;
export type AllowOptionalDependenciesWithOptions = {
+exclude: Array<string>,
};
export type AllowOptionalDependencies =
| boolean
| AllowOptionalDependenciesWithOptions;
export type BundlerResolution = $ReadOnly<{
type: 'sourceFile',
filePath: string,
}>;
export type Options<T = MixedOutput> = {
+resolve: ResolveFn,
+transform: TransformFn<T>,
+transformOptions: TransformInputOptions,
+onProgress: ?(numProcessed: number, total: number) => mixed,
+lazy: boolean,
+unstable_allowRequireContext: boolean,
+unstable_enablePackageExports: boolean,
+shallow: boolean,
};
export type DeltaResult<T = MixedOutput> = {
+added: Map<string, Module<T>>,
+modified: Map<string, Module<T>>,
+deleted: Set<string>,
+reset: boolean,
};
export type SerializerOptions = $ReadOnly<{
asyncRequireModulePath: string,
createModuleId: string => number,
dev: boolean,
getRunModuleStatement: (
moduleId: number | string,
globalPrefix: string,
) => string,
globalPrefix: string,
includeAsyncPaths: boolean,
inlineSourceMap: ?boolean,
modulesOnly: boolean,
processModuleFilter: (module: Module<>) => boolean,
projectRoot: string,
runBeforeMainModule: $ReadOnlyArray<string>,
runModule: boolean,
serverRoot: string,
shouldAddToIgnoreList: (Module<>) => boolean,
sourceMapUrl: ?string,
sourceUrl: ?string,
getSourceUrl: ?(Module<>) => string,
}>;

334
node_modules/metro/src/HmrServer.js generated vendored Normal file
View File

@@ -0,0 +1,334 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
var _hmrJSBundle = _interopRequireDefault(
require("./DeltaBundler/Serializers/hmrJSBundle"),
);
var _GraphNotFoundError = _interopRequireDefault(
require("./IncrementalBundler/GraphNotFoundError"),
);
var _RevisionNotFoundError = _interopRequireDefault(
require("./IncrementalBundler/RevisionNotFoundError"),
);
var _debounceAsyncQueue = _interopRequireDefault(
require("./lib/debounceAsyncQueue"),
);
var _formatBundlingError = _interopRequireDefault(
require("./lib/formatBundlingError"),
);
var _getGraphId = _interopRequireDefault(require("./lib/getGraphId"));
var _parseBundleOptionsFromBundleRequestUrl = _interopRequireDefault(
require("./lib/parseBundleOptionsFromBundleRequestUrl"),
);
var _splitBundleOptions = _interopRequireDefault(
require("./lib/splitBundleOptions"),
);
var transformHelpers = _interopRequireWildcard(
require("./lib/transformHelpers"),
);
var _metroCore = require("metro-core");
var _nullthrows = _interopRequireDefault(require("nullthrows"));
function _getRequireWildcardCache(e) {
if ("function" != typeof WeakMap) return null;
var r = new WeakMap(),
t = new WeakMap();
return (_getRequireWildcardCache = function (e) {
return e ? t : r;
})(e);
}
function _interopRequireWildcard(e, r) {
if (!r && e && e.__esModule) return e;
if (null === e || ("object" != typeof e && "function" != typeof e))
return { default: e };
var t = _getRequireWildcardCache(r);
if (t && t.has(e)) return t.get(e);
var n = { __proto__: null },
a = Object.defineProperty && Object.getOwnPropertyDescriptor;
for (var u in e)
if ("default" !== u && {}.hasOwnProperty.call(e, u)) {
var i = a ? Object.getOwnPropertyDescriptor(e, u) : null;
i && (i.get || i.set) ? Object.defineProperty(n, u, i) : (n[u] = e[u]);
}
return ((n.default = e), t && t.set(e, n), n);
}
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
const debug = require("debug")("Metro:HMR");
const { createActionStartEntry, createActionEndEntry, log } = _metroCore.Logger;
function send(sendFns, message) {
const strMessage = JSON.stringify(message);
sendFns.forEach((sendFn) => sendFn(strMessage));
}
class HmrServer {
constructor(bundler, createModuleId, config) {
this._config = config;
this._bundler = bundler;
this._createModuleId = createModuleId;
this._clientGroups = new Map();
}
onClientConnect = async (requestUrl, sendFn) => {
return {
sendFn,
revisionIds: [],
optedIntoHMR: false,
};
};
async _registerEntryPoint(client, requestUrl, sendFn) {
debug("Registering entry point: %s", requestUrl);
requestUrl = this._config.server.rewriteRequestUrl(requestUrl);
debug("Rewritten as: %s", requestUrl);
const { bundleType: _bundleType, ...options } = (0,
_parseBundleOptionsFromBundleRequestUrl.default)(
requestUrl,
new Set(this._config.resolver.platforms),
);
const { entryFile, resolverOptions, transformOptions, graphOptions } = (0,
_splitBundleOptions.default)(options);
const resolutionFn = await transformHelpers.getResolveDependencyFn(
this._bundler.getBundler(),
transformOptions.platform,
resolverOptions,
);
const resolvedEntryFilePath = resolutionFn(
(this._config.server.unstable_serverRoot ?? this._config.projectRoot) +
"/.",
{
name: entryFile,
data: {
key: entryFile,
asyncType: null,
isESMImport: false,
locs: [],
},
},
).filePath;
const graphId = (0, _getGraphId.default)(
resolvedEntryFilePath,
transformOptions,
{
resolverOptions,
shallow: graphOptions.shallow,
lazy: graphOptions.lazy,
unstable_allowRequireContext:
this._config.transformer.unstable_allowRequireContext,
},
);
const revPromise = this._bundler.getRevisionByGraphId(graphId);
if (!revPromise) {
send([sendFn], {
type: "error",
body: (0, _formatBundlingError.default)(
new _GraphNotFoundError.default(graphId),
),
});
return;
}
const { graph, id } = await revPromise;
client.revisionIds.push(id);
let clientGroup = this._clientGroups.get(id);
if (clientGroup != null) {
clientGroup.clients.add(client);
} else {
const clientUrl = new URL(requestUrl);
clientUrl.protocol = "http";
const clientQuery = clientUrl.searchParams;
clientQuery.delete("bundleEntry");
clientQuery.set("dev", clientQuery.get("dev") || "true");
clientQuery.set("minify", clientQuery.get("minify") || "false");
clientQuery.set("modulesOnly", "true");
clientQuery.set("runModule", clientQuery.get("runModule") || "false");
clientQuery.set("shallow", "true");
clientGroup = {
clients: new Set([client]),
clientUrl: new URL(clientUrl),
revisionId: id,
graphOptions,
unlisten: () => unlisten(),
};
this._clientGroups.set(id, clientGroup);
let latestEventArgs = [];
const debounceCallHandleFileChange = (0, _debounceAsyncQueue.default)(
async () => {
await this._handleFileChange(
(0, _nullthrows.default)(clientGroup),
{
isInitialUpdate: false,
},
...latestEventArgs,
);
},
50,
);
const unlisten = this._bundler
.getDeltaBundler()
.listen(graph, async (...args) => {
latestEventArgs = args;
await debounceCallHandleFileChange();
});
}
await this._handleFileChange(clientGroup, {
isInitialUpdate: true,
});
send([sendFn], {
type: "bundle-registered",
});
}
onClientMessage = async (client, message, sendFn) => {
let data;
try {
data = JSON.parse(String(message));
} catch (error) {
send([sendFn], {
type: "error",
body: (0, _formatBundlingError.default)(error),
});
return Promise.resolve();
}
if (data && data.type) {
switch (data.type) {
case "register-entrypoints":
return Promise.all(
data.entryPoints.map((entryPoint) =>
this._registerEntryPoint(client, entryPoint, sendFn),
),
);
case "log":
if (this._config.server.forwardClientLogs) {
this._config.reporter.update({
type: "client_log",
level: data.level,
data: data.data,
mode: data.mode,
});
}
break;
case "log-opt-in":
client.optedIntoHMR = true;
break;
default:
break;
}
}
return Promise.resolve();
};
onClientError = (client, e) => {
this._config.reporter.update({
type: "hmr_client_error",
error: e.error,
});
this.onClientDisconnect(client);
};
onClientDisconnect = (client) => {
client.revisionIds.forEach((revisionId) => {
const group = this._clientGroups.get(revisionId);
if (group != null) {
if (group.clients.size === 1) {
this._clientGroups.delete(revisionId);
group.unlisten();
} else {
group.clients.delete(client);
}
}
});
};
async _handleFileChange(group, options, changeEvent) {
const logger = !options.isInitialUpdate ? changeEvent?.logger : null;
if (logger) {
logger.point("fileChange_end");
logger.point("hmrPrepareAndSendMessage_start");
}
const optedIntoHMR = [...group.clients].some(
(client) => client.optedIntoHMR,
);
const processingHmrChange = log(
createActionStartEntry({
action_name: optedIntoHMR
? "Processing HMR change"
: "Processing HMR change (no client opt-in)",
}),
);
const sendFns = [...group.clients].map((client) => client.sendFn);
send(sendFns, {
type: "update-start",
body: options,
});
const message = await this._prepareMessage(group, options, changeEvent);
send(sendFns, message);
send(sendFns, {
type: "update-done",
});
log({
...createActionEndEntry(processingHmrChange),
outdated_modules:
message.type === "update"
? message.body.added.length + message.body.modified.length
: undefined,
});
if (logger) {
logger.point("hmrPrepareAndSendMessage_end");
logger.end("SUCCESS");
}
}
async _prepareMessage(group, options, changeEvent) {
const logger = !options.isInitialUpdate ? changeEvent?.logger : null;
try {
const revPromise = this._bundler.getRevision(group.revisionId);
if (!revPromise) {
return {
type: "error",
body: (0, _formatBundlingError.default)(
new _RevisionNotFoundError.default(group.revisionId),
),
};
}
logger?.point("updateGraph_start");
const { revision, delta } = await this._bundler.updateGraph(
await revPromise,
false,
);
logger?.point("updateGraph_end");
this._clientGroups.delete(group.revisionId);
group.revisionId = revision.id;
for (const client of group.clients) {
client.revisionIds = client.revisionIds.filter(
(revisionId) => revisionId !== group.revisionId,
);
client.revisionIds.push(revision.id);
}
this._clientGroups.set(group.revisionId, group);
logger?.point("serialize_start");
const hmrUpdate = (0, _hmrJSBundle.default)(delta, revision.graph, {
clientUrl: new URL(group.clientUrl),
createModuleId: this._createModuleId,
includeAsyncPaths: group.graphOptions.lazy,
projectRoot: this._config.projectRoot,
serverRoot:
this._config.server.unstable_serverRoot ?? this._config.projectRoot,
});
logger?.point("serialize_end");
return {
type: "update",
body: {
revisionId: revision.id,
isInitialUpdate: options.isInitialUpdate,
...hmrUpdate,
},
};
} catch (error) {
const formattedError = (0, _formatBundlingError.default)(error);
this._config.reporter.update({
type: "bundling_error",
error,
});
return {
type: "error",
body: formattedError,
};
}
}
}
exports.default = HmrServer;

389
node_modules/metro/src/HmrServer.js.flow generated vendored Normal file
View File

@@ -0,0 +1,389 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow
*/
import type IncrementalBundler, {RevisionId} from './IncrementalBundler';
import type {GraphOptions} from './shared/types';
import type {ConfigT, RootPerfLogger} from 'metro-config';
import type {
HmrClientMessage,
HmrErrorMessage,
HmrMessage,
HmrUpdateMessage,
} from 'metro-runtime/src/modules/types';
import hmrJSBundle from './DeltaBundler/Serializers/hmrJSBundle';
import GraphNotFoundError from './IncrementalBundler/GraphNotFoundError';
import RevisionNotFoundError from './IncrementalBundler/RevisionNotFoundError';
import debounceAsyncQueue from './lib/debounceAsyncQueue';
import formatBundlingError from './lib/formatBundlingError';
import getGraphId from './lib/getGraphId';
import parseBundleOptionsFromBundleRequestUrl from './lib/parseBundleOptionsFromBundleRequestUrl';
import splitBundleOptions from './lib/splitBundleOptions';
import * as transformHelpers from './lib/transformHelpers';
import {Logger} from 'metro-core';
import nullthrows from 'nullthrows';
// eslint-disable-next-line import/no-commonjs
const debug = require('debug')('Metro:HMR');
const {createActionStartEntry, createActionEndEntry, log} = Logger;
export type Client = {
optedIntoHMR: boolean,
revisionIds: Array<RevisionId>,
+sendFn: string => void,
};
type ClientGroup = {
+clients: Set<Client>,
clientUrl: URL,
revisionId: RevisionId,
+unlisten: () => void,
+graphOptions: GraphOptions,
};
function send(sendFns: Array<(string) => void>, message: HmrMessage): void {
const strMessage = JSON.stringify(message);
sendFns.forEach((sendFn: string => void) => sendFn(strMessage));
}
/**
* The HmrServer (Hot Module Reloading) implements a lightweight interface
* to communicate easily to the logic in the React Native repository (which
* is the one that handles the Web Socket connections).
*
* This interface allows the HmrServer to hook its own logic to WS clients
* getting connected, disconnected or having errors (through the
* `onClientConnect`, `onClientDisconnect` and `onClientError` methods).
*/
export default class HmrServer<TClient: Client> {
_config: ConfigT;
_bundler: IncrementalBundler;
_createModuleId: (path: string) => number;
_clientGroups: Map<RevisionId, ClientGroup>;
constructor(
bundler: IncrementalBundler,
createModuleId: (path: string) => number,
config: ConfigT,
) {
this._config = config;
this._bundler = bundler;
this._createModuleId = createModuleId;
this._clientGroups = new Map();
}
onClientConnect: (
requestUrl: string,
sendFn: (data: string) => void,
) => Promise<Client> = async (requestUrl, sendFn) => {
return {
sendFn,
revisionIds: [],
optedIntoHMR: false,
};
};
async _registerEntryPoint(
client: Client,
requestUrl: string,
sendFn: (data: string) => void,
): Promise<void> {
debug('Registering entry point: %s', requestUrl);
requestUrl = this._config.server.rewriteRequestUrl(requestUrl);
debug('Rewritten as: %s', requestUrl);
const {bundleType: _bundleType, ...options} =
parseBundleOptionsFromBundleRequestUrl(
requestUrl,
new Set(this._config.resolver.platforms),
);
const {entryFile, resolverOptions, transformOptions, graphOptions} =
splitBundleOptions(options);
/**
* `entryFile` is relative to projectRoot, we need to use resolution function
* to find the appropriate file with supported extensions.
*/
const resolutionFn = await transformHelpers.getResolveDependencyFn(
this._bundler.getBundler(),
transformOptions.platform,
resolverOptions,
);
const resolvedEntryFilePath = resolutionFn(
(this._config.server.unstable_serverRoot ?? this._config.projectRoot) +
'/.',
{
name: entryFile,
data: {
key: entryFile,
asyncType: null,
isESMImport: false,
locs: [],
},
},
).filePath;
const graphId = getGraphId(resolvedEntryFilePath, transformOptions, {
resolverOptions,
shallow: graphOptions.shallow,
lazy: graphOptions.lazy,
unstable_allowRequireContext:
this._config.transformer.unstable_allowRequireContext,
});
const revPromise = this._bundler.getRevisionByGraphId(graphId);
if (!revPromise) {
send([sendFn], {
type: 'error',
body: formatBundlingError(new GraphNotFoundError(graphId)),
});
return;
}
const {graph, id} = await revPromise;
client.revisionIds.push(id);
let clientGroup: ?ClientGroup = this._clientGroups.get(id);
if (clientGroup != null) {
clientGroup.clients.add(client);
} else {
const clientUrl = new URL(requestUrl);
// Prepare the clientUrl to be used as sourceUrl in HMR updates.
clientUrl.protocol = 'http';
const clientQuery = clientUrl.searchParams;
clientQuery.delete('bundleEntry');
clientQuery.set('dev', clientQuery.get('dev') || 'true');
clientQuery.set('minify', clientQuery.get('minify') || 'false');
clientQuery.set('modulesOnly', 'true');
clientQuery.set('runModule', clientQuery.get('runModule') || 'false');
clientQuery.set('shallow', 'true');
clientGroup = {
clients: new Set([client]),
clientUrl: new URL(clientUrl),
revisionId: id,
graphOptions,
unlisten: (): void => unlisten(),
};
this._clientGroups.set(id, clientGroup);
let latestEventArgs: Array<any> = [];
const debounceCallHandleFileChange = debounceAsyncQueue(async () => {
await this._handleFileChange(
nullthrows(clientGroup),
{isInitialUpdate: false},
...latestEventArgs,
);
}, 50);
const unlisten = this._bundler
.getDeltaBundler()
// $FlowFixMe[missing-local-annot]
.listen(graph, async (...args) => {
latestEventArgs = args;
await debounceCallHandleFileChange();
});
}
await this._handleFileChange(clientGroup, {isInitialUpdate: true});
send([sendFn], {type: 'bundle-registered'});
}
onClientMessage: (
client: TClient,
message: string | Buffer | ArrayBuffer | Array<Buffer>,
sendFn: (data: string) => void,
) => Promise<void> = async (client, message, sendFn) => {
let data: HmrClientMessage;
try {
data = JSON.parse(String(message));
} catch (error) {
send([sendFn], {
type: 'error',
body: formatBundlingError(error),
});
return Promise.resolve();
}
if (data && data.type) {
switch (data.type) {
case 'register-entrypoints':
return Promise.all(
data.entryPoints.map(entryPoint =>
this._registerEntryPoint(client, entryPoint, sendFn),
),
);
case 'log':
if (this._config.server.forwardClientLogs) {
this._config.reporter.update({
type: 'client_log',
level: data.level,
data: data.data,
mode: data.mode,
});
}
break;
case 'log-opt-in':
client.optedIntoHMR = true;
break;
default:
break;
}
}
return Promise.resolve();
};
onClientError: (client: TClient, e: ErrorEvent) => void = (client, e) => {
this._config.reporter.update({
type: 'hmr_client_error',
error: e.error,
});
this.onClientDisconnect(client);
};
onClientDisconnect: (client: TClient) => void = client => {
client.revisionIds.forEach(revisionId => {
const group = this._clientGroups.get(revisionId);
if (group != null) {
if (group.clients.size === 1) {
this._clientGroups.delete(revisionId);
group.unlisten();
} else {
group.clients.delete(client);
}
}
});
};
async _handleFileChange(
group: ClientGroup,
options: {isInitialUpdate: boolean},
changeEvent: ?{
logger: ?RootPerfLogger,
},
): Promise<void> {
const logger = !options.isInitialUpdate ? changeEvent?.logger : null;
if (logger) {
logger.point('fileChange_end');
logger.point('hmrPrepareAndSendMessage_start');
}
const optedIntoHMR = [...group.clients].some(
(client: Client) => client.optedIntoHMR,
);
const processingHmrChange = log(
createActionStartEntry({
// Even when HMR is disabled on the client, this function still
// runs so we can stash updates while it's off and apply them later.
// However, this would mess up our internal analytics because we track
// HMR as being used even for people who have it disabled.
// As a workaround, we use a different event name for clients
// that didn't explicitly opt into HMR.
action_name: optedIntoHMR
? 'Processing HMR change'
: 'Processing HMR change (no client opt-in)',
}),
);
const sendFns = [...group.clients].map((client: Client) => client.sendFn);
send(sendFns, {
type: 'update-start',
body: options,
});
const message = await this._prepareMessage(group, options, changeEvent);
send(sendFns, message);
send(sendFns, {type: 'update-done'});
log({
...createActionEndEntry(processingHmrChange),
outdated_modules:
message.type === 'update'
? message.body.added.length + message.body.modified.length
: undefined,
});
if (logger) {
logger.point('hmrPrepareAndSendMessage_end');
logger.end('SUCCESS');
}
}
async _prepareMessage(
group: ClientGroup,
options: {isInitialUpdate: boolean},
changeEvent: ?{
logger: ?RootPerfLogger,
},
): Promise<HmrUpdateMessage | HmrErrorMessage> {
const logger = !options.isInitialUpdate ? changeEvent?.logger : null;
try {
const revPromise = this._bundler.getRevision(group.revisionId);
if (!revPromise) {
return {
type: 'error',
body: formatBundlingError(
new RevisionNotFoundError(group.revisionId),
),
};
}
logger?.point('updateGraph_start');
const {revision, delta} = await this._bundler.updateGraph(
await revPromise,
false,
);
logger?.point('updateGraph_end');
this._clientGroups.delete(group.revisionId);
group.revisionId = revision.id;
for (const client of group.clients) {
client.revisionIds = client.revisionIds.filter(
revisionId => revisionId !== group.revisionId,
);
client.revisionIds.push(revision.id);
}
this._clientGroups.set(group.revisionId, group);
logger?.point('serialize_start');
const hmrUpdate = hmrJSBundle(delta, revision.graph, {
clientUrl: new URL(group.clientUrl),
createModuleId: this._createModuleId,
includeAsyncPaths: group.graphOptions.lazy,
projectRoot: this._config.projectRoot,
serverRoot:
this._config.server.unstable_serverRoot ?? this._config.projectRoot,
});
logger?.point('serialize_end');
return {
type: 'update',
body: {
revisionId: revision.id,
isInitialUpdate: options.isInitialUpdate,
...hmrUpdate,
},
};
} catch (error) {
const formattedError = formatBundlingError(error);
this._config.reporter.update({type: 'bundling_error', error});
return {type: 'error', body: formattedError};
}
}
}

98
node_modules/metro/src/IncrementalBundler.d.ts generated vendored Normal file
View File

@@ -0,0 +1,98 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @oncall react_native
*/
import type Bundler from './Bundler';
import type {
Options as DeltaBundlerOptions,
ReadOnlyDependencies,
TransformInputOptions,
} from './DeltaBundler/types';
import type {GraphId} from './lib/getGraphId';
import type {ConfigT} from 'metro-config';
import DeltaBundler, {DeltaResult, Graph, Module} from './DeltaBundler';
import {ResolverInputOptions} from './shared/types';
export type RevisionId = string;
export type OutputGraph = Graph<void>;
export interface OtherOptions {
readonly onProgress: DeltaBundlerOptions<void>['onProgress'];
readonly shallow: boolean;
}
export interface GraphRevision {
readonly id: RevisionId;
readonly date: Date;
readonly graphId: GraphId;
readonly graph: OutputGraph;
readonly prepend: ReadonlyArray<Module<void>>;
}
export interface IncrementalBundlerOptions {
readonly hasReducedPerformance?: boolean;
readonly watch?: boolean;
}
export default class IncrementalBundler {
static revisionIdFromString: (str: string) => RevisionId;
constructor(config: ConfigT, options?: IncrementalBundlerOptions);
end(): void;
getBundler(): Bundler;
getDeltaBundler(): DeltaBundler<void>;
getRevision(revisionId: RevisionId): Promise<GraphRevision> | null;
getRevisionByGraphId(graphId: GraphId): Promise<GraphRevision> | null;
buildGraphForEntries(
entryFiles: ReadonlyArray<string>,
transformOptions: TransformInputOptions,
resolverOptions: ResolverInputOptions,
otherOptions?: OtherOptions,
): Promise<OutputGraph>;
getDependencies(
entryFiles: ReadonlyArray<string>,
transformOptions: TransformInputOptions,
resolverOptions: ResolverInputOptions,
otherOptions?: OtherOptions,
): Promise<ReadOnlyDependencies<void>>;
buildGraph(
entryFile: string,
transformOptions: TransformInputOptions,
resolverOptions: ResolverInputOptions,
otherOptions?: OtherOptions,
): Promise<
Readonly<{graph: OutputGraph; prepend: ReadonlyArray<Module<void>>}>
>;
initializeGraph(
entryFile: string,
transformOptions: TransformInputOptions,
resolverOptions: ResolverInputOptions,
otherOptions?: OtherOptions,
): Promise<{
delta: DeltaResult<void>;
revision: GraphRevision;
}>;
updateGraph(
revision: GraphRevision,
reset: boolean,
): Promise<{
delta: DeltaResult<void>;
revision: GraphRevision;
}>;
endGraph(graphId: GraphId): Promise<void>;
ready(): Promise<void>;
}

305
node_modules/metro/src/IncrementalBundler.js generated vendored Normal file
View File

@@ -0,0 +1,305 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
var _Bundler = _interopRequireDefault(require("./Bundler"));
var _DeltaBundler = _interopRequireDefault(require("./DeltaBundler"));
var _ResourceNotFoundError = _interopRequireDefault(
require("./IncrementalBundler/ResourceNotFoundError"),
);
var _getGraphId = _interopRequireDefault(require("./lib/getGraphId"));
var _getPrependedScripts = _interopRequireDefault(
require("./lib/getPrependedScripts"),
);
var transformHelpers = _interopRequireWildcard(
require("./lib/transformHelpers"),
);
var _crypto = _interopRequireDefault(require("crypto"));
var _fs = _interopRequireDefault(require("fs"));
var _path = _interopRequireDefault(require("path"));
function _getRequireWildcardCache(e) {
if ("function" != typeof WeakMap) return null;
var r = new WeakMap(),
t = new WeakMap();
return (_getRequireWildcardCache = function (e) {
return e ? t : r;
})(e);
}
function _interopRequireWildcard(e, r) {
if (!r && e && e.__esModule) return e;
if (null === e || ("object" != typeof e && "function" != typeof e))
return { default: e };
var t = _getRequireWildcardCache(r);
if (t && t.has(e)) return t.get(e);
var n = { __proto__: null },
a = Object.defineProperty && Object.getOwnPropertyDescriptor;
for (var u in e)
if ("default" !== u && {}.hasOwnProperty.call(e, u)) {
var i = a ? Object.getOwnPropertyDescriptor(e, u) : null;
i && (i.get || i.set) ? Object.defineProperty(n, u, i) : (n[u] = e[u]);
}
return ((n.default = e), t && t.set(e, n), n);
}
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
function createRevisionId() {
return _crypto.default.randomBytes(8).toString("hex");
}
function revisionIdFromString(str) {
return str;
}
class IncrementalBundler {
_revisionsById = new Map();
_revisionsByGraphId = new Map();
static revisionIdFromString = revisionIdFromString;
constructor(config, options) {
this._config = config;
this._bundler = new _Bundler.default(config, options);
this._deltaBundler = new _DeltaBundler.default(this._bundler.getWatcher());
}
async end() {
this._deltaBundler.end();
await this._bundler.end();
}
getBundler() {
return this._bundler;
}
getDeltaBundler() {
return this._deltaBundler;
}
getRevision(revisionId) {
return this._revisionsById.get(revisionId);
}
getRevisionByGraphId(graphId) {
return this._revisionsByGraphId.get(graphId);
}
async buildGraphForEntries(
entryFiles,
transformOptions,
resolverOptions,
otherOptions = {
onProgress: null,
shallow: false,
lazy: false,
},
) {
const absoluteEntryFiles = await this._getAbsoluteEntryFiles(entryFiles);
const graph = await this._deltaBundler.buildGraph(absoluteEntryFiles, {
resolve: await transformHelpers.getResolveDependencyFn(
this._bundler,
transformOptions.platform,
resolverOptions,
),
transform: await transformHelpers.getTransformFn(
absoluteEntryFiles,
this._bundler,
this._deltaBundler,
this._config,
transformOptions,
resolverOptions,
),
transformOptions,
onProgress: otherOptions.onProgress,
lazy: otherOptions.lazy,
unstable_allowRequireContext:
this._config.transformer.unstable_allowRequireContext,
unstable_enablePackageExports:
this._config.resolver.unstable_enablePackageExports,
shallow: otherOptions.shallow,
});
this._config.serializer.experimentalSerializerHook(graph, {
added: graph.dependencies,
modified: new Map(),
deleted: new Set(),
reset: true,
});
return graph;
}
async getDependencies(
entryFiles,
transformOptions,
resolverOptions,
otherOptions = {
onProgress: null,
shallow: false,
lazy: false,
},
) {
const absoluteEntryFiles = await this._getAbsoluteEntryFiles(entryFiles);
const dependencies = await this._deltaBundler.getDependencies(
absoluteEntryFiles,
{
resolve: await transformHelpers.getResolveDependencyFn(
this._bundler,
transformOptions.platform,
resolverOptions,
),
transform: await transformHelpers.getTransformFn(
absoluteEntryFiles,
this._bundler,
this._deltaBundler,
this._config,
transformOptions,
resolverOptions,
),
transformOptions,
onProgress: otherOptions.onProgress,
lazy: otherOptions.lazy,
unstable_allowRequireContext:
this._config.transformer.unstable_allowRequireContext,
unstable_enablePackageExports:
this._config.resolver.unstable_enablePackageExports,
shallow: otherOptions.shallow,
},
);
return dependencies;
}
async buildGraph(
entryFile,
transformOptions,
resolverOptions,
otherOptions = {
onProgress: null,
shallow: false,
lazy: false,
},
) {
const graph = await this.buildGraphForEntries(
[entryFile],
transformOptions,
resolverOptions,
otherOptions,
);
const { type: _, ...transformOptionsWithoutType } = transformOptions;
const prepend = await (0, _getPrependedScripts.default)(
this._config,
transformOptionsWithoutType,
resolverOptions,
this._bundler,
this._deltaBundler,
);
return {
prepend,
graph,
};
}
async initializeGraph(
entryFile,
transformOptions,
resolverOptions,
otherOptions = {
onProgress: null,
shallow: false,
lazy: false,
},
) {
const graphId = (0, _getGraphId.default)(entryFile, transformOptions, {
resolverOptions,
shallow: otherOptions.shallow,
lazy: otherOptions.lazy,
unstable_allowRequireContext:
this._config.transformer.unstable_allowRequireContext,
});
const revisionId = createRevisionId();
const revisionPromise = (async () => {
const { graph, prepend } = await this.buildGraph(
entryFile,
transformOptions,
resolverOptions,
otherOptions,
);
return {
id: revisionId,
date: new Date(),
graphId,
graph,
prepend,
};
})();
this._revisionsById.set(revisionId, revisionPromise);
this._revisionsByGraphId.set(graphId, revisionPromise);
try {
const revision = await revisionPromise;
const delta = {
added: revision.graph.dependencies,
modified: new Map(),
deleted: new Set(),
reset: true,
};
return {
revision,
delta,
};
} catch (err) {
this._revisionsById.delete(revisionId);
this._revisionsByGraphId.delete(graphId);
throw err;
}
}
async updateGraph(revision, reset) {
const delta = await this._deltaBundler.getDelta(revision.graph, {
reset,
shallow: false,
});
this._config.serializer.experimentalSerializerHook(revision.graph, delta);
if (
delta.added.size > 0 ||
delta.modified.size > 0 ||
delta.deleted.size > 0
) {
this._revisionsById.delete(revision.id);
revision = {
...revision,
id: _crypto.default.randomBytes(8).toString("hex"),
date: new Date(),
};
const revisionPromise = Promise.resolve(revision);
this._revisionsById.set(revision.id, revisionPromise);
this._revisionsByGraphId.set(revision.graphId, revisionPromise);
}
return {
revision,
delta,
};
}
async endGraph(graphId) {
const revPromise = this._revisionsByGraphId.get(graphId);
if (!revPromise) {
return;
}
const revision = await revPromise;
this._deltaBundler.endGraph(revision.graph);
this._revisionsByGraphId.delete(graphId);
this._revisionsById.delete(revision.id);
}
async _getAbsoluteEntryFiles(entryFiles) {
const absoluteEntryFiles = entryFiles.map((entryFile) =>
_path.default.resolve(
this._config.server.unstable_serverRoot ?? this._config.projectRoot,
entryFile,
),
);
await Promise.all(
absoluteEntryFiles.map(
(entryFile) =>
new Promise((resolve, reject) => {
_fs.default.realpath(entryFile, (err) => {
if (err) {
reject(new _ResourceNotFoundError.default(entryFile));
} else {
resolve();
}
});
}),
),
);
return absoluteEntryFiles;
}
async ready() {
await this._bundler.ready();
}
}
exports.default = IncrementalBundler;

366
node_modules/metro/src/IncrementalBundler.js.flow generated vendored Normal file
View File

@@ -0,0 +1,366 @@
/**
* Copyright (c) Meta Platforms, Inc. and 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
* @oncall react_native
*/
import type {DeltaResult, Graph, MixedOutput, Module} from './DeltaBundler';
import type {
Options as DeltaBundlerOptions,
ReadOnlyDependencies,
TransformInputOptions,
} from './DeltaBundler/types';
import type {GraphId} from './lib/getGraphId';
import type {ResolverInputOptions} from './shared/types';
import type {ConfigT} from 'metro-config';
import Bundler from './Bundler';
import DeltaBundler from './DeltaBundler';
import ResourceNotFoundError from './IncrementalBundler/ResourceNotFoundError';
import getGraphId from './lib/getGraphId';
import getPrependedScripts from './lib/getPrependedScripts';
import * as transformHelpers from './lib/transformHelpers';
import crypto from 'crypto';
import fs from 'fs';
import path from 'path';
export opaque type RevisionId: string = string;
export type OutputGraph = Graph<>;
type OtherOptions = $ReadOnly<{
onProgress: DeltaBundlerOptions<>['onProgress'],
shallow: boolean,
lazy: boolean,
}>;
export type GraphRevision = {
// Identifies the last computed revision.
+id: RevisionId,
+date: Date,
+graphId: GraphId,
+graph: OutputGraph,
+prepend: $ReadOnlyArray<Module<>>,
};
export type IncrementalBundlerOptions = $ReadOnly<{
hasReducedPerformance?: boolean,
watch?: boolean,
}>;
function createRevisionId(): RevisionId {
return crypto.randomBytes(8).toString('hex');
}
function revisionIdFromString(str: string): RevisionId {
return str;
}
export default class IncrementalBundler {
_config: ConfigT;
_bundler: Bundler;
_deltaBundler: DeltaBundler<>;
_revisionsById: Map<RevisionId, Promise<GraphRevision>> = new Map();
_revisionsByGraphId: Map<GraphId, Promise<GraphRevision>> = new Map();
static revisionIdFromString: (str: string) => RevisionId =
revisionIdFromString;
constructor(config: ConfigT, options?: IncrementalBundlerOptions) {
this._config = config;
this._bundler = new Bundler(config, options);
this._deltaBundler = new DeltaBundler(this._bundler.getWatcher());
}
async end(): Promise<void> {
this._deltaBundler.end();
await this._bundler.end();
}
getBundler(): Bundler {
return this._bundler;
}
getDeltaBundler(): DeltaBundler<> {
return this._deltaBundler;
}
getRevision(revisionId: RevisionId): ?Promise<GraphRevision> {
return this._revisionsById.get(revisionId);
}
getRevisionByGraphId(graphId: GraphId): ?Promise<GraphRevision> {
return this._revisionsByGraphId.get(graphId);
}
async buildGraphForEntries(
entryFiles: $ReadOnlyArray<string>,
transformOptions: TransformInputOptions,
resolverOptions: ResolverInputOptions,
otherOptions?: OtherOptions = {
onProgress: null,
shallow: false,
lazy: false,
},
): Promise<OutputGraph> {
const absoluteEntryFiles = await this._getAbsoluteEntryFiles(entryFiles);
const graph = await this._deltaBundler.buildGraph(absoluteEntryFiles, {
resolve: await transformHelpers.getResolveDependencyFn(
this._bundler,
transformOptions.platform,
resolverOptions,
),
transform: await transformHelpers.getTransformFn(
absoluteEntryFiles,
this._bundler,
this._deltaBundler,
this._config,
transformOptions,
resolverOptions,
),
transformOptions,
onProgress: otherOptions.onProgress,
lazy: otherOptions.lazy,
unstable_allowRequireContext:
this._config.transformer.unstable_allowRequireContext,
unstable_enablePackageExports:
this._config.resolver.unstable_enablePackageExports,
shallow: otherOptions.shallow,
});
this._config.serializer.experimentalSerializerHook(graph, {
added: graph.dependencies,
modified: new Map(),
deleted: new Set(),
reset: true,
});
return graph;
}
async getDependencies(
entryFiles: $ReadOnlyArray<string>,
transformOptions: TransformInputOptions,
resolverOptions: ResolverInputOptions,
otherOptions?: OtherOptions = {
onProgress: null,
shallow: false,
lazy: false,
},
): Promise<ReadOnlyDependencies<>> {
const absoluteEntryFiles = await this._getAbsoluteEntryFiles(entryFiles);
const dependencies = await this._deltaBundler.getDependencies(
absoluteEntryFiles,
{
resolve: await transformHelpers.getResolveDependencyFn(
this._bundler,
transformOptions.platform,
resolverOptions,
),
transform: await transformHelpers.getTransformFn(
absoluteEntryFiles,
this._bundler,
this._deltaBundler,
this._config,
transformOptions,
resolverOptions,
),
transformOptions,
onProgress: otherOptions.onProgress,
lazy: otherOptions.lazy,
unstable_allowRequireContext:
this._config.transformer.unstable_allowRequireContext,
unstable_enablePackageExports:
this._config.resolver.unstable_enablePackageExports,
shallow: otherOptions.shallow,
},
);
return dependencies;
}
async buildGraph(
entryFile: string,
transformOptions: TransformInputOptions,
resolverOptions: ResolverInputOptions,
otherOptions?: OtherOptions = {
onProgress: null,
shallow: false,
lazy: false,
},
): Promise<{+graph: OutputGraph, +prepend: $ReadOnlyArray<Module<>>}> {
const graph = await this.buildGraphForEntries(
[entryFile],
transformOptions,
resolverOptions,
otherOptions,
);
const {type: _, ...transformOptionsWithoutType} = transformOptions;
const prepend = await getPrependedScripts(
this._config,
transformOptionsWithoutType,
resolverOptions,
this._bundler,
this._deltaBundler,
);
return {
prepend,
graph,
};
}
// TODO T34760750 (alexkirsz) Eventually, I'd like to get to a point where
// this class exposes only initializeGraph and updateGraph.
async initializeGraph(
entryFile: string,
transformOptions: TransformInputOptions,
resolverOptions: ResolverInputOptions,
otherOptions?: OtherOptions = {
onProgress: null,
shallow: false,
lazy: false,
},
): Promise<{
delta: DeltaResult<>,
revision: GraphRevision,
...
}> {
const graphId = getGraphId(entryFile, transformOptions, {
resolverOptions,
shallow: otherOptions.shallow,
lazy: otherOptions.lazy,
unstable_allowRequireContext:
this._config.transformer.unstable_allowRequireContext,
});
const revisionId = createRevisionId();
const revisionPromise = (async () => {
const {graph, prepend} = await this.buildGraph(
entryFile,
transformOptions,
resolverOptions,
otherOptions,
);
return {
id: revisionId,
date: new Date(),
graphId,
graph,
prepend,
};
})();
this._revisionsById.set(revisionId, revisionPromise);
this._revisionsByGraphId.set(graphId, revisionPromise);
try {
const revision = await revisionPromise;
const delta = {
added: revision.graph.dependencies,
modified: new Map<string, Module<MixedOutput>>(),
deleted: new Set<string>(),
reset: true,
};
return {
revision,
delta,
};
} catch (err) {
// Evict a bad revision from the cache since otherwise
// we'll keep getting it even after the build is fixed.
this._revisionsById.delete(revisionId);
this._revisionsByGraphId.delete(graphId);
throw err;
}
}
async updateGraph(
revision: GraphRevision,
reset: boolean,
): Promise<{
delta: DeltaResult<>,
revision: GraphRevision,
...
}> {
const delta = await this._deltaBundler.getDelta(revision.graph, {
reset,
shallow: false,
});
this._config.serializer.experimentalSerializerHook(revision.graph, delta);
if (
delta.added.size > 0 ||
delta.modified.size > 0 ||
delta.deleted.size > 0
) {
this._revisionsById.delete(revision.id);
revision = {
...revision,
// Generate a new revision id, to be used to verify the next incremental
// request.
id: crypto.randomBytes(8).toString('hex'),
date: new Date(),
};
const revisionPromise = Promise.resolve(revision);
this._revisionsById.set(revision.id, revisionPromise);
this._revisionsByGraphId.set(revision.graphId, revisionPromise);
}
return {revision, delta};
}
async endGraph(graphId: GraphId): Promise<void> {
const revPromise = this._revisionsByGraphId.get(graphId);
if (!revPromise) {
return;
}
const revision = await revPromise;
this._deltaBundler.endGraph(revision.graph);
this._revisionsByGraphId.delete(graphId);
this._revisionsById.delete(revision.id);
}
async _getAbsoluteEntryFiles(
entryFiles: $ReadOnlyArray<string>,
): Promise<$ReadOnlyArray<string>> {
const absoluteEntryFiles = entryFiles.map((entryFile: string) =>
path.resolve(
this._config.server.unstable_serverRoot ?? this._config.projectRoot,
entryFile,
),
);
await Promise.all(
absoluteEntryFiles.map(
(entryFile: string) =>
new Promise((resolve: void => void, reject: mixed => mixed) => {
// This should throw an error if the file doesn't exist.
// Using this instead of fs.exists to account for SimLinks.
fs.realpath(entryFile, err => {
if (err) {
reject(new ResourceNotFoundError(entryFile));
} else {
resolve();
}
});
}),
),
);
return absoluteEntryFiles;
}
// Wait for the bundler to become ready.
async ready(): Promise<void> {
await this._bundler.ready();
}
}

View File

@@ -0,0 +1,13 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
class GraphNotFoundError extends Error {
constructor(graphId) {
super(`The graph \`${graphId}\` was not found.`);
this.graphId = graphId;
}
}
exports.default = GraphNotFoundError;

View File

@@ -0,0 +1,21 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/
import type {GraphId} from '../lib/getGraphId';
export default class GraphNotFoundError extends Error {
graphId: GraphId;
constructor(graphId: GraphId) {
super(`The graph \`${graphId}\` was not found.`);
this.graphId = graphId;
}
}

View File

@@ -0,0 +1,13 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
class ResourceNotFoundError extends Error {
constructor(resourcePath) {
super(`The resource \`${resourcePath}\` was not found.`);
this.resourcePath = resourcePath;
}
}
exports.default = ResourceNotFoundError;

View File

@@ -0,0 +1,19 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/
export default class ResourceNotFoundError extends Error {
resourcePath: string;
constructor(resourcePath: string) {
super(`The resource \`${resourcePath}\` was not found.`);
this.resourcePath = resourcePath;
}
}

View File

@@ -0,0 +1,13 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
class RevisionNotFoundError extends Error {
constructor(revisionId) {
super(`The revision \`${revisionId}\` was not found.`);
this.revisionId = revisionId;
}
}
exports.default = RevisionNotFoundError;

View File

@@ -0,0 +1,21 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/
import type {RevisionId} from '../IncrementalBundler';
export default class RevisionNotFoundError extends Error {
revisionId: RevisionId;
constructor(revisionId: RevisionId) {
super(`The revision \`${revisionId}\` was not found.`);
this.revisionId = revisionId;
}
}

75
node_modules/metro/src/ModuleGraph/test-helpers.js generated vendored Normal file
View File

@@ -0,0 +1,75 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.comparableCode = exports.codeFromAst = void 0;
exports.toEqualComparableCode = toEqualComparableCode;
exports.toMatchCodeFrameSnapshot = toMatchCodeFrameSnapshot;
var _generator = _interopRequireDefault(require("@babel/generator"));
var _jestSnapshot = require("jest-snapshot");
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
const generateOptions = {
concise: true,
sourceType: "module",
};
const codeFromAst = (ast) => (0, _generator.default)(ast, generateOptions).code;
exports.codeFromAst = codeFromAst;
const comparableCode = (code) => code.trim().replace(/\s+/g, " ");
exports.comparableCode = comparableCode;
function toEqualComparableCode(received, expected) {
const comparableExpected = comparableCode(expected);
const pass = received === comparableExpected;
const options = {
isNot: this.isNot,
promise: this.promise,
};
const message = pass
? () =>
this.utils.matcherHint(
"toEqualComparableCode",
undefined,
undefined,
options,
) +
"\n\n" +
`Expected: not ${this.utils.printExpected(comparableExpected)}\n` +
`Received: ${this.utils.printReceived(received)}`
: () => {
const diffString = this.utils.printDiffOrStringify(
comparableExpected,
received,
"expected",
"received",
this.expand,
);
return (
this.utils.matcherHint(
"toEqualComparableCode",
undefined,
undefined,
options,
) +
"\n\n" +
diffString
);
};
return {
actual: received,
message,
pass,
};
}
const ANSI_PATTERN = /\x1b\[[0-9;]*m/g;
function trimANSICodes(input) {
return input.replace(ANSI_PATTERN, "");
}
function toMatchCodeFrameSnapshot(received) {
return _jestSnapshot.toMatchSnapshot.call(
this,
trimANSICodes(received),
"toMatchCodeFrameSnapshot",
);
}

View File

@@ -0,0 +1,144 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.WRAP_NAME = void 0;
exports.jsonToCommonJS = jsonToCommonJS;
exports.wrapJson = wrapJson;
exports.wrapModule = wrapModule;
exports.wrapPolyfill = wrapPolyfill;
var _template = _interopRequireDefault(require("@babel/template"));
var _traverse = _interopRequireDefault(require("@babel/traverse"));
var t = _interopRequireWildcard(require("@babel/types"));
var _invariant = _interopRequireDefault(require("invariant"));
function _getRequireWildcardCache(e) {
if ("function" != typeof WeakMap) return null;
var r = new WeakMap(),
t = new WeakMap();
return (_getRequireWildcardCache = function (e) {
return e ? t : r;
})(e);
}
function _interopRequireWildcard(e, r) {
if (!r && e && e.__esModule) return e;
if (null === e || ("object" != typeof e && "function" != typeof e))
return { default: e };
var t = _getRequireWildcardCache(r);
if (t && t.has(e)) return t.get(e);
var n = { __proto__: null },
a = Object.defineProperty && Object.getOwnPropertyDescriptor;
for (var u in e)
if ("default" !== u && {}.hasOwnProperty.call(e, u)) {
var i = a ? Object.getOwnPropertyDescriptor(e, u) : null;
i && (i.get || i.set) ? Object.defineProperty(n, u, i) : (n[u] = e[u]);
}
return ((n.default = e), t && t.set(e, n), n);
}
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
const WRAP_NAME = (exports.WRAP_NAME = "$$_REQUIRE");
const IIFE_PARAM = _template.default.expression(
"typeof globalThis !== 'undefined' ? globalThis : typeof global !== 'undefined' ? global : typeof window !== 'undefined' ? window : this",
);
function wrapModule(
fileAst,
importDefaultName,
importAllName,
dependencyMapName,
globalPrefix,
skipRequireRename,
{ unstable_useStaticHermesModuleFactory = false } = {},
) {
const params = buildParameters(
importDefaultName,
importAllName,
dependencyMapName,
);
const factory = functionFromProgram(fileAst.program, params);
const def = t.callExpression(t.identifier(`${globalPrefix}__d`), [
unstable_useStaticHermesModuleFactory
? t.callExpression(
t.memberExpression(
t.identifier("$SHBuiltin"),
t.identifier("moduleFactory"),
),
[t.identifier("_$$_METRO_MODULE_ID"), factory],
)
: factory,
]);
const ast = t.file(t.program([t.expressionStatement(def)]));
const requireName = skipRequireRename ? "require" : renameRequires(ast);
return {
ast,
requireName,
};
}
function wrapPolyfill(fileAst) {
const factory = functionFromProgram(fileAst.program, ["global"]);
const iife = t.callExpression(factory, [IIFE_PARAM()]);
return t.file(t.program([t.expressionStatement(iife)]));
}
function jsonToCommonJS(source) {
return `module.exports = ${source};`;
}
function wrapJson(
source,
globalPrefix,
unstable_useStaticHermesModuleFactory = false,
) {
const moduleFactoryParameters = buildParameters(
"_importDefaultUnused",
"_importAllUnused",
"_dependencyMapUnused",
);
const factory = [
`function(${moduleFactoryParameters.join(", ")}) {`,
` ${jsonToCommonJS(source)}`,
"}",
].join("\n");
return (
`${globalPrefix}__d(` +
(unstable_useStaticHermesModuleFactory
? "$SHBuiltin.moduleFactory(_$$_METRO_MODULE_ID, " + factory + ")"
: factory) +
");"
);
}
function functionFromProgram(program, parameters) {
return t.functionExpression(
undefined,
parameters.map(makeIdentifier),
t.blockStatement(program.body, program.directives),
);
}
function makeIdentifier(name) {
return t.identifier(name);
}
function buildParameters(importDefaultName, importAllName, dependencyMapName) {
return [
"global",
"require",
importDefaultName,
importAllName,
"module",
"exports",
dependencyMapName,
];
}
function renameRequires(ast) {
let newRequireName = WRAP_NAME;
(0, _traverse.default)(ast, {
Program(path) {
const body = path.get("body.0.expression.arguments.0.body");
(0, _invariant.default)(
!Array.isArray(body),
"metro: Expected `body` to be a single path.",
);
newRequireName = body.scope.generateUid(WRAP_NAME);
body.scope.rename("require", newRequireName);
},
});
return newRequireName;
}

View File

@@ -0,0 +1,163 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow strict-local
*/
import type {FunctionExpression, Identifier, Program} from '@babel/types';
import template from '@babel/template';
import traverse from '@babel/traverse';
import * as t from '@babel/types';
import invariant from 'invariant';
const WRAP_NAME = '$$_REQUIRE'; // note: babel will prefix this with _
// Check first the `global` variable as the global object. This way serializers
// can create a local variable called global to fake it as a global object
// without having to pollute the window object on web.
const IIFE_PARAM = template.expression(
"typeof globalThis !== 'undefined' ? globalThis : typeof global !== 'undefined' ? global : typeof window !== 'undefined' ? window : this",
);
function wrapModule(
fileAst: BabelNodeFile,
importDefaultName: string,
importAllName: string,
dependencyMapName: string,
globalPrefix: string,
skipRequireRename: boolean,
{
unstable_useStaticHermesModuleFactory = false,
}: $ReadOnly<{unstable_useStaticHermesModuleFactory?: boolean}> = {},
): {
ast: BabelNodeFile,
requireName: string,
} {
const params = buildParameters(
importDefaultName,
importAllName,
dependencyMapName,
);
const factory = functionFromProgram(fileAst.program, params);
const def = t.callExpression(t.identifier(`${globalPrefix}__d`), [
unstable_useStaticHermesModuleFactory
? t.callExpression(
t.memberExpression(
t.identifier('$SHBuiltin'),
t.identifier('moduleFactory'),
),
[t.identifier('_$$_METRO_MODULE_ID'), factory],
)
: factory,
]);
const ast = t.file(t.program([t.expressionStatement(def)]));
// `require` doesn't need to be scoped when Metro serializes to iife because the local function
// `require` will be used instead of the global one.
const requireName = skipRequireRename ? 'require' : renameRequires(ast);
return {ast, requireName};
}
function wrapPolyfill(fileAst: BabelNodeFile): BabelNodeFile {
const factory = functionFromProgram(fileAst.program, ['global']);
const iife = t.callExpression(factory, [IIFE_PARAM()]);
return t.file(t.program([t.expressionStatement(iife)]));
}
function jsonToCommonJS(source: string): string {
return `module.exports = ${source};`;
}
function wrapJson(
source: string,
globalPrefix: string,
unstable_useStaticHermesModuleFactory?: boolean = false,
): string {
// Unused parameters; remember that's wrapping JSON.
const moduleFactoryParameters = buildParameters(
'_importDefaultUnused',
'_importAllUnused',
'_dependencyMapUnused',
);
const factory = [
`function(${moduleFactoryParameters.join(', ')}) {`,
` ${jsonToCommonJS(source)}`,
'}',
].join('\n');
return (
`${globalPrefix}__d(` +
(unstable_useStaticHermesModuleFactory
? '$SHBuiltin.moduleFactory(_$$_METRO_MODULE_ID, ' + factory + ')'
: factory) +
');'
);
}
function functionFromProgram(
program: Program,
parameters: $ReadOnlyArray<string>,
): FunctionExpression {
return t.functionExpression(
undefined,
parameters.map(makeIdentifier),
t.blockStatement(program.body, program.directives),
);
}
function makeIdentifier(name: string): Identifier {
return t.identifier(name);
}
function buildParameters(
importDefaultName: string,
importAllName: string,
dependencyMapName: string,
): $ReadOnlyArray<string> {
return [
'global',
'require',
importDefaultName,
importAllName,
'module',
'exports',
dependencyMapName,
];
}
// Renaming requires should ideally only be done when generating for the target
// that expects the custom require name in the optimize step.
// This visitor currently renames all `require` references even if the module
// contains a custom `require` declaration. This should be fixed by only renaming
// if the `require` symbol hasn't been redeclared.
function renameRequires(ast: BabelNodeFile): string {
let newRequireName = WRAP_NAME;
traverse(ast, {
Program(path) {
const body = path.get('body.0.expression.arguments.0.body');
invariant(
!Array.isArray(body),
'metro: Expected `body` to be a single path.',
);
newRequireName = body.scope.generateUid(WRAP_NAME);
body.scope.rename('require', newRequireName);
},
});
return newRequireName;
}
export {WRAP_NAME, wrapJson, jsonToCommonJS, wrapModule, wrapPolyfill};

View File

@@ -0,0 +1,27 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @oncall react_native
*/
export type ContextMode = 'sync' | 'eager' | 'lazy' | 'lazy-once';
export interface ContextFilter {
pattern: string;
flags: string;
}
export interface RequireContextParams {
/* Should search for files recursively. Optional, default `true` when `require.context` is used */
readonly recursive: boolean;
/* Filename filter pattern for use in `require.context`. Optional, default `.*` (any file) when `require.context` is used */
readonly filter: Readonly<ContextFilter>;
/** Mode for resolving dynamic dependencies. Defaults to `sync` */
readonly mode: ContextMode;
}
export type DynamicRequiresBehavior = 'throwAtRuntime' | 'reject';

View File

@@ -0,0 +1,608 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = collectDependencies;
var _generator = _interopRequireDefault(require("@babel/generator"));
var _template = _interopRequireDefault(require("@babel/template"));
var _traverse = _interopRequireDefault(require("@babel/traverse"));
var _types = _interopRequireWildcard(require("@babel/types"));
var types = _types;
var _crypto = _interopRequireDefault(require("crypto"));
var _invariant = _interopRequireDefault(require("invariant"));
var _nullthrows = _interopRequireDefault(require("nullthrows"));
function _getRequireWildcardCache(e) {
if ("function" != typeof WeakMap) return null;
var r = new WeakMap(),
t = new WeakMap();
return (_getRequireWildcardCache = function (e) {
return e ? t : r;
})(e);
}
function _interopRequireWildcard(e, r) {
if (!r && e && e.__esModule) return e;
if (null === e || ("object" != typeof e && "function" != typeof e))
return { default: e };
var t = _getRequireWildcardCache(r);
if (t && t.has(e)) return t.get(e);
var n = { __proto__: null },
a = Object.defineProperty && Object.getOwnPropertyDescriptor;
for (var u in e)
if ("default" !== u && {}.hasOwnProperty.call(e, u)) {
var i = a ? Object.getOwnPropertyDescriptor(e, u) : null;
i && (i.get || i.set) ? Object.defineProperty(n, u, i) : (n[u] = e[u]);
}
return ((n.default = e), t && t.set(e, n), n);
}
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
function collectDependencies(ast, options) {
const visited = new WeakSet();
const state = {
asyncRequireModulePathStringLiteral: null,
dependencyCalls: new Set(),
dependencyRegistry: new DependencyRegistry(),
dependencyTransformer:
options.dependencyTransformer ?? DefaultDependencyTransformer,
dependencyMapIdentifier: null,
dynamicRequires: options.dynamicRequires,
keepRequireNames: options.keepRequireNames,
allowOptionalDependencies: options.allowOptionalDependencies,
unstable_allowRequireContext: options.unstable_allowRequireContext,
unstable_isESMImportAtSource: options.unstable_isESMImportAtSource ?? null,
};
const visitor = {
CallExpression(path, state) {
if (visited.has(path.node)) {
return;
}
const callee = path.node.callee;
const name = callee.type === "Identifier" ? callee.name : null;
if ((0, _types.isImport)(callee)) {
processImportCall(path, state, {
asyncType: "async",
isESMImport: true,
});
return;
}
if (name === "__prefetchImport" && !path.scope.getBinding(name)) {
processImportCall(path, state, {
asyncType: "prefetch",
isESMImport: true,
});
return;
}
if (
state.unstable_allowRequireContext &&
callee.type === "MemberExpression" &&
callee.object.type === "Identifier" &&
callee.object.name === "require" &&
callee.property.type === "Identifier" &&
callee.property.name === "context" &&
!callee.computed &&
!path.scope.getBinding("require")
) {
processRequireContextCall(path, state);
visited.add(path.node);
return;
}
if (
callee.type === "MemberExpression" &&
callee.object.type === "Identifier" &&
callee.object.name === "require" &&
callee.property.type === "Identifier" &&
callee.property.name === "resolveWeak" &&
!callee.computed &&
!path.scope.getBinding("require")
) {
processResolveWeakCall(path, state);
visited.add(path.node);
return;
}
if (
callee.type === "MemberExpression" &&
callee.object.type === "Identifier" &&
callee.object.name === "require" &&
callee.property.type === "Identifier" &&
callee.property.name === "unstable_importMaybeSync" &&
!callee.computed &&
!path.scope.getBinding("require")
) {
processImportCall(path, state, {
asyncType: "maybeSync",
isESMImport: true,
});
visited.add(path.node);
return;
}
if (
name != null &&
state.dependencyCalls.has(name) &&
!path.scope.getBinding(name)
) {
processRequireCall(path, state);
visited.add(path.node);
}
},
ImportDeclaration: collectImports,
ExportNamedDeclaration: collectImports,
ExportAllDeclaration: collectImports,
Program(path, state) {
state.asyncRequireModulePathStringLiteral = types.stringLiteral(
options.asyncRequireModulePath,
);
if (options.dependencyMapName != null) {
state.dependencyMapIdentifier = types.identifier(
options.dependencyMapName,
);
} else {
state.dependencyMapIdentifier =
path.scope.generateUidIdentifier("dependencyMap");
}
state.dependencyCalls = new Set(["require", ...options.inlineableCalls]);
},
};
(0, _traverse.default)(ast, visitor, null, state);
const collectedDependencies = state.dependencyRegistry.getDependencies();
const dependencies = new Array(collectedDependencies.length);
for (const { index, name, ...dependencyData } of collectedDependencies) {
dependencies[index] = {
name,
data: dependencyData,
};
}
return {
ast,
dependencies,
dependencyMapName: (0, _nullthrows.default)(state.dependencyMapIdentifier)
.name,
};
}
function getRequireContextArgs(path) {
const args = path.get("arguments");
let directory;
if (!Array.isArray(args) || args.length < 1) {
throw new InvalidRequireCallError(path);
} else {
const result = args[0].evaluate();
if (result.confident && typeof result.value === "string") {
directory = result.value;
} else {
throw new InvalidRequireCallError(
result.deopt ?? args[0],
"First argument of `require.context` should be a string denoting the directory to require.",
);
}
}
let recursive = true;
if (args.length > 1) {
const result = args[1].evaluate();
if (result.confident && typeof result.value === "boolean") {
recursive = result.value;
} else if (!(result.confident && typeof result.value === "undefined")) {
throw new InvalidRequireCallError(
result.deopt ?? args[1],
"Second argument of `require.context` should be an optional boolean indicating if files should be imported recursively or not.",
);
}
}
let filter = {
pattern: ".*",
flags: "",
};
if (args.length > 2) {
const result = args[2].evaluate();
const argNode = args[2].node;
if (argNode.type === "RegExpLiteral") {
filter = {
pattern: argNode.pattern,
flags: argNode.flags || "",
};
} else if (!(result.confident && typeof result.value === "undefined")) {
throw new InvalidRequireCallError(
args[2],
`Third argument of \`require.context\` should be an optional RegExp pattern matching all of the files to import, instead found node of type: ${argNode.type}.`,
);
}
}
let mode = "sync";
if (args.length > 3) {
const result = args[3].evaluate();
if (result.confident && typeof result.value === "string") {
mode = getContextMode(args[3], result.value);
} else if (!(result.confident && typeof result.value === "undefined")) {
throw new InvalidRequireCallError(
result.deopt ?? args[3],
'Fourth argument of `require.context` should be an optional string "mode" denoting how the modules will be resolved.',
);
}
}
if (args.length > 4) {
throw new InvalidRequireCallError(
path,
`Too many arguments provided to \`require.context\` call. Expected 4, got: ${args.length}`,
);
}
return [
directory,
{
recursive,
filter,
mode,
},
];
}
function getContextMode(path, mode) {
if (
mode === "sync" ||
mode === "eager" ||
mode === "lazy" ||
mode === "lazy-once"
) {
return mode;
}
throw new InvalidRequireCallError(
path,
`require.context "${mode}" mode is not supported. Expected one of: sync, eager, lazy, lazy-once`,
);
}
function processRequireContextCall(path, state) {
const [directory, contextParams] = getRequireContextArgs(path);
const transformer = state.dependencyTransformer;
const dep = registerDependency(
state,
{
name: directory,
contextParams,
asyncType: null,
isESMImport: false,
optional: isOptionalDependency(directory, path, state),
},
path,
);
path.get("callee").replaceWith(types.identifier("require"));
transformer.transformSyncRequire(path, dep, state);
}
function processResolveWeakCall(path, state) {
const name = getModuleNameFromCallArgs(path);
if (name == null) {
throw new InvalidRequireCallError(path);
}
const dependency = registerDependency(
state,
{
name,
asyncType: "weak",
isESMImport: false,
optional: isOptionalDependency(name, path, state),
},
path,
);
path.replaceWith(
makeResolveWeakTemplate({
MODULE_ID: createModuleIDExpression(dependency, state),
}),
);
}
function collectImports(path, state) {
if (path.node.source) {
(0, _invariant.default)(
path.node.source.type === "StringLiteral",
`Expected import source to be a string. Maybe you're using 'createImportExpressions', which is not currently supported.
See: https://github.com/facebook/metro/pull/1343`,
);
registerDependency(
state,
{
name: path.node.source.value,
asyncType: null,
isESMImport: true,
optional: false,
},
path,
);
}
}
function processImportCall(path, state, options) {
const name = getModuleNameFromCallArgs(path);
if (name == null) {
throw new InvalidRequireCallError(path);
}
const dep = registerDependency(
state,
{
name,
asyncType: options.asyncType,
isESMImport: options.isESMImport,
optional: isOptionalDependency(name, path, state),
},
path,
);
const transformer = state.dependencyTransformer;
switch (options.asyncType) {
case "async":
transformer.transformImportCall(path, dep, state);
break;
case "maybeSync":
transformer.transformImportMaybeSyncCall(path, dep, state);
break;
case "prefetch":
transformer.transformPrefetch(path, dep, state);
break;
case "weak":
throw new Error("Unreachable");
default:
options.asyncType;
throw new Error("Unreachable");
}
}
function processRequireCall(path, state) {
const name = getModuleNameFromCallArgs(path);
const transformer = state.dependencyTransformer;
if (name == null) {
if (state.dynamicRequires === "reject") {
throw new InvalidRequireCallError(path);
}
transformer.transformIllegalDynamicRequire(path, state);
return;
}
let isESMImport = false;
if (state.unstable_isESMImportAtSource) {
const isImport = state.unstable_isESMImportAtSource;
const loc = getNearestLocFromPath(path);
if (loc) {
isESMImport = isImport(loc);
}
}
const dep = registerDependency(
state,
{
name,
asyncType: null,
isESMImport,
optional: isOptionalDependency(name, path, state),
},
path,
);
transformer.transformSyncRequire(path, dep, state);
}
function getNearestLocFromPath(path) {
let current = path;
while (
current &&
!current.node.loc &&
!current.node.METRO_INLINE_REQUIRES_INIT_LOC
) {
current = current.parentPath;
}
if (current && (0, _types.isProgram)(current.node)) {
current = null;
}
return current?.node.METRO_INLINE_REQUIRES_INIT_LOC ?? current?.node.loc;
}
function registerDependency(state, qualifier, path) {
const dependency = state.dependencyRegistry.registerDependency(qualifier);
const loc = getNearestLocFromPath(path);
if (loc != null) {
dependency.locs.push(loc);
}
return dependency;
}
function isOptionalDependency(name, path, state) {
const { allowOptionalDependencies } = state;
if (name === state.asyncRequireModulePathStringLiteral?.value) {
return false;
}
const isExcluded = () =>
Array.isArray(allowOptionalDependencies.exclude) &&
allowOptionalDependencies.exclude.includes(name);
if (!allowOptionalDependencies || isExcluded()) {
return false;
}
let sCount = 0;
let p = path;
while (p && sCount < 3) {
if (p.isStatement()) {
if (p.node.type === "BlockStatement") {
return (
p.parentPath != null &&
p.parentPath.node.type === "TryStatement" &&
p.key === "block"
);
}
sCount += 1;
}
p = p.parentPath;
}
return false;
}
function getModuleNameFromCallArgs(path) {
const args = path.get("arguments");
if (!Array.isArray(args) || args.length !== 1) {
throw new InvalidRequireCallError(path);
}
const result = args[0].evaluate();
if (result.confident && typeof result.value === "string") {
return result.value;
}
return null;
}
collectDependencies.getModuleNameFromCallArgs = getModuleNameFromCallArgs;
class InvalidRequireCallError extends Error {
constructor({ node }, message) {
const line = node.loc && node.loc.start && node.loc.start.line;
super(
[
`Invalid call at line ${line || "<unknown>"}: ${(0, _generator.default)(node).code}`,
message,
]
.filter(Boolean)
.join("\n"),
);
}
}
collectDependencies.InvalidRequireCallError = InvalidRequireCallError;
const dynamicRequireErrorTemplate = _template.default.expression(`
(function(line) {
throw new Error(
'Dynamic require defined at line ' + line + '; not supported by Metro',
);
})(LINE)
`);
const makeAsyncRequireTemplate = _template.default.expression(`
require(ASYNC_REQUIRE_MODULE_PATH)(MODULE_ID, DEPENDENCY_MAP.paths)
`);
const makeAsyncRequireTemplateWithName = _template.default.expression(`
require(ASYNC_REQUIRE_MODULE_PATH)(MODULE_ID, DEPENDENCY_MAP.paths, MODULE_NAME)
`);
const makeAsyncPrefetchTemplate = _template.default.expression(`
require(ASYNC_REQUIRE_MODULE_PATH).prefetch(MODULE_ID, DEPENDENCY_MAP.paths)
`);
const makeAsyncPrefetchTemplateWithName = _template.default.expression(`
require(ASYNC_REQUIRE_MODULE_PATH).prefetch(MODULE_ID, DEPENDENCY_MAP.paths, MODULE_NAME)
`);
const makeAsyncImportMaybeSyncTemplate = _template.default.expression(`
require(ASYNC_REQUIRE_MODULE_PATH).unstable_importMaybeSync(MODULE_ID, DEPENDENCY_MAP.paths)
`);
const makeAsyncImportMaybeSyncTemplateWithName = _template.default.expression(`
require(ASYNC_REQUIRE_MODULE_PATH).unstable_importMaybeSync(MODULE_ID, DEPENDENCY_MAP.paths, MODULE_NAME)
`);
const makeResolveWeakTemplate = _template.default.expression(`
MODULE_ID
`);
const DefaultDependencyTransformer = {
transformSyncRequire(path, dependency, state) {
const moduleIDExpression = createModuleIDExpression(dependency, state);
path.node.arguments = [moduleIDExpression];
if (state.keepRequireNames) {
path.node.arguments.push(types.stringLiteral(dependency.name));
}
},
transformImportCall(path, dependency, state) {
const makeNode = state.keepRequireNames
? makeAsyncRequireTemplateWithName
: makeAsyncRequireTemplate;
const opts = {
ASYNC_REQUIRE_MODULE_PATH: (0, _nullthrows.default)(
state.asyncRequireModulePathStringLiteral,
),
MODULE_ID: createModuleIDExpression(dependency, state),
DEPENDENCY_MAP: (0, _nullthrows.default)(state.dependencyMapIdentifier),
...(state.keepRequireNames
? {
MODULE_NAME: createModuleNameLiteral(dependency),
}
: null),
};
path.replaceWith(makeNode(opts));
},
transformImportMaybeSyncCall(path, dependency, state) {
const makeNode = state.keepRequireNames
? makeAsyncImportMaybeSyncTemplateWithName
: makeAsyncImportMaybeSyncTemplate;
const opts = {
ASYNC_REQUIRE_MODULE_PATH: (0, _nullthrows.default)(
state.asyncRequireModulePathStringLiteral,
),
MODULE_ID: createModuleIDExpression(dependency, state),
DEPENDENCY_MAP: (0, _nullthrows.default)(state.dependencyMapIdentifier),
...(state.keepRequireNames
? {
MODULE_NAME: createModuleNameLiteral(dependency),
}
: null),
};
path.replaceWith(makeNode(opts));
},
transformPrefetch(path, dependency, state) {
const makeNode = state.keepRequireNames
? makeAsyncPrefetchTemplateWithName
: makeAsyncPrefetchTemplate;
const opts = {
ASYNC_REQUIRE_MODULE_PATH: (0, _nullthrows.default)(
state.asyncRequireModulePathStringLiteral,
),
MODULE_ID: createModuleIDExpression(dependency, state),
DEPENDENCY_MAP: (0, _nullthrows.default)(state.dependencyMapIdentifier),
...(state.keepRequireNames
? {
MODULE_NAME: createModuleNameLiteral(dependency),
}
: null),
};
path.replaceWith(makeNode(opts));
},
transformIllegalDynamicRequire(path, state) {
path.replaceWith(
dynamicRequireErrorTemplate({
LINE: types.numericLiteral(path.node.loc?.start.line ?? 0),
}),
);
},
};
function createModuleIDExpression(dependency, state) {
return types.memberExpression(
(0, _nullthrows.default)(state.dependencyMapIdentifier),
types.numericLiteral(dependency.index),
true,
);
}
function createModuleNameLiteral(dependency) {
return types.stringLiteral(dependency.name);
}
function getKeyForDependency(qualifier) {
const { asyncType, contextParams, isESMImport, name } = qualifier;
let key = [name, isESMImport ? "import" : "require"].join("\0");
if (asyncType != null) {
key += "\0" + asyncType;
}
if (contextParams) {
key += [
"",
"context",
String(contextParams.recursive),
String(contextParams.filter.pattern),
String(contextParams.filter.flags),
contextParams.mode,
].join("\0");
}
return key;
}
class DependencyRegistry {
_dependencies = new Map();
registerDependency(qualifier) {
const key = getKeyForDependency(qualifier);
let dependency = this._dependencies.get(key);
if (dependency == null) {
const newDependency = {
name: qualifier.name,
asyncType: qualifier.asyncType,
isESMImport: qualifier.isESMImport,
locs: [],
index: this._dependencies.size,
key: _crypto.default.createHash("sha1").update(key).digest("base64"),
};
if (qualifier.optional) {
newDependency.isOptional = true;
}
if (qualifier.contextParams) {
newDependency.contextParams = qualifier.contextParams;
}
dependency = newDependency;
} else {
if (dependency.isOptional && !qualifier.optional) {
dependency = {
...dependency,
isOptional: false,
};
}
}
this._dependencies.set(key, dependency);
return dependency;
}
getDependencies() {
return Array.from(this._dependencies.values());
}
}

View File

@@ -0,0 +1,929 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow
*/
import type {NodePath} from '@babel/traverse';
import type {CallExpression, Identifier, StringLiteral} from '@babel/types';
import type {
AllowOptionalDependencies,
AsyncDependencyType,
} from 'metro/private/DeltaBundler/types';
import generate from '@babel/generator';
import template from '@babel/template';
import traverse from '@babel/traverse';
import * as types from '@babel/types';
import {isImport, isProgram} from '@babel/types';
import crypto from 'crypto';
import invariant from 'invariant';
import nullthrows from 'nullthrows';
type ImportDependencyOptions = $ReadOnly<{
asyncType: AsyncDependencyType,
isESMImport: boolean,
}>;
export type Dependency = $ReadOnly<{
data: DependencyData,
name: string,
}>;
// TODO: Convert to a Flow enum
export type ContextMode = 'sync' | 'eager' | 'lazy' | 'lazy-once';
type ContextFilter = $ReadOnly<{pattern: string, flags: string}>;
export type RequireContextParams = $ReadOnly<{
/* Should search for files recursively. Optional, default `true` when `require.context` is used */
recursive: boolean,
/* Filename filter pattern for use in `require.context`. Optional, default `.*` (any file) when `require.context` is used */
filter: $ReadOnly<ContextFilter>,
/** Mode for resolving dynamic dependencies. Defaults to `sync` */
mode: ContextMode,
}>;
type DependencyData = $ReadOnly<{
// A locally unique key for this dependency within the current module.
key: string,
// If null, then the dependency is synchronous.
// (ex. `require('foo')`)
asyncType: AsyncDependencyType | null,
// If true, the dependency is declared using an ESM import, e.g.
// "import x from 'y'" or "await import('z')". A resolver should typically
// use this to assert either "import" or "require" for conditional exports
// and subpath imports.
isESMImport: boolean,
isOptional?: boolean,
locs: $ReadOnlyArray<BabelSourceLocation>,
/** Context for requiring a collection of modules. */
contextParams?: RequireContextParams,
}>;
export type MutableInternalDependency = {
...DependencyData,
locs: Array<BabelSourceLocation>,
index: number,
name: string,
};
export type InternalDependency = $ReadOnly<MutableInternalDependency>;
export type State = {
asyncRequireModulePathStringLiteral: ?StringLiteral,
dependencyCalls: Set<string>,
dependencyRegistry: DependencyRegistry,
dependencyTransformer: DependencyTransformer,
dynamicRequires: DynamicRequiresBehavior,
dependencyMapIdentifier: ?Identifier,
keepRequireNames: boolean,
allowOptionalDependencies: AllowOptionalDependencies,
/** Enable `require.context` statements which can be used to import multiple files in a directory. */
unstable_allowRequireContext: boolean,
unstable_isESMImportAtSource: ?(BabelSourceLocation) => boolean,
};
export type Options = $ReadOnly<{
asyncRequireModulePath: string,
dependencyMapName: ?string,
dynamicRequires: DynamicRequiresBehavior,
inlineableCalls: $ReadOnlyArray<string>,
keepRequireNames: boolean,
allowOptionalDependencies: AllowOptionalDependencies,
dependencyTransformer?: DependencyTransformer,
/** Enable `require.context` statements which can be used to import multiple files in a directory. */
unstable_allowRequireContext: boolean,
unstable_isESMImportAtSource?: ?(BabelSourceLocation) => boolean,
}>;
export type CollectedDependencies = $ReadOnly<{
ast: BabelNodeFile,
dependencyMapName: string,
dependencies: $ReadOnlyArray<Dependency>,
}>;
export interface DependencyTransformer {
transformSyncRequire(
path: NodePath<CallExpression>,
dependency: InternalDependency,
state: State,
): void;
transformImportCall(
path: NodePath<>,
dependency: InternalDependency,
state: State,
): void;
transformImportMaybeSyncCall(
path: NodePath<>,
dependency: InternalDependency,
state: State,
): void;
transformPrefetch(
path: NodePath<>,
dependency: InternalDependency,
state: State,
): void;
transformIllegalDynamicRequire(path: NodePath<>, state: State): void;
}
export type DynamicRequiresBehavior = 'throwAtRuntime' | 'reject';
/**
* Transform all the calls to `require()` and `import()` in a file into ID-
* independent code, and return the list of dependencies. For example, a call
* like `require('Foo')` could be transformed to `require(_depMap[3], 'Foo')`
* where `_depMap` is provided by the outer scope. As such, we don't need to
* know the actual module ID.
*
* The second argument is only provided for debugging purposes.
*/
export default function collectDependencies(
ast: BabelNodeFile,
options: Options,
): CollectedDependencies {
const visited = new WeakSet<BabelNodeCallExpression>();
const state: State = {
asyncRequireModulePathStringLiteral: null,
dependencyCalls: new Set(),
dependencyRegistry: new DependencyRegistry(),
dependencyTransformer:
options.dependencyTransformer ?? DefaultDependencyTransformer,
dependencyMapIdentifier: null,
dynamicRequires: options.dynamicRequires,
keepRequireNames: options.keepRequireNames,
allowOptionalDependencies: options.allowOptionalDependencies,
unstable_allowRequireContext: options.unstable_allowRequireContext,
unstable_isESMImportAtSource: options.unstable_isESMImportAtSource ?? null,
};
const visitor = {
CallExpression(
path: NodePath<BabelNodeCallExpression>,
state: State,
): void {
if (visited.has(path.node)) {
return;
}
const callee = path.node.callee;
const name = callee.type === 'Identifier' ? callee.name : null;
if (isImport(callee)) {
processImportCall(path, state, {
asyncType: 'async',
isESMImport: true,
});
return;
}
if (name === '__prefetchImport' && !path.scope.getBinding(name)) {
processImportCall(path, state, {
asyncType: 'prefetch',
isESMImport: true,
});
return;
}
// Match `require.context`
if (
// Feature gate, defaults to `false`.
state.unstable_allowRequireContext &&
callee.type === 'MemberExpression' &&
// `require`
callee.object.type === 'Identifier' &&
callee.object.name === 'require' &&
// `context`
callee.property.type === 'Identifier' &&
callee.property.name === 'context' &&
!callee.computed &&
// Ensure `require` refers to the global and not something else.
!path.scope.getBinding('require')
) {
processRequireContextCall(path, state);
visited.add(path.node);
return;
}
// Match `require.resolveWeak`
if (
callee.type === 'MemberExpression' &&
// `require`
callee.object.type === 'Identifier' &&
callee.object.name === 'require' &&
// `resolveWeak`
callee.property.type === 'Identifier' &&
callee.property.name === 'resolveWeak' &&
!callee.computed &&
// Ensure `require` refers to the global and not something else.
!path.scope.getBinding('require')
) {
processResolveWeakCall(path, state);
visited.add(path.node);
return;
}
// Match `require.unstable_importMaybeSync`
if (
callee.type === 'MemberExpression' &&
// `require`
callee.object.type === 'Identifier' &&
callee.object.name === 'require' &&
// `unstable_importMaybeSync`
callee.property.type === 'Identifier' &&
callee.property.name === 'unstable_importMaybeSync' &&
!callee.computed &&
// Ensure `require` refers to the global and not something else.
!path.scope.getBinding('require')
) {
processImportCall(path, state, {
asyncType: 'maybeSync',
// Treat require.unstable_importMaybeSync as an ESM import, like its
// async "await import()" counterpart. Subject to change while
// unstable_.
isESMImport: true,
});
visited.add(path.node);
return;
}
if (
name != null &&
state.dependencyCalls.has(name) &&
!path.scope.getBinding(name)
) {
processRequireCall(path, state);
visited.add(path.node);
}
},
ImportDeclaration: collectImports,
ExportNamedDeclaration: collectImports,
ExportAllDeclaration: collectImports,
Program(path: NodePath<BabelNodeProgram>, state: State) {
state.asyncRequireModulePathStringLiteral = types.stringLiteral(
options.asyncRequireModulePath,
);
if (options.dependencyMapName != null) {
state.dependencyMapIdentifier = types.identifier(
options.dependencyMapName,
);
} else {
state.dependencyMapIdentifier =
path.scope.generateUidIdentifier('dependencyMap');
}
state.dependencyCalls = new Set(['require', ...options.inlineableCalls]);
},
};
traverse(ast, visitor, null, state);
const collectedDependencies = state.dependencyRegistry.getDependencies();
// Compute the list of dependencies.
const dependencies = new Array<Dependency>(collectedDependencies.length);
for (const {index, name, ...dependencyData} of collectedDependencies) {
dependencies[index] = {
name,
data: dependencyData,
};
}
return {
ast,
dependencies,
dependencyMapName: nullthrows(state.dependencyMapIdentifier).name,
};
}
/** Extract args passed to the `require.context` method. */
function getRequireContextArgs(
path: NodePath<CallExpression>,
): [string, RequireContextParams] {
const args = path.get('arguments');
let directory: string;
if (!Array.isArray(args) || args.length < 1) {
throw new InvalidRequireCallError(path);
} else {
const result = args[0].evaluate();
if (result.confident && typeof result.value === 'string') {
directory = result.value;
} else {
throw new InvalidRequireCallError(
result.deopt ?? args[0],
'First argument of `require.context` should be a string denoting the directory to require.',
);
}
}
// Default to requiring through all directories.
let recursive: boolean = true;
if (args.length > 1) {
const result = args[1].evaluate();
if (result.confident && typeof result.value === 'boolean') {
recursive = result.value;
} else if (!(result.confident && typeof result.value === 'undefined')) {
throw new InvalidRequireCallError(
result.deopt ?? args[1],
'Second argument of `require.context` should be an optional boolean indicating if files should be imported recursively or not.',
);
}
}
// Default to all files.
let filter: ContextFilter = {pattern: '.*', flags: ''};
if (args.length > 2) {
// evaluate() to check for undefined (because it's technically a scope lookup)
// but check the AST for the regex literal, since evaluate() doesn't do regex.
const result = args[2].evaluate();
const argNode = args[2].node;
if (argNode.type === 'RegExpLiteral') {
// TODO: Handle `new RegExp(...)` -- `argNode.type === 'NewExpression'`
filter = {
pattern: argNode.pattern,
flags: argNode.flags || '',
};
} else if (!(result.confident && typeof result.value === 'undefined')) {
throw new InvalidRequireCallError(
args[2],
`Third argument of \`require.context\` should be an optional RegExp pattern matching all of the files to import, instead found node of type: ${argNode.type}.`,
);
}
}
// Default to `sync`.
let mode: ContextMode = 'sync';
if (args.length > 3) {
const result = args[3].evaluate();
if (result.confident && typeof result.value === 'string') {
mode = getContextMode(args[3], result.value);
} else if (!(result.confident && typeof result.value === 'undefined')) {
throw new InvalidRequireCallError(
result.deopt ?? args[3],
'Fourth argument of `require.context` should be an optional string "mode" denoting how the modules will be resolved.',
);
}
}
if (args.length > 4) {
throw new InvalidRequireCallError(
path,
`Too many arguments provided to \`require.context\` call. Expected 4, got: ${args.length}`,
);
}
return [
directory,
{
recursive,
filter,
mode,
},
];
}
function getContextMode(path: NodePath<>, mode: string): ContextMode {
if (
mode === 'sync' ||
mode === 'eager' ||
mode === 'lazy' ||
mode === 'lazy-once'
) {
return mode;
}
throw new InvalidRequireCallError(
path,
`require.context "${mode}" mode is not supported. Expected one of: sync, eager, lazy, lazy-once`,
);
}
function processRequireContextCall(
path: NodePath<CallExpression>,
state: State,
): void {
const [directory, contextParams] = getRequireContextArgs(path);
const transformer = state.dependencyTransformer;
const dep = registerDependency(
state,
{
// We basically want to "import" every file in a folder and then filter them out with the given `filter` RegExp.
name: directory,
// Capture the matching context
contextParams,
asyncType: null,
isESMImport: false,
optional: isOptionalDependency(directory, path, state),
},
path,
);
// require() the generated module representing this context
path.get('callee').replaceWith(types.identifier('require'));
transformer.transformSyncRequire(path, dep, state);
}
function processResolveWeakCall(
path: NodePath<CallExpression>,
state: State,
): void {
const name = getModuleNameFromCallArgs(path);
if (name == null) {
throw new InvalidRequireCallError(path);
}
const dependency = registerDependency(
state,
{
name,
asyncType: 'weak',
isESMImport: false,
optional: isOptionalDependency(name, path, state),
},
path,
);
path.replaceWith(
makeResolveWeakTemplate({
MODULE_ID: createModuleIDExpression(dependency, state),
}),
);
}
function collectImports(path: NodePath<>, state: State): void {
if (path.node.source) {
invariant(
path.node.source.type === 'StringLiteral',
`Expected import source to be a string. Maybe you're using 'createImportExpressions', which is not currently supported.
See: https://github.com/facebook/metro/pull/1343`,
);
registerDependency(
state,
{
name: path.node.source.value,
asyncType: null,
isESMImport: true,
optional: false,
},
path,
);
}
}
function processImportCall(
path: NodePath<CallExpression>,
state: State,
options: ImportDependencyOptions,
): void {
const name = getModuleNameFromCallArgs(path);
if (name == null) {
throw new InvalidRequireCallError(path);
}
const dep = registerDependency(
state,
{
name,
asyncType: options.asyncType,
isESMImport: options.isESMImport,
optional: isOptionalDependency(name, path, state),
},
path,
);
const transformer = state.dependencyTransformer;
switch (options.asyncType) {
case 'async':
transformer.transformImportCall(path, dep, state);
break;
case 'maybeSync':
transformer.transformImportMaybeSyncCall(path, dep, state);
break;
case 'prefetch':
transformer.transformPrefetch(path, dep, state);
break;
case 'weak':
throw new Error('Unreachable');
default:
options.asyncType as empty;
throw new Error('Unreachable');
}
}
function processRequireCall(
path: NodePath<CallExpression>,
state: State,
): void {
const name = getModuleNameFromCallArgs(path);
const transformer = state.dependencyTransformer;
if (name == null) {
if (state.dynamicRequires === 'reject') {
throw new InvalidRequireCallError(path);
}
transformer.transformIllegalDynamicRequire(path, state);
return;
}
let isESMImport = false;
if (state.unstable_isESMImportAtSource) {
const isImport = state.unstable_isESMImportAtSource;
const loc = getNearestLocFromPath(path);
if (loc) {
isESMImport = isImport(loc);
}
}
const dep = registerDependency(
state,
{
name,
asyncType: null,
isESMImport,
optional: isOptionalDependency(name, path, state),
},
path,
);
transformer.transformSyncRequire(path, dep, state);
}
function getNearestLocFromPath(path: NodePath<>): ?BabelSourceLocation {
let current: ?(NodePath<> | NodePath<BabelNode>) = path;
while (
current &&
!current.node.loc &&
// $FlowFixMe[prop-missing] METRO_INLINE_REQUIRES_INIT_LOC is Metro-specific and not typed
!current.node.METRO_INLINE_REQUIRES_INIT_LOC
) {
current = current.parentPath;
}
// Do not use the location of the `Program` node
if (current && isProgram(current.node)) {
current = null;
}
return (
// $FlowFixMe[prop-missing] METRO_INLINE_REQUIRES_INIT_LOC is Metro-specific and not typed
current?.node.METRO_INLINE_REQUIRES_INIT_LOC ?? current?.node.loc
);
}
export type ImportQualifier = $ReadOnly<{
name: string,
asyncType: AsyncDependencyType | null,
isESMImport: boolean,
optional: boolean,
contextParams?: RequireContextParams,
}>;
function registerDependency(
state: State,
qualifier: ImportQualifier,
path: NodePath<>,
): InternalDependency {
const dependency = state.dependencyRegistry.registerDependency(qualifier);
const loc = getNearestLocFromPath(path);
if (loc != null) {
dependency.locs.push(loc);
}
return dependency;
}
function isOptionalDependency(
name: string,
path: NodePath<>,
state: State,
): boolean {
const {allowOptionalDependencies} = state;
// The async require module is a 'built-in'. Resolving should never fail -> treat it as non-optional.
if (name === state.asyncRequireModulePathStringLiteral?.value) {
return false;
}
const isExcluded = () =>
Array.isArray(allowOptionalDependencies.exclude) &&
allowOptionalDependencies.exclude.includes(name);
if (!allowOptionalDependencies || isExcluded()) {
return false;
}
// Valid statement stack for single-level try-block: expressionStatement -> blockStatement -> tryStatement
let sCount = 0;
let p: ?(NodePath<> | NodePath<BabelNode>) = path;
while (p && sCount < 3) {
if (p.isStatement()) {
if (p.node.type === 'BlockStatement') {
// A single-level should have the tryStatement immediately followed BlockStatement
// with the key 'block' to distinguish from the finally block, which has key = 'finalizer'
return (
p.parentPath != null &&
p.parentPath.node.type === 'TryStatement' &&
p.key === 'block'
);
}
sCount += 1;
}
p = p.parentPath;
}
return false;
}
function getModuleNameFromCallArgs(path: NodePath<CallExpression>): ?string {
const args = path.get('arguments');
if (!Array.isArray(args) || args.length !== 1) {
throw new InvalidRequireCallError(path);
}
const result = args[0].evaluate();
if (result.confident && typeof result.value === 'string') {
return result.value;
}
return null;
}
collectDependencies.getModuleNameFromCallArgs = getModuleNameFromCallArgs;
class InvalidRequireCallError extends Error {
constructor({node}: NodePath<>, message?: string) {
const line = node.loc && node.loc.start && node.loc.start.line;
super(
[
`Invalid call at line ${line || '<unknown>'}: ${generate(node).code}`,
message,
]
.filter(Boolean)
.join('\n'),
);
}
}
collectDependencies.InvalidRequireCallError = InvalidRequireCallError;
/**
* Produces a Babel template that will throw at runtime when the require call
* is reached. This makes dynamic require errors catchable by libraries that
* want to use them.
*/
const dynamicRequireErrorTemplate = template.expression(`
(function(line) {
throw new Error(
'Dynamic require defined at line ' + line + '; not supported by Metro',
);
})(LINE)
`);
/**
* Produces a Babel template that transforms an "import(...)" call into a
* "require(...)" call to the asyncRequire specified.
*/
const makeAsyncRequireTemplate = template.expression(`
require(ASYNC_REQUIRE_MODULE_PATH)(MODULE_ID, DEPENDENCY_MAP.paths)
`);
const makeAsyncRequireTemplateWithName = template.expression(`
require(ASYNC_REQUIRE_MODULE_PATH)(MODULE_ID, DEPENDENCY_MAP.paths, MODULE_NAME)
`);
const makeAsyncPrefetchTemplate = template.expression(`
require(ASYNC_REQUIRE_MODULE_PATH).prefetch(MODULE_ID, DEPENDENCY_MAP.paths)
`);
const makeAsyncPrefetchTemplateWithName = template.expression(`
require(ASYNC_REQUIRE_MODULE_PATH).prefetch(MODULE_ID, DEPENDENCY_MAP.paths, MODULE_NAME)
`);
const makeAsyncImportMaybeSyncTemplate = template.expression(`
require(ASYNC_REQUIRE_MODULE_PATH).unstable_importMaybeSync(MODULE_ID, DEPENDENCY_MAP.paths)
`);
const makeAsyncImportMaybeSyncTemplateWithName = template.expression(`
require(ASYNC_REQUIRE_MODULE_PATH).unstable_importMaybeSync(MODULE_ID, DEPENDENCY_MAP.paths, MODULE_NAME)
`);
const makeResolveWeakTemplate = template.expression(`
MODULE_ID
`);
const DefaultDependencyTransformer: DependencyTransformer = {
transformSyncRequire(
path: NodePath<CallExpression>,
dependency: InternalDependency,
state: State,
): void {
const moduleIDExpression = createModuleIDExpression(dependency, state);
path.node.arguments = ([moduleIDExpression]: Array<
| BabelNodeExpression
| BabelNodeSpreadElement
| BabelNodeArgumentPlaceholder,
>);
// Always add the debug name argument last
if (state.keepRequireNames) {
path.node.arguments.push(types.stringLiteral(dependency.name));
}
},
transformImportCall(
path: NodePath<>,
dependency: InternalDependency,
state: State,
): void {
const makeNode = state.keepRequireNames
? makeAsyncRequireTemplateWithName
: makeAsyncRequireTemplate;
const opts = {
ASYNC_REQUIRE_MODULE_PATH: nullthrows(
state.asyncRequireModulePathStringLiteral,
),
MODULE_ID: createModuleIDExpression(dependency, state),
DEPENDENCY_MAP: nullthrows(state.dependencyMapIdentifier),
...(state.keepRequireNames
? {MODULE_NAME: createModuleNameLiteral(dependency)}
: null),
};
/* $FlowFixMe[incompatible-type] Natural Inference rollout. See
* https://fburl.com/gdoc/y8dn025u */
path.replaceWith(makeNode(opts));
},
transformImportMaybeSyncCall(
path: NodePath<>,
dependency: InternalDependency,
state: State,
): void {
const makeNode = state.keepRequireNames
? makeAsyncImportMaybeSyncTemplateWithName
: makeAsyncImportMaybeSyncTemplate;
const opts = {
ASYNC_REQUIRE_MODULE_PATH: nullthrows(
state.asyncRequireModulePathStringLiteral,
),
MODULE_ID: createModuleIDExpression(dependency, state),
DEPENDENCY_MAP: nullthrows(state.dependencyMapIdentifier),
...(state.keepRequireNames
? {MODULE_NAME: createModuleNameLiteral(dependency)}
: null),
};
/* $FlowFixMe[incompatible-type] Natural Inference rollout. See
* https://fburl.com/gdoc/y8dn025u */
path.replaceWith(makeNode(opts));
},
transformPrefetch(
path: NodePath<>,
dependency: InternalDependency,
state: State,
): void {
const makeNode = state.keepRequireNames
? makeAsyncPrefetchTemplateWithName
: makeAsyncPrefetchTemplate;
const opts = {
ASYNC_REQUIRE_MODULE_PATH: nullthrows(
state.asyncRequireModulePathStringLiteral,
),
MODULE_ID: createModuleIDExpression(dependency, state),
DEPENDENCY_MAP: nullthrows(state.dependencyMapIdentifier),
...(state.keepRequireNames
? {MODULE_NAME: createModuleNameLiteral(dependency)}
: null),
};
/* $FlowFixMe[incompatible-type] Natural Inference rollout. See
* https://fburl.com/gdoc/y8dn025u */
path.replaceWith(makeNode(opts));
},
transformIllegalDynamicRequire(path: NodePath<>, state: State): void {
path.replaceWith(
dynamicRequireErrorTemplate({
LINE: types.numericLiteral(path.node.loc?.start.line ?? 0),
}),
);
},
};
function createModuleIDExpression(
dependency: InternalDependency,
state: State,
): BabelNodeExpression {
return types.memberExpression(
nullthrows(state.dependencyMapIdentifier),
types.numericLiteral(dependency.index),
true,
);
}
function createModuleNameLiteral(dependency: InternalDependency) {
return types.stringLiteral(dependency.name);
}
/**
* Given an import qualifier, return a key used to register the dependency.
* Attributes can be appended to distinguish various combinations that would
* otherwise be considered the same dependency edge.
*
* For example, the following dependencies would collapse into a single edge
* if they simply utilized the `name` property:
*
* ```
* require('./foo');
* import foo from './foo'
* await import('./foo')
* require.context('./foo');
* require.context('./foo', true, /something/);
* require.context('./foo', false, /something/);
* require.context('./foo', false, /something/, 'lazy');
* ```
*
* This method should be utilized by `registerDependency`.
*/
function getKeyForDependency(qualifier: ImportQualifier): string {
const {asyncType, contextParams, isESMImport, name} = qualifier;
let key = [name, isESMImport ? 'import' : 'require'].join('\0');
if (asyncType != null) {
key += '\0' + asyncType;
}
// Add extra qualifiers when using `require.context` to prevent collisions.
if (contextParams) {
// NOTE(EvanBacon): Keep this synchronized with `RequireContextParams`, if any other properties are added
// then this key algorithm should be updated to account for those properties.
// Example: `./directory__true__/foobar/m__lazy`
key += [
'',
'context',
String(contextParams.recursive),
String(contextParams.filter.pattern),
String(contextParams.filter.flags),
contextParams.mode,
// Join together and append to the name:
].join('\0');
}
return key;
}
class DependencyRegistry {
_dependencies: Map<string, InternalDependency> = new Map();
registerDependency(qualifier: ImportQualifier): InternalDependency {
const key = getKeyForDependency(qualifier);
let dependency: ?InternalDependency = this._dependencies.get(key);
if (dependency == null) {
const newDependency: MutableInternalDependency = {
name: qualifier.name,
asyncType: qualifier.asyncType,
isESMImport: qualifier.isESMImport,
locs: [],
index: this._dependencies.size,
key: crypto.createHash('sha1').update(key).digest('base64'),
};
if (qualifier.optional) {
newDependency.isOptional = true;
}
if (qualifier.contextParams) {
newDependency.contextParams = qualifier.contextParams;
}
dependency = newDependency;
} else {
if (dependency.isOptional && !qualifier.optional) {
// A previously optionally required dependency was required non-optionally.
// Mark it non optional for the whole module
dependency = {
...dependency,
isOptional: false,
};
}
}
this._dependencies.set(key, dependency);
return dependency;
}
getDependencies(): Array<InternalDependency> {
return Array.from(this._dependencies.values());
}
}

View File

@@ -0,0 +1,26 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = generateImportNames;
var _traverse = _interopRequireDefault(require("@babel/traverse"));
var _nullthrows = _interopRequireDefault(require("nullthrows"));
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
function generateImportNames(ast) {
let importDefault;
let importAll;
(0, _traverse.default)(ast, {
Program(path) {
importAll = path.scope.generateUid("$$_IMPORT_ALL");
importDefault = path.scope.generateUid("$$_IMPORT_DEFAULT");
path.stop();
},
});
return {
importAll: (0, _nullthrows.default)(importAll),
importDefault: (0, _nullthrows.default)(importDefault),
};
}

View File

@@ -0,0 +1,38 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @flow strict-local
*/
import traverse from '@babel/traverse';
import nullthrows from 'nullthrows';
/**
* Select unused names for "metroImportDefault" and "metroImportAll", by
* calling "generateUid".
*/
export default function generateImportNames(ast: BabelNode): {
importAll: string,
importDefault: string,
} {
let importDefault;
let importAll;
traverse(ast, {
Program(path) {
importAll = path.scope.generateUid('$$_IMPORT_ALL');
importDefault = path.scope.generateUid('$$_IMPORT_DEFAULT');
path.stop();
},
});
return {
importAll: nullthrows(importAll),
importDefault: nullthrows(importDefault),
};
}

View File

@@ -0,0 +1,47 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.importLocationsPlugin = importLocationsPlugin;
exports.locToKey = locToKey;
function importLocationsPlugin({ types: t }) {
return {
visitor: {
ImportDeclaration(path, { importDeclarationLocs }) {
if (path.node.importKind !== "type" && path.node.loc != null) {
importDeclarationLocs.add(locToKey(path.node.loc));
}
},
ExportDeclaration(path, { importDeclarationLocs }) {
if (
path.node.source != null &&
path.node.exportKind !== "type" &&
path.node.loc != null
) {
importDeclarationLocs.add(locToKey(path.node.loc));
}
},
Program(path, state) {
state.importDeclarationLocs = new Set();
const metroMetadata = state.file.metadata;
if (!metroMetadata.metro) {
metroMetadata.metro = {
unstable_importDeclarationLocs: state.importDeclarationLocs,
};
} else {
metroMetadata.metro.unstable_importDeclarationLocs =
state.importDeclarationLocs;
}
},
},
};
}
const MISSING_LOC = {
line: -1,
column: -1,
};
function locToKey(loc) {
const { start = MISSING_LOC, end = MISSING_LOC } = loc;
return `${start.line},${start.column}:${end.line},${end.column}`;
}

View File

@@ -0,0 +1,86 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict-local
* @format
* @oncall react_native
*/
import type {File, PluginObj} from '@babel/core';
import typeof * as Types from '@babel/types';
import type {MetroBabelFileMetadata} from 'metro-babel-transformer';
type ImportDeclarationLocs = Set<string>;
type State = {
importDeclarationLocs: ImportDeclarationLocs,
file: File,
...
};
function importLocationsPlugin({
types: t,
}: {
types: Types,
...
}): PluginObj<State> {
return {
visitor: {
ImportDeclaration(path, {importDeclarationLocs}) {
if (
// Ignore type imports
path.node.importKind !== 'type' &&
// loc may not be set if this plugin runs alongside others which
// inject imports - eg Babel runtime helpers. We don't regard these
// as source import declarations.
path.node.loc != null
) {
importDeclarationLocs.add(locToKey(path.node.loc));
}
},
ExportDeclaration(path, {importDeclarationLocs}) {
if (
// If `source` is set, this is a re-export, so it declares an ESM
// dependency.
path.node.source != null &&
// Ignore type exports
path.node.exportKind !== 'type' &&
// As above, ignore injected imports.
path.node.loc != null
) {
importDeclarationLocs.add(locToKey(path.node.loc));
}
},
Program(path, state) {
// Initialise state
state.importDeclarationLocs = new Set();
// $FlowFixMe[incompatible-type] Babel `File` is not generically typed
const metroMetadata: MetroBabelFileMetadata = state.file.metadata;
// Set the result on a metadata property
if (!metroMetadata.metro) {
metroMetadata.metro = {
unstable_importDeclarationLocs: state.importDeclarationLocs,
};
} else {
metroMetadata.metro.unstable_importDeclarationLocs =
state.importDeclarationLocs;
}
},
},
};
}
// Very simple serialisation of a source location. This should remain opaque to
// the caller.
const MISSING_LOC = {line: -1, column: -1};
function locToKey(loc: BabelSourceLocation): string {
const {start = MISSING_LOC, end = MISSING_LOC} = loc;
return `${start.line},${start.column}:${end.line},${end.column}`;
}
export {importLocationsPlugin, locToKey};

118
node_modules/metro/src/Server.d.ts generated vendored Normal file
View File

@@ -0,0 +1,118 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @oncall react_native
*/
import type {AssetData} from './Asset';
import type {RamBundleInfo} from './DeltaBundler/Serializers/getRamBundleInfo';
import type {GraphId} from './lib/getGraphId';
import type MultipartResponse from './Server/MultipartResponse';
import type {
BuildOptions,
BundleOptions,
GraphOptions,
SplitBundleOptions,
} from './shared/types';
import type {IncomingMessage, ServerResponse} from 'http';
import type {
CustomTransformOptions,
TransformProfile,
} from 'metro-babel-transformer';
import type {ConfigT, RootPerfLogger} from 'metro-config';
import type {CustomResolverOptions} from 'metro-resolver';
import IncrementalBundler, {RevisionId} from './IncrementalBundler';
export interface SegmentLoadData {
[index: number]: [number[], number | null];
}
export interface BundleMetadata {
hash: string;
otaBuildNumber: string | null;
mobileConfigs: string[];
segmentHashes: string[];
segmentLoadData: SegmentLoadData;
}
export interface ProcessStartContext extends SplitBundleOptions {
readonly buildNumber: number;
readonly bundleOptions: BundleOptions;
readonly graphId: GraphId;
readonly graphOptions: GraphOptions;
readonly mres: MultipartResponse | ServerResponse;
readonly req: IncomingMessage;
readonly revisionId?: RevisionId | null;
readonly bundlePerfLogger: RootPerfLogger;
}
export interface ProcessDeleteContext {
readonly graphId: GraphId;
readonly req: IncomingMessage;
readonly res: ServerResponse;
}
export interface ProcessEndContext<T> extends ProcessStartContext {
readonly result: T;
}
export type ServerOptions = Readonly<{
hasReducedPerformance?: boolean;
onBundleBuilt?: (bundlePath: string) => void;
watch?: boolean;
}>;
export interface DefaultGraphOptions {
customResolverOptions: CustomResolverOptions;
customTransformOptions: CustomTransformOptions;
dev: boolean;
minify: boolean;
runtimeBytecodeVersion?: number;
unstable_transformProfile: TransformProfile;
}
export interface DefaultBundleOptions extends DefaultGraphOptions {
excludeSource: false;
inlineSourceMap: false;
modulesOnly: false;
onProgress: null;
runModule: true;
shallow: false;
sourceMapUrl: null;
sourceUrl: null;
}
export default class Server {
static DEFAULT_GRAPH_OPTIONS: DefaultGraphOptions;
static DEFAULT_BUNDLE_OPTIONS: BundleOptions;
constructor(config: ConfigT, options?: ServerOptions);
end(): void;
getBundler(): IncrementalBundler;
getCreateModuleId(): (path: string) => number;
build(
bundleOptions: BundleOptions,
buildOptions?: BuildOptions,
): Promise<{
code: string;
map: string;
assets?: ReadonlyArray<AssetData>;
}>;
getRamBundleInfo(options: BundleOptions): Promise<RamBundleInfo>;
getAssets(options: BundleOptions): Promise<ReadonlyArray<AssetData>>;
getOrderedDependencyPaths(options: {
readonly dev: boolean;
readonly entryFile: string;
readonly minify: boolean;
readonly platform: string;
}): Promise<string[]>;
processRequest(
IncomingMessage: IncomingMessage,
ServerResponse: ServerResponse,
next: (e: Error | null) => unknown,
): void;
}

1355
node_modules/metro/src/Server.js generated vendored Normal file

File diff suppressed because it is too large Load Diff

1667
node_modules/metro/src/Server.js.flow generated vendored Normal file

File diff suppressed because it is too large Load Diff

31
node_modules/metro/src/Server/MultipartResponse.d.ts generated vendored Normal file
View File

@@ -0,0 +1,31 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @format
* @oncall react_native
*/
import type {IncomingMessage, ServerResponse} from 'http';
export type Data = string | Buffer | Uint8Array;
export interface Headers {
[name: string]: string | number;
}
export default class MultipartResponse {
static wrapIfSupported(
req: IncomingMessage,
res: ServerResponse,
): MultipartResponse | ServerResponse;
static serializeHeaders(headers: Headers): string;
res: ServerResponse;
headers: Headers;
constructor(res: ServerResponse);
writeChunk(headers: Headers | null, data?: Data, isLast?: boolean): void;
writeHead(status: number, headers?: Headers): void;
setHeader(name: string, value: string | number): void;
end(data?: Data): void;
}

71
node_modules/metro/src/Server/MultipartResponse.js generated vendored Normal file
View File

@@ -0,0 +1,71 @@
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true,
});
exports.default = void 0;
var _accepts = _interopRequireDefault(require("accepts"));
function _interopRequireDefault(e) {
return e && e.__esModule ? e : { default: e };
}
const CRLF = "\r\n";
const BOUNDARY = "3beqjf3apnqeu3h5jqorms4i";
class MultipartResponse {
static wrapIfSupported(req, res) {
if ((0, _accepts.default)(req).types().includes("multipart/mixed")) {
return new MultipartResponse(res);
}
return res;
}
static serializeHeaders(headers) {
return Object.keys(headers)
.map((key) => `${key}: ${headers[key]}`)
.join(CRLF);
}
constructor(res) {
this.res = res;
this.headers = {};
res.writeHead(200, {
"Content-Type": `multipart/mixed; boundary="${BOUNDARY}"`,
});
res.write(
"If you are seeing this, your client does not support multipart response",
);
}
writeChunk(headers, data, isLast = false) {
if (this.res.finished) {
return;
}
this.res.write(`${CRLF}--${BOUNDARY}${CRLF}`);
if (headers) {
this.res.write(MultipartResponse.serializeHeaders(headers) + CRLF + CRLF);
}
if (data != null) {
this.res.write(data);
}
if (isLast) {
this.res.write(`${CRLF}--${BOUNDARY}--${CRLF}`);
}
}
writeHead(status, headers) {
this.setHeader("X-Http-Status", status);
if (!headers) {
return;
}
for (const key in headers) {
this.setHeader(key, headers[key]);
}
}
setHeader(name, value) {
this.headers[name] = value;
}
end(data) {
this.writeChunk(this.headers, data, true);
this.res.end();
}
once(name, fn) {
this.res.once(name, fn);
return this;
}
}
exports.default = MultipartResponse;

101
node_modules/metro/src/Server/MultipartResponse.js.flow generated vendored Normal file
View File

@@ -0,0 +1,101 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @flow strict
* @format
* @oncall react_native
*/
import type {IncomingMessage, ServerResponse} from 'http';
import accepts from 'accepts';
const CRLF = '\r\n';
const BOUNDARY = '3beqjf3apnqeu3h5jqorms4i';
type Data = string | Buffer | Uint8Array;
type Headers = {[string]: string | number};
export default class MultipartResponse {
static wrapIfSupported(
req: IncomingMessage,
res: ServerResponse,
): MultipartResponse | ServerResponse {
if (accepts(req).types().includes('multipart/mixed')) {
return new MultipartResponse(res);
}
return res;
}
static serializeHeaders(headers: Headers): string {
return Object.keys(headers)
.map(key => `${key}: ${headers[key]}`)
.join(CRLF);
}
res: ServerResponse;
headers: Headers;
constructor(res: ServerResponse) {
this.res = res;
this.headers = {};
res.writeHead(200, {
'Content-Type': `multipart/mixed; boundary="${BOUNDARY}"`,
});
res.write(
'If you are seeing this, your client does not support multipart response',
);
}
writeChunk(
headers: Headers | null,
data?: Data,
isLast?: boolean = false,
): void {
if (this.res.finished) {
return;
}
this.res.write(`${CRLF}--${BOUNDARY}${CRLF}`);
if (headers) {
this.res.write(MultipartResponse.serializeHeaders(headers) + CRLF + CRLF);
}
if (data != null) {
this.res.write(data);
}
if (isLast) {
this.res.write(`${CRLF}--${BOUNDARY}--${CRLF}`);
}
}
writeHead(status: number, headers?: Headers): void {
// We can't actually change the response HTTP status code
// because the headers have already been sent
this.setHeader('X-Http-Status', status);
if (!headers) {
return;
}
for (const key in headers) {
this.setHeader(key, headers[key]);
}
}
setHeader(name: string, value: string | number): void {
this.headers[name] = value;
}
end(data?: Data): void {
this.writeChunk(this.headers, data, true);
this.res.end();
}
once(name: string, fn: () => mixed): this {
this.res.once(name, fn);
return this;
}
}

Some files were not shown because too many files have changed in this diff Show More