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

Bugfix allow re-export of existing named members #698

Merged
merged 1 commit into from
Jun 21, 2022

Conversation

cpitt
Copy link
Contributor

@cpitt cpitt commented Jun 3, 2022

Add configurable: true to Object.defineProperty to allow overwriting existing named exports.

Consider the following valid code

// lib1.ts
export const a = '1';

// lib2.ts
export const a = '2'

// index.ts
export * from 'lib1.ts'
export * from 'lib2.ts'
export { a } from 'lib2.ts'

Currently tsc and babel compile this fine but sucrase fails with

TypeError: Cannot redefine property: a
        at Function.defineProperty (<anonymous>)

duplicate exports.

Consider the following valid code

```typescript
// lib1.ts
export const a = '1';

// lib2.ts
export const a = '2'

// index.ts
export * from 'lib1.ts'
export * from 'lib2.ts'
export { a } from 'lib2.ts'

```
Currently tsc and babel compile this fine but sucrase fails with

```
TypeError: Cannot redefine property: a
        at Function.defineProperty (<anonymous>)
```
@cpitt cpitt changed the title Bugfix allow re-export of existed named members Bugfix allow re-export of existing named members Jun 3, 2022
@codecov
Copy link

codecov bot commented Jun 6, 2022

Codecov Report

Merging #698 (dc899cf) into main (2658878) will not change coverage.
The diff coverage is n/a.

@@           Coverage Diff           @@
##             main     #698   +/-   ##
=======================================
  Coverage   82.73%   82.73%           
=======================================
  Files          56       56           
  Lines        5694     5694           
  Branches     1343     1343           
=======================================
  Hits         4711     4711           
  Misses        700      700           
  Partials      283      283           
Impacted Files Coverage Δ
src/HelperManager.ts 100.00% <ø> (ø)

📣 Codecov can now indicate which changes are the most critical in Pull Requests. Learn more

@github-actions
Copy link

github-actions bot commented Jun 6, 2022

Benchmark results

Before this PR: 254.3 thousand lines per second
After this PR: 262.4 thousand lines per second

Measured change: 3.19% faster (0.85% faster to 4.57% faster)
Summary: Probably faster

@alangpierce
Copy link
Owner

Hi @cpitt, thanks! I'm a bit busy right now but should be able to take a look in the next few days.

Copy link
Owner

@alangpierce alangpierce left a comment

Choose a reason for hiding this comment

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

Thanks! And sorry for the delay.

This does move some cases a little away from true spec compliance by making the property configurable, though I'd be very surprised if that caused trouble. Generally, Sucrase is ok with being more permissive than other transpilers/implementations, though usually that comes up in terms of compile-time errors that Sucrase skips rather than runtime behavior differences.

There's actually a way to write a test for cases like these using assertMultiFileOutput in imports-test.ts, so I'll add a quick test for this case in a follow-up and publish.

As part of investigating this, I took a look at the behavior with various situations and different tools:

Your example Just two star exports Named export at beginning
Sucrase before Crashes, Cannot redefine property: a Exports 1, not configurable Exports 2, not configurable
Sucrase after Exports 2, configurable Exports 1, configurable Exports 2, configurable
Babel Exports 2, not configurable Crashes, Cannot redefine property: a Exports 2, not configurable
TypeScript Exports 2, configurable Exports 1, not configurable Exports 2, configurable
Node.js 18 ESM Exports 2, not configurable Crashes, The requested module './index.js' contains conflicting star exports for name 'a' Exports 2, not configurable

Some notes from the comparison here and some additional testing:

  • It looks like the ideal behavior (assuming node implements this correctly) is that ambiguous star re-exports are disallowed, but if an explicit re-export clears up the ambiguity, then that's fine. So your example code is perfectly valid and should export '2'.
  • Really, all exports are supposed to be non-configurable in ESM, whether it's a star re-export, a named re-export, or a direct export. Node gets this correct, of course, but all transpilers use a configurable property for direct exports, and TS uses a configurable property for named re-exports. So there's certainly precedent for configurable properties, and really in the vast majority of cases (i.e. direct exports) they are configurable in all transpilers.

To be clear, here's how this case works in Babel and TS:

  • Babel creates a _exportNames object that's hoisted near the top of the file and includes the names of all explicitly-exported values. The export * from syntax knows to skip these.
  • TypeScript injects a line exports.a = void 0; before any import code, which sets a as a configurable property, and later defineProperty calls keep it configurable. Explicit re-exports take precedence over star re-exports, regardless of ordering, because star re-exports don't modify the exports object if the property already exists.

Both of these approaches are certainly doable in Sucrase (and the babel one seems better if I were to pick), though I think the simpler approach in this PR is the right approach for now.

@alangpierce alangpierce merged commit ce4dc62 into alangpierce:main Jun 21, 2022
alangpierce added a commit that referenced this pull request Jun 21, 2022
@alangpierce
Copy link
Owner

Just released as sucrase 3.21.1.

@cpitt
Copy link
Contributor Author

cpitt commented Jun 21, 2022

Thanks for the detailed response @alangpierce I really appreciate it. I learned a lot from that post.

1Lighty pushed a commit to Astra-mod/sucrase that referenced this pull request Aug 14, 2022
…duplicate exports. (alangpierce#698)

Consider the following valid code

```typescript
// lib1.ts
export const a = '1';

// lib2.ts
export const a = '2'

// index.ts
export * from 'lib1.ts'
export * from 'lib2.ts'
export { a } from 'lib2.ts'

```
Currently tsc and babel compile this fine but sucrase fails with

```
TypeError: Cannot redefine property: a
        at Function.defineProperty (<anonymous>)
```

Co-authored-by: cpitt <[email protected]>
1Lighty pushed a commit to Astra-mod/sucrase that referenced this pull request Aug 14, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants