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/remove node globals #435

Closed
wants to merge 4 commits into from

Conversation

hugomrdias
Copy link

This PR removes usage of node globals and adds buffer, events and next-tick to the dependencies. This will allow readable-stream to works with bundlers that don't inject node globals.

Some code is borrowed from this PR #414

Warning:
These lines should make the browsers tests fail
https://github.com/nodejs/readable-stream/blob/master/lib/internal/streams/buffer_list.js#L18-L19
https://github.com/nodejs/readable-stream/blob/master/lib/internal/streams/buffer_list.js#L200
because of this
https://github.com/nodejs/readable-stream/blob/master/package.json#L55
but they don't, so either airtap ignores 'util': false or something else is happening that i didn’t catch.
I need some feedback on how you guys would like to fix this, maybe use https://www.npmjs.com/package/browser-util-inspect or just mock inspect.

@hugomrdias
Copy link
Author

@vweevers i used next-tick instead of immediate (this one made one test fail) and it doesn't use global maybe we can switch abstract-leveldown to it ?

@vweevers
Copy link
Contributor

At fist glance, next-tick doesn't have a queue and doesn't support args (process.nextTick(fn,1,2)).

Copy link
Member

@mcollina mcollina left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the past we used https://github.com/calvinmetcalf/process-nextick-args instead. The reason we dropped was to remove a dependency (because there is a phobia of having too many dependencies).

next-tick would not work for this module because it does not support positional arguments.

@hugomrdias
Copy link
Author

calvinmetcalf/process-nextick-args

This module uses process.nextTick the goal of this PR is to NOT use process.nextTick.

Btw where are nextTick positional arguments used? If this is really the case maybe we need to add a test for that because everything is green now.

@@ -558,7 +560,7 @@ function emitReadable_(stream) {
function maybeReadMore(stream, state) {
if (!state.readingMore) {
state.readingMore = true;
process.nextTick(maybeReadMore_, stream, state);
nextTick(maybeReadMore_, stream, state);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should not work with the polyfill. I think our browser testing is adding process.nextTick.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its just using the first if here https://github.com/medikoo/next-tick/blob/master/index.js#L47-L49 because airtap is injecting everything, its hard to validate this with the current repo setup i will try to fix this

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes exactly. However if those transform are not put in there, it won't work.

@hugomrdias
Copy link
Author

@mcollina can you have a look at the warning i wrote in the PR description

@mcollina
Copy link
Member

can you have a look at the warning i wrote in the PR description

I agree, there is something weird going on. @vweevers do you think we can disable node globals with airtap?

@vweevers
Copy link
Contributor

@hugomrdias Are you sure that this line is called by any browser test?

return inspect(this, _objectSpread({}, options, {

If so, what is the actual value of inspect that you see? The other line isn't expected to fail, because util: false means that require('util') returns an empty object:

var _require2 = require('util'),
inspect = _require2.inspect;

@vweevers
Copy link
Contributor

@mcollina We can specify browserify options via .airtap.yml, along the lines of:

browserify:
  options:
    detectGlobals: false

Not sure if you mean globals (like process) or core modules (like util) though.

@hugomrdias
Copy link
Author

@hugomrdias Are you sure that this line is called by any browser test?

return inspect(this, _objectSpread({}, options, {

yeah i did some more testing and this line is never called in the browser tests

If so, what is the actual value of inspect that you see? The other line isn't expected to fail, because util: false means that require('util') returns an empty object:

var _require2 = require('util'),
inspect = _require2.inspect;

and yes the require2 is {} but inspect is undefined we should at least add a guard at the bottom to future proof

@hugomrdias
Copy link
Author

hugomrdias commented Apr 29, 2020

@mcollina We can specify browserify options via .airtap.yml, along the lines of:

browserify:
  options:
    detectGlobals: false

Not sure if you mean globals (like process) or core modules (like util) though.

it would be everything so we can be sure this works with webpack 5 or rollup without node plugin, etc.
the problem here is all the tests would break because they come directly from node core.

im not seeing a way to test this properly with the current setup, i can only validate this in ipfs/libp2p tests (where i can disable node stuff for the full test bundle) and report back any issue i find.

@mcollina
Copy link
Member

it would be everything so we can be sure this works with webpack 5 or rollup without node plugin, etc.
the problem here is all the tests would break because they come directly from node core.

This is not correct, we run different tests in the browser: https://github.com/nodejs/readable-stream/tree/master/test/browser. Also this is important

if (!global.console) {
global.console = {};
}
if (!global.console.log) {
global.console.log = function () {};
}
if (!global.console.error) {
global.console.error = global.console.log;
}
if (!global.console.info) {
global.console.info = global.console.log;
}
var test = require('tape');
var util = require('util');
// TODO: add replacements instead
global.process = {
env: {},
on: function () {},
cwd: function () {
return '/';
},
binding: function () {
return {
hasTracing: false
};
}
};
.

I think we should update the browser test environment so that we do not rely on node core stuff.

@vweevers
Copy link
Contributor

im not seeing a way to test this properly with the current setup, i can only validate this in ipfs/libp2p tests (where i can disable node stuff for the full test bundle)

This should do the trick:

browserify:
  - options:
      detectGlobals: false
      builtins: false

@hugomrdias
Copy link
Author

hugomrdias commented Apr 29, 2020

even if the tests code can run without node builtins how would tape run ?

builtins: false gives me this

Uncaught Error: Cannot find module '_process'
    at o (_prelude.js:1)
    at _prelude.js:1
    at Object./Users/hugomrdias/code/readable-stream/node_modules/tape/index.js../lib/default_stream (index.js:151)
    at o (_prelude.js:1)
    at _prelude.js:1
    at Object.<anonymous> (browser.js:13)
    at Object./Users/hugomrdias/code/readable-stream/test/browser.js../browser/test-stream-big-packet (browser.js:82)
    at o (_prelude.js:1)
    at r (_prelude.js:1)
    at _prelude.js:1

@mcollina
Copy link
Member

Ok, this is getting complicated. Before supporting this use case, we'd need a way to automatically verify this works at a very high level.

I'm happy to switch frontend framework if it helps

@vweevers
Copy link
Contributor

Alternatively, fix tape to work without globals as well (readable-stream isn't the only module that will hit this problem). /cc @ljharb do you reckon that's feasibe?

@ljharb
Copy link
Member

ljharb commented May 10, 2020

Bundlers that don’t inject node globals when bundling node modules are broken. The fix is to fix or replace your bundler, not attempt to add complexity to every module in the ecosystem.

@vweevers
Copy link
Contributor

@ljharb That's a discussion to take up with Webpack. I can kinda see their point but it's putting a strain on the ecosystem and its maintainers. Wish I had the time to engage Webpack folks.

@mcollina
Copy link
Member

Is there a flag or plugin that can be passed to webpack to do this?

@ljharb
Copy link
Member

ljharb commented May 11, 2020

@vweevers those who it's putting a strain on, are who should be taking up that discussion.

@vweevers
Copy link
Contributor

vweevers commented May 12, 2020

@mcollina There's no flag, because webpack 5 removes it:

image

@vweevers
Copy link
Contributor

I see a couple of options:

  1. Take a principled stance that bundlers must inject node globals. If nothing changes on webpack's side, this option will alienate webpack users.
  2. Remove node globals here, without the ability to verify it works. We'd be supporting webpack users in theory only; things can break without us knowing.
  3. Remove node globals here, run browser tests with webpack and alternatives to airtap and tape.

@ljharb
Copy link
Member

ljharb commented May 12, 2020

Webpack users will already likely stay on v4, since v5's choices break the ecosystem. I think the stance that "a bundler for X to environment Y should work for any code written for X that can possibly work in environment Y" isn't an unreasonable one to take.

@vweevers
Copy link
Contributor

I agree that's a reasonable stance. If we take it, I do believe further action is required. Because this PR and many others prove that "webpack users will already likely stay on v4" isn't true; webpack is actively encouraging folks to update modules like readable-stream and is not offering alternative solutions atm.

@ljharb
Copy link
Member

ljharb commented May 12, 2020

@vweevers i had some discussion on the webpack slack, and it looks like https://github.com/webpack/changelog-v5#automatic-nodejs-polyfills-removed tells you how to restore this behavior using the ProvidePlugin, without needing to alter any part of the ecosystem.

@vweevers
Copy link
Contributor

@hugomrdias Could you try that out? https://webpack.js.org/plugins/provide-plugin/

@mcollina mcollina mentioned this pull request May 27, 2020
@mcollina
Copy link
Member

#439

@calvinmetcalf
Copy link
Contributor

if we want a concrete example of the problems of using different microtask shims (ironically, all written by me) see this issue defunctzombie/node-process#88

hugomrdias added a commit to ipfs/aegir that referenced this pull request Jun 11, 2020
This PR changes webpack config to not include node globals and built-ins by default.

For now theres still a --node flag to allow node globals if needed.

`process` is still includes because `readable-stream` will only remove it in v4 nodejs/readable-stream#435

BREAKING CHANGE: browser code will NOT have node globals and builtins available.
hugomrdias added a commit to ipfs/aegir that referenced this pull request Jun 16, 2020
This PR changes webpack config to not include node globals and built-ins by default.

For now theres still a --node flag to allow node globals if needed.

`process` is still includes because `readable-stream` will only remove it in v4 nodejs/readable-stream#435

closes ipfs/js-ipfs#2924

BREAKING CHANGE: browser code will NOT have node globals and builtins available.
@martinheidegger
Copy link

martinheidegger commented Jun 19, 2020

In regards to tape: in my fork https://github.com/martinheidegger/tape I updated it to use no node-globals. It references in the dependencies back to this branch https://github.com/martinheidegger/tape/blob/5c2a1ebeabda328539dbd117a369ff91de9701d6/package.json#L38-L43 Outdated.

@martinheidegger
Copy link

As tape is committed to stay version compatible (see: #505 and #506), it seemed to me a bit tricky to make sure this works, so I decided to fork tapefresh-tape that compiles with webpack. Currently fresh-tape depends to this branch, and on through2#102. As such, I would love to see this branch merged.

@ljharb
Copy link
Member

ljharb commented Jun 23, 2020

I would urge this package to also retain support for older nodes.

@martinheidegger
Copy link

@ljharb If I was able to make it support node < 8 I would have just sent PR's to tape...

@ljharb
Copy link
Member

ljharb commented Jun 23, 2020

@martinheidegger i mean, concat-stream could still be changed to make that possible :-) but really the PRs should be sent to webpack.

@martinheidegger
Copy link

@ljharb concat-stream might work but through should be tricky, tap is very unlikely (though may also be unnecessary); additionally I am not sure about the compatibility of all new dependencies (like through2, inherits, path-browserify, ...)... In short: if you manage to get it to work I am happy to deprecate/archive fresh-tape in favor of tape...

Personally I am in favor of webpack enforcing a no-node-globals policy as this is probably good for the ecosystem - big picture, but either way I will adapt to what is given.

@kelly-tock
Copy link

is there any update on this? seems like it would fix quite a few issues with webpack 5.

@pkit
Copy link

pkit commented Mar 18, 2021

This PR removes usage of node globals and adds buffer, events and next-tick to the dependencies. This will allow readable-stream to works with bundlers that don't inject node globals.

Some code is borrowed from this PR #414

Warning:
These lines should make the browsers tests fail
https://github.com/nodejs/readable-stream/blob/master/lib/internal/streams/buffer_list.js#L18-L19
https://github.com/nodejs/readable-stream/blob/master/lib/internal/streams/buffer_list.js#L200
because of this
https://github.com/nodejs/readable-stream/blob/master/package.json#L55
but they don't, so either airtap ignores 'util': false or something else is happening that i didn’t catch.
I need some feedback on how you guys would like to fix this, maybe use https://www.npmjs.com/package/browser-util-inspect or just mock inspect.

Any non-node env will fail when stream is piped couple of times, like stream = stream.pipe(new PassThrough(stream)).pipe(new PassThrough(stream))
Webpack obviously produces failing code there:

var _require2 = __webpack_require__(/*! util */ "?1dff"),
    inspect = _require2.inspect;
/***/ "?1dff":
/*!**********************!*\
  !*** util (ignored) ***!
  \**********************/
/***/ (() => {

/* (ignored) */

/***/ }),

P.S. will fail in any other bundler I know, btw.

@reintroducing
Copy link

This package is a dependency of microphone-stream which is not working when using Vite. I believe this is cause by the use of global as seen in the screenshot of my bug report in their repo. Is the removal of the node globals something that can be handled or is it not possible, until a rewrite, to use this package in the Vite ecosystem?

@ljharb
Copy link
Member

ljharb commented Feb 26, 2022

@reintroducing the proper vix remains that vite, webpack 5, and whoever else has made this incorrect choice but claims to be able to bundle node modules, needs to provide, or transform global.

Vite (or anyone else) could very easily transform every global instance to globalThis by default, and it would Just Work for everyone, and not require any upstream changes.

@reintroducing
Copy link

@ljharb I've read the exchanges between you and others and engineers on the Vite team in various GH issues over the past few days while trying to find a way around this. it's an unfortunate reality that we are currently in but nonetheless a reality that exists. that being said, i cannot find a way to do this in Vite, nor have I found any sort of polyfill for it, so am I basically left with the reality that I either use webpack 4 with my codebase (that would be very unfortunate and not really an option for us realistically) or continue to use webpack 5/Vite and find a different way to achieve what I'm after in my particular code (basically have to find a replacement for microphone-stream)? I have been searching but can't find a way to polyfill this in vite (and realistically how to do it in webpack 5 is also not something I've come across successfully).

@ljharb
Copy link
Member

ljharb commented Feb 27, 2022

I would suggest avoiding tools that fail to interoperate with the existing, decade-old ecosystem and conventions.

@targos
Copy link
Member

targos commented Feb 27, 2022

I've been using this in my Vite config to work around a module that uses global:

    define: {
      global: 'globalThis',
    },

@reintroducing
Copy link

@targos amazingly that worked...! is it really that simple? I feel like it can't be, but if it is, im kind of shocked.

@reintroducing
Copy link

Lol, so after getting that working, I got to my next roadblock, which, in a production build of Vite, buffer.from is not available (as a result of another dependency of readable-stream,safe-buffer) . That led me down the path of trying to find a solution for that, which I still have yet to do, but i found another workaround for the global stuff, which was adding this in my vite config file:

import NodeModulesPolyfills from '@esbuild-plugins/node-modules-polyfill';

optimizeDeps: {
    esbuildOptions: {
        plugins: [NodeModulesPolyfills()]
    }
}

I tried adding to that the following:

import GlobalsPolyfills from '@esbuild-plugins/node-globals-polyfill'

optimizeDeps: {
    esbuildOptions: {
        plugins: [
            GlobalsPolyfills({
                buffer: true,
            }),
            NodeModulesPolyfills(),
        ],
    },
},

but of course that didn't work. I'm just documenting all of this here in case someone else lands on this in a google search eventually and is facing these issues to save them some time.

I feel like this is going to be a fun little game of cat and mouse until I can get the production build working for all dependencies.

@reintroducing
Copy link

was finally able to get the production build working (with global and buffer) using the following config:

import nodePolyfills from 'rollup-plugin-polyfill-node';
import NodeModulesPolyfills from '@esbuild-plugins/node-modules-polyfill';

resolve: {
    alias: {
        // don't need to add this to deps, it's included by @esbuild-plugins/node-modules-polyfill
        buffer: 'rollup-plugin-node-polyfills/polyfills/buffer-es6',
    },
},
optimizeDeps: {
    esbuildOptions: {
        plugins: [
            NodeModulesPolyfills(),
        ],
    },
},
build: {
    rollupOptions: {
        plugins: [nodePolyfills()],
    },
},

While this works, i have absolutely ZERO confidence in it in not only its current state but when updates to Vite (or any of these plugins) are made in the future.

@mcollina mcollina closed this May 23, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

10 participants