UNPKG

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