diff --git a/dist/rollup-plugin-typescript2.cjs.js b/dist/rollup-plugin-typescript2.cjs.js index 2853d80..295d381 100644 --- a/dist/rollup-plugin-typescript2.cjs.js +++ b/dist/rollup-plugin-typescript2.cjs.js @@ -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(); }, }; } diff --git a/dist/rollup-plugin-typescript2.es.js b/dist/rollup-plugin-typescript2.es.js index 36c1ba8..82c08aa 100644 --- a/dist/rollup-plugin-typescript2.es.js +++ b/dist/rollup-plugin-typescript2.es.js @@ -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(); }, }; } diff --git a/package.json b/package.json index 53b1d34..d180bec 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/rollup.config.js b/rollup.config.js index cc1330b..724cc81 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -7,14 +7,13 @@ export default { external: [ 'path', - 'fs', + 'fs-extra', 'object-assign', 'rollup-pluginutils', 'typescript', 'lodash', 'graphlib', - 'object-hash', - 'mkdirp' + 'object-hash' ], plugins: [ diff --git a/src/cache.ts b/src/cache.ts index 44bfbe4..6200291 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -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; + private typesCache: RollingCache; + private diagnosticsCache: RollingCache; - 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(`${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((_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; diff --git a/src/host.ts b/src/host.ts index 3856664..492419c 100644 --- a/src/host.ts +++ b/src/host.ts @@ -1,4 +1,4 @@ -import * as fs from "fs"; +import * as fs from "fs-extra"; import * as ts from "typescript"; import * as _ from "lodash"; diff --git a/src/index.ts b/src/index.ts index 78ff31a..defa952 100644 --- a/src/index.ts +++ b/src/index.ts @@ -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(); }, }; } diff --git a/src/rollingcache.ts b/src/rollingcache.ts new file mode 100644 index 0000000..638ee8d --- /dev/null +++ b/src/rollingcache.ts @@ -0,0 +1,61 @@ +import * as fs from "fs-extra"; +import * as _ from "lodash"; + +export class RollingCache +{ + 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, () => {} ); + }); + } +}