This repository was archived by the owner on Oct 1, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 95
Expand file tree
/
Copy pathconfig-parser.js
More file actions
418 lines (357 loc) · 13.9 KB
/
config-parser.js
File metadata and controls
418 lines (357 loc) · 13.9 KB
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
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
import fs from 'fs';
import path from 'path';
import zlib from 'zlib';
import mkdirp from 'mkdirp';
import {pfs} from './promise';
import FileChangedCache from './file-change-cache';
import CompilerHost from './compiler-host';
import registerRequireExtension from './require-hook';
const d = require('debug')('electron-compile:config-parser');
// NB: We intentionally delay-load this so that in production, you can create
// cache-only versions of these compilers
let allCompilerClasses = null;
function statSyncNoException(fsPath) {
if ('statSyncNoException' in fs) {
return fs.statSyncNoException(fsPath);
}
try {
return fs.statSync(fsPath);
} catch (e) {
return null;
}
}
/**
* Initialize the global hooks (protocol hook for file:, node.js hook)
* independent of initializing the compiler. This method is usually called by
* init instead of directly
*
* @param {CompilerHost} compilerHost The compiler host to use.
*
*/
export function initializeGlobalHooks(compilerHost) {
let globalVar = (global || window);
globalVar.globalCompilerHost = compilerHost;
registerRequireExtension(compilerHost);
if ('type' in process && process.type === 'browser') {
const { app } = require('electron');
const { initializeProtocolHook } = require('./protocol-hook');
let protoify = function() { initializeProtocolHook(compilerHost); };
if (app.isReady()) {
protoify();
} else {
app.on('ready', protoify);
}
}
}
/**
* Initialize electron-compile and set it up, either for development or
* production use. This is almost always the only method you need to use in order
* to use electron-compile.
*
* @param {string} appRoot The top-level directory for your application (i.e.
* the one which has your package.json).
*
* @param {string} mainModule The module to require in, relative to the module
* calling init, that will start your app. Write this
* as if you were writing a require call from here.
*
* @param {bool} productionMode If explicitly True/False, will set read-only
* mode to be disabled/enabled. If not, we'll
* guess based on the presence of a production
* cache.
*
* @param {string} cacheDir If not passed in, read-only will look in
* `appRoot/.cache` and dev mode will compile to a
* temporary directory. If it is passed in, both modes
* will cache to/from `appRoot/{cacheDir}`
*
* @param {string} sourceMapPath (optional) The directory to store sourcemap separately
* if compiler option enabled to emit.
* Default to cachePath if not specified, will be ignored for read-only mode.
*/
export function init(appRoot, mainModule, productionMode = null, cacheDir = null, sourceMapPath = null) {
let compilerHost = null;
let rootCacheDir = path.join(appRoot, cacheDir || '.cache');
if (productionMode === null) {
productionMode = !!statSyncNoException(rootCacheDir);
}
if (productionMode) {
compilerHost = CompilerHost.createReadonlyFromConfigurationSync(rootCacheDir, appRoot);
} else {
// if cacheDir was passed in, pass it along. Otherwise, default to a tempdir.
const cachePath = cacheDir ? rootCacheDir : null;
const mapPath = sourceMapPath ? path.join(appRoot, sourceMapPath) : cachePath;
compilerHost = createCompilerHostFromProjectRootSync(appRoot, cachePath, mapPath);
}
initializeGlobalHooks(compilerHost);
require.main.require(mainModule);
}
/**
* Creates a {@link CompilerHost} with the given information. This method is
* usually called by {@link createCompilerHostFromProjectRoot}.
*
* @private
*/
export function createCompilerHostFromConfiguration(info) {
let compilers = createCompilers();
let rootCacheDir = info.rootCacheDir || calculateDefaultCompileCacheDirectory();
const sourceMapPath = info.sourceMapPath || info.rootCacheDir;
if (info.sourceMapPath) {
createSourceMapDirectory(sourceMapPath);
}
d(`Creating CompilerHost: ${JSON.stringify(info)}, rootCacheDir = ${rootCacheDir}, sourceMapPath = ${sourceMapPath}`);
let fileChangeCache = new FileChangedCache(info.appRoot);
let compilerInfo = path.join(rootCacheDir, 'compiler-info.json.gz');
if (fs.existsSync(compilerInfo)) {
let buf = fs.readFileSync(compilerInfo);
let json = JSON.parse(zlib.gunzipSync(buf));
fileChangeCache = FileChangedCache.loadFromData(json.fileChangeCache, info.appRoot, false);
}
Object.keys(info.options || {}).forEach((x) => {
let opts = info.options[x];
if (!(x in compilers)) {
throw new Error(`Found compiler settings for missing compiler: ${x}`);
}
// NB: Let's hope this isn't a valid compiler option...
if (opts.passthrough) {
compilers[x] = compilers['text/plain'];
delete opts.passthrough;
}
d(`Setting options for ${x}: ${JSON.stringify(opts)}`);
compilers[x].compilerOptions = opts;
});
let ret = new CompilerHost(rootCacheDir, compilers, fileChangeCache, false, compilers['text/plain'], sourceMapPath);
// NB: It's super important that we guarantee that the configuration is saved
// out, because we'll need to re-read it in the renderer process
d(`Created compiler host with options: ${JSON.stringify(info)}`);
ret.saveConfigurationSync();
return ret;
}
/**
* Creates a compiler host from a .babelrc file. This method is usually called
* from {@link createCompilerHostFromProjectRoot} instead of used directly.
*
* @param {string} file The path to a .babelrc file
*
* @param {string} rootCacheDir (optional) The directory to use as a cache.
*
* @return {Promise<CompilerHost>} A set-up compiler host
*/
export async function createCompilerHostFromBabelRc(file, rootCacheDir=null, sourceMapPath = null) {
let info = JSON.parse(await pfs.readFile(file, 'utf8'));
// package.json
if ('babel' in info) {
info = info.babel;
}
if ('env' in info) {
let ourEnv = process.env.BABEL_ENV || process.env.NODE_ENV || 'development';
info = info.env[ourEnv];
}
// Are we still package.json (i.e. is there no babel info whatsoever?)
if ('name' in info && 'version' in info) {
return createCompilerHostFromConfiguration({
appRoot: path.dirname(file),
options: getDefaultConfiguration(),
rootCacheDir,
sourceMapPath
});
}
return createCompilerHostFromConfiguration({
appRoot: path.dirname(file),
options: {
'application/javascript': info
},
rootCacheDir,
sourceMapPath
});
}
/**
* Creates a compiler host from a .compilerc file. This method is usually called
* from {@link createCompilerHostFromProjectRoot} instead of used directly.
*
* @param {string} file The path to a .compilerc file
*
* @param {string} rootCacheDir (optional) The directory to use as a cache.
*
* @return {Promise<CompilerHost>} A set-up compiler host
*/
export async function createCompilerHostFromConfigFile(file, rootCacheDir=null, sourceMapPath = null) {
let info = JSON.parse(await pfs.readFile(file, 'utf8'));
if ('env' in info) {
let ourEnv = process.env.ELECTRON_COMPILE_ENV || process.env.NODE_ENV || 'development';
info = info.env[ourEnv];
}
return createCompilerHostFromConfiguration({
appRoot: path.dirname(file),
options: info,
rootCacheDir,
sourceMapPath
});
}
/**
* Creates a configured {@link CompilerHost} instance from the project root
* directory. This method first searches for a .compilerc (or .compilerc.json), then falls back to the
* default locations for Babel configuration info. If neither are found, defaults
* to standard settings
*
* @param {string} rootDir The root application directory (i.e. the directory
* that has the app's package.json)
*
* @param {string} rootCacheDir (optional) The directory to use as a cache.
*
* @param {string} sourceMapPath (optional) The directory to store sourcemap separately
* if compiler option enabled to emit.
*
* @return {Promise<CompilerHost>} A set-up compiler host
*/
export async function createCompilerHostFromProjectRoot(rootDir, rootCacheDir = null, sourceMapPath = null) {
let compilerc = path.join(rootDir, '.compilerc');
if (statSyncNoException(compilerc)) {
d(`Found a .compilerc at ${compilerc}, using it`);
return await createCompilerHostFromConfigFile(compilerc, rootCacheDir, sourceMapPath);
}
compilerc += '.json';
if (statSyncNoException(compilerc)) {
d(`Found a .compilerc at ${compilerc}, using it`);
return await createCompilerHostFromConfigFile(compilerc, rootCacheDir, sourceMapPath);
}
let babelrc = path.join(rootDir, '.babelrc');
if (statSyncNoException(babelrc)) {
d(`Found a .babelrc at ${babelrc}, using it`);
return await createCompilerHostFromBabelRc(babelrc, rootCacheDir, sourceMapPath);
}
d(`Using package.json or default parameters at ${rootDir}`);
return await createCompilerHostFromBabelRc(path.join(rootDir, 'package.json'), rootCacheDir, sourceMapPath);
}
export function createCompilerHostFromBabelRcSync(file, rootCacheDir=null, sourceMapPath = null) {
let info = JSON.parse(fs.readFileSync(file, 'utf8'));
// package.json
if ('babel' in info) {
info = info.babel;
}
if ('env' in info) {
let ourEnv = process.env.BABEL_ENV || process.env.NODE_ENV || 'development';
info = info.env[ourEnv];
}
// Are we still package.json (i.e. is there no babel info whatsoever?)
if ('name' in info && 'version' in info) {
return createCompilerHostFromConfiguration({
appRoot: path.dirname(file),
options: getDefaultConfiguration(),
rootCacheDir,
sourceMapPath
});
}
return createCompilerHostFromConfiguration({
appRoot: path.dirname(file),
options: {
'application/javascript': info
},
rootCacheDir,
sourceMapPath
});
}
export function createCompilerHostFromConfigFileSync(file, rootCacheDir=null, sourceMapPath = null) {
let info = JSON.parse(fs.readFileSync(file, 'utf8'));
if ('env' in info) {
let ourEnv = process.env.ELECTRON_COMPILE_ENV || process.env.NODE_ENV || 'development';
info = info.env[ourEnv];
}
return createCompilerHostFromConfiguration({
appRoot: path.dirname(file),
options: info,
rootCacheDir,
sourceMapPath
});
}
export function createCompilerHostFromProjectRootSync(rootDir, rootCacheDir = null, sourceMapPath = null) {
let compilerc = path.join(rootDir, '.compilerc');
if (statSyncNoException(compilerc)) {
d(`Found a .compilerc at ${compilerc}, using it`);
return createCompilerHostFromConfigFileSync(compilerc, rootCacheDir, sourceMapPath);
}
let babelrc = path.join(rootDir, '.babelrc');
if (statSyncNoException(babelrc)) {
d(`Found a .babelrc at ${babelrc}, using it`);
return createCompilerHostFromBabelRcSync(babelrc, rootCacheDir, sourceMapPath);
}
d(`Using package.json or default parameters at ${rootDir}`);
return createCompilerHostFromBabelRcSync(path.join(rootDir, 'package.json'), rootCacheDir, sourceMapPath);
}
/**
* Returns what electron-compile would use as a default rootCacheDir. Usually only
* used for debugging purposes
*
* @return {string} A path that may or may not exist where electron-compile would
* set up a development mode cache.
*/
export function calculateDefaultCompileCacheDirectory() {
let tmpDir = process.env.TEMP || process.env.TMPDIR || '/tmp';
let hash = require('crypto').createHash('md5').update(process.execPath).digest('hex');
let cacheDir = path.join(tmpDir, `compileCache_${hash}`);
mkdirp.sync(cacheDir);
d(`Using default cache directory: ${cacheDir}`);
return cacheDir;
}
function createSourceMapDirectory(sourceMapPath) {
mkdirp.sync(sourceMapPath);
d(`Using separate sourcemap path at ${sourceMapPath}`);
}
/**
* Returns the default .configrc if no configuration information can be found.
*
* @return {Object} A list of default config settings for electron-compiler.
*/
export function getDefaultConfiguration() {
return {
'application/javascript': {
"presets": ["es2016-node5", "react"],
"sourceMaps": "inline"
}
};
}
/**
* Allows you to create new instances of all compilers that are supported by
* electron-compile and use them directly. Currently supports Babel, CoffeeScript,
* TypeScript, Less, and Jade.
*
* @return {Object} An Object whose Keys are MIME types, and whose values
* are instances of @{link CompilerBase}.
*/
export function createCompilers() {
if (!allCompilerClasses) {
// First we want to see if electron-compilers itself has been installed with
// devDependencies. If that's not the case, check to see if
// electron-compilers is installed as a peer dependency (probably as a
// devDependency of the root project).
const locations = ['electron-compilers', '../../electron-compilers'];
for (let location of locations) {
try {
allCompilerClasses = require(location);
} catch (e) {
// Yolo
}
}
if (!allCompilerClasses) {
throw new Error("Electron compilers not found but were requested to be loaded");
}
}
// NB: Note that this code is carefully set up so that InlineHtmlCompiler
// (i.e. classes with `createFromCompilers`) initially get an empty object,
// but will have a reference to the final result of what we return, which
// resolves the circular dependency we'd otherwise have here.
let ret = {};
let instantiatedClasses = allCompilerClasses.map((Klass) => {
if ('createFromCompilers' in Klass) {
return Klass.createFromCompilers(ret);
} else {
return new Klass();
}
});
instantiatedClasses.reduce((acc,x) => {
let Klass = Object.getPrototypeOf(x).constructor;
for (let type of Klass.getInputMimeTypes()) { acc[type] = x; }
return acc;
}, ret);
return ret;
}