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
Handle falsy synthetic namespaces #4247
Conversation
Thank you for your contribution! ❤️You can try out this pull request locally by installing Rollup via npm install rollup/rollup#handle-falsy-synthetic-namespace or load it into the REPL: |
Codecov Report
@@ Coverage Diff @@
## master #4247 +/- ##
==========================================
- Coverage 98.39% 98.37% -0.02%
==========================================
Files 203 203
Lines 7342 7272 -70
Branches 2088 2081 -7
==========================================
- Hits 7224 7154 -70
Misses 58 58
Partials 60 60
Continue to review full report at Codecov.
|
Noticed spaces around
Is it worth bothering with --- a/src/utils/interopHelpers.ts
+++ b/src/utils/interopHelpers.ts
@@ -255,5 +255,5 @@ const loopOverNamespaces = (
return (
`m.forEach(${left}` +
- `Object.keys(e).forEach(${getFunctionIntro(['k'], {
+ `e${_}&&${_}Object.keys(e).forEach(${getFunctionIntro(['k'], {
isAsync: false,
name: null |
It's hard to discern from the large patch, but is there a test for the falsy |
I'd be interested to know how it's possible in core for a namespace to be null though, that seems to break the ES module spec!? |
Good point. I recall seeing individual characters of a string exported now that you mention it. The es2015 version of _mergeNamespaces may have the same issue as well.
It's not an ES module thing, per se, it is the result of commonjs converted to an ES module for the Edit: Specifically this is related to the rollup |
Example:
Expected:
es5 helpers:
es2015 helpers:
|
With @guybedford's suggestion above: --- a/src/utils/interopHelpers.ts
+++ b/src/utils/interopHelpers.ts
@@ -244,7 +244,8 @@ const loopOverNamespaces = (
return (
`for${_}(var i${_}=${_}0;${_}i${_}<${_}m.length;${_}i++)${_}{${n}` +
`${t}${t}${cnst} e${_}=${_}m[i];${n}` +
- `${t}${t}for${_}(${cnst} k in e)${_}${body}${n}${t}}`
+ `${t}${t}if (e && (typeof e === 'object' || typeof e === 'function')) {` +
+ `${t}${t}for${_}(${cnst} k in e)${_}${body}${n}${t}${t}}${n}${t}}`
);
}
const [left, right] = getDirectReturnFunction(['e'], {
@@ -254,7 +255,7 @@ const loopOverNamespaces = (
});
return (
`m.forEach(${left}` +
- `Object.keys(e).forEach(${getFunctionIntro(['k'], {
+ `e && (typeof e === 'object' || typeof e === 'function') && Object.keys(e).forEach(${getFunctionIntro(['k'], {
isAsync: false,
name: null
})}${body})${right});` es5 helpers:
es2015 helpers:
Edit 1: Added parentheses around the es5 helper condition. |
Synthetic namespaces are not really namespaces but it is something where a plugin says: If a named import is not found, take a property of that exported object. Unfortunately, there is no runtime check that the export is really an object. For "real" namespaces, this cannot happen. That being said, I am not really sure we need to fix the string case. It bloats helpers for everyone, it is not necessary to fix a crash, and as what we are doing does not follow any spec here anyway, this is kind of a gray area. The namespace object will have additional properties, but all expected properties will be present (which in the commonjs case will just be "default"). But I can add the additional code if you deem it critical enough. |
97acfe5
to
14c3a5c
Compare
14c3a5c
to
7993076
Compare
I'd vote in favor of correctness and matching NodeJS's and esbuild's functionality despite the minimal extra code, which is a fixed size. The failing test case above is a not unusual scenario where a CJS module returns something (perhaps as result of a calculation) that another module processes as JSON verbatim. esbuild output shown for comparison: $ esbuild dyn-import-abc.js --bundle --platform=node
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __markAsModule = (target) => __defProp(target, "__esModule", { value: true });
var __commonJS = (cb, mod) => function __require() {
return mod || (0, cb[Object.keys(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};
var __reExport = (target, module2, desc) => {
if (module2 && typeof module2 === "object" || typeof module2 === "function") {
for (let key of __getOwnPropNames(module2))
if (!__hasOwnProp.call(target, key) && key !== "default")
__defProp(target, key, { get: () => module2[key], enumerable: !(desc = __getOwnPropDesc(module2, key)) || desc.enumerable });
}
return target;
};
var __toModule = (module2) => {
return __reExport(__markAsModule(__defProp(module2 != null ? __create(__getProtoOf(module2)) : {}, "default", module2 && module2.__esModule && "default" in module2 ? { get: () => module2.default, enumerable: true } : { value: module2, enumerable: true })), module2);
};
// abc.js
var require_abc = __commonJS({
"abc.js"(exports2, module2) {
module2.exports = "Abc";
}
});
// dyn-import-abc.js
Promise.resolve().then(() => __toModule(require_abc())).then((m) => console.log(JSON.stringify(m, null, 2))); to the proposed rollup fix: $ rollup dyn-import-abc.js --generatedCode es5 -p node-resolve -p commonjs -f cjs --exports auto
dyn-import-abc.js → stdout...
//→ dyn-import-abc.js:
'use strict';
Promise.resolve().then(function () { return require('./abc-09f6dfe3.js'); }).then(function (n) { return n.abc; }).then(m=>console.log(JSON.stringify(m,null,2)));
//→ abc-09f6dfe3.js:
'use strict';
function _mergeNamespaces(n, m) {
m.forEach(function (e) {
e && (typeof e === 'object' || typeof e === 'function') && Object.keys(e).forEach(function (k) {
if (k !== 'default' && !(k in n)) {
var d = Object.getOwnPropertyDescriptor(e, k);
Object.defineProperty(n, k, d.get ? d : {
enumerable: true,
get: function () { return e[k]; }
});
}
});
});
return Object.freeze(n);
}
var abc = "Abc";
var abc$1 = /*#__PURE__*/Object.freeze(/*#__PURE__*/_mergeNamespaces({
__proto__: null,
default: abc
}, [abc]));
exports.abc = abc$1; |
I added special handling for strings but went for |
We failed to consider the default export of an array, whose
Expected:
Actual:
fwiw, esbuild 0.13.8 also differs from NodeJS behavior:
/cc @evanw |
Thanks for the clarification - you mean it reads it off the That does mean something more like |
05ac1df
to
9711ac7
Compare
I extended the code to also ignore arrays |
Due to the exclusion of the array or string value, iteration over their (own) properties would be avoided.
lgtm Values like non-zero numeric literals would get through that check, but because they don't have any of their own properties it's harmless. |
This PR contains:
Are tests included?
Breaking Changes?
List any relevant issue numbers:
resolves #4245
Description
The commonjs plugin always uses
module.exports
as the "fallback namespace" for modules. In the rare case where this is eithernull
orundefined
, this will cause Rollup's helper to error as that one assumes the namespace to be an object. This adds a small check to the helper to account for this case.