From 93042e103e2fbcb9154e5e65791fe1bf76f57b63 Mon Sep 17 00:00:00 2001 From: Eugene Zolenko Date: Tue, 7 Feb 2017 18:46:44 -0700 Subject: [PATCH 01/11] - cache for transpiled code and diagnostics --- package.json | 9 ++++- rollup.config.js | 4 +- src/cache.ts | 99 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 109 insertions(+), 3 deletions(-) create mode 100644 src/cache.ts diff --git a/package.json b/package.json index d3113ca..d90553b 100644 --- a/package.json +++ b/package.json @@ -23,19 +23,24 @@ "lint": "tslint -c ./tslint.json src/*.ts", "postinstall": "typings install" }, - "dependencies": {}, + "dependencies": { + "graphlib": "^2.1.1", + "object-hash": "^1.1.5", + "rollup-pluginutils": "^2.0.1", + }, "peerDependencies": { "typescript": "^2.1.5" }, "devDependencies": { "@alexlur/rollup-plugin-typescript": "^0.8.1", + "@types/graphlib": "^2.1.3", "@types/node": "^6.0.53", + "@types/object-hash": "^0.5.28", "glob": "^7.1.1", "glob-fs": "^0.1.6", "lodash": "^4.17.4", "rimraf": "^2.5.4", "rollup": "^0.41.4", - "rollup-pluginutils": "^2.0.1", "tslib": "^1.5.0", "tslint": "^4.1.1", "typescript": "^2.1.5", diff --git a/rollup.config.js b/rollup.config.js index ff8c2c4..0264deb 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -10,7 +10,9 @@ export default { 'fs', 'object-assign', 'rollup-pluginutils', - 'typescript' + 'typescript', + 'graphlib', + 'object-hash' ], plugins: [ diff --git a/src/cache.ts b/src/cache.ts new file mode 100644 index 0000000..e13c792 --- /dev/null +++ b/src/cache.ts @@ -0,0 +1,99 @@ +import * as ts from "typescript"; +import * as graph from "graphlib"; +import * as hash from "object-hash"; +import * as fs from "fs"; + +interface ICode +{ + code: string; + map: string; +} + +interface INodeLabel +{ + dirty?: boolean; + hash?: string; +} + +export class Cache +{ + private dependencyTree: graph.Graph; + private treeComplete: boolean = false; + + constructor(private cacheRoot: string, private options: ts.CompilerOptions, rootFilenames: string[]) + { + this.cacheRoot = `${this.cacheRoot}/${hash.sha1({ rootFilenames, options: this.options })}`; + fs.mkdirSync(this.cacheRoot); + + let dependencyTreeFile = `${this.cacheRoot}/tree`; + if (fs.existsSync(dependencyTreeFile)) + { + let data = fs.readFileSync(`${this.cacheRoot}/tree`, "utf8"); + + this.dependencyTree = graph.json.read(JSON.parse(data)); + } + else + this.dependencyTree = new graph.Graph(); + } + + public setDependency(importee: string, importer: string): void + { + this.dependencyTree.setEdge(importer, importee); + } + + public lastDependencySet() + { + this.treeComplete = true; + } + + public markAsDirty(fileName: string, snapshot: ts.IScriptSnapshot) + { + this.dependencyTree.setNode(fileName, { dirty: true }); + } + + public isDirty(fileName: string, snapshot: ts.IScriptSnapshot): boolean + { + let label = this.dependencyTree.node(fileName) as INodeLabel; + + // TODO: also if any dependencies are dirty + + return label.dirty; + } + + public getCompiled(fileName: string, snapshot: ts.IScriptSnapshot, transform: () => ICode | undefined): ICode | undefined + { + let path = this.makePath(fileName, snapshot); + + if (!fs.existsSync(path) || this.isDirty(fileName, snapshot)) + { + let data = transform(); + this.setCompiled(path, fileName, snapshot, data); + return data; + } + + return JSON.parse(fs.readFileSync(path, "utf8")) as ICode; + } + + public getDiagnostics(fileName: string, snapshot: ts.IScriptSnapshot, check: () => ts.Diagnostic[]): ts.Diagnostic[] + { + + } + + private setDiagnostics(path: string, fileName: string, snapshot: ts.IScriptSnapshot, data: ts.Diagnostic[]): void + { + + } + + private setCompiled(path: string, fileName: string, snapshot: ts.IScriptSnapshot, data: ICode) + { + fs.writeFileSync(path, JSON.stringify(data)); + + this.markAsDirty(fileName, snapshot); + } + + private makePath(fileName: string, snapshot: ts.IScriptSnapshot): string + { + let data = snapshot.getText(0, snapshot.getLength()); + return `${this.cacheRoot}/${hash.sha1({ data, fileName })}`; + } +} From 3cac1aae62abddb2eef46cfc0e583d12c796bfcd Mon Sep 17 00:00:00 2001 From: Eugene Zolenko Date: Wed, 8 Feb 2017 15:01:33 -0700 Subject: [PATCH 02/11] - cache --- src/cache.ts | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/src/cache.ts b/src/cache.ts index e13c792..b34928e 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -11,7 +11,7 @@ interface ICode interface INodeLabel { - dirty?: boolean; + dirty: boolean; hash?: string; } @@ -33,7 +33,9 @@ export class Cache this.dependencyTree = graph.json.read(JSON.parse(data)); } else - this.dependencyTree = new graph.Graph(); + this.dependencyTree = new graph.Graph({ directed: true }); + + this.dependencyTree.setDefaultNodeLabel((_node: string) => { return { dirty: false }; }); } public setDependency(importee: string, importer: string): void @@ -46,12 +48,12 @@ export class Cache this.treeComplete = true; } - public markAsDirty(fileName: string, snapshot: ts.IScriptSnapshot) + public markAsDirty(fileName: string, _snapshot: ts.IScriptSnapshot) { this.dependencyTree.setNode(fileName, { dirty: true }); } - public isDirty(fileName: string, snapshot: ts.IScriptSnapshot): boolean + public isDirty(fileName: string, _snapshot: ts.IScriptSnapshot): boolean { let label = this.dependencyTree.node(fileName) as INodeLabel; @@ -67,7 +69,7 @@ export class Cache if (!fs.existsSync(path) || this.isDirty(fileName, snapshot)) { let data = transform(); - this.setCompiled(path, fileName, snapshot, data); + this.setCache(path, fileName, snapshot, data); return data; } @@ -76,16 +78,23 @@ export class Cache public getDiagnostics(fileName: string, snapshot: ts.IScriptSnapshot, check: () => ts.Diagnostic[]): ts.Diagnostic[] { + let path = `${this.makePath(fileName, snapshot)}.diagnostics`; + if (!fs.existsSync(path) || this.isDirty(fileName, snapshot)) + { + let data = check(); + this.setCache(path, fileName, snapshot, data); + return data; + } + + return JSON.parse(fs.readFileSync(path, "utf8")) as ts.Diagnostic[]; } - private setDiagnostics(path: string, fileName: string, snapshot: ts.IScriptSnapshot, data: ts.Diagnostic[]): void + private setCache(path: string, fileName: string, snapshot: ts.IScriptSnapshot, data: ts.Diagnostic[] | ICode | undefined): void { + if (data === undefined) + return; - } - - private setCompiled(path: string, fileName: string, snapshot: ts.IScriptSnapshot, data: ICode) - { fs.writeFileSync(path, JSON.stringify(data)); this.markAsDirty(fileName, snapshot); From 3994d41607696274a5175e5855ba95e08f7d47ab Mon Sep 17 00:00:00 2001 From: Eugene Zolenko Date: Tue, 7 Feb 2017 18:46:44 -0700 Subject: [PATCH 03/11] - cache for transpiled code and diagnostics --- package.json | 2 + rollup.config.js | 4 +- src/cache.ts | 99 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 src/cache.ts diff --git a/package.json b/package.json index b22bee2..f712bd3 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "dependencies": { "lodash": "^4.17.4", "rollup-pluginutils": "^2.0.1", + "graphlib": "^2.1.1", "tslib": "^1.5.0" }, "peerDependencies": { @@ -35,6 +36,7 @@ "@alexlur/rollup-plugin-typescript": "^0.8.1", "@types/lodash": "^4.14.52", "@types/node": "^6.0.53", + "@types/graphlib": "^2.1.3", "rimraf": "^2.5.4", "rollup": "^0.41.4", "tslint": "^4.4.2", diff --git a/rollup.config.js b/rollup.config.js index 38d926a..f307304 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -11,7 +11,9 @@ export default { 'object-assign', 'rollup-pluginutils', 'typescript', - 'lodash' + 'lodash', + 'graphlib', + 'object-hash' ], plugins: [ diff --git a/src/cache.ts b/src/cache.ts new file mode 100644 index 0000000..e13c792 --- /dev/null +++ b/src/cache.ts @@ -0,0 +1,99 @@ +import * as ts from "typescript"; +import * as graph from "graphlib"; +import * as hash from "object-hash"; +import * as fs from "fs"; + +interface ICode +{ + code: string; + map: string; +} + +interface INodeLabel +{ + dirty?: boolean; + hash?: string; +} + +export class Cache +{ + private dependencyTree: graph.Graph; + private treeComplete: boolean = false; + + constructor(private cacheRoot: string, private options: ts.CompilerOptions, rootFilenames: string[]) + { + this.cacheRoot = `${this.cacheRoot}/${hash.sha1({ rootFilenames, options: this.options })}`; + fs.mkdirSync(this.cacheRoot); + + let dependencyTreeFile = `${this.cacheRoot}/tree`; + if (fs.existsSync(dependencyTreeFile)) + { + let data = fs.readFileSync(`${this.cacheRoot}/tree`, "utf8"); + + this.dependencyTree = graph.json.read(JSON.parse(data)); + } + else + this.dependencyTree = new graph.Graph(); + } + + public setDependency(importee: string, importer: string): void + { + this.dependencyTree.setEdge(importer, importee); + } + + public lastDependencySet() + { + this.treeComplete = true; + } + + public markAsDirty(fileName: string, snapshot: ts.IScriptSnapshot) + { + this.dependencyTree.setNode(fileName, { dirty: true }); + } + + public isDirty(fileName: string, snapshot: ts.IScriptSnapshot): boolean + { + let label = this.dependencyTree.node(fileName) as INodeLabel; + + // TODO: also if any dependencies are dirty + + return label.dirty; + } + + public getCompiled(fileName: string, snapshot: ts.IScriptSnapshot, transform: () => ICode | undefined): ICode | undefined + { + let path = this.makePath(fileName, snapshot); + + if (!fs.existsSync(path) || this.isDirty(fileName, snapshot)) + { + let data = transform(); + this.setCompiled(path, fileName, snapshot, data); + return data; + } + + return JSON.parse(fs.readFileSync(path, "utf8")) as ICode; + } + + public getDiagnostics(fileName: string, snapshot: ts.IScriptSnapshot, check: () => ts.Diagnostic[]): ts.Diagnostic[] + { + + } + + private setDiagnostics(path: string, fileName: string, snapshot: ts.IScriptSnapshot, data: ts.Diagnostic[]): void + { + + } + + private setCompiled(path: string, fileName: string, snapshot: ts.IScriptSnapshot, data: ICode) + { + fs.writeFileSync(path, JSON.stringify(data)); + + this.markAsDirty(fileName, snapshot); + } + + private makePath(fileName: string, snapshot: ts.IScriptSnapshot): string + { + let data = snapshot.getText(0, snapshot.getLength()); + return `${this.cacheRoot}/${hash.sha1({ data, fileName })}`; + } +} From 93fa5680d2f3e91e193f71481fcd296b66390b17 Mon Sep 17 00:00:00 2001 From: Eugene Zolenko Date: Wed, 8 Feb 2017 15:01:33 -0700 Subject: [PATCH 04/11] - cache --- src/cache.ts | 29 +++++++++++++++++++---------- 1 file changed, 19 insertions(+), 10 deletions(-) diff --git a/src/cache.ts b/src/cache.ts index e13c792..b34928e 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -11,7 +11,7 @@ interface ICode interface INodeLabel { - dirty?: boolean; + dirty: boolean; hash?: string; } @@ -33,7 +33,9 @@ export class Cache this.dependencyTree = graph.json.read(JSON.parse(data)); } else - this.dependencyTree = new graph.Graph(); + this.dependencyTree = new graph.Graph({ directed: true }); + + this.dependencyTree.setDefaultNodeLabel((_node: string) => { return { dirty: false }; }); } public setDependency(importee: string, importer: string): void @@ -46,12 +48,12 @@ export class Cache this.treeComplete = true; } - public markAsDirty(fileName: string, snapshot: ts.IScriptSnapshot) + public markAsDirty(fileName: string, _snapshot: ts.IScriptSnapshot) { this.dependencyTree.setNode(fileName, { dirty: true }); } - public isDirty(fileName: string, snapshot: ts.IScriptSnapshot): boolean + public isDirty(fileName: string, _snapshot: ts.IScriptSnapshot): boolean { let label = this.dependencyTree.node(fileName) as INodeLabel; @@ -67,7 +69,7 @@ export class Cache if (!fs.existsSync(path) || this.isDirty(fileName, snapshot)) { let data = transform(); - this.setCompiled(path, fileName, snapshot, data); + this.setCache(path, fileName, snapshot, data); return data; } @@ -76,16 +78,23 @@ export class Cache public getDiagnostics(fileName: string, snapshot: ts.IScriptSnapshot, check: () => ts.Diagnostic[]): ts.Diagnostic[] { + let path = `${this.makePath(fileName, snapshot)}.diagnostics`; + if (!fs.existsSync(path) || this.isDirty(fileName, snapshot)) + { + let data = check(); + this.setCache(path, fileName, snapshot, data); + return data; + } + + return JSON.parse(fs.readFileSync(path, "utf8")) as ts.Diagnostic[]; } - private setDiagnostics(path: string, fileName: string, snapshot: ts.IScriptSnapshot, data: ts.Diagnostic[]): void + private setCache(path: string, fileName: string, snapshot: ts.IScriptSnapshot, data: ts.Diagnostic[] | ICode | undefined): void { + if (data === undefined) + return; - } - - private setCompiled(path: string, fileName: string, snapshot: ts.IScriptSnapshot, data: ICode) - { fs.writeFileSync(path, JSON.stringify(data)); this.markAsDirty(fileName, snapshot); From acad7e7eaf617ab1e539ee47797276f37a9da040 Mon Sep 17 00:00:00 2001 From: Eugene Zolenko Date: Wed, 8 Feb 2017 17:37:27 -0700 Subject: [PATCH 05/11] - cashe --- src/cache.ts | 42 ++++++++++++++++++++++++------------------ 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/src/cache.ts b/src/cache.ts index b34928e..e2b9030 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -2,6 +2,7 @@ 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"; interface ICode { @@ -40,6 +41,7 @@ export class Cache public setDependency(importee: string, importer: string): void { + // importer -> importee this.dependencyTree.setEdge(importer, importee); } @@ -48,61 +50,65 @@ export class Cache this.treeComplete = true; } - public markAsDirty(fileName: string, _snapshot: ts.IScriptSnapshot) + public markAsDirty(id: string, _snapshot: ts.IScriptSnapshot) { - this.dependencyTree.setNode(fileName, { dirty: true }); + this.dependencyTree.setNode(id, { dirty: true }); } - public isDirty(fileName: string, _snapshot: ts.IScriptSnapshot): boolean + // returns true if node or any of its imports changed + public isDirty(id: string, _snapshot: ts.IScriptSnapshot, checkImports: boolean): boolean { - let label = this.dependencyTree.node(fileName) as INodeLabel; + let label = this.dependencyTree.node(id) as INodeLabel; - // TODO: also if any dependencies are dirty + if (checkImports || label.dirty) + return label.dirty; - return label.dirty; + let dependencies = graph.alg.dijkstra(this.dependencyTree, id); + + return _.some(_.keys(dependencies), (dependencyId: string) => (this.dependencyTree.node(dependencyId) as INodeLabel).dirty); } - public getCompiled(fileName: string, snapshot: ts.IScriptSnapshot, transform: () => ICode | undefined): ICode | undefined + public getCompiled(id: string, snapshot: ts.IScriptSnapshot, transform: () => ICode | undefined): ICode | undefined { - let path = this.makePath(fileName, snapshot); + let path = this.makePath(id, snapshot); - if (!fs.existsSync(path) || this.isDirty(fileName, snapshot)) + if (!fs.existsSync(path) || this.isDirty(id, snapshot, false)) { let data = transform(); - this.setCache(path, fileName, snapshot, data); + this.setCache(path, id, snapshot, data); return data; } return JSON.parse(fs.readFileSync(path, "utf8")) as ICode; } - public getDiagnostics(fileName: string, snapshot: ts.IScriptSnapshot, check: () => ts.Diagnostic[]): ts.Diagnostic[] + public getDiagnostics(id: string, snapshot: ts.IScriptSnapshot, check: () => ts.Diagnostic[]): ts.Diagnostic[] { - let path = `${this.makePath(fileName, snapshot)}.diagnostics`; + let path = `${this.makePath(id, snapshot)}.diagnostics`; - if (!fs.existsSync(path) || this.isDirty(fileName, snapshot)) + if (!fs.existsSync(path) || this.isDirty(id, snapshot, true)) { let data = check(); - this.setCache(path, fileName, snapshot, data); + this.setCache(path, id, snapshot, data); return data; } return JSON.parse(fs.readFileSync(path, "utf8")) as ts.Diagnostic[]; } - private setCache(path: string, fileName: string, snapshot: ts.IScriptSnapshot, data: ts.Diagnostic[] | ICode | undefined): void + private setCache(path: string, id: string, snapshot: ts.IScriptSnapshot, data: ts.Diagnostic[] | ICode | undefined): void { if (data === undefined) return; fs.writeFileSync(path, JSON.stringify(data)); - this.markAsDirty(fileName, snapshot); + this.markAsDirty(id, snapshot); } - private makePath(fileName: string, snapshot: ts.IScriptSnapshot): string + private makePath(id: string, snapshot: ts.IScriptSnapshot): string { let data = snapshot.getText(0, snapshot.getLength()); - return `${this.cacheRoot}/${hash.sha1({ data, fileName })}`; + return `${this.cacheRoot}/${hash.sha1({ data, id })}`; } } From b9b2172f62347977442be329de13c4fb7bf05dad Mon Sep 17 00:00:00 2001 From: Eugene Zolenko Date: Wed, 8 Feb 2017 18:30:44 -0700 Subject: [PATCH 06/11] - cache --- dist/rollup-plugin-typescript2.cjs.js | 183 ++++++++++++++++++++----- dist/rollup-plugin-typescript2.es.js | 187 +++++++++++++++++++++----- package.json | 5 +- src/cache.ts | 7 +- src/host.ts | 59 ++++++++ src/index.ts | 86 +++++++----- 6 files changed, 423 insertions(+), 104 deletions(-) create mode 100644 src/host.ts diff --git a/dist/rollup-plugin-typescript2.cjs.js b/dist/rollup-plugin-typescript2.cjs.js index 5379e1f..67156af 100644 --- a/dist/rollup-plugin-typescript2.cjs.js +++ b/dist/rollup-plugin-typescript2.cjs.js @@ -1,11 +1,13 @@ /* eslint-disable */ 'use strict'; -var ts = require('typescript'); -var rollupPluginutils = require('rollup-pluginutils'); var fs = require('fs'); -var path = require('path'); +var ts = require('typescript'); var _ = require('lodash'); +var graph = require('graphlib'); +var hash = require('object-hash'); +var rollupPluginutils = require('rollup-pluginutils'); +var path = require('path'); const __assign = Object.assign || function (target) { for (var source, i = 1; i < arguments.length; i++) { @@ -19,6 +21,113 @@ const __assign = Object.assign || function (target) { return target; }; +var LanguageServiceHost = (function () { + function LanguageServiceHost(parsedConfig) { + this.parsedConfig = parsedConfig; + this.cwd = process.cwd(); + this.snapshots = {}; + } + LanguageServiceHost.prototype.setSnapshot = function (fileName, data) { + var snapshot = ts.ScriptSnapshot.fromString(data); + this.snapshots[fileName] = snapshot; + return snapshot; + }; + LanguageServiceHost.prototype.getScriptSnapshot = function (fileName) { + if (_.has(this.snapshots, fileName)) + return this.snapshots[fileName]; + if (fs.existsSync(fileName)) { + this.snapshots[fileName] = ts.ScriptSnapshot.fromString(ts.sys.readFile(fileName)); + return this.snapshots[fileName]; + } + return undefined; + }; + LanguageServiceHost.prototype.getCurrentDirectory = function () { + return this.cwd; + }; + LanguageServiceHost.prototype.getScriptVersion = function (_fileName) { + return "0"; + }; + LanguageServiceHost.prototype.getScriptFileNames = function () { + return this.parsedConfig.fileNames; + }; + LanguageServiceHost.prototype.getCompilationSettings = function () { + return this.parsedConfig.options; + }; + LanguageServiceHost.prototype.getDefaultLibFileName = function (opts) { + return ts.getDefaultLibFilePath(opts); + }; + return LanguageServiceHost; +}()); + +var Cache = (function () { + function Cache(cacheRoot, options, rootFilenames) { + this.cacheRoot = cacheRoot; + this.options = options; + this.treeComplete = false; + this.cacheRoot = this.cacheRoot + "/" + hash.sha1({ rootFilenames: rootFilenames, options: this.options }); + fs.mkdirSync(this.cacheRoot); + var dependencyTreeFile = this.cacheRoot + "/tree"; + if (fs.existsSync(dependencyTreeFile)) { + var data = fs.readFileSync(this.cacheRoot + "/tree", "utf8"); + this.dependencyTree = graph.json.read(JSON.parse(data)); + } + else + this.dependencyTree = new graph.Graph({ directed: true }); + this.dependencyTree.setDefaultNodeLabel(function (_node) { return { dirty: false }; }); + } + Cache.prototype.walkTree = function (cb) { + _.each(graph.alg.topsort(this.dependencyTree), function (id) { return cb(id); }); + }; + Cache.prototype.setDependency = function (importee, importer) { + // importer -> importee + this.dependencyTree.setEdge(importer, importee); + }; + Cache.prototype.lastDependencySet = function () { + this.treeComplete = true; + }; + Cache.prototype.markAsDirty = function (id, _snapshot) { + this.dependencyTree.setNode(id, { dirty: true }); + }; + // returns true if node or any of its imports changed + Cache.prototype.isDirty = function (id, _snapshot, checkImports) { + var _this = this; + var label = this.dependencyTree.node(id); + if (checkImports || label.dirty) + return label.dirty; + var dependencies = graph.alg.dijkstra(this.dependencyTree, id); + return _.some(_.keys(dependencies), function (dependencyId) { return _this.dependencyTree.node(dependencyId).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)) { + 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)) { + var data = 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) { + var data = snapshot.getText(0, snapshot.getLength()); + return this.cacheRoot + "/" + hash.sha1({ data: data, id: id }); + }; + return Cache; +}()); + function getOptionsOverrides() { return { module: ts.ModuleKind.ES2015, @@ -66,15 +175,15 @@ function parseTsConfig() { var configParseResult = ts.parseJsonConfigFileContent(result.config, ts.sys, path.dirname(fileName), getOptionsOverrides(), fileName); return configParseResult; } -function printDiagnostics(context, diagnostics) { +function printDiagnostics(diagnostics) { diagnostics.forEach(function (diagnostic) { var message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n"); if (diagnostic.file) { var _a = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start), line = _a.line, character = _a.character; - context.warn({ message: diagnostic.file.fileName + " (" + (line + 1) + "," + (character + 1) + "): " + message }); + console.log(diagnostic.file.fileName + " (" + (line + 1) + "," + (character + 1) + "): " + message); } else - context.warn({ message: message }); + console.log(message); }); } @@ -84,21 +193,12 @@ function typescript(options) { delete options.include; delete options.exclude; var parsedConfig = parseTsConfig(); - var servicesHost = { - getScriptFileNames: function () { return parsedConfig.fileNames; }, - getScriptVersion: function (_fileName) { return "0"; }, - getScriptSnapshot: function (fileName) { - if (!fs.existsSync(fileName)) - return undefined; - return ts.ScriptSnapshot.fromString(ts.sys.readFile(fileName)); - }, - getCurrentDirectory: function () { return process.cwd(); }, - getCompilationSettings: function () { return parsedConfig.options; }, - getDefaultLibFileName: function (opts) { return ts.getDefaultLibFilePath(opts); }, - }; + var servicesHost = new LanguageServiceHost(parsedConfig); var services = ts.createLanguageService(servicesHost, ts.createDocumentRegistry()); + var cache = new Cache(process.cwd(), parsedConfig.options, parsedConfig.fileNames); return { resolveId: function (importee, importer) { + cache.setDependency(importee, importer); if (importee === TSLIB) return "\0" + TSLIB; if (!importer) @@ -115,24 +215,41 @@ function typescript(options) { load: function (id) { if (id === "\0" + TSLIB) return tslibSource; + return undefined; }, - transform: function (_code, id) { + transform: function (code, id) { + var _this = this; if (!filter(id)) return null; - var output = services.getEmitOutput(id); - var allDiagnostics = services - .getCompilerOptionsDiagnostics() - .concat(services.getSyntacticDiagnostics(id)) - .concat(services.getSemanticDiagnostics(id)); - printDiagnostics(this, allDiagnostics); - if (output.emitSkipped) - this.error({ message: "failed to transpile " + id }); - var code = _.find(output.outputFiles, function (entry) { return _.endsWith(entry.name, ".js"); }); - var map = _.find(output.outputFiles, function (entry) { return _.endsWith(entry.name, ".map"); }); - return { - code: code ? code.text : undefined, - map: map ? JSON.parse(map.text) : { mappings: "" }, - }; + var snapshot = servicesHost.setSnapshot(id, code); + var result = cache.getCompiled(id, snapshot, function () { + var output = services.getEmitOutput(id); + if (output.emitSkipped) + _this.error({ message: "failed to transpile " + id }); + var transpiled = _.find(output.outputFiles, function (entry) { return _.endsWith(entry.name, ".js"); }); + var map = _.find(output.outputFiles, function (entry) { return _.endsWith(entry.name, ".map"); }); + return { + code: transpiled ? transpiled.text : undefined, + map: map ? JSON.parse(map.text) : { mappings: "" }, + }; + }); + return result; + }, + outro: function () { + cache.lastDependencySet(); + cache.walkTree(function (id) { + var snapshot = servicesHost.getScriptSnapshot(id); + var diagnostics = cache.getDiagnostics(id, snapshot, function () { + return services + .getCompilerOptionsDiagnostics() + .concat(services.getSyntacticDiagnostics(id)) + .concat(services.getSemanticDiagnostics(id)); + }); + if (diagnostics.length !== 0) { + console.log(id); + printDiagnostics(diagnostics); + } + }); }, }; } diff --git a/dist/rollup-plugin-typescript2.es.js b/dist/rollup-plugin-typescript2.es.js index fb83cb8..a09aa01 100644 --- a/dist/rollup-plugin-typescript2.es.js +++ b/dist/rollup-plugin-typescript2.es.js @@ -1,13 +1,17 @@ /* eslint-disable */ +import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs'; +import * as fs from 'fs'; import { ModuleKind, ScriptSnapshot, createDocumentRegistry, createLanguageService, flattenDiagnosticMessageText, getDefaultLibFilePath, nodeModuleNameResolver, parseConfigFileTextToJson, parseJsonConfigFileContent, sys } from 'typescript'; import * as ts from 'typescript'; +import { each, endsWith, find, has, keys, some } from 'lodash'; +import * as _ from 'lodash'; +import { Graph, alg, json } from 'graphlib'; +import * as graph from 'graphlib'; +import { sha1 } from 'object-hash'; +import * as hash from 'object-hash'; import { createFilter } from 'rollup-pluginutils'; -import { existsSync, readFileSync } from 'fs'; -import * as fs from 'fs'; import { dirname, sep } from 'path'; import * as path from 'path'; -import { endsWith, find } from 'lodash'; -import * as _ from 'lodash'; const __assign = Object.assign || function (target) { for (var source, i = 1; i < arguments.length; i++) { @@ -21,6 +25,113 @@ const __assign = Object.assign || function (target) { return target; }; +var LanguageServiceHost = (function () { + function LanguageServiceHost(parsedConfig) { + this.parsedConfig = parsedConfig; + this.cwd = process.cwd(); + this.snapshots = {}; + } + LanguageServiceHost.prototype.setSnapshot = function (fileName, data) { + var snapshot = ScriptSnapshot.fromString(data); + this.snapshots[fileName] = snapshot; + return snapshot; + }; + LanguageServiceHost.prototype.getScriptSnapshot = function (fileName) { + if (has(this.snapshots, fileName)) + return this.snapshots[fileName]; + if (existsSync(fileName)) { + this.snapshots[fileName] = ScriptSnapshot.fromString(sys.readFile(fileName)); + return this.snapshots[fileName]; + } + return undefined; + }; + LanguageServiceHost.prototype.getCurrentDirectory = function () { + return this.cwd; + }; + LanguageServiceHost.prototype.getScriptVersion = function (_fileName) { + return "0"; + }; + LanguageServiceHost.prototype.getScriptFileNames = function () { + return this.parsedConfig.fileNames; + }; + LanguageServiceHost.prototype.getCompilationSettings = function () { + return this.parsedConfig.options; + }; + LanguageServiceHost.prototype.getDefaultLibFileName = function (opts) { + return getDefaultLibFilePath(opts); + }; + return LanguageServiceHost; +}()); + +var Cache = (function () { + function Cache(cacheRoot, options, rootFilenames) { + this.cacheRoot = cacheRoot; + this.options = options; + this.treeComplete = false; + this.cacheRoot = this.cacheRoot + "/" + sha1({ rootFilenames: rootFilenames, options: this.options }); + mkdirSync(this.cacheRoot); + var dependencyTreeFile = this.cacheRoot + "/tree"; + if (existsSync(dependencyTreeFile)) { + var data = readFileSync(this.cacheRoot + "/tree", "utf8"); + this.dependencyTree = json.read(JSON.parse(data)); + } + else + this.dependencyTree = new Graph({ directed: true }); + this.dependencyTree.setDefaultNodeLabel(function (_node) { return { dirty: false }; }); + } + Cache.prototype.walkTree = function (cb) { + each(alg.topsort(this.dependencyTree), function (id) { return cb(id); }); + }; + Cache.prototype.setDependency = function (importee, importer) { + // importer -> importee + this.dependencyTree.setEdge(importer, importee); + }; + Cache.prototype.lastDependencySet = function () { + this.treeComplete = true; + }; + Cache.prototype.markAsDirty = function (id, _snapshot) { + this.dependencyTree.setNode(id, { dirty: true }); + }; + // returns true if node or any of its imports changed + Cache.prototype.isDirty = function (id, _snapshot, checkImports) { + var _this = this; + var label = this.dependencyTree.node(id); + if (checkImports || label.dirty) + return label.dirty; + var dependencies = alg.dijkstra(this.dependencyTree, id); + return some(keys(dependencies), function (dependencyId) { return _this.dependencyTree.node(dependencyId).dirty; }); + }; + Cache.prototype.getCompiled = function (id, snapshot, transform) { + var path$$1 = this.makePath(id, snapshot); + if (!existsSync(path$$1) || this.isDirty(id, snapshot, false)) { + 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)) { + var data = 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) { + var data = snapshot.getText(0, snapshot.getLength()); + return this.cacheRoot + "/" + sha1({ data: data, id: id }); + }; + return Cache; +}()); + function getOptionsOverrides() { return { module: ModuleKind.ES2015, @@ -68,15 +179,15 @@ function parseTsConfig() { var configParseResult = parseJsonConfigFileContent(result.config, sys, dirname(fileName), getOptionsOverrides(), fileName); return configParseResult; } -function printDiagnostics(context, diagnostics) { +function printDiagnostics(diagnostics) { diagnostics.forEach(function (diagnostic) { var message = flattenDiagnosticMessageText(diagnostic.messageText, "\n"); if (diagnostic.file) { var _a = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start), line = _a.line, character = _a.character; - context.warn({ message: diagnostic.file.fileName + " (" + (line + 1) + "," + (character + 1) + "): " + message }); + console.log(diagnostic.file.fileName + " (" + (line + 1) + "," + (character + 1) + "): " + message); } else - context.warn({ message: message }); + console.log(message); }); } @@ -86,21 +197,12 @@ function typescript(options) { delete options.include; delete options.exclude; var parsedConfig = parseTsConfig(); - var servicesHost = { - getScriptFileNames: function () { return parsedConfig.fileNames; }, - getScriptVersion: function (_fileName) { return "0"; }, - getScriptSnapshot: function (fileName) { - if (!existsSync(fileName)) - return undefined; - return ScriptSnapshot.fromString(sys.readFile(fileName)); - }, - getCurrentDirectory: function () { return process.cwd(); }, - getCompilationSettings: function () { return parsedConfig.options; }, - getDefaultLibFileName: function (opts) { return getDefaultLibFilePath(opts); }, - }; + var servicesHost = new LanguageServiceHost(parsedConfig); var services = createLanguageService(servicesHost, createDocumentRegistry()); + var cache = new Cache(process.cwd(), parsedConfig.options, parsedConfig.fileNames); return { resolveId: function (importee, importer) { + cache.setDependency(importee, importer); if (importee === TSLIB) return "\0" + TSLIB; if (!importer) @@ -117,24 +219,41 @@ function typescript(options) { load: function (id) { if (id === "\0" + TSLIB) return tslibSource; + return undefined; }, - transform: function (_code, id) { + transform: function (code, id) { + var _this = this; if (!filter(id)) return null; - var output = services.getEmitOutput(id); - var allDiagnostics = services - .getCompilerOptionsDiagnostics() - .concat(services.getSyntacticDiagnostics(id)) - .concat(services.getSemanticDiagnostics(id)); - printDiagnostics(this, allDiagnostics); - if (output.emitSkipped) - this.error({ message: "failed to transpile " + id }); - var code = find(output.outputFiles, function (entry) { return endsWith(entry.name, ".js"); }); - var map = find(output.outputFiles, function (entry) { return endsWith(entry.name, ".map"); }); - return { - code: code ? code.text : undefined, - map: map ? JSON.parse(map.text) : { mappings: "" }, - }; + var snapshot = servicesHost.setSnapshot(id, code); + var result = cache.getCompiled(id, snapshot, function () { + var output = services.getEmitOutput(id); + if (output.emitSkipped) + _this.error({ message: "failed to transpile " + id }); + var transpiled = find(output.outputFiles, function (entry) { return endsWith(entry.name, ".js"); }); + var map = find(output.outputFiles, function (entry) { return endsWith(entry.name, ".map"); }); + return { + code: transpiled ? transpiled.text : undefined, + map: map ? JSON.parse(map.text) : { mappings: "" }, + }; + }); + return result; + }, + outro: function () { + cache.lastDependencySet(); + cache.walkTree(function (id) { + var snapshot = servicesHost.getScriptSnapshot(id); + var diagnostics = cache.getDiagnostics(id, snapshot, function () { + return services + .getCompilerOptionsDiagnostics() + .concat(services.getSyntacticDiagnostics(id)) + .concat(services.getSemanticDiagnostics(id)); + }); + if (diagnostics.length !== 0) { + console.log(id); + printDiagnostics(diagnostics); + } + }); }, }; } diff --git a/package.json b/package.json index bf2fa43..fa05e13 100644 --- a/package.json +++ b/package.json @@ -28,16 +28,17 @@ "lodash": "^4.17.4", "graphlib": "^2.1.1", "object-hash": "^1.1.5", - "rollup-pluginutils": "^2.0.1", + "rollup-pluginutils": "^2.0.1" }, "peerDependencies": { "typescript": "^2.0" }, "devDependencies": { "@alexlur/rollup-plugin-typescript": "^0.8.1", + "@types/graphlib": "^2.1.3", "@types/lodash": "^4.14.52", "@types/node": "^6.0.53", - "@types/graphlib": "^2.1.3", + "@types/object-hash": "^0.5.28", "rimraf": "^2.5.4", "rollup": "^0.41.4", "tslint": "^4.4.2", diff --git a/src/cache.ts b/src/cache.ts index e2b9030..f07b056 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -4,7 +4,7 @@ import * as hash from "object-hash"; import * as fs from "fs"; import * as _ from "lodash"; -interface ICode +export interface ICode { code: string; map: string; @@ -39,6 +39,11 @@ export class Cache this.dependencyTree.setDefaultNodeLabel((_node: string) => { return { dirty: false }; }); } + public walkTree(cb: (id: string) => void | false) + { + _.each(graph.alg.topsort(this.dependencyTree), (id: string) => cb(id)); + } + public setDependency(importee: string, importer: string): void { // importer -> importee diff --git a/src/host.ts b/src/host.ts new file mode 100644 index 0000000..3856664 --- /dev/null +++ b/src/host.ts @@ -0,0 +1,59 @@ +import * as fs from "fs"; +import * as ts from "typescript"; +import * as _ from "lodash"; + +export class LanguageServiceHost implements ts.LanguageServiceHost +{ + private cwd = process.cwd(); + private snapshots: { [fileName: string]: ts.IScriptSnapshot } = {}; + + constructor(private parsedConfig: ts.ParsedCommandLine) + { + } + + public setSnapshot(fileName: string, data: string): ts.IScriptSnapshot + { + let snapshot = ts.ScriptSnapshot.fromString(data); + this.snapshots[fileName] = snapshot; + return snapshot; + } + + public getScriptSnapshot(fileName: string): ts.IScriptSnapshot | undefined + { + if (_.has(this.snapshots, fileName)) + return this.snapshots[fileName]; + + if (fs.existsSync(fileName)) + { + this.snapshots[fileName] = ts.ScriptSnapshot.fromString(ts.sys.readFile(fileName)); + return this.snapshots[fileName]; + } + + return undefined; + } + + public getCurrentDirectory() + { + return this.cwd; + } + + public getScriptVersion(_fileName: string) + { + return "0"; + } + + public getScriptFileNames() + { + return this.parsedConfig.fileNames; + } + + public getCompilationSettings(): ts.CompilerOptions + { + return this.parsedConfig.options; + } + + public getDefaultLibFileName(opts: ts.CompilerOptions) + { + return ts.getDefaultLibFilePath(opts); + } +} diff --git a/src/index.ts b/src/index.ts index 253a2cd..35ed8ce 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,5 @@ +import { LanguageServiceHost } from "./host"; +import { Cache, ICode } from "./cache"; import * as ts from "typescript"; import { createFilter } from "rollup-pluginutils"; import * as fs from "fs"; @@ -77,7 +79,7 @@ interface Context warn(message: Message): void; error(message: Message): void; } -function printDiagnostics(context: Context, diagnostics: ts.Diagnostic[]) +function printDiagnostics(diagnostics: ts.Diagnostic[]) { diagnostics.forEach((diagnostic) => { @@ -85,10 +87,10 @@ function printDiagnostics(context: Context, diagnostics: ts.Diagnostic[]) if (diagnostic.file) { let { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start); - context.warn({ message: `${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}` }); + console.log(`${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`); } else - context.warn({ message }); + console.log(message); }); }; @@ -103,27 +105,18 @@ export default function typescript (options: any) let parsedConfig = parseTsConfig(); - const servicesHost: ts.LanguageServiceHost = { - getScriptFileNames: () => parsedConfig.fileNames, - getScriptVersion: (_fileName) => "0", - getScriptSnapshot: (fileName) => - { - if (!fs.existsSync(fileName)) - return undefined; - - return ts.ScriptSnapshot.fromString(ts.sys.readFile(fileName)); - }, - getCurrentDirectory: () => process.cwd(), - getCompilationSettings: () => parsedConfig.options, - getDefaultLibFileName: (opts) => ts.getDefaultLibFilePath(opts), - }; + const servicesHost = new LanguageServiceHost(parsedConfig); const services = ts.createLanguageService(servicesHost, ts.createDocumentRegistry()); + const cache = new Cache(process.cwd(), parsedConfig.options, parsedConfig.fileNames); + return { resolveId(importee: string, importer: string) { + cache.setDependency(importee, importer); + if (importee === TSLIB) return "\0" + TSLIB; @@ -145,35 +138,60 @@ export default function typescript (options: any) return null; }, - load(id: string): any + load(id: string): string | undefined { if (id === "\0" + TSLIB) return tslibSource; + + return undefined; }, - transform(this: Context, _code: string, id: string): any + transform(this: Context, code: string, id: string): ICode | null { - if (!filter(id)) return null; + if (!filter(id)) + return null; - let output = services.getEmitOutput(id); + const snapshot = servicesHost.setSnapshot(id, code); - let allDiagnostics = services - .getCompilerOptionsDiagnostics() - .concat(services.getSyntacticDiagnostics(id)) - .concat(services.getSemanticDiagnostics(id)); + let result = cache.getCompiled(id, snapshot, () => + { + const output = services.getEmitOutput(id); - printDiagnostics(this, allDiagnostics); + if (output.emitSkipped) + this.error({ message: `failed to transpile ${id}`}); - if (output.emitSkipped) - this.error({ message: `failed to transpile ${id}`}); + const transpiled: ts.OutputFile = _.find(output.outputFiles, (entry: ts.OutputFile) => _.endsWith(entry.name, ".js") ); + const map: ts.OutputFile = _.find(output.outputFiles, (entry: ts.OutputFile) => _.endsWith(entry.name, ".map") ); - const code: ts.OutputFile = _.find(output.outputFiles, (entry: ts.OutputFile) => _.endsWith(entry.name, ".js") ); - const map: ts.OutputFile = _.find(output.outputFiles, (entry: ts.OutputFile) => _.endsWith(entry.name, ".map") ); + return { + code: transpiled ? transpiled.text : undefined, + map: map ? JSON.parse(map.text) : { mappings: "" }, + }; + }); - return { - code: code ? code.text : undefined, - map: map ? JSON.parse(map.text) : { mappings: "" }, - }; + return result; + }, + + outro(): void + { + cache.lastDependencySet(); + + cache.walkTree((id: string) => + { + const snapshot = servicesHost.getScriptSnapshot(id); + const diagnostics = cache.getDiagnostics(id, snapshot, () => + { + return services + .getCompilerOptionsDiagnostics() + .concat(services.getSyntacticDiagnostics(id)) + .concat(services.getSemanticDiagnostics(id)); + }); + if (diagnostics.length !== 0) + { + console.log(id); + printDiagnostics(diagnostics); + } + }); }, }; } From 3cc2c6a8e2b3f9709fa2b209607c95b3c8090f7a Mon Sep 17 00:00:00 2001 From: ezolenko Date: Wed, 8 Feb 2017 23:22:57 -0700 Subject: [PATCH 07/11] - cache --- dist/rollup-plugin-typescript2.cjs.js | 90 ++++++++++++++++--------- dist/rollup-plugin-typescript2.es.js | 97 +++++++++++++++++---------- package.json | 8 ++- rollup.config.js | 3 +- src/cache.ts | 94 +++++++++++++++++++------- src/index.ts | 60 +++++++++-------- tsconfig.json | 3 +- 7 files changed, 232 insertions(+), 123 deletions(-) diff --git a/dist/rollup-plugin-typescript2.cjs.js b/dist/rollup-plugin-typescript2.cjs.js index 67156af..2853d80 100644 --- a/dist/rollup-plugin-typescript2.cjs.js +++ b/dist/rollup-plugin-typescript2.cjs.js @@ -6,6 +6,7 @@ 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'); @@ -63,23 +64,25 @@ var Cache = (function () { function Cache(cacheRoot, options, rootFilenames) { this.cacheRoot = cacheRoot; this.options = options; + this.cacheVersion = "0"; this.treeComplete = false; - this.cacheRoot = this.cacheRoot + "/" + hash.sha1({ rootFilenames: rootFilenames, options: this.options }); - fs.mkdirSync(this.cacheRoot); - var dependencyTreeFile = this.cacheRoot + "/tree"; - if (fs.existsSync(dependencyTreeFile)) { - var data = fs.readFileSync(this.cacheRoot + "/tree", "utf8"); - this.dependencyTree = graph.json.read(JSON.parse(data)); - } - else - this.dependencyTree = new graph.Graph({ directed: true }); + this.cacheRoot = this.cacheRoot + "/" + hash.sha1({ version: this.cacheVersion, rootFilenames: rootFilenames, options: this.options }); + mkdirp.sync(this.cacheRoot); + this.dependencyTree = new graph.Graph({ directed: true }); this.dependencyTree.setDefaultNodeLabel(function (_node) { return { dirty: false }; }); } Cache.prototype.walkTree = function (cb) { - _.each(graph.alg.topsort(this.dependencyTree), function (id) { return cb(id); }); + var acyclic = graph.alg.isAcyclic(this.dependencyTree); + if (acyclic) { + _.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) { - // importer -> importee + // console.log(importer, "->", importee); + // importee -> importer this.dependencyTree.setEdge(importer, importee); }; Cache.prototype.lastDependencySet = function () { @@ -92,14 +95,25 @@ var Cache = (function () { Cache.prototype.isDirty = function (id, _snapshot, checkImports) { var _this = this; var label = this.dependencyTree.node(id); - if (checkImports || label.dirty) + if (!label) + return false; + if (!checkImports || label.dirty) return label.dirty; var dependencies = graph.alg.dijkstra(this.dependencyTree, id); - return _.some(_.keys(dependencies), function (dependencyId) { return _this.dependencyTree.node(dependencyId).dirty; }); + return _.some(dependencies, function (dependency, node) { + if (!node || dependency.distance === Infinity) + return false; + var l = _this.dependencyTree.node(node); + var dirty = l === undefined ? true : l.dirty; + if (dirty) + console.log("dirty: " + id + " -> " + node); + 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; @@ -109,7 +123,8 @@ var Cache = (function () { 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)) { - var data = check(); + // console.log(`diagnostics cache miss: ${id}`); + var data = this.convert(check()); this.setCache(path$$1, id, snapshot, data); return data; } @@ -125,6 +140,18 @@ var Cache = (function () { var data = snapshot.getText(0, snapshot.getLength()); return this.cacheRoot + "/" + hash.sha1({ data: data, id: id }); }; + Cache.prototype.convert = function (data) { + return _.map(data, function (diagnostic) { + var entry = { + flatMessage: ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n"), + }; + 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) + ")"; + } + return entry; + }); + }; return Cache; }()); @@ -170,35 +197,31 @@ catch (e) { } function parseTsConfig() { var fileName = findFile(process.cwd(), "tsconfig.json"); + if (!fileName) + throw new Error("couldn't find 'tsconfig.json' in " + process.cwd()); var text = ts.sys.readFile(fileName); var result = ts.parseConfigFileTextToJson(fileName, text); var configParseResult = ts.parseJsonConfigFileContent(result.config, ts.sys, path.dirname(fileName), getOptionsOverrides(), fileName); return configParseResult; } function printDiagnostics(diagnostics) { - diagnostics.forEach(function (diagnostic) { - var message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n"); - if (diagnostic.file) { - var _a = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start), line = _a.line, character = _a.character; - console.log(diagnostic.file.fileName + " (" + (line + 1) + "," + (character + 1) + "): " + message); - } + _.each(diagnostics, function (diagnostic) { + if (diagnostic.fileLine) + console.log(diagnostic.fileLine + ": " + diagnostic.flatMessage); else - console.log(message); + console.log(diagnostic.flatMessage); }); } function typescript(options) { options = __assign({}, options); var filter = rollupPluginutils.createFilter(options.include || ["*.ts+(|x)", "**/*.ts+(|x)"], options.exclude || ["*.d.ts", "**/*.d.ts"]); - delete options.include; - delete options.exclude; var parsedConfig = parseTsConfig(); var servicesHost = new LanguageServiceHost(parsedConfig); var services = ts.createLanguageService(servicesHost, ts.createDocumentRegistry()); - var cache = new Cache(process.cwd(), parsedConfig.options, parsedConfig.fileNames); + var cache = new Cache(process.cwd() + "/.rts2_cache", parsedConfig.options, parsedConfig.fileNames); return { resolveId: function (importee, importer) { - cache.setDependency(importee, importer); if (importee === TSLIB) return "\0" + TSLIB; if (!importer) @@ -208,6 +231,8 @@ function typescript(options) { 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); return result.resolvedModule.resolvedFileName; } return null; @@ -220,17 +245,17 @@ function typescript(options) { transform: function (code, id) { var _this = this; if (!filter(id)) - return null; + return undefined; var snapshot = servicesHost.setSnapshot(id, code); var result = cache.getCompiled(id, snapshot, function () { var output = services.getEmitOutput(id); if (output.emitSkipped) _this.error({ message: "failed to transpile " + id }); var transpiled = _.find(output.outputFiles, function (entry) { return _.endsWith(entry.name, ".js"); }); - var map = _.find(output.outputFiles, function (entry) { return _.endsWith(entry.name, ".map"); }); + var map$$1 = _.find(output.outputFiles, function (entry) { return _.endsWith(entry.name, ".map"); }); return { code: transpiled ? transpiled.text : undefined, - map: map ? JSON.parse(map.text) : { mappings: "" }, + map: map$$1 ? JSON.parse(map$$1.text) : { mappings: "" }, }; }); return result; @@ -239,16 +264,17 @@ function typescript(options) { cache.lastDependencySet(); cache.walkTree(function (id) { var snapshot = servicesHost.getScriptSnapshot(id); + if (!snapshot) { + console.log("failed lo load snapshot for " + id); + return; + } var diagnostics = cache.getDiagnostics(id, snapshot, function () { return services .getCompilerOptionsDiagnostics() .concat(services.getSyntacticDiagnostics(id)) .concat(services.getSemanticDiagnostics(id)); }); - if (diagnostics.length !== 0) { - console.log(id); - printDiagnostics(diagnostics); - } + printDiagnostics(diagnostics); }); }, }; diff --git a/dist/rollup-plugin-typescript2.es.js b/dist/rollup-plugin-typescript2.es.js index a09aa01..36c1ba8 100644 --- a/dist/rollup-plugin-typescript2.es.js +++ b/dist/rollup-plugin-typescript2.es.js @@ -1,14 +1,16 @@ /* eslint-disable */ -import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs'; +import { existsSync, readFileSync, writeFileSync } from 'fs'; import * as fs from 'fs'; import { ModuleKind, ScriptSnapshot, createDocumentRegistry, createLanguageService, flattenDiagnosticMessageText, getDefaultLibFilePath, nodeModuleNameResolver, parseConfigFileTextToJson, parseJsonConfigFileContent, sys } from 'typescript'; import * as ts from 'typescript'; -import { each, endsWith, find, has, keys, some } from 'lodash'; +import { each, endsWith, find, has, map, some } from 'lodash'; import * as _ from 'lodash'; -import { Graph, alg, json } from 'graphlib'; +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'; @@ -67,23 +69,25 @@ var Cache = (function () { function Cache(cacheRoot, options, rootFilenames) { this.cacheRoot = cacheRoot; this.options = options; + this.cacheVersion = "0"; this.treeComplete = false; - this.cacheRoot = this.cacheRoot + "/" + sha1({ rootFilenames: rootFilenames, options: this.options }); - mkdirSync(this.cacheRoot); - var dependencyTreeFile = this.cacheRoot + "/tree"; - if (existsSync(dependencyTreeFile)) { - var data = readFileSync(this.cacheRoot + "/tree", "utf8"); - this.dependencyTree = json.read(JSON.parse(data)); - } - else - this.dependencyTree = new Graph({ directed: true }); + this.cacheRoot = this.cacheRoot + "/" + sha1({ version: this.cacheVersion, rootFilenames: rootFilenames, options: this.options }); + sync(this.cacheRoot); + this.dependencyTree = new Graph({ directed: true }); this.dependencyTree.setDefaultNodeLabel(function (_node) { return { dirty: false }; }); } Cache.prototype.walkTree = function (cb) { - each(alg.topsort(this.dependencyTree), function (id) { return cb(id); }); + var acyclic = alg.isAcyclic(this.dependencyTree); + if (acyclic) { + 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) { - // importer -> importee + // console.log(importer, "->", importee); + // importee -> importer this.dependencyTree.setEdge(importer, importee); }; Cache.prototype.lastDependencySet = function () { @@ -96,14 +100,25 @@ var Cache = (function () { Cache.prototype.isDirty = function (id, _snapshot, checkImports) { var _this = this; var label = this.dependencyTree.node(id); - if (checkImports || label.dirty) + if (!label) + return false; + if (!checkImports || label.dirty) return label.dirty; var dependencies = alg.dijkstra(this.dependencyTree, id); - return some(keys(dependencies), function (dependencyId) { return _this.dependencyTree.node(dependencyId).dirty; }); + return some(dependencies, function (dependency, node) { + if (!node || dependency.distance === Infinity) + return false; + var l = _this.dependencyTree.node(node); + var dirty = l === undefined ? true : l.dirty; + if (dirty) + console.log("dirty: " + id + " -> " + node); + 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; @@ -113,7 +128,8 @@ var Cache = (function () { Cache.prototype.getDiagnostics = function (id, snapshot, check) { var path$$1 = this.makePath(id, snapshot) + ".diagnostics"; if (!existsSync(path$$1) || this.isDirty(id, snapshot, true)) { - var data = check(); + // console.log(`diagnostics cache miss: ${id}`); + var data = this.convert(check()); this.setCache(path$$1, id, snapshot, data); return data; } @@ -129,6 +145,18 @@ var Cache = (function () { var data = snapshot.getText(0, snapshot.getLength()); return this.cacheRoot + "/" + sha1({ data: data, id: id }); }; + Cache.prototype.convert = function (data) { + return map(data, function (diagnostic) { + var entry = { + flatMessage: flattenDiagnosticMessageText(diagnostic.messageText, "\n"), + }; + 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) + ")"; + } + return entry; + }); + }; return Cache; }()); @@ -174,35 +202,31 @@ catch (e) { } function parseTsConfig() { var fileName = findFile(process.cwd(), "tsconfig.json"); + if (!fileName) + throw new Error("couldn't find 'tsconfig.json' in " + process.cwd()); var text = sys.readFile(fileName); var result = parseConfigFileTextToJson(fileName, text); var configParseResult = parseJsonConfigFileContent(result.config, sys, dirname(fileName), getOptionsOverrides(), fileName); return configParseResult; } function printDiagnostics(diagnostics) { - diagnostics.forEach(function (diagnostic) { - var message = flattenDiagnosticMessageText(diagnostic.messageText, "\n"); - if (diagnostic.file) { - var _a = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start), line = _a.line, character = _a.character; - console.log(diagnostic.file.fileName + " (" + (line + 1) + "," + (character + 1) + "): " + message); - } + each(diagnostics, function (diagnostic) { + if (diagnostic.fileLine) + console.log(diagnostic.fileLine + ": " + diagnostic.flatMessage); else - console.log(message); + console.log(diagnostic.flatMessage); }); } function typescript(options) { options = __assign({}, options); var filter = createFilter(options.include || ["*.ts+(|x)", "**/*.ts+(|x)"], options.exclude || ["*.d.ts", "**/*.d.ts"]); - delete options.include; - delete options.exclude; var parsedConfig = parseTsConfig(); var servicesHost = new LanguageServiceHost(parsedConfig); var services = createLanguageService(servicesHost, createDocumentRegistry()); - var cache = new Cache(process.cwd(), parsedConfig.options, parsedConfig.fileNames); + var cache = new Cache(process.cwd() + "/.rts2_cache", parsedConfig.options, parsedConfig.fileNames); return { resolveId: function (importee, importer) { - cache.setDependency(importee, importer); if (importee === TSLIB) return "\0" + TSLIB; if (!importer) @@ -212,6 +236,8 @@ function typescript(options) { 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); return result.resolvedModule.resolvedFileName; } return null; @@ -224,17 +250,17 @@ function typescript(options) { transform: function (code, id) { var _this = this; if (!filter(id)) - return null; + return undefined; var snapshot = servicesHost.setSnapshot(id, code); var result = cache.getCompiled(id, snapshot, function () { var output = services.getEmitOutput(id); if (output.emitSkipped) _this.error({ message: "failed to transpile " + id }); var transpiled = find(output.outputFiles, function (entry) { return endsWith(entry.name, ".js"); }); - var map = find(output.outputFiles, function (entry) { return endsWith(entry.name, ".map"); }); + var map$$1 = find(output.outputFiles, function (entry) { return endsWith(entry.name, ".map"); }); return { code: transpiled ? transpiled.text : undefined, - map: map ? JSON.parse(map.text) : { mappings: "" }, + map: map$$1 ? JSON.parse(map$$1.text) : { mappings: "" }, }; }); return result; @@ -243,16 +269,17 @@ function typescript(options) { cache.lastDependencySet(); cache.walkTree(function (id) { var snapshot = servicesHost.getScriptSnapshot(id); + if (!snapshot) { + console.log("failed lo load snapshot for " + id); + return; + } var diagnostics = cache.getDiagnostics(id, snapshot, function () { return services .getCompilerOptionsDiagnostics() .concat(services.getSyntacticDiagnostics(id)) .concat(services.getSemanticDiagnostics(id)); }); - if (diagnostics.length !== 0) { - console.log(id); - printDiagnostics(diagnostics); - } + printDiagnostics(diagnostics); }); }, }; diff --git a/package.json b/package.json index fa05e13..53b1d34 100644 --- a/package.json +++ b/package.json @@ -24,11 +24,12 @@ "postinstall": "typings install" }, "dependencies": { - "tslib": "^1.5.0", - "lodash": "^4.17.4", "graphlib": "^2.1.1", + "lodash": "^4.17.4", + "mkdirp": "^0.5.1", "object-hash": "^1.1.5", - "rollup-pluginutils": "^2.0.1" + "rollup-pluginutils": "^2.0.1", + "tslib": "^1.5.0" }, "peerDependencies": { "typescript": "^2.0" @@ -37,6 +38,7 @@ "@alexlur/rollup-plugin-typescript": "^0.8.1", "@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 f307304..cc1330b 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -13,7 +13,8 @@ export default { 'typescript', 'lodash', 'graphlib', - 'object-hash' + 'object-hash', + 'mkdirp' ], plugins: [ diff --git a/src/cache.ts b/src/cache.ts index f07b056..44bfbe4 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -3,11 +3,12 @@ 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"; export interface ICode { - code: string; - map: string; + code: string | undefined; + map: string | undefined; } interface INodeLabel @@ -16,46 +17,55 @@ interface INodeLabel hash?: string; } +export interface IDiagnostics +{ + flatMessage: string; + fileLine?: string; +} + export class Cache { + private cacheVersion = "0"; private dependencyTree: graph.Graph; private treeComplete: boolean = false; constructor(private cacheRoot: string, private options: ts.CompilerOptions, rootFilenames: string[]) { - this.cacheRoot = `${this.cacheRoot}/${hash.sha1({ rootFilenames, options: this.options })}`; - fs.mkdirSync(this.cacheRoot); + this.cacheRoot = `${this.cacheRoot}/${hash.sha1({ version: this.cacheVersion, rootFilenames, options: this.options })}`; - let dependencyTreeFile = `${this.cacheRoot}/tree`; - if (fs.existsSync(dependencyTreeFile)) - { - let data = fs.readFileSync(`${this.cacheRoot}/tree`, "utf8"); - - this.dependencyTree = graph.json.read(JSON.parse(data)); - } - else - this.dependencyTree = new graph.Graph({ directed: true }); + mkdirp.sync(this.cacheRoot); + this.dependencyTree = new graph.Graph({ directed: true }); this.dependencyTree.setDefaultNodeLabel((_node: string) => { return { dirty: false }; }); } - public walkTree(cb: (id: string) => void | false) + public walkTree(cb: (id: string) => void | false): void { - _.each(graph.alg.topsort(this.dependencyTree), (id: string) => cb(id)); + const acyclic = graph.alg.isAcyclic(this.dependencyTree); + + if (acyclic) + { + _.each(graph.alg.topsort(this.dependencyTree), (id: string) => cb(id)); + 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 { - // importer -> importee + // console.log(importer, "->", importee); + // importee -> importer this.dependencyTree.setEdge(importer, importee); } - public lastDependencySet() + public lastDependencySet(): void { this.treeComplete = true; } - public markAsDirty(id: string, _snapshot: ts.IScriptSnapshot) + public markAsDirty(id: string, _snapshot: ts.IScriptSnapshot): void { this.dependencyTree.setNode(id, { dirty: true }); } @@ -65,12 +75,27 @@ export class Cache { let label = this.dependencyTree.node(id) as INodeLabel; - if (checkImports || label.dirty) + if (!label) + return false; + + if (!checkImports || label.dirty) return label.dirty; let dependencies = graph.alg.dijkstra(this.dependencyTree, id); - return _.some(_.keys(dependencies), (dependencyId: string) => (this.dependencyTree.node(dependencyId) as INodeLabel).dirty); + return _.some(dependencies, (dependency, node) => + { + if (!node || dependency.distance === Infinity) + return false; + + let l = this.dependencyTree.node(node) as INodeLabel | undefined; + let dirty = l === undefined ? true : l.dirty; + + if (dirty) + console.log(`dirty: ${id} -> ${node}`); + + return dirty; + }); } public getCompiled(id: string, snapshot: ts.IScriptSnapshot, transform: () => ICode | undefined): ICode | undefined @@ -79,6 +104,7 @@ export class Cache 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; @@ -87,21 +113,22 @@ export class Cache return JSON.parse(fs.readFileSync(path, "utf8")) as ICode; } - public getDiagnostics(id: string, snapshot: ts.IScriptSnapshot, check: () => ts.Diagnostic[]): ts.Diagnostic[] + 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)) { - let data = check(); + // 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 ts.Diagnostic[]; + return JSON.parse(fs.readFileSync(path, "utf8")) as IDiagnostics[]; } - private setCache(path: string, id: string, snapshot: ts.IScriptSnapshot, data: ts.Diagnostic[] | ICode | undefined): void + private setCache(path: string, id: string, snapshot: ts.IScriptSnapshot, data: IDiagnostics[] | ICode | undefined): void { if (data === undefined) return; @@ -116,4 +143,23 @@ export class Cache let data = snapshot.getText(0, snapshot.getLength()); return `${this.cacheRoot}/${hash.sha1({ data, id })}`; } + + private convert(data: ts.Diagnostic[]): IDiagnostics[] + { + return _.map(data, (diagnostic) => + { + let entry: IDiagnostics = + { + flatMessage: ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n"), + }; + + if (diagnostic.file) + { + let { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start); + entry.fileLine = `${diagnostic.file.fileName} (${line + 1},${character + 1})`; + } + + return entry; + }); + } } diff --git a/src/index.ts b/src/index.ts index 35ed8ce..78ff31a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,5 +1,5 @@ import { LanguageServiceHost } from "./host"; -import { Cache, ICode } from "./cache"; +import { Cache, ICode, IDiagnostics } from "./cache"; import * as ts from "typescript"; import { createFilter } from "rollup-pluginutils"; import * as fs from "fs"; @@ -63,6 +63,9 @@ try function parseTsConfig() { const fileName = findFile(process.cwd(), "tsconfig.json"); + if (!fileName) + throw new Error(`couldn't find 'tsconfig.json' in ${process.cwd()}`); + const text = ts.sys.readFile(fileName); const result = ts.parseConfigFileTextToJson(fileName, text); const configParseResult = ts.parseJsonConfigFileContent(result.config, ts.sys, path.dirname(fileName), getOptionsOverrides(), fileName); @@ -79,44 +82,41 @@ interface Context warn(message: Message): void; error(message: Message): void; } -function printDiagnostics(diagnostics: ts.Diagnostic[]) +function printDiagnostics(diagnostics: IDiagnostics[]) { - diagnostics.forEach((diagnostic) => + _.each(diagnostics, (diagnostic) => { - let message = ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n"); - if (diagnostic.file) - { - let { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start); - console.log(`${diagnostic.file.fileName} (${line + 1},${character + 1}): ${message}`); - } + if (diagnostic.fileLine) + console.log(`${diagnostic.fileLine}: ${diagnostic.flatMessage}`); else - console.log(message); + console.log(diagnostic.flatMessage); }); }; -export default function typescript (options: any) +interface IOptions +{ + include?: string; + exclude?: string; +} + +export default function typescript (options: IOptions) { options = { ... options }; const filter = createFilter(options.include || [ "*.ts+(|x)", "**/*.ts+(|x)" ], options.exclude || [ "*.d.ts", "**/*.d.ts" ]); - delete options.include; - delete options.exclude; - let parsedConfig = parseTsConfig(); const servicesHost = new LanguageServiceHost(parsedConfig); const services = ts.createLanguageService(servicesHost, ts.createDocumentRegistry()); - const cache = new Cache(process.cwd(), parsedConfig.options, parsedConfig.fileNames); + const cache = new Cache(`${process.cwd()}/.rts2_cache`, parsedConfig.options, parsedConfig.fileNames); return { resolveId(importee: string, importer: string) { - cache.setDependency(importee, importer); - if (importee === TSLIB) return "\0" + TSLIB; @@ -132,6 +132,9 @@ export default function typescript (options: any) if (_.endsWith(result.resolvedModule.resolvedFileName, ".d.ts")) return null; + if (filter(result.resolvedModule.resolvedFileName)) + cache.setDependency(result.resolvedModule.resolvedFileName, importer); + return result.resolvedModule.resolvedFileName; } @@ -146,13 +149,12 @@ export default function typescript (options: any) return undefined; }, - transform(this: Context, code: string, id: string): ICode | null + transform(this: Context, code: string, id: string): ICode | undefined { if (!filter(id)) - return null; + return undefined; const snapshot = servicesHost.setSnapshot(id, code); - let result = cache.getCompiled(id, snapshot, () => { const output = services.getEmitOutput(id); @@ -160,8 +162,8 @@ export default function typescript (options: any) if (output.emitSkipped) this.error({ message: `failed to transpile ${id}`}); - const transpiled: ts.OutputFile = _.find(output.outputFiles, (entry: ts.OutputFile) => _.endsWith(entry.name, ".js") ); - const map: ts.OutputFile = _.find(output.outputFiles, (entry: ts.OutputFile) => _.endsWith(entry.name, ".map") ); + const transpiled = _.find(output.outputFiles, (entry: ts.OutputFile) => _.endsWith(entry.name, ".js") ); + const map = _.find(output.outputFiles, (entry: ts.OutputFile) => _.endsWith(entry.name, ".map") ); return { code: transpiled ? transpiled.text : undefined, @@ -179,6 +181,13 @@ export default function typescript (options: any) cache.walkTree((id: string) => { const snapshot = servicesHost.getScriptSnapshot(id); + + if (!snapshot) + { + console.log(`failed lo load snapshot for ${id}`); + return; + } + const diagnostics = cache.getDiagnostics(id, snapshot, () => { return services @@ -186,11 +195,8 @@ export default function typescript (options: any) .concat(services.getSyntacticDiagnostics(id)) .concat(services.getSemanticDiagnostics(id)); }); - if (diagnostics.length !== 0) - { - console.log(id); - printDiagnostics(diagnostics); - } + + printDiagnostics(diagnostics); }); }, }; diff --git a/tsconfig.json b/tsconfig.json index 7f6146b..26ac9c0 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -12,7 +12,8 @@ "listFiles": true, "pretty": true, "moduleResolution": "node", - "noEmitOnError": true + "noEmitOnError": true, + "strictNullChecks": true }, "include": [ "typings/**/*.d.ts", From 6df208b9c404acc7772066dba320f023cfb74ce0 Mon Sep 17 00:00:00 2001 From: ezolenko Date: Thu, 9 Feb 2017 23:09:43 -0700 Subject: [PATCH 08/11] - tracking global types --- dist/rollup-plugin-typescript2.cjs.js | 155 +++++++++++++++++-------- dist/rollup-plugin-typescript2.es.js | 160 +++++++++++++++++--------- package.json | 6 +- rollup.config.js | 5 +- src/cache.ts | 146 +++++++++++++---------- src/host.ts | 2 +- src/index.ts | 20 ++-- src/rollingcache.ts | 61 ++++++++++ 8 files changed, 377 insertions(+), 178 deletions(-) create mode 100644 src/rollingcache.ts 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, () => {} ); + }); + } +} From c485cd3aa630dbbf99a489b70ea4ded67c3d3ca8 Mon Sep 17 00:00:00 2001 From: Eugene Zolenko Date: Fri, 10 Feb 2017 17:40:27 -0700 Subject: [PATCH 09/11] - comments, safe colors --- src/cache.ts | 7 ++++--- src/index.ts | 10 +++++----- src/rollingcache.ts | 34 ++++++++++++++++++++++++++++++---- 3 files changed, 39 insertions(+), 12 deletions(-) diff --git a/src/cache.ts b/src/cache.ts index 6200291..bd6fdc9 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -2,6 +2,7 @@ import * as ts from "typescript"; import * as graph from "graphlib"; import * as hash from "object-hash"; import * as _ from "lodash"; +import * as colors from "colors/safe"; import { RollingCache } from "./rollingcache"; export interface ICode @@ -158,7 +159,7 @@ export class Cache let dirty = l === undefined ? true : l.dirty; if (dirty) - console.log(`dirty: ${id} -> ${node}`.gray); + console.log(colors.gray(`dirty: ${id} -> ${node}`)); return dirty; }); @@ -176,13 +177,13 @@ export class Cache { let entry: IDiagnostics = { - flatMessage: ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n").yellow, + flatMessage: ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n"), }; if (diagnostic.file) { let { line, character } = diagnostic.file.getLineAndCharacterOfPosition(diagnostic.start); - entry.fileLine = `${diagnostic.file.fileName} (${line + 1},${character + 1})`.white; + entry.fileLine = `${diagnostic.file.fileName} (${line + 1},${character + 1})`; } return entry; diff --git a/src/index.ts b/src/index.ts index defa952..363e62c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,7 +5,7 @@ import { createFilter } from "rollup-pluginutils"; import * as fs from "fs-extra"; import * as path from "path"; import * as _ from "lodash"; -import * as colors from "colors"; +import * as colors from "colors/safe"; function getOptionsOverrides(): ts.CompilerOptions { @@ -87,9 +87,9 @@ function printDiagnostics(diagnostics: IDiagnostics[]) _.each(diagnostics, (diagnostic) => { if (diagnostic.fileLine) - console.log(`${diagnostic.fileLine}: ${diagnostic.flatMessage}`); + console.log(`${diagnostic.fileLine}: ${colors.yellow(diagnostic.flatMessage)}`); else - console.log(diagnostic.flatMessage); + console.log(colors.yellow(diagnostic.flatMessage)); }); }; @@ -160,7 +160,7 @@ export default function typescript (options: IOptions) const output = services.getEmitOutput(id); if (output.emitSkipped) - this.error({ message: `failed to transpile ${id}`}); + this.error({ message: colors.red(`failed to transpile ${id}`)}); const transpiled = _.find(output.outputFiles, (entry: ts.OutputFile) => _.endsWith(entry.name, ".js") ); const map = _.find(output.outputFiles, (entry: ts.OutputFile) => _.endsWith(entry.name, ".map") ); @@ -184,7 +184,7 @@ export default function typescript (options: IOptions) if (!snapshot) { - console.log(`failed lo load snapshot for ${id}`); + console.log(colors.red(`failed lo load snapshot for ${id}`)); return; } diff --git a/src/rollingcache.ts b/src/rollingcache.ts index 638ee8d..aab4340 100644 --- a/src/rollingcache.ts +++ b/src/rollingcache.ts @@ -1,11 +1,19 @@ import * as fs from "fs-extra"; 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 { private oldCacheRoot: string; private newCacheRoot: string; + /** + * @param cacheRoot: root folder for the cache + * @param checkNewCache: whether to also look in new cache when reading from cache + */ constructor(private cacheRoot: string, private checkNewCache: boolean) { this.oldCacheRoot = `${this.cacheRoot}/cache`; @@ -14,6 +22,9 @@ export class RollingCache fs.emptyDirSync(this.newCacheRoot); } + /** + * @returns true if name exist in old cache (or either old of new cache if checkNewCache is true) + */ public exists(name: string): boolean { if (this.checkNewCache && fs.existsSync(`${this.newCacheRoot}/${name}`)) @@ -22,14 +33,20 @@ export class RollingCache return fs.existsSync(`${this.oldCacheRoot}/${name}`); } + /** + * @returns true if old cache contains all names and nothing more + */ public match(names: string[]): boolean { if (!fs.existsSync(this.oldCacheRoot)) - return false; + return names.length === 0; // empty folder matches return _.isEqual(fs.readdirSync(this.oldCacheRoot).sort(), names.sort()); } + /** + * @returns data for name, must exist in old cache (or either old of new cache if checkNewCache is true) + */ public read(name: string): DataType { if (this.checkNewCache && fs.existsSync(`${this.newCacheRoot}/${name}`)) @@ -43,19 +60,28 @@ export class RollingCache if (data === undefined) return; - fs.writeJson(`${this.newCacheRoot}/${name}`, data, { encoding: "utf8" }, () => {}); + if (this.checkNewCache) + 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) { - fs.ensureFile(`${this.newCacheRoot}/${name}`, () => {}); + if (this.checkNewCache) + fs.ensureFileSync(`${this.newCacheRoot}/${name}`); + else // won't be reading it this run + fs.ensureFile(`${this.newCacheRoot}/${name}`, () => { ; }); } + /** + * clears old cache and moves new in its place + */ public roll() { fs.remove(this.oldCacheRoot, () => { - fs.move(this.newCacheRoot, this.oldCacheRoot, () => {} ); + fs.move(this.newCacheRoot, this.oldCacheRoot, () => { ; }); }); } } From 37fe6093b7f6d494e2ed11f40269ce08dbfeca2a Mon Sep 17 00:00:00 2001 From: Eugene Zolenko Date: Fri, 10 Feb 2017 18:24:28 -0700 Subject: [PATCH 10/11] - options --- .vscode/settings.json | 3 +- dist/rollup-plugin-typescript2.cjs.js | 162 +++++++++++++++++++------ dist/rollup-plugin-typescript2.es.js | 167 ++++++++++++++++++++------ package.json | 6 +- rollup.config.js | 3 +- src/cache.ts | 42 +++++-- src/context.ts | 61 ++++++++++ src/index.ts | 84 +++++++------ 8 files changed, 397 insertions(+), 131 deletions(-) create mode 100644 src/context.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index f015944..90e1422 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,4 @@ { - "typescript.tsdk": "./node_modules/typescript/lib" + "editor.tabSize": 4, + "editor.useTabStops": true } \ No newline at end of file diff --git a/dist/rollup-plugin-typescript2.cjs.js b/dist/rollup-plugin-typescript2.cjs.js index 295d381..ba45c7d 100644 --- a/dist/rollup-plugin-typescript2.cjs.js +++ b/dist/rollup-plugin-typescript2.cjs.js @@ -8,6 +8,7 @@ var graph = require('graphlib'); var hash = require('object-hash'); var rollupPluginutils = require('rollup-pluginutils'); var path = require('path'); +var colors = require('colors/safe'); const __assign = Object.assign || function (target) { for (var source, i = 1; i < arguments.length; i++) { @@ -21,6 +22,42 @@ const __assign = Object.assign || function (target) { return target; }; +var VerbosityLevel; +(function (VerbosityLevel) { + VerbosityLevel[VerbosityLevel["Error"] = 0] = "Error"; + VerbosityLevel[VerbosityLevel["Warning"] = 1] = "Warning"; + VerbosityLevel[VerbosityLevel["Info"] = 2] = "Info"; + VerbosityLevel[VerbosityLevel["Debug"] = 3] = "Debug"; +})(VerbosityLevel || (VerbosityLevel = {})); +var ConsoleContext = (function () { + function ConsoleContext(verbosity, prefix) { + if (prefix === void 0) { prefix = ""; } + this.verbosity = verbosity; + this.prefix = prefix; + } + ConsoleContext.prototype.warn = function (message) { + if (this.verbosity < VerbosityLevel.Warning) + return; + console.log("" + this.prefix + message); + }; + ConsoleContext.prototype.error = function (message) { + if (this.verbosity < VerbosityLevel.Error) + return; + console.log("" + this.prefix + message); + }; + ConsoleContext.prototype.info = function (message) { + if (this.verbosity < VerbosityLevel.Info) + return; + console.log("" + this.prefix + message); + }; + ConsoleContext.prototype.debug = function (message) { + if (this.verbosity < VerbosityLevel.Debug) + return; + console.log("" + this.prefix + message); + }; + return ConsoleContext; +}()); + var LanguageServiceHost = (function () { function LanguageServiceHost(parsedConfig) { this.parsedConfig = parsedConfig; @@ -59,7 +96,15 @@ var LanguageServiceHost = (function () { return LanguageServiceHost; }()); +/** + * 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. + */ var RollingCache = (function () { + /** + * @param cacheRoot: root folder for the cache + * @param checkNewCache: whether to also look in new cache when reading from cache + */ function RollingCache(cacheRoot, checkNewCache) { this.cacheRoot = cacheRoot; this.checkNewCache = checkNewCache; @@ -67,16 +112,25 @@ var RollingCache = (function () { this.newCacheRoot = this.cacheRoot + "/cache_"; fs.emptyDirSync(this.newCacheRoot); } + /** + * @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.checkNewCache && fs.existsSync(this.newCacheRoot + "/" + name)) return true; return fs.existsSync(this.oldCacheRoot + "/" + name); }; + /** + * @returns true if old cache contains all names and nothing more + */ RollingCache.prototype.match = function (names) { if (!fs.existsSync(this.oldCacheRoot)) - return false; + return names.length === 0; // empty folder matches return _.isEqual(fs.readdirSync(this.oldCacheRoot).sort(), names.sort()); }; + /** + * @returns data for name, must exist in old cache (or either old of new cache if checkNewCache is true) + */ RollingCache.prototype.read = function (name) { if (this.checkNewCache && fs.existsSync(this.newCacheRoot + "/" + name)) return fs.readJsonSync(this.newCacheRoot + "/" + name, "utf8"); @@ -85,54 +139,72 @@ var RollingCache = (function () { RollingCache.prototype.write = function (name, data) { if (data === undefined) return; - fs.writeJson(this.newCacheRoot + "/" + name, data, { encoding: "utf8" }, function () { }); + if (this.checkNewCache) + fs.writeJsonSync(this.newCacheRoot + "/" + name, data); + else + fs.writeJson(this.newCacheRoot + "/" + name, data, { encoding: "utf8" }, function () { }); }; RollingCache.prototype.touch = function (name) { - fs.ensureFile(this.newCacheRoot + "/" + name, function () { }); + if (this.checkNewCache) + fs.ensureFileSync(this.newCacheRoot + "/" + name); + else + fs.ensureFile(this.newCacheRoot + "/" + name, function () { }); }; + /** + * 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 () { }); + fs.move(_this.newCacheRoot, _this.oldCacheRoot, function () { }); }); }; return RollingCache; }()); var Cache = (function () { - function Cache(host, cache, options, rootFilenames) { + function Cache(host, cache, options, rootFilenames, context) { var _this = this; this.host = host; this.options = options; + this.context = context; this.cacheVersion = "1"; - this.typesDirty = false; + this.ambientTypesDirty = 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"); }) + this.ambientTypes = _.filter(rootFilenames, function (file) { return _.endsWith(file, ".d.ts"); }) .map(function (id) { return { id: id, snapshot: _this.host.getScriptSnapshot(id) }; }); } + Cache.prototype.clean = function () { + this.context.info("cleaning cache: " + this.cacheDir); + fs.emptyDirSync(this.cacheDir); + }; Cache.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("import tree has cycles"); _.each(this.dependencyTree.nodes(), function (id) { return cb(id); }); }; Cache.prototype.setDependency = function (importee, importer) { // importee -> importer + this.context.debug(importee + " -> " + importer); this.dependencyTree.setEdge(importer, importee); }; Cache.prototype.compileDone = function () { var _this = this; - var typeNames = _.filter(this.types, function (snaphot) { return snaphot.snapshot !== undefined; }) + var typeNames = _.filter(this.ambientTypes, 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); + this.ambientTypesDirty = !this.typesCache.match(typeNames); + if (this.ambientTypesDirty) + this.context.info("ambient types changed, redoing all diagnostics"); _.each(typeNames, function (name) { return _this.typesCache.touch(name); }); }; Cache.prototype.diagnosticsDone = function () { @@ -143,12 +215,13 @@ var Cache = (function () { 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); + this.context.debug("fresh transpile for: " + id); var data_1 = transform(); this.codeCache.write(name, data_1); this.markAsDirty(id, snapshot); return data_1; } + this.context.debug("old transpile for: " + id); var data = this.codeCache.read(name); this.codeCache.write(name, data); return data; @@ -156,17 +229,19 @@ var Cache = (function () { 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); + this.context.debug("fresh diagnostics for: " + id); var data_2 = this.convert(check()); this.diagnosticsCache.write(name, data_2); this.markAsDirty(id, snapshot); return data_2; } + this.context.debug("old diagnostics for: " + id); var data = this.diagnosticsCache.read(name); this.diagnosticsCache.write(name, data); return data; }; Cache.prototype.markAsDirty = function (id, _snapshot) { + this.context.debug("changed: " + id); this.dependencyTree.setNode(id, { dirty: true }); }; // returns true if node or any of its imports or any of global types changed @@ -177,7 +252,7 @@ var Cache = (function () { return false; if (!checkImports || label.dirty) return label.dirty; - if (this.typesDirty) + if (this.ambientTypesDirty) return true; var dependencies = graph.alg.dijkstra(this.dependencyTree, id); return _.some(dependencies, function (dependency, node) { @@ -186,7 +261,7 @@ var Cache = (function () { var l = _this.dependencyTree.node(node); var dirty = l === undefined ? true : l.dirty; if (dirty) - console.log(("dirty: " + id + " -> " + node).gray); + _this.context.debug("import changed: " + id + " -> " + node); return dirty; }); }; @@ -197,11 +272,11 @@ var Cache = (function () { Cache.prototype.convert = function (data) { return _.map(data, function (diagnostic) { var entry = { - flatMessage: ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n").yellow, + flatMessage: ts.flattenDiagnosticMessageText(diagnostic.messageText, "\n"), }; 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) + ")").white; + entry.fileLine = diagnostic.file.fileName + " (" + (line + 1) + "," + (character + 1) + ")"; } return entry; }); @@ -223,17 +298,15 @@ function getOptionsOverrides() { // MIT Licenced function findFile(cwd, filename) { var fp = cwd ? (cwd + "/" + filename) : filename; - if (fs.existsSync(fp)) { + if (fs.existsSync(fp)) return fp; - } var segs = cwd.split(path.sep); var len = segs.length; while (len--) { cwd = segs.slice(0, len).join("/"); fp = cwd + "/" + filename; - if (fs.existsSync(fp)) { + if (fs.existsSync(fp)) return fp; - } } return null; } @@ -258,22 +331,33 @@ function parseTsConfig() { var configParseResult = ts.parseJsonConfigFileContent(result.config, ts.sys, path.dirname(fileName), getOptionsOverrides(), fileName); return configParseResult; } -function printDiagnostics(diagnostics) { +function printDiagnostics(context, diagnostics) { _.each(diagnostics, function (diagnostic) { if (diagnostic.fileLine) - console.log(diagnostic.fileLine + ": " + diagnostic.flatMessage); + context.warn(diagnostic.fileLine + ": " + colors.yellow(diagnostic.flatMessage)); else - console.log(diagnostic.flatMessage); + context.warn(colors.yellow(diagnostic.flatMessage)); }); } function typescript(options) { options = __assign({}, options); - var filter$$1 = rollupPluginutils.createFilter(options.include || ["*.ts+(|x)", "**/*.ts+(|x)"], options.exclude || ["*.d.ts", "**/*.d.ts"]); + _.defaults(options, { + check: true, + verbose: VerbosityLevel.Info, + clean: false, + cacheRoot: process.cwd() + "/.rts2_cache", + include: ["*.ts+(|x)", "**/*.ts+(|x)"], + exclude: ["*.d.ts", "**/*.d.ts"], + }); + var filter$$1 = rollupPluginutils.createFilter(options.include, options.exclude); var parsedConfig = parseTsConfig(); var servicesHost = new LanguageServiceHost(parsedConfig); var services = ts.createLanguageService(servicesHost, ts.createDocumentRegistry()); - var cache = new Cache(servicesHost, process.cwd() + "/.rts2_cache", parsedConfig.options, parsedConfig.fileNames); + var context = new ConsoleContext(options.verbose, ""); + var cache = new Cache(servicesHost, options.cacheRoot, parsedConfig.options, parsedConfig.fileNames, context); + if (options.clean) + cache.clean(); return { resolveId: function (importee, importer) { if (importee === TSLIB) @@ -304,7 +388,7 @@ function typescript(options) { var result = cache.getCompiled(id, snapshot, function () { var output = services.getEmitOutput(id); if (output.emitSkipped) - _this.error({ message: "failed to transpile " + id }); + _this.error({ message: colors.red("failed to transpile " + id) }); var transpiled = _.find(output.outputFiles, function (entry) { return _.endsWith(entry.name, ".js"); }); var map$$1 = _.find(output.outputFiles, function (entry) { return _.endsWith(entry.name, ".map"); }); return { @@ -316,20 +400,22 @@ function typescript(options) { }, outro: function () { cache.compileDone(); - cache.walkTree(function (id) { - var snapshot = servicesHost.getScriptSnapshot(id); - if (!snapshot) { - console.log("failed lo load snapshot for " + id); - return; - } - var diagnostics = cache.getDiagnostics(id, snapshot, function () { - return services - .getCompilerOptionsDiagnostics() - .concat(services.getSyntacticDiagnostics(id)) - .concat(services.getSemanticDiagnostics(id)); + if (options.check) { + cache.walkTree(function (id) { + var snapshot = servicesHost.getScriptSnapshot(id); + if (!snapshot) { + context.error(colors.red("failed lo load snapshot for " + id)); + return; + } + var diagnostics = cache.getDiagnostics(id, snapshot, function () { + return services + .getCompilerOptionsDiagnostics() + .concat(services.getSyntacticDiagnostics(id)) + .concat(services.getSemanticDiagnostics(id)); + }); + printDiagnostics(context, diagnostics); }); - printDiagnostics(diagnostics); - }); + } cache.diagnosticsDone(); }, }; diff --git a/dist/rollup-plugin-typescript2.es.js b/dist/rollup-plugin-typescript2.es.js index 82c08aa..c482424 100644 --- a/dist/rollup-plugin-typescript2.es.js +++ b/dist/rollup-plugin-typescript2.es.js @@ -1,9 +1,9 @@ /* eslint-disable */ -import { emptyDirSync, ensureFile, existsSync, move, readFileSync, readJsonSync, readdirSync, remove, writeJson } from 'fs-extra'; +import { emptyDirSync, ensureFile, ensureFileSync, existsSync, move, readFileSync, readJsonSync, readdirSync, remove, writeJson, writeJsonSync } 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, filter, find, has, isEqual, map, some } from 'lodash'; +import { defaults, each, endsWith, filter, find, has, isEqual, map, some } from 'lodash'; import * as _ from 'lodash'; import { Graph, alg } from 'graphlib'; import * as graph from 'graphlib'; @@ -12,6 +12,8 @@ import * as hash from 'object-hash'; import { createFilter } from 'rollup-pluginutils'; import { dirname, sep } from 'path'; import * as path from 'path'; +import { red, yellow } from 'colors/safe'; +import * as colors from 'colors/safe'; const __assign = Object.assign || function (target) { for (var source, i = 1; i < arguments.length; i++) { @@ -25,6 +27,42 @@ const __assign = Object.assign || function (target) { return target; }; +var VerbosityLevel; +(function (VerbosityLevel) { + VerbosityLevel[VerbosityLevel["Error"] = 0] = "Error"; + VerbosityLevel[VerbosityLevel["Warning"] = 1] = "Warning"; + VerbosityLevel[VerbosityLevel["Info"] = 2] = "Info"; + VerbosityLevel[VerbosityLevel["Debug"] = 3] = "Debug"; +})(VerbosityLevel || (VerbosityLevel = {})); +var ConsoleContext = (function () { + function ConsoleContext(verbosity, prefix) { + if (prefix === void 0) { prefix = ""; } + this.verbosity = verbosity; + this.prefix = prefix; + } + ConsoleContext.prototype.warn = function (message) { + if (this.verbosity < VerbosityLevel.Warning) + return; + console.log("" + this.prefix + message); + }; + ConsoleContext.prototype.error = function (message) { + if (this.verbosity < VerbosityLevel.Error) + return; + console.log("" + this.prefix + message); + }; + ConsoleContext.prototype.info = function (message) { + if (this.verbosity < VerbosityLevel.Info) + return; + console.log("" + this.prefix + message); + }; + ConsoleContext.prototype.debug = function (message) { + if (this.verbosity < VerbosityLevel.Debug) + return; + console.log("" + this.prefix + message); + }; + return ConsoleContext; +}()); + var LanguageServiceHost = (function () { function LanguageServiceHost(parsedConfig) { this.parsedConfig = parsedConfig; @@ -63,7 +101,15 @@ var LanguageServiceHost = (function () { return LanguageServiceHost; }()); +/** + * 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. + */ var RollingCache = (function () { + /** + * @param cacheRoot: root folder for the cache + * @param checkNewCache: whether to also look in new cache when reading from cache + */ function RollingCache(cacheRoot, checkNewCache) { this.cacheRoot = cacheRoot; this.checkNewCache = checkNewCache; @@ -71,16 +117,25 @@ var RollingCache = (function () { this.newCacheRoot = this.cacheRoot + "/cache_"; emptyDirSync(this.newCacheRoot); } + /** + * @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.checkNewCache && existsSync(this.newCacheRoot + "/" + name)) return true; return existsSync(this.oldCacheRoot + "/" + name); }; + /** + * @returns true if old cache contains all names and nothing more + */ RollingCache.prototype.match = function (names) { if (!existsSync(this.oldCacheRoot)) - return false; + return names.length === 0; // empty folder matches return isEqual(readdirSync(this.oldCacheRoot).sort(), names.sort()); }; + /** + * @returns data for name, must exist in old cache (or either old of new cache if checkNewCache is true) + */ RollingCache.prototype.read = function (name) { if (this.checkNewCache && existsSync(this.newCacheRoot + "/" + name)) return readJsonSync(this.newCacheRoot + "/" + name, "utf8"); @@ -89,54 +144,72 @@ var RollingCache = (function () { RollingCache.prototype.write = function (name, data) { if (data === undefined) return; - writeJson(this.newCacheRoot + "/" + name, data, { encoding: "utf8" }, function () { }); + if (this.checkNewCache) + writeJsonSync(this.newCacheRoot + "/" + name, data); + else + writeJson(this.newCacheRoot + "/" + name, data, { encoding: "utf8" }, function () { }); }; RollingCache.prototype.touch = function (name) { - ensureFile(this.newCacheRoot + "/" + name, function () { }); + if (this.checkNewCache) + ensureFileSync(this.newCacheRoot + "/" + name); + else + ensureFile(this.newCacheRoot + "/" + name, function () { }); }; + /** + * 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 () { }); + move(_this.newCacheRoot, _this.oldCacheRoot, function () { }); }); }; return RollingCache; }()); var Cache = (function () { - function Cache(host, cache, options, rootFilenames) { + function Cache(host, cache, options, rootFilenames, context) { var _this = this; this.host = host; this.options = options; + this.context = context; this.cacheVersion = "1"; - this.typesDirty = false; + this.ambientTypesDirty = 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"); }) + this.ambientTypes = filter(rootFilenames, function (file) { return endsWith(file, ".d.ts"); }) .map(function (id) { return { id: id, snapshot: _this.host.getScriptSnapshot(id) }; }); } + Cache.prototype.clean = function () { + this.context.info("cleaning cache: " + this.cacheDir); + emptyDirSync(this.cacheDir); + }; Cache.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("import tree has cycles"); each(this.dependencyTree.nodes(), function (id) { return cb(id); }); }; Cache.prototype.setDependency = function (importee, importer) { // importee -> importer + this.context.debug(importee + " -> " + importer); this.dependencyTree.setEdge(importer, importee); }; Cache.prototype.compileDone = function () { var _this = this; - var typeNames = filter(this.types, function (snaphot) { return snaphot.snapshot !== undefined; }) + var typeNames = filter(this.ambientTypes, 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); + this.ambientTypesDirty = !this.typesCache.match(typeNames); + if (this.ambientTypesDirty) + this.context.info("ambient types changed, redoing all diagnostics"); each(typeNames, function (name) { return _this.typesCache.touch(name); }); }; Cache.prototype.diagnosticsDone = function () { @@ -147,12 +220,13 @@ var Cache = (function () { 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); + this.context.debug("fresh transpile for: " + id); var data_1 = transform(); this.codeCache.write(name, data_1); this.markAsDirty(id, snapshot); return data_1; } + this.context.debug("old transpile for: " + id); var data = this.codeCache.read(name); this.codeCache.write(name, data); return data; @@ -160,17 +234,19 @@ var Cache = (function () { 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); + this.context.debug("fresh diagnostics for: " + id); var data_2 = this.convert(check()); this.diagnosticsCache.write(name, data_2); this.markAsDirty(id, snapshot); return data_2; } + this.context.debug("old diagnostics for: " + id); var data = this.diagnosticsCache.read(name); this.diagnosticsCache.write(name, data); return data; }; Cache.prototype.markAsDirty = function (id, _snapshot) { + this.context.debug("changed: " + id); this.dependencyTree.setNode(id, { dirty: true }); }; // returns true if node or any of its imports or any of global types changed @@ -181,7 +257,7 @@ var Cache = (function () { return false; if (!checkImports || label.dirty) return label.dirty; - if (this.typesDirty) + if (this.ambientTypesDirty) return true; var dependencies = alg.dijkstra(this.dependencyTree, id); return some(dependencies, function (dependency, node) { @@ -190,7 +266,7 @@ var Cache = (function () { var l = _this.dependencyTree.node(node); var dirty = l === undefined ? true : l.dirty; if (dirty) - console.log(("dirty: " + id + " -> " + node).gray); + _this.context.debug("import changed: " + id + " -> " + node); return dirty; }); }; @@ -201,11 +277,11 @@ var Cache = (function () { Cache.prototype.convert = function (data) { return map(data, function (diagnostic) { var entry = { - flatMessage: flattenDiagnosticMessageText(diagnostic.messageText, "\n").yellow, + flatMessage: flattenDiagnosticMessageText(diagnostic.messageText, "\n"), }; 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) + ")").white; + entry.fileLine = diagnostic.file.fileName + " (" + (line + 1) + "," + (character + 1) + ")"; } return entry; }); @@ -227,17 +303,15 @@ function getOptionsOverrides() { // MIT Licenced function findFile(cwd, filename) { var fp = cwd ? (cwd + "/" + filename) : filename; - if (existsSync(fp)) { + if (existsSync(fp)) return fp; - } var segs = cwd.split(sep); var len = segs.length; while (len--) { cwd = segs.slice(0, len).join("/"); fp = cwd + "/" + filename; - if (existsSync(fp)) { + if (existsSync(fp)) return fp; - } } return null; } @@ -262,22 +336,33 @@ function parseTsConfig() { var configParseResult = parseJsonConfigFileContent(result.config, sys, dirname(fileName), getOptionsOverrides(), fileName); return configParseResult; } -function printDiagnostics(diagnostics) { +function printDiagnostics(context, diagnostics) { each(diagnostics, function (diagnostic) { if (diagnostic.fileLine) - console.log(diagnostic.fileLine + ": " + diagnostic.flatMessage); + context.warn(diagnostic.fileLine + ": " + yellow(diagnostic.flatMessage)); else - console.log(diagnostic.flatMessage); + context.warn(yellow(diagnostic.flatMessage)); }); } function typescript(options) { options = __assign({}, options); - var filter$$1 = createFilter(options.include || ["*.ts+(|x)", "**/*.ts+(|x)"], options.exclude || ["*.d.ts", "**/*.d.ts"]); + defaults(options, { + check: true, + verbose: VerbosityLevel.Info, + clean: false, + cacheRoot: process.cwd() + "/.rts2_cache", + include: ["*.ts+(|x)", "**/*.ts+(|x)"], + exclude: ["*.d.ts", "**/*.d.ts"], + }); + var filter$$1 = createFilter(options.include, options.exclude); var parsedConfig = parseTsConfig(); var servicesHost = new LanguageServiceHost(parsedConfig); var services = createLanguageService(servicesHost, createDocumentRegistry()); - var cache = new Cache(servicesHost, process.cwd() + "/.rts2_cache", parsedConfig.options, parsedConfig.fileNames); + var context = new ConsoleContext(options.verbose, ""); + var cache = new Cache(servicesHost, options.cacheRoot, parsedConfig.options, parsedConfig.fileNames, context); + if (options.clean) + cache.clean(); return { resolveId: function (importee, importer) { if (importee === TSLIB) @@ -308,7 +393,7 @@ function typescript(options) { var result = cache.getCompiled(id, snapshot, function () { var output = services.getEmitOutput(id); if (output.emitSkipped) - _this.error({ message: "failed to transpile " + id }); + _this.error({ message: red("failed to transpile " + id) }); var transpiled = find(output.outputFiles, function (entry) { return endsWith(entry.name, ".js"); }); var map$$1 = find(output.outputFiles, function (entry) { return endsWith(entry.name, ".map"); }); return { @@ -320,20 +405,22 @@ function typescript(options) { }, outro: function () { cache.compileDone(); - cache.walkTree(function (id) { - var snapshot = servicesHost.getScriptSnapshot(id); - if (!snapshot) { - console.log("failed lo load snapshot for " + id); - return; - } - var diagnostics = cache.getDiagnostics(id, snapshot, function () { - return services - .getCompilerOptionsDiagnostics() - .concat(services.getSyntacticDiagnostics(id)) - .concat(services.getSemanticDiagnostics(id)); + if (options.check) { + cache.walkTree(function (id) { + var snapshot = servicesHost.getScriptSnapshot(id); + if (!snapshot) { + context.error(red("failed lo load snapshot for " + id)); + return; + } + var diagnostics = cache.getDiagnostics(id, snapshot, function () { + return services + .getCompilerOptionsDiagnostics() + .concat(services.getSyntacticDiagnostics(id)) + .concat(services.getSemanticDiagnostics(id)); + }); + printDiagnostics(context, diagnostics); }); - printDiagnostics(diagnostics); - }); + } cache.diagnosticsDone(); }, }; diff --git a/package.json b/package.json index d180bec..9c87003 100644 --- a/package.json +++ b/package.json @@ -20,8 +20,7 @@ "scripts": { "prebuild": "rimraf dist/*", "build": "rollup -c", - "lint": "tslint -c ./tslint.json src/*.ts", - "postinstall": "typings install" + "lint": "tslint -c ./tslint.json src/*.ts" }, "dependencies": { "colors": "^1.1.2", @@ -33,7 +32,8 @@ "tslib": "^1.5.0" }, "peerDependencies": { - "typescript": "^2.0" + "typescript": "^2.0", + "tslib": "^1.5.0" }, "devDependencies": { "@alexlur/rollup-plugin-typescript": "^0.8.1", diff --git a/rollup.config.js b/rollup.config.js index 724cc81..84f1099 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -13,7 +13,8 @@ export default { 'typescript', 'lodash', 'graphlib', - 'object-hash' + 'object-hash', + 'colors/safe' ], plugins: [ diff --git a/src/cache.ts b/src/cache.ts index bd6fdc9..8da7928 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -1,9 +1,10 @@ +import { IContext } from "./context"; import * as ts from "typescript"; import * as graph from "graphlib"; import * as hash from "object-hash"; import * as _ from "lodash"; -import * as colors from "colors/safe"; import { RollingCache } from "./rollingcache"; +import * as fs from "fs-extra"; export interface ICode { @@ -32,14 +33,14 @@ export class Cache { private cacheVersion = "1"; private dependencyTree: graph.Graph; - private types: ITypeSnapshot[]; - private typesDirty = false; + private ambientTypes: ITypeSnapshot[]; + private ambientTypesDirty = false; private cacheDir: string; private codeCache: RollingCache; private typesCache: RollingCache; private diagnosticsCache: RollingCache; - constructor(private host: ts.LanguageServiceHost, cache: string, private options: ts.CompilerOptions, rootFilenames: string[]) + constructor(private host: ts.LanguageServiceHost, cache: string, private options: ts.CompilerOptions, rootFilenames: string[], private context: IContext) { this.cacheDir = `${cache}/${hash.sha1({ version: this.cacheVersion, rootFilenames, options: this.options })}`; @@ -50,11 +51,17 @@ export class Cache this.dependencyTree = new graph.Graph({ directed: true }); this.dependencyTree.setDefaultNodeLabel((_node: string) => { return { dirty: false }; }); - this.types = _ + this.ambientTypes = _ .filter(rootFilenames, (file) => _.endsWith(file, ".d.ts")) .map((id) => { return { id, snapshot: this.host.getScriptSnapshot(id) }; }); } + public clean() + { + this.context.info(`cleaning cache: ${this.cacheDir}`); + fs.emptyDirSync(this.cacheDir); + } + public walkTree(cb: (id: string) => void | false): void { const acyclic = graph.alg.isAcyclic(this.dependencyTree); @@ -65,23 +72,29 @@ export class Cache return; } + this.context.info("import tree has cycles"); + _.each(this.dependencyTree.nodes(), (id: string) => cb(id)); } public setDependency(importee: string, importer: string): void { // importee -> importer + this.context.debug(`${importee} -> ${importer}`); this.dependencyTree.setEdge(importer, importee); } public compileDone(): void { let typeNames = _ - .filter(this.types, (snaphot) => snaphot.snapshot !== undefined) + .filter(this.ambientTypes, (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); + this.ambientTypesDirty = !this.typesCache.match(typeNames); + + if (this.ambientTypesDirty) + this.context.info("ambient types changed, redoing all diagnostics"); _.each(typeNames, (name) => this.typesCache.touch(name)); } @@ -99,13 +112,16 @@ export class Cache if (!this.codeCache.exists(name) || this.isDirty(id, snapshot, false)) { - console.log(`compile cache miss: ${id}`); + this.context.debug(`fresh transpile for: ${id}`); + let data = transform(); this.codeCache.write(name, data); this.markAsDirty(id, snapshot); return data; } + this.context.debug(`old transpile for: ${id}`); + let data = this.codeCache.read(name); this.codeCache.write(name, data); return data; @@ -117,13 +133,16 @@ export class Cache if (!this.diagnosticsCache.exists(name) || this.isDirty(id, snapshot, true)) { - console.log(`diag cache miss: ${id}`); + this.context.debug(`fresh diagnostics for: ${id}`); + let data = this.convert(check()); this.diagnosticsCache.write(name, data); this.markAsDirty(id, snapshot); return data; } + this.context.debug(`old diagnostics for: ${id}`); + let data = this.diagnosticsCache.read(name); this.diagnosticsCache.write(name, data); return data; @@ -131,6 +150,7 @@ export class Cache private markAsDirty(id: string, _snapshot: ts.IScriptSnapshot): void { + this.context.debug(`changed: ${id}`); this.dependencyTree.setNode(id, { dirty: true }); } @@ -145,7 +165,7 @@ export class Cache if (!checkImports || label.dirty) return label.dirty; - if (this.typesDirty) + if (this.ambientTypesDirty) return true; let dependencies = graph.alg.dijkstra(this.dependencyTree, id); @@ -159,7 +179,7 @@ export class Cache let dirty = l === undefined ? true : l.dirty; if (dirty) - console.log(colors.gray(`dirty: ${id} -> ${node}`)); + this.context.debug(`import changed: ${id} -> ${node}`); return dirty; }); diff --git a/src/context.ts b/src/context.ts new file mode 100644 index 0000000..3ca05be --- /dev/null +++ b/src/context.ts @@ -0,0 +1,61 @@ +interface Message +{ + message: string; +} + +export interface IRollupContext +{ + warn(message: Message | string): void; + error(message: Message | string): void; +} + +export interface IContext +{ + warn(message: string): void; + error(message: string): void; + info(message: string): void; + debug(message: string): void; +} + +export enum VerbosityLevel +{ + Error = 0, + Warning, + Info, + Debug, +} + +export class ConsoleContext implements IContext +{ + constructor(private verbosity: VerbosityLevel, private prefix: string = "") + { + } + + public warn(message: string): void + { + if (this.verbosity < VerbosityLevel.Warning) + return; + console.log(`${this.prefix}${message}`); + } + + public error(message: string): void + { + if (this.verbosity < VerbosityLevel.Error) + return; + console.log(`${this.prefix}${message}`); + } + + public info(message: string): void + { + if (this.verbosity < VerbosityLevel.Info) + return; + console.log(`${this.prefix}${message}`); + } + + public debug(message: string): void + { + if (this.verbosity < VerbosityLevel.Debug) + return; + console.log(`${this.prefix}${message}`); + } +} diff --git a/src/index.ts b/src/index.ts index 363e62c..1e824d5 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,3 +1,4 @@ +import { IContext, ConsoleContext, IRollupContext, VerbosityLevel } from "./context"; import { LanguageServiceHost } from "./host"; import { Cache, ICode, IDiagnostics } from "./cache"; import * as ts from "typescript"; @@ -26,9 +27,7 @@ function findFile(cwd: string, filename: string) let fp = cwd ? (cwd + "/" + filename) : filename; if (fs.existsSync(fp)) - { return fp; - } const segs = cwd.split(path.sep); let len = segs.length; @@ -38,9 +37,7 @@ function findFile(cwd: string, filename: string) cwd = segs.slice(0, len).join("/"); fp = cwd + "/" + filename; if (fs.existsSync(fp)) - { return fp; - } } return null; @@ -73,37 +70,42 @@ function parseTsConfig() return configParseResult; } -interface Message -{ - message: string; -} -interface Context -{ - warn(message: Message): void; - error(message: Message): void; -} -function printDiagnostics(diagnostics: IDiagnostics[]) +function printDiagnostics(context: IContext, diagnostics: IDiagnostics[]) { _.each(diagnostics, (diagnostic) => { if (diagnostic.fileLine) - console.log(`${diagnostic.fileLine}: ${colors.yellow(diagnostic.flatMessage)}`); + context.warn(`${diagnostic.fileLine}: ${colors.yellow(diagnostic.flatMessage)}`); else - console.log(colors.yellow(diagnostic.flatMessage)); + context.warn(colors.yellow(diagnostic.flatMessage)); }); }; interface IOptions { - include?: string; - exclude?: string; + include: string; + exclude: string; + check: boolean; + verbose: number; + clean: boolean; + cacheRoot: string; } export default function typescript (options: IOptions) { options = { ... options }; - const filter = createFilter(options.include || [ "*.ts+(|x)", "**/*.ts+(|x)" ], options.exclude || [ "*.d.ts", "**/*.d.ts" ]); + _.defaults(options, + { + check: true, + verbose: VerbosityLevel.Info, + clean: false, + cacheRoot: `${process.cwd()}/.rts2_cache`, + include: [ "*.ts+(|x)", "**/*.ts+(|x)" ], + exclude: [ "*.d.ts", "**/*.d.ts" ], + }); + + const filter = createFilter(options.include, options.exclude); let parsedConfig = parseTsConfig(); @@ -111,7 +113,12 @@ export default function typescript (options: IOptions) const services = ts.createLanguageService(servicesHost, ts.createDocumentRegistry()); - const cache = new Cache(servicesHost, `${process.cwd()}/.rts2_cache`, parsedConfig.options, parsedConfig.fileNames); + const context = new ConsoleContext(options.verbose, ""); + + const cache = new Cache(servicesHost, options.cacheRoot, parsedConfig.options, parsedConfig.fileNames, context); + + if (options.clean) + cache.clean(); return { @@ -149,7 +156,7 @@ export default function typescript (options: IOptions) return undefined; }, - transform(this: Context, code: string, id: string): ICode | undefined + transform(this: IRollupContext, code: string, id: string): ICode | undefined { if (!filter(id)) return undefined; @@ -178,26 +185,29 @@ export default function typescript (options: IOptions) { cache.compileDone(); - cache.walkTree((id: string) => + if (options.check) { - const snapshot = servicesHost.getScriptSnapshot(id); - - if (!snapshot) + cache.walkTree((id: string) => { - console.log(colors.red(`failed lo load snapshot for ${id}`)); - return; - } + const snapshot = servicesHost.getScriptSnapshot(id); - const diagnostics = cache.getDiagnostics(id, snapshot, () => - { - return services - .getCompilerOptionsDiagnostics() - .concat(services.getSyntacticDiagnostics(id)) - .concat(services.getSemanticDiagnostics(id)); + if (!snapshot) + { + context.error(colors.red(`failed lo load snapshot for ${id}`)); + return; + } + + const diagnostics = cache.getDiagnostics(id, snapshot, () => + { + return services + .getCompilerOptionsDiagnostics() + .concat(services.getSyntacticDiagnostics(id)) + .concat(services.getSemanticDiagnostics(id)); + }); + + printDiagnostics(context, diagnostics); }); - - printDiagnostics(diagnostics); - }); + } cache.diagnosticsDone(); }, From e039c7469518a9b05f692fa63b8be7b6906aad51 Mon Sep 17 00:00:00 2001 From: ezolenko Date: Fri, 10 Feb 2017 22:35:00 -0700 Subject: [PATCH 11/11] - readme, options --- README.md | 26 +++++++++++++++++++------- dist/rollup-plugin-typescript2.cjs.js | 14 +++++++++----- dist/rollup-plugin-typescript2.es.js | 14 +++++++++----- src/cache.ts | 15 +++++++++++---- src/index.ts | 6 +++--- 5 files changed, 51 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index f6068db..94e607b 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Rollup plugin for typescript with compiler errors. This is a rewrite of original rollup-plugin-typescript, starting and borrowing from [this fork](https://github.com/alexlur/rollup-plugin-typescript). -This version is significantly slower than original, but it will print out typescript errors and warnings. +This version is somewhat slower than original, but it will print out typescript syntactic and semantic diagnostic messages (the main reason for using typescript after all). ## Usage @@ -15,11 +15,11 @@ This version is significantly slower than original, but it will print out typesc import typescript from 'rollup-plugin-typescript'; export default { - entry: './main.ts', + entry: './main.ts', - plugins: [ - typescript() - ] + plugins: [ + typescript() + ] } ``` @@ -32,7 +32,19 @@ Following compiler options are forced though: * `importHelpers`: true * `noResolve`: false -Plugin itself takes standard include/exclude options (each a minimatch pattern, or array of minimatch patterns), which determine which files are transpiled by Typescript (all `.ts` and `.tsx` files by default) +Plugin takes following options: +* `check`: true + - set to false to avoid doing any diagnostic checks on the code +* `verbosity`: 2 + - goes up to 3 +* `clean`: false + - set to true for clean build (wipes out cache) +* `cacheRoot`: ".rts2_cache" + - path to cache +* `include`: `[ "*.ts+(|x)", "**/*.ts+(|x)" ]` + - passes all .ts files through typescript compiler. +* `exclude`: `[ "*.d.ts", "**/*.d.ts" ]` + - but not types ### TypeScript version -This plugin currently requires TypeScript > 2.0. +This plugin currently requires TypeScript 2.0+. diff --git a/dist/rollup-plugin-typescript2.cjs.js b/dist/rollup-plugin-typescript2.cjs.js index ba45c7d..b7e697c 100644 --- a/dist/rollup-plugin-typescript2.cjs.js +++ b/dist/rollup-plugin-typescript2.cjs.js @@ -171,17 +171,16 @@ var Cache = (function () { this.cacheVersion = "1"; this.ambientTypesDirty = 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.ambientTypes = _.filter(rootFilenames, function (file) { return _.endsWith(file, ".d.ts"); }) .map(function (id) { return { id: id, snapshot: _this.host.getScriptSnapshot(id) }; }); + this.init(); } Cache.prototype.clean = function () { this.context.info("cleaning cache: " + this.cacheDir); fs.emptyDirSync(this.cacheDir); + this.init(); }; Cache.prototype.walkTree = function (cb) { var acyclic = graph.alg.isAcyclic(this.dependencyTree); @@ -240,6 +239,11 @@ var Cache = (function () { this.diagnosticsCache.write(name, data); return data; }; + Cache.prototype.init = function () { + this.codeCache = new RollingCache(this.cacheDir + "/code", true); + this.typesCache = new RollingCache(this.cacheDir + "/types", false); + this.diagnosticsCache = new RollingCache(this.cacheDir + "/diagnostics", false); + }; Cache.prototype.markAsDirty = function (id, _snapshot) { this.context.debug("changed: " + id); this.dependencyTree.setNode(id, { dirty: true }); @@ -344,7 +348,7 @@ function typescript(options) { options = __assign({}, options); _.defaults(options, { check: true, - verbose: VerbosityLevel.Info, + verbosity: VerbosityLevel.Info, clean: false, cacheRoot: process.cwd() + "/.rts2_cache", include: ["*.ts+(|x)", "**/*.ts+(|x)"], @@ -354,7 +358,7 @@ function typescript(options) { var parsedConfig = parseTsConfig(); var servicesHost = new LanguageServiceHost(parsedConfig); var services = ts.createLanguageService(servicesHost, ts.createDocumentRegistry()); - var context = new ConsoleContext(options.verbose, ""); + var context = new ConsoleContext(options.verbosity, "rollup-plugin-typescript2: "); var cache = new Cache(servicesHost, options.cacheRoot, parsedConfig.options, parsedConfig.fileNames, context); if (options.clean) cache.clean(); diff --git a/dist/rollup-plugin-typescript2.es.js b/dist/rollup-plugin-typescript2.es.js index c482424..9d73f06 100644 --- a/dist/rollup-plugin-typescript2.es.js +++ b/dist/rollup-plugin-typescript2.es.js @@ -176,17 +176,16 @@ var Cache = (function () { this.cacheVersion = "1"; this.ambientTypesDirty = 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.ambientTypes = filter(rootFilenames, function (file) { return endsWith(file, ".d.ts"); }) .map(function (id) { return { id: id, snapshot: _this.host.getScriptSnapshot(id) }; }); + this.init(); } Cache.prototype.clean = function () { this.context.info("cleaning cache: " + this.cacheDir); emptyDirSync(this.cacheDir); + this.init(); }; Cache.prototype.walkTree = function (cb) { var acyclic = alg.isAcyclic(this.dependencyTree); @@ -245,6 +244,11 @@ var Cache = (function () { this.diagnosticsCache.write(name, data); return data; }; + Cache.prototype.init = function () { + this.codeCache = new RollingCache(this.cacheDir + "/code", true); + this.typesCache = new RollingCache(this.cacheDir + "/types", false); + this.diagnosticsCache = new RollingCache(this.cacheDir + "/diagnostics", false); + }; Cache.prototype.markAsDirty = function (id, _snapshot) { this.context.debug("changed: " + id); this.dependencyTree.setNode(id, { dirty: true }); @@ -349,7 +353,7 @@ function typescript(options) { options = __assign({}, options); defaults(options, { check: true, - verbose: VerbosityLevel.Info, + verbosity: VerbosityLevel.Info, clean: false, cacheRoot: process.cwd() + "/.rts2_cache", include: ["*.ts+(|x)", "**/*.ts+(|x)"], @@ -359,7 +363,7 @@ function typescript(options) { var parsedConfig = parseTsConfig(); var servicesHost = new LanguageServiceHost(parsedConfig); var services = createLanguageService(servicesHost, createDocumentRegistry()); - var context = new ConsoleContext(options.verbose, ""); + var context = new ConsoleContext(options.verbosity, "rollup-plugin-typescript2: "); var cache = new Cache(servicesHost, options.cacheRoot, parsedConfig.options, parsedConfig.fileNames, context); if (options.clean) cache.clean(); diff --git a/src/cache.ts b/src/cache.ts index 8da7928..5e00ba2 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -44,22 +44,22 @@ export class Cache { this.cacheDir = `${cache}/${hash.sha1({ version: this.cacheVersion, 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((_node: string) => { return { dirty: false }; }); this.ambientTypes = _ .filter(rootFilenames, (file) => _.endsWith(file, ".d.ts")) .map((id) => { return { id, snapshot: this.host.getScriptSnapshot(id) }; }); + + this.init(); } public clean() { this.context.info(`cleaning cache: ${this.cacheDir}`); fs.emptyDirSync(this.cacheDir); + + this.init(); } public walkTree(cb: (id: string) => void | false): void @@ -148,6 +148,13 @@ export class Cache return data; } + private init() + { + this.codeCache = new RollingCache(`${this.cacheDir}/code`, true); + this.typesCache = new RollingCache(`${this.cacheDir}/types`, false); + this.diagnosticsCache = new RollingCache(`${this.cacheDir}/diagnostics`, false); + } + private markAsDirty(id: string, _snapshot: ts.IScriptSnapshot): void { this.context.debug(`changed: ${id}`); diff --git a/src/index.ts b/src/index.ts index 1e824d5..e9bfa9c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -86,7 +86,7 @@ interface IOptions include: string; exclude: string; check: boolean; - verbose: number; + verbosity: number; clean: boolean; cacheRoot: string; } @@ -98,7 +98,7 @@ export default function typescript (options: IOptions) _.defaults(options, { check: true, - verbose: VerbosityLevel.Info, + verbosity: VerbosityLevel.Info, clean: false, cacheRoot: `${process.cwd()}/.rts2_cache`, include: [ "*.ts+(|x)", "**/*.ts+(|x)" ], @@ -113,7 +113,7 @@ export default function typescript (options: IOptions) const services = ts.createLanguageService(servicesHost, ts.createDocumentRegistry()); - const context = new ConsoleContext(options.verbose, ""); + const context = new ConsoleContext(options.verbosity, "rollup-plugin-typescript2: "); const cache = new Cache(servicesHost, options.cacheRoot, parsedConfig.options, parsedConfig.fileNames, context);