From 8589908b11f17cf79df1c4a8a4db130a2a237c98 Mon Sep 17 00:00:00 2001 From: Will Binns-Smith Date: Fri, 14 May 2021 12:56:23 -0700 Subject: [PATCH] Export an empty named export declaration to imply ESM --- .../src/index.ts | 214 ++++++++++-------- .../catch-clause/param-type/output.mjs | 2 + .../fixtures/declarations/erased/output.mjs | 2 + .../declarations/nested-namespace/output.mjs | 2 + .../exports/export-type-from/output.mjs | 1 + .../fixtures/exports/export-type/output.mjs | 1 + .../imports/elide-injected/output.mjs | 1 + .../fixtures/imports/elide-preact/output.mjs | 1 + .../fixtures/imports/elide-react/output.mjs | 1 + .../fixtures/imports/elide-typeof/output.mjs | 1 + .../imports/elision-qualifiedname/output.mjs | 1 + .../imports/elision-rename/output.mjs | 1 + .../test/fixtures/imports/enum-id/output.mjs | 1 + .../import-type-not-removed/output.mjs | 1 + .../fixtures/imports/import-type/output.mjs | 1 + .../imports/property-signature/output.mjs | 1 + .../fixtures/namespace/canonical/output.mjs | 2 + .../namespace/clobber-class/output.mjs | 2 + .../namespace/clobber-enum/output.mjs | 2 + .../namespace/contentious-names/output.mjs | 2 + .../namespace/module-nested/output.mjs | 2 + .../fixtures/namespace/multiple/output.mjs | 2 + .../namespace-nested-module/output.mjs | 2 + .../namespace/nested-destructuring/output.mjs | 2 + .../test/fixtures/namespace/nested/output.mjs | 2 + .../fixtures/namespace/same-name/output.mjs | 2 + .../fixtures/namespace/undeclared/output.mjs | 2 + 27 files changed, 162 insertions(+), 92 deletions(-) diff --git a/packages/babel-plugin-transform-typescript/src/index.ts b/packages/babel-plugin-transform-typescript/src/index.ts index aec840965bd9..ffdea78f5c25 100644 --- a/packages/babel-plugin-transform-typescript/src/index.ts +++ b/packages/babel-plugin-transform-typescript/src/index.ts @@ -21,8 +21,12 @@ function isInType(path) { } } -const PARSED_PARAMS = new WeakSet(); const GLOBAL_TYPES = new WeakMap(); +// Track programs which contain imports/exports of values, so that we can include +// empty exports for programs that do not, but were parsed as modules. This allows +// tools to infer unamibiguously that results are ESM. +const IMPLICITLY_ESM = new WeakSet(); +const PARSED_PARAMS = new WeakSet(); function isGlobalType(path, name) { const program = path.find(path => path.isProgram()).node; @@ -175,118 +179,135 @@ export default declare((api, opts) => { Identifier: visitPattern, RestElement: visitPattern, - Program(path, state) { - const { file } = state; - let fileJsxPragma = null; - let fileJsxPragmaFrag = null; + Program: { + enter(path, state) { + const { file } = state; + let fileJsxPragma = null; + let fileJsxPragmaFrag = null; - if (!GLOBAL_TYPES.has(path.node)) { - GLOBAL_TYPES.set(path.node, new Set()); - } + if (!GLOBAL_TYPES.has(path.node)) { + GLOBAL_TYPES.set(path.node, new Set()); + } - if (file.ast.comments) { - for (const comment of file.ast.comments) { - const jsxMatches = JSX_PRAGMA_REGEX.exec(comment.value); - if (jsxMatches) { - if (jsxMatches[1]) { - // isFragment - fileJsxPragmaFrag = jsxMatches[2]; - } else { - fileJsxPragma = jsxMatches[2]; + if (file.ast.comments) { + for (const comment of file.ast.comments) { + const jsxMatches = JSX_PRAGMA_REGEX.exec(comment.value); + if (jsxMatches) { + if (jsxMatches[1]) { + // isFragment + fileJsxPragmaFrag = jsxMatches[2]; + } else { + fileJsxPragma = jsxMatches[2]; + } } } } - } - - let pragmaImportName = fileJsxPragma || jsxPragma; - if (pragmaImportName) { - [pragmaImportName] = pragmaImportName.split("."); - } - let pragmaFragImportName = fileJsxPragmaFrag || jsxPragmaFrag; - if (pragmaFragImportName) { - [pragmaFragImportName] = pragmaFragImportName.split("."); - } + let pragmaImportName = fileJsxPragma || jsxPragma; + if (pragmaImportName) { + [pragmaImportName] = pragmaImportName.split("."); + } - // remove type imports - for (let stmt of path.get("body")) { - if (stmt.isImportDeclaration()) { - if (stmt.node.importKind === "type") { - stmt.remove(); - continue; - } + let pragmaFragImportName = fileJsxPragmaFrag || jsxPragmaFrag; + if (pragmaFragImportName) { + [pragmaFragImportName] = pragmaFragImportName.split("."); + } - // If onlyRemoveTypeImports is `true`, only remove type-only imports - // and exports introduced in TypeScript 3.8. - if (!onlyRemoveTypeImports) { - // Note: this will allow both `import { } from "m"` and `import "m";`. - // In TypeScript, the former would be elided. - if (stmt.node.specifiers.length === 0) { + // remove type imports + for (let stmt of path.get("body")) { + if (stmt.isImportDeclaration()) { + if (stmt.node.importKind === "type") { + stmt.remove(); continue; } - let allElided = true; - const importsToRemove: NodePath[] = []; - - for (const specifier of stmt.node.specifiers) { - const binding = stmt.scope.getBinding(specifier.local.name); - - // The binding may not exist if the import node was explicitly - // injected by another plugin. Currently core does not do a good job - // of keeping scope bindings synchronized with the AST. For now we - // just bail if there is no binding, since chances are good that if - // the import statement was injected then it wasn't a typescript type - // import anyway. - if ( - binding && - isImportTypeOnly({ - binding, - programPath: path, - pragmaImportName, - pragmaFragImportName, - }) - ) { - importsToRemove.push(binding.path); - } else { - allElided = false; + // If onlyRemoveTypeImports is `true`, only remove type-only imports + // and exports introduced in TypeScript 3.8. + if (onlyRemoveTypeImports) { + IMPLICITLY_ESM.add(path.node); + } else { + // Note: this will allow both `import { } from "m"` and `import "m";`. + // In TypeScript, the former would be elided. + if (stmt.node.specifiers.length === 0) { + IMPLICITLY_ESM.add(path.node); + continue; } - } - if (allElided) { - stmt.remove(); - } else { - for (const importPath of importsToRemove) { - importPath.remove(); + let allElided = true; + const importsToRemove: NodePath[] = []; + + for (const specifier of stmt.node.specifiers) { + const binding = stmt.scope.getBinding(specifier.local.name); + + // The binding may not exist if the import node was explicitly + // injected by another plugin. Currently core does not do a good job + // of keeping scope bindings synchronized with the AST. For now we + // just bail if there is no binding, since chances are good that if + // the import statement was injected then it wasn't a typescript type + // import anyway. + if ( + binding && + isImportTypeOnly({ + binding, + programPath: path, + pragmaImportName, + pragmaFragImportName, + }) + ) { + importsToRemove.push(binding.path); + } else { + allElided = false; + IMPLICITLY_ESM.add(path.node); + } + } + + if (allElided) { + stmt.remove(); + } else { + for (const importPath of importsToRemove) { + importPath.remove(); + } } } - } - continue; - } + continue; + } - if (stmt.isExportDeclaration()) { - stmt = stmt.get("declaration"); - } + if (stmt.isExportDeclaration()) { + stmt = stmt.get("declaration"); + } - if (stmt.isVariableDeclaration({ declare: true })) { - for (const name of Object.keys(stmt.getBindingIdentifiers())) { - registerGlobalType(path.scope, name); + if (stmt.isVariableDeclaration({ declare: true })) { + for (const name of Object.keys(stmt.getBindingIdentifiers())) { + registerGlobalType(path.scope, name); + } + } else if ( + stmt.isTSTypeAliasDeclaration() || + stmt.isTSDeclareFunction() || + stmt.isTSInterfaceDeclaration() || + stmt.isClassDeclaration({ declare: true }) || + stmt.isTSEnumDeclaration({ declare: true }) || + (stmt.isTSModuleDeclaration({ declare: true }) && + stmt.get("id").isIdentifier()) + ) { + registerGlobalType(path.scope, stmt.node.id.name); } - } else if ( - stmt.isTSTypeAliasDeclaration() || - stmt.isTSDeclareFunction() || - stmt.isTSInterfaceDeclaration() || - stmt.isClassDeclaration({ declare: true }) || - stmt.isTSEnumDeclaration({ declare: true }) || - (stmt.isTSModuleDeclaration({ declare: true }) && - stmt.get("id").isIdentifier()) + } + }, + exit(path) { + if ( + !IMPLICITLY_ESM.has(path.node) && + path.node.sourceType === "module" ) { - registerGlobalType(path.scope, stmt.node.id.name); + // If there are no remaining value exports, this file can no longer + // be inferred to be ESM. Leave behind an empty export declaration + // so it can be. + path.pushContainer("body", t.exportNamedDeclaration()); } - } + }, }, - ExportNamedDeclaration(path) { + ExportNamedDeclaration(path, state) { if (path.node.exportKind === "type") { path.remove(); return; @@ -307,24 +328,33 @@ export default declare((api, opts) => { ) ) { path.remove(); + return; } + + IMPLICITLY_ESM.add(state.file.ast.program); }, - ExportSpecifier(path) { + ExportSpecifier(path, state) { // remove type exports if (!path.parent.source && isGlobalType(path, path.node.local.name)) { path.remove(); + return; } + + IMPLICITLY_ESM.add(state.file.ast.program); }, - ExportDefaultDeclaration(path) { + ExportDefaultDeclaration(path, state) { // remove whole declaration if it's exporting a TS type if ( t.isIdentifier(path.node.declaration) && isGlobalType(path, path.node.declaration.name) ) { path.remove(); + return; } + + IMPLICITLY_ESM.add(state.file.ast.program); }, TSDeclareFunction(path) { diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/catch-clause/param-type/output.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/catch-clause/param-type/output.mjs index 0ead89004c5c..b87619d40797 100644 --- a/packages/babel-plugin-transform-typescript/test/fixtures/catch-clause/param-type/output.mjs +++ b/packages/babel-plugin-transform-typescript/test/fixtures/catch-clause/param-type/output.mjs @@ -1 +1,3 @@ try {} catch (e) {} + +export {}; diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/declarations/erased/output.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/declarations/erased/output.mjs index dab5d3d1c154..b7786bef8d9d 100644 --- a/packages/babel-plugin-transform-typescript/test/fixtures/declarations/erased/output.mjs +++ b/packages/babel-plugin-transform-typescript/test/fixtures/declarations/erased/output.mjs @@ -1 +1,3 @@ ; // Otherwise-empty file + +export {}; diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/declarations/nested-namespace/output.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/declarations/nested-namespace/output.mjs index dab5d3d1c154..b7786bef8d9d 100644 --- a/packages/babel-plugin-transform-typescript/test/fixtures/declarations/nested-namespace/output.mjs +++ b/packages/babel-plugin-transform-typescript/test/fixtures/declarations/nested-namespace/output.mjs @@ -1 +1,3 @@ ; // Otherwise-empty file + +export {}; diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/exports/export-type-from/output.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/exports/export-type-from/output.mjs index 092bc2b04126..95da36c2f396 100644 --- a/packages/babel-plugin-transform-typescript/test/fixtures/exports/export-type-from/output.mjs +++ b/packages/babel-plugin-transform-typescript/test/fixtures/exports/export-type-from/output.mjs @@ -1 +1,2 @@ ; +export {}; diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/exports/export-type/output.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/exports/export-type/output.mjs index 092bc2b04126..95da36c2f396 100644 --- a/packages/babel-plugin-transform-typescript/test/fixtures/exports/export-type/output.mjs +++ b/packages/babel-plugin-transform-typescript/test/fixtures/exports/export-type/output.mjs @@ -1 +1,2 @@ ; +export {}; diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/imports/elide-injected/output.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/imports/elide-injected/output.mjs index d049b7aa5dfe..6ddf72d41e2a 100644 --- a/packages/babel-plugin-transform-typescript/test/fixtures/imports/elide-injected/output.mjs +++ b/packages/babel-plugin-transform-typescript/test/fixtures/imports/elide-injected/output.mjs @@ -1,2 +1,3 @@ import local from "source"; local(); +export {}; diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/imports/elide-preact/output.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/imports/elide-preact/output.mjs index 0773757932ed..a8ee57e62871 100644 --- a/packages/babel-plugin-transform-typescript/test/fixtures/imports/elide-preact/output.mjs +++ b/packages/babel-plugin-transform-typescript/test/fixtures/imports/elide-preact/output.mjs @@ -1 +1,2 @@ const x = 0; +export {}; diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/imports/elide-react/output.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/imports/elide-react/output.mjs index 0773757932ed..a8ee57e62871 100644 --- a/packages/babel-plugin-transform-typescript/test/fixtures/imports/elide-react/output.mjs +++ b/packages/babel-plugin-transform-typescript/test/fixtures/imports/elide-react/output.mjs @@ -1 +1,2 @@ const x = 0; +export {}; diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/imports/elide-typeof/output.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/imports/elide-typeof/output.mjs index 0773757932ed..a8ee57e62871 100644 --- a/packages/babel-plugin-transform-typescript/test/fixtures/imports/elide-typeof/output.mjs +++ b/packages/babel-plugin-transform-typescript/test/fixtures/imports/elide-typeof/output.mjs @@ -1 +1,2 @@ const x = 0; +export {}; diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/imports/elision-qualifiedname/output.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/imports/elision-qualifiedname/output.mjs index 0773757932ed..a8ee57e62871 100644 --- a/packages/babel-plugin-transform-typescript/test/fixtures/imports/elision-qualifiedname/output.mjs +++ b/packages/babel-plugin-transform-typescript/test/fixtures/imports/elision-qualifiedname/output.mjs @@ -1 +1,2 @@ const x = 0; +export {}; diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/imports/elision-rename/output.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/imports/elision-rename/output.mjs index 0773757932ed..a8ee57e62871 100644 --- a/packages/babel-plugin-transform-typescript/test/fixtures/imports/elision-rename/output.mjs +++ b/packages/babel-plugin-transform-typescript/test/fixtures/imports/elision-rename/output.mjs @@ -1 +1,2 @@ const x = 0; +export {}; diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/imports/enum-id/output.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/imports/enum-id/output.mjs index 628c6ba0ff86..63d68f19ffe6 100644 --- a/packages/babel-plugin-transform-typescript/test/fixtures/imports/enum-id/output.mjs +++ b/packages/babel-plugin-transform-typescript/test/fixtures/imports/enum-id/output.mjs @@ -5,3 +5,4 @@ var Enum; })(Enum || (Enum = {})); ; +export {}; diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/imports/import-type-not-removed/output.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/imports/import-type-not-removed/output.mjs index 3e83039477c8..d3b5fc3d17c6 100644 --- a/packages/babel-plugin-transform-typescript/test/fixtures/imports/import-type-not-removed/output.mjs +++ b/packages/babel-plugin-transform-typescript/test/fixtures/imports/import-type-not-removed/output.mjs @@ -1,2 +1,3 @@ // TODO: This should not be removed ; +export {}; diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/imports/import-type/output.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/imports/import-type/output.mjs index 092bc2b04126..95da36c2f396 100644 --- a/packages/babel-plugin-transform-typescript/test/fixtures/imports/import-type/output.mjs +++ b/packages/babel-plugin-transform-typescript/test/fixtures/imports/import-type/output.mjs @@ -1 +1,2 @@ ; +export {}; diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/imports/property-signature/output.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/imports/property-signature/output.mjs index cba06b741a87..f88bedaa1f22 100644 --- a/packages/babel-plugin-transform-typescript/test/fixtures/imports/property-signature/output.mjs +++ b/packages/babel-plugin-transform-typescript/test/fixtures/imports/property-signature/output.mjs @@ -1,3 +1,4 @@ const obj = { A: 'foo' }; +export {}; diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/canonical/output.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/canonical/output.mjs index f6993c98b8f4..889b59b131c5 100644 --- a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/canonical/output.mjs +++ b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/canonical/output.mjs @@ -37,3 +37,5 @@ for (let s of strings) { console.log(`"${s}" - ${validators[name].isAcceptable(s) ? "matches" : "does not match"} ${name}`); } } + +export {}; diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/clobber-class/output.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/clobber-class/output.mjs index 98c88f006331..f7adb2fd6da0 100644 --- a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/clobber-class/output.mjs +++ b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/clobber-class/output.mjs @@ -3,3 +3,5 @@ class A {} (function (_A) { const B = _A.B = 1; })(A || (A = {})); + +export {}; diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/clobber-enum/output.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/clobber-enum/output.mjs index 5a1aa715f308..17f7f192da9b 100644 --- a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/clobber-enum/output.mjs +++ b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/clobber-enum/output.mjs @@ -7,3 +7,5 @@ var A; (function (_A) { const B = _A.B = 1; })(A || (A = {})); + +export {}; diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/contentious-names/output.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/contentious-names/output.mjs index 749100aaa780..dce6856390f7 100644 --- a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/contentious-names/output.mjs +++ b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/contentious-names/output.mjs @@ -137,3 +137,5 @@ let N; (function (_flatMap) {})(flatMap || (flatMap = {})); })(N || (N = {})); + +export {}; diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/module-nested/output.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/module-nested/output.mjs index 0078a2549068..b20510f1b189 100644 --- a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/module-nested/output.mjs +++ b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/module-nested/output.mjs @@ -21,3 +21,5 @@ let src; _ns2.foo = foo; })(ns2 || (ns2 = _src.ns2 || (_src.ns2 = {}))); })(src || (src = {})); + +export {}; diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/multiple/output.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/multiple/output.mjs index 12b4c7a92253..47b1ff88552b 100644 --- a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/multiple/output.mjs +++ b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/multiple/output.mjs @@ -3,3 +3,5 @@ let N; (function (_N) {})(N || (N = {})); (function (_N2) {})(N || (N = {})); + +export {}; diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/namespace-nested-module/output.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/namespace-nested-module/output.mjs index 7d9a95580e17..d71910f2b912 100644 --- a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/namespace-nested-module/output.mjs +++ b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/namespace-nested-module/output.mjs @@ -11,3 +11,5 @@ let N; (function (_M2) {})(M2 || (M2 = _N.M2 || (_N.M2 = {}))); })(N || (N = {})); + +export {}; diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/nested-destructuring/output.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/nested-destructuring/output.mjs index 7ba530d74ec9..905fe3646049 100644 --- a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/nested-destructuring/output.mjs +++ b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/nested-destructuring/output.mjs @@ -22,3 +22,5 @@ let N; } = C; _N.e = e, _N.c = c, _N.d = d; })(N || (N = {})); + +export {}; diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/nested/output.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/nested/output.mjs index 1e85423d2c94..31268af14753 100644 --- a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/nested/output.mjs +++ b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/nested/output.mjs @@ -47,3 +47,5 @@ class A {} L[L["M"] = 19] = "M"; })(L || (L = {})); })(A || (A = {})); + +export {}; diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/same-name/output.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/same-name/output.mjs index b8db977b690f..e696e0e2c6fa 100644 --- a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/same-name/output.mjs +++ b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/same-name/output.mjs @@ -27,3 +27,5 @@ let N; _N9._N = _N; })(N || (N = _N2.N || (_N2.N = {}))); })(N || (N = {})); + +export {}; diff --git a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/undeclared/output.mjs b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/undeclared/output.mjs index eca9b4570a75..f73af1fcfe84 100644 --- a/packages/babel-plugin-transform-typescript/test/fixtures/namespace/undeclared/output.mjs +++ b/packages/babel-plugin-transform-typescript/test/fixtures/namespace/undeclared/output.mjs @@ -1,3 +1,5 @@ let N; (function (_N) {})(N || (N = {})); + +export {};