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

fix(compartment-mapper): Support ESM imports defined property from CJ… #2330

Merged
merged 1 commit into from
Jul 17, 2024

Conversation

kriskowal
Copy link
Member

@kriskowal kriskowal commented Jun 22, 2024

…S from set property of CJS

Refs: Agoric/agoric-sdk#9408

Description

If a modern JavaScript module imports a named binding from a CommonJS module, we can hardly guarantee that it will always work. Outrageous heroics make it work under most circumstances using a heuristic lexical analysis of the CommonJS module to determine which names should exist on the module’s internal namespace. Then, the CommonJS runtime must account for all variety of shenanigans that might cause the binding for that name to change. These include assignment to property names on exports, reassignment to module.exports, and defineProperty on the original exports object. These changes must be reflected both on the module namespace object and on the default property thereof in in every case where this is in fact possible.

Consider a CJS module (2.cjs) that exports a name:

exports.meaning = 42;

Then, consider a TypeScript module (1.ts) that reexports this name:

export { meaning } from './2.cjs';

Which gets compiled down to a CommonJS module (1.cjs):

const universe = require('./1.cjs');
Object.defineProperty(exports, 'meaning', {
  enumerable: true,
  get() {
    return universe.meaning
  }
});

Which gets imported by an ESM by name (0.mjs):

import { meaning } from './1.cjs';

This has no right to work. We can trivially copy the getter from 1.cjs to the default export but the internal namespace object we use to emulate ESM in SES emits live binding notifications for every named property and doesn’t suffer defineProperty well. So, in the CommonJS emulation, we must attempt to propagate the current value from the getter to the module environment record after the module initializes.

Security Considerations

None.

Scaling Considerations

None.

Documentation Considerations

None. Folks already assume this works. It works in Node.js. Code exists that relies on it.

Testing Considerations

We have an existing test that exercises the case that the getter under a defineProperty can’t be expected to succeed on the stack of defineProperty.

Compatibility Considerations

This increases ecosystem compatibility.

Upgrade Considerations

None.

PS

It didn’t have to be like this.

@naugtur
Copy link
Member

naugtur commented Jun 24, 2024

I think we should add this case to the matrix in https://github.com/endojs/endo-e2e-tests (along with some new runtimes BTW) and see how it compares.

Also, I wonder if there's a noticeable change in the e2e test results form this. I remember at the time the cjs implementation was passing e2e with few exports missing and some exports being redundant.

One thing we're guaranteed to never fix is redundant default. I hope that never becomes an issue.

Anyway, I think a run of e2e on main vs this could be informative.

[edit]

updated the matrix endojs/endo-e2e-tests#10

https://github.com/endojs/endo-e2e-tests/blob/ccb193d8bdd04d03a93507090d932aced0019b27/matrix/table.md

@@ -176,10 +181,14 @@ export const wrap = ({
if (exportsHaveBeenOverwritten) {
moduleEnvironmentRecord.default = finalExports;
for (const prop of keys(moduleEnvironmentRecord.default || {})) {
if (prop !== 'default')
if (prop !== 'default') {
Copy link
Contributor

Choose a reason for hiding this comment

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

(suggestion) have a

export const DEFAULT = 'default';

somewhere and use it

import.meta.url,
).toString();

const assertFixture = t => t.pass();
Copy link
Contributor

Choose a reason for hiding this comment

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

this means "if this fixture does not error, we're good"?

@kriskowal
Copy link
Member Author

@naugtur Can I beg a ruling on this change?

mergify bot added a commit to Agoric/agoric-sdk that referenced this pull request Jun 28, 2024
closes: #9408

## Description

Pending endojs/endo#2330, we patch `@cosmjs/math` such that imports from ESM reflect the exported types.

### Security Considerations

None.

### Scaling Considerations

None.

### Documentation Considerations

None.

### Testing Considerations

Included test.

### Upgrade Considerations

None.
@kriskowal kriskowal force-pushed the kriskowal-fix-esm-import-cjs-define branch 2 times, most recently from c1a52fd to c838eff Compare July 3, 2024 01:07
}
}
for (const prop of redefined) {
Copy link
Member

Choose a reason for hiding this comment

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

there could be an else before this because redefined doesn't make a lot of sense when the default export was redefined. but it should also be an empty set in that case.

@kriskowal kriskowal force-pushed the kriskowal-fix-esm-import-cjs-define branch from c838eff to bfd51ee Compare July 17, 2024 22:48
@kriskowal kriskowal merged commit 3e3b8c6 into master Jul 17, 2024
17 checks passed
@kriskowal kriskowal deleted the kriskowal-fix-esm-import-cjs-define branch July 17, 2024 22:57
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.

3 participants