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 f51a3d6483..e7727315e8 100644 --- a/packages/metro/src/DeltaBundler/Serializers/helpers/__tests__/js-test.js +++ b/packages/metro/src/DeltaBundler/Serializers/helpers/__tests__/js-test.js @@ -12,6 +12,7 @@ import type {Dependency} from '../../../types'; 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'; @@ -149,7 +150,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"}});`, ); }); @@ -171,10 +172,42 @@ 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 override modulesOnly and runModule', () => { const dep = nullthrows(myModule.dependencies.get('bar')); myModule.dependencies.set('bar', { @@ -194,7 +227,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"}});`, ); }); @@ -318,7 +351,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 080445bb20..5dd41c3336 100644 --- a/packages/metro/src/DeltaBundler/Serializers/helpers/js.js +++ b/packages/metro/src/DeltaBundler/Serializers/helpers/js.js @@ -108,12 +108,11 @@ function getDefaultAsyncDependencyPath( const bundlePath = path.relative(options.serverRoot, dependency.absolutePath); return ( '/' + - 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/integration_tests/__tests__/server-test.js b/packages/metro/src/integration_tests/__tests__/server-test.js index 39b5a5169a..e084113dd9 100644 --- a/packages/metro/src/integration_tests/__tests__/server-test.js +++ b/packages/metro/src/integration_tests/__tests__/server-test.js @@ -99,10 +99,10 @@ 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', ]), ); });