diff --git a/etc/rollup/rollup-plugin-require-rewriter/require_rewriter.mjs b/etc/rollup/rollup-plugin-require-rewriter/require_rewriter.mjs deleted file mode 100644 index 7381a25f..00000000 --- a/etc/rollup/rollup-plugin-require-rewriter/require_rewriter.mjs +++ /dev/null @@ -1,50 +0,0 @@ -import assert from 'node:assert/strict'; -import MagicString from 'magic-string'; - -const CRYPTO_IMPORT_ESM_SRC = `import { randomBytes as nodejsRandomBytes } from 'crypto';`; -const BROWSER_ESM_SRC = `const nodejsRandomBytes = nodejsMathRandomBytes;`; -const CODE_TO_REPLACE = `const nodejsRandomBytes = (() => { - try { - return require('crypto').randomBytes; - } - catch { - return nodejsMathRandomBytes; - } -})();`; - -/** @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 - * Look for the function that depends on crypto, replace it with a top-level await - * and dynamic import for the crypto module. - * - * @param {string} code - source code of the module being transformed - * @param {string} id - module id (usually the source file name) - * @returns {{ code: string; map: import('magic-string').SourceMap }} - */ - transform(code, id) { - if (!id.includes('node_byte_utils')) { - return; - } - const start = code.indexOf(CODE_TO_REPLACE); - if (start === -1) { - throw new Error(`Unexpected! Code meant to be replaced is missing from ${id}`); - } - - const end = start + CODE_TO_REPLACE.length; - - // MagicString lets us edit the source code and still generate an accurate source map - const magicString = new MagicString(code); - magicString.overwrite(start, end, target === 'browser' ? BROWSER_ESM_SRC : CRYPTO_IMPORT_ESM_SRC); - - return { - code: magicString.toString(), - map: magicString.generateMap({ hires: true }) - }; - } - }; -} diff --git a/package.json b/package.json index 054a1824..e26b8718 100644 --- a/package.json +++ b/package.json @@ -117,4 +117,4 @@ "prepare": "node etc/prepare.js", "release": "standard-version -i HISTORY.md" } -} \ No newline at end of file +} diff --git a/rollup.config.mjs b/rollup.config.mjs index e303ede7..e38130ce 100644 --- a/rollup.config.mjs +++ b/rollup.config.mjs @@ -1,6 +1,5 @@ import { nodeResolve } from '@rollup/plugin-node-resolve'; import typescript from '@rollup/plugin-typescript'; -import { requireRewriter } from './etc/rollup/rollup-plugin-require-rewriter/require_rewriter.mjs'; import { RequireVendor } from './etc/rollup/rollup-plugin-require-vendor/require_vendor.mjs'; /** @type {typescript.RollupTypescriptOptions} */ @@ -57,7 +56,6 @@ const config = [ input, plugins: [ typescript(tsConfig), - requireRewriter({ target: 'browser' }), nodeResolve({ resolveOnly: [] }) ], output: { @@ -68,7 +66,7 @@ const config = [ }, { input, - plugins: [typescript(tsConfig), requireRewriter({ target: 'node' }), nodeResolve({ resolveOnly: [] })], + plugins: [typescript(tsConfig), nodeResolve({ resolveOnly: [] })], output: { file: 'lib/bson.node.mjs', format: 'esm', diff --git a/src/utils/node_byte_utils.ts b/src/utils/node_byte_utils.ts index 4a706613..8f2dc7df 100644 --- a/src/utils/node_byte_utils.ts +++ b/src/utils/node_byte_utils.ts @@ -26,34 +26,27 @@ type NodeJsBufferConstructor = Omit & { // This can be nullish, but we gate the nodejs functions on being exported whether or not this exists // Node.js global declare const Buffer: NodeJsBufferConstructor; -declare const require: (mod: 'crypto') => { randomBytes: (byteLength: number) => Uint8Array }; /** @internal */ -export function nodejsMathRandomBytes(byteLength: number) { +function nodejsMathRandomBytes(byteLength: number): NodeJsBuffer { return nodeJsByteUtils.fromNumberArray( Array.from({ length: byteLength }, () => Math.floor(Math.random() * 256)) ); } -/** - * @internal - * WARNING: REQUIRE WILL BE REWRITTEN - * - * This code is carefully used by require_rewriter.mjs any modifications must be reflected in the plugin. - * - * @remarks - * "crypto" is the only dependency BSON needs. This presents a problem for creating a bundle of the BSON library - * in an es module format that can be used both on the browser and in Node.js. In Node.js when BSON is imported as - * an es module, there will be no global require function defined, making the code below fallback to the much less desireable math.random bytes. - * In order to make our es module bundle work as expected on Node.js we need to change this `require()` to a dynamic import, and the dynamic - * import must be top-level awaited since es modules are async. So we rely on a custom rollup plugin to seek out the following lines of code - * and replace `require` with `await import` and the IIFE line (`nodejsRandomBytes = (() => { ... })()`) with `nodejsRandomBytes = await (async () => { ... })()` - * when generating an es module bundle. - */ -const nodejsRandomBytes: (byteLength: number) => Uint8Array = (() => { - try { - return require('crypto').randomBytes; - } catch { +/** @internal */ +function nodejsSecureRandomBytes(byteLength: number): NodeJsBuffer { + // @ts-expect-error: crypto.getRandomValues cannot actually be null here + return crypto.getRandomValues(nodeJsByteUtils.allocate(byteLength)); +} + +const nodejsRandomBytes = (() => { + const { crypto } = globalThis as { + crypto?: { getRandomValues?: (space: Uint8Array) => Uint8Array }; + }; + if (crypto != null && typeof crypto.getRandomValues === 'function') { + return nodejsSecureRandomBytes; + } else { return nodejsMathRandomBytes; } })();