Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Shares solution builder between instances #1176

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
30 changes: 21 additions & 9 deletions src/after-compile.ts
Expand Up @@ -6,6 +6,7 @@ import * as constants from './constants';
import { getEmitFromWatchHost, getEmitOutput } from './instances';
import {
FilePathKey,
LoaderOptions,
TSFiles,
TSInstance,
WebpackModule,
Expand All @@ -17,6 +18,7 @@ import {
formatErrors,
isReferencedFile,
populateReverseDependencyGraph,
tsLoaderSource,
} from './utils';

export function makeAfterCompile(
Expand All @@ -41,7 +43,7 @@ export function makeAfterCompile(
callback();
return;
}
removeCompilationTSLoaderErrors(compilation);
removeCompilationTSLoaderErrors(compilation, instance.loaderOptions);

provideCompilerOptionDiagnosticErrorsToWebpack(
getCompilerOptionDiagnostics,
Expand Down Expand Up @@ -235,7 +237,7 @@ function provideErrorsToWebpack(
const associatedModules = modules.get(instance.filePathKeyMapper(fileName));
if (associatedModules !== undefined) {
associatedModules.forEach(module => {
removeModuleTSLoaderError(module);
removeModuleTSLoaderError(module, loaderOptions);

// append errors
const formattedErrors = formatErrors(
Expand Down Expand Up @@ -280,6 +282,7 @@ function provideSolutionErrorsToWebpack(
) {
if (
!instance.solutionBuilderHost ||
instance.solutionBuilderHost.defaultInstance !== instance ||
!(
instance.solutionBuilderHost.diagnostics.global.length ||
instance.solutionBuilderHost.diagnostics.perFile.size
Expand All @@ -299,7 +302,7 @@ function provideSolutionErrorsToWebpack(
const associatedModules = modules.get(filePath);
if (associatedModules !== undefined) {
associatedModules.forEach(module => {
removeModuleTSLoaderError(module);
removeModuleTSLoaderError(module, loaderOptions);

// append errors
const formattedErrors = formatErrors(
Expand Down Expand Up @@ -430,7 +433,10 @@ function provideAssetsFromSolutionBuilderHost(
instance: TSInstance,
compilation: webpack.compilation.Compilation
) {
if (instance.solutionBuilderHost) {
if (
instance.solutionBuilderHost &&
instance.solutionBuilderHost.defaultInstance === instance
) {
// written files
outputFilesToAsset(instance.solutionBuilderHost.writtenFiles, compilation);
instance.solutionBuilderHost.writtenFiles.length = 0;
Expand All @@ -445,14 +451,18 @@ function provideAssetsFromSolutionBuilderHost(
* the loader, we need to detect and remove any pre-existing errors.
*/
function removeCompilationTSLoaderErrors(
compilation: webpack.compilation.Compilation
compilation: webpack.compilation.Compilation,
loaderOptions: LoaderOptions
) {
compilation.errors = compilation.errors.filter(
error => error.loaderSource !== 'ts-loader'
error => error.loaderSource !== tsLoaderSource(loaderOptions)
);
}

function removeModuleTSLoaderError(module: WebpackModule) {
function removeModuleTSLoaderError(
module: WebpackModule,
loaderOptions: LoaderOptions
) {
/**
* Since webpack 5, the `errors` property is deprecated,
* so we can check if some methods for reporting errors exist.
Expand All @@ -464,11 +474,13 @@ function removeModuleTSLoaderError(module: WebpackModule) {

Array.from(warnings || []).forEach(warning => module.addWarning(warning));
Array.from(errors || [])
.filter((error: any) => error.loaderSource !== 'ts-loader')
.filter(
(error: any) => error.loaderSource !== tsLoaderSource(loaderOptions)
)
.forEach(error => module.addError(error));
} else {
module.errors = module.errors.filter(
error => error.loaderSource !== 'ts-loader'
error => error.loaderSource !== tsLoaderSource(loaderOptions)
);
}
}
86 changes: 66 additions & 20 deletions src/instances.ts
Expand Up @@ -35,6 +35,10 @@ import {
import { makeWatchRun } from './watch-run';

const instances = {} as TSInstances;
const solutionBuilderHosts = new Map<
FilePathKey,
NonNullable<TSInstance['solutionBuilderHost']>
>();

/**
* The loader is executed once for each file seen by webpack. However, we need to keep
Expand All @@ -60,7 +64,13 @@ export function getTypeScriptInstance(
const compiler = getCompiler(loaderOptions, log);

if (compiler.errorMessage !== undefined) {
return { error: makeError(colors.red(compiler.errorMessage), undefined) };
return {
error: makeError(
loaderOptions,
colors.red(compiler.errorMessage),
undefined
),
};
}

return successfulTypeScriptInstance(
Expand Down Expand Up @@ -121,6 +131,7 @@ function successfulTypeScriptInstance(
const { message, file } = configFileAndPath.configFileError;
return {
error: makeError(
loaderOptions,
colors.red('error while reading tsconfig.json:' + EOL + message),
file
),
Expand Down Expand Up @@ -151,6 +162,7 @@ function successfulTypeScriptInstance(

return {
error: makeError(
loaderOptions,
colors.red('error while parsing tsconfig.json'),
configFilePath
),
Expand Down Expand Up @@ -223,6 +235,7 @@ function successfulTypeScriptInstance(
} catch (exc) {
return {
error: makeError(
loaderOptions,
colors.red(
`A file specified in tsconfig.json could not be found: ${normalizedFilePath!}`
),
Expand Down Expand Up @@ -415,25 +428,53 @@ export function buildSolutionReferences(
if (!instance.solutionBuilderHost) {
// Use solution builder
instance.log.logInfo('Using SolutionBuilder api');
const scriptRegex = getScriptRegexp(instance);
instance.solutionBuilderHost = makeSolutionBuilderHost(
scriptRegex,
loader,
instance
);
instance.solutionBuilder = instance.compiler.createSolutionBuilderWithWatch(
instance.solutionBuilderHost,
instance.configParseResult.projectReferences!.map(ref => ref.path),
{ verbose: true }
);
instance.solutionBuilder!.build();
ensureAllReferences(instance);
const key = instance.filePathKeyMapper(instance.configFilePath!);
const existing = getExistingSolutionBuilderHost(key);
if (existing) {
instance.log.logInfo(
`Reusing existing Solution builder:: ${existing.defaultInstance.configFilePath}`
);
instance.solutionBuilderHost = existing;
instance.solutionBuilderHost.instances.set(
instance.loaderOptions.instance,
instance
);
ensureAllReferences(instance);
instance.solutionBuilderHost.buildReferences();
} else if (instance.configParseResult.projectReferences?.length) {
const scriptRegex = getScriptRegexp(instance);
instance.solutionBuilderHost = makeSolutionBuilderHost(
scriptRegex,
loader,
instance
);
const solutionBuilder = instance.compiler.createSolutionBuilderWithWatch(
instance.solutionBuilderHost,
instance.configParseResult.projectReferences!.map(ref => ref.path),
{ verbose: true }
);
solutionBuilder.build();
ensureAllReferences(instance);
solutionBuilderHosts.set(key, instance.solutionBuilderHost);
}
} else {
instance.solutionBuilderHost.buildReferences();
}
}

function getExistingSolutionBuilderHost(key: FilePathKey) {
const existing = solutionBuilderHosts.get(key);
if (existing) return existing;
for (const solutionBuilderHost of solutionBuilderHosts.values()) {
if (solutionBuilderHost.configFileInfo.has(key)) {
return solutionBuilderHost;
}
}
return undefined;
}

function ensureAllReferences(instance: TSInstance) {
const defaultInstance = instance.solutionBuilderHost!.defaultInstance;
// Return result from the json without errors so that the extra errors from config are digested here
for (const configInfo of instance.solutionBuilderHost!.configFileInfo.values()) {
if (!configInfo.config) {
Expand All @@ -444,12 +485,17 @@ function ensureAllReferences(instance: TSInstance) {
const resolvedFileName = instance.filePathKeyMapper(file);
const existing = instance.otherFiles.get(resolvedFileName);
if (!existing) {
instance.otherFiles.set(resolvedFileName, {
fileName: path.resolve(file),
version: 1,
text: instance.compiler.sys.readFile(file),
modifiedTime: instance.compiler.sys.getModifiedTime!(file),
});
instance.otherFiles.set(
resolvedFileName,
defaultInstance === instance
? {
fileName: path.resolve(file),
version: 1,
text: instance.compiler.sys.readFile(file),
modifiedTime: instance.compiler.sys.getModifiedTime!(file),
}
: defaultInstance.otherFiles.get(resolvedFileName)!
);
}
});
}
Expand Down
7 changes: 3 additions & 4 deletions src/interfaces.ts
Expand Up @@ -129,10 +129,12 @@ export interface SolutionBuilderWithWatchHost
typescript.EmitAndSemanticDiagnosticsBuilderProgram
>,
WatchFactory {
defaultInstance: TSInstance;
instances: Map<string, TSInstance>;
diagnostics: SolutionDiagnostics;
writtenFiles: OutputFile[];
configFileInfo: Map<FilePathKey, ConfigFileInfo>;
outputAffectingInstanceVersion: Map<FilePathKey, true>;
outputAffectingInstanceVersion: Map<string, Map<FilePathKey, true>>;
getOutputFileKeyFromReferencedProject(
outputFileName: string
): FilePathKey | undefined;
Expand Down Expand Up @@ -204,9 +206,6 @@ export interface TSInstance {

reportTranspileErrors?: boolean;
solutionBuilderHost?: SolutionBuilderWithWatchHost;
solutionBuilder?: typescript.SolutionBuilder<
typescript.EmitAndSemanticDiagnosticsBuilderProgram
>;
configFilePath: string | undefined;

filePathKeyMapper: (fileName: string) => FilePathKey;
Expand Down