In this article, I will walk you through how to fix an error that many people are encountering with Angular CLI 6+.

The Problem

After upgrading one of my Angular applications, from version 5 to version 6, I tried to build it (using ng build), but had the following error:

ERROR in ./node_modules/contentful-sdk-core/dist/es-modules/get-user-agent.jsModule not found: Error: Can't resolve 'os' in 'path/to/project/node_modules/contentful-sdk-core/dist/es-modules'

For more details, see this issue I created in the Contentful SDK repository.

Many people ran into similar issues.

The Cause

When I checked the source code of get-user-agent.js I found this:

import { platform, release } from 'os';

The os module is a built-in Node.js module that provides utilities to work with the operating system. I was building for the web where nothing from the os module could run. The library I installed was using, like many libraries, was built for both the web, and Node. But it didn't provide a specific version for the web.

But It Used to Work Before Version 5 of the CLI

It used to work before Angular CLI 6 because the Angular CLI team provided support for it:

In Angular CLI we never provided a browser version of node built-ins. But we did:provide a shim for global and an empty module when fs, crypto, tls and net were requested.

The Angular CLI team believes that libraries that are meant to run inside the browser are not supposed to use Node API (I totally agree with them). Here is a quote from Filipe Silva.

We understand that this isn't great if your code relies, directly or indirectly, on a library that makes incorrect assumptions about browser environments. The best I can say is that you should bring this problem to their attention via an issue on their tracker. Maybe newer versions of that library don't have this behaviour anymore.
But although it is inconvenient to address these problems, I hope we can agree that the current behaviour is incorrect. Browser code should not rely on things that are not available in browser environments.

Therefore, they removed support for this kind of behavior via this Pull Request. That's why many applications broke after upgrading to version 6.

The Solution

If we had access to the internal Webpack configuration of the CLI, we could use Webpack's node configuration options to tell it to replace those modules with empty objects. But the ng eject command has been removed since Angular CLI 6...

The easy workaround I found was to use TypeScript path mapping:

  1. Create an empty file src/empty.ts.
  2. Add the "paths" property to the tsconfig.json file.

This tells the TypeScript compiler that imports from os should be looked in the file src/empty.ts.

This will fix the issue for our application. But we are not done yet. There is one more step to make it work for tests too: we have to add empty.ts to the files array of tsconfig.spec.json.


If you enjoyed this article, follow @ahasall on Twitter for more content like this.