UNPKG

@tscc/tscc

Version:

A typescript transpiler and bundler that wires up tsickle and closure compiler seamlessly

124 lines (123 loc) 7.05 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); /** * @fileoverview Starting from a provided set of files, it walks Typescript SourceFiles that are * referenced from previous SourceFiles. * * This information is provided to tsickleHost so that only such referenced files are processed by * tsickle. This is mainly concerned with what files to use to generate externs. Why not just feed * every `.d.ts` file to generate externs? Currently Typescript's type inclusion often includes "too * many files" -- If tsconfig.json does not specify `types` compiler option, it will include every * type declarations in `./node_modules/@types`, `../node_modules/@types`, * `../../node_modules/@types`. Such a behavior is actually OK for usual TS usecase, because types * anyway do not affect the Typescript transpilation output. However, in our setup they are all used * to generate externs, and the more the type declarations, the more it takes to compile and the * more it is prone to errors. * * An easy way(for me) would be to require users to provide every such package's name. But sometimes * a package(A) may implicitly refers to another package(B)'s type declarations, and that package B * also needs to be provided to tsickle, so this way requires users to __know__ what other packages * this package A refers to, which requires users to inspect its contents, and this is not * ergonomic. * * At the other extreme, we can include every .d.ts that typescript "sees". This will lead to the * most correct behavior in some sense, because this is something you see in your IDE. But this may * potentially lead to enormous amount of externs file and slow down the compilation as it will * include everything in `node_modules/@types` directory unless you use types[root] compiler option. * This may also cause more bugs coming from incompatibility between typescript and the closure * side. * * Therefore, an intermediate approach is taken here. We use the same module resolution logic to * find out which files were explicitly referenced by user-provided file. This requires discovering * files that are either (1) imported (2) triple-slash-path-referenced (3) * triple-slash-types-referenced. However, some declaration files that augments the global scope may * not be discoverable in this way, so we add external modules provided in spec file and any module * that is indicated in `compilerOptions.types` tsconfig key to this. * * There are some work going on from TS's side in a similar vein. * {@link https://github.com/microsoft/TypeScript/issues/40124} * * Currently, this is done using an unexposed API of Typescript. I'm not sure why this is unexposed * -- there are APIs such as `getResolvedModuleFileName/setResolvedModuleFileName`, but not * something to iterate over resolved module file names. */ const ts = require("typescript"); const patch_tsickle_module_resolver_1 = require("../tsickle_patches/patch_tsickle_module_resolver"); const path = require("path"); class TypescriptDependencyGraph { constructor(host) { this.host = host; this.visited = new Set(); this.defaultLibDir = path.normalize(path.dirname(ts.getDefaultLibFilePath(this.host.getCompilerOptions()))); this.walkModeAwareResolvedFileCache = (elem) => { this.walk(elem === null || elem === void 0 ? void 0 : elem.resolvedFileName); }; } isDefaultLib(fileName) { return fileName.startsWith(this.defaultLibDir); } isTslib(fileName) { return (0, patch_tsickle_module_resolver_1.getPackageBoundary)(fileName).endsWith(path.sep + 'tslib' + path.sep); } isTsccAsset(fileName) { return (0, patch_tsickle_module_resolver_1.getPackageBoundary)(fileName).endsWith(path.sep + '@tscc' + path.sep + 'tscc' + path.sep); } walk(fileName) { if (typeof fileName !== 'string') return; // Typescript may use unix-style path separators in internal APIs even on Windows environment. // We should normalize it because we use string === match on file names, for example in // shouldSkipTsickleProcessing. fileName = path.normalize(fileName); // Default libraries (lib.*.d.ts) files and tslib.d.ts are not processed by tsickle. if (this.isDefaultLib(fileName) || this.isTslib(fileName) || this.isTsccAsset(fileName)) return; // add file to visited set if (this.visited.has(fileName)) return; this.visited.add(fileName); const sf = this.host.getSourceFile(fileName); /** * Files imported to the current file are available in `resolvedModules` property. * See: Microsoft/Typescript/src/compiler/programs.ts `ts.createProgram > processImportedModules` * function. It calls `setResolvedModule` function for all external module references --> * This is the (only, presumably) place where all the external module references are available. */ if (sf.resolvedModules) { sf.resolvedModules.forEach(this.walkModeAwareResolvedFileCache); } /** * Files referenced from the current file via /// <reference path="...." /> are available in * `referencedFiles` property. Unlike the previous `resolvedModules`, this is a public API. * See: Microsoft/Typescript/src/compiler/programs.ts `ts.createProgram > processReferencedFiles` * These are always initialized, so no if check is needed: see ts.Parser.parseSourceFile */ for (let ref of sf.referencedFiles) { // Unlike the above API, this is not a resolved path, so we have to call TS API // to resolve it first. See the function body of `processReferencedFiles`. const resolvedReferencedFileName = ts.resolveTripleslashReference(ref === null || ref === void 0 ? void 0 : ref.fileName, fileName); this.walk(resolvedReferencedFileName); } /** * Files referenced from the current file via /// <reference type="..." /> are available in * `resolvedTypeReferenceDirectiveNames` internal API. This is also available in `typeReferencedFile`, * but it does not contain information about the file path a type reference is resolved to. * See: Microsoft/Typescript/src/compiler/programs.ts `ts.createProgram > processTypeReferenceDirectives` * see how this function calls `setResolvedTypeReferenceDirective` to mutate `sf.resolvedTypeRefernceDirectiveNames`. */ if (sf.resolvedTypeReferenceDirectiveNames) { sf.resolvedTypeReferenceDirectiveNames.forEach(this.walkModeAwareResolvedFileCache); } } addRootFile(fileName) { this.walk(fileName); } hasFile(fileName) { return this.visited.has(fileName); } // Currently this is only used in tests. iterateFiles() { return this.visited.values(); } } exports.default = TypescriptDependencyGraph;