diff --git a/.evergreen/config.yml b/.evergreen/config.yml index dfbf411b..5880bfac 100644 --- a/.evergreen/config.yml +++ b/.evergreen/config.yml @@ -81,6 +81,17 @@ functions: binary: bash args: - .evergreen/run-eslint-plugin-test.sh + run bundling: + - command: subprocess.exec + type: test + params: + working_dir: src + binary: bash + env: + NODE_VERSION: ${NODE_VERSION} + PROJECT_DIRECTORY: ${PROJECT_DIRECTORY} + args: + - .evergreen/run-bundling-test.sh tasks: - name: node-tests-v14 @@ -133,6 +144,13 @@ tasks: - func: run tests vars: TEST_TARGET: web + - name: bundling-tests + commands: + - func: fetch source + vars: + NODE_MAJOR_VERSION: 18 + - func: install dependencies + - func: run bundling - name: no-bigint-web-tests tags: ["no-bigint", "web"] commands: @@ -204,3 +222,4 @@ buildvariants: - check-typescript-oldest - check-typescript-current - check-typescript-next + - bundling-tests diff --git a/.evergreen/run-bundling-test.sh b/.evergreen/run-bundling-test.sh new file mode 100644 index 00000000..5cc2db4c --- /dev/null +++ b/.evergreen/run-bundling-test.sh @@ -0,0 +1,12 @@ +#! /usr/bin/env bash + +source "${PROJECT_DIRECTORY}/.evergreen/init-nvm.sh" + +set -o xtrace +set -o errexit + +pushd test/bundling/webpack + +npm install +npm run install:bson +npm run build diff --git a/etc/rollup/rollup-plugin-require-rewriter/require_rewriter.mjs b/etc/rollup/rollup-plugin-require-rewriter/require_rewriter.mjs index ea411639..796ba449 100644 --- a/etc/rollup/rollup-plugin-require-rewriter/require_rewriter.mjs +++ b/etc/rollup/rollup-plugin-require-rewriter/require_rewriter.mjs @@ -2,13 +2,13 @@ import MagicString from 'magic-string'; const CRYPTO_IMPORT_ESM_SRC = `const nodejsRandomBytes = await (async () => { try { - return (await import('node:crypto')).randomBytes;`; + return (await import('crypto')).randomBytes;`; export class RequireRewriter { /** * 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 node:crypto module. + * 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) @@ -23,12 +23,12 @@ export class RequireRewriter { } const start = code.indexOf('const nodejsRandomBytes'); - const endString = `return require('node:crypto').randomBytes;`; + const endString = `return require('crypto').randomBytes;`; const end = code.indexOf(endString) + endString.length; if (start < 0 || end < 0) { throw new Error( - `Unexpected! 'const nodejsRandomBytes' or 'return require('node:crypto').randomBytes;' not found` + `Unexpected! 'const nodejsRandomBytes' or 'return require('crypto').randomBytes;' not found` ); } diff --git a/src/utils/node_byte_utils.ts b/src/utils/node_byte_utils.ts index b25969fe..468acf89 100644 --- a/src/utils/node_byte_utils.ts +++ b/src/utils/node_byte_utils.ts @@ -22,7 +22,7 @@ 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: 'node:crypto') => { randomBytes: (byteLength: number) => Uint8Array }; +declare const require: (mod: 'crypto') => { randomBytes: (byteLength: number) => Uint8Array }; /** @internal */ export function nodejsMathRandomBytes(byteLength: number) { @@ -48,7 +48,7 @@ export function nodejsMathRandomBytes(byteLength: number) { */ const nodejsRandomBytes: (byteLength: number) => Uint8Array = (() => { try { - return require('node:crypto').randomBytes; + return require('crypto').randomBytes; } catch { return nodejsMathRandomBytes; } diff --git a/test/bundling/webpack/.gitignore b/test/bundling/webpack/.gitignore new file mode 100644 index 00000000..d8b83df9 --- /dev/null +++ b/test/bundling/webpack/.gitignore @@ -0,0 +1 @@ +package-lock.json diff --git a/test/bundling/webpack/install_bson.cjs b/test/bundling/webpack/install_bson.cjs new file mode 100644 index 00000000..ae0c7547 --- /dev/null +++ b/test/bundling/webpack/install_bson.cjs @@ -0,0 +1,24 @@ +'use strict'; + +const { execSync } = require('node:child_process'); +const { readFileSync } = require('node:fs'); +const { resolve } = require('node:path'); + +const xtrace = (...args) => { + console.log(`running: ${args[0]}`); + return execSync(...args); +}; + +const bsonRoot = resolve(__dirname, '../../..'); +console.log(`bson package root: ${bsonRoot}`); + +const bsonVersion = JSON.parse( + readFileSync(resolve(bsonRoot, 'package.json'), { encoding: 'utf8' }) +).version; +console.log(`bsonVersion: ${bsonVersion}`); + +xtrace('npm pack --pack-destination test/bundling/webpack', { cwd: bsonRoot }); + +xtrace(`npm install --no-save bson-${bsonVersion}.tgz`); + +console.log('bson installed!'); diff --git a/test/bundling/webpack/package.json b/test/bundling/webpack/package.json new file mode 100644 index 00000000..4cfc4e49 --- /dev/null +++ b/test/bundling/webpack/package.json @@ -0,0 +1,23 @@ +{ + "name": "my-webpack-project", + "version": "1.0.0", + "private": true, + "description": "My webpack project", + "main": "index.js", + "scripts": { + "install:bson": "node install_bson.cjs", + "test": "webpack", + "build": "webpack --mode=production --node-env=production", + "build:dev": "webpack --mode=development", + "build:prod": "webpack --mode=production --node-env=production", + "watch": "webpack --watch" + }, + "devDependencies": { + "@webpack-cli/generators": "^3.0.1", + "ts-loader": "^9.4.2", + "typescript": "^4.9.5", + "webpack": "^5.75.0", + "webpack-cli": "^5.0.1" + }, + "dependencies": {} +} diff --git a/test/bundling/webpack/readme.md b/test/bundling/webpack/readme.md new file mode 100644 index 00000000..099c64e4 --- /dev/null +++ b/test/bundling/webpack/readme.md @@ -0,0 +1,14 @@ +# Webpack BSON setup example + +In order to use BSON with webpack there are two changes beyond the default config file needed: +- Set `experiments: { topLevelAwait: true }` in the top-level config object +- Set `resolve: { fallback: { crypto: false } }` in the top-level config object + +## Testing + +To use this bundler test: +- Make changes to bson +- run `npm run build` in the root of the repo to rebuild the BSON src +- in this directory run `npm run install:bson` to install BSON as if it were from npm + - We use a `.tgz` install to make sure we're using exactly what will be published to npm +- run `npm run build` to check that webpack can pull in the changes diff --git a/test/bundling/webpack/src/index.ts b/test/bundling/webpack/src/index.ts new file mode 100644 index 00000000..48c90d45 --- /dev/null +++ b/test/bundling/webpack/src/index.ts @@ -0,0 +1,3 @@ +import { BSON } from 'bson'; + +console.log(new BSON.ObjectId()); diff --git a/test/bundling/webpack/tsconfig.json b/test/bundling/webpack/tsconfig.json new file mode 100644 index 00000000..31176e65 --- /dev/null +++ b/test/bundling/webpack/tsconfig.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "allowSyntheticDefaultImports": true, + "noImplicitAny": true, + "module": "es6", + "target": "es5", + "allowJs": true + }, + "files": ["src/index.ts"] +} diff --git a/test/bundling/webpack/webpack.config.js b/test/bundling/webpack/webpack.config.js new file mode 100644 index 00000000..dfd35e18 --- /dev/null +++ b/test/bundling/webpack/webpack.config.js @@ -0,0 +1,47 @@ +// Generated using webpack-cli https://github.com/webpack/webpack-cli +'use strict'; + +const path = require('path'); + +const isProduction = process.env.NODE_ENV === 'production'; + +const config = { + entry: './src/index.ts', + output: { + path: path.resolve(__dirname, 'dist') + }, + plugins: [ + // Add your plugins here + // Learn more about plugins from https://webpack.js.org/configuration/plugins/ + ], + experiments: { topLevelAwait: true }, + module: { + rules: [ + { + test: /\.(ts|tsx)$/i, + loader: 'ts-loader', + exclude: ['/node_modules/'] + }, + { + test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i, + type: 'asset' + } + + // Add your rules for custom modules here + // Learn more about loaders from https://webpack.js.org/loaders/ + ] + }, + resolve: { + extensions: ['.tsx', '.ts', '.jsx', '.js', '...'], + fallback: { crypto: false } + } +}; + +module.exports = () => { + if (isProduction) { + config.mode = 'production'; + } else { + config.mode = 'development'; + } + return config; +};