-
Notifications
You must be signed in to change notification settings - Fork 107
Expand file tree
/
Copy pathdev.js
More file actions
136 lines (120 loc) · 5.8 KB
/
Copy pathdev.js
File metadata and controls
136 lines (120 loc) · 5.8 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
// Development launcher: compiles src/Main and src/Renderer with Fable in parallel, then
// starts Electron (webpack dev server + app) via scripts/start.js.
//
// node scripts/dev.js fable watch, hot reload (npm run dev)
// node scripts/dev.js --once one-shot compile, no watching (npm run dev:once)
// node scripts/dev.js --asserts watch with ASSERTS define (npm run debug)
// node scripts/dev.js --no-app compile only, skip Electron
//
// The app starts as soon as both projects' generated JS is safe to load. In watch mode
// Fable signals that through the --run command (see fable-ready.js): immediately when all
// .fs.js files are up-to-date, otherwise after the first compilation. In --once mode a
// fully up-to-date project skips compilation in under a second.
const { spawn } = require('child_process');
const path = require('path');
const { reportRefreshStaleOutput } = require('./refresh-stale-output');
const { outDirOf, outPathOf } = require('./fable-output');
const { ensureRestored } = require('./fable-restore');
const root = path.join(__dirname, '..');
const once = process.argv.includes('--once');
const asserts = process.argv.includes('--asserts');
const noApp = process.argv.includes('--no-app');
const READY_MARKER = '__FABLE_READY__';
const reset = '\x1b[0m';
const projects = [
{ name: 'Main', dir: 'src/Main', color: '\x1b[36m', defines: [] },
{ name: 'Renderer', dir: 'src/Renderer', color: '\x1b[35m', defines: asserts ? ['ASSERTS'] : [] },
];
const fableProcs = [];
let appProc = null;
let shuttingDown = false;
function shutdown(code) {
if (shuttingDown) return;
shuttingDown = true;
for (const proc of fableProcs) proc.kill();
if (appProc) appProc.kill();
process.exit(code);
}
process.on('SIGINT', () => {
console.log('\nShutting down...');
shutdown(0);
});
// Switches that are not this script's own are meant for Electron, and start.js passes them on:
// `npm run dev -- --log=wire` is how a log category is on before the app's first line runs.
// A single dash counts too, so that -d and -w survive the trip - filtering on '--' silently ate
// them, which is half of why `npm run dev -- -d` never turned the Development menu on.
const ours = ['--once', '--asserts', '--no-app'];
const forwarded = process.argv.slice(2).filter((a) => a.startsWith('-') && !ours.includes(a));
function startApp() {
if (appProc || noApp) return;
console.log(`\x1b[32m[App]${reset} Starting webpack dev server and Electron...`);
appProc = spawn(process.execPath, [path.join(__dirname, 'start.js'), ...forwarded], {
cwd: root,
stdio: 'inherit',
env: { ...process.env, NODE_ENV: 'development', ELECTRON_ENABLE_LOGGING: 'true' },
});
appProc.on('close', (code) => shutdown(code === null ? 0 : code));
}
let readyCount = 0;
function onReady(p) {
if (p.ready) return;
p.ready = true;
readyCount += 1;
console.log(`${p.color}[${p.name}]${reset} ready`);
if (readyCount === projects.length) {
// Ready means either nothing needed compiling - so nothing can be stale - or the compile
// finished. Either way every generated file is current, and one that still looks older than
// its source would cost a full recompile on every future startup. See refresh-stale-output.js.
reportRefreshStaleOutput(projects.map((proj) => outPathOf(proj.dir)));
startApp();
}
}
// Restore here, once, rather than letting the two Fable processes below each do it as a side
// effect of cracking their project: started together they race to write src/Shared's
// project.assets.json and one of them aborts. See scripts/fable-restore.js.
ensureRestored();
for (const p of projects) {
const args = ['fable'];
if (!once) args.push('watch');
// --noRestore because that restore has just happened, once, above.
args.push(p.dir, '-s', '--noRestore');
// Each project compiles to its own tree, including its copy of the shared sources and of Fable's
// library. Without this both projects write src/Shared/*.fs.js, and whichever finishes last
// decides which library that file imports - which put two copies of it in the main bundle and
// broke every F# Map that crossed the seam. See scripts/fable-output.js.
args.push('-o', outDirOf(p.dir), '-e', '.fs.js');
for (const d of p.defines) args.push('--define', d);
// Relative, deliberately: this path is passed through two layers that join arguments back into
// a command line - `shell: true` below, and Fable's own --run, which re-emits it as
// `cmd /C node <path>` - and neither quotes. An absolute path therefore split at the first
// space, and a checkout under "C:\My Projects\issie" never saw the ready signal, so the app
// never started. A path relative to the repo root has no space wherever the repo lives, and
// the root is this process's cwd (see the spawn below) and Fable's.
if (!once) args.push('--run', 'node', 'scripts/fable-ready.js');
const proc = spawn('dotnet', args, { cwd: root, shell: true });
fableProcs.push(proc);
const forward = (write) => (data) => {
for (const line of data.toString().split('\n')) {
if (!line.trim()) continue;
write(`${p.color}[${p.name}]${reset} ${line}`);
if (!once && line.includes(READY_MARKER)) onReady(p);
}
};
proc.stdout.on('data', forward(console.log));
proc.stderr.on('data', forward(console.error));
proc.on('error', (err) => {
console.error(`${p.color}[${p.name}]${reset} failed to start:`, err.message);
shutdown(1);
});
proc.on('close', (code) => {
if (shuttingDown) return;
if (once && code === 0) {
onReady(p);
} else {
// In watch mode fable should never exit on its own; in once mode a non-zero
// exit is a compile failure. Either way, stop everything.
console.error(`${p.color}[${p.name}]${reset} fable exited with code ${code}`);
shutdown(code === 0 ? 1 : code);
}
});
}