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

Include named exports in CJS shims when using importSync #1531

Merged
merged 7 commits into from
Jul 16, 2023

Conversation

chancancode
Copy link
Contributor

importSync() is intended to work like import() (buy sync), which, in turns, is expected to work like a namespace import – import * as NS from "foo";. In general, we would expect that to be interchangable with const NS = importSync("foo");.

Prior to this commit, this is not true for commonjs packages:

import * as QUnit1 from "qunit";
// => { QUnit, assert, begin, config, ... }

QUnit1.assert // => function

const QUnit2 = importSync("qunit");
// => { default: { QUnit, assert, begin, config, ... } }

QUnit2.assert // => undefined

Of course, using ES imports on commonjs packages is, in itself, an ill-defined concept, but nevertheless, it is a very common interop requirement.

The previous behavior is introduced in #1076. The intent appears to be fixing a different interop issue, where we would expect these to work the same way:

import QUnit1 from "qunit";
// => { Qunit, assert, begin, config, ... }

QUnit1.assert // => function

const QUnit2 = importSync("qunit").default;
// => { QUnit, assert, begin, config, ... }

QUnit2.assert // => function

The fix in #1076, however, broke the exepctation that import() should behave like a namespace improt statement for CJS modules.

This commit attempts to satisfy both of these requirements, with one caveat: importSync("foo").default is present on the shimed CJS modules, but in import * as FOO from "foo";, FOO.default is undefined.

Arguably, this is a bug in webpack's implementation – if you are able to write an import statement that references the default export, you should be able to do the same in the namespace import as per the spec.

So, the implementation here is different but more correct.

@chancancode
Copy link
Contributor Author

Didn't find an integration/end-to-end test for importing cjs packages, so settled with a unit test. If I should add a test somewhere else instead let me know.

`importSync()` is intended to work like `import()` (buy sync),
which, in turns, is expected to work like a namespace import –
`import * as NS from "foo";`. In general, we would expect that to
be interchangable with `const NS = importSync("foo");`.

Prior to this commit, this is not true for commonjs packages:

```js
import * as QUnit1 from "qunit";
// => { QUnit, assert, begin, config, ... }

QUnit1.assert // => function

const QUnit2 = importSync("qunit");
// => { default: { QUnit, assert, begin, config, ... } }

QUnit2.assert // => undefined
```

Of course, using ES imports on commonjs packages is, in itself, an
ill-defined concept, but nevertheless, it is a very common interop
requirement.

The previous behavior is introduced in embroider-build#1076. The intent appears to
be fixing a different interop issue, where we would expect these to
work the same way:

```js
import QUnit1 from "qunit";
// => { Qunit, assert, begin, config, ... }

QUnit1.assert // => function

const QUnit2 = importSync("qunit").default;
// => { QUnit, assert, begin, config, ... }

QUnit2.assert // => function
```

The fix in embroider-build#1076, however, broke the exepctation that `import()`
should behave like a namespace improt statement for CJS modules.

This commit attempts to satisfy both of these requirements, with
one caveat: `importSync("foo").default` is present on the shimed
CJS modules, but in `import * as FOO from "foo";`, `FOO.default`
is undefined.

Arguably, this is a bug in webpack's implementation – if you are
able to write an import statement that references the default
export, you should be able to do the same in the namespace import
as per the spec.

So, the implementation here is different but more correct.

Fixes embroider-build#1530
@ef4
Copy link
Contributor

ef4 commented Jul 14, 2023

Thanks. The test wasn't really doing what you wanted it to do -- that particular file is misleading and kind of silly. It was only testing build-time code and couldn't test runtime code.

I moved it into a place that will do an end-to-end test.

(We definitely need some better contributing docs on how to organize tests.)

ef4 added 2 commits July 15, 2023 02:13
In classic builds, multiple different versions of `@embroider/macros` may get smashed together, overwriting each other's files in ways that we  cannot control.

If we want packages that upgrade to get this bugfix to reliably have it, they need to use a new name to refer to the module.
@ef4
Copy link
Contributor

ef4 commented Jul 15, 2023

Making this do the right thing on classic builds is more annoying than it would seem, given that classic builds mash all the copies of all the different versions of @embroider/macros together. It necessitates picking a new name for the module.

Then that introduced test failures under embroider, due to an outstanding issue that I'm already working on. I can land this after landing that fix.

@ef4 ef4 merged commit 0912a40 into embroider-build:main Jul 16, 2023
@ef4 ef4 added the bug Something isn't working label Jul 16, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants