angular: Ivy: Unable to write a reference

🐞 bug report

Affected Package

@angular/compiler-cli

Is this a regression?

No, does not seem so.

Description

When building my Angular app with Ivy enabled, I get this error:

ERROR in Unable to write a reference to AccordionDirective in /Users/dirkluijk/path/to/project/libs/widgets/src/lib/accordion/accordion.directive.ts from /Users/dirkluijk/path/to/project/libs/widgets/src/lib/accordion/accordion.module.ts

🔬 Minimal Reproduction

I have a complex project setup, a monorepo with multiple Angular libs.

đŸ”„ Exception or Error

With some debug statements in node_modules/@angular/compiler-cli/src/ngtsc/imports/src/emitter.js my output looks like this:

$ ng serve p1
  0% compiling

EMITTED LocalIdentifierStrategy HttpClientModule /Users/dirkluijk/path/to/project/libs/api/src/lib/api.module.ts
EMITTED AbsoluteModuleStrategy HttpClientModule /Users/dirkluijk/path/to/project/libs/api/src/lib/api.module.ts
EMITTED AbsoluteModuleStrategy HttpClientXsrfModule /Users/dirkluijk/path/to/project/libs/api/src/lib/api.module.ts
EMITTED AbsoluteModuleStrategy HttpClientXsrfModule /Users/dirkluijk/path/to/project/libs/api/src/lib/api.module.ts
EMITTED LocalIdentifierStrategy AccordionDirective /Users/dirkluijk/path/to/project/libs/widgets/src/lib/accordion/accordion.module.ts                                                                               

Date: 2019-03-17T19:52:02.577Z                                                  
Hash: 655d0cccad5d9464bf74
Time: 7484ms
chunk {main} main.js, main.js.map (main) 1.89 kB [initial] [rendered]
chunk {polyfills} polyfills.js, polyfills.js.map (polyfills) 704 bytes [initial] [rendered]
chunk {polyfills.es5} polyfills.es5.js, polyfills.es5.js.map (polyfills.es5) 322 kB [initial] [rendered]
chunk {runtime} runtime.js, runtime.js.map (runtime) 6.08 kB [entry] [rendered]
chunk {styles} styles.js, styles.js.map (styles) 643 kB [initial] [rendered]
chunk {vendor} vendor.js, vendor.js.map (vendor) 330 kB [initial] [rendered]

ERROR in Unable to write a reference to AccordionDirective in /Users/dirkluijk/path/to/project/libs/widgets/src/lib/accordion/accordion.directive.ts from /Users/dirkluijk/path/to/project/libs/widgets/src/lib/accordion/accordion.module.ts
Failed to compile.

🌍 Your Environment

Angular Version:


Angular CLI: 8.0.0-beta.7
Node: 10.9.0
OS: darwin x64
Angular: 8.0.0-beta.8
... animations, common, compiler, compiler-cli, core, forms
... http, language-service, platform-browser
... platform-browser-dynamic, router

Package                           Version
-----------------------------------------------------------
@angular-devkit/architect         0.800.0-beta.7
@angular-devkit/build-angular     0.800.0-beta.7
@angular-devkit/build-optimizer   0.800.0-beta.7
@angular-devkit/build-webpack     0.800.0-beta.7
@angular-devkit/core              8.0.0-beta.7
@angular-devkit/schematics        8.0.0-beta.7
@angular/cdk                      7.1.1
@angular/cli                      8.0.0-beta.7
@ngtools/webpack                  8.0.0-beta.7
@schematics/angular               8.0.0-beta.7
@schematics/update                0.800.0-beta.7
rxjs                              6.3.3
typescript                        3.1.6
webpack                           4.29.6

Anything else relevant? I make use of Typescript paths, combined with barrel files.

{
  "compilerOptions": {
    "paths": {
      "@p1/api": [
        "libs/api/src"
      ],
      "@p1/common": [
        "libs/common/src"
      ],
      "@p1/testing": [
        "libs/testing/src"
      ],
      "@p1/interactions": [
        "libs/interactions/src"
      ],
      "@p1/widgets": [
        "libs/widgets/src"
      ],
      "@p1/features": [
        "libs/features/src"
      ]
    },
  }
}

When I remove the error throw in node_modules/@angular/compiler-cli/src/ngtsc/imports/src/emitter.js, it seems that the files that fail are completely random:

image

About this issue

  • Original URL
  • State: closed
  • Created 5 years ago
  • Reactions: 36
  • Comments: 61 (25 by maintainers)

Commits related to this issue

Most upvoted comments

Hello all! For people monitoring this issue, I just wanted to give a quick update. I’ve put together a design doc describing the problem and my proposed solution. The basic idea is that in tsconfig.json you’ll tell the Angular compiler which of your path-mapped libraries are “private”. That will give the compiler enough information to correctly generate imports for the directives/components within those libraries.

Same error:

ERROR in Unable to write a reference to KeysPipe in /home/patrikx3/Projects/patrikx3/corifeus/corifeus-web-material/node_modules/corifeus-web/src/pipe/keys.ts from /home/patrikx3/Projects/patrikx3/corifeus/corifeus-web-material/node_modules/corifeus-web/src/module.ts

Only happens with Ivy.

@p3x-robot this works in my case:

  1. angular 8.2.0-next.1
  2. appending "rootDir": "." to the compilerOptions in the root tsconfig.json
  3. using index.ts like this:
"paths": {
    "@libs/api": ["libs/api/src/index.ts"],

BTW, thank you guys @pburkindine @james-criscuolo, you’re my favorite

I have exactly same problem, IMO freq will not be low Is there any workaround?

Hi we met the same problem, I think the freq is not low, and it blocks lots of angular lib to migrate to ivy. https://github.com/NG-ZORRO/ng-zorro-antd/pull/3740/files#diff-5

@alxhub Wow, thank you for your detailed explanation! Yes, it was hard to debug, as I didn’t even know what it is used for exactly.

So you say that a barrel file from an Angular Package Format module (e.g. built with ngPackagr) is not the same as a regular barrel file from a path-mapped directory? I didn’t know that before.

The reason I use this project layout, is that it should not matter if the package was actually pre-built or just a “local library” (monorepo approach). As a matter of fact, I just use the Nx Workspace project layout which is basically a Angular App + Angular Libs monorepo structure.

I will create a tracking issue at the Nrwl repo as well. In the mean time, what is your suggestion for monorepos like this?

  • no barrel imports?
  • no path mapping at all?
  • work with prebuilt libs instead?

I think Ivy is far from production stable.

@JoostK I can provide a repo here https://github.com/gnomeontherun/clarity/tree/angular9.

Run ng build --prod to see the following write reference error:

ERROR in Unable to write a reference to ClrIconCustomTag in src/clr-angular/icon/icon.ts from src/clr-angular/icon/icon.module.ts

I managed to get it working folloing @rt-gavrilov workaround and this article https://medium.com/@raviroshan.talk/fix-for-continuous-build-of-library-in-angular-workspace-c18422bab927

Steps to follow:

  1. Rename public-api.ts to index.ts in your libs. Update each ng-package.json of the libs to reference index.ts instead.

Library index.ts

export * from './src/features/maestros/conceptos/paginas/frm-e-conceptos/frm-e-conceptos.component';
export * from './src/features/maestros/conceptos/paginas/frm-e-conceptos/frm-e-conceptos.module';
export * from './src/appia-desktop.module';

ng-package.json

{
  "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
  "dest": "../../dist/appia-desktop",
  "lib": {
    "entryFile": "index.ts"
  }
}
  1. Edit the root tsconfig.json. Append “rootDir”: “.” to the compilerOptions.
    Change the paths inside compilerOptions to reference projects instead of dist

root tsconfig.json

{
  "compileOnSave": false,
  "compilerOptions": {
    "baseUrl": "./",
    "outDir": "./dist/out-tsc",
    "sourceMap": true,
    "declaration": false,
    "downlevelIteration": true,
    "experimentalDecorators": true,
    "module": "esnext",
    "moduleResolution": "node",
    "importHelpers": true,
    "target": "es2015",
    "typeRoots": [
      "node_modules/@types"
    ],
    "lib": [
      "es2018",
      "dom"
    ],
    "paths": {
      "appia-desktop": [
        "projects/appia-desktop"
      ],
      "appia-desktop/*": [
        "projects/appia-desktop/*"
      ],
    },
    "rootDir": "."
  },
  "angularCompilerOptions": {
    "fullTemplateTypeCheck": true,
    "strictInjectionParameters": true,
  }
}

AppModule

import { AppiaDesktopModule } from 'appia-desktop';
@NgModule({
  imports: [
    AppiaDesktopModule,

Now I can livereload angular libraries, wich is very convenient for development. I’m currently using “@angular/core”: “~9.0.0-next.3”. Hope it helps someone.

Seems like the main issue is that people want to avoid building library separately when it is consumed by the application and of course others also want a way to build the library separately and publish it.

Here are my thoughts, lets consider such mapping of paths in tsconfig:

"paths": {
      "xyz-core": [
        "projects/xyz/src/public-api.ts"
      ],
      "xyz-core/*": [
        "projects/xyz/src/lib/*"
      ]
}

Desired behavior from my side:

  • Inside application (or other libraries) only direct import from xyz-core should be allowed (just like any external lib)
  • Inside xyz-core lib imports with relative paths (scoped to the library) and imports via xyz-core/* path should be allowed. The latter is a bit more explicit and lets you avoid imports starting with '
/
/
/` (we also have tslint rule for this).
  • Bonus points if build would fail when importing from xyz-core/* in application / other library. Currently build still works.

At least this seem to work in non-ivy build,

Now the problem as I see is that when you build application, and import some.service.ts from xyz-core, it will most likely import other files which some of them may not even be part of barrel file (private) or use import from xyz-core/utils/index.

Maybe tsconfig.lib.ts could be used when path mapping points to a library barrel file to enrich typescript configuration (and ivy) with extra mappings during build? This way mapping of xyz-core/* could be left inside tsconfig.lib.json

@alxhub: Thank you for your hard work on this. Really appreciated!

For me at least the solution with a regular @nrwl/nx setup was just to add "rootDir": "." to the compilerOptions in tsconfig.json; thanks @james-criscuolo

On to the next error


I’m impressed by your debugging here! Out of curiosity, how hard was it to follow the logic?

You’re correct that the library not being in rootDirs is the cause of your error. This is not a supported configuration at the moment.

Currently, we support generating imports to files in two places:

  • Within the current project (relative imports used)
  • Within an Angular Package Format (APF) entry-point (absolute imports used)

APF guarantees that if FooModule is imported from ‘package/foo’, then the components it declares (e.g. FooComponent) are also available by importing ‘package/foo’.

Your path-mapped packages are of a third kind - outside the project, but also not in the APF. So assuming this layout:

libs/widgets/src:
  component.ts // declares WidgetComponent
  module.ts // declares WidgetModule with declarations [WidgetCmp]

your app might write

import {WidgetModule} from '@p1/widgets/module';

which is perfectly valid to do. However, It’s not valid to assume that WidgetComponent is also importable via the same path @p1/widgets/module, as it would be in an APF package. So what import should the Ivy compiler generate?

The obvious answer is @p1/widgets/component, but how does the compiler arrive at this? It would have to reverse engineer the path mapping and figure out that a file libs/widgets/src/X should be referred to as @p1/widgets/X. And the same directory can have multiple paths mapped to it. Path mappings were never intended to work this way.

So today, we don’t support this kind of layout. As we start looking more at this kind of multi-project use case, maybe we’ll introduce some mechanisms to support this.

by the way, do you guys know why Ivy is why has a 100kb larger bundle?

I fixed it! I am all repos on Ivy on Angular 8.1.0! The paths are working now, it looks like it does not like the @Host() decorator for Ivy. I replaced with a global variable and it is working! Ivy is picky, but I hacked it!

THANKS YO ALL!

@p3x-robot They do that for all issues that doesn’t affect the majority of users based on my experience. Also it’s not OP adding the labels, it’s the Angular-team. @dirkluijk is innocent.

I am no longer receiving the Unable to write a reference error. The problem was that my paths mapping in tsconfig.json was configured such that the libraries were referenced in the projects directory instead of the dist directory (for live library reload capability as commented above by @vgb1993). I understand however that this is not a supported configuration.

@JoostK thanks for responding. I added your suggested configuration but same result. I will try to create a reproducible repo that I can share.

as of 8.2.12 it is not deployed here: https://github.com/angular/angular/blob/8.2.12/packages/compiler-cli/src/ngtsc/imports/src/alias.ts Should have a FileToModuleAliasingHost class


@matsko: Will this be shipped in Angular@8.x.x?

Ivy does not like raw modules in node_modules. i needed a build step and move those packages into an out of node_modules into called build-modules (just copied those 2 modules into the build-modules folder) and now it is perfect.

{
    "compilerOptions": {
        "rootDir": ".",
        "baseUrl": ".",
        "skipLibCheck": true,
        "outDir": "build/aot",
        "target": "es2015",
        "module": "esnext",
        "moduleResolution": "node",
        "sourceMap": true,
        "emitDecoratorMetadata": true,
        "experimentalDecorators": true,
        "noImplicitAny": true,
        "suppressImplicitAnyIndexErrors": true,
        "lib": [
            "es2018",
            "dom"
        ],
        "paths": {
            "corifeus-web-material": [
                "build-modules/corifeus-web-material/index.ts"
            ],
            "corifeus-web": [
                "build-modules/corifeus-web/index.ts"
            ],
            "corifeus-web-material/*": [
                "build-modules/corifeus-web-material/*"
            ],
            "corifeus-web/*": [
                "build-modules/corifeus-web/*"
            ]
        }
    },
    "include": [
        "build-modules/**/*",
        "src/angular/**/*",
        "test/angular-webpack/**/*"
    ],
    "exclude": [
        "src/angular/bundle.ts",
        "test/angular-webpack/angular/bundle.ts",
        "test/angular-karma",
        "build/browser",
        "node_modules/corifeus-web/test",
        "node_modules/corifeus-web-material/test"
    ],
    "angularCompilerOptions-save": {
        "annotationsAs": "decorators",
        "preserveWhitespaces": false
    },
    "angularCompilerOptions": {
        "enableIvy": true
    }
}

I did some digging into the angular source and discovered my rootDir was “wrong” so the build process wasn’t finding anything. I happen to have a repo where the build directory and the src directory are in the same directory, and have set it up so that i can run my npm scripts from that parent and everything works. To get past this problem, I needed to add a rootDir argument to my tsconfig.json in my build directory pointing to my src directory. I had not set the rootDir in tsconfig.json previously, but now I can get past this error and complete a build (now on to runtime errors 😄 )

I realize this was mentioned earlier, but given I had no rootDir or rootDirs at all, I ignored it. If you also did that, it now appears one of the two must be set.