awesome-typescript-loader
Version:
Awesome TS loader for webpack
225 lines (186 loc) • 7 kB
text/typescript
/// <reference path='../node_modules/typescript/lib/typescriptServices.d.ts' />
/// <reference path="./defines.d.ts"/>
/// <reference path='../typings/tsd.d.ts' />
import * as _ from 'lodash';
import * as path from 'path';
let objectAssign = require('object-assign');
import { findCompiledModule, cache } from './cache';
import * as helpers from './helpers';
import { isTypeDeclaration } from './deps';
import { QueryOptions, IWebPack, ensureInstance, ICompilerInstance } from './instance';
import { PathsPlugin } from './paths-plugin';
let loaderUtils = require('loader-utils');
function loader(text) {
try {
compiler.call(undefined, this, text);
} catch(e) {
console.error(e, e.stack);
throw e;
}
}
interface Transformation {
text: string;
map: any;
}
function compiler(webpack: IWebPack, text: string): void {
if (webpack.cacheable) {
webpack.cacheable();
}
let options = <QueryOptions>loaderUtils.parseQuery(webpack.query);
let instanceName = options.instanceName || 'default';
let instance = ensureInstance(webpack, options, instanceName);
let state = instance.tsState;
let callback = webpack.async();
let fileName = helpers.toUnix(webpack.resourcePath);
let depsInjector = {
add: (depFileName) => webpack.addDependency(path.normalize(depFileName)),
clear: webpack.clearDependencies.bind(webpack)
};
let applyDeps = _.once(() => {
depsInjector.clear();
depsInjector.add(fileName);
state.fileAnalyzer.dependencies.applyCompiledFiles(fileName, depsInjector);
if (state.loaderConfig.reEmitDependentFiles) {
state.fileAnalyzer.dependencies.applyChain(fileName, depsInjector);
}
});
invokeKnownFilesOneTime(instance);
instance.compiledFiles[fileName] = true;
let doUpdate = false;
if (instance.loaderConfig.useWebpackText) {
if (state.updateFile(fileName, text, true)) {
doUpdate = true;
}
}
try {
let wasChanged = state.fileAnalyzer.checkDependencies(fileName);
if (wasChanged || doUpdate) {
instance.shouldUpdateProgram = true;
}
let compiledModule;
if (instance.loaderConfig.usePrecompiledFiles) {
compiledModule = findCompiledModule(fileName);
}
let transformation: Transformation = null;
if (compiledModule) {
state.fileAnalyzer.dependencies.addCompiledModule(fileName, compiledModule.fileName);
transformation = {
text: compiledModule.text,
map: compiledModule.map
? JSON.parse(compiledModule.map)
: null
};
} else {
let transformationFunction = () => transform(
webpack,
instance,
fileName,
text
);
if (instance.loaderConfig.useCache) {
transformation = cache<Transformation>({
source: text,
identifier: instance.cacheIdentifier,
directory: instance.loaderConfig.cacheDirectory,
options: webpack.query,
transform: transformationFunction
});
} else {
transformation = transformationFunction();
}
}
let resultText = transformation.text;
let resultSourceMap = transformation.map;
if (resultSourceMap) {
let sourcePath = path.relative(
instance.compilerConfig.options.sourceRoot || process.cwd(),
loaderUtils.getRemainingRequest(webpack)
);
resultSourceMap.sources = [ sourcePath ];
resultSourceMap.file = fileName;
resultSourceMap.sourcesContent = [ text ];
}
try {
callback(null, resultText, resultSourceMap);
} catch (e) {
console.error('Error in bail mode:', e, e.stack.join
? e.stack.join ('\n')
: e.stack
);
process.exit(1);
}
} catch (err) {
console.error(err.toString(), err.stack.toString());
callback(err, helpers.codegenErrorReport([err]));
} finally {
applyDeps();
}
}
function transform(webpack: IWebPack, instance: ICompilerInstance, fileName: string, text: string): Transformation {
let resultText;
let resultSourceMap = null;
let state = instance.tsState;
let useSlowEmit = state.compilerConfig.options.declaration || state.loaderConfig.disableFastEmit;
if (useSlowEmit) {
let output = state.emit(fileName);
let result = helpers.findResultFor(output, fileName);
if (result.text === undefined) {
throw new Error('No output found for ' + fileName);
}
if (result.declaration) {
webpack.emitFile(
path.relative(process.cwd(), result.declaration.sourceName),
result.declaration.text
);
}
resultText = result.text;
resultSourceMap = result.sourceMap;
} else {
let result = state.fastEmit(fileName);
resultText = result.text;
resultSourceMap = result.sourceMap;
}
let sourceFileName = fileName.replace(process.cwd() + '/', '');
if (resultSourceMap) {
resultSourceMap = JSON.parse(resultSourceMap);
resultSourceMap.sources = [ sourceFileName ];
resultSourceMap.file = sourceFileName;
resultSourceMap.sourcesContent = [ text ];
resultText = resultText.replace(/^\/\/# sourceMappingURL=[^\r\n]*/gm, '');
}
if (instance.loaderConfig.useBabel) {
let defaultOptions = {
inputSourceMap: resultSourceMap,
sourceRoot: process.cwd(),
filename: fileName,
sourceMap: true
};
let babelOptions = objectAssign({}, defaultOptions, instance.loaderConfig.babelOptions);
let babelResult = instance.babelImpl.transform(resultText, babelOptions);
resultText = babelResult.code;
resultSourceMap = babelResult.map;
}
return {
text: resultText,
map: resultSourceMap
};
}
function invokeKnownFilesOneTime(instance: ICompilerInstance) {
if (instance.loaderConfig.externals && !instance.externalsInvoked) {
instance.loaderConfig.externals
.filter(isTypeDeclaration)
.forEach(ext => instance.tsState.fileAnalyzer.checkDependencies(ext));
instance.externalsInvoked = true;
}
}
class ForkCheckerPlugin {
apply(compiler) {
compiler.plugin("watch-run", function(params, callback) {
compiler._tsFork = true;
callback();
});
}
}
(loader as any).ForkCheckerPlugin = ForkCheckerPlugin;
(loader as any).TsConfigPathsPlugin = PathsPlugin;
module.exports = loader;