Skip to content
This repository was archived by the owner on Dec 1, 2019. It is now read-only.

Commit 358441a

Browse files
author
Stanislav Panferov
committed
feat(*): implement file cache
1 parent 4bab831 commit 358441a

File tree

5 files changed

+234
-55
lines changed

5 files changed

+234
-55
lines changed

src/cache.ts

+148
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
import * as fs from 'fs';
2+
import * as crypto from 'crypto';
3+
import * as os from 'os';
4+
import * as path from 'path';
5+
import * as zlib from 'zlib';
6+
7+
export interface CompiledModule {
8+
fileName: string,
9+
text: string,
10+
map?: string,
11+
mapName?: string
12+
}
13+
14+
export function findCompiledModule(fileName: string): CompiledModule {
15+
let baseFileName = fileName.replace(/(\.ts|\.tsx)$/, '');
16+
let compiledFileName = `${baseFileName}.js`
17+
18+
if (fs.existsSync(compiledFileName)) {
19+
let mapFileName = `${baseFileName}.js.map`;
20+
let isMapExists = fs.existsSync(mapFileName);
21+
let result = {
22+
fileName: compiledFileName,
23+
text: fs.readFileSync(compiledFileName).toString(),
24+
mapName: isMapExists
25+
? mapFileName
26+
: null,
27+
map: isMapExists
28+
? fs.readFileSync(mapFileName).toString()
29+
: null
30+
}
31+
return result;
32+
} else {
33+
return null;
34+
}
35+
}
36+
37+
/**
38+
* Read the contents from the compressed file.
39+
*
40+
* @async
41+
*/
42+
function read(filename: string, callback) {
43+
return fs.readFile(filename, function(err, data) {
44+
if (err) { return callback(err); }
45+
46+
return zlib.gunzip(data, function(err, content) {
47+
let result = {};
48+
49+
if (err) { return callback(err); }
50+
51+
try {
52+
result = JSON.parse(content);
53+
} catch (e) {
54+
return callback(e);
55+
}
56+
57+
return callback(null, result);
58+
});
59+
});
60+
};
61+
62+
63+
/**
64+
* Write contents into a compressed file.
65+
*
66+
* @async
67+
* @params {String} filename
68+
* @params {String} result
69+
* @params {Function} callback
70+
*/
71+
function write(filename: string, result: any, callback) {
72+
let content = JSON.stringify(result);
73+
74+
return zlib.gzip(content as any, function(err, data) {
75+
if (err) { return callback(err); }
76+
77+
return fs.writeFile(filename, data, callback);
78+
});
79+
};
80+
81+
82+
/**
83+
* Build the filename for the cached file
84+
*
85+
* @params {String} source File source code
86+
* @params {Object} options Options used
87+
*
88+
* @return {String}
89+
*/
90+
function filename(source: string, identifier, options) {
91+
let hash = crypto.createHash('SHA1') as any;
92+
let contents = JSON.stringify({
93+
source: source,
94+
options: options,
95+
identifier: identifier,
96+
});
97+
98+
hash.end(contents);
99+
100+
return hash.read().toString('hex') + '.json.gzip';
101+
};
102+
103+
interface CacheParams {
104+
source: string;
105+
options: any;
106+
transform: (source: string, options: any) => string;
107+
identifier: any;
108+
directory: string;
109+
}
110+
111+
/**
112+
* Retrieve file from cache, or create a new one for future reads
113+
*
114+
* @async
115+
* @example
116+
*/
117+
export function cache(params: CacheParams, callback) {
118+
// Spread params into named variables
119+
// Forgive user whenever possible
120+
let source = params.source;
121+
let options = params.options || {};
122+
let transform = params.transform;
123+
let identifier = params.identifier;
124+
let directory = (typeof params.directory === 'string') ?
125+
params.directory :
126+
os.tmpdir();
127+
let file = path.join(directory, filename(source, identifier, options));
128+
129+
return read(file, function(err, content) {
130+
let result = {};
131+
// No errors mean that the file was previously cached
132+
// we just need to return it
133+
if (!err) { return callback(null, content); }
134+
135+
// Otherwise just transform the file
136+
// return it to the user asap and write it in cache
137+
try {
138+
result = transform(source, options);
139+
} catch (error) {
140+
return callback(error);
141+
}
142+
143+
return write(file, result, function(err) {
144+
return callback(err, result);
145+
});
146+
147+
});
148+
};

src/deps.ts

-30
Original file line numberDiff line numberDiff line change
@@ -383,36 +383,6 @@ export class ValidFilesManager {
383383
}
384384
}
385385

386-
export interface CompiledModule {
387-
fileName: string,
388-
text: string,
389-
map?: string,
390-
mapName?: string
391-
}
392-
393-
export function findCompiledModule(fileName: string): CompiledModule {
394-
let baseFileName = fileName.replace(/(\.ts|\.tsx)$/, '');
395-
let compiledFileName = `${baseFileName}.js`
396-
397-
if (fs.existsSync(compiledFileName)) {
398-
let mapFileName = `${baseFileName}.js.map`;
399-
let isMapExists = fs.existsSync(mapFileName);
400-
let result = {
401-
fileName: compiledFileName,
402-
text: fs.readFileSync(compiledFileName).toString(),
403-
mapName: isMapExists
404-
? mapFileName
405-
: null,
406-
map: isMapExists
407-
? fs.readFileSync(mapFileName).toString()
408-
: null
409-
}
410-
return result;
411-
} else {
412-
return null;
413-
}
414-
}
415-
416386
/**
417387
* Emit compilation result for a specified fileName.
418388
*/

src/host.ts

+3
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ export interface ICompilerOptions extends ts.CompilerOptions {
3939
doTypeCheck?: boolean;
4040
forkChecker?: boolean;
4141
useBabel?: boolean;
42+
usePrecompiledFiles?: boolean;
43+
useCache?: boolean;
44+
cacheDirectory?: string;
4245
}
4346

4447
export interface IOutputFile extends ts.OutputFile {

src/index.ts

+82-25
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,16 @@ import * as childProcess from 'child_process';
99
import * as colors from 'colors';
1010

1111
import { ICompilerOptions, TypeScriptCompilationError, State, ICompilerInfo } from './host';
12-
import { IResolver, ResolutionError, findCompiledModule } from './deps';
12+
import { IResolver, ResolutionError } from './deps';
13+
import { findCompiledModule, cache } from './cache';
1314
import * as helpers from './helpers';
1415
import { loadLib } from './helpers';
1516

1617
let loaderUtils = require('loader-utils');
1718

19+
let pkg = require('../package.json');
20+
let cachePromise = Promise.promisify(cache);
21+
1822
interface ICompiler {
1923
inputFileSystem: typeof fs;
2024
_tsInstances: {[key:string]: ICompilerInstance};
@@ -44,6 +48,7 @@ interface ICompilerInstance {
4448
options: ICompilerOptions;
4549
externalsInvoked: boolean;
4650
checker: any;
51+
cacheIdentifier: any;
4752
}
4853

4954
function getRootCompiler(compiler) {
@@ -238,6 +243,28 @@ function ensureInstance(webpack: IWebPack, options: ICompilerOptions, instanceNa
238243
}
239244
}
240245

246+
let cacheIdentifier = null;
247+
if (options.useCache) {
248+
console.log(webpack.query);
249+
250+
if (!options.cacheDirectory) {
251+
options.cacheDirectory = path.join(process.cwd(), '.awcache');
252+
}
253+
254+
if (!fs.existsSync(options.cacheDirectory)) {
255+
fs.mkdirSync(options.cacheDirectory)
256+
}
257+
258+
cacheIdentifier = {
259+
'typescript': tsImpl.version,
260+
'awesome-typescript-loader': pkg.version,
261+
'awesome-typescript-loader-query': webpack.query,
262+
'babel-core': babelImpl
263+
? babelImpl.version
264+
: null
265+
}
266+
}
267+
241268
let tsState = new State(options, webpack._compiler.inputFileSystem, compilerInfo);
242269
let compiler = (<any>webpack._compiler);
243270

@@ -322,7 +349,8 @@ function ensureInstance(webpack: IWebPack, options: ICompilerOptions, instanceNa
322349
externalsInvoked: false,
323350
checker: options.forkChecker
324351
? createChecker(compilerInfo, options)
325-
: null
352+
: null,
353+
cacheIdentifier
326354
}
327355
}
328356

@@ -388,40 +416,69 @@ function compiler(webpack: IWebPack, text: string): void {
388416
});
389417
})
390418
.then(() => {
391-
let resultText;
392-
let resultSourceMap;
419+
let compiledModule
420+
if (instance.options.usePrecompiledFiles) {
421+
compiledModule = findCompiledModule(fileName);
422+
}
393423

394-
let compiledModule = findCompiledModule(fileName);
395424
if (compiledModule) {
396425
state.fileAnalyzer.dependencies.addCompiledModule(fileName, compiledModule.fileName);
397-
resultText = compiledModule.text;
398-
resultSourceMap = JSON.parse(compiledModule.map);
426+
return {
427+
text: compiledModule.text,
428+
map: JSON.parse(compiledModule.map)
429+
}
399430
} else {
400-
let output = state.emit(fileName);
401-
let result = helpers.findResultFor(output, fileName);
402431

403-
if (result.text === undefined) {
404-
throw new Error('No output found for ' + fileName);
405-
}
432+
function transform() {
433+
let resultText;
434+
let resultSourceMap;
435+
let output = state.emit(fileName);
436+
let result = helpers.findResultFor(output, fileName);
406437

407-
resultText = result.text;
408-
resultSourceMap = JSON.parse(result.sourceMap);
409-
resultSourceMap.sources = [ fileName ];
410-
resultSourceMap.file = fileName;
411-
resultSourceMap.sourcesContent = [ text ];
438+
if (result.text === undefined) {
439+
throw new Error('No output found for ' + fileName);
440+
}
412441

413-
if (instance.options.useBabel) {
414-
let defaultOptions = {
415-
inputSourceMap: resultSourceMap,
416-
filename: fileName,
417-
sourceMap: true
442+
resultText = result.text;
443+
resultSourceMap = JSON.parse(result.sourceMap);
444+
resultSourceMap.sources = [ fileName ];
445+
resultSourceMap.file = fileName;
446+
resultSourceMap.sourcesContent = [ text ];
447+
448+
if (instance.options.useBabel) {
449+
let defaultOptions = {
450+
inputSourceMap: resultSourceMap,
451+
filename: fileName,
452+
sourceMap: true
453+
}
454+
455+
let babelResult = instance.babelImpl.transform(resultText, defaultOptions);
456+
resultText = babelResult.code;
457+
resultSourceMap = babelResult.map;
418458
}
419459

420-
let babelResult = instance.babelImpl.transform(resultText, defaultOptions);
421-
resultText = babelResult.code;
422-
resultSourceMap = babelResult.map;
460+
return {
461+
text: resultText,
462+
map: resultSourceMap
463+
};
464+
}
465+
466+
if (instance.options.useCache) {
467+
return cachePromise({
468+
source: text,
469+
identifier: instance.cacheIdentifier,
470+
directory: instance.options.cacheDirectory,
471+
options: webpack.query,
472+
transform: transform
473+
})
474+
} else {
475+
return transform();
423476
}
424477
}
478+
})
479+
.then((transform: { text: string; map: any }) => {
480+
let resultText = transform.text;
481+
let resultSourceMap = transform.map;
425482

426483
if (resultSourceMap) {
427484
resultSourceMap.sources = [ fileName ];

src/tsconfig.json

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"!./node_modules/**/*.ts"
1717
],
1818
"files": [
19+
"./cache.ts",
1920
"./checker.ts",
2021
"./deps.ts",
2122
"./helpers.ts",

0 commit comments

Comments
 (0)