diff --git a/docs/ModuleAPI.md b/docs/ModuleAPI.md index 307860efbf..01b08d8b73 100644 --- a/docs/ModuleAPI.md +++ b/docs/ModuleAPI.md @@ -57,7 +57,7 @@ In React Native projects that use `@react-native/babel-preset`, `import` and `ex 1. Enable [lazy bundling](https://github.com/react-native-community/discussions-and-proposals/blob/main/proposals/0605-lazy-bundling.md) by adding `&lazy=true` to the initial HTTP bundle URL your framework requests from Metro. 2. At runtime, `import()` calls a framework-defined function to [fetch and evaluate](https://github.com/react-native-community/discussions-and-proposals/blob/main/proposals/0605-lazy-bundling.md#__loadbundleasync-in-metro) the split bundle. Your framework **must** implement this function if it uses the `lazy=true` parameter, or runtime errors will occur. - For example, if the initial bundle URL is `/index.bundle?platform=ios&dev=true&lazy=true`, then `import('./src/Settings')` calls `global.__loadBundleAsync('/src/Settings.bundle?platform=ios&dev=true&lazy=true&modulesOnly=true&runModule=false')` before evaluating the module. + For example, if the initial bundle URL is `/index.bundle?platform=ios&dev=true&lazy=true`, then `import('./src/Settings')`, resolving to `src/Settings.js`, calls `global.__loadBundleAsync('/src/Settings.js.bundle?platform=ios&dev=true&lazy=true&modulesOnly=true&runModule=false')` before evaluating the module. ::: diff --git a/packages/metro/src/DeltaBundler/Serializers/__tests__/baseJSBundleWithLazyModuleDefinition-test.js b/packages/metro/src/DeltaBundler/Serializers/__tests__/baseJSBundleWithLazyModuleDefinition-test.js index 593e2187b5..f42ed43f74 100644 --- a/packages/metro/src/DeltaBundler/Serializers/__tests__/baseJSBundleWithLazyModuleDefinition-test.js +++ b/packages/metro/src/DeltaBundler/Serializers/__tests__/baseJSBundleWithLazyModuleDefinition-test.js @@ -208,6 +208,62 @@ test('modulesOnly omits the eager polyfills but keeps the segment', () => { expect(code).toContain('case 0:'); }); +test('async paths carry the platform of the bundle', () => { + const lazyFooModule: Module<> = { + ...fooModule, + dependencies: new Map([ + [ + './bar', + { + absolutePath: '/root/bar.ios.js', + data: { + data: { + asyncType: 'async', + isESMImport: false, + locs: [], + key: './bar', + }, + name: './bar', + }, + }, + ], + ]), + }; + const {code} = baseJSBundleWithLazyModuleDefinition( + '/root/foo', + [], + { + dependencies: new Map([['/root/foo', lazyFooModule]]), + entryPoints: new Set(['/root/foo']), + transformOptions: {...transformOptions, platform: 'ios'}, + }, + { + asyncRequireModulePath: '', + createModuleId: (filePath: string) => (filePath === '/root/foo' ? 0 : 1), + dev: true, + getRunModuleStatement: (moduleId: number | string) => + `require(${JSON.stringify(moduleId)});`, + globalPrefix: '', + includeAsyncPaths: true, + inlineSourceMap: false, + modulesOnly: false, + processModuleFilter: () => true, + projectRoot: '/root', + reservedString: '_$$_METRO_', + runBeforeMainModule: [], + runModule: true, + serverRoot: '/root', + shouldAddToIgnoreList: () => false, + sourceMapUrl: 'http://localhost/bundle.map', + sourceUrl: 'http://localhost/foo.ios.bundle?lazy=true', + getSourceUrl: null, + }, + ); + expect(code).toContain( + '"paths":{"1":"/bar.ios.js.bundle?lazy=true&platform=ios&modulesOnly=true&runModule=false"}', + ); +}); + test('prefixes the segment definer with the global prefix', () => { const {code} = serialize('app1'); diff --git a/packages/metro/src/DeltaBundler/Serializers/__tests__/hmrJSBundle-test.js b/packages/metro/src/DeltaBundler/Serializers/__tests__/hmrJSBundle-test.js index 46683c21c4..4ee881276d 100644 --- a/packages/metro/src/DeltaBundler/Serializers/__tests__/hmrJSBundle-test.js +++ b/packages/metro/src/DeltaBundler/Serializers/__tests__/hmrJSBundle-test.js @@ -14,7 +14,9 @@ import type {Module, ReadOnlyGraph, TransformInputOptions} from '../../types'; import CountingSet from '../../../lib/CountingSet'; +import parseBundleOptionsFromBundleRequestUrl from '../../../lib/parseBundleOptionsFromBundleRequestUrl'; import hmrJSBundle from '../hmrJSBundle'; +import * as jscSafeUrl from 'jsc-safe-url'; const fooModule: Module<> = { path: '/root/foo', @@ -124,12 +126,12 @@ Object { "module": Array [ 1, "__d(function() {/* code for foo */},1,[0],\\"foo\\",{}); -//# sourceMappingURL=http://localhost/foo.map -//# sourceURL=http://localhost/foo.bundle +//# sourceMappingURL=http://localhost/foo.map?platform=web +//# sourceURL=http://localhost/foo.bundle//&platform=web ", ], - "sourceMappingURL": "http://localhost/foo.map", - "sourceURL": "http://localhost/foo.bundle", + "sourceMappingURL": "http://localhost/foo.map?platform=web", + "sourceURL": "http://localhost/foo.bundle//&platform=web", }, ], "deleted": Array [], @@ -138,12 +140,12 @@ Object { "module": Array [ 0, "__d(function() {/* code for bar */},0,null,\\"bar\\",{}); -//# sourceMappingURL=http://localhost/bar.map -//# sourceURL=http://localhost/bar.bundle +//# sourceMappingURL=http://localhost/bar.map?platform=web +//# sourceURL=http://localhost/bar.bundle//&platform=web ", ], - "sourceMappingURL": "http://localhost/bar.map", - "sourceURL": "http://localhost/bar.bundle", + "sourceMappingURL": "http://localhost/bar.map?platform=web", + "sourceURL": "http://localhost/bar.bundle//&platform=web", }, ], } @@ -173,12 +175,12 @@ Object { "module": Array [ 2, "__d(function() {/* code for ascii file with non ascii characters: %30.бундл.Øಚ😁AA */},2,null,\\"%30.бундл.Øಚ😁AA/src/?/foo=bar/#.js\\",{}); -//# sourceMappingURL=http://localhost/%2530.%D0%B1%D1%83%D0%BD%D0%B4%D0%BB.%C3%98%E0%B2%9A%F0%9F%98%81AA/src/%3F/foo%3Dbar/%23.map -//# sourceURL=http://localhost/%2530.%D0%B1%D1%83%D0%BD%D0%B4%D0%BB.%C3%98%E0%B2%9A%F0%9F%98%81AA/src/%3F/foo%3Dbar/%23.bundle +//# sourceMappingURL=http://localhost/%2530.%D0%B1%D1%83%D0%BD%D0%B4%D0%BB.%C3%98%E0%B2%9A%F0%9F%98%81AA/src/%3F/foo%3Dbar/%23.js.map?platform=web +//# sourceURL=http://localhost/%2530.%D0%B1%D1%83%D0%BD%D0%B4%D0%BB.%C3%98%E0%B2%9A%F0%9F%98%81AA/src/%3F/foo%3Dbar/%23.js.bundle//&platform=web ", ], - "sourceMappingURL": "http://localhost/%2530.%D0%B1%D1%83%D0%BD%D0%B4%D0%BB.%C3%98%E0%B2%9A%F0%9F%98%81AA/src/%3F/foo%3Dbar/%23.map", - "sourceURL": "http://localhost/%2530.%D0%B1%D1%83%D0%BD%D0%B4%D0%BB.%C3%98%E0%B2%9A%F0%9F%98%81AA/src/%3F/foo%3Dbar/%23.bundle", + "sourceMappingURL": "http://localhost/%2530.%D0%B1%D1%83%D0%BD%D0%B4%D0%BB.%C3%98%E0%B2%9A%F0%9F%98%81AA/src/%3F/foo%3Dbar/%23.js.map?platform=web", + "sourceURL": "http://localhost/%2530.%D0%B1%D1%83%D0%BD%D0%B4%D0%BB.%C3%98%E0%B2%9A%F0%9F%98%81AA/src/%3F/foo%3Dbar/%23.js.bundle//&platform=web", }, ], } @@ -207,10 +209,90 @@ test('gives a virtual module anchored at a real file URLs distinct from that fil expect( added.map(({sourceURL, sourceMappingURL}) => [sourceURL, sourceMappingURL]), ).toEqual([ - ['http://localhost/foo.bundle', 'http://localhost/foo.map'], [ - 'http://localhost/foo.bundle//&virtual=true', - 'http://localhost/foo.map?virtual=true', + 'http://localhost/foo.bundle//&platform=web', + 'http://localhost/foo.map?platform=web', + ], + [ + 'http://localhost/foo.bundle//&platform=web&virtual=true', + 'http://localhost/foo.map?platform=web&virtual=true', ], ]); }); + +test('module URLs resolve back to the exact file and platform', () => { + const cjsModule: Module<> = { + ...barModule, + path: '/root/dual.ios.cjs', + inverseDependencies: new CountingSet(), + }; + const {added} = hmrJSBundle( + { + added: new Map([['/root/dual.ios.cjs', cjsModule]]), + modified: new Map(), + deleted: new Set(), + reset: false, + }, + { + entryPoints: new Set(['/root/dual.ios.cjs']), + dependencies: new Map([['/root/dual.ios.cjs', cjsModule]]), + transformOptions: {...transformOptions, platform: 'ios'}, + }, + {...options, clientUrl: new URL('http://localhost/index.ios.bundle')}, + ); + // The server must find `dual.ios.cjs`, not a sibling such as `dual.js` + // found through `sourceExts`, and build it for the same platform. + for (const url of [added[0].sourceURL, added[0].sourceMappingURL]) { + expect( + parseBundleOptionsFromBundleRequestUrl( + jscSafeUrl.toNormalUrl(url), + new Set(['ios']), + ), + ).toMatchObject({entryFile: './dual.ios.cjs', platform: 'ios'}); + } +}); + +test('async paths in updated modules carry the platform of the bundle', () => { + const lazyModule: Module<> = { + ...fooModule, + path: '/root/lazy.ios.js', + dependencies: new Map([ + [ + './chunk', + { + absolutePath: '/root/chunk.ios.js', + data: { + data: { + asyncType: 'async', + isESMImport: false, + locs: [], + key: './chunk', + }, + name: './chunk', + }, + }, + ], + ]), + }; + const {added} = hmrJSBundle( + { + added: new Map([['/root/lazy.ios.js', lazyModule]]), + modified: new Map(), + deleted: new Set(), + reset: false, + }, + { + entryPoints: new Set(['/root/lazy.ios.js']), + dependencies: new Map([['/root/lazy.ios.js', lazyModule]]), + transformOptions: {...transformOptions, platform: 'ios'}, + }, + { + ...options, + clientUrl: new URL('http://localhost/index.ios.bundle?lazy=true'), + includeAsyncPaths: true, + }, + ); + expect(added[0].module[1]).toContain( + '"paths":{"0":"/chunk.ios.js.bundle?lazy=true&platform=ios&modulesOnly=true&runModule=false"}', + ); +}); diff --git a/packages/metro/src/DeltaBundler/Serializers/baseJSBundle.js b/packages/metro/src/DeltaBundler/Serializers/baseJSBundle.js index 7bae6fa6d1..b3c5265d2f 100644 --- a/packages/metro/src/DeltaBundler/Serializers/baseJSBundle.js +++ b/packages/metro/src/DeltaBundler/Serializers/baseJSBundle.js @@ -35,6 +35,7 @@ export default function baseJSBundle( createModuleId: options.createModuleId, dev: options.dev, includeAsyncPaths: options.includeAsyncPaths, + platform: graph.transformOptions.platform, projectRoot: options.projectRoot, serverRoot: options.serverRoot, sourceUrl: options.sourceUrl, diff --git a/packages/metro/src/DeltaBundler/Serializers/baseJSBundleWithLazyModuleDefinition.js b/packages/metro/src/DeltaBundler/Serializers/baseJSBundleWithLazyModuleDefinition.js index 71ba7aeb9b..38e16b052b 100644 --- a/packages/metro/src/DeltaBundler/Serializers/baseJSBundleWithLazyModuleDefinition.js +++ b/packages/metro/src/DeltaBundler/Serializers/baseJSBundleWithLazyModuleDefinition.js @@ -64,6 +64,7 @@ export default function baseJSBundleWithLazyModuleDefinition( createModuleId: options.createModuleId, dev: options.dev, includeAsyncPaths: options.includeAsyncPaths, + platform: graph.transformOptions.platform, projectRoot: options.projectRoot, serverRoot: options.serverRoot, sourceUrl: options.sourceUrl, diff --git a/packages/metro/src/DeltaBundler/Serializers/helpers/__tests__/js-test.js b/packages/metro/src/DeltaBundler/Serializers/helpers/__tests__/js-test.js index 648ae7a1c9..82d72235a0 100644 --- a/packages/metro/src/DeltaBundler/Serializers/helpers/__tests__/js-test.js +++ b/packages/metro/src/DeltaBundler/Serializers/helpers/__tests__/js-test.js @@ -13,6 +13,7 @@ import type {Dependency} from '../../../types'; import {createPathNormalizer} from '../../../__tests__/test-utils'; import CountingSet from '../../../../lib/CountingSet'; +import parseBundleOptionsFromBundleRequestUrl from '../../../../lib/parseBundleOptionsFromBundleRequestUrl'; import {inlineModuleIdReferences, wrapModule} from '../js'; import {wrap as raw} from 'jest-snapshot-serializer-raw'; import createModuleIdFactory from 'metro-config/private/defaults/createModuleIdFactory'; @@ -152,7 +153,7 @@ describe('wrapModule()', () => { }), ), ).toMatchInlineSnapshot( - `__d(function() { console.log("foo") },0,{"0":1,"1":2,"paths":{"1":"/../bar.bundle?param1=true¶m2=1234&modulesOnly=true&runModule=false"}});`, + `__d(function() { console.log("foo") },0,{"0":1,"1":2,"paths":{"1":"/../bar.js.bundle?param1=true¶m2=1234&modulesOnly=true&runModule=false"}});`, ); }); @@ -195,7 +196,7 @@ describe('wrapModule()', () => { }), ), ).toMatchInlineSnapshot( - `__d(function() { console.log("foo") },0,{"0":1,"1":2,"paths":{"1":"/bar.bundle?param1=true&modulesOnly=true&runModule=false&virtual=true"}});`, + `__d(function() { console.log("foo") },0,{"0":1,"1":2,"paths":{"1":"/bar.js.bundle?param1=true&modulesOnly=true&runModule=false&virtual=true"}});`, ); }); @@ -217,10 +218,73 @@ describe('wrapModule()', () => { }), ), ).toMatchInlineSnapshot( - `__d(function() { console.log("foo") },0,{"0":1,"1":2,"paths":{"1":"/bar.bundle?param1=true¶m2=1234&modulesOnly=true&runModule=false"}});`, + `__d(function() { console.log("foo") },0,{"0":1,"1":2,"paths":{"1":"/bar.js.bundle?param1=true¶m2=1234&modulesOnly=true&runModule=false"}});`, ); }); + test('async dependency paths keep the resolved file extension', () => { + myModule.dependencies.set('bar', { + absolutePath: '/root/node_modules/bar/dist/index.cjs', + data: { + data: {asyncType: 'async', isESMImport: false, locs: [], key: 'bar'}, + name: 'bar', + }, + }); + const sourceUrl = 'http://localhost/Main.bundle?platform=ios'; + const wrapped = wrapModule(myModule, { + createModuleId: createModuleIdFactory(), + dev: false, + includeAsyncPaths: true, + projectRoot: '/root', + serverRoot: '/root', + sourceUrl, + }); + expect(raw(wrapped)).toMatchInlineSnapshot( + `__d(function() { console.log("foo") },0,{"0":1,"1":2,"paths":{"1":"/node_modules/bar/dist/index.cjs.bundle?platform=ios&modulesOnly=true&runModule=false"}});`, + ); + + // The server must resolve the chunk URL back to the same file, not to a + // sibling such as `index.js` found through `sourceExts`. + const asyncPath = nullthrows(wrapped.match(/"paths":\{"1":"([^"]+)"/))[1]; + expect( + parseBundleOptionsFromBundleRequestUrl( + 'http://localhost' + asyncPath, + new Set(['ios']), + ).entryFile, + ).toBe('./node_modules/bar/dist/index.cjs'); + }); + + test('async bundle paths carry the platform of the bundle', () => { + const dep = nullthrows(myModule.dependencies.get('bar')); + myModule.dependencies.set('bar', { + ...dep, + absolutePath: p('/root/bar.ios.js'), + data: {...dep.data, data: {...dep.data.data, asyncType: 'async'}}, + }); + const wrapped = wrapModule(myModule, { + createModuleId: createModuleIdFactory(), + dev: false, + includeAsyncPaths: true, + platform: 'ios', + projectRoot: p('/root'), + serverRoot: p('/root'), + sourceUrl: 'http://localhost/Main.ios.bundle?param1=true', + }); + expect(raw(wrapped)).toMatchInlineSnapshot( + `__d(function() { console.log("foo") },0,{"0":1,"1":2,"paths":{"1":"/bar.ios.js.bundle?param1=true&platform=ios&modulesOnly=true&runModule=false"}});`, + ); + + // The platform can't be inferred from the chunk's file name, so it must be + // in the query string. + const asyncPath = nullthrows(wrapped.match(/"paths":\{"1":"([^"]+)"/))[1]; + expect( + parseBundleOptionsFromBundleRequestUrl( + 'http://localhost' + asyncPath, + new Set(['ios']), + ).platform, + ).toBe('ios'); + }); + test('async bundle paths override modulesOnly and runModule', () => { const dep = nullthrows(myModule.dependencies.get('bar')); myModule.dependencies.set('bar', { @@ -240,7 +304,7 @@ describe('wrapModule()', () => { }), ), ).toMatchInlineSnapshot( - `__d(function() { console.log("foo") },0,{"0":1,"1":2,"paths":{"1":"/../bar.bundle?modulesOnly=true&runModule=false"}});`, + `__d(function() { console.log("foo") },0,{"0":1,"1":2,"paths":{"1":"/../bar.js.bundle?modulesOnly=true&runModule=false"}});`, ); }); @@ -364,7 +428,7 @@ describe('wrapModule() with inlined module ids', () => { `__d(function(g,r,i,a,m,e,${NAME}){r(${'1'.padEnd( ref(0).length, )});r(${'2'.padEnd(ref(1).length)})},0,` + - `{"paths":{"1":"/../bar.bundle?param1=true&modulesOnly=true&runModule=false"}});`, + `{"paths":{"1":"/../bar.js.bundle?param1=true&modulesOnly=true&runModule=false"}});`, ); }); diff --git a/packages/metro/src/DeltaBundler/Serializers/helpers/js.js b/packages/metro/src/DeltaBundler/Serializers/helpers/js.js index fe4f7ebcdc..2f3a17c398 100644 --- a/packages/metro/src/DeltaBundler/Serializers/helpers/js.js +++ b/packages/metro/src/DeltaBundler/Serializers/helpers/js.js @@ -34,6 +34,9 @@ export type Options = Readonly<{ createModuleId: (absolutePath: string) => number | string, dev: boolean, includeAsyncPaths: boolean, + // The platform the bundle is built for, set on async bundle URLs so that the + // server builds each split bundle for the same platform. + platform?: ?string, projectRoot: string, serverRoot: string, sourceUrl: ?string, @@ -106,6 +109,9 @@ function getDefaultAsyncDependencyPath( // most parameters from the main bundle's URL. const {searchParams} = new URL(jscSafeUrl.toNormalUrl(options.sourceUrl)); + if (options.platform != null) { + searchParams.set('platform', options.platform); + } searchParams.set('modulesOnly', 'true'); searchParams.set('runModule', 'false'); if (isVirtualModulePath(dependency.absolutePath)) { @@ -118,14 +124,11 @@ function getDefaultAsyncDependencyPath( ); return ( '/' + - normalizePathSeparatorsToPosix( - 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)), - ), - ) + + // TODO: This is not the proper Metro URL encoding of a file path + // Keep the file extension: the server strips only the `.bundle` suffix, so + // the chunk resolves to this exact file and not to a sibling found through + // `sourceExts` (e.g. `index.js` instead of `index.cjs`). + normalizePathSeparatorsToPosix(bundlePath) + '.bundle?' + searchParams.toString() ); diff --git a/packages/metro/src/DeltaBundler/Serializers/helpers/processModules.js b/packages/metro/src/DeltaBundler/Serializers/helpers/processModules.js index ba44c1eb49..fc5abd3153 100644 --- a/packages/metro/src/DeltaBundler/Serializers/helpers/processModules.js +++ b/packages/metro/src/DeltaBundler/Serializers/helpers/processModules.js @@ -21,6 +21,7 @@ export default function processModules( createModuleId, dev, includeAsyncPaths, + platform, projectRoot, serverRoot, sourceUrl, @@ -32,6 +33,7 @@ export default function processModules( createModuleId: (absolutePath: string) => number, dev: boolean, includeAsyncPaths: boolean, + platform: ?string, projectRoot: string, serverRoot: string, sourceUrl: ?string, @@ -49,6 +51,7 @@ export default function processModules( createModuleId, dev, includeAsyncPaths, + platform, projectRoot, serverRoot, sourceUrl, diff --git a/packages/metro/src/DeltaBundler/Serializers/hmrJSBundle.js b/packages/metro/src/DeltaBundler/Serializers/hmrJSBundle.js index 60f22a9390..d43f8729c1 100644 --- a/packages/metro/src/DeltaBundler/Serializers/hmrJSBundle.js +++ b/packages/metro/src/DeltaBundler/Serializers/hmrJSBundle.js @@ -49,12 +49,9 @@ function generateModules( path .relative( options.serverRoot ?? options.projectRoot, - path.join( - path.dirname(sourcePath), - path.basename(sourcePath, path.extname(sourcePath)) + - '.' + - extension, - ), + // Keep the file extension, as for async bundle paths, so that the + // server resolves the URL to this exact file. + sourcePath + '.' + extension, ) .split(path.sep) // using this Metro particular convention for encoding file paths as URL paths. @@ -65,6 +62,9 @@ function generateModules( const clientUrl = new URL(options.clientUrl); clientUrl.searchParams.delete('excludeSource'); + if (graph.transformOptions.platform != null) { + clientUrl.searchParams.set('platform', graph.transformOptions.platform); + } if (isVirtualModulePath(module.path)) { clientUrl.searchParams.set(VIRTUAL_MODULE_URL_PARAM, 'true'); } @@ -105,6 +105,7 @@ function prepareModule( ): string { const code = wrapModule(module, { ...options, + platform: graph.transformOptions.platform, sourceUrl: options.clientUrl.toString(), dev: true, }); diff --git a/packages/metro/src/__tests__/HmrServer-test.js b/packages/metro/src/__tests__/HmrServer-test.js index d4bd0c577f..e394d1c3c4 100644 --- a/packages/metro/src/__tests__/HmrServer-test.js +++ b/packages/metro/src/__tests__/HmrServer-test.js @@ -86,6 +86,7 @@ describe('HmrServer', () => { mockedGraph = { dependencies: new Map<$FlowFixMe, $FlowFixMe>(), entryPoint: p('/root/EntryPoint.js'), + transformOptions: {platform: 'ios'}, }; changeHandlerPromises.clear(); diff --git a/packages/metro/src/integration_tests/__tests__/server-test.js b/packages/metro/src/integration_tests/__tests__/server-test.js index c3893ec6b9..b2fd11e07a 100644 --- a/packages/metro/src/integration_tests/__tests__/server-test.js +++ b/packages/metro/src/integration_tests/__tests__/server-test.js @@ -107,10 +107,26 @@ describe('Metro development server serves bundles via HTTP', () => { expect(bundlesDownloaded).toEqual( new Set([ '/import-export/index.bundle?platform=ios&dev=true&minify=false&lazy=true', - '/import-export/export-5.bundle?platform=ios&dev=true&minify=false&lazy=true&modulesOnly=true&runModule=false', - '/import-export/export-6.bundle?platform=ios&dev=true&minify=false&lazy=true&modulesOnly=true&runModule=false', - '/import-export/export-7.bundle?platform=ios&dev=true&minify=false&lazy=true&modulesOnly=true&runModule=false', - '/import-export/export-8.bundle?platform=ios&dev=true&minify=false&lazy=true&modulesOnly=true&runModule=false', + '/import-export/export-5.js.bundle?platform=ios&dev=true&minify=false&lazy=true&modulesOnly=true&runModule=false', + '/import-export/export-6.js.bundle?platform=ios&dev=true&minify=false&lazy=true&modulesOnly=true&runModule=false', + '/import-export/export-7.js.bundle?platform=ios&dev=true&minify=false&lazy=true&modulesOnly=true&runModule=false', + '/import-export/export-8.js.bundle?platform=ios&dev=true&minify=false&lazy=true&modulesOnly=true&runModule=false', + ]), + ); + }); + + test('builds lazy bundles for the platform inferred from the entry file name', async () => { + const object = await downloadAndExec( + '/async-platform/index.ios.bundle?dev=true&minify=false&lazy=true', + ); + await expect(object.asyncImport).resolves.toHaveProperty( + 'default', + 'dep.ios.js', + ); + expect(bundlesDownloaded).toEqual( + new Set([ + '/async-platform/index.ios.bundle?dev=true&minify=false&lazy=true', + '/async-platform/chunk.js.bundle?dev=true&minify=false&lazy=true&platform=ios&modulesOnly=true&runModule=false', ]), ); }); diff --git a/packages/metro/src/integration_tests/basic_bundle/async-platform/chunk.js b/packages/metro/src/integration_tests/basic_bundle/async-platform/chunk.js new file mode 100644 index 0000000000..d5ffc7f5c6 --- /dev/null +++ b/packages/metro/src/integration_tests/basic_bundle/async-platform/chunk.js @@ -0,0 +1,13 @@ +/** + * 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 + */ + +'use strict'; + +module.exports = require('./dep'); diff --git a/packages/metro/src/integration_tests/basic_bundle/async-platform/dep.ios.js b/packages/metro/src/integration_tests/basic_bundle/async-platform/dep.ios.js new file mode 100644 index 0000000000..e94823c5e6 --- /dev/null +++ b/packages/metro/src/integration_tests/basic_bundle/async-platform/dep.ios.js @@ -0,0 +1,13 @@ +/** + * 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 + */ + +'use strict'; + +module.exports = 'dep.ios.js'; diff --git a/packages/metro/src/integration_tests/basic_bundle/async-platform/dep.js b/packages/metro/src/integration_tests/basic_bundle/async-platform/dep.js new file mode 100644 index 0000000000..8c74a145aa --- /dev/null +++ b/packages/metro/src/integration_tests/basic_bundle/async-platform/dep.js @@ -0,0 +1,13 @@ +/** + * 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 + */ + +'use strict'; + +module.exports = 'dep.js'; diff --git a/packages/metro/src/integration_tests/basic_bundle/async-platform/index.ios.js b/packages/metro/src/integration_tests/basic_bundle/async-platform/index.ios.js new file mode 100644 index 0000000000..2712972f14 --- /dev/null +++ b/packages/metro/src/integration_tests/basic_bundle/async-platform/index.ios.js @@ -0,0 +1,13 @@ +/** + * 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 + */ + +'use strict'; + +export const asyncImport = import('./chunk');