diff --git a/packages/metro-transform-plugins/src/__tests__/import-export-plugin-test.js b/packages/metro-transform-plugins/src/__tests__/import-export-plugin-test.js index de4a65164a..4eade05492 100644 --- a/packages/metro-transform-plugins/src/__tests__/import-export-plugin-test.js +++ b/packages/metro-transform-plugins/src/__tests__/import-export-plugin-test.js @@ -255,6 +255,36 @@ test('exports destructured named object members', () => { compare([importExportPlugin], code, expected, opts); }); +test('exports destructured renamed object members', () => { + const code = ` + export const {foo: bar, baz} = {foo: 'bar', baz: 'baz'}; + `; + + const expected = ` + Object.defineProperty(exports, '__esModule', {value: true}); + const {foo: bar,baz} = {foo: 'bar', baz: 'baz'}; + exports.bar = bar; + exports.baz = baz; + `; + + compare([importExportPlugin], code, expected, opts); +}); + +test('exports destructured object rest members', () => { + const code = ` + export const {foo, ...bar} = {foo: 'foo', bar: 'bar', baz: 'baz'}; + `; + + const expected = ` + Object.defineProperty(exports, '__esModule', {value: true}); + const {foo,...bar} = {foo: 'foo', bar: 'bar', baz: 'baz'}; + exports.foo = foo; + exports.bar = bar; + `; + + compare([importExportPlugin], code, expected, opts); +}); + test('exports destructured named array members', () => { const code = ` export const [foo,bar] = ['bar','baz']; @@ -270,6 +300,21 @@ test('exports destructured named array members', () => { compare([importExportPlugin], code, expected, opts); }); +test('exports destructured array rest members', () => { + const code = ` + export const [foo, ...bar] = ['foo','bar','baz']; + `; + + const expected = ` + Object.defineProperty(exports, '__esModule', {value: true}); + const [foo,...bar] = ['foo','bar','baz']; + exports.foo = foo; + exports.bar = bar; + `; + + compare([importExportPlugin], code, expected, opts); +}); + test('exports members of another module directly from an import (as all)', () => { const code = ` export * from 'bar'; diff --git a/packages/metro-transform-plugins/src/import-export-plugin.js b/packages/metro-transform-plugins/src/import-export-plugin.js index cf5c3d4dbf..db8295491f 100644 --- a/packages/metro-transform-plugins/src/import-export-plugin.js +++ b/packages/metro-transform-plugins/src/import-export-plugin.js @@ -224,40 +224,9 @@ export default function importExportPlugin({ if (declaration) { if (isVariableDeclaration(declaration)) { - declaration.declarations.forEach(d => { - switch (d.id.type) { - case 'ObjectPattern': - { - const properties = d.id.properties; - properties.forEach(p => { - // $FlowFixMe[incompatible-use] Flow error uncovered by typing Babel more strictly - // $FlowFixMe[prop-missing] - const name = p.key.name; - // $FlowFixMe[incompatible-type] - state.exportNamed.push({local: name, remote: name, loc}); - }); - } - break; - case 'ArrayPattern': - { - const elements = d.id.elements; - elements.forEach(e => { - // $FlowFixMe[incompatible-use] Flow error uncovered by typing Babel more strictly - // $FlowFixMe[prop-missing] - const name = e.name; - // $FlowFixMe[incompatible-type] - state.exportNamed.push({local: name, remote: name, loc}); - }); - } - break; - default: - { - const name = d.id.name; - // $FlowFixMe[incompatible-type] - state.exportNamed.push({local: name, remote: name, loc}); - } - break; - } + const bindings = t.getBindingIdentifiers(declaration); + Object.keys(bindings).forEach(name => { + state.exportNamed.push({local: name, remote: name, loc}); }); } else { const id = declaration.id || path.scope.generateUidIdentifier(); diff --git a/packages/metro/src/integration_tests/__tests__/__snapshots__/import-export-test.js.snap b/packages/metro/src/integration_tests/__tests__/__snapshots__/import-export-test.js.snap index 5233683c96..b20e405717 100644 --- a/packages/metro/src/integration_tests/__tests__/__snapshots__/import-export-test.js.snap +++ b/packages/metro/src/integration_tests/__tests__/__snapshots__/import-export-test.js.snap @@ -16,6 +16,10 @@ Object { }, "default": "export-4: FOO", "extraData": Object { + "arrayFirst": "export-destructuring: ARRAY_FIRST", + "arrayRest": Array [ + "export-destructuring: ARRAY_REST", + ], "foo": "export-null: FOO", "importStar": Object { "default": "export-2: DEFAULT", @@ -27,8 +31,12 @@ Object { "myFunction": "export-1: MY_FUNCTION", "namespaceReExportDefault": "export-2: DEFAULT", "namespaceReExportFoo": "export-2: FOO", + "objectRest": Object { + "remaining": "export-destructuring: OBJECT_REST", + }, "primitiveDefault": "export-primitive-default: DEFAULT", "primitiveFoo": "export-primitive-default: FOO", + "renamedObject": "export-destructuring: RENAMED_OBJECT", }, "namedDefaultExported": "export-3: DEFAULT", } diff --git a/packages/metro/src/integration_tests/__tests__/import-export-test.js b/packages/metro/src/integration_tests/__tests__/import-export-test.js index 55646a2625..a26f5da727 100644 --- a/packages/metro/src/integration_tests/__tests__/import-export-test.js +++ b/packages/metro/src/integration_tests/__tests__/import-export-test.js @@ -27,6 +27,16 @@ test('builds a simple bundle', async () => { const object = execBundle(result.code); const cjs = await object.asyncImportCJS; + expect(object.extraData.renamedObject).toBe( + 'export-destructuring: RENAMED_OBJECT', + ); + expect(object.extraData.objectRest).toEqual({ + remaining: 'export-destructuring: OBJECT_REST', + }); + expect(object.extraData.arrayFirst).toBe('export-destructuring: ARRAY_FIRST'); + expect(object.extraData.arrayRest).toEqual([ + 'export-destructuring: ARRAY_REST', + ]); expect(object.extraData.namespaceReExportDefault).toBe('export-2: DEFAULT'); expect(object.extraData.namespaceReExportFoo).toBe('export-2: FOO'); expect(object).toMatchSnapshot(); diff --git a/packages/metro/src/integration_tests/basic_bundle/import-export/export-destructuring.js b/packages/metro/src/integration_tests/basic_bundle/import-export/export-destructuring.js new file mode 100644 index 0000000000..8d36febb73 --- /dev/null +++ b/packages/metro/src/integration_tests/basic_bundle/import-export/export-destructuring.js @@ -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 + * @flow strict + */ + +'use strict'; + +const objectSource = { + original: 'export-destructuring: RENAMED_OBJECT', + remaining: 'export-destructuring: OBJECT_REST', +}; + +export const {original: renamedObject, ...objectRest} = objectSource; + +const arraySource = [ + 'export-destructuring: ARRAY_FIRST', + 'export-destructuring: ARRAY_REST', +]; + +// $FlowFixMe[invalid-exported-annotation] Flow can't infer an exported annotation for an array rest binding +export const [arrayFirst, ...arrayRest] = arraySource; diff --git a/packages/metro/src/integration_tests/basic_bundle/import-export/index.js b/packages/metro/src/integration_tests/basic_bundle/import-export/index.js index 9c3ceadfb5..c0574ed7e9 100644 --- a/packages/metro/src/integration_tests/basic_bundle/import-export/index.js +++ b/packages/metro/src/integration_tests/basic_bundle/import-export/index.js @@ -14,6 +14,12 @@ import type {RequireWithUnstableImportMaybeSync} from './utils'; import {default as myDefault, foo as myFoo, myFunction} from './export-1'; import * as importStar from './export-2'; +import { + arrayFirst, + arrayRest, + objectRest, + renamedObject, +} from './export-destructuring'; import {namespaceReExport} from './export-namespace'; import {foo} from './export-null'; import primitiveDefault, { @@ -26,6 +32,8 @@ export {default as namedDefaultExported} from './export-3'; export {foo as default} from './export-4'; export const extraData = { + arrayFirst, + arrayRest, foo, importStar, myDefault, @@ -33,8 +41,10 @@ export const extraData = { myFunction: myFunction() as string, namespaceReExportDefault: namespaceReExport.default, namespaceReExportFoo: namespaceReExport.foo, + objectRest, primitiveDefault, primitiveFoo, + renamedObject, }; export const asyncImportCJS = import('./export-5');