Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 9581658

Browse files
devversionmatsko
authored andcommitted
fix(bazel): do not use manifest paths for generated imports within compilation unit (#35841)
Currently, the `ng_module` rule incorrectly uses manifest paths for generated imports from the Angular compiler. This breaks packaging as prodmode output (i.e. `esnext`) is copied in various targets (`es5` and `es2015`) to the npm package output. e.g. imports are generated like: _node_modules/my-pkg/es2015/imports/public-api.js_ ```ts import * as i1 from "angular/packages/bazel/test/ng_package/example/imports/second"; ``` while it should be actually: ```ts import * as i1 from "./second"; ``` The imports can, and should be relative so that the files are self-contained and do not rely on custom module resolution. PR Close #35841
1 parent 68bebd6 commit 9581658

File tree

3 files changed

+65
-23
lines changed

3 files changed

+65
-23
lines changed

‎packages/bazel/src/ng_module.bzl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,12 @@ def _ngc_tsconfig(ctx, files, srcs, **kwargs):
315315
"createExternalSymbolFactoryReexports": (not _is_bazel()),
316316
# FIXME: wrong place to de-dupe
317317
"expectedOut": depset([o.path for o in expected_outs]).to_list(),
318+
# We instruct the compiler to use the host for import generation in Blaze. By default,
319+
# module names between source files of the same compilation unit are relative paths. This
320+
# is not desired in google3 where the generated module names are used as qualified names
321+
# for aliased exports. We disable relative paths and always use manifest paths in google3.
318322
"_useHostForImportGeneration": (not _is_bazel()),
323+
"_useManifestPathsAsModuleName": (not _is_bazel()),
319324
}
320325

321326
if _should_produce_flat_module_outs(ctx):

‎packages/bazel/src/ngc-wrapped/index.ts

Lines changed: 58 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -113,19 +113,17 @@ export function runOneBuild(args: string[], inputs?: {[path: string]: string}):
113113
}
114114
}
115115

116-
const expectedOuts = config['angularCompilerOptions']['expectedOut'];
116+
// These are options passed through from the `ng_module` rule which aren't supported
117+
// by the `@angular/compiler-cli` and are only intended for `ngc-wrapped`.
118+
const {expectedOut, _useManifestPathsAsModuleName} = config['angularCompilerOptions'];
117119

118120
const {basePath} = ng.calcProjectFileAndBasePath(project);
119121
const compilerOpts = ng.createNgCompilerOptions(basePath, config, tsOptions);
120122
const tsHost = ts.createCompilerHost(compilerOpts, true);
121123
const {diagnostics} = compile({
122124
allDepsCompiledWithBazel: ALL_DEPS_COMPILED_WITH_BAZEL,
123-
compilerOpts,
124-
tsHost,
125-
bazelOpts,
126-
files,
127-
inputs,
128-
expectedOuts
125+
useManifestPathsAsModuleName: _useManifestPathsAsModuleName,
126+
expectedOuts: expectedOut, compilerOpts, tsHost, bazelOpts, files, inputs,
129127
});
130128
if (diagnostics.length) {
131129
console.error(ng.formatDiagnostics(diagnostics));
@@ -144,9 +142,11 @@ export function relativeToRootDirs(filePath: string, rootDirs: string[]): string
144142
return filePath;
145143
}
146144

147-
export function compile({allDepsCompiledWithBazel = true, compilerOpts, tsHost, bazelOpts, files,
148-
inputs, expectedOuts, gatherDiagnostics, bazelHost}: {
145+
export function compile({allDepsCompiledWithBazel = true, useManifestPathsAsModuleName,
146+
compilerOpts, tsHost, bazelOpts, files, inputs, expectedOuts,
147+
gatherDiagnostics, bazelHost}: {
149148
allDepsCompiledWithBazel?: boolean,
149+
useManifestPathsAsModuleName?: boolean,
150150
compilerOpts: ng.CompilerOptions,
151151
tsHost: ts.CompilerHost, inputs?: {[path: string]: string},
152152
bazelOpts: BazelOptions,
@@ -199,13 +199,14 @@ export function compile({allDepsCompiledWithBazel = true, compilerOpts, tsHost,
199199
throw new Error(`Couldn't find bazel bin in the rootDirs: ${compilerOpts.rootDirs}`);
200200
}
201201

202-
const expectedOutsSet = new Set(expectedOuts.map(p => p.replace(/\\/g, '/')));
202+
const expectedOutsSet = new Set(expectedOuts.map(p => convertToForwardSlashPath(p)));
203203

204204
const originalWriteFile = tsHost.writeFile.bind(tsHost);
205205
tsHost.writeFile =
206206
(fileName: string, content: string, writeByteOrderMark: boolean,
207207
onError?: (message: string) => void, sourceFiles?: ts.SourceFile[]) => {
208-
const relative = relativeToRootDirs(fileName.replace(/\\/g, '/'), [compilerOpts.rootDir]);
208+
const relative =
209+
relativeToRootDirs(convertToForwardSlashPath(fileName), [compilerOpts.rootDir]);
209210
if (expectedOutsSet.has(relative)) {
210211
expectedOutsSet.delete(relative);
211212
originalWriteFile(fileName, content, writeByteOrderMark, onError, sourceFiles);
@@ -290,20 +291,32 @@ export function compile({allDepsCompiledWithBazel = true, compilerOpts, tsHost,
290291

291292
const ngHost = ng.createCompilerHost({options: compilerOpts, tsHost: bazelHost});
292293
const fileNameToModuleNameCache = new Map<string, string>();
293-
ngHost.fileNameToModuleName = (importedFilePath: string, containingFilePath: string) => {
294+
ngHost.fileNameToModuleName = (importedFilePath: string, containingFilePath?: string) => {
295+
const cacheKey = `${importedFilePath}:${containingFilePath}`;
294296
// Memoize this lookup to avoid expensive re-parses of the same file
295297
// When run as a worker, the actual ts.SourceFile is cached
296298
// but when we don't run as a worker, there is no cache.
297299
// For one example target in g3, we saw a cache hit rate of 7590/7695
298-
if (fileNameToModuleNameCache.has(importedFilePath)) {
299-
return fileNameToModuleNameCache.get(importedFilePath);
300+
if (fileNameToModuleNameCache.has(cacheKey)) {
301+
return fileNameToModuleNameCache.get(cacheKey);
300302
}
301-
const result = doFileNameToModuleName(importedFilePath);
302-
fileNameToModuleNameCache.set(importedFilePath, result);
303+
const result = doFileNameToModuleName(importedFilePath, containingFilePath);
304+
fileNameToModuleNameCache.set(cacheKey, result);
303305
return result;
304306
};
305307

306-
function doFileNameToModuleName(importedFilePath: string): string {
308+
function doFileNameToModuleName(importedFilePath: string, containingFilePath?: string): string {
309+
const relativeTargetPath =
310+
relativeToRootDirs(importedFilePath, compilerOpts.rootDirs).replace(EXT, '');
311+
const manifestTargetPath = `${bazelOpts.workspaceName}/${relativeTargetPath}`;
312+
if (useManifestPathsAsModuleName === true) {
313+
return manifestTargetPath;
314+
}
315+
316+
// Unless manifest paths are explicitly enforced, we initially check if a module name is
317+
// set for the given source file. The compiler host from `@bazel/typescript` sets source
318+
// file module names if the compilation targets either UMD or AMD. To ensure that the AMD
319+
// module names match, we first consider those.
307320
try {
308321
const sourceFile = ngHost.getSourceFile(importedFilePath, ts.ScriptTarget.Latest);
309322
if (sourceFile && sourceFile.moduleName) {
@@ -342,11 +355,31 @@ export function compile({allDepsCompiledWithBazel = true, compilerOpts, tsHost,
342355
ngHost.amdModuleName) {
343356
return ngHost.amdModuleName({ fileName: importedFilePath } as ts.SourceFile);
344357
}
345-
const result = relativeToRootDirs(importedFilePath, compilerOpts.rootDirs).replace(EXT, '');
346-
if (result.startsWith(NODE_MODULES)) {
347-
return result.substr(NODE_MODULES.length);
358+
359+
// If no AMD module name has been set for the source file by the `@bazel/typescript` compiler
360+
// host, and the target file is not part of a flat module node module package, we use the
361+
// following rules (in order):
362+
// 1. If target file is part of `node_modules/`, we use the package module name.
363+
// 2. If no containing file is specified, or the target file is part of a different
364+
// compilation unit, we use a Bazel manifest path. Relative paths are not possible
365+
// since we don't have a containing file, and the target file could be located in the
366+
// output directory, or in an external Bazel repository.
367+
// 3. If both rules above didn't match, we compute a relative path between the source files
368+
// since they are part of the same compilation unit.
369+
// Note that we don't want to always use (2) because it could mean that compilation outputs
370+
// are always leaking Bazel-specific paths, and the output is not self-contained. This could
371+
// break `esm2015` or `esm5` output for Angular package release output
372+
// Omit the `node_modules` prefix if the module name of an NPM package is requested.
373+
if (relativeTargetPath.startsWith(NODE_MODULES)) {
374+
return relativeTargetPath.substr(NODE_MODULES.length);
375+
} else if (
376+
containingFilePath == null || !bazelOpts.compilationTargetSrc.includes(importedFilePath)) {
377+
return manifestTargetPath;
348378
}
349-
return bazelOpts.workspaceName + '/' + result;
379+
const containingFileDir =
380+
path.dirname(relativeToRootDirs(containingFilePath, compilerOpts.rootDirs));
381+
const relativeImportPath = path.posix.relative(containingFileDir, relativeTargetPath);
382+
return relativeImportPath.startsWith('.') ? relativeImportPath : `./${relativeImportPath}`;
350383
}
351384

352385
ngHost.toSummaryFileName = (fileName: string, referringSrcFileName: string) => path.posix.join(
@@ -464,6 +497,10 @@ function isCompilationTarget(bazelOpts: BazelOptions, sf: ts.SourceFile): boolea
464497
(bazelOpts.compilationTargetSrc.indexOf(sf.fileName) !== -1);
465498
}
466499

500+
function convertToForwardSlashPath(filePath: string): string {
501+
return filePath.replace(/\\/g, '/');
502+
}
503+
467504
function gatherDiagnosticsForInputsOnly(
468505
options: ng.CompilerOptions, bazelOpts: BazelOptions,
469506
ngProgram: ng.Program): (ng.Diagnostic | ts.Diagnostic)[] {

‎packages/bazel/test/ng_package/example_package.golden

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1455,7 +1455,7 @@ export { MyService } from './public-api';
14551455
import { Injectable } from '@angular/core';
14561456
import { MySecondService } from './second';
14571457
import * as i0 from "@angular/core";
1458-
import * as i1 from "angular/packages/bazel/test/ng_package/example/imports/second";
1458+
import * as i1 from "./second";
14591459
export class MyService {
14601460
/**
14611461
* @param {?} secondService
@@ -1684,7 +1684,7 @@ import { __decorate, __metadata } from "tslib";
16841684
import { Injectable } from '@angular/core';
16851685
import { MySecondService } from './second';
16861686
import * as i0 from "@angular/core";
1687-
import * as i1 from "angular/packages/bazel/test/ng_package/example/imports/second";
1687+
import * as i1 from "./second";
16881688
var MyService = /** @class */ (function () {
16891689
function MyService(secondService) {
16901690
this.secondService = secondService;

0 commit comments

Comments
 (0)