* - partial fix for watch mode (#6)

* - trying to detect watch mode

* - support for watch mode
This commit is contained in:
Eugene Zolenko 2017-03-14 19:04:59 -06:00 committed by GitHub
parent 1dbe1cca94
commit 96b284635f
11 changed files with 329 additions and 142 deletions

2
.gitignore vendored
View File

@ -1,4 +1,4 @@
/node_modules
/npm-debug.log
/typings
/.rts2_cache
/.rpt2_cache

View File

@ -70,6 +70,12 @@ Plugin takes following options:
On windows typescript resolver favors POSIX path, while commonjs plugin (and maybe others?) uses native path as module id. This can result in `namedExports` being ignored if rollup happened to use typescript's resolution. Set to true to pass resolved module path through `resolve()` to match up with `rollup-plugin-commonjs`.
### Watch mode
The way typescript handles type-only imports and ambient types effectively hides them from rollup watch, because import statements are not generated and changing them doesn't trigger a rebuild.
Otherwise the plugin should work in watch mode. Make sure to run a normal build after watch session to catch any type errors.
### Version
This plugin currently requires TypeScript `2.0+`.

View File

@ -113,6 +113,10 @@ var LanguageServiceHost = (function () {
this.snapshots = {};
this.versions = {};
}
LanguageServiceHost.prototype.reset = function () {
this.snapshots = {};
this.versions = {};
};
LanguageServiceHost.prototype.setSnapshot = function (fileName, data) {
var snapshot = ts.ScriptSnapshot.fromString(data);
this.snapshots[fileName] = snapshot;
@ -131,8 +135,8 @@ var LanguageServiceHost = (function () {
LanguageServiceHost.prototype.getCurrentDirectory = function () {
return this.cwd;
};
LanguageServiceHost.prototype.getScriptVersion = function (_fileName) {
return (this.versions[_fileName] || 0).toString();
LanguageServiceHost.prototype.getScriptVersion = function (fileName) {
return (this.versions[fileName] || 0).toString();
};
LanguageServiceHost.prototype.getScriptFileNames = function () {
return this.parsedConfig.fileNames;
@ -158,6 +162,7 @@ var RollingCache = (function () {
function RollingCache(cacheRoot, checkNewCache) {
this.cacheRoot = cacheRoot;
this.checkNewCache = checkNewCache;
this.rolled = false;
this.oldCacheRoot = this.cacheRoot + "/cache";
this.newCacheRoot = this.cacheRoot + "/cache_";
fs.emptyDirSync(this.newCacheRoot);
@ -166,6 +171,8 @@ var RollingCache = (function () {
* @returns true if name exist in old cache (or either old of new cache if checkNewCache is true)
*/
RollingCache.prototype.exists = function (name) {
if (this.rolled)
return false;
if (this.checkNewCache && fs.existsSync(this.newCacheRoot + "/" + name))
return true;
return fs.existsSync(this.oldCacheRoot + "/" + name);
@ -177,6 +184,8 @@ var RollingCache = (function () {
* @returns true if old cache contains all names and nothing more
*/
RollingCache.prototype.match = function (names) {
if (this.rolled)
return false;
if (!fs.existsSync(this.oldCacheRoot))
return names.length === 0; // empty folder matches
return _.isEqual(fs.readdirSync(this.oldCacheRoot).sort(), names.sort());
@ -190,27 +199,29 @@ var RollingCache = (function () {
return fs.readJsonSync(this.oldCacheRoot + "/" + name, "utf8");
};
RollingCache.prototype.write = function (name, data) {
if (this.rolled)
return;
if (data === undefined)
return;
if (this.checkNewCache)
fs.writeJsonSync(this.newCacheRoot + "/" + name, data);
if (this.rolled)
fs.writeJsonSync(this.oldCacheRoot + "/" + name, data);
else
fs.writeJson(this.newCacheRoot + "/" + name, data, { encoding: "utf8" }, function () { });
fs.writeJsonSync(this.newCacheRoot + "/" + name, data);
};
RollingCache.prototype.touch = function (name) {
if (this.checkNewCache)
fs.ensureFileSync(this.newCacheRoot + "/" + name);
else
fs.ensureFile(this.newCacheRoot + "/" + name, function () { });
if (this.rolled)
return;
fs.ensureFileSync(this.newCacheRoot + "/" + name);
};
/**
* clears old cache and moves new in its place
*/
RollingCache.prototype.roll = function () {
var _this = this;
fs.remove(this.oldCacheRoot, function () {
fs.move(_this.newCacheRoot, _this.oldCacheRoot, function () { });
});
if (this.rolled)
return;
this.rolled = true;
fs.removeSync(this.oldCacheRoot);
fs.move(this.newCacheRoot, this.oldCacheRoot, function () { });
};
return RollingCache;
}());
@ -228,13 +239,13 @@ function convertDiagnostic(data) {
return entry;
});
}
var Cache = (function () {
function Cache(host, cache, options, rootFilenames, context) {
var TsCache = (function () {
function TsCache(host, cache, options, rootFilenames, context) {
var _this = this;
this.host = host;
this.options = options;
this.context = context;
this.cacheVersion = "2";
this.cacheVersion = "3";
this.ambientTypesDirty = false;
this.cacheDir = cache + "/" + hash.sha1({
version: this.cacheVersion,
@ -253,25 +264,34 @@ var Cache = (function () {
this.init();
this.checkAmbientTypes();
}
Cache.prototype.clean = function () {
TsCache.prototype.clean = function () {
this.context.info(colors.blue("cleaning cache: " + this.cacheDir));
fs.emptyDirSync(this.cacheDir);
this.init();
};
Cache.prototype.setDependency = function (importee, importer) {
TsCache.prototype.setDependency = function (importee, importer) {
// importee -> importer
this.context.debug(colors.blue("dependency") + " '" + importee + "'");
this.context.debug(" imported by '" + importer + "'");
this.dependencyTree.setEdge(importer, importee);
};
Cache.prototype.done = function () {
TsCache.prototype.walkTree = function (cb) {
var acyclic = graph.alg.isAcyclic(this.dependencyTree);
if (acyclic) {
_.each(graph.alg.topsort(this.dependencyTree), function (id) { return cb(id); });
return;
}
this.context.info(colors.yellow("import tree has cycles"));
_.each(this.dependencyTree.nodes(), function (id) { return cb(id); });
};
TsCache.prototype.done = function () {
this.context.info(colors.blue("rolling caches"));
this.codeCache.roll();
this.semanticDiagnosticsCache.roll();
this.syntacticDiagnosticsCache.roll();
this.typesCache.roll();
};
Cache.prototype.getCompiled = function (id, snapshot, transform) {
TsCache.prototype.getCompiled = function (id, snapshot, transform) {
var name = this.makeName(id, snapshot);
this.context.info(colors.blue("transpiling") + " '" + id + "'");
this.context.debug(" cache: '" + this.codeCache.path(name) + "'");
@ -287,13 +307,13 @@ var Cache = (function () {
this.codeCache.write(name, data);
return data;
};
Cache.prototype.getSyntacticDiagnostics = function (id, snapshot, check) {
TsCache.prototype.getSyntacticDiagnostics = function (id, snapshot, check) {
return this.getDiagnostics(this.syntacticDiagnosticsCache, id, snapshot, check);
};
Cache.prototype.getSemanticDiagnostics = function (id, snapshot, check) {
TsCache.prototype.getSemanticDiagnostics = function (id, snapshot, check) {
return this.getDiagnostics(this.semanticDiagnosticsCache, id, snapshot, check);
};
Cache.prototype.checkAmbientTypes = function () {
TsCache.prototype.checkAmbientTypes = function () {
var _this = this;
this.context.debug(colors.blue("Ambient types:"));
var typeNames = _.filter(this.ambientTypes, function (snapshot) { return snapshot.snapshot !== undefined; })
@ -307,7 +327,7 @@ var Cache = (function () {
this.context.info(colors.yellow("ambient types changed, redoing all semantic diagnostics"));
_.each(typeNames, function (name) { return _this.typesCache.touch(name); });
};
Cache.prototype.getDiagnostics = function (cache, id, snapshot, check) {
TsCache.prototype.getDiagnostics = function (cache, id, snapshot, check) {
var name = this.makeName(id, snapshot);
this.context.debug(" cache: '" + cache.path(name) + "'");
if (!cache.exists(name) || this.isDirty(id, snapshot, true)) {
@ -322,17 +342,17 @@ var Cache = (function () {
cache.write(name, data);
return data;
};
Cache.prototype.init = function () {
TsCache.prototype.init = function () {
this.codeCache = new RollingCache(this.cacheDir + "/code", true);
this.typesCache = new RollingCache(this.cacheDir + "/types", false);
this.syntacticDiagnosticsCache = new RollingCache(this.cacheDir + "/syntacticDiagnostics", false);
this.semanticDiagnosticsCache = new RollingCache(this.cacheDir + "/semanticDiagnostics", false);
this.typesCache = new RollingCache(this.cacheDir + "/types", true);
this.syntacticDiagnosticsCache = new RollingCache(this.cacheDir + "/syntacticDiagnostics", true);
this.semanticDiagnosticsCache = new RollingCache(this.cacheDir + "/semanticDiagnostics", true);
};
Cache.prototype.markAsDirty = function (id, _snapshot) {
TsCache.prototype.markAsDirty = function (id, _snapshot) {
this.dependencyTree.setNode(id, { dirty: true });
};
// returns true if node or any of its imports or any of global types changed
Cache.prototype.isDirty = function (id, _snapshot, checkImports) {
TsCache.prototype.isDirty = function (id, _snapshot, checkImports) {
var _this = this;
var label = this.dependencyTree.node(id);
if (!label)
@ -352,11 +372,11 @@ var Cache = (function () {
return dirty;
});
};
Cache.prototype.makeName = function (id, snapshot) {
TsCache.prototype.makeName = function (id, snapshot) {
var data = snapshot.getText(0, snapshot.getLength());
return hash.sha1({ data: data, id: id });
};
return Cache;
return TsCache;
}());
// tslint:disable-next-line:no-var-requires
@ -432,20 +452,23 @@ function typescript(options) {
abortOnError: true,
rollupCommonJSResolveHack: false,
});
var watchMode = false;
var round = 0;
var targetCount = 0;
var context = new ConsoleContext(options.verbosity, "rpt2: ");
context.info("Typescript version: " + ts.version);
context.debug("Options: " + JSON.stringify(options, undefined, 4));
var filter$$1 = createFilter(options.include, options.exclude);
var parsedConfig = parseTsConfig(context);
var servicesHost = new LanguageServiceHost(parsedConfig);
var services = ts.createLanguageService(servicesHost, ts.createDocumentRegistry());
var cache = new Cache(servicesHost, options.cacheRoot, parsedConfig.options, parsedConfig.fileNames, context);
var cleanTranspile = true;
var service = ts.createLanguageService(servicesHost, ts.createDocumentRegistry());
var cache = new TsCache(servicesHost, options.cacheRoot, parsedConfig.options, parsedConfig.fileNames, context);
var noErrors = true;
if (options.clean)
cache.clean();
// printing compiler option errors
if (options.check)
printDiagnostics(context, convertDiagnostic(services.getCompilerOptionsDiagnostics()));
printDiagnostics(context, convertDiagnostic(service.getCompilerOptionsDiagnostics()));
return {
resolveId: function (importee, importer) {
if (importee === TSLIB)
@ -481,14 +504,14 @@ function typescript(options) {
var snapshot = servicesHost.setSnapshot(id, code);
// getting compiled file from cache or from ts
var result = cache.getCompiled(id, snapshot, function () {
var output = services.getEmitOutput(id);
var output = service.getEmitOutput(id);
if (output.emitSkipped) {
cleanTranspile = false;
noErrors = false;
// always checking on fatal errors, even if options.check is set to false
var diagnostics = cache.getSyntacticDiagnostics(id, snapshot, function () {
return services.getSyntacticDiagnostics(id);
return service.getSyntacticDiagnostics(id);
}).concat(cache.getSemanticDiagnostics(id, snapshot, function () {
return services.getSemanticDiagnostics(id);
return service.getSemanticDiagnostics(id);
}));
printDiagnostics(contextWrapper, diagnostics);
// since no output was generated, aborting compilation
@ -503,20 +526,42 @@ function typescript(options) {
});
if (options.check) {
var diagnostics = cache.getSyntacticDiagnostics(id, snapshot, function () {
return services.getSyntacticDiagnostics(id);
return service.getSyntacticDiagnostics(id);
}).concat(cache.getSemanticDiagnostics(id, snapshot, function () {
return services.getSemanticDiagnostics(id);
return service.getSemanticDiagnostics(id);
}));
if (diagnostics.length !== 0)
cleanTranspile = false;
noErrors = false;
printDiagnostics(contextWrapper, diagnostics);
}
return result;
},
ongenerate: function () {
cache.done();
if (!cleanTranspile)
ongenerate: function (bundleOptions) {
if (_.isArray(bundleOptions.targets))
targetCount = bundleOptions.targets.length;
if (round >= targetCount) {
watchMode = true;
round = 0;
}
context.debug("generating target " + round + " of " + bundleOptions.targets.length);
if (watchMode && round === 0) {
context.debug("running in watch mode");
// hack to fix ts lagging
servicesHost.reset();
service.cleanupSemanticCache();
cache.walkTree(function (id) {
var diagnostics = convertDiagnostic(service.getSyntacticDiagnostics(id)).concat(convertDiagnostic(service.getSemanticDiagnostics(id)));
if (diagnostics.length > 0)
noErrors = false;
printDiagnostics(context, diagnostics);
});
}
if (!noErrors) {
noErrors = true;
context.info(colors.yellow("there were errors or warnings above."));
}
cache.done();
round++;
},
};
}

View File

@ -1,9 +1,9 @@
/* eslint-disable */
import { emptyDirSync, ensureFile, ensureFileSync, existsSync, move, readFileSync, readJsonSync, readdirSync, remove, writeJson, writeJsonSync } from 'fs-extra';
import { emptyDirSync, ensureFileSync, existsSync, move, readFileSync, readJsonSync, readdirSync, removeSync, writeJsonSync } from 'fs-extra';
import * as fs from 'fs-extra';
import { DiagnosticCategory, ModuleKind, ScriptSnapshot, createDocumentRegistry, createLanguageService, findConfigFile, flattenDiagnosticMessageText, getAutomaticTypeDirectiveNames, getDefaultLibFilePath, nodeModuleNameResolver, parseConfigFileTextToJson, parseJsonConfigFileContent, resolveTypeReferenceDirective, sys, version } from 'typescript';
import * as ts from 'typescript';
import { defaults, each, endsWith, filter, find, has, isEqual, map, some } from 'lodash';
import { defaults, each, endsWith, filter, find, has, isArray, isEqual, map, some } from 'lodash';
import * as _ from 'lodash';
import { Graph, alg } from 'graphlib';
import * as graph from 'graphlib';
@ -119,6 +119,10 @@ var LanguageServiceHost = (function () {
this.snapshots = {};
this.versions = {};
}
LanguageServiceHost.prototype.reset = function () {
this.snapshots = {};
this.versions = {};
};
LanguageServiceHost.prototype.setSnapshot = function (fileName, data) {
var snapshot = ScriptSnapshot.fromString(data);
this.snapshots[fileName] = snapshot;
@ -137,8 +141,8 @@ var LanguageServiceHost = (function () {
LanguageServiceHost.prototype.getCurrentDirectory = function () {
return this.cwd;
};
LanguageServiceHost.prototype.getScriptVersion = function (_fileName) {
return (this.versions[_fileName] || 0).toString();
LanguageServiceHost.prototype.getScriptVersion = function (fileName) {
return (this.versions[fileName] || 0).toString();
};
LanguageServiceHost.prototype.getScriptFileNames = function () {
return this.parsedConfig.fileNames;
@ -164,6 +168,7 @@ var RollingCache = (function () {
function RollingCache(cacheRoot, checkNewCache) {
this.cacheRoot = cacheRoot;
this.checkNewCache = checkNewCache;
this.rolled = false;
this.oldCacheRoot = this.cacheRoot + "/cache";
this.newCacheRoot = this.cacheRoot + "/cache_";
emptyDirSync(this.newCacheRoot);
@ -172,6 +177,8 @@ var RollingCache = (function () {
* @returns true if name exist in old cache (or either old of new cache if checkNewCache is true)
*/
RollingCache.prototype.exists = function (name) {
if (this.rolled)
return false;
if (this.checkNewCache && existsSync(this.newCacheRoot + "/" + name))
return true;
return existsSync(this.oldCacheRoot + "/" + name);
@ -183,6 +190,8 @@ var RollingCache = (function () {
* @returns true if old cache contains all names and nothing more
*/
RollingCache.prototype.match = function (names) {
if (this.rolled)
return false;
if (!existsSync(this.oldCacheRoot))
return names.length === 0; // empty folder matches
return isEqual(readdirSync(this.oldCacheRoot).sort(), names.sort());
@ -196,27 +205,29 @@ var RollingCache = (function () {
return readJsonSync(this.oldCacheRoot + "/" + name, "utf8");
};
RollingCache.prototype.write = function (name, data) {
if (this.rolled)
return;
if (data === undefined)
return;
if (this.checkNewCache)
writeJsonSync(this.newCacheRoot + "/" + name, data);
if (this.rolled)
writeJsonSync(this.oldCacheRoot + "/" + name, data);
else
writeJson(this.newCacheRoot + "/" + name, data, { encoding: "utf8" }, function () { });
writeJsonSync(this.newCacheRoot + "/" + name, data);
};
RollingCache.prototype.touch = function (name) {
if (this.checkNewCache)
ensureFileSync(this.newCacheRoot + "/" + name);
else
ensureFile(this.newCacheRoot + "/" + name, function () { });
if (this.rolled)
return;
ensureFileSync(this.newCacheRoot + "/" + name);
};
/**
* clears old cache and moves new in its place
*/
RollingCache.prototype.roll = function () {
var _this = this;
remove(this.oldCacheRoot, function () {
move(_this.newCacheRoot, _this.oldCacheRoot, function () { });
});
if (this.rolled)
return;
this.rolled = true;
removeSync(this.oldCacheRoot);
move(this.newCacheRoot, this.oldCacheRoot, function () { });
};
return RollingCache;
}());
@ -234,13 +245,13 @@ function convertDiagnostic(data) {
return entry;
});
}
var Cache = (function () {
function Cache(host, cache, options, rootFilenames, context) {
var TsCache = (function () {
function TsCache(host, cache, options, rootFilenames, context) {
var _this = this;
this.host = host;
this.options = options;
this.context = context;
this.cacheVersion = "2";
this.cacheVersion = "3";
this.ambientTypesDirty = false;
this.cacheDir = cache + "/" + sha1({
version: this.cacheVersion,
@ -259,25 +270,34 @@ var Cache = (function () {
this.init();
this.checkAmbientTypes();
}
Cache.prototype.clean = function () {
TsCache.prototype.clean = function () {
this.context.info(blue("cleaning cache: " + this.cacheDir));
emptyDirSync(this.cacheDir);
this.init();
};
Cache.prototype.setDependency = function (importee, importer) {
TsCache.prototype.setDependency = function (importee, importer) {
// importee -> importer
this.context.debug(blue("dependency") + " '" + importee + "'");
this.context.debug(" imported by '" + importer + "'");
this.dependencyTree.setEdge(importer, importee);
};
Cache.prototype.done = function () {
TsCache.prototype.walkTree = function (cb) {
var acyclic = alg.isAcyclic(this.dependencyTree);
if (acyclic) {
each(alg.topsort(this.dependencyTree), function (id) { return cb(id); });
return;
}
this.context.info(yellow("import tree has cycles"));
each(this.dependencyTree.nodes(), function (id) { return cb(id); });
};
TsCache.prototype.done = function () {
this.context.info(blue("rolling caches"));
this.codeCache.roll();
this.semanticDiagnosticsCache.roll();
this.syntacticDiagnosticsCache.roll();
this.typesCache.roll();
};
Cache.prototype.getCompiled = function (id, snapshot, transform) {
TsCache.prototype.getCompiled = function (id, snapshot, transform) {
var name = this.makeName(id, snapshot);
this.context.info(blue("transpiling") + " '" + id + "'");
this.context.debug(" cache: '" + this.codeCache.path(name) + "'");
@ -293,13 +313,13 @@ var Cache = (function () {
this.codeCache.write(name, data);
return data;
};
Cache.prototype.getSyntacticDiagnostics = function (id, snapshot, check) {
TsCache.prototype.getSyntacticDiagnostics = function (id, snapshot, check) {
return this.getDiagnostics(this.syntacticDiagnosticsCache, id, snapshot, check);
};
Cache.prototype.getSemanticDiagnostics = function (id, snapshot, check) {
TsCache.prototype.getSemanticDiagnostics = function (id, snapshot, check) {
return this.getDiagnostics(this.semanticDiagnosticsCache, id, snapshot, check);
};
Cache.prototype.checkAmbientTypes = function () {
TsCache.prototype.checkAmbientTypes = function () {
var _this = this;
this.context.debug(blue("Ambient types:"));
var typeNames = filter(this.ambientTypes, function (snapshot) { return snapshot.snapshot !== undefined; })
@ -313,7 +333,7 @@ var Cache = (function () {
this.context.info(yellow("ambient types changed, redoing all semantic diagnostics"));
each(typeNames, function (name) { return _this.typesCache.touch(name); });
};
Cache.prototype.getDiagnostics = function (cache, id, snapshot, check) {
TsCache.prototype.getDiagnostics = function (cache, id, snapshot, check) {
var name = this.makeName(id, snapshot);
this.context.debug(" cache: '" + cache.path(name) + "'");
if (!cache.exists(name) || this.isDirty(id, snapshot, true)) {
@ -328,17 +348,17 @@ var Cache = (function () {
cache.write(name, data);
return data;
};
Cache.prototype.init = function () {
TsCache.prototype.init = function () {
this.codeCache = new RollingCache(this.cacheDir + "/code", true);
this.typesCache = new RollingCache(this.cacheDir + "/types", false);
this.syntacticDiagnosticsCache = new RollingCache(this.cacheDir + "/syntacticDiagnostics", false);
this.semanticDiagnosticsCache = new RollingCache(this.cacheDir + "/semanticDiagnostics", false);
this.typesCache = new RollingCache(this.cacheDir + "/types", true);
this.syntacticDiagnosticsCache = new RollingCache(this.cacheDir + "/syntacticDiagnostics", true);
this.semanticDiagnosticsCache = new RollingCache(this.cacheDir + "/semanticDiagnostics", true);
};
Cache.prototype.markAsDirty = function (id, _snapshot) {
TsCache.prototype.markAsDirty = function (id, _snapshot) {
this.dependencyTree.setNode(id, { dirty: true });
};
// returns true if node or any of its imports or any of global types changed
Cache.prototype.isDirty = function (id, _snapshot, checkImports) {
TsCache.prototype.isDirty = function (id, _snapshot, checkImports) {
var _this = this;
var label = this.dependencyTree.node(id);
if (!label)
@ -358,11 +378,11 @@ var Cache = (function () {
return dirty;
});
};
Cache.prototype.makeName = function (id, snapshot) {
TsCache.prototype.makeName = function (id, snapshot) {
var data = snapshot.getText(0, snapshot.getLength());
return sha1({ data: data, id: id });
};
return Cache;
return TsCache;
}());
// tslint:disable-next-line:no-var-requires
@ -438,20 +458,23 @@ function typescript(options) {
abortOnError: true,
rollupCommonJSResolveHack: false,
});
var watchMode = false;
var round = 0;
var targetCount = 0;
var context = new ConsoleContext(options.verbosity, "rpt2: ");
context.info("Typescript version: " + version);
context.debug("Options: " + JSON.stringify(options, undefined, 4));
var filter$$1 = createFilter(options.include, options.exclude);
var parsedConfig = parseTsConfig(context);
var servicesHost = new LanguageServiceHost(parsedConfig);
var services = createLanguageService(servicesHost, createDocumentRegistry());
var cache = new Cache(servicesHost, options.cacheRoot, parsedConfig.options, parsedConfig.fileNames, context);
var cleanTranspile = true;
var service = createLanguageService(servicesHost, createDocumentRegistry());
var cache = new TsCache(servicesHost, options.cacheRoot, parsedConfig.options, parsedConfig.fileNames, context);
var noErrors = true;
if (options.clean)
cache.clean();
// printing compiler option errors
if (options.check)
printDiagnostics(context, convertDiagnostic(services.getCompilerOptionsDiagnostics()));
printDiagnostics(context, convertDiagnostic(service.getCompilerOptionsDiagnostics()));
return {
resolveId: function (importee, importer) {
if (importee === TSLIB)
@ -487,14 +510,14 @@ function typescript(options) {
var snapshot = servicesHost.setSnapshot(id, code);
// getting compiled file from cache or from ts
var result = cache.getCompiled(id, snapshot, function () {
var output = services.getEmitOutput(id);
var output = service.getEmitOutput(id);
if (output.emitSkipped) {
cleanTranspile = false;
noErrors = false;
// always checking on fatal errors, even if options.check is set to false
var diagnostics = cache.getSyntacticDiagnostics(id, snapshot, function () {
return services.getSyntacticDiagnostics(id);
return service.getSyntacticDiagnostics(id);
}).concat(cache.getSemanticDiagnostics(id, snapshot, function () {
return services.getSemanticDiagnostics(id);
return service.getSemanticDiagnostics(id);
}));
printDiagnostics(contextWrapper, diagnostics);
// since no output was generated, aborting compilation
@ -509,20 +532,42 @@ function typescript(options) {
});
if (options.check) {
var diagnostics = cache.getSyntacticDiagnostics(id, snapshot, function () {
return services.getSyntacticDiagnostics(id);
return service.getSyntacticDiagnostics(id);
}).concat(cache.getSemanticDiagnostics(id, snapshot, function () {
return services.getSemanticDiagnostics(id);
return service.getSemanticDiagnostics(id);
}));
if (diagnostics.length !== 0)
cleanTranspile = false;
noErrors = false;
printDiagnostics(contextWrapper, diagnostics);
}
return result;
},
ongenerate: function () {
cache.done();
if (!cleanTranspile)
ongenerate: function (bundleOptions) {
if (isArray(bundleOptions.targets))
targetCount = bundleOptions.targets.length;
if (round >= targetCount) {
watchMode = true;
round = 0;
}
context.debug("generating target " + round + " of " + bundleOptions.targets.length);
if (watchMode && round === 0) {
context.debug("running in watch mode");
// hack to fix ts lagging
servicesHost.reset();
service.cleanupSemanticCache();
cache.walkTree(function (id) {
var diagnostics = convertDiagnostic(service.getSyntacticDiagnostics(id)).concat(convertDiagnostic(service.getSemanticDiagnostics(id)));
if (diagnostics.length > 0)
noErrors = false;
printDiagnostics(context, diagnostics);
});
}
if (!noErrors) {
noErrors = true;
context.info(yellow("there were errors or warnings above."));
}
cache.done();
round++;
},
};
}

View File

@ -1,6 +1,6 @@
{
"name": "rollup-plugin-typescript2",
"version": "0.3.0",
"version": "0.4.0",
"description": "Seamless integration between Rollup and TypeScript. Now with errors.",
"main": "dist/rollup-plugin-typescript2.cjs.js",
"module": "dist/rollup-plugin-typescript2.es.js",
@ -43,13 +43,14 @@
"@types/node": "^6.0.53",
"@types/object-hash": "^0.5.28",
"@types/resolve": "0.0.4",
"resolve": "^1.1.7",
"rimraf": "^2.5.4",
"rollup": "^0.41.4",
"rollup-plugin-typescript2": "^0.2.2",
"rollup-plugin-typescript2": "^0.3.0",
"rollup-watch": "^3.2.2",
"tslib": "^1.6.0",
"tslint": "^4.5.1",
"typescript": "^2.2.1",
"resolve": "^1.1.7",
"tslib": "^1.6.0"
"typescript": "^2.2.1"
},
"repository": {
"type": "git",

View File

@ -19,7 +19,7 @@ export default {
],
plugins: [
ts({ verbosity: 2 }),
ts({ verbosity: 3 }),
],
banner: '/* eslint-disable */',

View File

@ -12,6 +12,12 @@ export class LanguageServiceHost implements ts.LanguageServiceHost
{
}
public reset()
{
this.snapshots = {};
this.versions = {};
}
public setSnapshot(fileName: string, data: string): ts.IScriptSnapshot
{
let snapshot = ts.ScriptSnapshot.fromString(data);
@ -39,9 +45,9 @@ export class LanguageServiceHost implements ts.LanguageServiceHost
return this.cwd;
}
public getScriptVersion(_fileName: string)
public getScriptVersion(fileName: string)
{
return (this.versions[_fileName] || 0).toString();
return (this.versions[fileName] || 0).toString();
}
public getScriptFileNames()

16
src/icache.ts Normal file
View File

@ -0,0 +1,16 @@
export interface ICache <DataType>
{
exists(name: string): boolean;
path(name: string): string;
match(names: string[]): boolean;
read(name: string): DataType;
write(name: string, data: DataType): void;
touch(name: string): void;
roll(): void;
};

View File

@ -1,7 +1,7 @@
import { RollupContext } from "./rollupcontext";
import { IContext, ConsoleContext, IRollupContext, VerbosityLevel } from "./context";
import { LanguageServiceHost } from "./host";
import { Cache, convertDiagnostic, ICode, IDiagnostics } from "./cache";
import { TsCache, convertDiagnostic, ICode, IDiagnostics } from "./tscache";
import * as ts from "typescript";
import * as fs from "fs-extra";
import * as path from "path";
@ -115,6 +115,10 @@ export default function typescript (options: IOptions)
rollupCommonJSResolveHack: false,
});
let watchMode = false;
let round = 0;
let targetCount = 0;
const context = new ConsoleContext(options.verbosity, "rpt2: ");
context.info(`Typescript version: ${ts.version}`);
@ -124,20 +128,20 @@ export default function typescript (options: IOptions)
const parsedConfig = parseTsConfig(context);
const servicesHost = new LanguageServiceHost(parsedConfig);
let servicesHost = new LanguageServiceHost(parsedConfig);
const services = ts.createLanguageService(servicesHost, ts.createDocumentRegistry());
let service = ts.createLanguageService(servicesHost, ts.createDocumentRegistry());
const cache = new Cache(servicesHost, options.cacheRoot, parsedConfig.options, parsedConfig.fileNames, context);
const cache = new TsCache(servicesHost, options.cacheRoot, parsedConfig.options, parsedConfig.fileNames, context);
let cleanTranspile = true;
let noErrors = true;
if (options.clean)
cache.clean();
// printing compiler option errors
if (options.check)
printDiagnostics(context, convertDiagnostic(services.getCompilerOptionsDiagnostics()));
printDiagnostics(context, convertDiagnostic(service.getCompilerOptionsDiagnostics()));
return {
@ -194,19 +198,19 @@ export default function typescript (options: IOptions)
// getting compiled file from cache or from ts
const result = cache.getCompiled(id, snapshot, () =>
{
const output = services.getEmitOutput(id);
const output = service.getEmitOutput(id);
if (output.emitSkipped)
{
cleanTranspile = false;
noErrors = false;
// always checking on fatal errors, even if options.check is set to false
const diagnostics = cache.getSyntacticDiagnostics(id, snapshot, () =>
{
return services.getSyntacticDiagnostics(id);
return service.getSyntacticDiagnostics(id);
}).concat(cache.getSemanticDiagnostics(id, snapshot, () =>
{
return services.getSemanticDiagnostics(id);
return service.getSemanticDiagnostics(id);
}));
printDiagnostics(contextWrapper, diagnostics);
@ -227,14 +231,14 @@ export default function typescript (options: IOptions)
{
const diagnostics = cache.getSyntacticDiagnostics(id, snapshot, () =>
{
return services.getSyntacticDiagnostics(id);
return service.getSyntacticDiagnostics(id);
}).concat(cache.getSemanticDiagnostics(id, snapshot, () =>
{
return services.getSemanticDiagnostics(id);
return service.getSemanticDiagnostics(id);
}));
if (diagnostics.length !== 0)
cleanTranspile = false;
noErrors = false;
printDiagnostics(contextWrapper, diagnostics);
}
@ -242,11 +246,47 @@ export default function typescript (options: IOptions)
return result;
},
ongenerate(): void
ongenerate(bundleOptions: any): void
{
cache.done();
if (!cleanTranspile)
if (_.isArray(bundleOptions.targets))
targetCount = bundleOptions.targets.length;
if (round >= targetCount) // ongenerate() is called for each target
{
watchMode = true;
round = 0;
}
context.debug(`generating target ${round} of ${bundleOptions.targets.length}`);
if (watchMode && round === 0)
{
context.debug("running in watch mode");
// hack to fix ts lagging
servicesHost.reset();
service.cleanupSemanticCache();
cache.walkTree((id) =>
{
const diagnostics = convertDiagnostic(service.getSyntacticDiagnostics(id)).concat(convertDiagnostic(service.getSemanticDiagnostics(id)));
if (diagnostics.length > 0)
noErrors = false;
printDiagnostics(context, diagnostics);
});
}
if (!noErrors)
{
noErrors = true;
context.info(colors.yellow("there were errors or warnings above."));
}
cache.done();
round++;
},
};
}

View File

@ -1,3 +1,4 @@
import { ICache } from "./icache";
import * as fs from "fs-extra";
import * as _ from "lodash";
@ -5,11 +6,13 @@ import * as _ from "lodash";
* Saves data in new cache folder or reads it from old one.
* Avoids perpetually growing cache and situations when things need to consider changed and then reverted data to be changed.
*/
export class RollingCache <DataType>
export class RollingCache <DataType> implements ICache<DataType>
{
private oldCacheRoot: string;
private newCacheRoot: string;
private rolled: boolean = false;
/**
* @param cacheRoot: root folder for the cache
* @param checkNewCache: whether to also look in new cache when reading from cache
@ -27,6 +30,9 @@ export class RollingCache <DataType>
*/
public exists(name: string): boolean
{
if (this.rolled)
return false;
if (this.checkNewCache && fs.existsSync(`${this.newCacheRoot}/${name}`))
return true;
@ -43,6 +49,9 @@ export class RollingCache <DataType>
*/
public match(names: string[]): boolean
{
if (this.rolled)
return false;
if (!fs.existsSync(this.oldCacheRoot))
return names.length === 0; // empty folder matches
@ -62,21 +71,23 @@ export class RollingCache <DataType>
public write(name: string, data: DataType): void
{
if (this.rolled)
return;
if (data === undefined)
return;
if (this.checkNewCache)
if (this.rolled)
fs.writeJsonSync(`${this.oldCacheRoot}/${name}`, data);
else
fs.writeJsonSync(`${this.newCacheRoot}/${name}`, data);
else // won't be reading it this run
fs.writeJson(`${this.newCacheRoot}/${name}`, data, { encoding: "utf8" }, () => { ; });
}
public touch(name: string)
{
if (this.checkNewCache)
fs.ensureFileSync(`${this.newCacheRoot}/${name}`);
else // won't be reading it this run
fs.ensureFile(`${this.newCacheRoot}/${name}`, () => { ; });
if (this.rolled)
return;
fs.ensureFileSync(`${this.newCacheRoot}/${name}`);
}
/**
@ -84,9 +95,11 @@ export class RollingCache <DataType>
*/
public roll()
{
fs.remove(this.oldCacheRoot, () =>
{
fs.move(this.newCacheRoot, this.oldCacheRoot, () => { ; });
});
if (this.rolled)
return;
this.rolled = true;
fs.removeSync(this.oldCacheRoot);
fs.move(this.newCacheRoot, this.oldCacheRoot, () => { ; });
}
}

View File

@ -6,6 +6,7 @@ import * as _ from "lodash";
import { RollingCache } from "./rollingcache";
import * as fs from "fs-extra";
import * as colors from "colors/safe";
import { ICache } from "./icache";
export interface ICode
{
@ -51,17 +52,17 @@ export function convertDiagnostic(data: ts.Diagnostic[]): IDiagnostics[]
});
}
export class Cache
export class TsCache
{
private cacheVersion = "2";
private cacheVersion = "3";
private dependencyTree: graph.Graph;
private ambientTypes: ITypeSnapshot[];
private ambientTypesDirty = false;
private cacheDir: string;
private codeCache: RollingCache<ICode | undefined>;
private typesCache: RollingCache<string>;
private semanticDiagnosticsCache: RollingCache<IDiagnostics[]>;
private syntacticDiagnosticsCache: RollingCache<IDiagnostics[]>;
private codeCache: ICache<ICode | undefined>;
private typesCache: ICache<string>;
private semanticDiagnosticsCache: ICache<IDiagnostics[]>;
private syntacticDiagnosticsCache: ICache<IDiagnostics[]>;
constructor(private host: ts.LanguageServiceHost, cache: string, private options: ts.CompilerOptions, rootFilenames: string[], private context: IContext)
{
@ -106,6 +107,21 @@ export class Cache
this.dependencyTree.setEdge(importer, importee);
}
public walkTree(cb: (id: string) => void | false): void
{
const acyclic = graph.alg.isAcyclic(this.dependencyTree);
if (acyclic)
{
_.each(graph.alg.topsort(this.dependencyTree), (id: string) => cb(id));
return;
}
this.context.info(colors.yellow("import tree has cycles"));
_.each(this.dependencyTree.nodes(), (id: string) => cb(id));
}
public done()
{
this.context.info(colors.blue("rolling caches"));
@ -159,7 +175,6 @@ export class Cache
this.context.debug(` ${snapshot.id}`);
return this.makeName(snapshot.id, snapshot.snapshot!);
});
// types dirty if any d.ts changed, added or removed
this.ambientTypesDirty = !this.typesCache.match(typeNames);
@ -169,7 +184,7 @@ export class Cache
_.each(typeNames, (name) => this.typesCache.touch(name));
}
private getDiagnostics(cache: RollingCache<IDiagnostics[]>, id: string, snapshot: ts.IScriptSnapshot, check: () => ts.Diagnostic[]): IDiagnostics[]
private getDiagnostics(cache: ICache<IDiagnostics[]>, id: string, snapshot: ts.IScriptSnapshot, check: () => ts.Diagnostic[]): IDiagnostics[]
{
const name = this.makeName(id, snapshot);
@ -195,9 +210,9 @@ export class Cache
private init()
{
this.codeCache = new RollingCache<ICode>(`${this.cacheDir}/code`, true);
this.typesCache = new RollingCache<string>(`${this.cacheDir}/types`, false);
this.syntacticDiagnosticsCache = new RollingCache<IDiagnostics[]>(`${this.cacheDir}/syntacticDiagnostics`, false);
this.semanticDiagnosticsCache = new RollingCache<IDiagnostics[]>(`${this.cacheDir}/semanticDiagnostics`, false);
this.typesCache = new RollingCache<string>(`${this.cacheDir}/types`, true);
this.syntacticDiagnosticsCache = new RollingCache<IDiagnostics[]>(`${this.cacheDir}/syntacticDiagnostics`, true);
this.semanticDiagnosticsCache = new RollingCache<IDiagnostics[]>(`${this.cacheDir}/semanticDiagnostics`, true);
}
private markAsDirty(id: string, _snapshot: ts.IScriptSnapshot): void