diff --git a/apps/heft/src/plugins/SassTypingsPlugin/SassTypingsGenerator.ts b/apps/heft/src/plugins/SassTypingsPlugin/SassTypingsGenerator.ts index 3ba513ca9e2..8ba7510cd8c 100644 --- a/apps/heft/src/plugins/SassTypingsPlugin/SassTypingsGenerator.ts +++ b/apps/heft/src/plugins/SassTypingsPlugin/SassTypingsGenerator.ts @@ -143,6 +143,12 @@ export class SassTypingsGenerator extends StringValuesTypingsGenerator { indentedSyntax: path.extname(filePath).toLowerCase() === '.sass' }); + // Register any @import files as dependencies. + const target: string = result.stats.entry; + for (const dependency of result.stats.includedFiles) { + this.registerDependency(target, dependency); + } + return result.css.toString(); } diff --git a/common/changes/@rushstack/heft/halfnibble-typings-dep-maps_2020-11-11-06-09.json b/common/changes/@rushstack/heft/halfnibble-typings-dep-maps_2020-11-11-06-09.json new file mode 100644 index 00000000000..2d040b18af3 --- /dev/null +++ b/common/changes/@rushstack/heft/halfnibble-typings-dep-maps_2020-11-11-06-09.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "@rushstack/heft", + "comment": "Update Sass typings generation to update in watch mode when a dependency changes.", + "type": "patch" + } + ], + "packageName": "@rushstack/heft", + "email": "halfnibble@users.noreply.github.com" +} \ No newline at end of file diff --git a/common/changes/@rushstack/typings-generator/halfnibble-typings-dep-maps_2020-11-11-06-09.json b/common/changes/@rushstack/typings-generator/halfnibble-typings-dep-maps_2020-11-11-06-09.json new file mode 100644 index 00000000000..81acd92494e --- /dev/null +++ b/common/changes/@rushstack/typings-generator/halfnibble-typings-dep-maps_2020-11-11-06-09.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "@rushstack/typings-generator", + "comment": "Add register dependency feature for typings generation. ", + "type": "patch" + } + ], + "packageName": "@rushstack/typings-generator", + "email": "halfnibble@users.noreply.github.com" +} \ No newline at end of file diff --git a/common/reviews/api/typings-generator.api.md b/common/reviews/api/typings-generator.api.md index c0383146a83..67feec96ead 100644 --- a/common/reviews/api/typings-generator.api.md +++ b/common/reviews/api/typings-generator.api.md @@ -54,6 +54,7 @@ export class TypingsGenerator { generateTypingsAsync(): Promise; // (undocumented) protected _options: ITypingsGeneratorOptions; + registerDependency(target: string, dependency: string): void; // (undocumented) runWatcherAsync(): Promise; } diff --git a/libraries/typings-generator/src/TypingsGenerator.ts b/libraries/typings-generator/src/TypingsGenerator.ts index 988ab88d586..45f2a2702e2 100644 --- a/libraries/typings-generator/src/TypingsGenerator.ts +++ b/libraries/typings-generator/src/TypingsGenerator.ts @@ -34,6 +34,12 @@ export interface ITypingsGeneratorOptions { * @public */ export class TypingsGenerator { + // Map of target file path -> Set + private _targetMap: Map>; + + // Map of dependency file path -> Set + private _dependencyMap: Map>; + protected _options: ITypingsGeneratorOptions; public constructor(options: ITypingsGeneratorOptions) { @@ -70,6 +76,10 @@ export class TypingsGenerator { } this._options.fileExtensions = this._normalizeFileExtensions(this._options.fileExtensions); + + this._targetMap = new Map(); + + this._dependencyMap = new Map(); } public async generateTypingsAsync(): Promise { @@ -121,7 +131,37 @@ export class TypingsGenerator { }); } + /** + * Register file dependencies that may effect the typings of a target file. + * Note: This feature is only useful in watch mode. + * The registerDependency method must be called in the body of parseAndGenerateTypings every + * time because the registry for a file is cleared at the beginning of processing. + */ + public registerDependency(target: string, dependency: string): void { + let targetDependencySet: Set | undefined = this._targetMap.get(target); + if (!targetDependencySet) { + targetDependencySet = new Set(); + this._targetMap.set(target, targetDependencySet); + } + targetDependencySet.add(dependency); + + let dependencyTargetSet: Set | undefined = this._dependencyMap.get(dependency); + if (!dependencyTargetSet) { + dependencyTargetSet = new Set(); + this._dependencyMap.set(dependency, dependencyTargetSet); + } + dependencyTargetSet.add(target); + } + private async _parseFileAndGenerateTypingsAsync(locFilePath: string): Promise { + // Clear registered dependencies prior to reprocessing. + this._clearDependencies(locFilePath); + + // Check for targets that register this file as a dependency, and reprocess them too. + for (const target of this._getDependencyTargets(locFilePath)) { + await this._parseFileAndGenerateTypingsAsync(target); + } + try { const fileContents: string = await FileSystem.readFileAsync(locFilePath); const typingsData: string | undefined = await this._options.parseAndGenerateTypings( @@ -152,6 +192,20 @@ export class TypingsGenerator { } } + private _clearDependencies(target: string): void { + const targetDependencySet: Set | undefined = this._targetMap.get(target); + if (targetDependencySet) { + for (const dependency of targetDependencySet) { + this._dependencyMap.get(dependency)!.delete(target); + } + targetDependencySet.clear(); + } + } + + private _getDependencyTargets(dependency: string): string[] { + return [...(this._dependencyMap.get(dependency)?.keys() || [])]; + } + private _getTypingsFilePath(locFilePath: string): string { return path.resolve( this._options.generatedTsFolder,