Skip to content

Commit b50e994

Browse files
posvaAkryum
andauthored
feat(electron): work on node envioronments (#1780)
Co-authored-by: Guillaume Chau <[email protected]>
1 parent 2e1e907 commit b50e994

File tree

18 files changed

+273
-206
lines changed

18 files changed

+273
-206
lines changed

.eslintrc.js

+1
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ module.exports = {
5454
'/packages/*/lib/',
5555
'dist/',
5656
'build/',
57+
'build-node/',
5758
'/legacy',
5859
],
5960
overrides: [

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@ yarn-error.log
1313

1414
/packages/*/lib
1515
.amo.env.json
16+
build-node

packages/app-backend-core/src/app.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import {
55
BackendContext,
66
DevtoolsBackend,
77
} from '@vue-devtools/app-backend-api'
8-
import { BridgeEvents, SharedData } from '@vue-devtools/shared-utils'
8+
import { BridgeEvents, isBrowser, SharedData } from '@vue-devtools/shared-utils'
99
import { App } from '@vue/devtools-api'
1010
import slug from 'speakingurl'
1111
import { JobQueue } from './util/queue'
@@ -72,7 +72,7 @@ async function createAppRecord (options: AppRecordOptions, backend: DevtoolsBack
7272
instanceMap: new Map(),
7373
rootInstance,
7474
perfGroupIds: new Map(),
75-
iframe: document !== el.ownerDocument ? el.ownerDocument?.location?.pathname : null,
75+
iframe: isBrowser && document !== el.ownerDocument ? el.ownerDocument?.location?.pathname : null,
7676
meta: options.meta ?? {},
7777
}
7878

packages/app-backend-core/src/hook.ts

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
// this script is injected into every page.
2+
import { isBrowser, target } from '@vue-devtools/shared-utils'
23

34
/**
45
* Install the hook on window, which is an event emitter.
@@ -36,6 +37,8 @@ export function installHook (target, isIframe = false) {
3637

3738
let iframeChecks = 0
3839
function injectToIframes () {
40+
if (!isBrowser) return
41+
3942
const iframes = document.querySelectorAll<HTMLIFrameElement>('iframe:not([data-vue-devtools-ignore])')
4043
for (const iframe of iframes) {
4144
injectIframeHook(iframe)
@@ -549,7 +552,7 @@ export function installHook (target, isIframe = false) {
549552
}
550553

551554
// DOM objects
552-
if (object instanceof HTMLElement) {
555+
if (typeof HTMLElement !== 'undefined' && object instanceof HTMLElement) {
553556
return object.cloneNode(false)
554557
}
555558

packages/app-backend-core/src/index.ts

+9-3
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import {
1717
target,
1818
getPluginSettings,
1919
SharedData,
20+
isBrowser,
21+
raf,
2022
} from '@vue-devtools/shared-utils'
2123
import debounce from 'lodash/debounce'
2224
import throttle from 'lodash/throttle'
@@ -54,6 +56,8 @@ export async function initBackend (bridge: Bridge) {
5456
persist: false,
5557
})
5658

59+
SharedData.isBrowser = isBrowser
60+
5761
initOnPageConfig()
5862

5963
if (!connected) {
@@ -180,7 +184,7 @@ async function connect () {
180184
for (let i = 0; i < parentInstances.length; i++) {
181185
const parentId = await getComponentId(app, parentUid, parentInstances[i], ctx)
182186
if (i < 2 && isSubscribed(BridgeSubscriptions.COMPONENT_TREE, sub => sub.payload.instanceId === parentId)) {
183-
requestAnimationFrame(() => {
187+
raf(() => {
184188
sendComponentTreeData(appRecord, parentId, appRecord.componentFilter, null, ctx)
185189
})
186190
}
@@ -221,7 +225,7 @@ async function connect () {
221225
if (parentInstances.length) {
222226
const parentId = await getComponentId(app, parentUid, parentInstances[0], ctx)
223227
if (isSubscribed(BridgeSubscriptions.COMPONENT_TREE, sub => sub.payload.instanceId === parentId)) {
224-
requestAnimationFrame(async () => {
228+
raf(async () => {
225229
try {
226230
sendComponentTreeData(await getAppRecord(app, ctx), parentId, appRecord.componentFilter, null, ctx)
227231
} catch (e) {
@@ -449,13 +453,14 @@ function connectBridge () {
449453
const [el] = await ctx.currentAppRecord.backend.api.getComponentRootElements(instance)
450454
if (el) {
451455
// @ts-ignore
452-
window.__VUE_DEVTOOLS_INSPECT_TARGET__ = el
456+
target.__VUE_DEVTOOLS_INSPECT_TARGET__ = el
453457
ctx.bridge.send(BridgeEvents.TO_FRONT_COMPONENT_INSPECT_DOM, null)
454458
}
455459
}
456460
})
457461

458462
ctx.bridge.on(BridgeEvents.TO_BACK_COMPONENT_SCROLL_TO, async ({ instanceId }) => {
463+
if (!isBrowser) return
459464
const instance = getComponentInstance(ctx.currentAppRecord, instanceId, ctx)
460465
if (instance) {
461466
const [el] = await ctx.currentAppRecord.backend.api.getComponentRootElements(instance)
@@ -494,6 +499,7 @@ function connectBridge () {
494499
})
495500

496501
ctx.bridge.on(BridgeEvents.TO_BACK_COMPONENT_RENDER_CODE, async ({ instanceId }) => {
502+
if (!isBrowser) return
497503
const instance = getComponentInstance(ctx.currentAppRecord, instanceId, ctx)
498504
if (instance) {
499505
const { code } = await ctx.currentAppRecord.backend.api.getComponentRenderCode(instance)

packages/app-backend-core/src/perf.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { BackendContext, DevtoolsBackend } from '@vue-devtools/app-backend-api'
22
import { App, ComponentInstance } from '@vue/devtools-api'
3-
import { BridgeSubscriptions, SharedData } from '@vue-devtools/shared-utils'
3+
import { BridgeSubscriptions, raf, SharedData } from '@vue-devtools/shared-utils'
44
import { addTimelineEvent } from './timeline'
55
import { getAppRecord } from './app'
66
import { getComponentId, sendComponentTreeData } from './component'
@@ -144,7 +144,7 @@ export async function performanceMarkEnd (
144144
// Update component tree
145145
const id = await getComponentId(app, uid, instance, ctx)
146146
if (isSubscribed(BridgeSubscriptions.COMPONENT_TREE, sub => sub.payload.instanceId === id)) {
147-
requestAnimationFrame(() => {
147+
raf(() => {
148148
sendComponentTreeData(appRecord, id, ctx.currentAppRecord.componentFilter, null, ctx)
149149
})
150150
}

packages/app-backend-core/src/timeline.ts

+42-40
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { BackendContext, AppRecord } from '@vue-devtools/app-backend-api'
2-
import { BridgeEvents, HookEvents, stringify, SharedData } from '@vue-devtools/shared-utils'
2+
import { BridgeEvents, HookEvents, stringify, SharedData, isBrowser } from '@vue-devtools/shared-utils'
33
import { App, ID, TimelineEventOptions, WithId, now, isPerformanceSupported } from '@vue/devtools-api'
44
import { hook } from './global-hook'
55
import { getAppRecord, getAppRecordId } from './app'
@@ -21,50 +21,52 @@ export function addBuiltinLayers (appRecord: AppRecord, ctx: BackendContext) {
2121
}
2222

2323
function setupBuiltinLayers (ctx: BackendContext) {
24-
['mousedown', 'mouseup', 'click', 'dblclick'].forEach(eventType => {
25-
// @ts-ignore
26-
window.addEventListener(eventType, async (event: MouseEvent) => {
27-
await addTimelineEvent({
28-
layerId: 'mouse',
29-
event: {
30-
time: now(),
31-
data: {
32-
type: eventType,
33-
x: event.clientX,
34-
y: event.clientY,
24+
if (isBrowser) {
25+
['mousedown', 'mouseup', 'click', 'dblclick'].forEach(eventType => {
26+
// @ts-ignore
27+
window.addEventListener(eventType, async (event: MouseEvent) => {
28+
await addTimelineEvent({
29+
layerId: 'mouse',
30+
event: {
31+
time: now(),
32+
data: {
33+
type: eventType,
34+
x: event.clientX,
35+
y: event.clientY,
36+
},
37+
title: eventType,
3538
},
36-
title: eventType,
37-
},
38-
}, null, ctx)
39-
}, {
40-
capture: true,
41-
passive: true,
39+
}, null, ctx)
40+
}, {
41+
capture: true,
42+
passive: true,
43+
})
4244
})
43-
})
4445

45-
;['keyup', 'keydown', 'keypress'].forEach(eventType => {
46-
// @ts-ignore
47-
window.addEventListener(eventType, async (event: KeyboardEvent) => {
48-
await addTimelineEvent({
49-
layerId: 'keyboard',
50-
event: {
51-
time: now(),
52-
data: {
53-
type: eventType,
54-
key: event.key,
55-
ctrlKey: event.ctrlKey,
56-
shiftKey: event.shiftKey,
57-
altKey: event.altKey,
58-
metaKey: event.metaKey,
46+
;['keyup', 'keydown', 'keypress'].forEach(eventType => {
47+
// @ts-ignore
48+
window.addEventListener(eventType, async (event: KeyboardEvent) => {
49+
await addTimelineEvent({
50+
layerId: 'keyboard',
51+
event: {
52+
time: now(),
53+
data: {
54+
type: eventType,
55+
key: event.key,
56+
ctrlKey: event.ctrlKey,
57+
shiftKey: event.shiftKey,
58+
altKey: event.altKey,
59+
metaKey: event.metaKey,
60+
},
61+
title: event.key,
5962
},
60-
title: event.key,
61-
},
62-
}, null, ctx)
63-
}, {
64-
capture: true,
65-
passive: true,
63+
}, null, ctx)
64+
}, {
65+
capture: true,
66+
passive: true,
67+
})
6668
})
67-
})
69+
}
6870

6971
hook.on(HookEvents.COMPONENT_EMIT, async (app, instance, event, params) => {
7072
try {

packages/app-frontend/src/util/keyboard.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ type KeyboardHandler = (event: KeyboardEvent) => boolean | void | Promise<boolea
44

55
function handleKeyboard (type: 'keyup' | 'keydown', cb: KeyboardHandler) {
66
function handler (event: KeyboardEvent) {
7-
if (event.target instanceof HTMLElement && (
7+
if (typeof HTMLElement !== 'undefined' && event.target instanceof HTMLElement && (
88
event.target.tagName === 'INPUT' ||
99
event.target.tagName === 'TEXTAREA'
1010
)) {

packages/shared-utils/src/bridge.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { EventEmitter } from 'events'
2+
import { raf } from './raf'
23

34
const BATCH_DURATION = 100
45

@@ -118,6 +119,6 @@ export class Bridge extends EventEmitter {
118119
}
119120
}
120121
this._sending = false
121-
requestAnimationFrame(() => this._nextSend())
122+
raf(() => this._nextSend())
122123
}
123124
}

packages/shared-utils/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,4 @@ export * from './shell'
1010
export * from './storage'
1111
export * from './transfer'
1212
export * from './util'
13+
export * from './raf'

packages/shared-utils/src/raf.ts

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
2+
let pendingCallbacks: Array<(time: number) => void> = []
3+
4+
/**
5+
* requestAnimationFrame that also works on non-browser environments like Node.
6+
*/
7+
export const raf = typeof requestAnimationFrame === 'function'
8+
? requestAnimationFrame
9+
: (fn: (time: number) => void) => {
10+
if (!pendingCallbacks.length) {
11+
setImmediate(() => {
12+
const now = performance.now()
13+
const cbs = pendingCallbacks
14+
// in case cbs add new callbacks
15+
pendingCallbacks = []
16+
cbs.forEach(cb => cb(now))
17+
})
18+
}
19+
20+
pendingCallbacks.push(fn)
21+
}

packages/shared-utils/src/shared-data.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { setStorage, getStorage } from './storage'
22
import { Bridge } from './bridge'
3-
import { isMac } from './env'
3+
import { isBrowser, isMac } from './env'
44

55
// Initial state
66
const internalSharedData = {
@@ -31,6 +31,7 @@ const internalSharedData = {
3131
trackUpdates: true,
3232
flashUpdates: false,
3333
debugInfo: false,
34+
isBrowser,
3435
}
3536

3637
type TSharedData = typeof internalSharedData

packages/shared-utils/src/util.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ function replacer (key) {
233233
return encodeCache.cache(val, () => getCustomComponentDefinitionDetails(val))
234234
} else if (val.constructor && val.constructor.name === 'VNode') {
235235
return `[native VNode <${val.tag}>]`
236-
} else if (val instanceof HTMLElement) {
236+
} else if (typeof HTMLElement !== 'undefined' && val instanceof HTMLElement) {
237237
return encodeCache.cache(val, () => getCustomHTMLElementDetails(val))
238238
}
239239
const customDetails = getCustomObjectDetails(val, proto)
@@ -474,7 +474,7 @@ export function revive (val) {
474474
return Symbol.for(string)
475475
} else if (specialTypeRE.test(val)) {
476476
const [, type, string,, details] = specialTypeRE.exec(val)
477-
const result = new window[type](string)
477+
const result = new target[type](string)
478478
if (type === 'Error' && details) {
479479
result.stack = details
480480
}

packages/shell-electron/index.js

+12-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
require('./build/hook.js')
1+
const isBrowser = typeof window !== 'undefined'
2+
3+
if (isBrowser) {
4+
require('./build/hook.js')
5+
} else {
6+
require('./build-node/hook.js')
7+
}
28

39
const target = typeof window !== 'undefined'
410
? window
@@ -14,7 +20,11 @@ module.exports = {
1420
if (showToast) target.__VUE_DEVTOOLS_TOAST__ = showToast
1521
if (app) target.__VUE_ROOT_INSTANCES__ = Array.isArray(app) ? app : [app]
1622

17-
require('./build/backend.js')
23+
if (isBrowser) {
24+
require('./build/backend.js')
25+
} else {
26+
require('./build-node/backend.js')
27+
}
1828
},
1929
init: (Vue) => {
2030
const tools = target.__VUE_DEVTOOLS_GLOBAL_HOOK__

packages/shell-electron/package.json

+9-5
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,11 @@
1414
"types": "types/index.d.ts",
1515
"scripts": {
1616
"start": "node bin.js",
17-
"dev": "webpack --watch",
18-
"build": "rm -rf ./build && cross-env NODE_ENV=production webpack",
19-
"prepublishOnly": "npm run build"
17+
"dev:client": "webpack --watch",
18+
"dev:node": "webpack --watch --config webpack.node.config.js",
19+
"build": "npm run build:client && npm run build:node",
20+
"build:client": "rm -rf ./build && cross-env NODE_ENV=production webpack",
21+
"build:node": "rm -rf ./build-node && cross-env NODE_ENV=production webpack --config webpack.node.config.js"
2022
},
2123
"author": "",
2224
"license": "MIT",
@@ -28,7 +30,9 @@
2830
"electron": "^12.0.6",
2931
"express": "^4.17.1",
3032
"ip": "^1.1.5",
31-
"socket.io": "^2.0.4"
33+
"socket.io": "^4.4.0",
34+
"socket.io-client": "^4.4.1",
35+
"utf-8-validate": "^5.0.9"
3236
},
3337
"devDependencies": {
3438
"@vue-devtools/app-backend-core": "^0.0.0",
@@ -38,4 +42,4 @@
3842
"webpack": "^5.35.1",
3943
"webpack-cli": "^4.6.0"
4044
}
41-
}
45+
}

packages/shell-electron/server.js

+7-4
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1-
const app = require('express')()
2-
const http = require('http').Server(app)
3-
const io = require('socket.io')(http)
41
const path = require('path')
52
const fs = require('fs')
3+
const app = require('express')()
4+
const { createServer } = require('http')
5+
const { Server } = require('socket.io')
66

77
const port = process.env.PORT || 8098
88

9+
const httpServer = createServer(app)
10+
const io = new Server(httpServer, {})
11+
912
app.get('/', function (req, res) {
1013
const hookContent = fs.readFileSync(path.join(__dirname, '/build/hook.js'), 'utf8')
1114
const backendContent = fs.readFileSync(path.join(__dirname, '/build/backend.js'), 'utf8')
@@ -32,7 +35,7 @@ io.on('connection', function (socket) {
3235
})
3336
})
3437

35-
http.listen(port, '0.0.0.0', () => {
38+
httpServer.listen(port, '0.0.0.0', () => {
3639
// eslint-disable-next-line no-console
3740
console.log('listening on 0.0.0.0:' + port)
3841
})

0 commit comments

Comments
 (0)