-
Notifications
You must be signed in to change notification settings - Fork 1.4k
/
Copy pathcaching-precompiler.js
185 lines (143 loc) · 5.14 KB
/
caching-precompiler.js
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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
'use strict';
var path = require('path');
var fs = require('fs');
var generateMapFileComment = require('convert-source-map').generateMapFileComment;
var convertSourceMap = require('convert-source-map');
var cachingTransform = require('caching-transform');
var objectAssign = require('object-assign');
var stripBom = require('strip-bom');
var md5Hex = require('md5-hex');
var enhanceAssert = require('./enhance-assert');
function CachingPrecompiler(cacheDirPath, babelConfig) {
if (!(this instanceof CachingPrecompiler)) {
throw new TypeError('Class constructor CachingPrecompiler cannot be invoked without \'new\'');
}
this.babelConfig = babelConfig;
this.cacheDirPath = cacheDirPath;
this.fileHashes = {};
Object.keys(CachingPrecompiler.prototype).forEach(function (name) {
this[name] = this[name].bind(this);
}, this);
this.transform = this._createTransform();
}
module.exports = CachingPrecompiler;
CachingPrecompiler.prototype.precompileFile = function (filePath) {
if (!this.fileHashes[filePath]) {
var source = stripBom(fs.readFileSync(filePath));
this.transform(source, filePath);
}
return this.fileHashes[filePath];
};
// conditionally called by caching-transform when precompiling is required
CachingPrecompiler.prototype._factory = function () {
this._init();
return this._transform;
};
CachingPrecompiler.prototype._init = function () {
this.babel = require('babel-core');
this.defaultPresets = [
require('babel-preset-stage-2'),
require('babel-preset-es2015')
];
var transformRuntime = require('babel-plugin-transform-runtime');
var rewriteBabelPaths = this._createRewritePlugin();
var powerAssert = this._createEspowerPlugin();
this.defaultPlugins = [
powerAssert,
rewriteBabelPaths,
transformRuntime
];
};
CachingPrecompiler.prototype._transform = function (code, filePath, hash) {
code = code.toString();
var options = this._buildOptions(filePath, code);
var result = this.babel.transform(code, options);
// save source map
var mapPath = path.join(this.cacheDirPath, hash + '.js.map');
fs.writeFileSync(mapPath, JSON.stringify(result.map));
// When loading the test file, test workers intercept the require call and
// load the cached code instead. Libraries like nyc may also be intercepting
// require calls, however they won't know that different code was loaded.
// They may then attempt to resolve a source map from the original file
// location.
//
// Add a source map file comment to the cached code. The file path is
// relative from the directory of the original file to where the source map
// is cached. This will allow the source map to be resolved.
var dirPath = path.dirname(filePath);
var relativeMapPath = path.relative(dirPath, mapPath);
var comment = generateMapFileComment(relativeMapPath);
return result.code + '\n' + comment;
};
CachingPrecompiler.prototype._buildOptions = function (filePath, code) {
var options = {babelrc: false};
if (!this.babelConfig || this.babelConfig === 'default') {
objectAssign(options, {presets: this.defaultPresets});
} else if (this.babelConfig === 'inherit') {
objectAssign(options, {babelrc: true});
} else {
objectAssign(options, this.babelConfig);
}
var sourceMap = this._getSourceMap(filePath, code);
objectAssign(options, {
inputSourceMap: sourceMap,
filename: filePath,
sourceMaps: true,
ast: false
});
options.plugins = (options.plugins || []).concat(this.defaultPlugins);
return options;
};
CachingPrecompiler.prototype._getSourceMap = function (filePath, code) {
var sourceMap = convertSourceMap.fromSource(code);
if (!sourceMap) {
var dirPath = path.dirname(filePath);
sourceMap = convertSourceMap.fromMapFileSource(code, dirPath);
}
if (sourceMap) {
sourceMap = sourceMap.toObject();
}
return sourceMap;
};
CachingPrecompiler.prototype._createRewritePlugin = function () {
var wrapListener = require('babel-plugin-detective/wrap-listener');
return wrapListener(this._rewriteBabelRuntimePaths, 'rewrite-runtime', {
generated: true,
require: true,
import: true
});
};
CachingPrecompiler.prototype._rewriteBabelRuntimePaths = function (path) {
var isBabelPath = /^babel-runtime[\\\/]?/.test(path.node.value);
if (path.isLiteral() && isBabelPath) {
path.node.value = require.resolve(path.node.value);
}
};
CachingPrecompiler.prototype._createEspowerPlugin = function () {
var createEspowerPlugin = require('babel-plugin-espower/create');
// initialize power-assert
return createEspowerPlugin(this.babel, {
patterns: enhanceAssert.PATTERNS
});
};
CachingPrecompiler.prototype._createTransform = function () {
var dependencies = {
'babel-plugin-espower': require('babel-plugin-espower/package.json').version,
'ava': require('../package.json').version,
'babel-core': require('babel-core/package.json').version,
'babelConfig': this.babelConfig
};
var salt = new Buffer(JSON.stringify(dependencies));
return cachingTransform({
factory: this._factory,
cacheDir: this.cacheDirPath,
hash: this._generateHash,
salt: salt,
ext: '.js'
});
};
CachingPrecompiler.prototype._generateHash = function (code, filePath, salt) {
var hash = md5Hex([code, filePath, salt]);
this.fileHashes[filePath] = hash;
return hash;
};