Skip to content

Commit

Permalink
Add all the files from composite project as dependencies as any chang…
Browse files Browse the repository at this point in the history
…e in them can result in errors resulting in changes to the own output of the file
  • Loading branch information
sheetalkamat committed Apr 8, 2020
1 parent 31c2bb4 commit 8f384b6
Show file tree
Hide file tree
Showing 79 changed files with 1,703 additions and 165 deletions.
189 changes: 135 additions & 54 deletions src/index.ts
Expand Up @@ -10,7 +10,8 @@ import {
getInputFileNameFromOutput,
getTypeScriptInstance,
initializeInstance,
isReferencedFile
isReferencedFile,
reportTranspileErrors
} from './instances';
import {
LoaderOptions,
Expand Down Expand Up @@ -45,9 +46,8 @@ function loader(this: webpack.loader.LoaderContext, contents: string) {
callback(new Error(instanceOrError.error.message));
return;
}

const instance = instanceOrError.instance!;
buildSolutionReferences(instance, this, instance.log);
buildSolutionReferences(instance, this);
successLoader(this, contents, callback, instance);
}

Expand All @@ -58,6 +58,7 @@ function successLoader(
instance: TSInstance
) {
initializeInstance(loaderContext, instance);
reportTranspileErrors(instance, loaderContext);
const rawFilePath = path.normalize(loaderContext.resourcePath);

const filePath =
Expand Down Expand Up @@ -470,65 +471,70 @@ function getEmit(
loaderContext.clearDependencies();
loaderContext.addDependency(rawFilePath);

const allDefinitionFiles = isReferencedFile(instance, filePath)
? []
: [...instance.files.keys()].filter(
defFilePath =>
defFilePath.match(constants.dtsDtsxOrDtsDtsxMapRegex) &&
// Remove the project reference d.ts as we are adding dependency for .ts later
// This removed extra build pass (resulting in new stats object in initial build)
(!instance.solutionBuilderHost ||
instance.solutionBuilderHost.getOutputFileFromReferencedProject(
defFilePath
) !== undefined)
);
const dependencies: string[] = [];
const addDependency = (file: string) => {
file = path.resolve(file);
loaderContext.addDependency(file);
dependencies.push(file);
};

// Make this file dependent on *all* definition files in the program
const addDependency = loaderContext.addDependency.bind(loaderContext);
allDefinitionFiles.forEach(addDependency);
if (!isReferencedFile(instance, filePath)) {
for (const defFilePath of instance.files.keys()) {
if (
defFilePath.match(constants.dtsDtsxOrDtsDtsxMapRegex) &&
// Remove the project reference d.ts as we are adding dependency for .ts later
// This removed extra build pass (resulting in new stats object in initial build)
(!instance.solutionBuilderHost ||
instance.solutionBuilderHost.getOutputFileFromReferencedProject(
defFilePath
) !== undefined)
) {
addDependency(defFilePath);
}
}
}

// Additionally make this file dependent on all imported files
const fileDependencies = instance.dependencyGraph[filePath];
const additionalDependencies =
fileDependencies === undefined
? []
: fileDependencies.map(({ resolvedFileName, originalFileName }) => {
const projectReference = getAndCacheProjectReference(
if (fileDependencies) {
for (const { resolvedFileName, originalFileName } of fileDependencies) {
const projectReference = getAndCacheProjectReference(
resolvedFileName,
instance
);
// In the case of dependencies that are part of a project reference,
// the real dependency that webpack should watch is the JS output file.
if (projectReference !== undefined) {
addDependency(
getAndCacheOutputJSFileName(
resolvedFileName,
projectReference,
instance
);
// In the case of dependencies that are part of a project reference,
// the real dependency that webpack should watch is the JS output file.
if (projectReference !== undefined) {
return getAndCacheOutputJSFileName(
resolvedFileName,
projectReference,
instance
);
}
return (
getInputFileNameFromOutput(
instance,
path.resolve(resolvedFileName)
) || originalFileName
);
});

if (additionalDependencies.length > 0) {
additionalDependencies.forEach(addDependency);
)
);
} else {
addDependency(
getInputFileNameFromOutput(
instance,
path.resolve(resolvedFileName)
) || originalFileName
);
}
}
}

loaderContext._module.buildMeta.tsLoaderDefinitionFileVersions = allDefinitionFiles
.concat(additionalDependencies)
.map(
defFilePath =>
defFilePath +
'@' +
(
instance.files.get(defFilePath) ||
instance.otherFiles.get(defFilePath) || { version: '?' }
).version
);
addDependenciesFromSolutionBuilder(instance, filePath, addDependency);

loaderContext._module.buildMeta.tsLoaderDefinitionFileVersions = dependencies.map(
defFilePath =>
defFilePath +
'@' +
(
instance.files.get(defFilePath) ||
instance.otherFiles.get(defFilePath) || { version: '?' }
).version
);

const outputFile = outputFiles
.filter(file => file.name.match(constants.jsJsx))
Expand All @@ -540,10 +546,81 @@ function getEmit(
.pop();
const sourceMapText =
sourceMapFile === undefined ? undefined : sourceMapFile.text;

return { outputText, sourceMapText };
}

function addDependenciesFromSolutionBuilder(
instance: TSInstance,
filePath: string,
addDependency: (file: string) => void
) {
if (!instance.solutionBuilderHost) {
return;
}
// Add all the input files from the references as
const resolvedFilePath = path.resolve(filePath);
for (const [
configFile,
configInfo
] of instance.solutionBuilderHost.configFileInfo.entries()) {
if (
!configInfo.config ||
!configInfo.config.projectReferences ||
!configInfo.config.projectReferences.length
) {
continue;
}
if (configInfo.outputFileNames) {
if (!configInfo.outputFileNames.has(resolvedFilePath)) {
continue;
}
} else if (
!configInfo.config.fileNames.some(
f => path.resolve(f) === resolvedFilePath
)
) {
continue;
}

// This is the config for the input file
const seenMap = new Map<string, true>();
seenMap.set(configFile, true);

// Depend on all the dts files from the program
if (configInfo.dtsFiles) {
configInfo.dtsFiles.forEach(addDependency);
}

// Add dependencies to all the input files from the project reference files since building them
const queue = configInfo.config.projectReferences.slice();
while (true) {
const currentRef = queue.pop();
if (!currentRef) {
break;
}
const refConfigFile = path.resolve(
instance.compiler.resolveProjectReferencePath(currentRef)
);
if (seenMap.has(refConfigFile)) {
continue;
}
const refConfigInfo = instance.solutionBuilderHost.configFileInfo.get(
refConfigFile
);
if (!refConfigInfo) {
continue;
}
seenMap.set(refConfigFile, true);
if (refConfigInfo.config) {
refConfigInfo.config.fileNames.forEach(addDependency);
if (refConfigInfo.config.projectReferences) {
queue.push(...refConfigInfo.config.projectReferences);
}
}
}
}
}

/**
* Transpile file
*/
Expand All @@ -564,6 +641,10 @@ function getTranspilationEmit(
fileName
});

addDependenciesFromSolutionBuilder(instance, fileName, file =>
loaderContext.addDependency(path.resolve(file))
);

// _module.errors is not available inside happypack - see https://github.com/TypeStrong/ts-loader/issues/336
if (
!instance.loaderOptions.happyPackMode &&
Expand Down
87 changes: 64 additions & 23 deletions src/instances.ts
Expand Up @@ -168,6 +168,7 @@ function successfulTypeScriptInstance(
transformers: {} as typescript.CustomTransformers, // this is only set temporarily, custom transformers are created further down
colors,
initialSetupPending: true,
reportTranspileErrors: true,
configFilePath,
configParseResult,
log
Expand Down Expand Up @@ -273,24 +274,14 @@ export function initializeInstance(
})
: instance.compiler.createProgram([], instance.compilerOptions));

// happypack does not have _module.errors - see https://github.com/TypeStrong/ts-loader/issues/336
if (!instance.loaderOptions.happyPackMode) {
const solutionErrors: WebpackError[] = getSolutionErrors(
instance,
loader.context
);
const diagnostics = program.getOptionsDiagnostics();
const errors = formatErrors(
diagnostics,
instance.loaderOptions,
instance.colors,
instance.compiler,
{ file: instance.configFilePath || 'tsconfig.json' },
loader.context
instance.transformers = getCustomTransformers(program);
// Setup watch run for solution building
if (instance.solutionBuilderHost) {
loader._compiler.hooks.watchRun.tapAsync(
'ts-loader',
makeWatchRun(instance)
);
loader._module.errors.push(...solutionErrors, ...errors);
}
instance.transformers = getCustomTransformers(program);
} else {
if (!loader._compiler.hooks) {
throw new Error(
Expand All @@ -307,7 +298,6 @@ export function initializeInstance(
// If there is api available for watch, use it instead of language service
instance.watchHost = makeWatchHost(
getScriptRegexp(instance),
instance.log,
loader,
instance,
instance.configParseResult.projectReferences
Expand All @@ -322,7 +312,6 @@ export function initializeInstance(
} else {
instance.servicesHost = makeServicesHost(
getScriptRegexp(instance),
instance.log,
loader,
instance,
instance.loaderOptions.experimentalFileCaching,
Expand Down Expand Up @@ -364,21 +353,46 @@ function getScriptRegexp(instance: TSInstance) {
: /\.tsx?$/i;
}

export function reportTranspileErrors(
instance: TSInstance,
loader: webpack.loader.LoaderContext
) {
if (!instance.reportTranspileErrors) {
return;
}
instance.reportTranspileErrors = false;
// happypack does not have _module.errors - see https://github.com/TypeStrong/ts-loader/issues/336
if (!instance.loaderOptions.happyPackMode) {
const solutionErrors: WebpackError[] = getSolutionErrors(
instance,
loader.context
);
const diagnostics = instance.program!.getOptionsDiagnostics();
const errors = formatErrors(
diagnostics,
instance.loaderOptions,
instance.colors,
instance.compiler,
{ file: instance.configFilePath || 'tsconfig.json' },
loader.context
);
loader._module.errors.push(...solutionErrors, ...errors);
}
}

export function buildSolutionReferences(
instance: TSInstance,
loader: webpack.loader.LoaderContext,
log: logger.Logger
loader: webpack.loader.LoaderContext
) {
if (!supportsSolutionBuild(instance)) {
return;
}
if (!instance.solutionBuilderHost) {
// Use solution builder
log.logInfo('Using SolutionBuilder api');
instance.log.logInfo('Using SolutionBuilder api');
const scriptRegex = getScriptRegexp(instance);
instance.solutionBuilderHost = makeSolutionBuilderHost(
scriptRegex,
log,
loader,
instance
);
Expand All @@ -387,8 +401,35 @@ export function buildSolutionReferences(
[instance.configFilePath!],
{ verbose: true }
);
instance.solutionBuilder!.buildReferences(instance.configFilePath!);
ensureAllReferences(instance);
} else {
instance.solutionBuilderHost.buildReferences();
}
}

function ensureAllReferences(instance: TSInstance) {
// Return result from the json without errors so that the extra errors from config are digested here
const rootConfigInfo = instance.solutionBuilderHost!.configFileInfo.get(
instance.configFilePath!
);
for (const configInfo of instance.solutionBuilderHost!.configFileInfo.values()) {
if (configInfo === rootConfigInfo || !configInfo.config) {
continue;
}
// Load all the input files
configInfo.config.fileNames.forEach(file => {
const resolvedFileName = path.resolve(file);
const existing = instance.otherFiles.get(resolvedFileName);
if (!existing) {
instance.otherFiles.set(resolvedFileName, {
version: 1,
text: instance.compiler.sys.readFile(file),
modifiedTime: instance.compiler.sys.getModifiedTime!(file)
});
}
});
}
instance.solutionBuilder!.buildReferences(instance.configFilePath!);
}

export function forEachResolvedProjectReference<T>(
Expand Down

0 comments on commit 8f384b6

Please sign in to comment.