Skip to content

Commit 09bc926

Browse files
committed
on exit
1 parent 905a2af commit 09bc926

File tree

11 files changed

+298
-29
lines changed

11 files changed

+298
-29
lines changed

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
"@vue/runtime-core": "^3.2.31"
1313
},
1414
"devDependencies": {
15+
"@types/node": "^17.0.18",
1516
"@vitejs/plugin-vue": "^2.2.0",
1617
"typescript": "^4.5.4",
1718
"vite": "^2.8.0",

pnpm-lock.yaml

+6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/tui/App.vue

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script setup lang="ts">
2-
import { ref, onMounted } from './renderer'
2+
import { ref, onMounted, onUnmounted } from './renderer'
33
const n = ref(0)
44
55
onMounted(() => {
@@ -8,6 +8,10 @@ onMounted(() => {
88
n.value++
99
}, 1000)
1010
})
11+
12+
onUnmounted(() => {
13+
console.log('removed')
14+
})
1115
</script>
1216

1317
<template>Counter: {{ n }}</template>

src/tui/deps/signal-exit/index.ts

+199
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
// @ts-nocheck
2+
import assert from 'node:assert'
3+
import EE from 'node:events'
4+
import allSignals from './signals'
5+
export type { Signal } from './signals'
6+
7+
let signals = allSignals
8+
9+
const processOk = function (process: NodeJS.Process) {
10+
return (
11+
process &&
12+
typeof process === 'object' &&
13+
typeof process.removeListener === 'function' &&
14+
typeof process.emit === 'function' &&
15+
// typeof process.reallyExit === 'function' &&
16+
typeof process.listeners === 'function' &&
17+
typeof process.kill === 'function' &&
18+
typeof process.pid === 'number' &&
19+
typeof process.on === 'function'
20+
)
21+
}
22+
23+
// some kind of non-node environment, just no-op
24+
/* istanbul ignore if */
25+
var isWin = /^win/i.test(process.platform)
26+
27+
var emitter
28+
if (process.__signal_exit_emitter__) {
29+
emitter = process.__signal_exit_emitter__
30+
} else {
31+
emitter = process.__signal_exit_emitter__ = new EE()
32+
emitter.count = 0
33+
emitter.emitted = {}
34+
}
35+
36+
// Because this emitter is a global, we have to check to see if a
37+
// previous version of this library failed to enable infinite listeners.
38+
// I know what you're about to say. But literally everything about
39+
// signal-exit is a compromise with evil. Get used to it.
40+
if (!emitter.infinite) {
41+
emitter.setMaxListeners(Infinity)
42+
emitter.infinite = true
43+
}
44+
45+
export function onExit(
46+
cb: (code: number | null, signal: signalExit.Signal | null) => void,
47+
opts?: signalExit.Options
48+
): () => void {
49+
/* istanbul ignore if */
50+
if (!processOk(global.process)) {
51+
return function () {}
52+
}
53+
assert.equal(
54+
typeof cb,
55+
'function',
56+
'a callback must be provided for exit handler'
57+
)
58+
59+
if (loaded === false) {
60+
load()
61+
}
62+
63+
var ev = 'exit'
64+
if (opts && opts.alwaysLast) {
65+
ev = 'afterexit'
66+
}
67+
68+
var remove = function () {
69+
emitter.removeListener(ev, cb)
70+
if (
71+
emitter.listeners('exit').length === 0 &&
72+
emitter.listeners('afterexit').length === 0
73+
) {
74+
unload()
75+
}
76+
}
77+
emitter.on(ev, cb)
78+
79+
return remove
80+
}
81+
82+
export var unload = function unload() {
83+
if (!loaded || !processOk(global.process)) {
84+
return
85+
}
86+
loaded = false
87+
88+
signals.forEach(function (sig) {
89+
try {
90+
process.removeListener(sig, sigListeners[sig])
91+
} catch (er) {}
92+
})
93+
process.emit = originalProcessEmit
94+
process.reallyExit = originalProcessReallyExit
95+
emitter.count -= 1
96+
}
97+
98+
var emit = function emit(event, code, signal) {
99+
/* istanbul ignore if */
100+
if (emitter.emitted[event]) {
101+
return
102+
}
103+
emitter.emitted[event] = true
104+
emitter.emit(event, code, signal)
105+
}
106+
107+
// { <signal>: <listener fn>, ... }
108+
var sigListeners = {}
109+
signals.forEach(function (sig) {
110+
sigListeners[sig] = function listener() {
111+
/* istanbul ignore if */
112+
if (!processOk(global.process)) {
113+
return
114+
}
115+
// If there are no other listeners, an exit is coming!
116+
// Simplest way: remove us and then re-send the signal.
117+
// We know that this will kill the process, so we can
118+
// safely emit now.
119+
var listeners = process.listeners(sig)
120+
if (listeners.length === emitter.count) {
121+
unload()
122+
emit('exit', null, sig)
123+
/* istanbul ignore next */
124+
emit('afterexit', null, sig)
125+
/* istanbul ignore next */
126+
if (isWin && sig === 'SIGHUP') {
127+
// "SIGHUP" throws an `ENOSYS` error on Windows,
128+
// so use a supported signal instead
129+
sig = 'SIGINT'
130+
}
131+
/* istanbul ignore next */
132+
process.kill(process.pid, sig)
133+
}
134+
}
135+
})
136+
137+
export { signals }
138+
139+
var loaded = false
140+
141+
var load = function load() {
142+
if (loaded || !processOk(global.process)) {
143+
return
144+
}
145+
loaded = true
146+
147+
// This is the number of onSignalExit's that are in play.
148+
// It's important so that we can count the correct number of
149+
// listeners on signals, and don't wait for the other one to
150+
// handle it instead of us.
151+
emitter.count += 1
152+
153+
signals = signals.filter(function (sig) {
154+
try {
155+
process.on(sig, sigListeners[sig])
156+
return true
157+
} catch (er) {
158+
return false
159+
}
160+
})
161+
162+
process.emit = processEmit
163+
process.reallyExit = processReallyExit
164+
}
165+
166+
export { load }
167+
168+
var originalProcessReallyExit = process.reallyExit
169+
var processReallyExit = function processReallyExit(code) {
170+
/* istanbul ignore if */
171+
if (!processOk(global.process)) {
172+
return
173+
}
174+
process.exitCode = code || /* istanbul ignore next */ 0
175+
emit('exit', process.exitCode, null)
176+
/* istanbul ignore next */
177+
emit('afterexit', process.exitCode, null)
178+
/* istanbul ignore next */
179+
originalProcessReallyExit.call(process, process.exitCode)
180+
}
181+
182+
var originalProcessEmit = process.emit
183+
var processEmit = function processEmit(ev, arg) {
184+
if (ev === 'exit' && processOk(global.process)) {
185+
/* istanbul ignore else */
186+
if (arg !== undefined) {
187+
process.exitCode = arg
188+
}
189+
var ret = originalProcessEmit.apply(this, arguments)
190+
/* istanbul ignore next */
191+
emit('exit', process.exitCode, null)
192+
/* istanbul ignore next */
193+
emit('afterexit', process.exitCode, null)
194+
/* istanbul ignore next */
195+
return ret
196+
} else {
197+
return originalProcessEmit.apply(this, arguments)
198+
}
199+
}

src/tui/deps/signal-exit/signals.ts

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
// This is not the set of all possible signals.
2+
//
3+
// It IS, however, the set of all signals that trigger
4+
// an exit on either Linux or BSD systems. Linux is a
5+
// superset of the signal names supported on BSD, and
6+
// the unknown signals just fail to register, so we can
7+
// catch that easily enough.
8+
//
9+
// Don't bother with SIGKILL. It's uncatchable, which
10+
// means that we can't fire any callbacks anyway.
11+
//
12+
// If a user does happen to register a handler on a non-
13+
// fatal signal like SIGWINCH or something, and then
14+
// exit, it'll end up firing `process.emit('exit')`, so
15+
// the handler will be fired anyway.
16+
//
17+
// SIGBUS, SIGFPE, SIGSEGV and SIGILL, when not raised
18+
// artificially, inherently leave the process in a
19+
// state from which it is not safe to try and enter JS
20+
// listeners.
21+
22+
export type Signal =
23+
| 'SIGABRT'
24+
| 'SIGALRM'
25+
| 'SIGHUP'
26+
| 'SIGINT'
27+
| 'SIGTERM'
28+
| string
29+
30+
const signals: Signal[] = ['SIGABRT', 'SIGALRM', 'SIGHUP', 'SIGINT', 'SIGTERM']
31+
32+
if (process.platform !== 'win32') {
33+
signals.push(
34+
'SIGVTALRM',
35+
'SIGXCPU',
36+
'SIGXFSZ',
37+
'SIGUSR2',
38+
'SIGTRAP',
39+
'SIGSYS',
40+
'SIGQUIT',
41+
'SIGIOT'
42+
// should detect profiler and enable/disable accordingly.
43+
// see #21
44+
// 'SIGPROF'
45+
)
46+
}
47+
48+
if (process.platform === 'linux') {
49+
signals.push('SIGIO', 'SIGPOLL', 'SIGPWR', 'SIGSTKFLT', 'SIGUNUSED')
50+
}
51+
52+
export default signals

src/tui/index.ts

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
import App from './App.vue'
22
import { createApp } from './renderer'
33

4-
createApp(App).mount({
5-
ROOT: true,
6-
})
4+
createApp(App).mount()

src/tui/renderer/index.ts

+32-4
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
import { createRenderer } from '@vue/runtime-core'
2-
import type { RendererNode, RendererElement } from '@vue/runtime-core'
2+
import { onExit } from '../deps/signal-exit'
3+
import type { App, Component } from '@vue/runtime-core'
34

45
export interface TuiNode {}
56
export interface TuiElement extends TuiNode {}
67

7-
const { render, createApp } = createRenderer<TuiNode, TuiElement>({
8+
const { render, createApp: baseCreateApp } = createRenderer<
9+
TuiNode,
10+
TuiElement
11+
>({
812
patchProp(el, key, prevValue, nextValue) {
913
console.log('patchProp', { el, key, nextValue })
1014
},
@@ -51,8 +55,32 @@ const { render, createApp } = createRenderer<TuiNode, TuiElement>({
5155
},
5256
})
5357

54-
// `render` is the low-level API
55-
// `createApp` returns an app instance
58+
type TODO = any
59+
60+
export interface TuiApp extends Omit<App<TODO>, 'mount'> {
61+
mount(): void
62+
}
63+
64+
function createApp(
65+
rootComponent: Component,
66+
rootProps?: Record<string, unknown> | null
67+
) {
68+
const app = baseCreateApp(rootComponent, rootProps)
69+
const mount = app.mount
70+
// TODO: actually build a host
71+
const host = { ROOT: true }
72+
const newApp = app as unknown as TuiApp
73+
newApp.mount = () => {
74+
mount(host)
75+
}
76+
77+
onExit(() => {
78+
newApp.unmount()
79+
})
80+
81+
return newApp
82+
}
83+
5684
export { render, createApp }
5785

5886
// re-export Vue core APIs

src/tui/vuminal/App.vue

-13
This file was deleted.

src/tui/vuminal/main.ts

-7
This file was deleted.

tsconfig.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
"sourceMap": true,
1010
"resolveJsonModule": true,
1111
"esModuleInterop": true,
12-
"lib": ["esnext", "dom"]
12+
"lib": ["esnext"]
1313
},
1414
"include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
1515
"references": [{ "path": "./tsconfig.node.json" }]

0 commit comments

Comments
 (0)