mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
Bundlers like Rollup may use an element access expression for an export declaration, which causes ngcc to ignore those export declarations possibly resulting in incomplete processing of packages. Element access syntax may be used when the declared name is not considered as valid JS identifier, but bundlers may be conservative in determining whether an identifier can be used (to emit a property access) and opt for a string literal in an element access instead. The element access syntax introduces a problem for ngcc, where it wouldn't consider such export as class declaration, causing them to be skipped. The ngtsc compiler is implemented with the assumption that all class declarations use a `ts.Identifier` as name, whereas the element access is using a string literal for the declared name. This makes it troublesome for ngcc to support this syntax form in UMD bundles. To work around the problem, this function transforms these access expressions into regular property accesses. The source text is parsed to an AST to allow finding the element accesses in a robust way, after which the affected text ranges are replaced with property accesses in the original source text. Closes #44037 PR Close #44669
137 lines
5.4 KiB
TypeScript
137 lines
5.4 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright Google LLC All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by an MIT-style license that can be
|
|
* found in the LICENSE file at https://angular.io/license
|
|
*/
|
|
import ts from 'typescript';
|
|
|
|
import {absoluteFrom, AbsoluteFsPath, getFileSystem, NgtscCompilerHost} from '../../../src/ngtsc/file_system';
|
|
import {TestFile} from '../../../src/ngtsc/file_system/testing';
|
|
import {DtsProcessing} from '../../src/execution/tasks/api';
|
|
import {BundleProgram, makeBundleProgram} from '../../src/packages/bundle_program';
|
|
import {NgccEntryPointConfig} from '../../src/packages/configuration';
|
|
import {EntryPoint, EntryPointFormat} from '../../src/packages/entry_point';
|
|
import {EntryPointBundle} from '../../src/packages/entry_point_bundle';
|
|
import {NgccSourcesCompilerHost} from '../../src/packages/ngcc_compiler_host';
|
|
import {createModuleResolutionCache, EntryPointFileCache, SharedFileCache} from '../../src/packages/source_file_cache';
|
|
|
|
export type TestConfig = Pick<NgccEntryPointConfig, 'generateDeepReexports'>;
|
|
|
|
export function makeTestEntryPoint(
|
|
entryPointName: string, packageName: string = entryPointName, config?: TestConfig): EntryPoint {
|
|
return {
|
|
name: entryPointName,
|
|
path: absoluteFrom(`/node_modules/${entryPointName}`),
|
|
packageName,
|
|
packagePath: absoluteFrom(`/node_modules/${packageName}`),
|
|
repositoryUrl: `https://github.com/${packageName}`,
|
|
packageJson: {name: entryPointName},
|
|
typings: absoluteFrom(`/node_modules/${entryPointName}/index.d.ts`),
|
|
compiledByAngular: true,
|
|
ignoreMissingDependencies: false,
|
|
generateDeepReexports: config !== undefined ? !!config.generateDeepReexports : false,
|
|
};
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param format The format of the bundle.
|
|
* @param files The source files to include in the bundle.
|
|
* @param dtsFiles The typings files to include the bundle.
|
|
*/
|
|
export function makeTestEntryPointBundle(
|
|
packageName: string, format: EntryPointFormat, isCore: boolean, srcRootNames: AbsoluteFsPath[],
|
|
dtsRootNames?: AbsoluteFsPath[], config?: TestConfig,
|
|
enableI18nLegacyMessageIdFormat = false): EntryPointBundle {
|
|
const entryPoint = makeTestEntryPoint(packageName, packageName, config);
|
|
const src = makeTestBundleProgram(srcRootNames[0], isCore);
|
|
const dts = dtsRootNames ?
|
|
makeTestDtsBundleProgram(dtsRootNames[0], entryPoint.packagePath, isCore) :
|
|
null;
|
|
const isFlatCore = isCore && src.r3SymbolsFile === null;
|
|
return {
|
|
entryPoint,
|
|
format,
|
|
rootDirs: [absoluteFrom('/')],
|
|
src,
|
|
dts,
|
|
dtsProcessing: dtsRootNames ? DtsProcessing.Yes : DtsProcessing.No,
|
|
isCore,
|
|
isFlatCore,
|
|
enableI18nLegacyMessageIdFormat
|
|
};
|
|
}
|
|
|
|
export function makeTestBundleProgram(
|
|
path: AbsoluteFsPath, isCore: boolean = false,
|
|
additionalFiles?: AbsoluteFsPath[]): BundleProgram {
|
|
const fs = getFileSystem();
|
|
const entryPointPath = fs.dirname(path);
|
|
const rootDir = fs.dirname(entryPointPath);
|
|
const options: ts.CompilerOptions =
|
|
{allowJs: true, maxNodeModuleJsDepth: Infinity, checkJs: false, rootDir, rootDirs: [rootDir]};
|
|
const moduleResolutionCache = createModuleResolutionCache(fs);
|
|
const entryPointFileCache =
|
|
new EntryPointFileCache(fs, new SharedFileCache(fs), sourceText => sourceText);
|
|
const host =
|
|
new NgccSourcesCompilerHost(fs, options, entryPointFileCache, moduleResolutionCache, rootDir);
|
|
return makeBundleProgram(
|
|
fs, isCore, rootDir, path, 'r3_symbols.js', options, host, additionalFiles);
|
|
}
|
|
|
|
export function makeTestDtsBundleProgram(
|
|
path: AbsoluteFsPath, packagePath: AbsoluteFsPath, isCore: boolean = false): BundleProgram {
|
|
const fs = getFileSystem();
|
|
const options = {};
|
|
const host = new NgtscCompilerHost(fs, options);
|
|
return makeBundleProgram(fs, isCore, packagePath, path, 'r3_symbols.d.ts', options, host);
|
|
}
|
|
|
|
export function convertToDirectTsLibImport(filesystem: TestFile[]) {
|
|
return filesystem.map(file => {
|
|
const contents =
|
|
file.contents
|
|
.replace(
|
|
`import * as tslib_1 from 'tslib';`,
|
|
`import { __decorate, __metadata, __read, __values, __param, __extends, __assign } from 'tslib';`)
|
|
.replace(/tslib_1\./g, '');
|
|
return {...file, contents};
|
|
});
|
|
}
|
|
|
|
export function convertToInlineTsLib(filesystem: TestFile[], suffix: string = '') {
|
|
return filesystem.map(file => {
|
|
const contents = file.contents
|
|
.replace(`import * as tslib_1 from 'tslib';`, `
|
|
var __decorate${suffix} = null;
|
|
var __metadata${suffix} = null;
|
|
var __read${suffix} = null;
|
|
var __values${suffix} = null;
|
|
var __param${suffix} = null;
|
|
var __extends${suffix} = null;
|
|
var __assign${suffix} = null;
|
|
`).replace(/tslib_1\.([_a-z]+)/gi, '$1' + suffix.replace('$', '$$'));
|
|
return {...file, contents};
|
|
});
|
|
}
|
|
|
|
export function getRootFiles(testFiles: TestFile[]): AbsoluteFsPath[] {
|
|
return testFiles.filter(f => f.isRoot !== false).map(f => absoluteFrom(f.name));
|
|
}
|
|
|
|
/**
|
|
* Mock out the lockfile path resolution, which uses `require.resolve()`.
|
|
*/
|
|
export function mockRequireResolveForLockfile() {
|
|
const moduleConstructor: any = module.constructor;
|
|
const originalResolveFileName = moduleConstructor._resolveFilename;
|
|
spyOn<any>(moduleConstructor, '_resolveFilename').and.callFake(function(request: string) {
|
|
if (request === '@angular/compiler-cli/package.json') {
|
|
return '/node_modules/' + request;
|
|
} else {
|
|
return originalResolveFileName.apply(null, arguments as any);
|
|
}
|
|
});
|
|
}
|