Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
569ec8b
fix: Add ESM unwrapping to all CommonJS require calls (#1675)
kitten May 8, 2026
551e87f
Bump @babel/plugin-transform-modules-systemjs to fix CVE-2026-44728 (…
CalixTang May 11, 2026
5189343
fix(metro-file-map): Fix Windows cross-drive path handling (#1711)
kitten May 12, 2026
cc430fe
Resolver tests: Refactor to test observable "browser"-spec redirectio…
robhogan May 29, 2026
b147128
Resolver: Refactor to avoid redundant categorisation of specifier, li…
robhogan Jun 1, 2026
a14f0cf
Resolver: Avoid intermediate allocations in matchSubpathFromMainField…
robhogan Jun 1, 2026
e1d2de4
Resolver: Gracefully reject illegal browser-spec redirects of bare sp…
robhogan Jun 1, 2026
5eefeed
Resolver: Fix browser-spec bare-specifier->relative path redirect for…
robhogan Jun 2, 2026
7c64a9b
Resolver: Implement PACKAGE_SELF_RESOLVE for self-referencing imports…
robhogan Jun 2, 2026
037eb13
Fix HttpStore write retries using the read endpoint's config (#1751)
durvesh1992 Jun 27, 2026
5aca287
Fix mergeConfig dropping state when a config in the chain is async (#…
durvesh1992 Jul 6, 2026
1f7315d
Stop publishing integration_tests, __fixtures__, etc to npm (#1769)
robhogan Jul 9, 2026
6872323
Flow: Fix `@babel/types` libdef for `getBindingIdentifiers` (#1770)
robhogan Jul 9, 2026
2b8b084
Add namespace re-export support to import-export plugin (#1768)
robhogan Jul 9, 2026
f8e9c2f
experimentalImportSupport: Fix exported names under relabelling and r…
robhogan Jul 9, 2026
d3f8dfe
experimentalImportSupport: Place export-all assignments before explic…
robhogan Jul 10, 2026
7ccb568
Hoist re-export dependencies before module body (#1774)
robhogan Jul 10, 2026
47f2d13
experimentalImportSupport: Fix `export * from` incorrectly re-exporti…
robhogan Jul 18, 2026
9c66034
Variable naming: clarify what "filename" is
robhogan Aug 5, 2026
d7106e5
Runtime: Materialise lazily-registered modules during Fast Refresh
robhogan Aug 14, 2026
6889e7c
Drop ci-info dependency, respect process.env.CI directly for watch de…
robhogan Aug 17, 2026
0ee53c1
Babel preset: Make Platform inlining opt-in via inlinePlatform (defau…
robhogan Aug 17, 2026
31488ee
Handle request socket errors when writing to HttpStore
Aug 25, 2026
d00434b
Preserve Platform write targets during inlining (#1890)
OskarEichler Sep 4, 2026
f8f0370
Server: Add a charset to Content-Type for text assets and sources (#1…
robhogan Sep 4, 2026
df380ed
Inline the last duplicate Platform.select key (#1889)
OskarEichler Sep 4, 2026
7590c52
metro-file-map: Drop `walker` dependency from `FallbackWatcher`, redu…
robhogan Sep 7, 2026
d5b387a
metro-file-map: Watch directories before listing them in fallback (Li…
robhogan Sep 9, 2026
e6cd576
Fix mismatched asset paths in builds when using `unstable_serverRoot`…
robhogan Sep 15, 2026
a549769
Propagate Bundler transformer initialization errors (#1854)
Boulea7 Sep 23, 2026
dad7f0c
Fix RAM bundle passing the deps function (not its result) to getTrans…
durvesh1992 Sep 23, 2026
8b075d0
Fix MockPlugin clobbering the active mock when a non-active duplicate…
durvesh1992 Sep 24, 2026
8a4eb52
metro-file-map: Fix missed changes and a stop hang in FallbackWatcher…
robhogan Sep 25, 2026
f9ad6bf
Fix SVG dimension parsing treating stroke-width as width (#1972)
GabrielDumbrava Sep 25, 2026
0c33ad6
metro-file-map: Identify TreeFS directory nodes by shape, not realm (…
robhogan Sep 25, 2026
c1e7fe4
NativeWatcher: Emit events in the order fs.watch reported them - fix …
robhogan Sep 25, 2026
a162cac
Fix section offsets in generatedMappings() for indexed source maps (#…
kwy404 Sep 26, 2026
5939588
Fix exact "exports" and "imports" keys with no target falling back to…
kwy404 Sep 27, 2026
f349f54
metro-symbolicate: Don't throw on stack frames with no source positio…
robhogan Sep 28, 2026
70c5362
Release workers and watchers after failed Bundler initialisation (#1963)
robhogan Sep 28, 2026
21417b5
Consumer: Don't decode every source map segment twice (#1978)
robhogan Sep 28, 2026
50f4b97
fix: skip errors that occurred on virtual lines (#1226)
EvanBacon Sep 29, 2026
3c975b0
Fix backslashes in async bundle paths on Windows (#1938)
robhogan Oct 15, 2026
8e975d6
Use indexed watch folder paths for external assets (#1892)
robhogan Sep 29, 2026
2d7aa4e
[0.84] Publish 0.84.7
robhogan Oct 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 19 additions & 4 deletions docs/Resolution.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,18 +75,21 @@ Parameters: (*context*, *moduleName*, *platform*)
2. If resolved as a Haste package path, then
1. Perform the algorithm for resolving a path (step 2 above). Throw an error if this resolution fails.
For example, if the Haste package path for `'a/b'` is `foo/package.json`, perform step 2 as if _moduleName_ was `foo/c`.
6. If [`context.disableHierarchicalLookup`](#disableHierarchicalLookup-boolean) is not `true`, then
6. If [`context.enablePackageExports`](#enablepackageexports-boolean) is enabled, then
1. Get the result of [**PACKAGE_SELF_RESOLVE**](#package_self_resolve)(*context*, *moduleName*, *platform*).
2. If resolved, return result.
7. If [`context.disableHierarchicalLookup`](#disableHierarchicalLookup-boolean) is not `true`, then
1. Try resolving _moduleName_ under `node_modules` from the current directory (i.e. parent of [`context.originModulePath`](#originmodulepath-string)) up to the root directory.
2. Perform [**RESOLVE_PACKAGE**](#resolve_package)(*context*, *modulePath*, *platform*) for each candidate path.
7. For each element _nodeModulesPath_ of [`context.nodeModulesPaths`](#nodemodulespaths-readonlyarraystring):
8. For each element _nodeModulesPath_ of [`context.nodeModulesPaths`](#nodemodulespaths-readonlyarraystring):
1. Try resolving _moduleName_ under _nodeModulesPath_ as if the latter was another `node_modules` directory (similar to step 5 above).
2. Perform [**RESOLVE_PACKAGE**](#resolve_package)(*context*, *modulePath*, *platform*) for each candidate path.
8. If [`context.extraNodeModules`](#extranodemodules-string-string) is set:
9. If [`context.extraNodeModules`](#extranodemodules-string-string) is set:
1. Split _moduleName_ into a package name (including an optional [scope](https://docs.npmjs.com/cli/v8/using-npm/scope)) and relative path.
2. Look up the package name in [`context.extraNodeModules`](#extranodemodules-string-string). If found, then
1. Construct a path _modulePath_ by replacing the package name part of _moduleName_ with the value found in [`context.extraNodeModules`](#extranodemodules-string-string)
2. Return the result of [**RESOLVE_PACKAGE**](#resolve_package)(*context*, *modulePath*, *platform*).
9. If no valid resolution has been found, throw a resolution failure error.
10. If no valid resolution has been found, throw a resolution failure error.

#### RESOLVE_MODULE

Expand All @@ -110,6 +113,18 @@ Parameters: (*context*, *moduleName*, *platform*)
2. Else, log either a package configuration or package encapsulation warning.
2. Return the result of [**RESOLVE_MODULE**](#resolve_module)(*context*, *filePath*, *platform*).

#### PACKAGE_SELF_RESOLVE

Parameters: (*context*, *moduleName*, *platform*)

1. Find the closest package to [`context.originModulePath`](#originmodulepath-string). If no package is found, return no resolution.
2. If the package does not declare both a `"name"` field and an `"exports"` field, return no resolution.
3. Split _moduleName_ into a package name (including an optional [scope](https://docs.npmjs.com/cli/v8/using-npm/scope)) and relative path.
4. If the package name does not match the current package's `"name"` field, return no resolution.
5. Get the result of [**RESOLVE_PACKAGE_EXPORTS**](#resolve_package-exports)(*context*, *packagePath*, *filePath*, *exportsField*, *platform*) against the current package's `"exports"` field.
1. If resolved path exists, return result.
2. Else, log either a package configuration or package encapsulation warning, then continue with normal resolution.

#### RESOLVE_PACKAGE_EXPORTS

Parameters: (*context*, *packagePath*, *filePath*, *exportsField*, *platform*)
Expand Down
4 changes: 2 additions & 2 deletions flow-typed/npm/babel-types_v7.x.x.js
Original file line number Diff line number Diff line change
Expand Up @@ -3848,8 +3848,8 @@ declare module "@babel/types" {
declare export function prependToMemberExpression(member: BabelNodeMemberExpression, prepend: BabelNodeExpression): BabelNodeMemberExpression
declare export function removeProperties<T>(n: T, opts: ?{}): void;
declare export function removePropertiesDeep<T>(n: T, opts: ?{}): T;
declare export function getBindingIdentifiers(node: BabelNode, duplicates: boolean, outerOnly?: boolean): { [key: string]: BabelNodeIdentifier | Array<BabelNodeIdentifier> }
declare export function getOuterBindingIdentifiers(node: Node, duplicates: boolean): { [key: string]: BabelNodeIdentifier | Array<BabelNodeIdentifier> }
declare export function getBindingIdentifiers(node: BabelNode, duplicates?: boolean, outerOnly?: boolean): { [key: string]: BabelNodeIdentifier | Array<BabelNodeIdentifier> }
declare export function getOuterBindingIdentifiers(node: Node, duplicates?: boolean): { [key: string]: BabelNodeIdentifier | Array<BabelNodeIdentifier> }
declare export type TraversalAncestors = Array<{
node: BabelNode,
key: string,
Expand Down
2 changes: 1 addition & 1 deletion packages/buck-worker-tool/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "buck-worker-tool",
"version": "0.84.6",
"version": "0.84.7",
"description": "Implementation of the Buck worker protocol for Node.js.",
"license": "MIT",
"repository": {
Expand Down
2 changes: 1 addition & 1 deletion packages/metro-babel-register/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "metro-babel-register",
"version": "0.84.6",
"version": "0.84.7",
"description": "🚇 babel/register configuration for Metro.",
"main": "src/babel-register.js",
"exports": {
Expand Down
4 changes: 2 additions & 2 deletions packages/metro-babel-transformer/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "metro-babel-transformer",
"version": "0.84.6",
"version": "0.84.7",
"description": "🚇 Base Babel transformer for Metro.",
"main": "src/index.js",
"exports": {
Expand All @@ -26,7 +26,7 @@
"@babel/core": "^7.25.2",
"flow-enums-runtime": "^0.0.6",
"hermes-parser": "0.35.0",
"metro-cache-key": "0.84.6",
"metro-cache-key": "0.84.7",
"nullthrows": "^1.1.1"
},
"engines": {
Expand Down
8 changes: 7 additions & 1 deletion packages/metro-babel-transformer/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ type BabelTransformerOptions = Readonly<{
extendsBabelConfigPath?: string,
experimentalImportSupport?: boolean,
hermesParser?: boolean,
inlinePlatform?: boolean,
minify: boolean,
platform: ?string,
projectRoot: string,
Expand Down Expand Up @@ -105,7 +106,12 @@ function transform(
const babelConfig: BabelTransformOptions = {
ast: true,
babelrc: options.enableBabelRCLookup,
caller: {bundler: 'metro', name: 'metro', platform: options.platform},
caller: {
bundler: 'metro',
name: 'metro',
platform: options.platform,
inlinePlatform: options.inlinePlatform,
},
// NOTE(EvanBacon): We split the parse/transform steps up to accommodate
// Hermes parsing, but this defaults to cloning the AST which increases
// the transformation time by a fair amount.
Expand Down
3 changes: 2 additions & 1 deletion packages/metro-babel-transformer/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
*
* @noformat
* @oncall react_native
* @generated SignedSource<<2503f88efd80b39e4d860870017af66d>>
* @generated SignedSource<<d2abc246f3316d666e3344863aa9f844>>
*
* This file was translated from Flow by scripts/generateTypeScriptDefinitions.js
* Original file: packages/metro-babel-transformer/src/index.js
Expand Down Expand Up @@ -35,6 +35,7 @@ type BabelTransformerOptions = Readonly<{
extendsBabelConfigPath?: string;
experimentalImportSupport?: boolean;
hermesParser?: boolean;
inlinePlatform?: boolean;
minify: boolean;
platform: null | undefined | string;
projectRoot: string;
Expand Down
2 changes: 1 addition & 1 deletion packages/metro-cache-key/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "metro-cache-key",
"version": "0.84.6",
"version": "0.84.7",
"description": "🚇 Cache key utility.",
"main": "src/index.js",
"exports": {
Expand Down
4 changes: 2 additions & 2 deletions packages/metro-cache/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "metro-cache",
"version": "0.84.6",
"version": "0.84.7",
"description": "🚇 Cache layers for Metro.",
"main": "src/index.js",
"exports": {
Expand All @@ -21,7 +21,7 @@
"exponential-backoff": "^3.1.1",
"flow-enums-runtime": "^0.0.6",
"https-proxy-agent": "^7.0.5",
"metro-core": "0.84.6"
"metro-core": "0.84.7"
},
"devDependencies": {
"memfs": "^4.38.2"
Expand Down
20 changes: 15 additions & 5 deletions packages/metro-cache/src/stores/HttpStore.js
Original file line number Diff line number Diff line change
Expand Up @@ -369,10 +369,22 @@ export default class HttpStore<T> {
res.resume();
});

// Without this listener a socket-level failure mid-upload (e.g. the peer
// resetting a pooled keep-alive connection) is emitted as an unhandled
// 'error' and takes down the whole process instead of rejecting, which
// also makes #withRetries unable to retry the write.
req.on('error', err => {
reject(new NetworkError(err.message, err.code));
});

req.on('timeout', () => {
req.destroy(new Error('Request timed out'));
});

gzip.on('error', err => {
reject(err);
});

gzip.pipe(req);

if (value instanceof Buffer) {
Expand All @@ -396,14 +408,12 @@ export default class HttpStore<T> {
return backOff(fn, {
jitter: 'full',
maxDelay: 30000,
numOfAttempts: this.#getEndpoint.maxAttempts || Number.POSITIVE_INFINITY,
numOfAttempts: endpoint.maxAttempts || Number.POSITIVE_INFINITY,
retry: (e: Error) => {
if (e instanceof HttpError) {
return this.#getEndpoint.retryStatuses.has(e.code);
return endpoint.retryStatuses.has(e.code);
}
return (
e instanceof NetworkError && this.#getEndpoint.retryNetworkErrors
);
return e instanceof NetworkError && endpoint.retryNetworkErrors;
},
});
}
Expand Down
83 changes: 83 additions & 0 deletions packages/metro-cache/src/stores/__tests__/HttpStore-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,39 @@ describe('HttpStore', () => {
});
});

test('set() retries use the set endpoint config, not the get endpoint', async () => {
jest.useRealTimers();
// Distinct read/write retry config: reads do not retry, writes retry 503s.
const store = new HttpStore({
getOptions: {endpoint: 'http://example.com', maxAttempts: 1},
setOptions: {
endpoint: 'http://example.com',
maxAttempts: 2,
retryStatuses: [503],
},
});
const {request} = require('http');

request.mockImplementation((opts, callback) => {
if (request.mock.calls.length === 1) {
callback(responseHttpError(503));
} else {
callback(responseHttpOk(''));
}
return new PassThrough();
});

let error;
try {
await store.set(Buffer.from('key-set'), {foo: 42});
} catch (e) {
error = e;
}

expect(error).toBeUndefined();
expect(request).toHaveBeenCalledTimes(2);
});

test('sets using the network via PUT method', done => {
const store = new HttpStore({endpoint: 'http://www.example.com/endpoint'});
const promise = store.set(Buffer.from('key-set'), {foo: 42});
Expand Down Expand Up @@ -332,6 +365,56 @@ describe('HttpStore', () => {
});
});

test('rejects with a NetworkError when the set request socket errors', async () => {
const store = new HttpStore({
endpoint: 'http://example.com',
maxAttempts: 1,
});
const promise = store.set(Buffer.from('key-set'), {foo: 42});
const [opts] = require('http').request.mock.calls[0];

expect(opts.method).toEqual('PUT');

const err = new Error('read ECONNRESET');
err.code = 'ECONNRESET';
httpPassThrough.emit('error', err);

await expect(promise).rejects.toMatchObject({
constructor: HttpStore.NetworkError,
code: 'ECONNRESET',
message: 'read ECONNRESET',
});
});

test('set() retries a request socket error when retryNetworkErrors is set', async () => {
jest.useRealTimers();
const store = new HttpStore({
endpoint: 'http://example.com',
maxAttempts: 2,
retryNetworkErrors: true,
});
const {request} = require('http');

request.mockImplementation((opts, callback) => {
const req = new PassThrough();

if (request.mock.calls.length === 1) {
const err = new Error('read ECONNRESET');
err.code = 'ECONNRESET';
process.nextTick(() => req.emit('error', err));
} else {
callback(responseHttpOk(''));
req.resume();
}

return req;
});

await store.set(Buffer.from('key-set'), {foo: 42});

expect(request).toHaveBeenCalledTimes(2);
});

test('set() resolves when the HTTP code is in additionalSuccessStatuses', done => {
const store = new HttpStore({
endpoint: 'http://www.example.com/endpoint',
Expand Down
12 changes: 6 additions & 6 deletions packages/metro-config/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "metro-config",
"version": "0.84.6",
"version": "0.84.7",
"description": "🚇 Config parser for Metro.",
"main": "src/index.js",
"exports": {
Expand All @@ -22,15 +22,15 @@
"connect": "^3.6.5",
"flow-enums-runtime": "^0.0.6",
"jest-validate": "^29.7.0",
"metro": "0.84.6",
"metro-cache": "0.84.6",
"metro-core": "0.84.6",
"metro-runtime": "0.84.6",
"metro": "0.84.7",
"metro-cache": "0.84.7",
"metro-core": "0.84.7",
"metro-runtime": "0.84.7",
"yaml": "^2.6.1"
},
"devDependencies": {
"@types/connect": "^3.4.35",
"metro-babel-register": "0.84.6",
"metro-babel-register": "0.84.7",
"pretty-format": "^29.7.0"
},
"engines": {
Expand Down
14 changes: 14 additions & 0 deletions packages/metro-config/src/__tests__/mergeConfig-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,20 @@ describe('mergeConfig', () => {
});
});

test('applies trailing overrides after an async config function', async () => {
const base: InputConfigT = {server: {port: 8081}};
const asyncOverride = (): Promise<InputConfigT> =>
Promise.resolve({transformer: {assetPlugins: ['async-plugin']}});
const trailing: InputConfigT = {resolver: {sourceExts: ['ts']}};

const result = await mergeConfig(base, asyncOverride, trailing);

// The base and every config in the chain must survive the async branch.
expect(result.server?.port).toBe(8081);
expect(result.transformer?.assetPlugins).toEqual(['async-plugin']);
expect(result.resolver?.sourceExts).toEqual(['ts']);
});

describe('server.tls merging', () => {
describe('override IS applied when tls is false or object', () => {
test('override tls: object replaces base tls: false', () => {
Expand Down
7 changes: 6 additions & 1 deletion packages/metro-config/src/loadConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,12 @@ function mergeConfig<
typeof next === 'function' ? next(currentConfig) : next;
if (nextConfig instanceof Promise) {
// $FlowFixMe[incompatible-type] Not clear why Flow doesn't like this
return mergeConfigAsync(nextConfig, reversedConfigs.toReversed());
return mergeConfigAsync(
nextConfig.then(resolved =>
mergeConfigObjects(currentConfig, resolved),
),
...reversedConfigs.toReversed(),
);
}
currentConfig = mergeConfigObjects(currentConfig, nextConfig) as T;
}
Expand Down
4 changes: 2 additions & 2 deletions packages/metro-core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "metro-core",
"version": "0.84.6",
"version": "0.84.7",
"description": "🚇 Metro's core package.",
"main": "src/index.js",
"exports": {
Expand All @@ -20,7 +20,7 @@
"dependencies": {
"flow-enums-runtime": "^0.0.6",
"lodash.throttle": "^4.1.1",
"metro-resolver": "0.84.6"
"metro-resolver": "0.84.7"
},
"license": "MIT",
"engines": {
Expand Down
5 changes: 2 additions & 3 deletions packages/metro-file-map/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "metro-file-map",
"version": "0.84.6",
"version": "0.84.7",
"description": "[Experimental] - 🚇 File crawling, watching and mapping for Metro",
"main": "src/index.js",
"exports": {
Expand All @@ -26,8 +26,7 @@
"invariant": "^2.2.4",
"jest-worker": "^29.7.0",
"micromatch": "^4.0.4",
"nullthrows": "^1.1.1",
"walker": "^1.0.7"
"nullthrows": "^1.1.1"
},
"devDependencies": {
"slash": "^3.0.0"
Expand Down
Loading
Loading