# Angular components bazel package output Overview of issues that came up and needed investigation/workarounds: | Description | Workaround? | | -------- | -------- | | `ng_package` rule that implements Angular Package Format breaks for secondary entry-points with factories. [See issue](https://github.com/angular/angular/issues/32651). | ✅ (needs proposal)| | `ng_package` rule not working on windows with many rollup globals. [See issue](https://github.com/angular/angular/issues/32603) |✅ (needs fix) | | `ng_module` rule does not allow specification of flat module entry-point. [See proposal PR to fix this](https://github.com/angular/angular/pulls) | ✅ (proposal; needs review)| | `ng_package` rule does not support `ts_library` targets for secondary entry-points. [See PR to fix this](https://github.com/angular/angular/pull/32610) | ✅ (fix landed) | | `ng_module` rules generate invalid imports for `injectables` that rely on symbols from flat module bundles. [More details below](#invalid-imports-blocker) | ✅ (needs fix). Very difficult to land upstream. See explanation of the problem to understand why. | | `ng_module` rules generate invalid imports for`injectables` that leak Bazel-specific workspace paths. More details below | ❌ (fix in progress; not clear whether it's possible to land) | ### Invalid imports blocker The `ng_module` bazel rule does not include metadata files when building a package. For example, if we build `material/snack-bar`, the metadata files of `cdk/overlay` are not included. Just the summary files of the `cdk/overlay` are available. This means that the compilation misses information about the flat module bundle of `cdk/overlay` and uses the summaries to resolve exported symbols. Since summaries are generated on a per-file basis, there is no concept of summaries for the dynamically generated flat module bundle. This means that symbols across different entry-points will be resolved to their original source file location. This causes NGC to generate imports to deep files rather than using the flat module entry-point (which is usually `index.js`). e.g. ```javascript= var i1 = require("@angular/cdk/overlay/overlay"); var i2 = require("@angular/cdk/a11y/live-announcer/live-announcer"); var i3 = require("@angular/cdk/layout/breakpoints-observer"); var i4 = require("@angular/material/snack-bar/snack-bar-module"); ``` This is problematic because as per implementation of the Angular package format, references to other entry-points are **only** possible if done through the actual entry-point (no deep imports). This is necessary to support the individual `package.json` fields for deeply-referenced entry-points (e.g. `es2015`, `main`, `fesm2015`). **Note**: here is an isolated reproduction that demonstrates the issue: https://github.com/devversion/bazel-ngc-wrapped-invalid-imports #### Why does this not surface in the current Gulp setup? This issue does not happen within the current gulp setup because our subsequent compilations have access to metadata files of previous entry-points. e.g. `cdk/overlay` is compiled with a flat module bundle and stored in `dist/packages/cdk/overlay/`. Now compilations for `material/snack-bar` can just have a path mapping to `dist/packages/cdk/overlay`, and NGC will be able to properly generate imports through the flat module bundle entry-point (as it knows that everything is exported in the entry-point due to the flat module metadata) #### How can we fix it? It looks like `ngc-wrapped` (which is the wrapper for the `ng_module` rule), overwrites the `fileExists` method of the host that is passed to NGC. ```typescript= bazelHost.fileExists = (fileName: string) => { // For ngsummary.json files (which match this regex), the "fileExists" // call will be delegated to the real TypeScript host. So it will check // on the disk. if (NGC_ASSETS.test(fileName)) { return tsHost.fileExists(fileName); } // For other files which aren't `ngsummary.json` files, the // `@bazel/typescript` compiler host will be used. **NOTE** // that this host does not check the *real file system*.. it // just checks if the file is part of the Bazel action inputs. // Metadata files are not added as inputs of the compilation! return origBazelHostFileExist.call(bazelHost, fileName); }; ``` https://github.com/angular/angular/blob/2b64031ddc1bb90b1e7a7da04937763c7a49b567/packages/bazel/src/ngc-wrapped/index.ts#L257-L262 Modifying the `NGC_ASSETS` regular expression in `ngc-wrapped` to allow `.metadata.json` files to be take into account, **fixes the issue**. Though it unveils another issue for the Bazel `ng_module` compilations. #### What's the issue if we allow metadata files in Bazel? > Disclaimer: There might be more reasons why metadata files are > not allowed in the Bazel compilations, but this is the one that appears > in the `components` repository if we allow metadata files. Let's consider we fixed the metadata reading issues in ngc-wrapped (as of the section above). Now that symbols are resolved through metadata (and not through summaries), the `StaticSymbolResolver` handles the symbol resolution. Within the compiler, there seems to be logic to **discard** metadata of class symbols which are from previous compilation units: ```typescript= if (this.summaryResolver.isLibraryFile(sourceSymbol.filePath) && !isTsFile && metadata && metadata['__symbolic'] === 'class') { const transformedMeta = {__symbolic: 'class', arity: metadata.arity}; return new ResolvedStaticSymbol(sourceSymbol, transformedMeta); } ``` https://github.com/angular/angular/blob/master/packages/compiler/src/aot/static_symbol_resolver.ts#L374-L384 We don't have this issue in the `angular/components` repository with our Gulp setup because it seems that the issue depends on the **`generateCodeForLibraries`** flag. The `ng_module` rule always it sets `generateCodeForLibraries` to `false`, while in vanilla `ngc` the flag is enabled by default. If the flag is set to `false`, the `isLibraryFile` check (see snippet above) will be always true for files from other compilation units and the metadata will be discarded. Causing the compilations to fail with exceptions like: ``` Unexpected value 'BidiModule in C:/users/paul/_bazel_paul/kn4tsvyh/execroot/angular_material/bazel-out/x64_windows-fastbuild/bin/src/cdk/bidi/index.d.ts' imported by the module 'ScrollingModule in C:/users/paul/_bazel_paul/kn4tsvyh/execroot/angular_material/src/cdk/scrolling/scro lling-module.ts'. Please add a @NgModule annotation. ``` Here is a reproduction for that issue: https://github.com/devversion/bazel-ngc-wrapped-invalid-imports#how-to-reproduce-the-bazel-generatecodeforlibaries-issue > [name=Paul] Not setting `generateCodeForLibaries` to `false` would solve the issue, but I > guess the reason we explicitly set it to `false` is that otherwise compilations would > write genfiles to locations which are out of scope of the current package. Basically > it would break hermeticity of the `ng_module` rule. It feels wrong to me that > metadata resolution depends on the `generateCodeForLibraries` flag. The flag is > described to only prevent generation of genfiles for libraries (which makes sense > in bazel to keep hermeticity), but apparently it does more than that. > ### Second issue that came up with import generation With the section above, we handled the issue of invalid imports being generated that should actually resolve to flat module bundle entries. A second issue with invalid imports comes from the fact that the `ng_module` rule always uses absolute manifest paths for importing symbols that aren't known to the summary resolver, or static symbol resolver. [See the actual code for details](https://github.com/angular/angular/blob/master/packages/compiler/src/aot/compiler.ts#L702-L705). This issue commonly surfaces when services inject other symbols from the same entry-point. [Concrete example in the components focus-trap](https://unpkg.com/browse/@angular/cdk@9.0.0/esm2015/a11y/focus-trap/focus-trap.js). The `ng_module` rule leaks Bazel specific paths into the release output. This is problematic, and the actual import should be relative since it's part of the same entry-point. The intent was to fix this for the `ng_module` rule, by never using full manifest paths for generated imports within the compilation unit. See: [angular#35841](https://github.com/angular/angular/pull/35841) for more details.