Angular CLI: "Module not found: Error: Can't resolve..."
In this article, I will walk you through how to fix an error that many people are encountering after the release of Angular CLI v6.
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.js Module 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.
I was not alone, 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 process,
- supply 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–and 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 which introduced a breaking change. 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:
- Create an empty file
src/empty.ts
. - Add the
"paths"
property to thetsconfig.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 our unit tests too: we have to add empty.ts
to the files array of tsconfig.spec.json
.
Related issue: Uncaught ReferenceError: global is not defined
In Node.js, global variables are attached to a special object called global
–which is the equivalent to the window
object in the Browser. When we call setTimeout()
in Node.js, we are in fact calling global.setTimeout()
.
Some libraries rely on this global
object which doesn't exist in the Browsers leading to the following error:
Uncaught ReferenceError: global is not defined
Because the window
object of the Browser and the global
object of Node.js are pretty much the same, we can trick the browser to fake the global
object with the window
object by adding the following code to your polyfills.ts
file:
(window as any).global = window;
I hope that you enjoyed this article and that it has helped find a solution to your issue. If so, please let me know in the comment section below.
References
If you want more context about this issue, check out the following links:
- contentful-sdk-core#58: my original issue on the Contentful SDK repo,
- Angular CLI#9812: the introduction of the BREAKING CHANGE,
- Angular CLI#10681 and Angular CLI#9827: discussions around the issue,
- Related issue: "Uncaught ReferenceError: global is not defined".