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

Support export * from 'module' ESM syntax #43

Merged
merged 5 commits into from
Dec 13, 2023

Conversation

jsumners-nr
Copy link
Contributor

This change adds support for modules that export entities through the export * from 'module' ESM syntax. This resolves issue #31.

@jsumners-nr
Copy link
Contributor Author

Putting this in draft because there is another case that needs to be handled.

@jsumners-nr jsumners-nr marked this pull request as draft December 7, 2023 17:53
@jsumners-nr jsumners-nr marked this pull request as ready for review December 7, 2023 20:54
@jsumners-nr
Copy link
Contributor Author

Okay, I can't promise this is going to work for everything, but it at least clear the reproduction case provided in #31.

This change adds support for modules that export entities through the
`export * from 'module'` ESM syntax. This resolves issue nodejs#31.
hook.js Outdated Show resolved Hide resolved
@bengl bengl merged commit 5ec0576 into nodejs:main Dec 13, 2023
43 checks passed
@jsumners-nr jsumners-nr deleted the star-export branch December 13, 2023 20:04
bengl pushed a commit that referenced this pull request Jan 8, 2024
Ref: newrelic/node-newrelic#1920

<strike>👋 my last "fix" (#43) broke in a new way thanks to the helpful
ESM allowance of exporting `default` multiple times. This PR resolves
the issue. I suspect there could be further problems with the exports
since I have no idea how exporting `default` multiple times is meant to
be interpreted, but I suspect any such issues will be an extreme edge
case.

The only other thing I can think of to do here is to somehow track that
we already have a `default` export and munge the names of any subsequent
ones.</strike>

-----

I have since learned that ESM doesn't actually allow exporting `default`
multiple times. Transitive `default` exports get mapped to some other
name for the module that has imported them, and only the directly
imported `default` gets exported. Still, we have to handle figuring out
which `default` is the right one and construct our shim namespace
accordingly.

-----

2023-01-03: this can only be supported on Node versions where we parse
the modules with an AST parser and interpret the code manually. So I
have updated the test suite for this feature to only run on such
versions.

---------

Co-authored-by: James Sumners <[email protected]>
bengl pushed a commit that referenced this pull request Apr 17, 2024
The README example says the Hook callback `exported` arg is
"effectively `import * as exported from ${url}`".
https://tc39.es/ecma262/#sec-module-namespace-objects specs that
a Module Namespace Object has a `@@toStringTag` property with value
"Module" and no constructor.

Fixes: #57
Obsoletes: #64

* * *

This behaviour changed with the changes in #43 when the
`register(...)`'d namespace changed from using an actual imported module
object to using a plain object with module properties copied over to it:

https://github.com/DataDog/import-in-the-middle/pull/43/files#diff-e69a24a4c3746fa1ee96a78e12bb12d2dd4eb6e4cacbced2bf1f4084952681d9L130-R208
I suspect that was an unintentional change.

The main question here is **whether you would like import-in-the-middle
to promise this**: that the `exported` namespace returned to the `Hook`
callback mimics this aspect of `import * as exported from '...'`.

As links to #57 show, this would help the OpenTelemetry JS project. I
[started](open-telemetry/opentelemetry-js-contrib#1694 (comment))
using `exported[Symbol.toStringTag]` in OpenTelemetry instrumentations a
while back as a way to handle differences in instrumentating a module
based on whether it was being used from ESM code vs CommonJS code. This
is convenient because **OpenTelemetry core instrumentation code uses the
same hook function for require-in-the-middle and import-in-the-middle
hooks**. It also seemed reasonable given the `Module Namespace Object`
spec entry. However, I grant that the `exported` arg need not be a
Module Namespace Object.

* * *

Assume you are willing to accept this, a note on my implementation:

I chose to explicitly add the `@@toStringTag` property because:
- it is more explicit
- the `for (const k of Object.getOwnPropertySymbols(primary)) { ... }`
alternative (proposed in #57 and #64) will only ever include the
`@@toStringTag`. Assuming my read of the
https://tc39.es/ecma262/#sec-module-namespace-objects and
https://tc39.es/ecma262/#sec-module-namespace-exotic-objects sections is
correct, the module object will only ever have *string* keys (for the
"export"s), plus the one `@@toStringTag` property.
- the `@@toStringTag` property should not be enumerable (i.e.
`Object.getOwnPropertyDescriptor(exported,
Symbol.toStringTag).enumerable === false`). The other implementation
does not persist that descriptor value.
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