UNPKG

23.4 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.getEmitOutput = exports.getEmitFromWatchHost = exports.getInputFileNameFromOutput = exports.getOutputFileNames = exports.forEachResolvedProjectReference = exports.buildSolutionReferences = exports.reportTranspileErrors = exports.initializeInstance = exports.getTypeScriptInstance = void 0;
4const chalk_1 = require("chalk");
5const fs = require("fs");
6const path = require("path");
7const webpack = require("webpack");
8const after_compile_1 = require("./after-compile");
9const compilerSetup_1 = require("./compilerSetup");
10const config_1 = require("./config");
11const constants_1 = require("./constants");
12const instance_cache_1 = require("./instance-cache");
13const logger = require("./logger");
14const servicesHost_1 = require("./servicesHost");
15const utils_1 = require("./utils");
16const watch_run_1 = require("./watch-run");
17const instancesBySolutionBuilderConfigs = new Map();
18/**
19 * The loader is executed once for each file seen by webpack. However, we need to keep
20 * a persistent instance of TypeScript that contains all of the files in the program
21 * along with definition files and options. This function either creates an instance
22 * or returns the existing one. Multiple instances are possible by using the
23 * `instance` property.
24 */
25function getTypeScriptInstance(loaderOptions, loader) {
26 const existing = instance_cache_1.getTSInstanceFromCache(loader._compiler, loaderOptions.instance);
27 if (existing) {
28 if (!existing.initialSetupPending) {
29 utils_1.ensureProgram(existing);
30 }
31 return { instance: existing };
32 }
33 const colors = new chalk_1.default.constructor({ enabled: loaderOptions.colors });
34 const log = logger.makeLogger(loaderOptions, colors);
35 const compiler = compilerSetup_1.getCompiler(loaderOptions, log);
36 if (compiler.errorMessage !== undefined) {
37 return {
38 error: utils_1.makeError(loaderOptions, colors.red(compiler.errorMessage), undefined),
39 };
40 }
41 return successfulTypeScriptInstance(loaderOptions, loader, log, colors, compiler.compiler, compiler.compilerCompatible, compiler.compilerDetailsLogMessage);
42}
43exports.getTypeScriptInstance = getTypeScriptInstance;
44function createFilePathKeyMapper(compiler, loaderOptions) {
45 // FileName lowercasing copied from typescript
46 const fileNameLowerCaseRegExp = /[^\u0130\u0131\u00DFa-z0-9\\/:\-_\. ]+/g;
47 return utils_1.useCaseSensitiveFileNames(compiler, loaderOptions)
48 ? pathResolve
49 : toFileNameLowerCase;
50 function pathResolve(x) {
51 return path.resolve(x);
52 }
53 function toFileNameLowerCase(x) {
54 const filePathKey = pathResolve(x);
55 return fileNameLowerCaseRegExp.test(filePathKey)
56 ? filePathKey.replace(fileNameLowerCaseRegExp, ch => ch.toLowerCase())
57 : filePathKey;
58 }
59}
60function successfulTypeScriptInstance(loaderOptions, loader, log, colors, compiler, compilerCompatible, compilerDetailsLogMessage) {
61 const configFileAndPath = config_1.getConfigFile(compiler, colors, loader, loaderOptions, compilerCompatible, log, compilerDetailsLogMessage);
62 if (configFileAndPath.configFileError !== undefined) {
63 const { message, file } = configFileAndPath.configFileError;
64 return {
65 error: utils_1.makeError(loaderOptions, colors.red('error while reading tsconfig.json:' + constants_1.EOL + message), file),
66 };
67 }
68 const { configFilePath, configFile } = configFileAndPath;
69 const filePathKeyMapper = createFilePathKeyMapper(compiler, loaderOptions);
70 if (configFilePath && loaderOptions.projectReferences) {
71 const configFileKey = filePathKeyMapper(configFilePath);
72 const existing = getExistingSolutionBuilderHost(configFileKey);
73 if (existing) {
74 // Reuse the instance if config file for project references is shared.
75 instance_cache_1.setTSInstanceInCache(loader._compiler, loaderOptions.instance, existing);
76 return { instance: existing };
77 }
78 }
79 const module = loader._module;
80 const basePath = loaderOptions.context || path.dirname(configFilePath || '');
81 const configParseResult = config_1.getConfigParseResult(compiler, configFile, basePath, configFilePath, loaderOptions);
82 if (configParseResult.errors.length > 0 && !loaderOptions.happyPackMode) {
83 const errors = utils_1.formatErrors(configParseResult.errors, loaderOptions, colors, compiler, { file: configFilePath }, loader.context);
84 /**
85 * Since webpack 5, the `errors` property is deprecated,
86 * so we can check if some methods for reporting errors exist.
87 */
88 if (module.addError) {
89 errors.forEach(error => module.addError(error));
90 }
91 else {
92 module.errors.push(...errors);
93 }
94 return {
95 error: utils_1.makeError(loaderOptions, colors.red('error while parsing tsconfig.json'), configFilePath),
96 };
97 }
98 const compilerOptions = compilerSetup_1.getCompilerOptions(configParseResult);
99 const rootFileNames = new Set();
100 const files = new Map();
101 const otherFiles = new Map();
102 const appendTsTsxSuffixesIfRequired = loaderOptions.appendTsSuffixTo.length > 0 ||
103 loaderOptions.appendTsxSuffixTo.length > 0
104 ? (filePath) => utils_1.appendSuffixesIfMatch({
105 '.ts': loaderOptions.appendTsSuffixTo,
106 '.tsx': loaderOptions.appendTsxSuffixTo,
107 }, filePath)
108 : (filePath) => filePath;
109 if (loaderOptions.transpileOnly) {
110 // quick return for transpiling
111 // we do need to check for any issues with TS options though
112 const transpileInstance = {
113 compiler,
114 compilerOptions,
115 appendTsTsxSuffixesIfRequired,
116 loaderOptions,
117 rootFileNames,
118 files,
119 otherFiles,
120 version: 0,
121 program: undefined,
122 dependencyGraph: new Map(),
123 transformers: {},
124 colors,
125 initialSetupPending: true,
126 reportTranspileErrors: true,
127 configFilePath,
128 configParseResult,
129 log,
130 filePathKeyMapper,
131 };
132 instance_cache_1.setTSInstanceInCache(loader._compiler, loaderOptions.instance, transpileInstance);
133 return { instance: transpileInstance };
134 }
135 // Load initial files (core lib files, any files specified in tsconfig.json)
136 let normalizedFilePath;
137 try {
138 const filesToLoad = loaderOptions.onlyCompileBundledFiles
139 ? configParseResult.fileNames.filter(fileName => constants_1.dtsDtsxOrDtsDtsxMapRegex.test(fileName))
140 : configParseResult.fileNames;
141 filesToLoad.forEach(filePath => {
142 normalizedFilePath = path.normalize(filePath);
143 files.set(filePathKeyMapper(normalizedFilePath), {
144 fileName: normalizedFilePath,
145 text: fs.readFileSync(normalizedFilePath, 'utf-8'),
146 version: 0,
147 });
148 rootFileNames.add(normalizedFilePath);
149 });
150 }
151 catch (exc) {
152 return {
153 error: utils_1.makeError(loaderOptions, colors.red(`A file specified in tsconfig.json could not be found: ${normalizedFilePath}`), normalizedFilePath),
154 };
155 }
156 const instance = {
157 compiler,
158 compilerOptions,
159 appendTsTsxSuffixesIfRequired,
160 loaderOptions,
161 rootFileNames,
162 files,
163 otherFiles,
164 languageService: null,
165 version: 0,
166 transformers: {},
167 dependencyGraph: new Map(),
168 colors,
169 initialSetupPending: true,
170 configFilePath,
171 configParseResult,
172 log,
173 filePathKeyMapper,
174 };
175 instance_cache_1.setTSInstanceInCache(loader._compiler, loaderOptions.instance, instance);
176 return { instance };
177}
178function getExistingSolutionBuilderHost(key) {
179 const existing = instancesBySolutionBuilderConfigs.get(key);
180 if (existing)
181 return existing;
182 for (const instance of instancesBySolutionBuilderConfigs.values()) {
183 if (instance.solutionBuilderHost.configFileInfo.has(key)) {
184 return instance;
185 }
186 }
187 return undefined;
188}
189// Adding assets in afterCompile is deprecated in webpack 5 so we
190// need different behavior for webpack4 and 5
191const addAssetHooks = !!webpack.version.match(/^4.*/)
192 ? (loader, instance) => {
193 // add makeAfterCompile with addAssets = true to emit assets and report errors
194 loader._compiler.hooks.afterCompile.tapAsync('ts-loader', after_compile_1.makeAfterCompile(instance, true, true, instance.configFilePath));
195 }
196 : (loader, instance) => {
197 // We must be running under webpack 5+
198 // Add makeAfterCompile with addAssets = false to suppress emitting assets
199 // during the afterCompile stage. Errors will be still be reported to webpack
200 loader._compiler.hooks.afterCompile.tapAsync('ts-loader', after_compile_1.makeAfterCompile(instance, false, true, instance.configFilePath));
201 // Emit the assets at the afterProcessAssets stage
202 loader._compilation.hooks.afterProcessAssets.tap('ts-loader', (_) => {
203 after_compile_1.makeAfterCompile(instance, true, false, instance.configFilePath)(loader._compilation, () => {
204 return null;
205 });
206 });
207 // It may be better to add assets at the processAssets stage (https://webpack.js.org/api/compilation-hooks/#processassets)
208 // This requires Compilation.PROCESS_ASSETS_STAGE_ADDITIONAL, which does not exist in webpack4
209 // Consider changing this when ts-loader is built using webpack5
210 };
211function initializeInstance(loader, instance) {
212 if (!instance.initialSetupPending) {
213 return;
214 }
215 instance.initialSetupPending = false;
216 // same strategy as https://github.com/s-panferov/awesome-typescript-loader/pull/531/files
217 let { getCustomTransformers: customerTransformers } = instance.loaderOptions;
218 let getCustomTransformers = Function.prototype;
219 if (typeof customerTransformers === 'function') {
220 getCustomTransformers = customerTransformers;
221 }
222 else if (typeof customerTransformers === 'string') {
223 try {
224 customerTransformers = require(customerTransformers);
225 }
226 catch (err) {
227 throw new Error(`Failed to load customTransformers from "${instance.loaderOptions.getCustomTransformers}": ${err.message}`);
228 }
229 if (typeof customerTransformers !== 'function') {
230 throw new Error(`Custom transformers in "${instance.loaderOptions.getCustomTransformers}" should export a function, got ${typeof getCustomTransformers}`);
231 }
232 getCustomTransformers = customerTransformers;
233 }
234 if (instance.loaderOptions.transpileOnly) {
235 const program = (instance.program =
236 instance.configParseResult.projectReferences !== undefined
237 ? instance.compiler.createProgram({
238 rootNames: instance.configParseResult.fileNames,
239 options: instance.configParseResult.options,
240 projectReferences: instance.configParseResult.projectReferences,
241 })
242 : instance.compiler.createProgram([], instance.compilerOptions));
243 instance.transformers = getCustomTransformers(program);
244 // Setup watch run for solution building
245 if (instance.solutionBuilderHost) {
246 addAssetHooks(loader, instance);
247 loader._compiler.hooks.watchRun.tapAsync('ts-loader', watch_run_1.makeWatchRun(instance, loader));
248 }
249 }
250 else {
251 if (!loader._compiler.hooks) {
252 throw new Error("You may be using an old version of webpack; please check you're using at least version 4");
253 }
254 if (instance.loaderOptions.experimentalWatchApi) {
255 instance.log.logInfo('Using watch api');
256 // If there is api available for watch, use it instead of language service
257 instance.watchHost = servicesHost_1.makeWatchHost(getScriptRegexp(instance), loader, instance, instance.configParseResult.projectReferences);
258 instance.watchOfFilesAndCompilerOptions = instance.compiler.createWatchProgram(instance.watchHost);
259 instance.builderProgram = instance.watchOfFilesAndCompilerOptions.getProgram();
260 instance.program = instance.builderProgram.getProgram();
261 instance.transformers = getCustomTransformers(instance.program);
262 }
263 else {
264 instance.servicesHost = servicesHost_1.makeServicesHost(getScriptRegexp(instance), loader, instance, instance.configParseResult.projectReferences);
265 instance.languageService = instance.compiler.createLanguageService(instance.servicesHost, instance.compiler.createDocumentRegistry());
266 instance.transformers = getCustomTransformers(instance.languageService.getProgram());
267 }
268 addAssetHooks(loader, instance);
269 loader._compiler.hooks.watchRun.tapAsync('ts-loader', watch_run_1.makeWatchRun(instance, loader));
270 }
271}
272exports.initializeInstance = initializeInstance;
273function getScriptRegexp(instance) {
274 // If resolveJsonModules is set, we should accept json files
275 if (instance.configParseResult.options.resolveJsonModule) {
276 // if allowJs is set then we should accept js(x) files
277 return instance.configParseResult.options.allowJs === true
278 ? /\.tsx?$|\.json$|\.jsx?$/i
279 : /\.tsx?$|\.json$/i;
280 }
281 // if allowJs is set then we should accept js(x) files
282 return instance.configParseResult.options.allowJs === true
283 ? /\.tsx?$|\.jsx?$/i
284 : /\.tsx?$/i;
285}
286function reportTranspileErrors(instance, loader) {
287 if (!instance.reportTranspileErrors) {
288 return;
289 }
290 const module = loader._module;
291 instance.reportTranspileErrors = false;
292 // happypack does not have _module.errors - see https://github.com/TypeStrong/ts-loader/issues/336
293 if (!instance.loaderOptions.happyPackMode) {
294 const solutionErrors = servicesHost_1.getSolutionErrors(instance, loader.context);
295 const diagnostics = instance.program.getOptionsDiagnostics();
296 const errors = utils_1.formatErrors(diagnostics, instance.loaderOptions, instance.colors, instance.compiler, { file: instance.configFilePath || 'tsconfig.json' }, loader.context);
297 /**
298 * Since webpack 5, the `errors` property is deprecated,
299 * so we can check if some methods for reporting errors exist.
300 */
301 if (module.addError) {
302 [...solutionErrors, ...errors].forEach(error => module.addError(error));
303 }
304 else {
305 module.errors.push(...solutionErrors, ...errors);
306 }
307 }
308}
309exports.reportTranspileErrors = reportTranspileErrors;
310function buildSolutionReferences(instance, loader) {
311 if (!utils_1.supportsSolutionBuild(instance)) {
312 return;
313 }
314 if (!instance.solutionBuilderHost) {
315 // Use solution builder
316 instance.log.logInfo('Using SolutionBuilder api');
317 const scriptRegex = getScriptRegexp(instance);
318 instance.solutionBuilderHost = servicesHost_1.makeSolutionBuilderHost(scriptRegex, loader, instance);
319 const solutionBuilder = instance.compiler.createSolutionBuilderWithWatch(instance.solutionBuilderHost, instance.configParseResult.projectReferences.map(ref => ref.path), { verbose: true });
320 solutionBuilder.build();
321 instance.solutionBuilderHost.ensureAllReferenceTimestamps();
322 instancesBySolutionBuilderConfigs.set(instance.filePathKeyMapper(instance.configFilePath), instance);
323 }
324 else {
325 instance.solutionBuilderHost.buildReferences();
326 }
327}
328exports.buildSolutionReferences = buildSolutionReferences;
329function forEachResolvedProjectReference(resolvedProjectReferences, cb) {
330 let seenResolvedRefs;
331 return worker(resolvedProjectReferences);
332 function worker(resolvedRefs) {
333 if (resolvedRefs) {
334 for (const resolvedRef of resolvedRefs) {
335 if (!resolvedRef) {
336 continue;
337 }
338 if (seenResolvedRefs &&
339 seenResolvedRefs.some(seenRef => seenRef === resolvedRef)) {
340 // ignore recursives
341 continue;
342 }
343 (seenResolvedRefs || (seenResolvedRefs = [])).push(resolvedRef);
344 const result = cb(resolvedRef) || worker(resolvedRef.references);
345 if (result) {
346 return result;
347 }
348 }
349 }
350 return undefined;
351 }
352}
353exports.forEachResolvedProjectReference = forEachResolvedProjectReference;
354// This code is here as a temporary holder
355function fileExtensionIs(fileName, ext) {
356 return fileName.endsWith(ext);
357}
358function rootDirOfOptions(instance, configFile) {
359 return (configFile.options.rootDir ||
360 instance.compiler.getDirectoryPath(configFile.options.configFilePath));
361}
362function getOutputPathWithoutChangingExt(instance, inputFileName, configFile, ignoreCase, outputDir) {
363 return outputDir
364 ? instance.compiler.resolvePath(outputDir, instance.compiler.getRelativePathFromDirectory(rootDirOfOptions(instance, configFile), inputFileName, ignoreCase))
365 : inputFileName;
366}
367function getOutputJSFileName(instance, inputFileName, configFile, ignoreCase) {
368 if (configFile.options.emitDeclarationOnly) {
369 return undefined;
370 }
371 const isJsonFile = fileExtensionIs(inputFileName, '.json');
372 const outputFileName = instance.compiler.changeExtension(getOutputPathWithoutChangingExt(instance, inputFileName, configFile, ignoreCase, configFile.options.outDir), isJsonFile
373 ? '.json'
374 : fileExtensionIs(inputFileName, '.tsx') &&
375 configFile.options.jsx === instance.compiler.JsxEmit.Preserve
376 ? '.jsx'
377 : '.js');
378 return !isJsonFile ||
379 instance.compiler.comparePaths(inputFileName, outputFileName, configFile.options.configFilePath, ignoreCase) !== instance.compiler.Comparison.EqualTo
380 ? outputFileName
381 : undefined;
382}
383function getOutputFileNames(instance, configFile, inputFileName) {
384 const ignoreCase = !utils_1.useCaseSensitiveFileNames(instance.compiler, instance.loaderOptions);
385 if (instance.compiler.getOutputFileNames) {
386 return instance.compiler.getOutputFileNames(configFile, inputFileName, ignoreCase);
387 }
388 const outputs = [];
389 const addOutput = (fileName) => fileName && outputs.push(fileName);
390 const js = getOutputJSFileName(instance, inputFileName, configFile, ignoreCase);
391 addOutput(js);
392 if (!fileExtensionIs(inputFileName, '.json')) {
393 if (js && configFile.options.sourceMap) {
394 addOutput(`${js}.map`);
395 }
396 if ((configFile.options.declaration || configFile.options.composite) &&
397 instance.compiler.hasTSFileExtension(inputFileName)) {
398 const dts = instance.compiler.getOutputDeclarationFileName(inputFileName, configFile, ignoreCase);
399 addOutput(dts);
400 if (configFile.options.declarationMap) {
401 addOutput(`${dts}.map`);
402 }
403 }
404 }
405 return outputs;
406}
407exports.getOutputFileNames = getOutputFileNames;
408function getInputFileNameFromOutput(instance, filePath) {
409 if (filePath.match(constants_1.tsTsxRegex) && !fileExtensionIs(filePath, '.d.ts')) {
410 return undefined;
411 }
412 if (instance.solutionBuilderHost) {
413 return instance.solutionBuilderHost.getInputFileNameFromOutput(filePath);
414 }
415 const program = utils_1.ensureProgram(instance);
416 return (program &&
417 program.getResolvedProjectReferences &&
418 forEachResolvedProjectReference(program.getResolvedProjectReferences(), ({ commandLine }) => {
419 const { options, fileNames } = commandLine;
420 if (!options.outFile && !options.out) {
421 const input = fileNames.find(file => getOutputFileNames(instance, commandLine, file).find(name => path.resolve(name) === filePath));
422 return input && path.resolve(input);
423 }
424 return undefined;
425 }));
426}
427exports.getInputFileNameFromOutput = getInputFileNameFromOutput;
428function getEmitFromWatchHost(instance, filePath) {
429 const program = utils_1.ensureProgram(instance);
430 const builderProgram = instance.builderProgram;
431 if (builderProgram && program) {
432 if (filePath) {
433 const existing = instance.watchHost.outputFiles.get(instance.filePathKeyMapper(filePath));
434 if (existing) {
435 return existing;
436 }
437 }
438 const outputFiles = [];
439 const writeFile = (fileName, text, writeByteOrderMark) => {
440 if (fileName.endsWith('.tsbuildinfo')) {
441 instance.watchHost.tsbuildinfo = {
442 name: fileName,
443 writeByteOrderMark,
444 text,
445 };
446 }
447 else {
448 outputFiles.push({ name: fileName, writeByteOrderMark, text });
449 }
450 };
451 const sourceFile = filePath ? program.getSourceFile(filePath) : undefined;
452 // Try emit Next file
453 while (true) {
454 const result = builderProgram.emitNextAffectedFile(writeFile,
455 /*cancellationToken*/ undefined,
456 /*emitOnlyDtsFiles*/ false, instance.transformers);
457 if (!result) {
458 break;
459 }
460 // Only put the output file in the cache if the source came from webpack and
461 // was processed by the loaders
462 if (result.affected === sourceFile) {
463 instance.watchHost.outputFiles.set(instance.filePathKeyMapper(result.affected.fileName), outputFiles.slice());
464 return outputFiles;
465 }
466 }
467 }
468 return undefined;
469}
470exports.getEmitFromWatchHost = getEmitFromWatchHost;
471function getEmitOutput(instance, filePath) {
472 if (fileExtensionIs(filePath, instance.compiler.Extension.Dts)) {
473 return [];
474 }
475 if (utils_1.isReferencedFile(instance, filePath)) {
476 return instance.solutionBuilderHost.getOutputFilesFromReferencedProjectInput(filePath);
477 }
478 const program = utils_1.ensureProgram(instance);
479 if (program !== undefined) {
480 const sourceFile = program.getSourceFile(filePath);
481 const outputFiles = [];
482 const writeFile = (fileName, text, writeByteOrderMark) => outputFiles.push({ name: fileName, writeByteOrderMark, text });
483 const outputFilesFromWatch = getEmitFromWatchHost(instance, filePath);
484 if (outputFilesFromWatch) {
485 return outputFilesFromWatch;
486 }
487 program.emit(sourceFile, writeFile,
488 /*cancellationToken*/ undefined,
489 /*emitOnlyDtsFiles*/ false, instance.transformers);
490 return outputFiles;
491 }
492 else {
493 // Emit Javascript
494 return instance.languageService.getProgram().getSourceFile(filePath) ===
495 undefined
496 ? []
497 : instance.languageService.getEmitOutput(filePath).outputFiles;
498 }
499}
500exports.getEmitOutput = getEmitOutput;
501//# sourceMappingURL=instances.js.map
\No newline at end of file