13

I want to add ngx-socket-io to my Angular application. I use Bazel to run my Angular dev-server. Unfortunately ngx-socket-io doesn't seem to work with the ts_devserver out of the box. I get this error in the browser console:

Uncaught TypeError: XMLHttpRequest is not a constructor
    at ts_scripts.js?v=1587802098203:16776
    at Object.23.../transport (ts_scripts.js?v=1587802098203:16780)
    at o (ts_scripts.js?v=1587802098203:11783)

It seems to be caused by xmlhttprequest-ssl which is a dependency of engine.io-client and it is needed by ngx-socket-io. But this problem only occurs with the ts_devserver. Running the Angular app in production works totally fine.

Minimal Reproduction

You can easily try it yourself: https://github.com/flolu/bazel-socketio-issue

Just run yarn install and then yarn dev (it causes the error in the browser console @ http://localhost:4200). And note that yarn prod @ http://localhost:8080 works just fine!

Edit 1

Currently there seems to be another issue on Windows. So you can only try the example repo if you're running Mac or Linux

Florian Ludewig
  • 4,338
  • 11
  • 71
  • 137

1 Answers1

5

The problem comes from engine.io-client, which is used internally by socket.io-client:

When socket.io-client gets built as UMD module triggered by

     "@npm//socket.io-client:socket.io-client__umd",

in BUILD.bazel, the browser key of engine.io-client/package.json:

 "browser": {
   "ws": false,
   "xmlhttprequest-ssl": "./lib/xmlhttprequest.js"
 },

is seemingly ignored.

As a consequence, the require('xmlhttprequest-ssl') statements in node_modules/engine.io-client/lib/transports/*.js remain in the UMD build. Because xmlhttprequest-ssl is intended for headless Node environments and does not work in browsers, this results in an error.

I couldn't find a reason/issue for this behavior, but I found a solution (which shouldn't be considered as a workaround):

Rewrite engine.io-client with a postinstall script:

  1. install shelljs package: yarn add -D shelljs
  2. update postinstall in package.json to: "postinstall": "node --preserve-symlinks --preserve-symlinks-main ./postinstall-patches.js && ngcc"
  3. put the following code into postinstall-patches.js at project root:
try {
  require.resolve('shelljs');
} catch (e) {
  // We are in an bazel managed external node_modules repository
  // and the resolve has failed because node did not preserve the symlink
  // when loading the script.
  // This can be fixed using the --preserve-symlinks-main flag which
  // is introduced in node 10.2.0
  console.warn(
      `Running postinstall-patches.js script in an external repository requires --preserve-symlinks-main node flag introduced in node 10.2.0. ` +
      `Current node version is ${process.version}. Node called with '${process.argv.join(' ')}'.`);
  process.exit(0);
}

const {set, cd, sed, ls} = require('shelljs');
const path = require('path');
const log = console.info;

log('===== about to run the postinstall-patches.js script     =====');
// fail on first error
set('-e');
// print commands as being executed
set('-v');

cd(__dirname);

log('\n# patch engine.io-client: rewriting \'xmlhttprequest-ssl\' to browser shim');
ls('node_modules/engine.io-client/lib/transports/*.js').forEach(function (file) {
  sed('-i', '\'xmlhttprequest-ssl\'', '\'../xmlhttprequest\'', file);
});

log('===== finished running the postinstall-patches.js script =====');

(Inspiration: https://bazelbuild.github.io/rules_nodejs/#patching-the-npm-packages, which links to the example https://github.com/angular/angular/blob/master/tools/postinstall-patches.js)

  1. yarn install
  2. yarn dev

I'll submit a pull request to your GitHub repro in a few minutes.


Possible alternatives, couldn't get them to work:

  • use socket.io-client/dist/socket.io.js but with an additional "UMD shim" because it seems to be an "anonymous UMD" module, OR
  • some npm_umd_bundle magic

See issue Every new npm dep needs a unique approach how to add it to ts_devserver #1055 in bazelbuild/rules_nodejs for more information about both ways.

Sebastian B.
  • 2,141
  • 13
  • 21
  • PR has been submitted – Sebastian B. Jun 10 '20 at 22:40
  • wow! I've just tried it and it works perfectly! I still have two questions: (1) you said "which shouldn't be considered as a workaround". do you mean that this shouldn't be used or that it is okay to use it? (2) the patching of the `engine.io-client` might also be done with something like [patch-package](https://www.npmjs.com/package/patch-package) with a .patch file? – Florian Ludewig Jun 11 '20 at 05:24
  • 1
    First: I'm just started with Bazel yesterday :-) To (1): I think patching is very OK, see "Patching the packages" at . I also found many issues where people needed to work around the pre-packaged builds published on NPM. So regarding the Bazel ecosystem, it's a fact that this is needed so there are tools and tricks like this. (2) Yes, should work. Alternative: see "Patching the built-in release" on the linked page. `postinstall-patches.js` gives you more flexibility and is more stable to future upgrades (e.g. we patch without hardcoded filenames). – Sebastian B. Jun 11 '20 at 08:04
  • Thank you so much! I'll wait until tomorrow before giving out the bounty :) What was your process of "detecting" the issue? I knew it had something to do with `xmlhttprequest` but, how did you drill it down to `engine.io-client`? – Florian Ludewig Jun 11 '20 at 08:07
  • 1
    I knew that `xmlhttprequest-ssl` is not intended for use in browser, so I wondered why it still was required when running the dev server. Finally, a small grep (ripgrep, more specifically: `rg -ul xmlhttprequest-ssl`) revealed references to `xmlhttprequest-ssl` in `socket.io-client` (but there not within its sources, only in the sourcemaps) and `engine.io-client`. – Sebastian B. Jun 11 '20 at 10:02
  • Since you have managed to fix this issue it would be awesome if you could take a look at this similar issue: https://stackoverflow.com/questions/64209912 – Florian Ludewig Oct 05 '20 at 13:50