tenebrous-dicebot/api/node_modules/ts-loader/dist/index.js

429 lines
18 KiB
JavaScript

"use strict";
const crypto = require("crypto");
const loaderUtils = require("loader-utils");
const path = require("path");
const constants = require("./constants");
const instances_1 = require("./instances");
const utils_1 = require("./utils");
const loaderOptionsCache = {};
/**
* The entry point for ts-loader
*/
function loader(contents) {
this.cacheable && this.cacheable();
const callback = this.async();
const options = getLoaderOptions(this);
const instanceOrError = instances_1.getTypeScriptInstance(options, this);
if (instanceOrError.error !== undefined) {
callback(new Error(instanceOrError.error.message));
return;
}
const instance = instanceOrError.instance;
instances_1.buildSolutionReferences(instance, this);
successLoader(this, contents, callback, instance);
}
function successLoader(loaderContext, contents, callback, instance) {
instances_1.initializeInstance(loaderContext, instance);
instances_1.reportTranspileErrors(instance, loaderContext);
const rawFilePath = path.normalize(loaderContext.resourcePath);
const filePath = instance.loaderOptions.appendTsSuffixTo.length > 0 ||
instance.loaderOptions.appendTsxSuffixTo.length > 0
? utils_1.appendSuffixesIfMatch({
'.ts': instance.loaderOptions.appendTsSuffixTo,
'.tsx': instance.loaderOptions.appendTsxSuffixTo,
}, rawFilePath)
: rawFilePath;
const fileVersion = updateFileInCache(instance.loaderOptions, filePath, contents, instance);
const { outputText, sourceMapText } = instance.loaderOptions.transpileOnly
? getTranspilationEmit(filePath, contents, instance, loaderContext)
: getEmit(rawFilePath, filePath, instance, loaderContext);
makeSourceMapAndFinish(sourceMapText, outputText, filePath, contents, loaderContext, fileVersion, callback, instance);
}
function makeSourceMapAndFinish(sourceMapText, outputText, filePath, contents, loaderContext, fileVersion, callback, instance) {
if (outputText === null || outputText === undefined) {
setModuleMeta(loaderContext, instance, fileVersion);
const additionalGuidance = utils_1.isReferencedFile(instance, filePath)
? ' The most common cause for this is having errors when building referenced projects.'
: !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'
: '';
callback(new Error(`TypeScript emitted no output for ${filePath}.${additionalGuidance}`), outputText, undefined);
return;
}
const { sourceMap, output } = makeSourceMap(sourceMapText, outputText, filePath, contents, loaderContext);
setModuleMeta(loaderContext, instance, fileVersion);
callback(null, output, sourceMap);
}
function setModuleMeta(loaderContext, instance, fileVersion) {
// _module.meta is not available inside happypack
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;
}
}
/**
* Get a unique hash based on the contents of the options
* Hash is created from the values converted to strings
* Values which are functions (such as getCustomTransformers) are
* converted to strings by this code, which JSON.stringify would not do.
*/
function getOptionsHash(loaderOptions) {
const hash = crypto.createHash('sha256');
Object.keys(loaderOptions).forEach(key => {
const value = loaderOptions[key];
if (value !== undefined) {
const valueString = typeof value === 'function' ? value.toString() : JSON.stringify(value);
hash.update(key + valueString);
}
});
return hash.digest('hex').substring(0, 16);
}
/**
* either retrieves loader options from the cache
* or creates them, adds them to the cache and returns
*/
function getLoaderOptions(loaderContext) {
const loaderOptions = loaderUtils.getOptions(loaderContext) ||
{};
// If no instance name is given in the options, use the hash of the loader options
// In this way, if different options are given the instances will be different
const instanceName = loaderOptions.instance || 'default_' + getOptionsHash(loaderOptions);
if (!loaderOptionsCache.hasOwnProperty(instanceName)) {
loaderOptionsCache[instanceName] = new WeakMap();
}
const cache = loaderOptionsCache[instanceName];
if (cache.has(loaderOptions)) {
return cache.get(loaderOptions);
}
validateLoaderOptions(loaderOptions);
const options = makeLoaderOptions(instanceName, loaderOptions);
cache.set(loaderOptions, options);
return options;
}
const validLoaderOptions = [
'silent',
'logLevel',
'logInfoToStdOut',
'instance',
'compiler',
'context',
'configFile',
'transpileOnly',
'ignoreDiagnostics',
'errorFormatter',
'colors',
'compilerOptions',
'appendTsSuffixTo',
'appendTsxSuffixTo',
'onlyCompileBundledFiles',
'happyPackMode',
'getCustomTransformers',
'reportFiles',
'experimentalWatchApi',
'allowTsInNodeModules',
'experimentalFileCaching',
'projectReferences',
'resolveModuleName',
'resolveTypeReferenceDirective',
'useCaseSensitiveFileNames',
];
/**
* Validate the supplied loader options.
* At present this validates the option names only; in future we may look at validating the values too
* @param loaderOptions
*/
function validateLoaderOptions(loaderOptions) {
const loaderOptionKeys = Object.keys(loaderOptions);
for (let i = 0; i < loaderOptionKeys.length; i++) {
const option = loaderOptionKeys[i];
const isUnexpectedOption = validLoaderOptions.indexOf(option) === -1;
if (isUnexpectedOption) {
throw new Error(`ts-loader was supplied with an unexpected loader option: ${option}
Please take a look at the options you are supplying; the following are valid options:
${validLoaderOptions.join(' / ')}
`);
}
}
if (loaderOptions.context !== undefined &&
!path.isAbsolute(loaderOptions.context)) {
throw new Error(`Option 'context' has to be an absolute path. Given '${loaderOptions.context}'.`);
}
}
function makeLoaderOptions(instanceName, loaderOptions) {
const options = Object.assign({}, {
silent: false,
logLevel: 'WARN',
logInfoToStdOut: false,
compiler: 'typescript',
configFile: 'tsconfig.json',
context: undefined,
transpileOnly: false,
compilerOptions: {},
appendTsSuffixTo: [],
appendTsxSuffixTo: [],
transformers: {},
happyPackMode: false,
colors: true,
onlyCompileBundledFiles: false,
reportFiles: [],
// When the watch API usage stabilises look to remove this option and make watch usage the default behaviour when available
experimentalWatchApi: false,
allowTsInNodeModules: false,
experimentalFileCaching: true,
}, loaderOptions);
options.ignoreDiagnostics = utils_1.arrify(options.ignoreDiagnostics).map(Number);
options.logLevel = options.logLevel.toUpperCase();
options.instance = instanceName;
// happypack can be used only together with transpileOnly mode
options.transpileOnly = options.happyPackMode ? true : options.transpileOnly;
return options;
}
/**
* Either add file to the overall files cache or update it in the cache when the file contents have changed
* Also add the file to the modified files
*/
function updateFileInCache(options, filePath, contents, instance) {
let fileWatcherEventKind;
// Update file contents
const key = instance.filePathKeyMapper(filePath);
let file = instance.files.get(key);
if (file === undefined) {
file = instance.otherFiles.get(key);
if (file !== undefined) {
if (!utils_1.isReferencedFile(instance, filePath)) {
instance.otherFiles.delete(key);
instance.files.set(key, file);
instance.changedFilesList = true;
}
}
else {
if (instance.watchHost !== undefined) {
fileWatcherEventKind = instance.compiler.FileWatcherEventKind.Created;
}
file = { fileName: filePath, version: 0 };
if (!utils_1.isReferencedFile(instance, filePath)) {
instance.files.set(key, file);
instance.changedFilesList = true;
}
}
}
if (instance.watchHost !== undefined && contents === undefined) {
fileWatcherEventKind = instance.compiler.FileWatcherEventKind.Deleted;
}
// filePath is a root file as it was passed to the loader. But it
// could have been found earlier as a dependency of another file. If
// that is the case, compiling this file changes the structure of
// the program and we need to increase the instance version.
//
// See https://github.com/TypeStrong/ts-loader/issues/943
if (!utils_1.isReferencedFile(instance, filePath) &&
!instance.rootFileNames.has(filePath) &&
// however, be careful not to add files from node_modules unless
// it is allowed by the options.
(options.allowTsInNodeModules || filePath.indexOf('node_modules') === -1)) {
instance.version++;
instance.rootFileNames.add(filePath);
}
if (file.text !== contents) {
file.version++;
file.text = contents;
file.modifiedTime = new Date();
instance.version++;
if (instance.watchHost !== undefined &&
fileWatcherEventKind === undefined) {
fileWatcherEventKind = instance.compiler.FileWatcherEventKind.Changed;
}
}
// Added in case the files were already updated by the watch API
if (instance.modifiedFiles && instance.modifiedFiles.get(key)) {
fileWatcherEventKind = instance.compiler.FileWatcherEventKind.Changed;
}
if (instance.watchHost !== undefined && fileWatcherEventKind !== undefined) {
instance.hasUnaccountedModifiedFiles =
instance.watchHost.invokeFileWatcher(filePath, fileWatcherEventKind) ||
instance.hasUnaccountedModifiedFiles;
}
// push this file to modified files hash.
if (!instance.modifiedFiles) {
instance.modifiedFiles = new Map();
}
instance.modifiedFiles.set(key, true);
return file.version;
}
function getEmit(rawFilePath, filePath, instance, loaderContext) {
var _a;
const outputFiles = instances_1.getEmitOutput(instance, filePath);
loaderContext.clearDependencies();
loaderContext.addDependency(rawFilePath);
const dependencies = [];
const addDependency = (file) => {
file = path.resolve(file);
loaderContext.addDependency(file);
dependencies.push(file);
};
// Make this file dependent on *all* definition files in the program
if (!utils_1.isReferencedFile(instance, filePath)) {
for (const { fileName: defFilePath } of instance.files.values()) {
if (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)
!((_a = instance.solutionBuilderHost) === null || _a === void 0 ? void 0 : _a.getOutputFileKeyFromReferencedProject(defFilePath))) {
addDependency(defFilePath);
}
}
}
// Additionally make this file dependent on all imported files
const fileDependencies = instance.dependencyGraph.get(instance.filePathKeyMapper(filePath));
if (fileDependencies) {
for (const { resolvedFileName, originalFileName } of fileDependencies) {
// In the case of dependencies that are part of a project reference,
// the real dependency that webpack should watch is the JS output file.
addDependency(instances_1.getInputFileNameFromOutput(instance, path.resolve(resolvedFileName)) ||
originalFileName);
}
}
addDependenciesFromSolutionBuilder(instance, filePath, addDependency);
loaderContext._module.buildMeta.tsLoaderDefinitionFileVersions = dependencies.map(defFilePath => path.relative(loaderContext.rootContext, defFilePath) +
'@' +
(utils_1.isReferencedFile(instance, defFilePath)
? instance
.solutionBuilderHost.getInputFileStamp(defFilePath)
.toString()
: (instance.files.get(instance.filePathKeyMapper(defFilePath)) ||
instance.otherFiles.get(instance.filePathKeyMapper(defFilePath)) || {
version: '?',
}).version));
return getOutputAndSourceMapFromOutputFiles(outputFiles);
}
function getOutputAndSourceMapFromOutputFiles(outputFiles) {
const outputFile = outputFiles
.filter(file => file.name.match(constants.jsJsx))
.pop();
const outputText = outputFile === undefined ? undefined : outputFile.text;
const sourceMapFile = outputFiles
.filter(file => file.name.match(constants.jsJsxMap))
.pop();
const sourceMapText = sourceMapFile === undefined ? undefined : sourceMapFile.text;
return { outputText, sourceMapText };
}
function addDependenciesFromSolutionBuilder(instance, filePath, addDependency) {
if (!instance.solutionBuilderHost) {
return;
}
// Add all the input files from the references as
const resolvedFilePath = instance.filePathKeyMapper(filePath);
if (!utils_1.isReferencedFile(instance, filePath)) {
if (instance.configParseResult.fileNames.some(f => instance.filePathKeyMapper(f) === resolvedFilePath)) {
addDependenciesFromProjectReferences(instance, instance.configFilePath, instance.configParseResult.projectReferences, addDependency);
}
return;
}
// Referenced file find the config for it
for (const [configFile, configInfo,] of instance.solutionBuilderHost.configFileInfo.entries()) {
if (!configInfo.config ||
!configInfo.config.projectReferences ||
!configInfo.config.projectReferences.length) {
continue;
}
if (configInfo.outputFileNames) {
if (!configInfo.outputFileNames.has(resolvedFilePath)) {
continue;
}
}
else if (!configInfo.config.fileNames.some(f => instance.filePathKeyMapper(f) === resolvedFilePath)) {
continue;
}
// Depend on all the dts files from the program
if (configInfo.dtsFiles) {
configInfo.dtsFiles.forEach(addDependency);
}
addDependenciesFromProjectReferences(instance, configFile, configInfo.config.projectReferences, addDependency);
break;
}
}
function addDependenciesFromProjectReferences(instance, configFile, projectReferences, addDependency) {
if (!projectReferences || !projectReferences.length) {
return;
}
// This is the config for the input file
const seenMap = new Map();
seenMap.set(instance.filePathKeyMapper(configFile), true);
// Add dependencies to all the input files from the project reference files since building them
const queue = projectReferences.slice();
while (true) {
const currentRef = queue.pop();
if (!currentRef) {
break;
}
const refConfigFile = instance.filePathKeyMapper(instance.compiler.resolveProjectReferencePath(currentRef));
if (seenMap.has(refConfigFile)) {
continue;
}
const refConfigInfo = instance.solutionBuilderHost.configFileInfo.get(refConfigFile);
if (!refConfigInfo) {
continue;
}
seenMap.set(refConfigFile, true);
if (refConfigInfo.config) {
refConfigInfo.config.fileNames.forEach(addDependency);
if (refConfigInfo.config.projectReferences) {
queue.push(...refConfigInfo.config.projectReferences);
}
}
}
}
/**
* Transpile file
*/
function getTranspilationEmit(fileName, contents, instance, loaderContext) {
if (utils_1.isReferencedFile(instance, fileName)) {
const outputFiles = instance.solutionBuilderHost.getOutputFilesFromReferencedProjectInput(fileName);
addDependenciesFromSolutionBuilder(instance, fileName, file => loaderContext.addDependency(path.resolve(file)));
return getOutputAndSourceMapFromOutputFiles(outputFiles);
}
const { outputText, sourceMapText, diagnostics, } = instance.compiler.transpileModule(contents, {
compilerOptions: Object.assign(Object.assign({}, instance.compilerOptions), { rootDir: undefined }),
transformers: instance.transformers,
reportDiagnostics: true,
fileName,
});
const module = loaderContext._module;
addDependenciesFromSolutionBuilder(instance, fileName, file => loaderContext.addDependency(path.resolve(file)));
// _module.errors is not available inside happypack - see https://github.com/TypeStrong/ts-loader/issues/336
if (!instance.loaderOptions.happyPackMode) {
const errors = utils_1.formatErrors(diagnostics, instance.loaderOptions, instance.colors, instance.compiler, { module }, loaderContext.context);
/**
* Since webpack 5, the `errors` property is deprecated,
* so we can check if some methods for reporting errors exist.
*/
if (module.addError) {
errors.forEach(error => module.addError(error));
}
else {
module.errors.push(...errors);
}
}
return { outputText, sourceMapText };
}
function makeSourceMap(sourceMapText, outputText, filePath, contents, loaderContext) {
if (sourceMapText === undefined) {
return { output: outputText, sourceMap: undefined };
}
return {
output: outputText.replace(/^\/\/# sourceMappingURL=[^\r\n]*/gm, ''),
sourceMap: Object.assign(JSON.parse(sourceMapText), {
sources: [loaderUtils.getRemainingRequest(loaderContext)],
file: filePath,
sourcesContent: [contents],
}),
};
}
module.exports = loader;
//# sourceMappingURL=index.js.map