From 655cbc03f920d3df0d8bb47cb09a184abb5884eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20V=C3=A1zquez=20P=C3=BAa?= Date: Tue, 4 Jun 2019 12:44:10 +0200 Subject: [PATCH 1/5] Keep track of root file names from tsconfig.json --- src/instances.ts | 71 ++++++++++++++++++++++++++++++----------------- src/interfaces.ts | 4 +++ 2 files changed, 49 insertions(+), 26 deletions(-) diff --git a/src/instances.ts b/src/instances.ts index c745687b3..820e83e7a 100644 --- a/src/instances.ts +++ b/src/instances.ts @@ -199,6 +199,10 @@ function successfulTypeScriptInstance( compilerOptions, appendTsTsxSuffixesIfRequired, loaderOptions, + basePath, + configFile, + configFilePath, + rootFileNames: new Set(), files, otherFiles, program, @@ -211,32 +215,6 @@ function successfulTypeScriptInstance( return { instance: instances[loaderOptions.instance] }; } - // Load initial files (core lib files, any files specified in tsconfig.json) - let normalizedFilePath: string; - try { - const filesToLoad = loaderOptions.onlyCompileBundledFiles - ? configParseResult.fileNames.filter(fileName => - dtsDtsxOrDtsDtsxMapRegex.test(fileName) - ) - : configParseResult.fileNames; - filesToLoad.forEach(filePath => { - normalizedFilePath = path.normalize(filePath); - files.set(normalizedFilePath, { - text: fs.readFileSync(normalizedFilePath, 'utf-8'), - version: 0 - }); - }); - } catch (exc) { - return { - error: makeError( - colors.red( - `A file specified in tsconfig.json could not be found: ${normalizedFilePath!}` - ), - normalizedFilePath! - ) - }; - } - // if allowJs is set then we should accept js(x) files const scriptRegex = configParseResult.options.allowJs === true @@ -248,6 +226,10 @@ function successfulTypeScriptInstance( compilerOptions, appendTsTsxSuffixesIfRequired, loaderOptions, + basePath, + configFile, + configFilePath, + rootFileNames: new Set(), files, otherFiles, languageService: null, @@ -265,6 +247,28 @@ function successfulTypeScriptInstance( ); } + // Load initial files (core lib files, any files specified in tsconfig.json) + updateInstanceRootFileNames(loaderOptions, instance, configParseResult); + let normalizedFilePath: string; + try { + instance.rootFileNames.forEach(filePath => { + normalizedFilePath = path.normalize(filePath); + files.set(normalizedFilePath, { + text: fs.readFileSync(normalizedFilePath, 'utf-8'), + version: 0 + }); + }); + } catch (exc) { + return { + error: makeError( + colors.red( + `A file specified in tsconfig.json could not be found: ${normalizedFilePath!}` + ), + normalizedFilePath! + ) + }; + } + if (loaderOptions.experimentalWatchApi && compiler.createWatchProgram) { log.logInfo('Using watch api'); @@ -317,6 +321,21 @@ function successfulTypeScriptInstance( return { instance }; } +/** Update the set of root filenames in the instance from a parsed tsconfig.json */ +export function updateInstanceRootFileNames( + loaderOptions: LoaderOptions, + instance: TSInstance, + configParseResult: typescript.ParsedCommandLine +) { + const files = loaderOptions.onlyCompileBundledFiles + ? configParseResult.fileNames.filter(fileName => + dtsDtsxOrDtsDtsxMapRegex.test(fileName) + ) + : configParseResult.fileNames; + + instance.rootFileNames = new Set(files); +} + export function getEmitOutput(instance: TSInstance, filePath: string) { const program = ensureProgram(instance); if (program !== undefined) { diff --git a/src/interfaces.ts b/src/interfaces.ts index f5de787fe..f84b3c956 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -55,6 +55,10 @@ export interface TSInstance { /** Used for Vue for the most part */ appendTsTsxSuffixesIfRequired: (filePath: string) => string; loaderOptions: LoaderOptions; + /** + * Root files as specified by tsconfig.json' include/exclude/files + */ + rootFileNames: Set; /** * a cache of all the files */ From 29b100dcfb56e6a7a8f35bbd6fa31da0bfcd4302 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20V=C3=A1zquez=20P=C3=BAa?= Date: Tue, 4 Jun 2019 13:25:35 +0200 Subject: [PATCH 2/5] Update rootFileNames when a new file (not in cache) is found. --- src/config.ts | 7 +---- src/index.ts | 70 +++++++++++++++++++++++++++++++++++++++++++++-- src/interfaces.ts | 9 ++++++ 3 files changed, 77 insertions(+), 9 deletions(-) diff --git a/src/config.ts b/src/config.ts index d0cef0206..7cb78e806 100644 --- a/src/config.ts +++ b/src/config.ts @@ -4,15 +4,10 @@ import * as semver from 'semver'; import * as typescript from 'typescript'; import * as webpack from 'webpack'; -import { LoaderOptions, WebpackError } from './interfaces'; +import { ConfigFile, LoaderOptions, WebpackError } from './interfaces'; import * as logger from './logger'; import { formatErrors } from './utils'; -interface ConfigFile { - config?: any; - error?: typescript.Diagnostic; -} - export function getConfigFile( compiler: typeof typescript, colors: Chalk, diff --git a/src/index.ts b/src/index.ts index 1d5351b5f..e9c5a1e46 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,8 +3,13 @@ import * as path from 'path'; import * as typescript from 'typescript'; import * as webpack from 'webpack'; +import { getConfigParseResult } from './config'; import * as constants from './constants'; -import { getEmitOutput, getTypeScriptInstance } from './instances'; +import { + getEmitOutput, + getTypeScriptInstance, + updateInstanceRootFileNames +} from './instances'; import { LoaderOptions, LoaderOptionsCache, @@ -68,7 +73,16 @@ function successLoader( ) : rawFilePath; - const fileVersion = updateFileInCache(filePath, contents, instance); + const { version: fileVersion, changedFilesList } = updateFileInCache( + filePath, + contents, + instance + ); + + if (changedFilesList) { + reloadRootFileNamesFromConfig(loaderContext, instance); + } + const referencedProject = getAndCacheProjectReference(filePath, instance); if (referencedProject !== undefined) { const [relativeProjectConfigPath, relativeFilePath] = [ @@ -150,6 +164,50 @@ function successLoader( } } +/** + * Update the rootFileNames of the instance, by finding all the files + * that match the specs of the tsconfig. + */ +function reloadRootFileNamesFromConfig( + loaderContext: webpack.loader.LoaderContext, + instance: TSInstance +) { + const { + compiler, + basePath, + configFile, + configFilePath, + colors, + loaderOptions + } = instance; + + const configParseResult = getConfigParseResult( + compiler, + configFile, + basePath, + configFilePath + ); + + if (configParseResult.errors.length > 0 && !loaderOptions.happyPackMode) { + const errors = formatErrors( + configParseResult.errors, + loaderOptions, + colors, + compiler, + { file: configFilePath }, + loaderContext.context + ); + + loaderContext._module.errors.push(...errors); + + throw new Error(colors.red('error while parsing tsconfig.json')); + } + + updateInstanceRootFileNames(loaderOptions, instance, configParseResult); + + return; +} + function makeSourceMapAndFinish( sourceMapText: string | undefined, outputText: string | undefined, @@ -337,6 +395,8 @@ function updateFileInCache( instance: TSInstance ) { let fileWatcherEventKind: typescript.FileWatcherEventKind | undefined; + let changedFilesList = false; + // Update file contents let file = instance.files.get(filePath); if (file === undefined) { @@ -351,6 +411,7 @@ function updateFileInCache( file = { version: 0 }; instance.files.set(filePath, file); } + changedFilesList = true; instance.changedFilesList = true; } @@ -381,7 +442,10 @@ function updateFileInCache( instance.modifiedFiles = new Map(); } instance.modifiedFiles.set(filePath, file); - return file.version; + return { + version: file.version, + changedFilesList + }; } function getEmit( diff --git a/src/interfaces.ts b/src/interfaces.ts index f84b3c956..9d18d754b 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -3,6 +3,11 @@ import * as typescript from 'typescript'; import { Chalk } from 'chalk'; +export interface ConfigFile { + config?: any; + error?: typescript.Diagnostic; +} + export interface ErrorInfo { code: number; severity: Severity; @@ -55,6 +60,10 @@ export interface TSInstance { /** Used for Vue for the most part */ appendTsTsxSuffixesIfRequired: (filePath: string) => string; loaderOptions: LoaderOptions; + + basePath: string; + configFile: ConfigFile; + configFilePath: string | undefined; /** * Root files as specified by tsconfig.json' include/exclude/files */ From faf3c1422097bdde9062c1da1c838b6f8db760af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20V=C3=A1zquez=20P=C3=BAa?= Date: Tue, 4 Jun 2019 13:30:10 +0200 Subject: [PATCH 3/5] Throw an error when a file outside of the project is found --- src/index.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index e9c5a1e46..82b112e9c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -79,8 +79,16 @@ function successLoader( instance ); - if (changedFilesList) { + if (changedFilesList && !instance.rootFileNames.has(filePath)) { reloadRootFileNamesFromConfig(loaderContext, instance); + if ( + !instance.rootFileNames.has(filePath) && + !options.onlyCompileBundledFiles + ) { + throw new Error( + `The file ${filePath} is not part of the project specified in tsconfig.json. Make sure it is covered by the fields 'include', 'exclude' and 'files'.` + ); + } } const referencedProject = getAndCacheProjectReference(filePath, instance); From 2414f1b3e0bd21425c356a77f198b2dcbae9c22d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20V=C3=A1zquez=20P=C3=BAa?= Date: Tue, 4 Jun 2019 15:39:33 +0200 Subject: [PATCH 4/5] Increase instance version when new root files are added Fixes #943 --- src/index.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/index.ts b/src/index.ts index 82b112e9c..5e189d3b5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -423,6 +423,17 @@ function updateFileInCache( instance.changedFilesList = true; } + // instance.version must be increased when a new root file is added. + // + // Note that the file could actually be in the cache if it was found + // as a dependency before. + // + // See https://github.com/TypeStrong/ts-loader/issues/943 + // + if (!instance.rootFileNames.has(filePath)) { + instance.version!++; + } + if (instance.watchHost !== undefined && contents === undefined) { fileWatcherEventKind = instance.compiler.FileWatcherEventKind.Deleted; } From 51b7a22e10653056933441d9c493796e57616e81 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20V=C3=A1zquez=20P=C3=BAa?= Date: Wed, 5 Jun 2019 01:09:39 +0200 Subject: [PATCH 5/5] Fix most comparison tests --- test/comparison-tests/appendSuffixTo/tsconfig.json | 5 ++++- test/comparison-tests/basic/tsconfig.json | 6 ++++-- test/comparison-tests/codeSplitting/tsconfig.json | 7 ++----- test/comparison-tests/conditionalRequire/tsconfig.json | 8 ++------ test/comparison-tests/declarationDeps/tsconfig.json | 4 +--- test/comparison-tests/declarationWatch/tsconfig.json | 6 ++++-- test/comparison-tests/es6codeSplitting/tsconfig.json | 7 ++----- test/comparison-tests/externals/tsconfig.json | 5 +---- .../localTsImplementationOfTypings/tsconfig.json | 5 +++-- .../tsconfig.json | 7 +++++-- test/comparison-tests/npmLink/tsconfig.json | 5 +++-- test/comparison-tests/tsconfigSearch/tsconfig.json | 5 +---- 12 files changed, 32 insertions(+), 38 deletions(-) diff --git a/test/comparison-tests/appendSuffixTo/tsconfig.json b/test/comparison-tests/appendSuffixTo/tsconfig.json index b71270932..1db219bc0 100644 --- a/test/comparison-tests/appendSuffixTo/tsconfig.json +++ b/test/comparison-tests/appendSuffixTo/tsconfig.json @@ -1,4 +1,7 @@ { "compilerOptions": { - } + }, + "files": [ + "index.vue.ts" + ] } diff --git a/test/comparison-tests/basic/tsconfig.json b/test/comparison-tests/basic/tsconfig.json index b4b606f26..d88c63814 100644 --- a/test/comparison-tests/basic/tsconfig.json +++ b/test/comparison-tests/basic/tsconfig.json @@ -3,6 +3,8 @@ }, "files": [ - "lib/externalLib.d.ts" + "lib/externalLib.d.ts", + "submodule/submodule.ts", + "app.ts", ] -} \ No newline at end of file +} diff --git a/test/comparison-tests/codeSplitting/tsconfig.json b/test/comparison-tests/codeSplitting/tsconfig.json index bdab84486..fe91e3e94 100644 --- a/test/comparison-tests/codeSplitting/tsconfig.json +++ b/test/comparison-tests/codeSplitting/tsconfig.json @@ -1,8 +1,5 @@ { "compilerOptions": { - }, - "files": [ - "require.d.ts" - ] -} \ No newline at end of file + } +} diff --git a/test/comparison-tests/conditionalRequire/tsconfig.json b/test/comparison-tests/conditionalRequire/tsconfig.json index 2b790044f..fe91e3e94 100644 --- a/test/comparison-tests/conditionalRequire/tsconfig.json +++ b/test/comparison-tests/conditionalRequire/tsconfig.json @@ -1,9 +1,5 @@ { "compilerOptions": { - }, - "files": [ - "require.d.ts", - "globals.d.ts" - ] -} \ No newline at end of file + } +} diff --git a/test/comparison-tests/declarationDeps/tsconfig.json b/test/comparison-tests/declarationDeps/tsconfig.json index 518e957d5..0db3279e4 100644 --- a/test/comparison-tests/declarationDeps/tsconfig.json +++ b/test/comparison-tests/declarationDeps/tsconfig.json @@ -1,5 +1,3 @@ { - "files": [ - "./references.d.ts" - ] + } diff --git a/test/comparison-tests/declarationWatch/tsconfig.json b/test/comparison-tests/declarationWatch/tsconfig.json index a447fcaa0..55f8b24c4 100644 --- a/test/comparison-tests/declarationWatch/tsconfig.json +++ b/test/comparison-tests/declarationWatch/tsconfig.json @@ -3,6 +3,8 @@ }, "files": [ - "thing.d.ts" + "thing.d.ts", + "app.ts", + "dep.ts" ] -} \ No newline at end of file +} diff --git a/test/comparison-tests/es6codeSplitting/tsconfig.json b/test/comparison-tests/es6codeSplitting/tsconfig.json index 297e4ddc6..310c00f46 100644 --- a/test/comparison-tests/es6codeSplitting/tsconfig.json +++ b/test/comparison-tests/es6codeSplitting/tsconfig.json @@ -2,8 +2,5 @@ "compilerOptions": { "target": "es5", "module": "commonjs" - }, - "files": [ - "require.d.ts" - ] -} \ No newline at end of file + } +} diff --git a/test/comparison-tests/externals/tsconfig.json b/test/comparison-tests/externals/tsconfig.json index 559b5ab8e..2c63c0851 100644 --- a/test/comparison-tests/externals/tsconfig.json +++ b/test/comparison-tests/externals/tsconfig.json @@ -1,5 +1,2 @@ { - "files": [ - "hello.d.ts" - ] -} \ No newline at end of file +} diff --git a/test/comparison-tests/localTsImplementationOfTypings/tsconfig.json b/test/comparison-tests/localTsImplementationOfTypings/tsconfig.json index 8ed5d221a..cfde73078 100644 --- a/test/comparison-tests/localTsImplementationOfTypings/tsconfig.json +++ b/test/comparison-tests/localTsImplementationOfTypings/tsconfig.json @@ -3,6 +3,7 @@ }, "include": [ - "app.ts" + "app.ts", + "fake.ts" ] -} \ No newline at end of file +} diff --git a/test/comparison-tests/nodeModulesMeaningfulErrorWhenImportingTs/tsconfig.json b/test/comparison-tests/nodeModulesMeaningfulErrorWhenImportingTs/tsconfig.json index 94d996e96..0fa41fd51 100644 --- a/test/comparison-tests/nodeModulesMeaningfulErrorWhenImportingTs/tsconfig.json +++ b/test/comparison-tests/nodeModulesMeaningfulErrorWhenImportingTs/tsconfig.json @@ -1,5 +1,8 @@ { "compilerOptions": { - + "include": [ + "node_modules", + "app.ts" + ] } -} \ No newline at end of file +} diff --git a/test/comparison-tests/npmLink/tsconfig.json b/test/comparison-tests/npmLink/tsconfig.json index 95468fb69..8ac2e0152 100644 --- a/test/comparison-tests/npmLink/tsconfig.json +++ b/test/comparison-tests/npmLink/tsconfig.json @@ -3,7 +3,8 @@ "module": "commonjs" }, // so that ts files there is part of this ts project - "include": [ + "include": [ + "app.ts", "../../test/comparison-tests/testLib/*" ] -} \ No newline at end of file +} diff --git a/test/comparison-tests/tsconfigSearch/tsconfig.json b/test/comparison-tests/tsconfigSearch/tsconfig.json index d31f2e66a..2c63c0851 100644 --- a/test/comparison-tests/tsconfigSearch/tsconfig.json +++ b/test/comparison-tests/tsconfigSearch/tsconfig.json @@ -1,5 +1,2 @@ { - "files": [ - "lib/externalLib.d.ts" - ] -} \ No newline at end of file +}