Skip to content

Commit

Permalink
fix(NODE-5048): webpack unable to bundle import with leading 'node:' (#…
Browse files Browse the repository at this point in the history
…564)

Co-authored-by: Bailey Pearson <bailey.pearson@mongodb.com>
  • Loading branch information
nbbeeken and baileympearson authored Feb 16, 2023
1 parent 50e90fc commit 3aed24a
Show file tree
Hide file tree
Showing 11 changed files with 159 additions and 6 deletions.
19 changes: 19 additions & 0 deletions .evergreen/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -204,3 +222,4 @@ buildvariants:
- check-typescript-oldest
- check-typescript-current
- check-typescript-next
- bundling-tests
12 changes: 12 additions & 0 deletions .evergreen/run-bundling-test.sh
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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`
);
}

Expand Down
4 changes: 2 additions & 2 deletions src/utils/node_byte_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ type NodeJsBufferConstructor = Omit<Uint8ArrayConstructor, 'from'> & {
// 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) {
Expand All @@ -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;
}
Expand Down
1 change: 1 addition & 0 deletions test/bundling/webpack/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
package-lock.json
24 changes: 24 additions & 0 deletions test/bundling/webpack/install_bson.cjs
Original file line number Diff line number Diff line change
@@ -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!');
23 changes: 23 additions & 0 deletions test/bundling/webpack/package.json
Original file line number Diff line number Diff line change
@@ -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": {}
}
14 changes: 14 additions & 0 deletions test/bundling/webpack/readme.md
Original file line number Diff line number Diff line change
@@ -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
3 changes: 3 additions & 0 deletions test/bundling/webpack/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { BSON } from 'bson';

console.log(new BSON.ObjectId());
10 changes: 10 additions & 0 deletions test/bundling/webpack/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"noImplicitAny": true,
"module": "es6",
"target": "es5",
"allowJs": true
},
"files": ["src/index.ts"]
}
47 changes: 47 additions & 0 deletions test/bundling/webpack/webpack.config.js
Original file line number Diff line number Diff line change
@@ -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;
};

0 comments on commit 3aed24a

Please sign in to comment.