Solvedweb3.js Angular hack with node: {crypto: true} doesn't work anymore in Angular 12

Angular 12 has migrated to Webpack 5 and made a bunch of other changes to it's build system. Now the fix mentioned in readme - #2260 (comment) - has stopped to work: after modifying node_modules/@angular-devkit/build-angular/src/webpack/configs/browser.js by replacing node: false with node: {crypto: true, stream: true}, build fails with the next error:

[ng] An unhandled exception occurred: Invalid configuration object. Webpack has been initialized using a configuration object that does not match the API schema.
[ng]  - configuration.node should be one of these:
[ng]    false | object { __dirname?, __filename?, global? }
[ng]    -> Include polyfills or mocks for various node stuff.
[ng]    Details:
[ng]     * configuration.node has an unknown property 'crypto'. These properties are valid:
[ng]       object { __dirname?, __filename?, global? }
[ng]       -> Options object for node compatibility features.
[ng]     * configuration.node has an unknown property 'stream'. These properties are valid:
[ng]       object { __dirname?, __filename?, global? }
[ng]       -> Options object for node compatibility features.
[ng]     * configuration.node has an unknown property 'os'. These properties are valid:
[ng]       object { __dirname?, __filename?, global? }
[ng]       -> Options object for node compatibility features.
[ng]     * configuration.node has an unknown property 'http'. These properties are valid:
[ng]       object { __dirname?, __filename?, global? }
[ng]       -> Options object for node compatibility features.
[ng]     * configuration.node has an unknown property 'https'. These properties are valid:
[ng]       object { __dirname?, __filename?, global? }
[ng]       -> Options object for node compatibility features.
[ng]     * configuration.node has an unknown property 'assert'. These properties are valid:
[ng]       object { __dirname?, __filename?, global? }
[ng]       -> Options object for node compatibility features.
[ng] See "/private/var/folders/hn/608xz_1x3rn_6q0xptx_lm100000gn/T/ng-RDkDB8/angular-errors.log" for further details.

Seems like some validation was added to browser config.

Could you please take a look for possible workarounds?

Environment

Angular 12.0.0
web3 1.3.6

23 Answers

✔️Accepted Answer

@NicolasKritter Thanks for sharing your solution, I've fixed the process and Buffer errors the way you've described.

There is another solution to provide node polyfills. Instead of modifying node_modules/@angular-devkit/build-angular/src/webpack/configs/browser.js and adding postinstall script to provide resolve.fallback, you can specify paths property in tsconfig.json, e.g.:

tsconfig.json

{
  "compilerOptions": {
    "paths" : {
      "crypto": ["./node_modules/crypto-browserify"],
      "stream": ["./node_modules/stream-browserify"],
      "assert": ["./node_modules/assert"],
      "http": ["./node_modules/stream-http"],
      "https": ["./node_modules/https-browserify"],
      "os": ["./node_modules/os-browserify"],
    }
  }
}

Of course you still need to install mentioned packages, as you've mentioned above.

This makes the solution a bit cleaner. Hope it helps.

Other Answers:

Ok guys, after another couple of hours of debugging a finally reached the solution.

Solution:
Remove the script (see above from @NicolasKritter) from the index.html file and add the following lines to the polyfills.ts file:

 (window as any).global = window;
 import { Buffer } from 'buffer';
 global.Buffer = Buffer;
 global.process = {
    env: { DEBUG: undefined },
    version: '',
    nextTick: require('next-tick')
    } as any;

Hi, i spent 2 days on the same issue and found a solution.

in the same file: node_modules/@angular-devkit/build-angular/src/webpack/configs/browser.js you should add this property in the resolve part (line 51) such as:

fallback:{
    http: require.resolve("stream-http"),
    https: require.resolve("https-browserify"),
    crypto: require.resolve("crypto-browserify"),
    stream:require.resolve("stream-browserify"),
    os:require.resolve("os-browserify/browser"),
    assert:require.resolve("assert/"),
}

once you defined the resolve.fallback, you need to manually install theses packages (npm i stream-http...) (when you try co compile, angular will tell you which package to install.
I tried just setting the package to false like crypto:false in the fallback it it creates other errors

Secondly, I had other issue to fix such as global is not defined, Buffer is not defined,process is not defined) after the compilation when i go the the localhost:4200

solution:
in index.html

<script>
  var global = global || window; //open this if you take global error

  var process = process || {
    env: {
      DEBUG: undefined
    },
    version: ''
  };
</script>

in polyfills.ts:

just put
global.Buffer = global.Buffer || require('buffer').Buffer;

I also tried this "cleaner way"

import { Buffer } from 'buffer';
global.Buffer = Buffer;

It works now, I probably will find a better way to handle this. (probably we can disable some of the fallback by setting them to false).

This problem is caused by webpack 5 not including the node polyfills anymore.

this seems to be the reason:
https://github.com/webpack/changelog-v5/blob/master/README.md#automatic-nodejs-polyfills-removed

NOTE: you will need to edit the browsers.js after each npm i if it reinstall angular, i did this script to do it automagically:
and put in package.json:
"postinstall": "node patch.js && ngcc"

/* eslint-disable @typescript-eslint/no-var-requires */

const {
  readFileSync,
  writeFileSync,
  existsSync
} = require('fs');

const fix = `fallback:{
  http: require.resolve("stream-http"),
  https: require.resolve("https-browserify"),
  crypto: require.resolve("crypto-browserify"),
  stream:require.resolve("stream-browserify"),
  os:require.resolve("os-browserify/browser"),
  assert:require.resolve("assert/")
},`;


const patchTag = '//-patched';

const fileToPatch = 'node_modules/@angular-devkit/build-angular/src/webpack/configs/browser.js';
const codeToPatch = 'mainFields: [\'es2015\', \'browser\', \'module\', \'main\'],';
const patch =codeToPatch+ fix +patchTag;


function getAllIndexes(arr, val) {
  const indexes = [];
  let  i;
  for (i = 0; i < arr.length; i++)
  {if (arr[i].indexOf(val) !== -1)
  {indexes.push(i);}}
  return indexes;
}
function doPatch(fileName, sourceCode, patchCode, patchIdentifier) {
  if (!existsSync(fileName)) {
    console.log('file not found ' + fileName);
    return;
  }
  const contents = readFileSync(fileName).toString().split('\n');
  // Check if code has been patched already
  const hasBeenPatched = contents.find(line => line.indexOf(patchIdentifier) !== -1);

  if (!hasBeenPatched) {
    const lineNumbers = getAllIndexes(contents, sourceCode);
    if (lineNumbers.length < 1) {
      console.error('Could not find source code. Please check ' + fileName + ' and update the patch accordingly');
      return;
    }
    // replace the line
    lineNumbers.forEach((lineNumber) => {
      contents.splice(lineNumber, 1, patchCode);
    });
    const updatedContents = contents.join('\n');
    writeFileSync(fileName, updatedContents);

    console.log('Monkey patched');
  } else {
    console.log('already been patched');
  }
}
doPatch(fileToPatch, codeToPatch, patch, patchTag);

Cheers

Ok guys, after another couple of hours of debugging a finally reached the solution.

Solution:
Remove the script (see above from @NicolasKritter) from the index.html file and add the following lines to the polyfills.ts file:

 (window as any).global = window;
 import { Buffer } from 'buffer';
 global.Buffer = Buffer;
 global.process = {
    env: { DEBUG: undefined },
    version: '',
    nextTick: require('next-tick')
    } as any;

Absolute magic, thank you!

Thanks @dmitry-salnikov This is a better solution as it does not require a dirty fix in the angular installation and is commited

I just had to change the pasth from ./node_modules/crypto-browserify to ../node_modules/crypto-browserify as I use "src" as baseUrl

More Issues: