Skip to content
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

fix: circumvent issues with ESM process polyfills #543

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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
27 changes: 27 additions & 0 deletions lib/internal/shims/process.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/* wraps the internal process module, circumventing issues with some polyfills (see #539) */

/** @type {import('node:process')} */
const process = ((base, esmKey, keys, isValid) => {
// check if top-level es module, in which case it may have a default export
if (esmKey in base && base[esmKey] === true) {
let candidate
for (const key of keys) {
if (!(key in base)) {
continue
}
candidate = base[key]
// sanity check
if (isValid(candidate)) {
return candidate
}
}
}
return base
})(
require('process/'),
'__esModule',
['default', 'process'],
(candidate) => 'nextTick' in candidate
)

module.exports = process
2 changes: 1 addition & 1 deletion lib/internal/streams/destroy.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

/* replacement start */

const process = require('process/')
const process = require('../shims/process')

/* replacement end */

Expand Down
2 changes: 1 addition & 1 deletion lib/internal/streams/duplexify.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* replacement start */

const process = require('process/')
const process = require('../shims/process')

/* replacement end */

Expand Down
2 changes: 1 addition & 1 deletion lib/internal/streams/end-of-stream.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* replacement start */

const process = require('process/')
const process = require('../shims/process')

/* replacement end */
// Ported from https://github.com/mafintosh/end-of-stream with
Expand Down
2 changes: 1 addition & 1 deletion lib/internal/streams/from.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

/* replacement start */

const process = require('process/')
const process = require('../shims/process')

/* replacement end */

Expand Down
2 changes: 1 addition & 1 deletion lib/internal/streams/pipeline.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* replacement start */

const process = require('process/')
const process = require('../shims/process')

/* replacement end */
// Ported from https://github.com/mafintosh/pump with
Expand Down
2 changes: 1 addition & 1 deletion lib/internal/streams/readable.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* replacement start */

const process = require('process/')
const process = require('../shims/process')

/* replacement end */
// Copyright Joyent, Inc. and other Node contributors.
Expand Down
2 changes: 1 addition & 1 deletion lib/internal/streams/writable.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* replacement start */

const process = require('process/')
const process = require('../shims/process')

/* replacement end */
// Copyright Joyent, Inc. and other Node contributors.
Expand Down
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
"test:prepare": "node test/browser/runner-prepare.mjs",
"test:browsers": "node test/browser/runner-browser.mjs",
"test:bundlers": "node test/browser/runner-node.mjs",
"test:vite": "node test/browser/runner-vite.mjs",
"test:readable-stream-only": "node readable-stream-test/runner-prepare.mjs",
"coverage": "c8 -c ./c8.json tap --rcfile=./tap.yml test/parallel/test-*.js test/ours/test-*.js",
"format": "prettier -w src lib test",
Expand Down Expand Up @@ -77,6 +78,9 @@
"tape": "^5.5.3",
"tar": "^6.1.11",
"undici": "^5.1.1",
"vite": "^5.4.9",
"vite-plugin-commonjs": "^0.10.3",
"vite-plugin-node-polyfills": "^0.22.0",
"webpack": "^5.72.1",
"webpack-cli": "^4.9.2"
},
Expand Down
10 changes: 10 additions & 0 deletions test/browser/fixtures/vite/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>readable-stream test:vite</title>
</head>
<body>
<h1>Testing...</h1>
<script src="index.mjs" type="module"></script>
</body>
</html>
56 changes: 56 additions & 0 deletions test/browser/fixtures/vite/index.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { reportSuccess, reportError, reportLog } from './lib/reporting.mjs'
import { createBankStream } from './lib/bank.mjs'
import { createCollectorStream } from './lib/collector.mjs'

const DATA_SIZE = 512

async function test() {
await reportLog('Generating test data...')

const expected = new Uint8Array(DATA_SIZE)
window.crypto.getRandomValues(expected)

await reportLog('Creating input stream...')
const readable = createBankStream(expected)

await reportLog('Creating output stream...')
const writable = createCollectorStream()

await reportLog('Piping...')
await new Promise((resolve, reject) => {
readable.pipe(writable)
.on('finish', resolve)
.on('error', reject)
})

await reportLog('Comparing...')
const retrieved = writable.collect()
if (retrieved.length !== DATA_SIZE) {
throw new Error(`Expected output data of length ${DATA_SIZE}, got ${retrieved.length}`)
}

let nMatch = 0
let firstNonMatch = -1
for (let i = 0; i < DATA_SIZE; i++) {
if (expected[i] === retrieved[i]) {
nMatch++
} else if (firstNonMatch === -1) {
firstNonMatch = i
}
}

if (firstNonMatch === -1) {
await reportLog('100% match!')
} else {
await reportLog(`expected: ${expected}`)
await reportLog(`actual: ${retrieved}`)

const percent = (nMatch / DATA_SIZE) * 100
throw new Error(`${percent.toFixed(2)}% match (first mismatch at position ${firstNonMatch})`)
}
}

(async () => {
await test()
await reportSuccess()
})().catch(reportError)
24 changes: 24 additions & 0 deletions test/browser/fixtures/vite/lib/bank.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Readable } from '@me'

/**
* Returns a Readable stream that reports the content of a buffer
* @param buffer {Uint8Array}
* @return {import('stream').Readable}
*/
export function createBankStream(buffer) {
let bytesRead = 0
const readable = new Readable()
readable._read = function (count) {
let end = bytesRead + count
let done = false
if (end >= buffer.byteLength) {
end = buffer.byteLength
done = true
}
readable.push(buffer.subarray(bytesRead, bytesRead = end))
if (done) {
readable.push(null)
}
}
return readable
}
51 changes: 51 additions & 0 deletions test/browser/fixtures/vite/lib/collector.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { Writable } from '@me'

const LOAD_FACTOR = 0.75
const INITIAL_CAPACITY = 16

/**
* Returns a Writable stream that stores chunks to an internal buffer, retrievable with #collect().
* @returns {import("stream").Writable & { collect(): Uint8Array }}
*/
export function createCollectorStream() {
let capacity = INITIAL_CAPACITY
let buffer = new Uint8Array(capacity)
let size = 0

// ensures that "buffer" can hold n additional bytes
function provision(n) {
const requiredSize = size + n
if (requiredSize <= capacity) {
return
}
const newCapacity = Math.ceil((requiredSize + 1) / LOAD_FACTOR)
let arrayBuffer = buffer.buffer
if ('transfer' in arrayBuffer) {
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer/transfer
arrayBuffer = arrayBuffer.transfer(newCapacity)
buffer = new Uint8Array(arrayBuffer)
} else {
const copy = new Uint8Array(newCapacity)
copy.set(buffer, 0)
buffer = copy
}
capacity = newCapacity
}

const writable = new Writable()
writable._write = function (chunk, _, cb) {
if (!(chunk instanceof Uint8Array)) {
throw new Error('Unexpected chunk')
}
provision(chunk.byteLength)
buffer.set(chunk, size)
size += chunk.byteLength
cb(null)
}

return Object.assign(writable, {
collect: function () {
return buffer.subarray(0, size)
}
})
}
52 changes: 52 additions & 0 deletions test/browser/fixtures/vite/lib/reporting.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
const ENDPOINT_STATUS = '/status'
const ENDPOINT_LOG = '/log'

function closeCurrentWindow() {
window.close()
}

async function reportRaw(endpoint, data, tryBeacon) {
data = JSON.stringify(data)
if (tryBeacon && typeof navigator === 'object' && 'sendBeacon' in navigator) {
data = (new TextEncoder()).encode(data)
data = data.buffer
navigator.sendBeacon(endpoint, data)
} else {
await fetch(endpoint, {
method: 'POST',
body: data,
headers: {
'Content-Type': 'application/json'
}
})
}
}

export async function reportSuccess() {
await reportRaw(ENDPOINT_STATUS, { success: true }, true)
closeCurrentWindow()
}

export async function reportError(error) {
let msg = 'Unknown error'
if (typeof error === 'string') {
msg = error
} else if (typeof error === 'object') {
if (error instanceof Error) {
msg = error.message
} else if (error !== null) {
msg = `${error}`
}
} else {
msg = `${error}`
}
await reportRaw(ENDPOINT_STATUS, { success: false, error: msg }, true)
closeCurrentWindow()
}

/**
* @param message {string}
*/
export async function reportLog(message) {
await reportRaw(ENDPOINT_LOG, { message }, false)
}
15 changes: 15 additions & 0 deletions test/browser/fixtures/vite/vite.config.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { defineConfig } from 'vite'
import { nodePolyfills } from 'vite-plugin-node-polyfills'
import commonjs from 'vite-plugin-commonjs'

// noinspection JSUnusedGlobalSymbols
export default defineConfig({
mode: 'development',
build: {
sourcemap: true
},
plugins: [
commonjs(),
nodePolyfills()
]
})
Loading