Skip to content

Commit

Permalink
feat(electron): work on node envioronments (#1780)
Browse files Browse the repository at this point in the history
Co-authored-by: Guillaume Chau <guillaume.b.chau@gmail.com>
  • Loading branch information
posva and Akryum authored Mar 14, 2022
1 parent 2e1e907 commit b50e994
Show file tree
Hide file tree
Showing 18 changed files with 273 additions and 206 deletions.
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ module.exports = {
'/packages/*/lib/',
'dist/',
'build/',
'build-node/',
'/legacy',
],
overrides: [
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,4 @@ yarn-error.log

/packages/*/lib
.amo.env.json
build-node
4 changes: 2 additions & 2 deletions packages/app-backend-core/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import {
BackendContext,
DevtoolsBackend,
} from '@vue-devtools/app-backend-api'
import { BridgeEvents, SharedData } from '@vue-devtools/shared-utils'
import { BridgeEvents, isBrowser, SharedData } from '@vue-devtools/shared-utils'
import { App } from '@vue/devtools-api'
import slug from 'speakingurl'
import { JobQueue } from './util/queue'
Expand Down Expand Up @@ -72,7 +72,7 @@ async function createAppRecord (options: AppRecordOptions, backend: DevtoolsBack
instanceMap: new Map(),
rootInstance,
perfGroupIds: new Map(),
iframe: document !== el.ownerDocument ? el.ownerDocument?.location?.pathname : null,
iframe: isBrowser && document !== el.ownerDocument ? el.ownerDocument?.location?.pathname : null,
meta: options.meta ?? {},
}

Expand Down
5 changes: 4 additions & 1 deletion packages/app-backend-core/src/hook.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// this script is injected into every page.
import { isBrowser, target } from '@vue-devtools/shared-utils'

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

let iframeChecks = 0
function injectToIframes () {
if (!isBrowser) return

const iframes = document.querySelectorAll<HTMLIFrameElement>('iframe:not([data-vue-devtools-ignore])')
for (const iframe of iframes) {
injectIframeHook(iframe)
Expand Down Expand Up @@ -549,7 +552,7 @@ export function installHook (target, isIframe = false) {
}

// DOM objects
if (object instanceof HTMLElement) {
if (typeof HTMLElement !== 'undefined' && object instanceof HTMLElement) {
return object.cloneNode(false)
}

Expand Down
12 changes: 9 additions & 3 deletions packages/app-backend-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import {
target,
getPluginSettings,
SharedData,
isBrowser,
raf,
} from '@vue-devtools/shared-utils'
import debounce from 'lodash/debounce'
import throttle from 'lodash/throttle'
Expand Down Expand Up @@ -54,6 +56,8 @@ export async function initBackend (bridge: Bridge) {
persist: false,
})

SharedData.isBrowser = isBrowser

initOnPageConfig()

if (!connected) {
Expand Down Expand Up @@ -180,7 +184,7 @@ async function connect () {
for (let i = 0; i < parentInstances.length; i++) {
const parentId = await getComponentId(app, parentUid, parentInstances[i], ctx)
if (i < 2 && isSubscribed(BridgeSubscriptions.COMPONENT_TREE, sub => sub.payload.instanceId === parentId)) {
requestAnimationFrame(() => {
raf(() => {
sendComponentTreeData(appRecord, parentId, appRecord.componentFilter, null, ctx)
})
}
Expand Down Expand Up @@ -221,7 +225,7 @@ async function connect () {
if (parentInstances.length) {
const parentId = await getComponentId(app, parentUid, parentInstances[0], ctx)
if (isSubscribed(BridgeSubscriptions.COMPONENT_TREE, sub => sub.payload.instanceId === parentId)) {
requestAnimationFrame(async () => {
raf(async () => {
try {
sendComponentTreeData(await getAppRecord(app, ctx), parentId, appRecord.componentFilter, null, ctx)
} catch (e) {
Expand Down Expand Up @@ -449,13 +453,14 @@ function connectBridge () {
const [el] = await ctx.currentAppRecord.backend.api.getComponentRootElements(instance)
if (el) {
// @ts-ignore
window.__VUE_DEVTOOLS_INSPECT_TARGET__ = el
target.__VUE_DEVTOOLS_INSPECT_TARGET__ = el
ctx.bridge.send(BridgeEvents.TO_FRONT_COMPONENT_INSPECT_DOM, null)
}
}
})

ctx.bridge.on(BridgeEvents.TO_BACK_COMPONENT_SCROLL_TO, async ({ instanceId }) => {
if (!isBrowser) return
const instance = getComponentInstance(ctx.currentAppRecord, instanceId, ctx)
if (instance) {
const [el] = await ctx.currentAppRecord.backend.api.getComponentRootElements(instance)
Expand Down Expand Up @@ -494,6 +499,7 @@ function connectBridge () {
})

ctx.bridge.on(BridgeEvents.TO_BACK_COMPONENT_RENDER_CODE, async ({ instanceId }) => {
if (!isBrowser) return
const instance = getComponentInstance(ctx.currentAppRecord, instanceId, ctx)
if (instance) {
const { code } = await ctx.currentAppRecord.backend.api.getComponentRenderCode(instance)
Expand Down
4 changes: 2 additions & 2 deletions packages/app-backend-core/src/perf.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { BackendContext, DevtoolsBackend } from '@vue-devtools/app-backend-api'
import { App, ComponentInstance } from '@vue/devtools-api'
import { BridgeSubscriptions, SharedData } from '@vue-devtools/shared-utils'
import { BridgeSubscriptions, raf, SharedData } from '@vue-devtools/shared-utils'
import { addTimelineEvent } from './timeline'
import { getAppRecord } from './app'
import { getComponentId, sendComponentTreeData } from './component'
Expand Down Expand Up @@ -144,7 +144,7 @@ export async function performanceMarkEnd (
// Update component tree
const id = await getComponentId(app, uid, instance, ctx)
if (isSubscribed(BridgeSubscriptions.COMPONENT_TREE, sub => sub.payload.instanceId === id)) {
requestAnimationFrame(() => {
raf(() => {
sendComponentTreeData(appRecord, id, ctx.currentAppRecord.componentFilter, null, ctx)
})
}
Expand Down
82 changes: 42 additions & 40 deletions packages/app-backend-core/src/timeline.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { BackendContext, AppRecord } from '@vue-devtools/app-backend-api'
import { BridgeEvents, HookEvents, stringify, SharedData } from '@vue-devtools/shared-utils'
import { BridgeEvents, HookEvents, stringify, SharedData, isBrowser } from '@vue-devtools/shared-utils'
import { App, ID, TimelineEventOptions, WithId, now, isPerformanceSupported } from '@vue/devtools-api'
import { hook } from './global-hook'
import { getAppRecord, getAppRecordId } from './app'
Expand All @@ -21,50 +21,52 @@ export function addBuiltinLayers (appRecord: AppRecord, ctx: BackendContext) {
}

function setupBuiltinLayers (ctx: BackendContext) {
['mousedown', 'mouseup', 'click', 'dblclick'].forEach(eventType => {
// @ts-ignore
window.addEventListener(eventType, async (event: MouseEvent) => {
await addTimelineEvent({
layerId: 'mouse',
event: {
time: now(),
data: {
type: eventType,
x: event.clientX,
y: event.clientY,
if (isBrowser) {
['mousedown', 'mouseup', 'click', 'dblclick'].forEach(eventType => {
// @ts-ignore
window.addEventListener(eventType, async (event: MouseEvent) => {
await addTimelineEvent({
layerId: 'mouse',
event: {
time: now(),
data: {
type: eventType,
x: event.clientX,
y: event.clientY,
},
title: eventType,
},
title: eventType,
},
}, null, ctx)
}, {
capture: true,
passive: true,
}, null, ctx)
}, {
capture: true,
passive: true,
})
})
})

;['keyup', 'keydown', 'keypress'].forEach(eventType => {
// @ts-ignore
window.addEventListener(eventType, async (event: KeyboardEvent) => {
await addTimelineEvent({
layerId: 'keyboard',
event: {
time: now(),
data: {
type: eventType,
key: event.key,
ctrlKey: event.ctrlKey,
shiftKey: event.shiftKey,
altKey: event.altKey,
metaKey: event.metaKey,
;['keyup', 'keydown', 'keypress'].forEach(eventType => {
// @ts-ignore
window.addEventListener(eventType, async (event: KeyboardEvent) => {
await addTimelineEvent({
layerId: 'keyboard',
event: {
time: now(),
data: {
type: eventType,
key: event.key,
ctrlKey: event.ctrlKey,
shiftKey: event.shiftKey,
altKey: event.altKey,
metaKey: event.metaKey,
},
title: event.key,
},
title: event.key,
},
}, null, ctx)
}, {
capture: true,
passive: true,
}, null, ctx)
}, {
capture: true,
passive: true,
})
})
})
}

hook.on(HookEvents.COMPONENT_EMIT, async (app, instance, event, params) => {
try {
Expand Down
2 changes: 1 addition & 1 deletion packages/app-frontend/src/util/keyboard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ type KeyboardHandler = (event: KeyboardEvent) => boolean | void | Promise<boolea

function handleKeyboard (type: 'keyup' | 'keydown', cb: KeyboardHandler) {
function handler (event: KeyboardEvent) {
if (event.target instanceof HTMLElement && (
if (typeof HTMLElement !== 'undefined' && event.target instanceof HTMLElement && (
event.target.tagName === 'INPUT' ||
event.target.tagName === 'TEXTAREA'
)) {
Expand Down
3 changes: 2 additions & 1 deletion packages/shared-utils/src/bridge.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { EventEmitter } from 'events'
import { raf } from './raf'

const BATCH_DURATION = 100

Expand Down Expand Up @@ -118,6 +119,6 @@ export class Bridge extends EventEmitter {
}
}
this._sending = false
requestAnimationFrame(() => this._nextSend())
raf(() => this._nextSend())
}
}
1 change: 1 addition & 0 deletions packages/shared-utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ export * from './shell'
export * from './storage'
export * from './transfer'
export * from './util'
export * from './raf'
21 changes: 21 additions & 0 deletions packages/shared-utils/src/raf.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@

let pendingCallbacks: Array<(time: number) => void> = []

/**
* requestAnimationFrame that also works on non-browser environments like Node.
*/
export const raf = typeof requestAnimationFrame === 'function'
? requestAnimationFrame
: (fn: (time: number) => void) => {
if (!pendingCallbacks.length) {
setImmediate(() => {
const now = performance.now()
const cbs = pendingCallbacks
// in case cbs add new callbacks
pendingCallbacks = []
cbs.forEach(cb => cb(now))
})
}

pendingCallbacks.push(fn)
}
3 changes: 2 additions & 1 deletion packages/shared-utils/src/shared-data.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { setStorage, getStorage } from './storage'
import { Bridge } from './bridge'
import { isMac } from './env'
import { isBrowser, isMac } from './env'

// Initial state
const internalSharedData = {
Expand Down Expand Up @@ -31,6 +31,7 @@ const internalSharedData = {
trackUpdates: true,
flashUpdates: false,
debugInfo: false,
isBrowser,
}

type TSharedData = typeof internalSharedData
Expand Down
4 changes: 2 additions & 2 deletions packages/shared-utils/src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ function replacer (key) {
return encodeCache.cache(val, () => getCustomComponentDefinitionDetails(val))
} else if (val.constructor && val.constructor.name === 'VNode') {
return `[native VNode <${val.tag}>]`
} else if (val instanceof HTMLElement) {
} else if (typeof HTMLElement !== 'undefined' && val instanceof HTMLElement) {
return encodeCache.cache(val, () => getCustomHTMLElementDetails(val))
}
const customDetails = getCustomObjectDetails(val, proto)
Expand Down Expand Up @@ -474,7 +474,7 @@ export function revive (val) {
return Symbol.for(string)
} else if (specialTypeRE.test(val)) {
const [, type, string,, details] = specialTypeRE.exec(val)
const result = new window[type](string)
const result = new target[type](string)
if (type === 'Error' && details) {
result.stack = details
}
Expand Down
14 changes: 12 additions & 2 deletions packages/shell-electron/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
require('./build/hook.js')
const isBrowser = typeof window !== 'undefined'

if (isBrowser) {
require('./build/hook.js')
} else {
require('./build-node/hook.js')
}

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

require('./build/backend.js')
if (isBrowser) {
require('./build/backend.js')
} else {
require('./build-node/backend.js')
}
},
init: (Vue) => {
const tools = target.__VUE_DEVTOOLS_GLOBAL_HOOK__
Expand Down
14 changes: 9 additions & 5 deletions packages/shell-electron/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@
"types": "types/index.d.ts",
"scripts": {
"start": "node bin.js",
"dev": "webpack --watch",
"build": "rm -rf ./build && cross-env NODE_ENV=production webpack",
"prepublishOnly": "npm run build"
"dev:client": "webpack --watch",
"dev:node": "webpack --watch --config webpack.node.config.js",
"build": "npm run build:client && npm run build:node",
"build:client": "rm -rf ./build && cross-env NODE_ENV=production webpack",
"build:node": "rm -rf ./build-node && cross-env NODE_ENV=production webpack --config webpack.node.config.js"
},
"author": "",
"license": "MIT",
Expand All @@ -28,7 +30,9 @@
"electron": "^12.0.6",
"express": "^4.17.1",
"ip": "^1.1.5",
"socket.io": "^2.0.4"
"socket.io": "^4.4.0",
"socket.io-client": "^4.4.1",
"utf-8-validate": "^5.0.9"
},
"devDependencies": {
"@vue-devtools/app-backend-core": "^0.0.0",
Expand All @@ -38,4 +42,4 @@
"webpack": "^5.35.1",
"webpack-cli": "^4.6.0"
}
}
}
11 changes: 7 additions & 4 deletions packages/shell-electron/server.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
const app = require('express')()
const http = require('http').Server(app)
const io = require('socket.io')(http)
const path = require('path')
const fs = require('fs')
const app = require('express')()
const { createServer } = require('http')
const { Server } = require('socket.io')

const port = process.env.PORT || 8098

const httpServer = createServer(app)
const io = new Server(httpServer, {})

app.get('/', function (req, res) {
const hookContent = fs.readFileSync(path.join(__dirname, '/build/hook.js'), 'utf8')
const backendContent = fs.readFileSync(path.join(__dirname, '/build/backend.js'), 'utf8')
Expand All @@ -32,7 +35,7 @@ io.on('connection', function (socket) {
})
})

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

0 comments on commit b50e994

Please sign in to comment.