1 | "use strict";
|
2 | const crypto = require("crypto");
|
3 | const loaderUtils = require("loader-utils");
|
4 | const path = require("path");
|
5 | const constants = require("./constants");
|
6 | const instances_1 = require("./instances");
|
7 | const utils_1 = require("./utils");
|
8 | const loaderOptionsCache = {};
|
9 |
|
10 |
|
11 |
|
12 | function loader(contents) {
|
13 | this.cacheable && this.cacheable();
|
14 | const callback = this.async();
|
15 | const options = getLoaderOptions(this);
|
16 | const instanceOrError = instances_1.getTypeScriptInstance(options, this);
|
17 | if (instanceOrError.error !== undefined) {
|
18 | callback(new Error(instanceOrError.error.message));
|
19 | return;
|
20 | }
|
21 | const instance = instanceOrError.instance;
|
22 | instances_1.buildSolutionReferences(instance, this);
|
23 | successLoader(this, contents, callback, instance);
|
24 | }
|
25 | function successLoader(loaderContext, contents, callback, instance) {
|
26 | instances_1.initializeInstance(loaderContext, instance);
|
27 | instances_1.reportTranspileErrors(instance, loaderContext);
|
28 | const rawFilePath = path.normalize(loaderContext.resourcePath);
|
29 | const filePath = instance.loaderOptions.appendTsSuffixTo.length > 0 ||
|
30 | instance.loaderOptions.appendTsxSuffixTo.length > 0
|
31 | ? utils_1.appendSuffixesIfMatch({
|
32 | '.ts': instance.loaderOptions.appendTsSuffixTo,
|
33 | '.tsx': instance.loaderOptions.appendTsxSuffixTo,
|
34 | }, rawFilePath)
|
35 | : rawFilePath;
|
36 | const fileVersion = updateFileInCache(instance.loaderOptions, filePath, contents, instance);
|
37 | const { outputText, sourceMapText } = instance.loaderOptions.transpileOnly
|
38 | ? getTranspilationEmit(filePath, contents, instance, loaderContext)
|
39 | : getEmit(rawFilePath, filePath, instance, loaderContext);
|
40 | makeSourceMapAndFinish(sourceMapText, outputText, filePath, contents, loaderContext, fileVersion, callback, instance);
|
41 | }
|
42 | function makeSourceMapAndFinish(sourceMapText, outputText, filePath, contents, loaderContext, fileVersion, callback, instance) {
|
43 | if (outputText === null || outputText === undefined) {
|
44 | setModuleMeta(loaderContext, instance, fileVersion);
|
45 | const additionalGuidance = utils_1.isReferencedFile(instance, filePath)
|
46 | ? ' The most common cause for this is having errors when building referenced projects.'
|
47 | : !instance.loaderOptions.allowTsInNodeModules &&
|
48 | filePath.indexOf('node_modules') !== -1
|
49 | ? ' By default, ts-loader will not compile .ts files in node_modules.\n' +
|
50 | 'You should not need to recompile .ts files there, but if you really want to, use the allowTsInNodeModules option.\n' +
|
51 | 'See: https://github.com/Microsoft/TypeScript/issues/12358'
|
52 | : '';
|
53 | callback(new Error(`TypeScript emitted no output for ${filePath}.${additionalGuidance}`), outputText, undefined);
|
54 | return;
|
55 | }
|
56 | const { sourceMap, output } = makeSourceMap(sourceMapText, outputText, filePath, contents, loaderContext);
|
57 | setModuleMeta(loaderContext, instance, fileVersion);
|
58 | callback(null, output, sourceMap);
|
59 | }
|
60 | function setModuleMeta(loaderContext, instance, fileVersion) {
|
61 |
|
62 | if (!instance.loaderOptions.happyPackMode &&
|
63 | loaderContext._module.buildMeta !== undefined) {
|
64 |
|
65 |
|
66 |
|
67 | loaderContext._module.buildMeta.tsLoaderFileVersion = fileVersion;
|
68 | }
|
69 | }
|
70 |
|
71 |
|
72 |
|
73 |
|
74 |
|
75 |
|
76 | function getOptionsHash(loaderOptions) {
|
77 | const hash = crypto.createHash('sha256');
|
78 | Object.keys(loaderOptions).forEach(key => {
|
79 | const value = loaderOptions[key];
|
80 | if (value) {
|
81 | hash.update(key + value.toString());
|
82 | }
|
83 | });
|
84 | return hash.digest('hex').substring(0, 16);
|
85 | }
|
86 |
|
87 |
|
88 |
|
89 |
|
90 | function getLoaderOptions(loaderContext) {
|
91 | const loaderOptions = loaderUtils.getOptions(loaderContext) ||
|
92 | {};
|
93 |
|
94 |
|
95 | const instanceName = loaderOptions.instance || 'default_' + getOptionsHash(loaderOptions);
|
96 | if (!loaderOptionsCache.hasOwnProperty(instanceName)) {
|
97 | loaderOptionsCache[instanceName] = new WeakMap();
|
98 | }
|
99 | const cache = loaderOptionsCache[instanceName];
|
100 | if (cache.has(loaderOptions)) {
|
101 | return cache.get(loaderOptions);
|
102 | }
|
103 | validateLoaderOptions(loaderOptions);
|
104 | const options = makeLoaderOptions(instanceName, loaderOptions);
|
105 | cache.set(loaderOptions, options);
|
106 | return options;
|
107 | }
|
108 | const validLoaderOptions = [
|
109 | 'silent',
|
110 | 'logLevel',
|
111 | 'logInfoToStdOut',
|
112 | 'instance',
|
113 | 'compiler',
|
114 | 'context',
|
115 | 'configFile',
|
116 | 'transpileOnly',
|
117 | 'ignoreDiagnostics',
|
118 | 'errorFormatter',
|
119 | 'colors',
|
120 | 'compilerOptions',
|
121 | 'appendTsSuffixTo',
|
122 | 'appendTsxSuffixTo',
|
123 | 'onlyCompileBundledFiles',
|
124 | 'happyPackMode',
|
125 | 'getCustomTransformers',
|
126 | 'reportFiles',
|
127 | 'experimentalWatchApi',
|
128 | 'allowTsInNodeModules',
|
129 | 'experimentalFileCaching',
|
130 | 'projectReferences',
|
131 | 'resolveModuleName',
|
132 | 'resolveTypeReferenceDirective',
|
133 | 'useCaseSensitiveFileNames',
|
134 | ];
|
135 |
|
136 |
|
137 |
|
138 |
|
139 |
|
140 | function validateLoaderOptions(loaderOptions) {
|
141 | const loaderOptionKeys = Object.keys(loaderOptions);
|
142 | for (let i = 0; i < loaderOptionKeys.length; i++) {
|
143 | const option = loaderOptionKeys[i];
|
144 | const isUnexpectedOption = validLoaderOptions.indexOf(option) === -1;
|
145 | if (isUnexpectedOption) {
|
146 | throw new Error(`ts-loader was supplied with an unexpected loader option: ${option}
|
147 |
|
148 | Please take a look at the options you are supplying; the following are valid options:
|
149 | ${validLoaderOptions.join(' / ')}
|
150 | `);
|
151 | }
|
152 | }
|
153 | if (loaderOptions.context !== undefined &&
|
154 | !path.isAbsolute(loaderOptions.context)) {
|
155 | throw new Error(`Option 'context' has to be an absolute path. Given '${loaderOptions.context}'.`);
|
156 | }
|
157 | }
|
158 | function makeLoaderOptions(instanceName, loaderOptions) {
|
159 | const options = Object.assign({}, {
|
160 | silent: false,
|
161 | logLevel: 'WARN',
|
162 | logInfoToStdOut: false,
|
163 | compiler: 'typescript',
|
164 | configFile: 'tsconfig.json',
|
165 | context: undefined,
|
166 | transpileOnly: false,
|
167 | compilerOptions: {},
|
168 | appendTsSuffixTo: [],
|
169 | appendTsxSuffixTo: [],
|
170 | transformers: {},
|
171 | happyPackMode: false,
|
172 | colors: true,
|
173 | onlyCompileBundledFiles: false,
|
174 | reportFiles: [],
|
175 |
|
176 | experimentalWatchApi: false,
|
177 | allowTsInNodeModules: false,
|
178 | experimentalFileCaching: true,
|
179 | }, loaderOptions);
|
180 | options.ignoreDiagnostics = utils_1.arrify(options.ignoreDiagnostics).map(Number);
|
181 | options.logLevel = options.logLevel.toUpperCase();
|
182 | options.instance = instanceName;
|
183 |
|
184 | options.transpileOnly = options.happyPackMode ? true : options.transpileOnly;
|
185 | return options;
|
186 | }
|
187 |
|
188 |
|
189 |
|
190 |
|
191 | function updateFileInCache(options, filePath, contents, instance) {
|
192 | let fileWatcherEventKind;
|
193 |
|
194 | const key = instance.filePathKeyMapper(filePath);
|
195 | let file = instance.files.get(key);
|
196 | if (file === undefined) {
|
197 | file = instance.otherFiles.get(key);
|
198 | if (file !== undefined) {
|
199 | if (!utils_1.isReferencedFile(instance, filePath)) {
|
200 | instance.otherFiles.delete(key);
|
201 | instance.files.set(key, file);
|
202 | instance.changedFilesList = true;
|
203 | }
|
204 | }
|
205 | else {
|
206 | if (instance.watchHost !== undefined) {
|
207 | fileWatcherEventKind = instance.compiler.FileWatcherEventKind.Created;
|
208 | }
|
209 | file = { fileName: filePath, version: 0 };
|
210 | if (!utils_1.isReferencedFile(instance, filePath)) {
|
211 | instance.files.set(key, file);
|
212 | instance.changedFilesList = true;
|
213 | }
|
214 | }
|
215 | }
|
216 | if (instance.watchHost !== undefined && contents === undefined) {
|
217 | fileWatcherEventKind = instance.compiler.FileWatcherEventKind.Deleted;
|
218 | }
|
219 |
|
220 |
|
221 |
|
222 |
|
223 |
|
224 |
|
225 | if (!utils_1.isReferencedFile(instance, filePath) &&
|
226 | !instance.rootFileNames.has(filePath) &&
|
227 |
|
228 |
|
229 | (options.allowTsInNodeModules || filePath.indexOf('node_modules') === -1)) {
|
230 | instance.version++;
|
231 | instance.rootFileNames.add(filePath);
|
232 | }
|
233 | if (file.text !== contents) {
|
234 | file.version++;
|
235 | file.text = contents;
|
236 | file.modifiedTime = new Date();
|
237 | instance.version++;
|
238 | if (instance.watchHost !== undefined &&
|
239 | fileWatcherEventKind === undefined) {
|
240 | fileWatcherEventKind = instance.compiler.FileWatcherEventKind.Changed;
|
241 | }
|
242 | }
|
243 |
|
244 | if (instance.modifiedFiles && instance.modifiedFiles.get(key)) {
|
245 | fileWatcherEventKind = instance.compiler.FileWatcherEventKind.Changed;
|
246 | }
|
247 | if (instance.watchHost !== undefined && fileWatcherEventKind !== undefined) {
|
248 | instance.hasUnaccountedModifiedFiles =
|
249 | instance.watchHost.invokeFileWatcher(filePath, fileWatcherEventKind) ||
|
250 | instance.hasUnaccountedModifiedFiles;
|
251 | }
|
252 |
|
253 | if (!instance.modifiedFiles) {
|
254 | instance.modifiedFiles = new Map();
|
255 | }
|
256 | instance.modifiedFiles.set(key, true);
|
257 | return file.version;
|
258 | }
|
259 | function getEmit(rawFilePath, filePath, instance, loaderContext) {
|
260 | var _a;
|
261 | const outputFiles = instances_1.getEmitOutput(instance, filePath);
|
262 | loaderContext.clearDependencies();
|
263 | loaderContext.addDependency(rawFilePath);
|
264 | const dependencies = [];
|
265 | const addDependency = (file) => {
|
266 | file = path.resolve(file);
|
267 | loaderContext.addDependency(file);
|
268 | dependencies.push(file);
|
269 | };
|
270 |
|
271 | if (!utils_1.isReferencedFile(instance, filePath)) {
|
272 | for (const { fileName: defFilePath } of instance.files.values()) {
|
273 | if (defFilePath.match(constants.dtsDtsxOrDtsDtsxMapRegex) &&
|
274 |
|
275 |
|
276 | !((_a = instance.solutionBuilderHost) === null || _a === void 0 ? void 0 : _a.getOutputFileKeyFromReferencedProject(defFilePath))) {
|
277 | addDependency(defFilePath);
|
278 | }
|
279 | }
|
280 | }
|
281 |
|
282 | const fileDependencies = instance.dependencyGraph.get(instance.filePathKeyMapper(filePath));
|
283 | if (fileDependencies) {
|
284 | for (const { resolvedFileName, originalFileName } of fileDependencies) {
|
285 |
|
286 |
|
287 | addDependency(instances_1.getInputFileNameFromOutput(instance, path.resolve(resolvedFileName)) ||
|
288 | originalFileName);
|
289 | }
|
290 | }
|
291 | addDependenciesFromSolutionBuilder(instance, filePath, addDependency);
|
292 | loaderContext._module.buildMeta.tsLoaderDefinitionFileVersions = dependencies.map(defFilePath => path.relative(loaderContext.rootContext, defFilePath) +
|
293 | '@' +
|
294 | (utils_1.isReferencedFile(instance, defFilePath)
|
295 | ? instance
|
296 | .solutionBuilderHost.getInputFileStamp(defFilePath)
|
297 | .toString()
|
298 | : (instance.files.get(instance.filePathKeyMapper(defFilePath)) ||
|
299 | instance.otherFiles.get(instance.filePathKeyMapper(defFilePath)) || {
|
300 | version: '?',
|
301 | }).version));
|
302 | return getOutputAndSourceMapFromOutputFiles(outputFiles);
|
303 | }
|
304 | function getOutputAndSourceMapFromOutputFiles(outputFiles) {
|
305 | const outputFile = outputFiles
|
306 | .filter(file => file.name.match(constants.jsJsx))
|
307 | .pop();
|
308 | const outputText = outputFile === undefined ? undefined : outputFile.text;
|
309 | const sourceMapFile = outputFiles
|
310 | .filter(file => file.name.match(constants.jsJsxMap))
|
311 | .pop();
|
312 | const sourceMapText = sourceMapFile === undefined ? undefined : sourceMapFile.text;
|
313 | return { outputText, sourceMapText };
|
314 | }
|
315 | function addDependenciesFromSolutionBuilder(instance, filePath, addDependency) {
|
316 | if (!instance.solutionBuilderHost) {
|
317 | return;
|
318 | }
|
319 |
|
320 | const resolvedFilePath = instance.filePathKeyMapper(filePath);
|
321 | if (!utils_1.isReferencedFile(instance, filePath)) {
|
322 | if (instance.configParseResult.fileNames.some(f => instance.filePathKeyMapper(f) === resolvedFilePath)) {
|
323 | addDependenciesFromProjectReferences(instance, instance.configFilePath, instance.configParseResult.projectReferences, addDependency);
|
324 | }
|
325 | return;
|
326 | }
|
327 |
|
328 | for (const [configFile, configInfo,] of instance.solutionBuilderHost.configFileInfo.entries()) {
|
329 | if (!configInfo.config ||
|
330 | !configInfo.config.projectReferences ||
|
331 | !configInfo.config.projectReferences.length) {
|
332 | continue;
|
333 | }
|
334 | if (configInfo.outputFileNames) {
|
335 | if (!configInfo.outputFileNames.has(resolvedFilePath)) {
|
336 | continue;
|
337 | }
|
338 | }
|
339 | else if (!configInfo.config.fileNames.some(f => instance.filePathKeyMapper(f) === resolvedFilePath)) {
|
340 | continue;
|
341 | }
|
342 |
|
343 | if (configInfo.dtsFiles) {
|
344 | configInfo.dtsFiles.forEach(addDependency);
|
345 | }
|
346 | addDependenciesFromProjectReferences(instance, configFile, configInfo.config.projectReferences, addDependency);
|
347 | break;
|
348 | }
|
349 | }
|
350 | function addDependenciesFromProjectReferences(instance, configFile, projectReferences, addDependency) {
|
351 | if (!projectReferences || !projectReferences.length) {
|
352 | return;
|
353 | }
|
354 |
|
355 | const seenMap = new Map();
|
356 | seenMap.set(instance.filePathKeyMapper(configFile), true);
|
357 |
|
358 | const queue = projectReferences.slice();
|
359 | while (true) {
|
360 | const currentRef = queue.pop();
|
361 | if (!currentRef) {
|
362 | break;
|
363 | }
|
364 | const refConfigFile = instance.filePathKeyMapper(instance.compiler.resolveProjectReferencePath(currentRef));
|
365 | if (seenMap.has(refConfigFile)) {
|
366 | continue;
|
367 | }
|
368 | const refConfigInfo = instance.solutionBuilderHost.configFileInfo.get(refConfigFile);
|
369 | if (!refConfigInfo) {
|
370 | continue;
|
371 | }
|
372 | seenMap.set(refConfigFile, true);
|
373 | if (refConfigInfo.config) {
|
374 | refConfigInfo.config.fileNames.forEach(addDependency);
|
375 | if (refConfigInfo.config.projectReferences) {
|
376 | queue.push(...refConfigInfo.config.projectReferences);
|
377 | }
|
378 | }
|
379 | }
|
380 | }
|
381 |
|
382 |
|
383 |
|
384 | function getTranspilationEmit(fileName, contents, instance, loaderContext) {
|
385 | if (utils_1.isReferencedFile(instance, fileName)) {
|
386 | const outputFiles = instance.solutionBuilderHost.getOutputFilesFromReferencedProjectInput(fileName);
|
387 | addDependenciesFromSolutionBuilder(instance, fileName, file => loaderContext.addDependency(path.resolve(file)));
|
388 | return getOutputAndSourceMapFromOutputFiles(outputFiles);
|
389 | }
|
390 | const { outputText, sourceMapText, diagnostics, } = instance.compiler.transpileModule(contents, {
|
391 | compilerOptions: Object.assign(Object.assign({}, instance.compilerOptions), { rootDir: undefined }),
|
392 | transformers: instance.transformers,
|
393 | reportDiagnostics: true,
|
394 | fileName,
|
395 | });
|
396 | const module = loaderContext._module;
|
397 | addDependenciesFromSolutionBuilder(instance, fileName, file => loaderContext.addDependency(path.resolve(file)));
|
398 |
|
399 | if (!instance.loaderOptions.happyPackMode) {
|
400 | const errors = utils_1.formatErrors(diagnostics, instance.loaderOptions, instance.colors, instance.compiler, { module }, loaderContext.context);
|
401 | |
402 |
|
403 |
|
404 |
|
405 | if (module.addError) {
|
406 | errors.forEach(error => module.addError(error));
|
407 | }
|
408 | else {
|
409 | module.errors.push(...errors);
|
410 | }
|
411 | }
|
412 | return { outputText, sourceMapText };
|
413 | }
|
414 | function makeSourceMap(sourceMapText, outputText, filePath, contents, loaderContext) {
|
415 | if (sourceMapText === undefined) {
|
416 | return { output: outputText, sourceMap: undefined };
|
417 | }
|
418 | return {
|
419 | output: outputText.replace(/^\/\/# sourceMappingURL=[^\r\n]*/gm, ''),
|
420 | sourceMap: Object.assign(JSON.parse(sourceMapText), {
|
421 | sources: [loaderUtils.getRemainingRequest(loaderContext)],
|
422 | file: filePath,
|
423 | sourcesContent: [contents],
|
424 | }),
|
425 | };
|
426 | }
|
427 | module.exports = loader;
|
428 |
|
\ | No newline at end of file |