- tracking global types

This commit is contained in:
ezolenko 2017-02-09 23:09:43 -07:00
parent 3cc2c6a8e2
commit 6df208b9c4
8 changed files with 377 additions and 178 deletions

View File

@ -1,12 +1,11 @@
/* eslint-disable */
'use strict';
var fs = require('fs');
var fs = require('fs-extra');
var ts = require('typescript');
var _ = require('lodash');
var graph = require('graphlib');
var hash = require('object-hash');
var mkdirp = require('mkdirp');
var rollupPluginutils = require('rollup-pluginutils');
var path = require('path');
@ -60,16 +59,61 @@ var LanguageServiceHost = (function () {
return LanguageServiceHost;
}());
var Cache = (function () {
function Cache(cacheRoot, options, rootFilenames) {
var RollingCache = (function () {
function RollingCache(cacheRoot, checkNewCache) {
this.cacheRoot = cacheRoot;
this.checkNewCache = checkNewCache;
this.oldCacheRoot = this.cacheRoot + "/cache";
this.newCacheRoot = this.cacheRoot + "/cache_";
fs.emptyDirSync(this.newCacheRoot);
}
RollingCache.prototype.exists = function (name) {
if (this.checkNewCache && fs.existsSync(this.newCacheRoot + "/" + name))
return true;
return fs.existsSync(this.oldCacheRoot + "/" + name);
};
RollingCache.prototype.match = function (names) {
if (!fs.existsSync(this.oldCacheRoot))
return false;
return _.isEqual(fs.readdirSync(this.oldCacheRoot).sort(), names.sort());
};
RollingCache.prototype.read = function (name) {
if (this.checkNewCache && fs.existsSync(this.newCacheRoot + "/" + name))
return fs.readJsonSync(this.newCacheRoot + "/" + name, "utf8");
return fs.readJsonSync(this.oldCacheRoot + "/" + name, "utf8");
};
RollingCache.prototype.write = function (name, data) {
if (data === undefined)
return;
fs.writeJson(this.newCacheRoot + "/" + name, data, { encoding: "utf8" }, function () { });
};
RollingCache.prototype.touch = function (name) {
fs.ensureFile(this.newCacheRoot + "/" + name, function () { });
};
RollingCache.prototype.roll = function () {
var _this = this;
fs.remove(this.oldCacheRoot, function () {
fs.move(_this.newCacheRoot, _this.oldCacheRoot, function () { });
});
};
return RollingCache;
}());
var Cache = (function () {
function Cache(host, cache, options, rootFilenames) {
var _this = this;
this.host = host;
this.options = options;
this.cacheVersion = "0";
this.treeComplete = false;
this.cacheRoot = this.cacheRoot + "/" + hash.sha1({ version: this.cacheVersion, rootFilenames: rootFilenames, options: this.options });
mkdirp.sync(this.cacheRoot);
this.cacheVersion = "1";
this.typesDirty = false;
this.cacheDir = cache + "/" + hash.sha1({ version: this.cacheVersion, rootFilenames: rootFilenames, options: this.options });
this.codeCache = new RollingCache(this.cacheDir + "/code", true);
this.typesCache = new RollingCache(this.cacheDir + "/types", false);
this.diagnosticsCache = new RollingCache(this.cacheDir + "/diagnostics", false);
this.dependencyTree = new graph.Graph({ directed: true });
this.dependencyTree.setDefaultNodeLabel(function (_node) { return { dirty: false }; });
this.types = _.filter(rootFilenames, function (file) { return _.endsWith(file, ".d.ts"); })
.map(function (id) { return { id: id, snapshot: _this.host.getScriptSnapshot(id) }; });
}
Cache.prototype.walkTree = function (cb) {
var acyclic = graph.alg.isAcyclic(this.dependencyTree);
@ -77,21 +121,55 @@ var Cache = (function () {
_.each(graph.alg.topsort(this.dependencyTree), function (id) { return cb(id); });
return;
}
// console.log("cycles detected in dependency graph, not sorting");
_.each(this.dependencyTree.nodes(), function (id) { return cb(id); });
};
Cache.prototype.setDependency = function (importee, importer) {
// console.log(importer, "->", importee);
// importee -> importer
this.dependencyTree.setEdge(importer, importee);
};
Cache.prototype.lastDependencySet = function () {
this.treeComplete = true;
Cache.prototype.compileDone = function () {
var _this = this;
var typeNames = _.filter(this.types, function (snaphot) { return snaphot.snapshot !== undefined; })
.map(function (snaphot) { return _this.makeName(snaphot.id, snaphot.snapshot); });
// types dirty if any d.ts changed, added or removed
this.typesDirty = !this.typesCache.match(typeNames);
_.each(typeNames, function (name) { return _this.typesCache.touch(name); });
};
Cache.prototype.diagnosticsDone = function () {
this.codeCache.roll();
this.diagnosticsCache.roll();
this.typesCache.roll();
};
Cache.prototype.getCompiled = function (id, snapshot, transform) {
var name = this.makeName(id, snapshot);
if (!this.codeCache.exists(name) || this.isDirty(id, snapshot, false)) {
console.log("compile cache miss: " + id);
var data_1 = transform();
this.codeCache.write(name, data_1);
this.markAsDirty(id, snapshot);
return data_1;
}
var data = this.codeCache.read(name);
this.codeCache.write(name, data);
return data;
};
Cache.prototype.getDiagnostics = function (id, snapshot, check) {
var name = this.makeName(id, snapshot);
if (!this.diagnosticsCache.exists(name) || this.isDirty(id, snapshot, true)) {
console.log("diag cache miss: " + id);
var data_2 = this.convert(check());
this.diagnosticsCache.write(name, data_2);
this.markAsDirty(id, snapshot);
return data_2;
}
var data = this.diagnosticsCache.read(name);
this.diagnosticsCache.write(name, data);
return data;
};
Cache.prototype.markAsDirty = function (id, _snapshot) {
this.dependencyTree.setNode(id, { dirty: true });
};
// returns true if node or any of its imports changed
// returns true if node or any of its imports or any of global types changed
Cache.prototype.isDirty = function (id, _snapshot, checkImports) {
var _this = this;
var label = this.dependencyTree.node(id);
@ -99,6 +177,8 @@ var Cache = (function () {
return false;
if (!checkImports || label.dirty)
return label.dirty;
if (this.typesDirty)
return true;
var dependencies = graph.alg.dijkstra(this.dependencyTree, id);
return _.some(dependencies, function (dependency, node) {
if (!node || dependency.distance === Infinity)
@ -106,48 +186,22 @@ var Cache = (function () {
var l = _this.dependencyTree.node(node);
var dirty = l === undefined ? true : l.dirty;
if (dirty)
console.log("dirty: " + id + " -> " + node);
console.log(("dirty: " + id + " -> " + node).gray);
return dirty;
});
};
Cache.prototype.getCompiled = function (id, snapshot, transform) {
var path$$1 = this.makePath(id, snapshot);
if (!fs.existsSync(path$$1) || this.isDirty(id, snapshot, false)) {
// console.log(`compile cache miss: ${id}`);
var data = transform();
this.setCache(path$$1, id, snapshot, data);
return data;
}
return JSON.parse(fs.readFileSync(path$$1, "utf8"));
};
Cache.prototype.getDiagnostics = function (id, snapshot, check) {
var path$$1 = this.makePath(id, snapshot) + ".diagnostics";
if (!fs.existsSync(path$$1) || this.isDirty(id, snapshot, true)) {
// console.log(`diagnostics cache miss: ${id}`);
var data = this.convert(check());
this.setCache(path$$1, id, snapshot, data);
return data;
}
return JSON.parse(fs.readFileSync(path$$1, "utf8"));
};
Cache.prototype.setCache = function (path$$1, id, snapshot, data) {
if (data === undefined)
return;
fs.writeFileSync(path$$1, JSON.stringify(data));
this.markAsDirty(id, snapshot);
};
Cache.prototype.makePath = function (id, snapshot) {
Cache.prototype.makeName = function (id, snapshot) {
var data = snapshot.getText(0, snapshot.getLength());
return this.cacheRoot + "/" + hash.sha1({ data: data, id: id });
return hash.sha1({ data: data, id: id });
};
Cache.prototype.convert = function (data) {
return _.map(data, function (diagnostic) {
var entry = {
flatMessage: ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n"),
flatMessage: ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n").yellow,
};
if (diagnostic.file) {
var _a = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start), line = _a.line, character = _a.character;
entry.fileLine = diagnostic.file.fileName + " (" + (line + 1) + "," + (character + 1) + ")";
entry.fileLine = (diagnostic.file.fileName + " (" + (line + 1) + "," + (character + 1) + ")").white;
}
return entry;
});
@ -215,11 +269,11 @@ function printDiagnostics(diagnostics) {
function typescript(options) {
options = __assign({}, options);
var filter = rollupPluginutils.createFilter(options.include || ["*.ts+(|x)", "**/*.ts+(|x)"], options.exclude || ["*.d.ts", "**/*.d.ts"]);
var filter$$1 = rollupPluginutils.createFilter(options.include || ["*.ts+(|x)", "**/*.ts+(|x)"], options.exclude || ["*.d.ts", "**/*.d.ts"]);
var parsedConfig = parseTsConfig();
var servicesHost = new LanguageServiceHost(parsedConfig);
var services = ts.createLanguageService(servicesHost, ts.createDocumentRegistry());
var cache = new Cache(process.cwd() + "/.rts2_cache", parsedConfig.options, parsedConfig.fileNames);
var cache = new Cache(servicesHost, process.cwd() + "/.rts2_cache", parsedConfig.options, parsedConfig.fileNames);
return {
resolveId: function (importee, importer) {
if (importee === TSLIB)
@ -229,10 +283,10 @@ function typescript(options) {
importer = importer.split("\\").join("/");
var result = ts.nodeModuleNameResolver(importee, importer, parsedConfig.options, ts.sys);
if (result.resolvedModule && result.resolvedModule.resolvedFileName) {
if (filter$$1(result.resolvedModule.resolvedFileName))
cache.setDependency(result.resolvedModule.resolvedFileName, importer);
if (_.endsWith(result.resolvedModule.resolvedFileName, ".d.ts"))
return null;
if (filter(result.resolvedModule.resolvedFileName))
cache.setDependency(result.resolvedModule.resolvedFileName, importer);
return result.resolvedModule.resolvedFileName;
}
return null;
@ -244,7 +298,7 @@ function typescript(options) {
},
transform: function (code, id) {
var _this = this;
if (!filter(id))
if (!filter$$1(id))
return undefined;
var snapshot = servicesHost.setSnapshot(id, code);
var result = cache.getCompiled(id, snapshot, function () {
@ -261,7 +315,7 @@ function typescript(options) {
return result;
},
outro: function () {
cache.lastDependencySet();
cache.compileDone();
cache.walkTree(function (id) {
var snapshot = servicesHost.getScriptSnapshot(id);
if (!snapshot) {
@ -276,6 +330,7 @@ function typescript(options) {
});
printDiagnostics(diagnostics);
});
cache.diagnosticsDone();
},
};
}

View File

@ -1,16 +1,14 @@
/* eslint-disable */
import { existsSync, readFileSync, writeFileSync } from 'fs';
import * as fs from 'fs';
import { emptyDirSync, ensureFile, existsSync, move, readFileSync, readJsonSync, readdirSync, remove, writeJson } from 'fs-extra';
import * as fs from 'fs-extra';
import { ModuleKind, ScriptSnapshot, createDocumentRegistry, createLanguageService, flattenDiagnosticMessageText, getDefaultLibFilePath, nodeModuleNameResolver, parseConfigFileTextToJson, parseJsonConfigFileContent, sys } from 'typescript';
import * as ts from 'typescript';
import { each, endsWith, find, has, map, some } from 'lodash';
import { each, endsWith, filter, find, has, isEqual, map, some } from 'lodash';
import * as _ from 'lodash';
import { Graph, alg } from 'graphlib';
import * as graph from 'graphlib';
import { sha1 } from 'object-hash';
import * as hash from 'object-hash';
import { sync } from 'mkdirp';
import * as mkdirp from 'mkdirp';
import { createFilter } from 'rollup-pluginutils';
import { dirname, sep } from 'path';
import * as path from 'path';
@ -65,16 +63,61 @@ var LanguageServiceHost = (function () {
return LanguageServiceHost;
}());
var Cache = (function () {
function Cache(cacheRoot, options, rootFilenames) {
var RollingCache = (function () {
function RollingCache(cacheRoot, checkNewCache) {
this.cacheRoot = cacheRoot;
this.checkNewCache = checkNewCache;
this.oldCacheRoot = this.cacheRoot + "/cache";
this.newCacheRoot = this.cacheRoot + "/cache_";
emptyDirSync(this.newCacheRoot);
}
RollingCache.prototype.exists = function (name) {
if (this.checkNewCache && existsSync(this.newCacheRoot + "/" + name))
return true;
return existsSync(this.oldCacheRoot + "/" + name);
};
RollingCache.prototype.match = function (names) {
if (!existsSync(this.oldCacheRoot))
return false;
return isEqual(readdirSync(this.oldCacheRoot).sort(), names.sort());
};
RollingCache.prototype.read = function (name) {
if (this.checkNewCache && existsSync(this.newCacheRoot + "/" + name))
return readJsonSync(this.newCacheRoot + "/" + name, "utf8");
return readJsonSync(this.oldCacheRoot + "/" + name, "utf8");
};
RollingCache.prototype.write = function (name, data) {
if (data === undefined)
return;
writeJson(this.newCacheRoot + "/" + name, data, { encoding: "utf8" }, function () { });
};
RollingCache.prototype.touch = function (name) {
ensureFile(this.newCacheRoot + "/" + name, function () { });
};
RollingCache.prototype.roll = function () {
var _this = this;
remove(this.oldCacheRoot, function () {
move(_this.newCacheRoot, _this.oldCacheRoot, function () { });
});
};
return RollingCache;
}());
var Cache = (function () {
function Cache(host, cache, options, rootFilenames) {
var _this = this;
this.host = host;
this.options = options;
this.cacheVersion = "0";
this.treeComplete = false;
this.cacheRoot = this.cacheRoot + "/" + sha1({ version: this.cacheVersion, rootFilenames: rootFilenames, options: this.options });
sync(this.cacheRoot);
this.cacheVersion = "1";
this.typesDirty = false;
this.cacheDir = cache + "/" + sha1({ version: this.cacheVersion, rootFilenames: rootFilenames, options: this.options });
this.codeCache = new RollingCache(this.cacheDir + "/code", true);
this.typesCache = new RollingCache(this.cacheDir + "/types", false);
this.diagnosticsCache = new RollingCache(this.cacheDir + "/diagnostics", false);
this.dependencyTree = new Graph({ directed: true });
this.dependencyTree.setDefaultNodeLabel(function (_node) { return { dirty: false }; });
this.types = filter(rootFilenames, function (file) { return endsWith(file, ".d.ts"); })
.map(function (id) { return { id: id, snapshot: _this.host.getScriptSnapshot(id) }; });
}
Cache.prototype.walkTree = function (cb) {
var acyclic = alg.isAcyclic(this.dependencyTree);
@ -82,21 +125,55 @@ var Cache = (function () {
each(alg.topsort(this.dependencyTree), function (id) { return cb(id); });
return;
}
// console.log("cycles detected in dependency graph, not sorting");
each(this.dependencyTree.nodes(), function (id) { return cb(id); });
};
Cache.prototype.setDependency = function (importee, importer) {
// console.log(importer, "->", importee);
// importee -> importer
this.dependencyTree.setEdge(importer, importee);
};
Cache.prototype.lastDependencySet = function () {
this.treeComplete = true;
Cache.prototype.compileDone = function () {
var _this = this;
var typeNames = filter(this.types, function (snaphot) { return snaphot.snapshot !== undefined; })
.map(function (snaphot) { return _this.makeName(snaphot.id, snaphot.snapshot); });
// types dirty if any d.ts changed, added or removed
this.typesDirty = !this.typesCache.match(typeNames);
each(typeNames, function (name) { return _this.typesCache.touch(name); });
};
Cache.prototype.diagnosticsDone = function () {
this.codeCache.roll();
this.diagnosticsCache.roll();
this.typesCache.roll();
};
Cache.prototype.getCompiled = function (id, snapshot, transform) {
var name = this.makeName(id, snapshot);
if (!this.codeCache.exists(name) || this.isDirty(id, snapshot, false)) {
console.log("compile cache miss: " + id);
var data_1 = transform();
this.codeCache.write(name, data_1);
this.markAsDirty(id, snapshot);
return data_1;
}
var data = this.codeCache.read(name);
this.codeCache.write(name, data);
return data;
};
Cache.prototype.getDiagnostics = function (id, snapshot, check) {
var name = this.makeName(id, snapshot);
if (!this.diagnosticsCache.exists(name) || this.isDirty(id, snapshot, true)) {
console.log("diag cache miss: " + id);
var data_2 = this.convert(check());
this.diagnosticsCache.write(name, data_2);
this.markAsDirty(id, snapshot);
return data_2;
}
var data = this.diagnosticsCache.read(name);
this.diagnosticsCache.write(name, data);
return data;
};
Cache.prototype.markAsDirty = function (id, _snapshot) {
this.dependencyTree.setNode(id, { dirty: true });
};
// returns true if node or any of its imports changed
// returns true if node or any of its imports or any of global types changed
Cache.prototype.isDirty = function (id, _snapshot, checkImports) {
var _this = this;
var label = this.dependencyTree.node(id);
@ -104,6 +181,8 @@ var Cache = (function () {
return false;
if (!checkImports || label.dirty)
return label.dirty;
if (this.typesDirty)
return true;
var dependencies = alg.dijkstra(this.dependencyTree, id);
return some(dependencies, function (dependency, node) {
if (!node || dependency.distance === Infinity)
@ -111,48 +190,22 @@ var Cache = (function () {
var l = _this.dependencyTree.node(node);
var dirty = l === undefined ? true : l.dirty;
if (dirty)
console.log("dirty: " + id + " -> " + node);
console.log(("dirty: " + id + " -> " + node).gray);
return dirty;
});
};
Cache.prototype.getCompiled = function (id, snapshot, transform) {
var path$$1 = this.makePath(id, snapshot);
if (!existsSync(path$$1) || this.isDirty(id, snapshot, false)) {
// console.log(`compile cache miss: ${id}`);
var data = transform();
this.setCache(path$$1, id, snapshot, data);
return data;
}
return JSON.parse(readFileSync(path$$1, "utf8"));
};
Cache.prototype.getDiagnostics = function (id, snapshot, check) {
var path$$1 = this.makePath(id, snapshot) + ".diagnostics";
if (!existsSync(path$$1) || this.isDirty(id, snapshot, true)) {
// console.log(`diagnostics cache miss: ${id}`);
var data = this.convert(check());
this.setCache(path$$1, id, snapshot, data);
return data;
}
return JSON.parse(readFileSync(path$$1, "utf8"));
};
Cache.prototype.setCache = function (path$$1, id, snapshot, data) {
if (data === undefined)
return;
writeFileSync(path$$1, JSON.stringify(data));
this.markAsDirty(id, snapshot);
};
Cache.prototype.makePath = function (id, snapshot) {
Cache.prototype.makeName = function (id, snapshot) {
var data = snapshot.getText(0, snapshot.getLength());
return this.cacheRoot + "/" + sha1({ data: data, id: id });
return sha1({ data: data, id: id });
};
Cache.prototype.convert = function (data) {
return map(data, function (diagnostic) {
var entry = {
flatMessage: flattenDiagnosticMessageText(diagnostic.messageText, "\n"),
flatMessage: flattenDiagnosticMessageText(diagnostic.messageText, "\n").yellow,
};
if (diagnostic.file) {
var _a = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start), line = _a.line, character = _a.character;
entry.fileLine = diagnostic.file.fileName + " (" + (line + 1) + "," + (character + 1) + ")";
entry.fileLine = (diagnostic.file.fileName + " (" + (line + 1) + "," + (character + 1) + ")").white;
}
return entry;
});
@ -220,11 +273,11 @@ function printDiagnostics(diagnostics) {
function typescript(options) {
options = __assign({}, options);
var filter = createFilter(options.include || ["*.ts+(|x)", "**/*.ts+(|x)"], options.exclude || ["*.d.ts", "**/*.d.ts"]);
var filter$$1 = createFilter(options.include || ["*.ts+(|x)", "**/*.ts+(|x)"], options.exclude || ["*.d.ts", "**/*.d.ts"]);
var parsedConfig = parseTsConfig();
var servicesHost = new LanguageServiceHost(parsedConfig);
var services = createLanguageService(servicesHost, createDocumentRegistry());
var cache = new Cache(process.cwd() + "/.rts2_cache", parsedConfig.options, parsedConfig.fileNames);
var cache = new Cache(servicesHost, process.cwd() + "/.rts2_cache", parsedConfig.options, parsedConfig.fileNames);
return {
resolveId: function (importee, importer) {
if (importee === TSLIB)
@ -234,10 +287,10 @@ function typescript(options) {
importer = importer.split("\\").join("/");
var result = nodeModuleNameResolver(importee, importer, parsedConfig.options, sys);
if (result.resolvedModule && result.resolvedModule.resolvedFileName) {
if (filter$$1(result.resolvedModule.resolvedFileName))
cache.setDependency(result.resolvedModule.resolvedFileName, importer);
if (endsWith(result.resolvedModule.resolvedFileName, ".d.ts"))
return null;
if (filter(result.resolvedModule.resolvedFileName))
cache.setDependency(result.resolvedModule.resolvedFileName, importer);
return result.resolvedModule.resolvedFileName;
}
return null;
@ -249,7 +302,7 @@ function typescript(options) {
},
transform: function (code, id) {
var _this = this;
if (!filter(id))
if (!filter$$1(id))
return undefined;
var snapshot = servicesHost.setSnapshot(id, code);
var result = cache.getCompiled(id, snapshot, function () {
@ -266,7 +319,7 @@ function typescript(options) {
return result;
},
outro: function () {
cache.lastDependencySet();
cache.compileDone();
cache.walkTree(function (id) {
var snapshot = servicesHost.getScriptSnapshot(id);
if (!snapshot) {
@ -281,6 +334,7 @@ function typescript(options) {
});
printDiagnostics(diagnostics);
});
cache.diagnosticsDone();
},
};
}

View File

@ -24,9 +24,10 @@
"postinstall": "typings install"
},
"dependencies": {
"colors": "^1.1.2",
"fs-extra": "^2.0.0",
"graphlib": "^2.1.1",
"lodash": "^4.17.4",
"mkdirp": "^0.5.1",
"object-hash": "^1.1.5",
"rollup-pluginutils": "^2.0.1",
"tslib": "^1.5.0"
@ -36,9 +37,10 @@
},
"devDependencies": {
"@alexlur/rollup-plugin-typescript": "^0.8.1",
"@types/colors": "^1.1.1",
"@types/fs-extra": "0.0.37",
"@types/graphlib": "^2.1.3",
"@types/lodash": "^4.14.52",
"@types/mkdirp": "^0.3.29",
"@types/node": "^6.0.53",
"@types/object-hash": "^0.5.28",
"rimraf": "^2.5.4",

View File

@ -7,14 +7,13 @@ export default {
external: [
'path',
'fs',
'fs-extra',
'object-assign',
'rollup-pluginutils',
'typescript',
'lodash',
'graphlib',
'object-hash',
'mkdirp'
'object-hash'
],
plugins: [

View File

@ -1,9 +1,8 @@
import * as ts from "typescript";
import * as graph from "graphlib";
import * as hash from "object-hash";
import * as fs from "fs";
import * as _ from "lodash";
import * as mkdirp from "mkdirp";
import { RollingCache } from "./rollingcache";
export interface ICode
{
@ -14,7 +13,6 @@ export interface ICode
interface INodeLabel
{
dirty: boolean;
hash?: string;
}
export interface IDiagnostics
@ -23,20 +21,37 @@ export interface IDiagnostics
fileLine?: string;
}
interface ITypeSnapshot
{
id: string;
snapshot: ts.IScriptSnapshot | undefined;
}
export class Cache
{
private cacheVersion = "0";
private cacheVersion = "1";
private dependencyTree: graph.Graph;
private treeComplete: boolean = false;
private types: ITypeSnapshot[];
private typesDirty = false;
private cacheDir: string;
private codeCache: RollingCache<ICode | undefined>;
private typesCache: RollingCache<string>;
private diagnosticsCache: RollingCache<IDiagnostics[]>;
constructor(private cacheRoot: string, private options: ts.CompilerOptions, rootFilenames: string[])
constructor(private host: ts.LanguageServiceHost, cache: string, private options: ts.CompilerOptions, rootFilenames: string[])
{
this.cacheRoot = `${this.cacheRoot}/${hash.sha1({ version: this.cacheVersion, rootFilenames, options: this.options })}`;
this.cacheDir = `${cache}/${hash.sha1({ version: this.cacheVersion, rootFilenames, options: this.options })}`;
mkdirp.sync(this.cacheRoot);
this.codeCache = new RollingCache<ICode>(`${this.cacheDir}/code`, true);
this.typesCache = new RollingCache<string>(`${this.cacheDir}/types`, false);
this.diagnosticsCache = new RollingCache<IDiagnostics[]>(`${this.cacheDir}/diagnostics`, false);
this.dependencyTree = new graph.Graph({ directed: true });
this.dependencyTree.setDefaultNodeLabel((_node: string) => { return { dirty: false }; });
this.types = _
.filter(rootFilenames, (file) => _.endsWith(file, ".d.ts"))
.map((id) => { return { id, snapshot: this.host.getScriptSnapshot(id) }; });
}
public walkTree(cb: (id: string) => void | false): void
@ -49,29 +64,77 @@ export class Cache
return;
}
// console.log("cycles detected in dependency graph, not sorting");
_.each(this.dependencyTree.nodes(), (id: string) => cb(id));
}
public setDependency(importee: string, importer: string): void
{
// console.log(importer, "->", importee);
// importee -> importer
this.dependencyTree.setEdge(importer, importee);
}
public lastDependencySet(): void
public compileDone(): void
{
this.treeComplete = true;
let typeNames = _
.filter(this.types, (snaphot) => snaphot.snapshot !== undefined)
.map((snaphot) => this.makeName(snaphot.id, snaphot.snapshot!));
// types dirty if any d.ts changed, added or removed
this.typesDirty = !this.typesCache.match(typeNames);
_.each(typeNames, (name) => this.typesCache.touch(name));
}
public markAsDirty(id: string, _snapshot: ts.IScriptSnapshot): void
public diagnosticsDone()
{
this.codeCache.roll();
this.diagnosticsCache.roll();
this.typesCache.roll();
}
public getCompiled(id: string, snapshot: ts.IScriptSnapshot, transform: () => ICode | undefined): ICode | undefined
{
let name = this.makeName(id, snapshot);
if (!this.codeCache.exists(name) || this.isDirty(id, snapshot, false))
{
console.log(`compile cache miss: ${id}`);
let data = transform();
this.codeCache.write(name, data);
this.markAsDirty(id, snapshot);
return data;
}
let data = this.codeCache.read(name);
this.codeCache.write(name, data);
return data;
}
public getDiagnostics(id: string, snapshot: ts.IScriptSnapshot, check: () => ts.Diagnostic[]): IDiagnostics[]
{
let name = this.makeName(id, snapshot);
if (!this.diagnosticsCache.exists(name) || this.isDirty(id, snapshot, true))
{
console.log(`diag cache miss: ${id}`);
let data = this.convert(check());
this.diagnosticsCache.write(name, data);
this.markAsDirty(id, snapshot);
return data;
}
let data = this.diagnosticsCache.read(name);
this.diagnosticsCache.write(name, data);
return data;
}
private markAsDirty(id: string, _snapshot: ts.IScriptSnapshot): void
{
this.dependencyTree.setNode(id, { dirty: true });
}
// returns true if node or any of its imports changed
public isDirty(id: string, _snapshot: ts.IScriptSnapshot, checkImports: boolean): boolean
// returns true if node or any of its imports or any of global types changed
private isDirty(id: string, _snapshot: ts.IScriptSnapshot, checkImports: boolean): boolean
{
let label = this.dependencyTree.node(id) as INodeLabel;
@ -81,6 +144,9 @@ export class Cache
if (!checkImports || label.dirty)
return label.dirty;
if (this.typesDirty)
return true;
let dependencies = graph.alg.dijkstra(this.dependencyTree, id);
return _.some(dependencies, (dependency, node) =>
@ -92,56 +158,16 @@ export class Cache
let dirty = l === undefined ? true : l.dirty;
if (dirty)
console.log(`dirty: ${id} -> ${node}`);
console.log(`dirty: ${id} -> ${node}`.gray);
return dirty;
});
}
public getCompiled(id: string, snapshot: ts.IScriptSnapshot, transform: () => ICode | undefined): ICode | undefined
{
let path = this.makePath(id, snapshot);
if (!fs.existsSync(path) || this.isDirty(id, snapshot, false))
{
// console.log(`compile cache miss: ${id}`);
let data = transform();
this.setCache(path, id, snapshot, data);
return data;
}
return JSON.parse(fs.readFileSync(path, "utf8")) as ICode;
}
public getDiagnostics(id: string, snapshot: ts.IScriptSnapshot, check: () => ts.Diagnostic[]): IDiagnostics[]
{
let path = `${this.makePath(id, snapshot)}.diagnostics`;
if (!fs.existsSync(path) || this.isDirty(id, snapshot, true))
{
// console.log(`diagnostics cache miss: ${id}`);
let data = this.convert(check());
this.setCache(path, id, snapshot, data);
return data;
}
return JSON.parse(fs.readFileSync(path, "utf8")) as IDiagnostics[];
}
private setCache(path: string, id: string, snapshot: ts.IScriptSnapshot, data: IDiagnostics[] | ICode | undefined): void
{
if (data === undefined)
return;
fs.writeFileSync(path, JSON.stringify(data));
this.markAsDirty(id, snapshot);
}
private makePath(id: string, snapshot: ts.IScriptSnapshot): string
private makeName(id: string, snapshot: ts.IScriptSnapshot)
{
let data = snapshot.getText(0, snapshot.getLength());
return `${this.cacheRoot}/${hash.sha1({ data, id })}`;
return hash.sha1({ data, id });
}
private convert(data: ts.Diagnostic[]): IDiagnostics[]
@ -150,13 +176,13 @@ export class Cache
{
let entry: IDiagnostics =
{
flatMessage: ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n"),
flatMessage: ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n").yellow,
};
if (diagnostic.file)
{
let { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start);
entry.fileLine = `${diagnostic.file.fileName} (${line + 1},${character + 1})`;
entry.fileLine = `${diagnostic.file.fileName} (${line + 1},${character + 1})`.white;
}
return entry;

View File

@ -1,4 +1,4 @@
import * as fs from "fs";
import * as fs from "fs-extra";
import * as ts from "typescript";
import * as _ from "lodash";

View File

@ -2,10 +2,10 @@ import { LanguageServiceHost } from "./host";
import { Cache, ICode, IDiagnostics } from "./cache";
import * as ts from "typescript";
import { createFilter } from "rollup-pluginutils";
import * as fs from "fs";
import * as fs from "fs-extra";
import * as path from "path";
import { existsSync } from "fs";
import * as _ from "lodash";
import * as colors from "colors";
function getOptionsOverrides(): ts.CompilerOptions
{
@ -25,7 +25,7 @@ function findFile(cwd: string, filename: string)
{
let fp = cwd ? (cwd + "/" + filename) : filename;
if (existsSync(fp))
if (fs.existsSync(fp))
{
return fp;
}
@ -37,7 +37,7 @@ function findFile(cwd: string, filename: string)
{
cwd = segs.slice(0, len).join("/");
fp = cwd + "/" + filename;
if (existsSync(fp))
if (fs.existsSync(fp))
{
return fp;
}
@ -111,7 +111,7 @@ export default function typescript (options: IOptions)
const services = ts.createLanguageService(servicesHost, ts.createDocumentRegistry());
const cache = new Cache(`${process.cwd()}/.rts2_cache`, parsedConfig.options, parsedConfig.fileNames);
const cache = new Cache(servicesHost, `${process.cwd()}/.rts2_cache`, parsedConfig.options, parsedConfig.fileNames);
return {
@ -129,12 +129,12 @@ export default function typescript (options: IOptions)
if (result.resolvedModule && result.resolvedModule.resolvedFileName)
{
if (_.endsWith(result.resolvedModule.resolvedFileName, ".d.ts"))
return null;
if (filter(result.resolvedModule.resolvedFileName))
cache.setDependency(result.resolvedModule.resolvedFileName, importer);
if (_.endsWith(result.resolvedModule.resolvedFileName, ".d.ts"))
return null;
return result.resolvedModule.resolvedFileName;
}
@ -176,7 +176,7 @@ export default function typescript (options: IOptions)
outro(): void
{
cache.lastDependencySet();
cache.compileDone();
cache.walkTree((id: string) =>
{
@ -198,6 +198,8 @@ export default function typescript (options: IOptions)
printDiagnostics(diagnostics);
});
cache.diagnosticsDone();
},
};
}

61
src/rollingcache.ts Normal file
View File

@ -0,0 +1,61 @@
import * as fs from "fs-extra";
import * as _ from "lodash";
export class RollingCache <DataType>
{
private oldCacheRoot: string;
private newCacheRoot: string;
constructor(private cacheRoot: string, private checkNewCache: boolean)
{
this.oldCacheRoot = `${this.cacheRoot}/cache`;
this.newCacheRoot = `${this.cacheRoot}/cache_`;
fs.emptyDirSync(this.newCacheRoot);
}
public exists(name: string): boolean
{
if (this.checkNewCache && fs.existsSync(`${this.newCacheRoot}/${name}`))
return true;
return fs.existsSync(`${this.oldCacheRoot}/${name}`);
}
public match(names: string[]): boolean
{
if (!fs.existsSync(this.oldCacheRoot))
return false;
return _.isEqual(fs.readdirSync(this.oldCacheRoot).sort(), names.sort());
}
public read(name: string): DataType
{
if (this.checkNewCache && fs.existsSync(`${this.newCacheRoot}/${name}`))
return fs.readJsonSync(`${this.newCacheRoot}/${name}`, "utf8");
return fs.readJsonSync(`${this.oldCacheRoot}/${name}`, "utf8");
}
public write(name: string, data: DataType): void
{
if (data === undefined)
return;
fs.writeJson(`${this.newCacheRoot}/${name}`, data, { encoding: "utf8" }, () => {});
}
public touch(name: string)
{
fs.ensureFile(`${this.newCacheRoot}/${name}`, () => {});
}
public roll()
{
fs.remove(this.oldCacheRoot, () =>
{
fs.move(this.newCacheRoot, this.oldCacheRoot, () => {} );
});
}
}