esbuild import path resolution issue with angular v17 for modules outsides the workspace
Which @angular/* package(s) are the source of the bug?
compiler-cli
Is this a regression?
Yes
Description
A workspace has been created with angular version 17 and the project does not compile.
The issue arises when ng tries to compiles modules outsides the workspace.
As seen in ng/src/app/app.module.ts, the application imports some modules that are outside the workspace. One of them is GongModule.
...
import { GongModule } from 'gong'
@NgModule({
declarations: [
AppComponent
],
imports: [
...
GongModule,
As seen in vendor/github.com/fullstack-lang/gong/ng/projects/gong/src/lib/gong.module.ts, GongModule itself imports angular modules
import { NgModule } from '@angular/core';
@NgModule({
declarations: [
],
imports: [
CommonModule,
...
...
Angular cannot build the application
% ng build
⠋ Building...✘ [ERROR] Could not resolve "@angular/common/http"
../vendor/github.com/fullstack-lang/gong/ng/projects/gong/src/lib/commitnbfromback.service.ts:2:27:
2 │ import { HttpParams } from '@angular/common/http';
╵ ~~~~~~~~~~~~~~~~~~~~~~
...
Preliminary analysis
This is new with ng 17
This problem did not occur with ng v16
This is an issue related to the import path
going verbose and having a trace in ng/build_trace.txt, one better understands what's wrong
% ng build --verbose > build_trace.txt 2>&1
ng/build_trace.txt
Searching for "@angular/common/http" in "node_modules" directories starting from "/private/tmp/ngimportissue/vendor/github.com/fullstack-lang/gongdoc/ng/projects/gongdoc/src/lib"
Matching "@angular/common/http" against "paths" in "/private/tmp/ngimportissue/ng/tsconfig.app.json"
Using "/private/tmp/ngimportissue/ng" as "baseURL"
Found a fuzzy match for "*" in "paths"
Attempting to load "/private/tmp/ngimportissue/ng/node_modules/@angular/common/http" as a file
Checking for file "http"
Checking for file "http.mjs"
Checking for file "http.js"
Checking for file "http.ts"
Checking for file "http.tsx"
Failed to find file "http"
Attempting to load "/private/tmp/ngimportissue/ng/node_modules/@angular/common/http" as a directory
Read 2 entries for directory "/private/tmp/ngimportissue/ng/node_modules/@angular/common/http"
No "browser" map found in directory "/private/tmp/ngimportissue/ng/node_modules/@angular/common/http"
Failed to find file "/private/tmp/ngimportissue/ng/node_modules/@angular/common/http/index.mjs"
Failed to find file "/private/tmp/ngimportissue/ng/node_modules/@angular/common/http/index.js"
Failed to find file "/private/tmp/ngimportissue/ng/node_modules/@angular/common/http/index.ts"
Failed to find file "/private/tmp/ngimportissue/ng/node_modules/@angular/common/http/index.tsx"
Parsed package name "@angular/common" and package subpath "./http"
✘ [ERROR] Could not resolve "@angular/common/http"
It seems the builder is mislead by the the import path. It does not search the esm2022 or fesm2022 where the index file is present. Indeed ng/node_modules/@angular/common/esm2022/http/http.mjs is present (for information, this file is present but not in the git repo also, you need to perfom npm i to have it present).
The import path in ng/tsconfig.json that worked with ng v16 is configured to work with import path outside the workspace.
"paths": {
"*": [
"./node_modules/*"
],
This is related to esbuild
As stated in https://angular.io/guide/esbuild#using-the-application-builder, ng uses esbuild when it is application. Therefore I suspects the is an issue with esbuild import path resolution.
Please provide a link to a minimal reproduction of the bug
https://github.com/thomaspeugeot/ngimportissue
Please provide the exception or error you saw
see above
Please provide the environment you discovered this bug in (run ng version)
% ng version
...
Angular CLI: 17.0.3
Anything else?
No response
This is actually expected behavior for this configuration. TypeScript has provided more formalization of the behavior for the paths option. It includes a section on this specific issue which can be found here: https://www.typescriptlang.org/docs/handbook/modules/reference.html#paths-should-not-point-to-monorepo-packages-or-node_modules-packages
And includes the following:
While module specifiers that match paths aliases are bare specifiers, once the alias is resolved, module resolution proceeds on the resolved path as a relative path. Consequently, resolution features that happen for node_modules package lookups, including package.json "exports" field support, do not take effect when a paths alias is matched.
Unfortunately, as packages and projects adopt the newer methods of defining package entry points, the above used path mapping configuration will become increasingly problematic and can lead to both TypeScript and bundling errors.
The recommended solution within the linked documentation is to leverage the project's package manager to link the relevant packages into the node module hierarchy of the project. In this case, it should allow standard node module resolution to occur for the @angular/ packages.
Thank you @clydin for the explanation. That this is the expected behavior makes the port to ng 17 not routine at all for this use case. Allow me to present this use case and a recommendation.
That ng v17 enforces the resolution of packages via node_modules
I had a look at the documentation
https://www.typescriptlang.org/docs/handbook/modules/reference.html#paths-should-not-point-to-monorepo-packages-or-node_modules-packages
I noticed the following
The same caveat applies to packages referencing each other in a monorepo. Instead of using paths to make TypeScript artificially resolve "@my-scope/lib" to a sibling package, it’s best to use workspaces via npm, yarn, or pnpm to symlink your packages into node_modules, so both TypeScript and the runtime or bundler perform real node_modules package lookups. This is especially important if the monorepo packages will be published to npm—the packages will reference each other via node_modules package lookups once installed by users, and using workspaces allows you to test that behavior during local development.
The assumption that the angular code will be published to public/private repos does not apply to this use case.
This use case is a fullstack application with a backend in go and the front end in angular. The front end dist is embedded in the go code (via a //go:embed directive).
Also this stack uses other stacks with the same architecture and the go dependency mechanism is the sole mechanism that is used to implement the dependency to both the backend end code and to the front code of the other stack (this is done via the go vendor mechanism). From a configuration management point of view, this single dependency mechanism is strong since it enforces the synchronization between the front end code and and back end code of the dependency. For debugging, this is also very practical because you can tweak the dependency code as well.
In this use case, the angular code of some dependencies does not sit as compiled code in nmp registry but sits as source code on github.
So this use case does not publish / import via a public/private repo. Doing so will be a major effort with no obvious benefit I can see so far.
That the configuration breaks is normal
OK, there is an issue when porting the code of this use case to the ng V17 env. The current issue is with the code that is ported, not with ng v17.
Fair enough, during previous ports to new version of angular, there was some effort but not something so deep. I bring to your attention that this could be a bit disturbing for a go programmer familiar with the go compatibility promise.
This particular issue should be mentioned in the documentation or a warning should be displayed
https://angular.io/guide/esbuild#esm-default-imports-vs-namespace-imports
TypeScript by default allows default exports to be imported as namespace imports and then used in call expressions. This is unfortunately a divergence from the ECMAScript specification. The underlying bundler (esbuild) within the new build system expects ESM code that conforms to the specification. The build system will now generate a warning if your application uses an incorrect type of import of a package.
With the incorrect type of import of a package of this use case, the build system makes more than warning, it breaks the build altogether.
There is one of two possible recommendation:
- This part of the document should be updated to indicate that the build system can break or warn
- or, the builder shall issue a warning but not breaks the build
I've noticed an issue also that in general, aliases specified in "paths" are not working anymore after the upgrade if switching to "application" as building system
With 17.1.1 works for me
Closing as this is expected and working as intended.
This issue has been automatically locked due to inactivity. Please file a new issue if you are encountering a similar or related problem.
Read more about our automatic conversation locking policy.
This action has been performed automatically by a bot.