-
-
Notifications
You must be signed in to change notification settings - Fork 601
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(commonjs): support CJS modules re-exporting transpiled ESM modules #1165
fix(commonjs): support CJS modules re-exporting transpiled ESM modules #1165
Conversation
BREAKING CHANGES: requires Node 12 No longer supports require.cache
BREAKING CHANGES: Requires at least rollup@2.68.0
This is exhibited for example in Next.js, see https://github.com/vercel/next.js/blob/5feb400aff8e7b8968174b4e339b98ce48412180/packages/next/link.js#L1 which doesn't specify __esModule but re-exports a module that does. See https://www.runpkg.com/?next@12.1.4/link.js and https://www.runpkg.com/?next@12.1.4/dist/client/link.js for the compiled example.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it looks good and is rather minimal.
Two things:
- Could you convert the tests as I outlined?
- Could you rebase to and change your PR target directly to the
commonjs/strict-require-order
branch? Then I will make sure they are released together and I do not need to resolve the merge conflicts :) I really want to release that thing now.
packages/commonjs/test/test.js
Outdated
@@ -258,6 +259,78 @@ test('import CommonJS module with esm property should get default export ', asyn | |||
t.is(result2.error.message, 'lib is not a function'); | |||
}); | |||
|
|||
test('import CommonJS module with re-exported esm default should get default export', async (t) => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you do me a favour and instead of adding to this huge test file (which I would really hope to get rid of eventually, but we need it for some tricky caching things) and instead convert them to "function" tests? The reason is that for those tests, everything is neatly together in a separate folder and you do not have to scroll and search around for your fixtures.
Basically for each test, you add a folder to test/fixtures/function
with your test fixtures (entry should always be main.js
) and a _config.js
file as well with something like
module.exports: {
description: '...some text to describe the test case',
// options for the commonjs plugin
pluginOptions: {
defaultIsModuleExports: 'auto'
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Of course, will do in the next day or two! Thanks for the speedy review :)
@@ -0,0 +1,11 @@ | |||
import * as entry from './proxy'; | |||
|
|||
// Note: There ideally shouldn't be a default generated property. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note this particular bug that the change introduces. I'm not sure it's possible to avoid it, but would love suggestions.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, will need to thing about this. In any case your observation
In order for this to work, we need to check the value __esModule at runtime (unless we prefer to analyze files recursively with Rollup, which appears difficult given Rollup's architecture).
is not correct. Rollup can "look into a module before including it" vie this.load
, and as a matter of fact, it already does extensively in resolve-require-sources
. So it should actually be possible to extend this logic to figure out e.g. if we want to create a default export I guess (currently we only know if we are requiring CJS or ES), but this will make things MUCH more complicated.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah! That's useful info. I'd be willing to explore that if you prefer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, have a look, but the logic is slightly involved, feel free to ask questions. I guess the current logic will be good enough as well, but I will need to think a little more about it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you, still exploring and not quite understanding everything yet but I do have a question!
Is the definition of this test intentionally replacing the module.exports
object—which has its __esModule
property set to true
—with a string? Is the idea to have the static analysis kick in but have __esModule
missing at runtime?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After exploring a bit more, I found out that this spurious default
export is already expected in other tests such as this one, so it's not actually new behaviour, and not something we need to worry about in the context of this PR.
I think there is a more general refactor required, which like you suggested would look at the meta
data collected in required modules and only generate a default export if we expect there to be one. This could even allow us to remove the getDefaultExportFromCjs
helper entirely (it would always be export default x["default"]
).
I'm now of the opinion that this general refactor would be better done in a separate PR, to be implemented after #1038. It may also be alright to leave it unchanged :)
I propose to merge this PR as-is.
Ready for another round of review! |
See: - https://github.com/rollup/plugins/blob/d637611a79a2701c359f5f2f6ffb49978070da38/packages/commonjs/test/fixtures/function/transpiled-esm-entry-named/main.js#L3 - https://github.com/rollup/plugins/blob/d637611a79a2701c359f5f2f6ffb49978070da38/packages/commonjs/test/fixtures/function/transpiled-esm-namespace-named/main.js#L5
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good to me! Will look into getting this one released.
d637611
to
6277f08
Compare
… into cjs-reexport
#1165) * fix(commonjs): attach correct plugin meta-data to commonjs modules * feat(commonjs): reimplement dynamic import handling BREAKING CHANGES: requires Node 12 No longer supports require.cache * feat(commonjs): add strictRequires option to wrap modules * feat(commonjs): automatically wrap cyclic modules * feat(commonjs): Infer type for unidentified modules * feat(commonjs): make namespace callable when requiring ESM with function default * fix(commonjs): handle relative external imports * feat(commonjs): limit ignoreTryCatch to external requires * feat(commonjs): auto-detect conditional requires * feat(commonjs): add dynamicRequireRoot option * feat(commonjs): throw for dynamic requires from outside the configured root * refactor(commonjs): deconflict helpers only once globals are known * feat(commonjs): expose plugin version * fix(commonjs): do not transform "typeof exports" for mixed modules resolves #1014 * fix(commonjs): inject module name into dynamic require function * fix(commonjs): validate node-resolve peer version * fix(commonjs): use correct version and add package exports * fix(commonjs): proxy all entries to not break legacy polyfill plugins * fix(commonjs): add heuristic to deoptimize requires after calling imported function * fix(commonjs): make sure type changes of esm imports are tracked BREAKING CHANGES: Requires at least rollup@2.68.0 * fix(commonjs): handle external dependencies when using the cache * fix(commonjs): Do not change semantics when removing requires in if statements * fix(commonjs): Allow to dynamically require an empty file * Add a test to illustrate problematic re-exported module This is exhibited for example in Next.js, see https://github.com/vercel/next.js/blob/5feb400aff8e7b8968174b4e339b98ce48412180/packages/next/link.js#L1 which doesn't specify __esModule but re-exports a module that does. See https://www.runpkg.com/?next@12.1.4/link.js and https://www.runpkg.com/?next@12.1.4/dist/client/link.js for the compiled example. * fix(commonjs): support CJS modules re-exporting ESM modules * fix(commonjs): Warn when plugins do not pass options to resolveId * Update snapshots post-merge * Update tests * Update snapshot * Remove unnecessary fixtures * Remove comment given other tests do exactly the same See: - https://github.com/rollup/plugins/blob/d637611a79a2701c359f5f2f6ffb49978070da38/packages/commonjs/test/fixtures/function/transpiled-esm-entry-named/main.js#L3 - https://github.com/rollup/plugins/blob/d637611a79a2701c359f5f2f6ffb49978070da38/packages/commonjs/test/fixtures/function/transpiled-esm-namespace-named/main.js#L5 * Update snapshots Co-authored-by: Lukas Taegert-Atkinson <lukas.taegert-atkinson@tngtech.com>
Rollup Plugin Name:
commonjs
This PR contains:
Are tests included?
Breaking Changes?
List any relevant issue numbers:
#1116
vitejs/vite#2139
Description
Rollup with the CommonJS plugin currently cannot handle the following scenario (real example with
next@12.1.4
):Example ESM code:
Definitions in
next@12.1.4
NPM package (CJS):This happens because the
__esModule
property is not defined innext/link.js
, but insidenext/dist/client/link.js
instead. Technically,next/link.js
does have this property transitively, but that is not apparent with static analysis.In order for this to work, we need to check the value
__esModule
at runtime (unless we prefer to analyze files recursively with Rollup, which appears difficult given Rollup's architecture). For that to happen,shouldWrap
anddetectWrappedDefault
must both be true.This PR implements this additional check. As a side-effect, one existing snapshot for
produces optimized code when importing esm with a known default export
is arguably not as optimized anymore. I'm not sure it's possible to fix this bug while keeping that particular example as simple, which means some Rollup builds may end up slightly larger. I'd argue this is better than Rollup incorrectly wrapping default exports!Note 1: This PR should merge relatively cleanly with #1038. I'm happy to submit a patch for that branch as well.
Note 2: I am new to this codebase, and I could obviously have missed an important use case. I'd love a bit of guidance to make sure it's top-notch before it gets merged.
@lukastaegert FYI