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: support canonical module #5040

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 7 additions & 1 deletion lib/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,7 @@ exports.type = function type(value) {
exports.stringify = function (value) {
var typeHint = canonicalType(value);

if (!~['object', 'array', 'function'].indexOf(typeHint)) {
if (!~['object', 'array', 'function', 'module'].indexOf(typeHint)) {
if (typeHint === 'buffer') {
var json = Buffer.prototype.toJSON.call(value);
// Based on the toJSON result
Expand Down Expand Up @@ -399,6 +399,12 @@ exports.canonicalize = function canonicalize(value, stack, typeHint) {
break;
}
/* falls through */
case 'module':
Copy link
Contributor Author

@JacobLey JacobLey Apr 18, 2024

Choose a reason for hiding this comment

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

With a bit more manual testing (when trying to reproduce) I think this issue may fundamentally occur anytime you are dealing with null-prototyped instances...

Which is an overall rare occurrence, but maybe this isn't the right approach?

e.g.

const foo = Object.create(null, { 
    [Symbol.toStringTag]: { value: 'Foo' }, 
     bing: { get: () => 'bong' } 
});

Object.prototype.toString.call(x); // '[object Foo]'

foo + ''; // Uncaught TypeError: Cannot convert object to primitive value

So while this solution works for modules, I don't think it actually solves the underlying issue

if (value[Symbol.toStringTag] === 'Module') {
canonicalizedObj = canonicalizedObj || {};
canonicalizedObj['[Symbol.toStringTag]'] = 'Module';
}
/* falls through */
case 'object':
canonicalizedObj = canonicalizedObj || {};
withStack(value, function () {
Expand Down
4 changes: 4 additions & 0 deletions test/unit/fixtures/module.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export default 123;

export const foo = 'abc';
export const bar = true;
23 changes: 23 additions & 0 deletions test/unit/utils.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
'use strict';

var utils = require('../../lib/utils');
const esmUtils = require('../../lib/nodejs/esm-utils');
const Path = require('node:path');
var sinon = require('sinon');

describe('lib/utils', function () {
Expand Down Expand Up @@ -288,6 +290,27 @@ describe('lib/utils', function () {
].join('\n');
expect(stringify(expected), 'to be', actual);
});

it('should represent modules', async function () {
if (process.browser) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The esm-utils and node:path modules both come through as undefined in browser (which is kinda expected).

In theory though, it probably isn't necessary to use esm-utils at all, and the path logic we are using is just a very basic join, which we can probably safely default to __dirname + '/fixtures/module.mjs' and load via a dynamic import import() (Maybe keep the path module around for node+windows support)

However, it appears the current version of rollup has issues parsing that. It appears rollup is ~2 major versions out of date, so I wouldn't be surprised if it just isn't equipped to handle "modern" js syntax.

I tried bumping rollup version and it was not straightforward. I'm sure someone with more rollup experience can easily knock it out, but it felt out of scope for what I was trying to achieve with this fix.

// Current rollup config cannot `import()`
this.skip();
return;
}
const expected = await esmUtils.requireOrImport(
Path.join(__dirname, './fixtures/module.mjs')
);
const actual = [
'{',
' "[Symbol.toStringTag]": "Module"',
' "bar": true',
' "default": 123',
' "foo": "abc"',
'}'
].join('\n');

expect(stringify(expected), 'to be', actual);
});
});

it('should canonicalize the object', function () {
Expand Down