Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ts] Insert export {} when necessary to imply ESM #13314

Merged

Conversation

wbinnssmith
Copy link
Contributor

@wbinnssmith wbinnssmith commented May 14, 2021

This diff is best viewed with whitespace diff disabled: https://github.com/babel/babel/pull/13314/files?w=1

This makes the TypeScript transformer insert an empty export {} declaration into files that would otherwise start as modules but become ambiguous after transformation, specifically when there are no imports of exports in the transformed result. See #12568 for context.

Since type imports and exports are removed, this change tracks when imports and exports are left behind as value imports/exports. If there are any left behind, the export {} expression does not get added.

Q                       A
Fixed Issues? Fixes #12568
Patch: Bug Fix? No
Major: Breaking Change? No
Minor: New Feature? Yes
Tests Added + Pass? Adjusted existing, which cover use
Documentation PR Link None
Any Dependency Changes? None
License MIT

@codesandbox-ci
Copy link

codesandbox-ci bot commented May 14, 2021

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

Latest deployment of this branch, based on commit 0fe318f:

Sandbox Source
babel-repl-custom-plugin Configuration
babel-plugin-multi-config Configuration

@wbinnssmith wbinnssmith changed the title Export an empty named export declaration to imply ESM TypeScript transform: export an empty named export declaration to imply ESM May 14, 2021
@wbinnssmith wbinnssmith changed the title TypeScript transform: export an empty named export declaration to imply ESM TypeScript transform: insert an empty named export declaration when necessary to imply ESM May 14, 2021
@@ -63,6 +63,8 @@ export default declare((api, opts) => {
var { allowDeclareFields = false } = opts;
}

let hasValueExports = false;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this could also be attached to state instead of closing over it

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can either use a WeakMap from the program node to this variable, or attach it to state. I prefer the WeakMap approach since it's similar to what we use in many other plugins.

Closing over causes problems, because Babel doesn't reinstantiate the plugin for each file but only when the options change.

Btw, do we need both hasValueExports/hasValueImports, or can it be a single boolean?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer the WeakMap approach since it's similar to what we use in many other plugins.

Great idea 🙂

Btw, do we need both hasValueExports/hasValueImports, or can it be a single boolean?

Nope, only one is necessary. I'll combine them.

@fedeci fedeci added area: typescript PR: Spec Compliance 👓 A type of pull request used for our changelog categories labels May 15, 2021
@wbinnssmith wbinnssmith force-pushed the wbinnssmith/empty-export-named-decl branch from 23a2480 to 8589908 Compare May 18, 2021 00:18
Copy link
Contributor

@JLHwung JLHwung left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM with some nits.

For reviewers: please ignore whitespace changes when reviewing.

}

IMPLICITLY_ESM.add(state.file.ast.program);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Alternatively we can add this to the ExportDeclaration visitor, it covers ExportExportAllDeclaration, ExportDefaultDeclaration and ExportNamedDeclaration.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We would have to be careful about ordering, since we only want to add the node to the WeakSet if it's not removed.

}

IMPLICITLY_ESM.add(state.file.ast.program);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Q: Why do we need this for ExportSpecifier?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah good point — looks like this is already handled above.

nicolo-ribaudo
nicolo-ribaudo previously approved these changes May 18, 2021
Copy link
Member

@nicolo-ribaudo nicolo-ribaudo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://github.com/babel/babel/pull/13314/files?diff=unified&w=1#r633982388 could probably be removed, but except for that lgtm 👍

@nicolo-ribaudo
Copy link
Member

Oh it looks like GH actions are down

@nicolo-ribaudo nicolo-ribaudo dismissed their stale review May 18, 2021 20:42

Regarding the failing test, I checked how TS handles var a: string: it doesn't inject export {} (https://www.typescriptlang.org/play?module=6#code/G4QwTgBCBcEM4BcwEsB2BzA3EA). However, with this PR Babel transforms it to var a; export {};. I think it would be better to align our behavior and only inject export {} if we actually removed import/export statements.

@wbinnssmith wbinnssmith force-pushed the wbinnssmith/empty-export-named-decl branch 2 times, most recently from 8bd591c to ba831ff Compare May 18, 2021 21:20
@babel-bot
Copy link
Collaborator

babel-bot commented May 18, 2021

Build successful! You can test your changes in the REPL here: https://babeljs.io/repl/build/46307/

@nicolo-ribaudo
Copy link
Member

Could you address #13314 (comment)? 🙏

@wbinnssmith wbinnssmith force-pushed the wbinnssmith/empty-export-named-decl branch from ba831ff to 97f9a7e Compare May 20, 2021 02:06
@wbinnssmith
Copy link
Contributor Author

wbinnssmith commented May 20, 2021

Could you address #13314 (comment)? 🙏

Ah, missed what you meant here. Just implemented this and checked a few of Babel's test cases agains the typescript playground and they behave the same now 😄

@wbinnssmith wbinnssmith force-pushed the wbinnssmith/empty-export-named-decl branch from 97f9a7e to 0fe318f Compare May 20, 2021 17:45
@nicolo-ribaudo nicolo-ribaudo changed the title TypeScript transform: insert an empty named export declaration when necessary to imply ESM [ts] Insert export {} when necessary to imply ESM May 20, 2021
@nicolo-ribaudo nicolo-ribaudo merged commit b4c798e into babel:main May 20, 2021
@nicolo-ribaudo
Copy link
Member

Thanks!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area: typescript outdated A closed issue/PR that is archived due to age. Recommended to make a new issue PR: Spec Compliance 👓 A type of pull request used for our changelog categories
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Typescript plugin doesn't insert export {} like tsc does
7 participants