forked from kmagiera/babel-watch
-
Notifications
You must be signed in to change notification settings - Fork 0
/
runner.js
120 lines (108 loc) · 3.98 KB
/
runner.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
// @flow
const path = require('path');
const fs = require('fs');
const sourceMapSupport = require('source-map-support');
let sources = {};
let maps = {};
let pipeFd;
const BUFFER = new Buffer.alloc(10 * 1024);
// $FlowIgnore Flow doesn't recognize require.extensions
const reqExtensions/*: any */ = require.extensions;
// Node by default uses '.js' loader to load all the files with unknown extensions
const DEFAULT_LOADER = reqExtensions['.js'];
function readLength(fd) {
let bytes = 0;
while (typeof bytes === 'number' && bytes !== 4) {
// $FlowIgnore position can be null
bytes = fs.readSync(fd, BUFFER, 0, 4, null);
}
return BUFFER.readUInt32BE(0);
}
function readFileFromPipeSync(fd) {
let length = readLength(fd);
let result = Buffer.alloc(0);
while (length > 0) {
// $FlowIgnore position can be null
const newBytes = fs.readSync(fd, BUFFER, 0, Math.min(BUFFER.length, length));
length -= newBytes;
result = Buffer.concat([result, BUFFER], result.length + newBytes);
}
return result.toString();
}
function babelWatchLoader(module_, filename, defaultHandler) {
// apparently require loader needs to be synchronous, which
// complicates things a little bit as we need to get source
// file from the parent process synchronously.
// The best method I've found so far is to use readFileSync on
// a named unix pipe (mkfifo). All the alternative ways would
// require writing native code which usually brings large
// dependencies to the project and I prefer to avoid that
//
// $FlowIgnore we know process.send exists b/c this is a child process
process.send({
event: 'babel-watch-filename',
filename: filename,
});
const source = readFileFromPipeSync(pipeFd);
const map = readFileFromPipeSync(pipeFd);
if (source) {
maps[filename] = map && JSON.parse(map);
module_._compile(source, filename);
} else {
defaultHandler(module_, filename);
}
}
function registerExtension(ext) {
const defaultHandler = reqExtensions[ext] || DEFAULT_LOADER;
reqExtensions[ext] = (module_, filename) => {
// ignore node_modules by default. don't you dare contacting the parent process!
if (filename.split(path.sep).indexOf('node_modules') < 0) {
babelWatchLoader(module_, filename, defaultHandler);
} else {
defaultHandler(module_, filename);
if (filename.indexOf('/node_modules/source-map-support/') !== -1 && module_.exports.install !== undefined) {
// When app is running in babel-watch the source-map-support library should not be used by separately. The
// runner process is initializing source-map-support passing a special source map loader that makes it possible
// to use maps generated by the "parent" process instead of reading them from the filesystem.
// We don't allow for source-map-support library to be reinitialized
module_.exports.install = () => {};
}
}
};
}
function replaceExtensionHooks(extensions) {
for (const ext in reqExtensions) {
registerExtension(ext);
}
for (let i = 0; i < extensions.length; i++) {
const ext = extensions[i];
if (!(ext in reqExtensions)) {
registerExtension(ext);
}
}
}
process.on('message', (options) => {
if (!options || options.event !== 'babel-watch-start') return;
replaceExtensionHooks(options.transpileExtensions);
sourceMapSupport.install({
environment: 'node',
hookRequire: options.debug,
handleUncaughtExceptions: !!options.handleUncaughtExceptions,
retrieveSourceMap(filename) {
const map = maps && maps[filename];
if (map) {
return {
url: filename,
map: map
};
}
return null;
},
});
// We don't allow for source-map-support library to be reinitialized (see comment in registerExtension function)
sourceMapSupport.install = () => {};
pipeFd = fs.openSync(options.pipe, 'r');
process.argv = ["node"].concat(options.args);
// $FlowIgnore doesn't recognize 'module' as it is internal
require('module').runMain();
});