Skip to content

Commit 3aed24a

Browse files
fix(NODE-5048): webpack unable to bundle import with leading 'node:' (#564)
Co-authored-by: Bailey Pearson <[email protected]>
1 parent 50e90fc commit 3aed24a

File tree

11 files changed

+159
-6
lines changed

11 files changed

+159
-6
lines changed

Diff for: .evergreen/config.yml

+19
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,17 @@ functions:
8181
binary: bash
8282
args:
8383
- .evergreen/run-eslint-plugin-test.sh
84+
run bundling:
85+
- command: subprocess.exec
86+
type: test
87+
params:
88+
working_dir: src
89+
binary: bash
90+
env:
91+
NODE_VERSION: ${NODE_VERSION}
92+
PROJECT_DIRECTORY: ${PROJECT_DIRECTORY}
93+
args:
94+
- .evergreen/run-bundling-test.sh
8495

8596
tasks:
8697
- name: node-tests-v14
@@ -133,6 +144,13 @@ tasks:
133144
- func: run tests
134145
vars:
135146
TEST_TARGET: web
147+
- name: bundling-tests
148+
commands:
149+
- func: fetch source
150+
vars:
151+
NODE_MAJOR_VERSION: 18
152+
- func: install dependencies
153+
- func: run bundling
136154
- name: no-bigint-web-tests
137155
tags: ["no-bigint", "web"]
138156
commands:
@@ -204,3 +222,4 @@ buildvariants:
204222
- check-typescript-oldest
205223
- check-typescript-current
206224
- check-typescript-next
225+
- bundling-tests

Diff for: .evergreen/run-bundling-test.sh

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#! /usr/bin/env bash
2+
3+
source "${PROJECT_DIRECTORY}/.evergreen/init-nvm.sh"
4+
5+
set -o xtrace
6+
set -o errexit
7+
8+
pushd test/bundling/webpack
9+
10+
npm install
11+
npm run install:bson
12+
npm run build

Diff for: etc/rollup/rollup-plugin-require-rewriter/require_rewriter.mjs

+4-4
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@ import MagicString from 'magic-string';
22

33
const CRYPTO_IMPORT_ESM_SRC = `const nodejsRandomBytes = await (async () => {
44
try {
5-
return (await import('node:crypto')).randomBytes;`;
5+
return (await import('crypto')).randomBytes;`;
66

77
export class RequireRewriter {
88
/**
99
* Take the compiled source code input; types are expected to already have been removed
1010
* Look for the function that depends on crypto, replace it with a top-level await
11-
* and dynamic import for the node:crypto module.
11+
* and dynamic import for the crypto module.
1212
*
1313
* @param {string} code - source code of the module being transformed
1414
* @param {string} id - module id (usually the source file name)
@@ -23,12 +23,12 @@ export class RequireRewriter {
2323
}
2424

2525
const start = code.indexOf('const nodejsRandomBytes');
26-
const endString = `return require('node:crypto').randomBytes;`;
26+
const endString = `return require('crypto').randomBytes;`;
2727
const end = code.indexOf(endString) + endString.length;
2828

2929
if (start < 0 || end < 0) {
3030
throw new Error(
31-
`Unexpected! 'const nodejsRandomBytes' or 'return require('node:crypto').randomBytes;' not found`
31+
`Unexpected! 'const nodejsRandomBytes' or 'return require('crypto').randomBytes;' not found`
3232
);
3333
}
3434

Diff for: src/utils/node_byte_utils.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ type NodeJsBufferConstructor = Omit<Uint8ArrayConstructor, 'from'> & {
2222
// This can be nullish, but we gate the nodejs functions on being exported whether or not this exists
2323
// Node.js global
2424
declare const Buffer: NodeJsBufferConstructor;
25-
declare const require: (mod: 'node:crypto') => { randomBytes: (byteLength: number) => Uint8Array };
25+
declare const require: (mod: 'crypto') => { randomBytes: (byteLength: number) => Uint8Array };
2626

2727
/** @internal */
2828
export function nodejsMathRandomBytes(byteLength: number) {
@@ -48,7 +48,7 @@ export function nodejsMathRandomBytes(byteLength: number) {
4848
*/
4949
const nodejsRandomBytes: (byteLength: number) => Uint8Array = (() => {
5050
try {
51-
return require('node:crypto').randomBytes;
51+
return require('crypto').randomBytes;
5252
} catch {
5353
return nodejsMathRandomBytes;
5454
}

Diff for: test/bundling/webpack/.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
package-lock.json

Diff for: test/bundling/webpack/install_bson.cjs

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
'use strict';
2+
3+
const { execSync } = require('node:child_process');
4+
const { readFileSync } = require('node:fs');
5+
const { resolve } = require('node:path');
6+
7+
const xtrace = (...args) => {
8+
console.log(`running: ${args[0]}`);
9+
return execSync(...args);
10+
};
11+
12+
const bsonRoot = resolve(__dirname, '../../..');
13+
console.log(`bson package root: ${bsonRoot}`);
14+
15+
const bsonVersion = JSON.parse(
16+
readFileSync(resolve(bsonRoot, 'package.json'), { encoding: 'utf8' })
17+
).version;
18+
console.log(`bsonVersion: ${bsonVersion}`);
19+
20+
xtrace('npm pack --pack-destination test/bundling/webpack', { cwd: bsonRoot });
21+
22+
xtrace(`npm install --no-save bson-${bsonVersion}.tgz`);
23+
24+
console.log('bson installed!');

Diff for: test/bundling/webpack/package.json

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"name": "my-webpack-project",
3+
"version": "1.0.0",
4+
"private": true,
5+
"description": "My webpack project",
6+
"main": "index.js",
7+
"scripts": {
8+
"install:bson": "node install_bson.cjs",
9+
"test": "webpack",
10+
"build": "webpack --mode=production --node-env=production",
11+
"build:dev": "webpack --mode=development",
12+
"build:prod": "webpack --mode=production --node-env=production",
13+
"watch": "webpack --watch"
14+
},
15+
"devDependencies": {
16+
"@webpack-cli/generators": "^3.0.1",
17+
"ts-loader": "^9.4.2",
18+
"typescript": "^4.9.5",
19+
"webpack": "^5.75.0",
20+
"webpack-cli": "^5.0.1"
21+
},
22+
"dependencies": {}
23+
}

Diff for: test/bundling/webpack/readme.md

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# Webpack BSON setup example
2+
3+
In order to use BSON with webpack there are two changes beyond the default config file needed:
4+
- Set `experiments: { topLevelAwait: true }` in the top-level config object
5+
- Set `resolve: { fallback: { crypto: false } }` in the top-level config object
6+
7+
## Testing
8+
9+
To use this bundler test:
10+
- Make changes to bson
11+
- run `npm run build` in the root of the repo to rebuild the BSON src
12+
- in this directory run `npm run install:bson` to install BSON as if it were from npm
13+
- We use a `.tgz` install to make sure we're using exactly what will be published to npm
14+
- run `npm run build` to check that webpack can pull in the changes

Diff for: test/bundling/webpack/src/index.ts

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import { BSON } from 'bson';
2+
3+
console.log(new BSON.ObjectId());

Diff for: test/bundling/webpack/tsconfig.json

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"compilerOptions": {
3+
"allowSyntheticDefaultImports": true,
4+
"noImplicitAny": true,
5+
"module": "es6",
6+
"target": "es5",
7+
"allowJs": true
8+
},
9+
"files": ["src/index.ts"]
10+
}

Diff for: test/bundling/webpack/webpack.config.js

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Generated using webpack-cli https://github.com/webpack/webpack-cli
2+
'use strict';
3+
4+
const path = require('path');
5+
6+
const isProduction = process.env.NODE_ENV === 'production';
7+
8+
const config = {
9+
entry: './src/index.ts',
10+
output: {
11+
path: path.resolve(__dirname, 'dist')
12+
},
13+
plugins: [
14+
// Add your plugins here
15+
// Learn more about plugins from https://webpack.js.org/configuration/plugins/
16+
],
17+
experiments: { topLevelAwait: true },
18+
module: {
19+
rules: [
20+
{
21+
test: /\.(ts|tsx)$/i,
22+
loader: 'ts-loader',
23+
exclude: ['/node_modules/']
24+
},
25+
{
26+
test: /\.(eot|svg|ttf|woff|woff2|png|jpg|gif)$/i,
27+
type: 'asset'
28+
}
29+
30+
// Add your rules for custom modules here
31+
// Learn more about loaders from https://webpack.js.org/loaders/
32+
]
33+
},
34+
resolve: {
35+
extensions: ['.tsx', '.ts', '.jsx', '.js', '...'],
36+
fallback: { crypto: false }
37+
}
38+
};
39+
40+
module.exports = () => {
41+
if (isProduction) {
42+
config.mode = 'production';
43+
} else {
44+
config.mode = 'development';
45+
}
46+
return config;
47+
};

0 commit comments

Comments
 (0)