Improved method that closely follows the recommendation of solutionBuilder.getNextInvalidatedProject().
By manually invoking solutionBuilder.getNextInvalidatedProject() .emit(...), you have the ability to include transformers. This action signifies that the build process is complete, and it will not emit again in its standard uncustomized manner.
You should utilize this method both before the initial build()
step and within the WatchStatusReporter
callback.
This approach allows you to introduce custom transformers while preserving the built-in watching mechanism. You can see this strategy in action with the proof-of-concept script provided below:
Below is the complete code snippet; also view it on Gist and Repl.it.
// @ts-check
var ts = require('typescript');
var tsconfig_json = JSON.stringify({
compilerOptions: {
outFile: __filename + '.out.js',
allowJs: true,
checkJs: true,
target: 'es3'
},
files: [__filename]
}, null, 2);
var s = {
delete: 3
};
/** @type {import('typescript').System} */
var sysOverride = {};
for (var k in ts.sys) { sysOverride[k] = ts.sys[k]; }
sysOverride.readFile = function (file) {
if (ts.sys.resolvePath(file) === ts.sys.resolvePath(__dirname + '/tsconfig.json')) {
// console.log('readFile(', file, ') -> overridden tsconfig_json');
return tsconfig_json;
}
else {
var result = ts.sys.readFile(file);
// if (!/node_modules/.test(file))
// console.log('readFile(', file, ') -> ' + (typeof result === 'string' ? '"' + result.length + '"' : typeof result));
return result;
}
};
sysOverride.writeFile = function (file, content) {
console.log(' sys.writeFile(', file, ', [', content.length, '])');
ts.sys.writeFile(file, content);
};
var host = ts.createSolutionBuilderWithWatchHost(
sysOverride,
void 0,
reportDiag,
reportDiag,
reportWatch);
var buildStart = Date.now();
var solutionBuilder = ts.createSolutionBuilderWithWatch(
host,
[__dirname],
{ incremental: false }, {});
initiateFirstBuild();
function initiateFirstBuild() {
var firstBuild = solutionBuilder.getNextInvalidatedProject();
if (firstBuild) {
buildStart = Date.now();
startBuild(firstBuild);
}
solutionBuilder.build();
}
/**
* @param {import('typescript').InvalidatedProject<import('typescript').EmitAndSemanticDiagnosticsBuilderProgram>} proj
* @param {import('typescript').Diagnostic=} watchDiag
*/
function startBuild(proj, watchDiag) {
ts.sys.write(
'\x1b[93m ' + (ts.InvalidatedProjectKind[proj.kind] + ' ').slice(0, 10) + '\x1b[0m' +
(watchDiag ? '' : '\n'));
if (watchDiag) reportDiag(watchDiag);
buildStart = Date.now();
if (proj && proj.kind === ts.InvalidatedProjectKind.Build) {
progSource = proj;
proj.emit(
void 0,
void 0,
void 0,
void 0,
{ after: [transformInjectStatementNumbers] });
}
}
function completeBuild(watchDiag) {
ts.sys.write('\x1b[90m ' + (((Date.now() - buildStart) / 1000) + 's ').slice(0, 10) + '\x1b[0m');
if (watchDiag) reportDiag(watchDiag);
}
/** @type {import('typescript').FormatDiagnosticsHost} */
var diagHost;
/** @param {import('typescript').Diagnostic} diag */
function reportDiag(diag) {
if (!diagHost) {
diagHost = {
getCanonicalFileName: function (fileName) {
return ts.sys.resolvePath(fileName)
},
getCurrentDirectory: function () {
return ts.sys.getCurrentDirectory();
},
getNewLine: function () {
return ts.sys.newLine;
}
};
}
var output = ts.sys.writeOutputIsTTY && ts.sys.writeOutputIsTTY() ?
ts.formatDiagnosticsWithColorAndContext([diag], diagHost) :
ts.formatDiagnostic(diag, diagHost);
output = output.replace(/^[\r\n]+/, '').replace(/[\r\n]+$/, '');
ts.sys.write(output + '\n');
}
/** @param {import('typescript').Diagnostic} diag */
function reportWatch(diag) {
var proj = solutionBuilder.getNextInvalidatedProject();
if (proj && /** @type {*} */(proj).getProgram) {
progSource = /** @type {*} */(proj);
}
if (proj)
startBuild(proj, diag);
else
completeBuild(diag);
}
/** @type {{ getProgram(): import('typescript').Program }} */
var progSource;
/** @type {import('typescript').TypeChecker} */
var checker;
/** @param {import('typescript').TransformationContext} context */
function transformInjectStatementNumbers(context) {
checker = progSource.getProgram().getTypeChecker();
return transformFile;
function transformFile(sourceFile) {
console.log(' transforming(', sourceFile.fileName, ')...');
return ts.updateSourceFileNode(
sourceFile,
sourceFile.statements.map(decorateStatementWithComplexityAndType));
}
}
/**
* @param {import('typescript').Statement} statement
*/
function decorateStatementWithComplexityAndType(statement) {
var nodeCount = 0;
var type;
ts.forEachChild(statement, visitStatementChild);
return ts.addSyntheticLeadingComment(
statement, ts.SyntaxKind.SingleLineCommentTrivia,
' INJECTED >> complexity: ' + nodeCount +
(!type ? '' : ' : ' + checker.typeToString(type)));
/**
* @param {import('typescript').Node} child
*/
function visitStatementChild(child) {
nodeCount++;
if (!type) type = checker.getTypeAtLocation(child);
if (type.getFlags() === ts.TypeFlags.Any) type = null;
ts.forEachChild(child, visitStatementChild);
}
}