Skip to content

Commit

Permalink
Make sure to build all solution builder files before and track input …
Browse files Browse the repository at this point in the history
…and output
  • Loading branch information
sheetalkamat committed Apr 3, 2020
1 parent a412371 commit 311f421
Show file tree
Hide file tree
Showing 32 changed files with 714 additions and 470 deletions.
58 changes: 35 additions & 23 deletions src/after-compile.ts
Expand Up @@ -3,7 +3,11 @@ import * as ts from 'typescript';
import * as webpack from 'webpack';

import * as constants from './constants';
import { getEmitFromWatchHost, getEmitOutput } from './instances';
import {
getEmitFromWatchHost,
getEmitOutput,
isReferencedFile
} from './instances';
import {
TSFile,
TSFiles,
Expand Down Expand Up @@ -339,25 +343,24 @@ function provideDeclarationFilesToWebpack(
continue;
}

addDeclarationFilesAsAsset(
getEmitOutput(
instance,
filePath,
/*skipActualOutputReadOfReferencedFile*/ true
),
compilation
);
if (!isReferencedFile(instance, filePath)) {
addDeclarationFilesAsAsset(
getEmitOutput(instance, filePath),
compilation
);
}
}
}

function addDeclarationFilesAsAsset(
outputFiles: ts.OutputFile[] | IterableIterator<ts.OutputFile>,
compilation: webpack.compilation.Compilation
function addDeclarationFilesAsAsset<T extends ts.OutputFile>(
outputFiles: T[] | IterableIterator<T>,
compilation: webpack.compilation.Compilation,
skipOutputFile?: (outputFile: T) => boolean
) {
outputFilesToAsset(
outputFiles,
compilation,
outputFile => !outputFile.name.match(constants.dtsDtsxOrDtsDtsxMapRegex)
outputFilesToAsset(outputFiles, compilation, outputFile =>
skipOutputFile && skipOutputFile(outputFile)
? true
: !outputFile.name.match(constants.dtsDtsxOrDtsDtsxMapRegex)
);
}

Expand All @@ -375,10 +378,10 @@ function outputFileToAsset(
};
}

function outputFilesToAsset(
outputFiles: ts.OutputFile[] | IterableIterator<ts.OutputFile>,
function outputFilesToAsset<T extends ts.OutputFile>(
outputFiles: T[] | IterableIterator<T>,
compilation: webpack.compilation.Compilation,
skipOutputFile?: (outputFile: ts.OutputFile) => boolean
skipOutputFile?: (outputFile: T) => boolean
) {
for (const outputFile of outputFiles) {
if (!skipOutputFile || !skipOutputFile(outputFile)) {
Expand Down Expand Up @@ -417,12 +420,21 @@ function provideAssetsFromSolutionBuilderHost(
// written files
addDeclarationFilesAsAsset(
instance.solutionBuilderHost.outputFiles.values(),
compilation
compilation,
outputFile => !outputFile.isNew
);
// tsbuild infos
outputFilesToAsset(instance.solutionBuilderHost.tsbuildinfos, compilation);
instance.solutionBuilderHost.outputFiles.clear();
instance.solutionBuilderHost.tsbuildinfos.length = 0;
outputFilesToAsset(
instance.solutionBuilderHost.tsbuildinfos.values(),
compilation,
outputFile => !outputFile.isNew
);
instance.solutionBuilderHost.outputFiles.forEach(
outputFile => (outputFile.isNew = false)
);
instance.solutionBuilderHost.tsbuildinfos.forEach(
outputFile => (outputFile.isNew = false)
);
}
}

Expand Down
184 changes: 100 additions & 84 deletions src/index.ts
Expand Up @@ -5,9 +5,11 @@ import * as webpack from 'webpack';

import * as constants from './constants';
import {
buildSolutionReferences,
getEmitOutput,
getInputFileNameFromOutput,
getTypeScriptInstance,
initializeInstance,
isReferencedFile
} from './instances';
import {
Expand Down Expand Up @@ -44,36 +46,38 @@ function loader(this: webpack.loader.LoaderContext, contents: string) {
return;
}

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

function successLoader(
loaderContext: webpack.loader.LoaderContext,
contents: string,
callback: webpack.loader.loaderCallback,
options: LoaderOptions,
instance: TSInstance
) {
initializeInstance(loaderContext, instance);
const rawFilePath = path.normalize(loaderContext.resourcePath);

const filePath =
options.appendTsSuffixTo.length > 0 || options.appendTsxSuffixTo.length > 0
instance.loaderOptions.appendTsSuffixTo.length > 0 ||
instance.loaderOptions.appendTsxSuffixTo.length > 0
? appendSuffixesIfMatch(
{
'.ts': options.appendTsSuffixTo,
'.tsx': options.appendTsxSuffixTo
'.ts': instance.loaderOptions.appendTsSuffixTo,
'.tsx': instance.loaderOptions.appendTsxSuffixTo
},
rawFilePath
)
: rawFilePath;

const fileVersion = updateFileInCache(options, filePath, contents, instance);
const fileVersion = updateFileInCache(
instance.loaderOptions,
filePath,
contents,
instance
);
const referencedProject = getAndCacheProjectReference(filePath, instance);
if (referencedProject !== undefined) {
const [relativeProjectConfigPath, relativeFilePath] = [
Expand Down Expand Up @@ -133,13 +137,12 @@ function successLoader(
filePath,
contents,
loaderContext,
options,
fileVersion,
callback,
instance
);
} else {
const { outputText, sourceMapText } = options.transpileOnly
const { outputText, sourceMapText } = instance.loaderOptions.transpileOnly
? getTranspilationEmit(filePath, contents, instance, loaderContext)
: getEmit(rawFilePath, filePath, instance, loaderContext);

Expand All @@ -149,7 +152,6 @@ function successLoader(
filePath,
contents,
loaderContext,
options,
fileVersion,
callback,
instance
Expand All @@ -163,23 +165,29 @@ function makeSourceMapAndFinish(
filePath: string,
contents: string,
loaderContext: webpack.loader.LoaderContext,
options: LoaderOptions,
fileVersion: number,
callback: webpack.loader.loaderCallback,
instance: TSInstance
) {
if (outputText === null || outputText === undefined) {
setModuleMeta(loaderContext, instance, fileVersion);
const additionalGuidance = isReferencedFile(instance, filePath)
? ' The most common cause for this is having errors when building referenced projects.'
: !options.allowTsInNodeModules && filePath.indexOf('node_modules') !== -1
: !instance.loaderOptions.allowTsInNodeModules &&
filePath.indexOf('node_modules') !== -1
? ' By default, ts-loader will not compile .ts files in node_modules.\n' +
'You should not need to recompile .ts files there, but if you really want to, use the allowTsInNodeModules option.\n' +
'See: https://github.com/Microsoft/TypeScript/issues/12358'
: '';

throw new Error(
`TypeScript emitted no output for ${filePath}.${additionalGuidance}`
callback(
new Error(
`TypeScript emitted no output for ${filePath}.${additionalGuidance}`
),
outputText,
undefined
);
return;
}

const { sourceMap, output } = makeSourceMap(
Expand All @@ -190,15 +198,25 @@ function makeSourceMapAndFinish(
loaderContext
);

setModuleMeta(loaderContext, instance, fileVersion);
callback(null, output, sourceMap);
}

function setModuleMeta(
loaderContext: webpack.loader.LoaderContext,
instance: TSInstance,
fileVersion: number
) {
// _module.meta is not available inside happypack
if (!options.happyPackMode && loaderContext._module.buildMeta !== undefined) {
if (
!instance.loaderOptions.happyPackMode &&
loaderContext._module.buildMeta !== undefined
) {
// Make sure webpack is aware that even though the emitted JavaScript may be the same as
// a previously cached version the TypeScript may be different and therefore should be
// treated as new
loaderContext._module.buildMeta.tsLoaderFileVersion = fileVersion;
}

callback(null, output, sourceMap);
}

/**
Expand Down Expand Up @@ -395,14 +413,15 @@ function updateFileInCache(
// it is allowed by the options.
(options.allowTsInNodeModules || filePath.indexOf('node_modules') === -1)
) {
instance.version!++;
instance.version++;
instance.rootFileNames.add(filePath);
}

if (file.text !== contents) {
file.version++;
file.text = contents;
instance.version!++;
file.modifiedTime = new Date();
instance.version++;
if (
(instance.watchHost !== undefined ||
instance.solutionBuilderHost !== undefined) &&
Expand Down Expand Up @@ -447,73 +466,70 @@ function getEmit(
instance: TSInstance,
loaderContext: webpack.loader.LoaderContext
) {
const outputFiles = getEmitOutput(
instance,
filePath,
/*skipActualOutputReadOfReferencedFile*/ false
);

if (!isReferencedFile(instance, filePath)) {
loaderContext.clearDependencies();
loaderContext.addDependency(rawFilePath);
const outputFiles = getEmitOutput(instance, filePath);
loaderContext.clearDependencies();
loaderContext.addDependency(rawFilePath);

const allDefinitionFiles = [...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 ||
!getInputFileNameFromOutput(instance, defFilePath))
);
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)
);

// Make this file dependent on *all* definition files in the program
const addDependency = loaderContext.addDependency.bind(loaderContext);
allDefinitionFiles.forEach(addDependency);

// 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(
// Make this file dependent on *all* definition files in the program
const addDependency = loaderContext.addDependency.bind(loaderContext);
allDefinitionFiles.forEach(addDependency);

// 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(
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) {
return 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);
}

loaderContext._module.buildMeta.tsLoaderDefinitionFileVersions = allDefinitionFiles
.concat(additionalDependencies)
.map(
defFilePath =>
defFilePath +
'@' +
(
instance.files.get(defFilePath) ||
instance.otherFiles.get(defFilePath) || { version: '?' }
).version
);
}
return (
getInputFileNameFromOutput(
instance,
path.resolve(resolvedFileName)
) || originalFileName
);
});

if (additionalDependencies.length > 0) {
additionalDependencies.forEach(addDependency);
}

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

const outputFile = outputFiles
.filter(file => file.name.match(constants.jsJsx))
.pop();
Expand Down

0 comments on commit 311f421

Please sign in to comment.