Skip to content

Commit 3949d13

Browse files
committed
Initial version
0 parents  commit 3949d13

File tree

8 files changed

+510
-0
lines changed

8 files changed

+510
-0
lines changed

cache.js

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
'use strict';
2+
3+
const path = require("path");
4+
const fs = require("fs");
5+
const assert = require("assert");
6+
const LRU = require("lru-cache");
7+
const utils = require("./utils");
8+
const pkgVersion = require("./package.json").version;
9+
const random = require("random-js")();
10+
11+
function ensureCacheDir(cacheDir) {
12+
cacheDir = path.resolve(
13+
cacheDir ||
14+
process.env.TYPESCRIPT_CACHE_DIR ||
15+
path.join(
16+
process.env.HOME || process.env.USERPROFILE || __dirname,
17+
".typescript-cache"
18+
)
19+
);
20+
21+
try {
22+
utils.mkdirp(cacheDir);
23+
} catch (error) {
24+
if (error.code !== "EEXIST") {
25+
throw error;
26+
}
27+
}
28+
29+
return cacheDir;
30+
}
31+
32+
class Cache {
33+
34+
constructor(compileFn, cacheDir) {
35+
assert.strictEqual(typeof compileFn, "function");
36+
37+
this.compileFn = compileFn;
38+
this.cacheDir = ensureCacheDir(cacheDir);
39+
40+
const maxSize = process.env.TYPESCRIPT_CACHE_SIZE;
41+
this._cache = new LRU({
42+
max: maxSize || 1024 * 10 * 10
43+
});
44+
}
45+
46+
get(source, options) {
47+
let cacheKey = utils.deepHash(pkgVersion, source, options);
48+
let compileResult = this._cache.get(cacheKey);
49+
50+
if (! compileResult) {
51+
compileResult = this._readCache(cacheKey);
52+
}
53+
54+
if (! compileResult) {
55+
compileResult = this.compileFn(source, options);
56+
this._cache.set(cacheKey, compileResult);
57+
this._writeCacheAsync(cacheKey, compileResult);
58+
}
59+
60+
return compileResult;
61+
}
62+
63+
_cacheFilename(cacheKey) {
64+
// We want cacheKeys to be hex so that they work on any FS
65+
// and never end in .cache.
66+
if (!/^[a-f0-9]+$/.test(cacheKey)) {
67+
throw Error('bad cacheKey: ' + cacheKey);
68+
}
69+
70+
return path.join(this.cacheDir, cacheKey + '.cache');
71+
}
72+
73+
_readFileOrNull(filename) {
74+
try {
75+
return fs.readFileSync(filename, 'utf8');
76+
} catch (e) {
77+
if (e && e.code === 'ENOENT')
78+
return null;
79+
throw e;
80+
}
81+
}
82+
83+
_parseJSONOrNull(json) {
84+
try {
85+
return JSON.parse(json);
86+
} catch (e) {
87+
if (e instanceof SyntaxError)
88+
return null;
89+
throw e;
90+
}
91+
}
92+
93+
// Returns null if the file does not exist or can't be parsed; otherwise
94+
// returns the parsed compileResult in the file.
95+
_readAndParseCompileResultOrNull(filename) {
96+
var content = this._readFileOrNull(filename);
97+
return this._parseJSONOrNull(content);
98+
}
99+
100+
_readCache(cacheKey) {
101+
if (! this.cacheDir) {
102+
return null;
103+
}
104+
105+
var cacheFilename = this._cacheFilename(cacheKey);
106+
var compileResult = this._readAndParseCompileResultOrNull(cacheFilename);
107+
if (! compileResult) {
108+
return null;
109+
}
110+
this._cache.set(cacheKey, compileResult);
111+
112+
return compileResult;
113+
}
114+
115+
// We want to write the file atomically.
116+
// But we also don't want to block processing on the file write.
117+
_writeFileAsync(filename, contents) {
118+
var tempFilename = filename + '.tmp.' + random.uuid4();
119+
fs.writeFile(tempFilename, contents, (err) => {
120+
// ignore errors, it's just a cache
121+
if (err) {
122+
return;
123+
}
124+
fs.rename(tempFilename, filename, (err) => {
125+
// ignore this error too.
126+
});
127+
});
128+
}
129+
130+
_writeCacheAsync(cacheKey, compileResult) {
131+
if (! this.cacheDir) return;
132+
133+
var cacheFilename = this._cacheFilename(cacheKey);
134+
var cacheContents = JSON.stringify(compileResult);
135+
this._writeFileAsync(cacheFilename, cacheContents);
136+
}
137+
138+
}
139+
140+
exports.Cache = Cache;
141+

index.js

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
'use strict';
2+
3+
const getDefaultOptions = require("./options").getDefaultOptions;
4+
const tsCompile = require("./typescript").compile;
5+
const Cache = require("./cache").Cache;
6+
7+
function setCacheDir(cacheDir) {
8+
if (compileCache && compileCache.cacheDir === cacheDir) {
9+
return;
10+
}
11+
12+
compileCache = new Cache(function(source, options) {
13+
return tsCompile(source, options);
14+
}, cacheDir);
15+
}
16+
17+
exports.setCacheDir = setCacheDir;
18+
19+
let compileCache;
20+
exports.compile = function compile(source, options) {
21+
options = options || {compilerOptions: getDefaultOptions()};
22+
23+
if (! options.useCache) {
24+
return tsCompile(source, options);
25+
}
26+
27+
if (! compileCache) {
28+
setCacheDir();
29+
}
30+
31+
return compileCache.get(source, options);
32+
};

options.js

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
'use strict';
2+
3+
const ts = require("typescript");
4+
const _ = require("underscore");
5+
6+
function getCompilerOptions(customOptions) {
7+
let compilerOptions = ts.getDefaultCompilerOptions();
8+
9+
_.extend(compilerOptions, customOptions);
10+
11+
// Support decorators by default.
12+
compilerOptions.experimentalDecorators = true;
13+
14+
// Declaration files are expected to
15+
// be generated separately.
16+
compilerOptions.declaration = false;
17+
18+
// Overrides watching,
19+
// it is handled by Meteor itself.
20+
compilerOptions.watch = false;
21+
22+
// We use source maps via Meteor file API,
23+
// This class's API provides source maps
24+
// separately but alongside compilation results.
25+
// Hence, skip generating inline source maps.
26+
compilerOptions.inlineSourceMap = false;
27+
compilerOptions.inlineSources = false;
28+
29+
// Always emit.
30+
compilerOptions.noEmit = false;
31+
compilerOptions.noEmitOnError = false;
32+
33+
// Don't generate any files, hence,
34+
// skip setting outDir and outFile.
35+
compilerOptions.outDir = null;
36+
compilerOptions.outFile = null;
37+
38+
// This is not need as well.
39+
// API doesn't have paramless methods.
40+
compilerOptions.rootDir = null;
41+
compilerOptions.sourceRoot = null;
42+
43+
return compilerOptions;
44+
}
45+
46+
exports.getCompilerOptions = getCompilerOptions;
47+
48+
// Default compiler options.
49+
function getDefaultOptions() {
50+
return {
51+
module : ts.ModuleKind.None,
52+
target: ts.ScriptTarget.ES5,
53+
sourceMap: true,
54+
noResolve: false,
55+
diagnostics: true,
56+
// Custom option to turn on/off cache.
57+
useCache: true,
58+
// Always emit class metadata,
59+
// especially useful for Angular2.
60+
emitDecoratorMetadata: true
61+
}
62+
}
63+
64+
exports.getDefaultOptions = getDefaultOptions;

package.json

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"name": "meteor-typescript",
3+
"author": "@barbatus",
4+
"version": "0.1.0",
5+
"license": "MIT",
6+
"description": "TypeScript wrapper package for use with Meteor",
7+
"keywords": [
8+
"meteor",
9+
"typescript",
10+
"es6",
11+
"es7",
12+
"transpiler",
13+
"transpilation",
14+
"compilation"
15+
],
16+
"main": "index.js",
17+
"scripts": {
18+
"test": "tests/run.sh"
19+
},
20+
"repository": {
21+
"type": "git",
22+
"url": "https://github.com/barbatus/meteor-typescript.git"
23+
},
24+
"bugs": {
25+
"url": "https://github.com/barbatus/meteor-typescript/issues"
26+
},
27+
"dependencies": {
28+
"typescript": "^1.7.5",
29+
"lru-cache": "^2.6.4",
30+
"underscore": "^1.8.3",
31+
"random-js": "^1.0.3"
32+
},
33+
"devDependencies": {
34+
"mocha": "^2.2.5"
35+
}
36+
}

tests/run.sh

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#!/usr/bin/env bash
2+
3+
cd $(dirname $0)
4+
TEST_DIR=$(pwd)
5+
6+
TYPESCRIPT_CACHE_DIR=${TEST_DIR}/.cache
7+
export TYPESCRIPT_CACHE_DIR
8+
9+
node tests.js

tests/tests.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
var meteorTS = require("../index");
2+
var assert = require("assert");
3+
4+
// Some dummy test.
5+
var result = meteorTS.compile("export const foo = 600;");
6+
assert.equal(result.code.indexOf("exports.foo = 600;"), 0);
7+

0 commit comments

Comments
 (0)