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: _interopRequireWildcard should only cache objects #10574

Merged
merged 3 commits into from Oct 19, 2019
Merged

fix: _interopRequireWildcard should only cache objects #10574

merged 3 commits into from Oct 19, 2019

Conversation

samMeow
Copy link
Contributor

@samMeow samMeow commented Oct 18, 2019

Q                       A
Fixed Issues? Fixes #10561
Patch: Bug Fix? Yes
Major: Breaking Change? No
Minor: New Feature? No
Tests Added + Pass? Yes
Documentation PR Link No
Any Dependency Changes? No
License MIT

_interopRequireWildcard (used for dynamic import) wrongly cached string modules (e.g. base64 image) to Weakmap throws error. This fix is to cache object only for the function.

@buildsize
Copy link

buildsize bot commented Oct 18, 2019

File name Previous Size New Size Change
babel-preset-env.js 2.73 MB 2.73 MB 136 bytes (0%)
babel-preset-env.min.js 1.65 MB 1.65 MB 132 bytes (0%)
babel.js 2.92 MB 2.92 MB 136 bytes (0%)
babel.min.js 1.61 MB 1.61 MB 132 bytes (0%)
test262.tap 4.84 MB [deleted]

JLHwung
JLHwung previously approved these changes Oct 18, 2019
@@ -620,6 +620,10 @@ helpers.interopRequireWildcard = helper("7.0.0-beta.0")`
return obj;
}

if (!(obj instanceof Object)) {
Copy link
Member

Choose a reason for hiding this comment

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

This isn't safe, for example, if __proto__ is null. You should use typeof.

Copy link
Contributor

Choose a reason for hiding this comment

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

I wish we can use _.isObject in the helpers. I copied from there

typeof obj === "object" || typeof obj === "function"

because (new WeakMap).set(new Function, 42) is valid.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the advices. Inspiring from _.isObject. We should also add null check, because typeof null === 'object' and (new WeakMap).set(null, { default: null }) throws error.

@JLHwung JLHwung dismissed their stale review October 18, 2019 21:48

Nicolò points out a potential issue.

@@ -620,6 +620,10 @@ helpers.interopRequireWildcard = helper("7.0.0-beta.0")`
return obj;
}

if (obj === null || (typeof obj !== "object" && typeof obj !== "function")) {

Choose a reason for hiding this comment

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

What about adding tests for this? It would be easier to understand how this function should work, and prevent possible issues in the future.

Copy link
Contributor Author

@samMeow samMeow Oct 19, 2019

Choose a reason for hiding this comment

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

For test case, you may refer to packages/babel-plugin-proposal-dynamic-import/test/fixtures/commonjs/*. You will find an example for function module exec-interop and newly added exec-interop-string, exec-interop-null.

@theKashey
Copy link

Sorry for the offtopic, but are you sure it's still working correctly?

For example, lets double check construction like import * as classnames from 'classnames'
classnames are defined in a more or less common way, for many "old" packages

classNames.default = classNames;
module.exports = classNames;

So let's imagine - import * as classnames from 'classnames' (* required by TS, as long as there is no(properly defined) "default" import)

  • Q: What webpack would emit:

  • A: var classnames__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! classnames */ "./node_modules/classnames/index.js");

  • Q: What is it?

  • A: It's a function you can use

  • Q: What would happen next?

  • A Nothing bad


  • Q: What parcel would emit (uses babel):

  • A: var classnames = _interopRequireWildcard(require("classnames"));

  • Q: What is it?

  • A: It's a {default: classNames}

  • Q: What would happen next?

  • A: classnames is not a function.


  • Q: What would happen if import classnames from 'classnames'?
  • A: It would work, but that's not correct.

So, question - what _interopRequireWildcard is doing? Why it's trying to return an object with .default

As far as I understand - the inner loop creates an object clone, plus stores the original object as default. Look like that's working for objects, but not for the functions.

So:

  • _interopRequireWildcard(1) == 1
  • _interopRequireWildcard(false) == false
  • _interopRequireWildcard("hey") == "hey
  • _interopRequireWildcard({test: 42}).test == 42
  • _interopRequireWildcard(function(){return 'test')) === ???

Right now compiling the same file using webpack or parcel(babel) produces not only different code - it's not working in the second case.

@nicolo-ribaudo
Copy link
Member

nicolo-ribaudo commented Oct 19, 2019

import * as x must always return an object. If webpack returns a function, it's a webpack bug. (To be 100% correct, it should be an object with __proto__ === null and read-only)

In your example, import classnames from 'classnames' is correct.

@theKashey
Copy link

Is it a part of ESM specification? As long as that would be TypeScript bug in this case, and all the projects using TypeScript and wildcard imports to read exports as is from commonjs module, and exposing ESM modules as the output... are broken.
Well, this is how I got into this issue - ‘import * as isNode’ from “detect-node”’ - and no default export - https://unpkg.com/browse/@types/detect-node@2.0.0/index.d.ts

@nicolo-ribaudo
Copy link
Member

@theKashey Yes, the specification defines imported namespaces (with * as foo) as that kind of exotic objects. The default exported by a module is accessible as foo.default.
That object is defined at https://tc39.es/ecma262/#table-29. As you can see at https://tc39.es/ecma262/#function-object, a function is an object with an internal [[Call]] property, which imported namespaces don't have.

Also note that Babel aligns with Node's implementation in this case: the default export of a module is it's module.exports.

@nicolo-ribaudo nicolo-ribaudo changed the title fix: [#10561] _interopRequireWildcard should cache object only fix: _interopRequireWildcard should only cache objects Oct 19, 2019
@nicolo-ribaudo nicolo-ribaudo merged commit fe258de into babel:master Oct 19, 2019
@nicolo-ribaudo nicolo-ribaudo added i: regression PR: Bug Fix 🐛 A type of pull request used for our changelog categories labels Oct 19, 2019
@ExE-Boss
Copy link
Contributor

There’s a second non‑nullish check a few lines after this, so I’m removing it in #10585, since it’s no longer necessary.

@lock lock bot added the outdated A closed issue/PR that is archived due to age. Recommended to make a new issue label Jan 24, 2020
@lock lock bot locked as resolved and limited conversation to collaborators Jan 24, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
i: regression outdated A closed issue/PR that is archived due to age. Recommended to make a new issue PR: Bug Fix 🐛 A type of pull request used for our changelog categories
Projects
None yet
Development

Successfully merging this pull request may close these issues.

babel transfrom runtime incompatible with url loader
7 participants