Skip to content

test(NODE-6920): esm bundles do not have top-level await #790

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

Merged
merged 3 commits into from
Apr 24, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import assert from 'node:assert/strict';
import MagicString from 'magic-string';

const CRYPTO_IMPORT_ESM_SRC = `import { randomBytes as nodejsRandomBytes } from 'crypto';`;
Expand All @@ -11,7 +12,10 @@ const CODE_TO_REPLACE = `const nodejsRandomBytes = (() => {
}
})();`;

export function requireRewriter({ isBrowser = false } = {}) {
/** @param {{ target: 'browser' | 'node'}} configuration - destination information that changes the replacement syntax used. */
export function requireRewriter({ target }) {
assert.match(target, /^(node|browser)$/, 'target must be either "node" or "browser"');

return {
/**
* Take the compiled source code input; types are expected to already have been removed
Expand All @@ -35,7 +39,7 @@ export function requireRewriter({ isBrowser = false } = {}) {

// MagicString lets us edit the source code and still generate an accurate source map
const magicString = new MagicString(code);
magicString.overwrite(start, end, isBrowser ? BROWSER_ESM_SRC : CRYPTO_IMPORT_ESM_SRC);
magicString.overwrite(start, end, target === 'browser' ? BROWSER_ESM_SRC : CRYPTO_IMPORT_ESM_SRC);

return {
code: magicString.toString(),
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@
"exports": {
"browser": {
"types": "./bson.d.ts",
"default": "./lib/bson.browser.mjs"
"default": "./lib/bson.mjs"
},
"react-native": "./lib/bson.rn.cjs",
"default": {
Expand Down
6 changes: 3 additions & 3 deletions rollup.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -57,18 +57,18 @@ const config = [
input,
plugins: [
typescript(tsConfig),
requireRewriter({ isBrowser: true }),
requireRewriter({ target: 'browser' }),
nodeResolve({ resolveOnly: [] })
],
output: {
file: 'lib/bson.browser.mjs',
file: 'lib/bson.mjs',
format: 'esm',
sourcemap: true
}
},
{
input,
plugins: [typescript(tsConfig), requireRewriter(), nodeResolve({ resolveOnly: [] })],
plugins: [typescript(tsConfig), requireRewriter({ target: 'node' }), nodeResolve({ resolveOnly: [] })],
output: {
file: 'lib/bson.node.mjs',
format: 'esm',
Expand Down
30 changes: 30 additions & 0 deletions test/node/exports.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as BSON from '../register-bson';
import { sorted, byStrings } from './tools/utils';
import { readFile } from 'fs/promises';
import { resolve } from 'path';
import * as child_process from 'node:child_process';

const EXPECTED_EXPORTS = [
// This is our added web indicator not a real export but a small exception for this test.
Expand Down Expand Up @@ -41,6 +42,7 @@ const EXPECTED_EXPORTS = [
];

const EXPECTED_EJSON_EXPORTS = ['parse', 'stringify', 'serialize', 'deserialize'];
const NODE_MAJOR = Number(process.versions.node.split('.')[0]);

describe('bson entrypoint', () => {
it('should export all and only the expected keys in expected_exports', () => {
Expand Down Expand Up @@ -96,4 +98,32 @@ describe('bson entrypoint', () => {
expect(pkg).nested.property('exports.default.types', './bson.d.ts');
});
});

function testSyncESMImport(name, module) {
return () => {
const child = child_process.spawnSync(
'node',
['--experimental-print-required-tla', '--print', `require('${module}')`],
{ encoding: 'utf-8' }
);

expect(
child.status,
`expected to be able to 'require' to import the ${name} ESM because there should be no top-level await:\n` +
child.stderr
).to.equal(0);
};
}

const itFn = NODE_MAJOR < 22 ? it.skip : it;

itFn(
'browser bundle does not use top-level await',
testSyncESMImport('browser', './lib/bson.mjs')
);

itFn(
'node bundle does not use top-level await',
testSyncESMImport('node', './lib/bson.node.mjs')
);
});
10 changes: 6 additions & 4 deletions test/node/release.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,16 @@ const REQUIRED_FILES = [
'README.md',
'bson.d.ts',
'etc/prepare.js',
'lib/bson.bundle.js',
'lib/bson.bundle.js.map',
'lib/bson.cjs',
'lib/bson.bundle.js',
'lib/bson.cjs.map',
'lib/bson.mjs',
'lib/bson.cjs',
'lib/bson.mjs.map',
'lib/bson.rn.cjs',
'lib/bson.mjs',
'lib/bson.node.mjs.map',
'lib/bson.node.mjs',
'lib/bson.rn.cjs.map',
'lib/bson.rn.cjs',
'package.json',
'src/binary.ts',
'src/bson_value.ts',
Expand Down