Skip to content

Instantly share code, notes, and snippets.

@mihailik mihailik/build.js

Last active Jun 1, 2020
Embed
What would you like to do?
TypeScript custom transformers with ts.createSolutionBuilderWithWatch
// @ts-check
/// <reference types="node" />
var ts = require('typescript'); // little change
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);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.