aboutsummaryrefslogtreecommitdiff
path: root/node_modules/ava/lib/caching-precompiler.js
blob: 937309bf0bc992399d8f3f08767360b63dbcef09 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
'use strict';
const path = require('path');
const fs = require('fs');
const convertSourceMap = require('convert-source-map');
const cachingTransform = require('caching-transform');
const packageHash = require('package-hash');
const stripBomBuf = require('strip-bom-buf');
const autoBind = require('auto-bind');
const md5Hex = require('md5-hex');

function getSourceMap(filePath, code) {
	let sourceMap = convertSourceMap.fromSource(code);

	if (!sourceMap) {
		const dirPath = path.dirname(filePath);
		sourceMap = convertSourceMap.fromMapFileSource(code, dirPath);
	}

	if (sourceMap) {
		sourceMap = sourceMap.toObject();
	}

	return sourceMap;
}

class CachingPrecompiler {
	constructor(options) {
		autoBind(this);

		this.getBabelOptions = options.getBabelOptions;
		this.babelCacheKeys = options.babelCacheKeys;
		this.cacheDirPath = options.path;
		this.fileHashes = {};
		this.transform = this._createTransform();
	}
	precompileFile(filePath) {
		if (!this.fileHashes[filePath]) {
			const source = stripBomBuf(fs.readFileSync(filePath));
			this.transform(source, filePath);
		}

		return this.fileHashes[filePath];
	}
	// Conditionally called by caching-transform when precompiling is required
	_init() {
		this.babel = require('babel-core');
		return this._transform;
	}
	_transform(code, filePath, hash) {
		code = code.toString();

		let result;
		const originalBabelDisableCache = process.env.BABEL_DISABLE_CACHE;
		try {
			// Disable Babel's cache. AVA has good cache management already.
			process.env.BABEL_DISABLE_CACHE = '1';

			result = this.babel.transform(code, Object.assign(this.getBabelOptions(), {
				inputSourceMap: getSourceMap(filePath, code),
				filename: filePath,
				sourceMaps: true,
				ast: false
			}));
		} finally {
			// Restore the original value. It is passed to workers, where users may
			// not want Babel's cache to be disabled.
			process.env.BABEL_DISABLE_CACHE = originalBabelDisableCache;
		}

		// Save source map
		const mapPath = path.join(this.cacheDirPath, `${hash}.js.map`);
		fs.writeFileSync(mapPath, JSON.stringify(result.map));

		// Append source map comment to transformed code
		// So that other libraries (like nyc) can find the source map
		const dirPath = path.dirname(filePath);
		const relativeMapPath = path.relative(dirPath, mapPath);
		const comment = convertSourceMap.generateMapFileComment(relativeMapPath);

		return `${result.code}\n${comment}`;
	}
	_createTransform() {
		const salt = packageHash.sync([
			require.resolve('../package.json'),
			require.resolve('babel-core/package.json')
		], this.babelCacheKeys);

		return cachingTransform({
			factory: this._init,
			cacheDir: this.cacheDirPath,
			hash: this._generateHash,
			salt,
			ext: '.js'
		});
	}
	_generateHash(code, filePath, salt) {
		const hash = md5Hex([code, filePath, salt]);
		this.fileHashes[filePath] = hash;
		return hash;
	}
}

module.exports = CachingPrecompiler;