From 4ef14f9a2f6c0b566df86f9caebb5a98bb7ba52a Mon Sep 17 00:00:00 2001 From: Moti Zilberman Date: Mon, 24 Apr 2023 01:28:09 -0700 Subject: [PATCH] Support `lazy` parameter in bundle requests (#971) Summary: Pull Request resolved: https://github.com/facebook/metro/pull/971 Adds support for [lazy bundling](https://github.com/react-native-community/discussions-and-proposals/blob/main/proposals/0605-lazy-bundling.md) in Metro by accepting the `lazy` URL parameter in bundle requests. This replaces the old `server.experimentalImportBundleSupport` config option, which is no longer recognised. Docs for this feature will follow in a separate commit. Changelog: NOTE: We may want to group together items related to lazy bundling in this release (the first one where lazy bundling will be a stable and non-experimental feature). * **[Feature]**: Support `lazy` parameter in bundle requests. See the [lazy bundling RFC](https://github.com/react-native-community/discussions-and-proposals/blob/main/proposals/0605-lazy-bundling.md) for more details. * **[Experimental]** Removed `server.experimentalImportBundleSupport` config option. Reviewed By: huntie Differential Revision: D43600055 fbshipit-source-id: cf5747fa25e3c4635ed2c2c0fd8ea42e7703f0d3 --- .../__snapshots__/loadConfig-test.js.snap | 4 -- packages/metro-config/src/configTypes.flow.js | 1 - packages/metro-config/src/defaults/index.js | 1 - packages/metro-config/types/configTypes.d.ts | 1 - packages/metro/src/DeltaBundler/Graph.js | 23 ++++------- .../__tests__/DeltaBundler-test.js | 2 +- .../__tests__/DeltaCalculator-context-test.js | 2 +- .../__tests__/DeltaCalculator-test.js | 2 +- .../src/DeltaBundler/__tests__/Graph-test.js | 9 ++-- packages/metro/src/DeltaBundler/types.flow.js | 2 +- packages/metro/src/HmrServer.js | 8 ++-- packages/metro/src/IncrementalBundler.js | 14 ++++--- packages/metro/src/Server.js | 41 ++++++++++++------- .../metro/src/Server/__tests__/Server-test.js | 6 +-- .../metro/src/__tests__/HmrServer-test.js | 9 ++-- .../__snapshots__/server-test.js.snap | 32 +++++++++++++++ .../__tests__/server-test.js | 40 ++++++++++++++++-- .../basic_bundle/loadBundleAsyncForTest.js | 20 +++++++++ .../metro/src/integration_tests/execBundle.js | 5 ++- .../src/integration_tests/metro.config.js | 5 +++ .../src/lib/__tests__/getGraphId-test.js | 28 ++++++------- packages/metro/src/lib/getGraphId.js | 6 +-- packages/metro/src/lib/getPrependedScripts.js | 3 +- packages/metro/src/lib/parseOptionsFromUrl.js | 1 + packages/metro/src/lib/splitBundleOptions.js | 1 + packages/metro/src/lib/transformHelpers.js | 3 +- packages/metro/src/shared/types.flow.js | 2 + packages/metro/types/DeltaBundler/types.d.ts | 2 +- packages/metro/types/shared/types.d.ts | 2 + 29 files changed, 186 insertions(+), 89 deletions(-) create mode 100644 packages/metro/src/integration_tests/basic_bundle/loadBundleAsyncForTest.js diff --git a/packages/metro-config/src/__tests__/__snapshots__/loadConfig-test.js.snap b/packages/metro-config/src/__tests__/__snapshots__/loadConfig-test.js.snap index cc21ef510c..e64ebf497a 100644 --- a/packages/metro-config/src/__tests__/__snapshots__/loadConfig-test.js.snap +++ b/packages/metro-config/src/__tests__/__snapshots__/loadConfig-test.js.snap @@ -98,7 +98,6 @@ Object { }, "server": Object { "enhanceMiddleware": [Function], - "experimentalImportBundleSupport": false, "port": 8080, "rewriteRequestUrl": [Function], "runInspectorProxy": true, @@ -276,7 +275,6 @@ Object { }, "server": Object { "enhanceMiddleware": [Function], - "experimentalImportBundleSupport": false, "port": 8080, "rewriteRequestUrl": [Function], "runInspectorProxy": true, @@ -454,7 +452,6 @@ Object { }, "server": Object { "enhanceMiddleware": [Function], - "experimentalImportBundleSupport": false, "port": 8080, "rewriteRequestUrl": [Function], "runInspectorProxy": true, @@ -632,7 +629,6 @@ Object { }, "server": Object { "enhanceMiddleware": [Function], - "experimentalImportBundleSupport": false, "port": 8080, "rewriteRequestUrl": [Function], "runInspectorProxy": true, diff --git a/packages/metro-config/src/configTypes.flow.js b/packages/metro-config/src/configTypes.flow.js index 340e8e305a..1a61c3f8e2 100644 --- a/packages/metro-config/src/configTypes.flow.js +++ b/packages/metro-config/src/configTypes.flow.js @@ -168,7 +168,6 @@ type MetalConfigT = { type ServerConfigT = { enhanceMiddleware: (Middleware, Server) => Middleware, - experimentalImportBundleSupport: boolean, port: number, rewriteRequestUrl: string => string, runInspectorProxy: boolean, diff --git a/packages/metro-config/src/defaults/index.js b/packages/metro-config/src/defaults/index.js index b89dc7cb89..59142eeb04 100644 --- a/packages/metro-config/src/defaults/index.js +++ b/packages/metro-config/src/defaults/index.js @@ -72,7 +72,6 @@ const getDefaultValues = (projectRoot: ?string): ConfigT => ({ server: { enhanceMiddleware: middleware => middleware, - experimentalImportBundleSupport: false, port: 8080, rewriteRequestUrl: url => url, runInspectorProxy: true, diff --git a/packages/metro-config/types/configTypes.d.ts b/packages/metro-config/types/configTypes.d.ts index c18cdbfe4e..8c7d7d5d18 100644 --- a/packages/metro-config/types/configTypes.d.ts +++ b/packages/metro-config/types/configTypes.d.ts @@ -163,7 +163,6 @@ export interface MetalConfigT { export interface ServerConfigT { enhanceMiddleware: (middleware: Middleware, server: Server) => Middleware; - experimentalImportBundleSupport: boolean; port: number; rewriteRequestUrl: (url: string) => string; runInspectorProxy: boolean; diff --git a/packages/metro/src/DeltaBundler/Graph.js b/packages/metro/src/DeltaBundler/Graph.js index 531d205b53..d2f9b1f454 100644 --- a/packages/metro/src/DeltaBundler/Graph.js +++ b/packages/metro/src/DeltaBundler/Graph.js @@ -94,7 +94,7 @@ type Delta = $ReadOnly<{ }>; type InternalOptions = $ReadOnly<{ - experimentalImportBundleSupport: boolean, + lazy: boolean, onDependencyAdd: () => mixed, onDependencyAdded: () => mixed, resolve: Options['resolve'], @@ -106,14 +106,14 @@ function getInternalOptions({ transform, resolve, onProgress, - experimentalImportBundleSupport, + lazy, shallow, }: Options): InternalOptions { let numProcessed = 0; let total = 0; return { - experimentalImportBundleSupport, + lazy, transform, resolve, onDependencyAdd: () => onProgress && onProgress(numProcessed, ++total), @@ -357,10 +357,7 @@ export class Graph { // Don't add a node for the module if the graph is shallow (single-module). } else if (dependency.data.data.asyncType === 'weak') { // Exclude weak dependencies from the bundle. - } else if ( - options.experimentalImportBundleSupport && - dependency.data.data.asyncType != null - ) { + } 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. @@ -421,10 +418,7 @@ export class Graph { return; } - if ( - options.experimentalImportBundleSupport && - dependency.data.data.asyncType != null - ) { + if (options.lazy && dependency.data.data.asyncType != null) { this._decrementImportBundleReference(dependency, parentModule); } @@ -629,7 +623,7 @@ export class Graph { ); invariant( importBundleNode.inverseDependencies.has(parentModule.path), - 'experimentalImportBundleSupport: import bundle inverse references', + 'lazy: import bundle inverse references', ); importBundleNode.inverseDependencies.delete(parentModule.path); if (importBundleNode.inverseDependencies.size === 0) { @@ -784,13 +778,12 @@ export class Graph { function dependenciesEqual( a: Dependency, b: Dependency, - options: $ReadOnly<{experimentalImportBundleSupport: boolean, ...}>, + options: $ReadOnly<{lazy: boolean, ...}>, ): boolean { return ( a === b || (a.absolutePath === b.absolutePath && - (!options.experimentalImportBundleSupport || - a.data.data.asyncType === b.data.data.asyncType) && + (!options.lazy || a.data.data.asyncType === b.data.data.asyncType) && contextParamsEqual(a.data.data.contextParams, b.data.data.contextParams)) ); } diff --git a/packages/metro/src/DeltaBundler/__tests__/DeltaBundler-test.js b/packages/metro/src/DeltaBundler/__tests__/DeltaBundler-test.js index 84671a66c2..02a33057fe 100644 --- a/packages/metro/src/DeltaBundler/__tests__/DeltaBundler-test.js +++ b/packages/metro/src/DeltaBundler/__tests__/DeltaBundler-test.js @@ -31,7 +31,7 @@ describe('DeltaBundler', () => { const options = { unstable_allowRequireContext: false, unstable_enablePackageExports: false, - experimentalImportBundleSupport: false, + lazy: false, onProgress: null, resolve: (from: string, to: string) => { throw new Error('Never called'); diff --git a/packages/metro/src/DeltaBundler/__tests__/DeltaCalculator-context-test.js b/packages/metro/src/DeltaBundler/__tests__/DeltaCalculator-context-test.js index e19f55ed19..9b58e96f19 100644 --- a/packages/metro/src/DeltaBundler/__tests__/DeltaCalculator-context-test.js +++ b/packages/metro/src/DeltaBundler/__tests__/DeltaCalculator-context-test.js @@ -39,7 +39,7 @@ describe('DeltaCalculator + require.context', () => { const options = { unstable_allowRequireContext: true, unstable_enablePackageExports: false, - experimentalImportBundleSupport: false, + lazy: false, onProgress: null, resolve: (from: string, to: string) => { throw new Error('Never called'); diff --git a/packages/metro/src/DeltaBundler/__tests__/DeltaCalculator-test.js b/packages/metro/src/DeltaBundler/__tests__/DeltaCalculator-test.js index 074cacc0d1..6323b7b2b2 100644 --- a/packages/metro/src/DeltaBundler/__tests__/DeltaCalculator-test.js +++ b/packages/metro/src/DeltaBundler/__tests__/DeltaCalculator-test.js @@ -32,7 +32,7 @@ describe.each(['linux', 'win32'])('DeltaCalculator (%s)', osPlatform => { const options = { unstable_allowRequireContext: false, unstable_enablePackageExports: true, - experimentalImportBundleSupport: false, + lazy: false, onProgress: null, resolve: (from: string, to: string) => { throw new Error('Never called'); diff --git a/packages/metro/src/DeltaBundler/__tests__/Graph-test.js b/packages/metro/src/DeltaBundler/__tests__/Graph-test.js index 6e6b6d6802..79e846b8ec 100644 --- a/packages/metro/src/DeltaBundler/__tests__/Graph-test.js +++ b/packages/metro/src/DeltaBundler/__tests__/Graph-test.js @@ -228,10 +228,7 @@ function computeInverseDependencies( } for (const module of graph.dependencies.values()) { for (const dependency of module.dependencies.values()) { - if ( - options.experimentalImportBundleSupport && - dependency.data.data.asyncType != null - ) { + if (options.lazy && dependency.data.data.asyncType != null) { // Async deps aren't tracked in inverseDependencies continue; } @@ -327,7 +324,7 @@ beforeEach(async () => { options = { unstable_allowRequireContext: false, unstable_enablePackageExports: false, - experimentalImportBundleSupport: false, + lazy: false, onProgress: null, resolve: (from: string, to: string) => { const deps = getMockDependency(from); @@ -1411,7 +1408,7 @@ describe('edge cases', () => { beforeEach(() => { localOptions = { ...options, - experimentalImportBundleSupport: true, + lazy: true, }; }); diff --git a/packages/metro/src/DeltaBundler/types.flow.js b/packages/metro/src/DeltaBundler/types.flow.js index ddfa038557..9df5c53b2b 100644 --- a/packages/metro/src/DeltaBundler/types.flow.js +++ b/packages/metro/src/DeltaBundler/types.flow.js @@ -130,7 +130,7 @@ export type Options = { +transform: TransformFn, +transformOptions: TransformInputOptions, +onProgress: ?(numProcessed: number, total: number) => mixed, - +experimentalImportBundleSupport: boolean, + +lazy: boolean, +unstable_allowRequireContext: boolean, +unstable_enablePackageExports: boolean, +shallow: boolean, diff --git a/packages/metro/src/HmrServer.js b/packages/metro/src/HmrServer.js index 3575b7e820..1b3bc2e9fb 100644 --- a/packages/metro/src/HmrServer.js +++ b/packages/metro/src/HmrServer.js @@ -11,6 +11,7 @@ 'use strict'; import type IncrementalBundler, {RevisionId} from './IncrementalBundler'; +import type {GraphOptions} from './shared/types.flow'; import type {ConfigT, RootPerfLogger} from 'metro-config'; import type { HmrClientMessage, @@ -48,6 +49,7 @@ type ClientGroup = { clientUrl: EntryPointURL, revisionId: RevisionId, +unlisten: () => void, + +graphOptions: GraphOptions, }; function send(sendFns: Array<(string) => void>, message: HmrMessage): void { @@ -123,8 +125,7 @@ class HmrServer { const graphId = getGraphId(resolvedEntryFilePath, transformOptions, { resolverOptions, shallow: graphOptions.shallow, - experimentalImportBundleSupport: - this._config.server.experimentalImportBundleSupport, + lazy: graphOptions.lazy, unstable_allowRequireContext: this._config.transformer.unstable_allowRequireContext, }); @@ -167,6 +168,7 @@ class HmrServer { clients: new Set([client]), clientUrl, revisionId: id, + graphOptions, unlisten: (): void => unlisten(), }; @@ -356,7 +358,7 @@ class HmrServer { const hmrUpdate = hmrJSBundle(delta, revision.graph, { clientUrl: group.clientUrl, createModuleId: this._createModuleId, - includeAsyncPaths: this._config.server.experimentalImportBundleSupport, + includeAsyncPaths: group.graphOptions.lazy, projectRoot: this._config.projectRoot, serverRoot: this._config.server.unstable_serverRoot ?? this._config.projectRoot, diff --git a/packages/metro/src/IncrementalBundler.js b/packages/metro/src/IncrementalBundler.js index d6a05f53d0..5b6023c235 100644 --- a/packages/metro/src/IncrementalBundler.js +++ b/packages/metro/src/IncrementalBundler.js @@ -38,6 +38,7 @@ export type OutputGraph = Graph<>; type OtherOptions = $ReadOnly<{ onProgress: $PropertyType, 'onProgress'>, shallow: boolean, + lazy: boolean, }>; export type GraphRevision = { @@ -107,6 +108,7 @@ class IncrementalBundler { otherOptions?: OtherOptions = { onProgress: null, shallow: false, + lazy: false, }, ): Promise { const absoluteEntryFiles = await this._getAbsoluteEntryFiles(entryFiles); @@ -127,8 +129,7 @@ class IncrementalBundler { ), transformOptions, onProgress: otherOptions.onProgress, - experimentalImportBundleSupport: - this._config.server.experimentalImportBundleSupport, + lazy: otherOptions.lazy, unstable_allowRequireContext: this._config.transformer.unstable_allowRequireContext, unstable_enablePackageExports: @@ -153,6 +154,7 @@ class IncrementalBundler { otherOptions?: OtherOptions = { onProgress: null, shallow: false, + lazy: false, }, ): Promise> { const absoluteEntryFiles = await this._getAbsoluteEntryFiles(entryFiles); @@ -175,8 +177,7 @@ class IncrementalBundler { ), transformOptions, onProgress: otherOptions.onProgress, - experimentalImportBundleSupport: - this._config.server.experimentalImportBundleSupport, + lazy: otherOptions.lazy, unstable_allowRequireContext: this._config.transformer.unstable_allowRequireContext, unstable_enablePackageExports: @@ -195,6 +196,7 @@ class IncrementalBundler { otherOptions?: OtherOptions = { onProgress: null, shallow: false, + lazy: false, }, ): Promise<{+graph: OutputGraph, +prepend: $ReadOnlyArray>}> { const graph = await this.buildGraphForEntries( @@ -229,6 +231,7 @@ class IncrementalBundler { otherOptions?: OtherOptions = { onProgress: null, shallow: false, + lazy: false, }, ): Promise<{ delta: DeltaResult<>, @@ -238,8 +241,7 @@ class IncrementalBundler { const graphId = getGraphId(entryFile, transformOptions, { resolverOptions, shallow: otherOptions.shallow, - experimentalImportBundleSupport: - this._config.server.experimentalImportBundleSupport, + lazy: otherOptions.lazy, unstable_allowRequireContext: this._config.transformer.unstable_allowRequireContext, }); diff --git a/packages/metro/src/Server.js b/packages/metro/src/Server.js index ae0eb67ae7..0aa403e7ce 100644 --- a/packages/metro/src/Server.js +++ b/packages/metro/src/Server.js @@ -201,6 +201,7 @@ class Server { { onProgress, shallow: graphOptions.shallow, + lazy: graphOptions.lazy, }, ); @@ -219,7 +220,7 @@ class Server { createModuleId: this._createModuleId, getRunModuleStatement: this._config.serializer.getRunModuleStatement, dev: transformOptions.dev, - includeAsyncPaths: this._config.server.experimentalImportBundleSupport, + includeAsyncPaths: graphOptions.lazy, projectRoot: this._config.projectRoot, modulesOnly: serializerOptions.modulesOnly, runBeforeMainModule: @@ -282,7 +283,11 @@ class Server { entryFile, transformOptions, resolverOptions, - {onProgress, shallow: graphOptions.shallow}, + { + onProgress, + shallow: graphOptions.shallow, + lazy: graphOptions.lazy, + }, ); const entryPoint = this._getEntryPointAbsolutePath(entryFile); @@ -302,7 +307,7 @@ class Server { excludeSource: serializerOptions.excludeSource, getRunModuleStatement: this._config.serializer.getRunModuleStatement, getTransformOptions: this._config.transformer.getTransformOptions, - includeAsyncPaths: this._config.server.experimentalImportBundleSupport, + includeAsyncPaths: graphOptions.lazy, platform: transformOptions.platform, projectRoot: this._config.projectRoot, modulesOnly: serializerOptions.modulesOnly, @@ -327,7 +332,7 @@ class Server { [entryFile], transformOptions, resolverOptions, - {onProgress, shallow: false}, + {onProgress, shallow: false, lazy: false}, ); return await getAssets(dependencies, { @@ -364,7 +369,7 @@ class Server { entryFile, transformOptions, resolverOptions, - {onProgress, shallow: false}, + {onProgress, shallow: false, lazy: false}, ); const platform = @@ -596,12 +601,11 @@ class Server { transformOptions, }); const graphId = getGraphId(resolvedEntryFilePath, transformOptions, { - experimentalImportBundleSupport: - this._config.server.experimentalImportBundleSupport, unstable_allowRequireContext: this._config.transformer.unstable_allowRequireContext, resolverOptions, shallow: graphOptions.shallow, + lazy: graphOptions.lazy, }); // For resources that support deletion, handle the DELETE method. @@ -830,6 +834,7 @@ class Server { { onProgress, shallow: graphOptions.shallow, + lazy: graphOptions.lazy, }, )); bundlePerfLogger.point('resolvingAndTransformingDependencies_end'); @@ -856,8 +861,7 @@ class Server { processModuleFilter: this._config.serializer.processModuleFilter, createModuleId: this._createModuleId, getRunModuleStatement: this._config.serializer.getRunModuleStatement, - includeAsyncPaths: - this._config.server.experimentalImportBundleSupport, + includeAsyncPaths: graphOptions.lazy, dev: transformOptions.dev, projectRoot: this._config.projectRoot, modulesOnly: serializerOptions.modulesOnly, @@ -974,7 +978,11 @@ class Server { entryFile, transformOptions, resolverOptions, - {onProgress, shallow: graphOptions.shallow}, + { + onProgress, + shallow: graphOptions.shallow, + lazy: graphOptions.lazy, + }, )); } else { ({revision} = await this._bundler.updateGraph(await revPromise, false)); @@ -1028,7 +1036,7 @@ class Server { [entryFile], transformOptions, resolverOptions, - {onProgress, shallow: false}, + {onProgress, shallow: false, lazy: false}, ); return await getAssets(dependencies, { @@ -1177,12 +1185,11 @@ class Server { }); const graphId = getGraphId(resolvedEntryFilePath, transformOptions, { - experimentalImportBundleSupport: - this._config.server.experimentalImportBundleSupport, unstable_allowRequireContext: this._config.transformer.unstable_allowRequireContext, resolverOptions, shallow: graphOptions.shallow, + lazy: graphOptions.lazy, }); let revision; const revPromise = this._bundler.getRevisionByGraphId(graphId); @@ -1191,7 +1198,11 @@ class Server { resolvedEntryFilePath, transformOptions, resolverOptions, - {onProgress, shallow: graphOptions.shallow}, + { + onProgress, + shallow: graphOptions.shallow, + lazy: graphOptions.lazy, + }, )); } else { ({revision} = await this._bundler.updateGraph(await revPromise, false)); @@ -1266,6 +1277,7 @@ class Server { ...typeof Server.DEFAULT_GRAPH_OPTIONS, excludeSource: false, inlineSourceMap: false, + lazy: false, modulesOnly: false, onProgress: null, runModule: true, @@ -1276,6 +1288,7 @@ class Server { ...Server.DEFAULT_GRAPH_OPTIONS, excludeSource: false, inlineSourceMap: false, + lazy: false, modulesOnly: false, onProgress: null, runModule: true, diff --git a/packages/metro/src/Server/__tests__/Server-test.js b/packages/metro/src/Server/__tests__/Server-test.js index 811ac55bd7..cf3d331b5d 100644 --- a/packages/metro/src/Server/__tests__/Server-test.js +++ b/packages/metro/src/Server/__tests__/Server-test.js @@ -646,7 +646,7 @@ describe('processRequest', () => { expect(getResolveDependencyFn).toBeCalled(); expect(buildGraph).toBeCalledWith(['/root/index.js'], { - experimentalImportBundleSupport: false, + lazy: false, onProgress: expect.any(Function), resolve: expect.any(Function), shallow: false, @@ -681,7 +681,7 @@ describe('processRequest', () => { expect(getResolveDependencyFn).toBeCalled(); expect(buildGraph).toBeCalledWith(['/root/index.js'], { - experimentalImportBundleSupport: false, + lazy: false, onProgress: expect.any(Function), resolve: expect.any(Function), shallow: false, @@ -903,7 +903,7 @@ describe('processRequest', () => { expect(getResolveDependencyFn).toBeCalled(); expect(buildGraph).toBeCalledWith(['/root/foo file'], { - experimentalImportBundleSupport: false, + lazy: false, onProgress: null, resolve: expect.any(Function), shallow: false, diff --git a/packages/metro/src/__tests__/HmrServer-test.js b/packages/metro/src/__tests__/HmrServer-test.js index 7266be3214..e22a61551d 100644 --- a/packages/metro/src/__tests__/HmrServer-test.js +++ b/packages/metro/src/__tests__/HmrServer-test.js @@ -126,7 +126,6 @@ describe('HmrServer', () => { }, resolver: {platforms: []}, server: { - experimentalImportBundleSupport: false, rewriteRequestUrl(requrl) { const rewritten = requrl.replace( /__REMOVE_THIS_WHEN_REWRITING__/g, @@ -209,7 +208,7 @@ describe('HmrServer', () => { }, { shallow: false, - experimentalImportBundleSupport: false, + lazy: false, unstable_allowRequireContext: false, resolverOptions: {}, }, @@ -236,7 +235,7 @@ describe('HmrServer', () => { }, { shallow: false, - experimentalImportBundleSupport: false, + lazy: false, unstable_allowRequireContext: false, resolverOptions: {}, }, @@ -263,7 +262,7 @@ describe('HmrServer', () => { }, { shallow: false, - experimentalImportBundleSupport: false, + lazy: false, unstable_allowRequireContext: false, resolverOptions: {}, }, @@ -290,7 +289,7 @@ describe('HmrServer', () => { }, { shallow: false, - experimentalImportBundleSupport: false, + lazy: false, unstable_allowRequireContext: false, resolverOptions: {}, }, diff --git a/packages/metro/src/integration_tests/__tests__/__snapshots__/server-test.js.snap b/packages/metro/src/integration_tests/__tests__/__snapshots__/server-test.js.snap index 3401404e15..4d86a7668f 100644 --- a/packages/metro/src/integration_tests/__tests__/__snapshots__/server-test.js.snap +++ b/packages/metro/src/integration_tests/__tests__/__snapshots__/server-test.js.snap @@ -29,6 +29,38 @@ Object { } `; +exports[`Metro development server serves bundles via HTTP should serve lazy bundles 1`] = ` +Object { + "default": Object { + "foo": "export-5: FOO", + }, + "foo": "export-5: FOO", +} +`; + +exports[`Metro development server serves bundles via HTTP should serve lazy bundles 2`] = ` +Object { + "default": "export-6: DEFAULT", + "foo": "export-6: FOO", +} +`; + +exports[`Metro development server serves bundles via HTTP should serve non-lazy bundles by default 1`] = ` +Object { + "default": Object { + "foo": "export-5: FOO", + }, + "foo": "export-5: FOO", +} +`; + +exports[`Metro development server serves bundles via HTTP should serve non-lazy bundles by default 2`] = ` +Object { + "default": "export-6: DEFAULT", + "foo": "export-6: FOO", +} +`; + exports[`Metro development server serves bundles via HTTP should serve production bundles 1`] = ` Object { "Bar": Object { diff --git a/packages/metro/src/integration_tests/__tests__/server-test.js b/packages/metro/src/integration_tests/__tests__/server-test.js index a150da1c57..3c2a417333 100644 --- a/packages/metro/src/integration_tests/__tests__/server-test.js +++ b/packages/metro/src/integration_tests/__tests__/server-test.js @@ -21,11 +21,13 @@ jest.setTimeout(60 * 1000); describe('Metro development server serves bundles via HTTP', () => { let config; let httpServer; + const bundlesDownloaded = new Set(); - async function downloadAndExec(path: string): mixed { + async function downloadAndExec(path: string, context = {}): mixed { const response = await fetch( 'http://localhost:' + config.server.port + path, ); + bundlesDownloaded.add(path); const body = await response.text(); @@ -34,11 +36,15 @@ describe('Metro development server serves bundles via HTTP', () => { throw new Error('Metro responded with status code: ' + response.status); } - - return execBundle(body); + if (!context.__DOWNLOAD_AND_EXEC_FOR_TESTS__) { + context.__DOWNLOAD_AND_EXEC_FOR_TESTS__ = p => + downloadAndExec(p, context); + } + return execBundle(body, context); } beforeEach(async () => { + bundlesDownloaded.clear(); config = await Metro.loadConfig({ config: require.resolve('../metro.config.js'), }); @@ -67,4 +73,32 @@ describe('Metro development server serves bundles via HTTP', () => { ), ).toMatchSnapshot(); }); + + it('should serve lazy bundles', async () => { + const object = await downloadAndExec( + '/import-export/index.bundle?platform=ios&dev=true&minify=false&lazy=true', + ); + await expect(object.asyncImportCJS).resolves.toMatchSnapshot(); + await expect(object.asyncImportESM).resolves.toMatchSnapshot(); + expect(bundlesDownloaded).toEqual( + new Set([ + '/import-export/index.bundle?platform=ios&dev=true&minify=false&lazy=true', + '/import-export/export-6.bundle?platform=ios&dev=true&minify=false&lazy=true&modulesOnly=true&runModule=false', + '/import-export/export-5.bundle?platform=ios&dev=true&minify=false&lazy=true&modulesOnly=true&runModule=false', + ]), + ); + }); + + it('should serve non-lazy bundles by default', async () => { + const object = await downloadAndExec( + '/import-export/index.bundle?platform=ios&dev=true&minify=false', + ); + await expect(object.asyncImportCJS).resolves.toMatchSnapshot(); + await expect(object.asyncImportESM).resolves.toMatchSnapshot(); + expect(bundlesDownloaded).toEqual( + new Set([ + '/import-export/index.bundle?platform=ios&dev=true&minify=false', + ]), + ); + }); }); diff --git a/packages/metro/src/integration_tests/basic_bundle/loadBundleAsyncForTest.js b/packages/metro/src/integration_tests/basic_bundle/loadBundleAsyncForTest.js new file mode 100644 index 0000000000..c0082a7c7e --- /dev/null +++ b/packages/metro/src/integration_tests/basic_bundle/loadBundleAsyncForTest.js @@ -0,0 +1,20 @@ +/** + * 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 + */ + +'use strict'; + +declare var __METRO_GLOBAL_PREFIX__: string; +declare var __DOWNLOAD_AND_EXEC_FOR_TESTS__: (path: string) => Promise; + +const key = `${global.__METRO_GLOBAL_PREFIX__ ?? ''}__loadBundleAsync`; + +global[key] = async function loadBundleAsyncForTest(path: string) { + await __DOWNLOAD_AND_EXEC_FOR_TESTS__(path); +}; diff --git a/packages/metro/src/integration_tests/execBundle.js b/packages/metro/src/integration_tests/execBundle.js index 0e802721bd..f040dad425 100644 --- a/packages/metro/src/integration_tests/execBundle.js +++ b/packages/metro/src/integration_tests/execBundle.js @@ -13,6 +13,9 @@ const vm = require('vm'); -module.exports = function execBundle(code: string, context: {...} = {}): mixed { +module.exports = function execBundle(code: string, context: any = {}): mixed { + if (vm.isContext(context)) { + return vm.runInContext(code, context); + } return vm.runInNewContext(code, context); }; diff --git a/packages/metro/src/integration_tests/metro.config.js b/packages/metro/src/integration_tests/metro.config.js index c1f18435cd..0527b954ad 100644 --- a/packages/metro/src/integration_tests/metro.config.js +++ b/packages/metro/src/integration_tests/metro.config.js @@ -40,4 +40,9 @@ module.exports = { ramGroups: [], }), }, + serializer: { + getPolyfills: () => [ + require.resolve('./basic_bundle/loadBundleAsyncForTest'), + ], + }, }; diff --git a/packages/metro/src/lib/__tests__/getGraphId-test.js b/packages/metro/src/lib/__tests__/getGraphId-test.js index 94a324d054..3b82c76953 100644 --- a/packages/metro/src/lib/__tests__/getGraphId-test.js +++ b/packages/metro/src/lib/__tests__/getGraphId-test.js @@ -28,7 +28,7 @@ describe('getGraphId', () => { }, { shallow: false, - experimentalImportBundleSupport: false, + lazy: false, unstable_allowRequireContext: false, resolverOptions: {}, }, @@ -46,7 +46,7 @@ describe('getGraphId', () => { }, { shallow: false, - experimentalImportBundleSupport: false, + lazy: false, unstable_allowRequireContext: false, resolverOptions: {}, }, @@ -68,7 +68,7 @@ describe('getGraphId', () => { }, { shallow: false, - experimentalImportBundleSupport: false, + lazy: false, unstable_allowRequireContext: false, resolverOptions: {}, }, @@ -86,7 +86,7 @@ describe('getGraphId', () => { }, { shallow: false, - experimentalImportBundleSupport: false, + lazy: false, unstable_allowRequireContext: false, resolverOptions: {}, }, @@ -108,7 +108,7 @@ describe('getGraphId', () => { }, { shallow: false, - experimentalImportBundleSupport: false, + lazy: false, unstable_allowRequireContext: false, resolverOptions: {}, }, @@ -126,7 +126,7 @@ describe('getGraphId', () => { }, { shallow: false, - experimentalImportBundleSupport: false, + lazy: false, unstable_allowRequireContext: false, resolverOptions: {}, }, @@ -152,7 +152,7 @@ describe('getGraphId', () => { }, { shallow: false, - experimentalImportBundleSupport: false, + lazy: false, unstable_allowRequireContext: false, resolverOptions: {}, }, @@ -174,7 +174,7 @@ describe('getGraphId', () => { }, { shallow: false, - experimentalImportBundleSupport: false, + lazy: false, unstable_allowRequireContext: false, resolverOptions: {}, }, @@ -195,7 +195,7 @@ describe('getGraphId', () => { expect( getGraphId('/root/waddup', transformOptions, { shallow: false, - experimentalImportBundleSupport: false, + lazy: false, unstable_allowRequireContext: false, resolverOptions: { customResolverOptions: { @@ -206,7 +206,7 @@ describe('getGraphId', () => { ).not.toBe( getGraphId('/root/waddup', transformOptions, { shallow: false, - experimentalImportBundleSupport: false, + lazy: false, unstable_allowRequireContext: false, resolverOptions: { customResolverOptions: { @@ -230,7 +230,7 @@ describe('getGraphId', () => { expect( getGraphId('/root/waddup', transformOptions, { shallow: false, - experimentalImportBundleSupport: false, + lazy: false, unstable_allowRequireContext: false, resolverOptions: { customResolverOptions: { @@ -242,7 +242,7 @@ describe('getGraphId', () => { ).toBe( getGraphId('/root/waddup', transformOptions, { shallow: false, - experimentalImportBundleSupport: false, + lazy: false, unstable_allowRequireContext: false, resolverOptions: { customResolverOptions: { @@ -270,7 +270,7 @@ describe('getGraphId', () => { }, { shallow: false, - experimentalImportBundleSupport: false, + lazy: false, unstable_allowRequireContext: false, resolverOptions: { customResolverOptions: undefined, @@ -290,7 +290,7 @@ describe('getGraphId', () => { }, { shallow: false, - experimentalImportBundleSupport: false, + lazy: false, unstable_allowRequireContext: false, resolverOptions: {}, }, diff --git a/packages/metro/src/lib/getGraphId.js b/packages/metro/src/lib/getGraphId.js index 38fe9181da..947fbe49c6 100644 --- a/packages/metro/src/lib/getGraphId.js +++ b/packages/metro/src/lib/getGraphId.js @@ -23,12 +23,12 @@ function getGraphId( options: TransformInputOptions, { shallow, - experimentalImportBundleSupport, + lazy, unstable_allowRequireContext, resolverOptions, }: $ReadOnly<{ shallow: boolean, - experimentalImportBundleSupport: boolean, + lazy: boolean, unstable_allowRequireContext: boolean, resolverOptions: ResolverInputOptions, }>, @@ -46,7 +46,7 @@ function getGraphId( unstable_disableES6Transforms: options.unstable_disableES6Transforms, platform: options.platform != null ? options.platform : null, type: options.type, - experimentalImportBundleSupport, + lazy, unstable_allowRequireContext, shallow, unstable_transformProfile: diff --git a/packages/metro/src/lib/getPrependedScripts.js b/packages/metro/src/lib/getPrependedScripts.js index 7b2f46bddf..3518dcb99d 100644 --- a/packages/metro/src/lib/getPrependedScripts.js +++ b/packages/metro/src/lib/getPrependedScripts.js @@ -67,8 +67,7 @@ async function getPrependedScripts( config.transformer.unstable_allowRequireContext, transformOptions, onProgress: null, - experimentalImportBundleSupport: - config.server.experimentalImportBundleSupport, + lazy: false, unstable_enablePackageExports: config.resolver.unstable_enablePackageExports, shallow: false, diff --git a/packages/metro/src/lib/parseOptionsFromUrl.js b/packages/metro/src/lib/parseOptionsFromUrl.js index 9507511c07..7de7f01c1e 100644 --- a/packages/metro/src/lib/parseOptionsFromUrl.js +++ b/packages/metro/src/lib/parseOptionsFromUrl.js @@ -60,6 +60,7 @@ module.exports = function parseOptionsFromUrl( excludeSource: getBoolean(query, 'excludeSource', false), hot: true, inlineSourceMap: getBoolean(query, 'inlineSourceMap', false), + lazy: getBoolean(query, 'lazy', false), minify: getBoolean(query, 'minify', false), modulesOnly: getBoolean(query, 'modulesOnly', false), onProgress: null, diff --git a/packages/metro/src/lib/splitBundleOptions.js b/packages/metro/src/lib/splitBundleOptions.js index 761404dbaf..1fa1f77e0e 100644 --- a/packages/metro/src/lib/splitBundleOptions.js +++ b/packages/metro/src/lib/splitBundleOptions.js @@ -41,6 +41,7 @@ function splitBundleOptions(options: BundleOptions): SplitBundleOptions { }, graphOptions: { shallow: options.shallow, + lazy: options.lazy, }, onProgress: options.onProgress, }; diff --git a/packages/metro/src/lib/transformHelpers.js b/packages/metro/src/lib/transformHelpers.js index e4571b602a..63069baeb2 100644 --- a/packages/metro/src/lib/transformHelpers.js +++ b/packages/metro/src/lib/transformHelpers.js @@ -85,8 +85,7 @@ async function calcTransformerOptions( ), transformOptions: options, onProgress: null, - experimentalImportBundleSupport: - config.server.experimentalImportBundleSupport, + lazy: false, unstable_allowRequireContext: config.transformer.unstable_allowRequireContext, unstable_enablePackageExports: diff --git a/packages/metro/src/shared/types.flow.js b/packages/metro/src/shared/types.flow.js index 0a9a410858..2d4fd618b8 100644 --- a/packages/metro/src/shared/types.flow.js +++ b/packages/metro/src/shared/types.flow.js @@ -50,6 +50,7 @@ export type BundleOptions = { +excludeSource: boolean, +hot: boolean, +inlineSourceMap: boolean, + +lazy: boolean, minify: boolean, +modulesOnly: boolean, onProgress: ?(doneCont: number, totalCount: number) => mixed, @@ -76,6 +77,7 @@ export type SerializerOptions = { }; export type GraphOptions = { + +lazy: boolean, +shallow: boolean, }; diff --git a/packages/metro/types/DeltaBundler/types.d.ts b/packages/metro/types/DeltaBundler/types.d.ts index 5499b8a866..9d6e4d7150 100644 --- a/packages/metro/types/DeltaBundler/types.d.ts +++ b/packages/metro/types/DeltaBundler/types.d.ts @@ -139,7 +139,7 @@ export interface Options { readonly onProgress: | ((numProcessed: number, total: number) => unknown) | null; - readonly experimentalImportBundleSupport: boolean; + readonly lazy: boolean; readonly unstable_allowRequireContext: boolean; readonly shallow: boolean; } diff --git a/packages/metro/types/shared/types.d.ts b/packages/metro/types/shared/types.d.ts index 235289216a..0430652453 100644 --- a/packages/metro/types/shared/types.d.ts +++ b/packages/metro/types/shared/types.d.ts @@ -46,6 +46,7 @@ export interface BundleOptions { readonly excludeSource: boolean; readonly hot: boolean; readonly inlineSourceMap: boolean; + readonly lazy: boolean; minify: boolean; readonly modulesOnly: boolean; onProgress?: (doneCont: number, totalCount: number) => unknown; @@ -73,6 +74,7 @@ export interface SerializerOptions { } export interface GraphOptions { + readonly lazy: boolean; readonly shallow: boolean; }