From 88ee8dea4ea5f93cee43594acdc497d0bfa9bc1e Mon Sep 17 00:00:00 2001 From: ygj6 Date: Fri, 25 Mar 2022 11:56:00 +0800 Subject: [PATCH 1/3] feat: Refactor the ipc module. --- packages/@vue/cli-shared-utils/lib/env.js | 68 +++++++++++ packages/@vue/cli-shared-utils/lib/ipc.js | 112 +++++++++++++----- packages/@vue/cli-shared-utils/package.json | 1 - .../@vue/cli-ui/apollo-server/util/ipc.js | 100 ++++++++++++---- packages/@vue/cli-ui/package.json | 1 - 5 files changed, 229 insertions(+), 53 deletions(-) diff --git a/packages/@vue/cli-shared-utils/lib/env.js b/packages/@vue/cli-shared-utils/lib/env.js index d45616dee3..4a64a25148 100644 --- a/packages/@vue/cli-shared-utils/lib/env.js +++ b/packages/@vue/cli-shared-utils/lib/env.js @@ -3,6 +3,7 @@ const fs = require('fs') const path = require('path') const LRU = require('lru-cache') const semver = require('semver') +const { Buffer } = require('buffer') let _hasYarn const _yarnProjects = new LRU({ @@ -14,6 +15,7 @@ const _gitProjects = new LRU({ max: 10, maxAge: 1000 }) +const CRLF = '\r\n' // env detection exports.hasYarn = () => { @@ -216,3 +218,69 @@ exports.getInstalledBrowsers = () => { return browsers } + +exports.getPipePath = (id) => { + if (exports.isWindows && !id.startsWith('\\\\.\\pipe\\')) { + id = id.replace(/^\//, '') + id = id.replace(/\//g, '-') + id = `\\\\.\\pipe\\${id}` + } + return id +} + +exports.encodeIpcData = (type, data) => { + const jsonstr = JSON.stringify({ + data, + type + }) + const massage = `Content-Length: ${Buffer.byteLength(jsonstr)}${CRLF + CRLF}${jsonstr}` + return Buffer.from(massage) +} + +exports.parseIpcData = (data, reserveData) => { + let { contentLength, rawData } = reserveData + rawData += data + const messages = [] + while (true) { + if (contentLength >= 0) { + if (rawData.length >= contentLength) { + const message = rawData.slice(0, contentLength) + rawData = rawData.slice(contentLength) + contentLength = -1 + if (message.length > 0) { + let msg + try { + msg = JSON.parse(message) + } catch (error) { + msg = { + type: 'error', + data: `Error handling data: ${error}` + } + } + messages.push(msg) + } + continue + } + } else { + const idx = rawData.indexOf(CRLF + CRLF) + if (idx !== -1) { + const header = rawData.slice(0, idx) + const lines = header.split(CRLF) + for (let i = 0; i < lines.length; i++) { + const pair = lines[i].split(/: +/) + if (pair[0] === 'Content-Length') { + contentLength = +pair[1] + } + } + rawData = rawData.slice(idx + (CRLF + CRLF).length) + continue + } + } + break + } + + reserveData.contentLength = contentLength + reserveData.rawData = rawData + + return messages +} diff --git a/packages/@vue/cli-shared-utils/lib/ipc.js b/packages/@vue/cli-shared-utils/lib/ipc.js index 1f7f88d331..7ccea35640 100644 --- a/packages/@vue/cli-shared-utils/lib/ipc.js +++ b/packages/@vue/cli-shared-utils/lib/ipc.js @@ -1,4 +1,5 @@ -const ipc = require('@achrinza/node-ipc') +const { getPipePath, encodeIpcData, parseIpcData } = require('./env') +const net = require('net') const DEFAULT_ID = process.env.VUE_CLI_IPC || 'vue-cli' const DEFAULT_IDLE_TIMEOUT = 3000 @@ -15,12 +16,18 @@ const PROJECT_ID = process.env.VUE_CLI_PROJECT_ID exports.IpcMessenger = class IpcMessenger { constructor (options = {}) { options = Object.assign({}, DEFAULT_OPTIONS, options) - ipc.config.id = this.id = options.networkId - ipc.config.retry = 1500 - ipc.config.silent = true + this.id = options.networkId + this.retry = 1500 + this.ipcTimer = null + this.reserveData = { + contentLength: -1, + rawData: '' + } + this.socket = null this.connected = false this.connecting = false + this.disconnected = false this.disconnecting = false this.queue = null this.options = options @@ -40,7 +47,7 @@ exports.IpcMessenger = class IpcMessenger { } checkConnection () { - if (!ipc.of[this.id]) { + if (!this.socket) { this.connected = false } } @@ -55,7 +62,8 @@ exports.IpcMessenger = class IpcMessenger { } } - ipc.of[this.id].emit(type, data) + const massages = encodeIpcData(type, data) + this.socket.write(massages) clearTimeout(this.idleTimer) if (this.options.disconnectOnIdle) { @@ -76,14 +84,7 @@ exports.IpcMessenger = class IpcMessenger { if (this.connected || this.connecting) return this.connecting = true this.disconnecting = false - ipc.connectTo(this.id, () => { - this.connected = true - this.connecting = false - this.queue && this.queue.forEach(data => this.send(data)) - this.queue = null - - ipc.of[this.id].on('message', this._onMessage) - }) + this._connectTo() } disconnect () { @@ -92,18 +93,11 @@ exports.IpcMessenger = class IpcMessenger { this.disconnecting = true this.connecting = false - const ipcTimer = setTimeout(() => { + this.ipcTimer = setTimeout(() => { this._disconnect() }, this.disconnectTimeout) this.send({ done: true }, 'ack') - - ipc.of[this.id].on('ack', data => { - if (data.ok) { - clearTimeout(ipcTimer) - this._disconnect() - } - }) } on (listener) { @@ -118,25 +112,85 @@ exports.IpcMessenger = class IpcMessenger { _reset () { this.queue = [] this.connected = false + this.socket = null } _disconnect () { + if (!this.socket) { + return + } this.connected = false this.disconnecting = false - ipc.disconnect(this.id) + this.disconnected = true + this.socket.destroy() this._reset() } - _onMessage (data) { - this.listeners.forEach(fn => { - if (this.options.namespaceOnProject && data._projectId) { - if (data._projectId === PROJECT_ID) { - data = data._data + _onMessage (massage) { + let { type, data } = massage + if (type === 'ack') { + if (data.ok) { + clearTimeout(this.ipcTimer) + this._disconnect() + } + } else { + this.listeners.forEach((resolve, reject) => { + if (this.options.namespaceOnProject && data._projectId) { + if (data._projectId === PROJECT_ID) { + data = data._data + } else { + return + } + } + if (type === 'error') { + reject(data) } else { + resolve(data) + } + }) + } + } + + _connectTo () { + const pipPath = getPipePath(this.id) + const socket = net.createConnection({ path: pipPath }) + socket.setEncoding('utf-8') + + socket.on('connect', () => { + this.connected = true + this.connecting = false + this.queue && this.queue.forEach(data => this.send(data)) + this.queue = null + }) + + socket.on('data', (massages) => { + const queue = parseIpcData(massages, this.reserveData) + queue.forEach(massage => { + this._onMessage(massage) + }) + }) + + socket.on('close', () => { + if (this.disconnected) { + return + } + setTimeout(() => { + if (this.disconnected) { + this._disconnect() return } + this._connectTo() + }, this.retry) + }) + + socket.on('error', (error) => { + const massage = { + type: 'error', + data: error } - fn(data) + this._onMessage(massage) }) + + this.socket = socket } } diff --git a/packages/@vue/cli-shared-utils/package.json b/packages/@vue/cli-shared-utils/package.json index ec14e6417e..6020956602 100644 --- a/packages/@vue/cli-shared-utils/package.json +++ b/packages/@vue/cli-shared-utils/package.json @@ -20,7 +20,6 @@ }, "homepage": "https://github.com/vuejs/vue-cli/tree/dev/packages/@vue/cli-shared-utils#readme", "dependencies": { - "@achrinza/node-ipc": "9.2.2", "chalk": "^4.1.2", "execa": "^1.0.0", "joi": "^17.4.0", diff --git a/packages/@vue/cli-ui/apollo-server/util/ipc.js b/packages/@vue/cli-ui/apollo-server/util/ipc.js index b7eb294cfd..b1b1be2320 100644 --- a/packages/@vue/cli-ui/apollo-server/util/ipc.js +++ b/packages/@vue/cli-ui/apollo-server/util/ipc.js @@ -1,36 +1,62 @@ -const ipc = require('@achrinza/node-ipc') +const net = require('net') +const fs = require('fs') + // Utils const { log, dumpObject } = require('../util/logger') +const { getPipePath, encodeIpcData, parseIpcData } = require('@vue/cli-shared-utils') -ipc.config.id = process.env.VUE_CLI_IPC || 'vue-cli' -ipc.config.retry = 1500 -ipc.config.silent = true +const id = process.env.VUE_CLI_IPC || 'vue-cli' const listeners = [] -ipc.serve(() => { - ipc.server.on('message', (data, socket) => { - log('IPC message', dumpObject(data)) - for (const listener of listeners) { - listener({ - data, - emit: data => { - ipc.server.emit(socket, 'message', data) - } - }) +const pipePath = getPipePath(id) + +let curSocket = null + +let reserveData = { + contentLength: -1, + rawData: '' +} + +fs.unlink(pipePath, () => { + const server = net.createServer((socket) => { + curSocket = socket + if (socket.setEncoding) { + socket.setEncoding('utf-8') } + + socket.on('data', (massages) => { + const queue = parseIpcData(massages, reserveData) + queue.forEach(massage => { + _onMessage(massage) + }) + }) + + socket.on('close', () => { + if (curSocket && curSocket.destroy) { + curSocket.destroy() + } + reserveData = { + contentLength: -1, + rawData: '' + } + curSocket = null + }) + + socket.on('error', (error) => { + const massage = { + type: 'error', + data: error + } + _onMessage(massage) + }) }) - ipc.server.on('ack', (data, socket) => { - log('IPC ack', dumpObject(data)) - if (data.done) { - ipc.server.emit(socket, 'ack', { ok: true }) - } + server.listen({ + path: pipePath }) }) -ipc.server.start() - function on (cb) { listeners.push(cb) return () => off(cb) @@ -43,7 +69,37 @@ function off (cb) { function send (data) { log('IPC send', dumpObject(data)) - ipc.server.broadcast('message', data) + const massages = encodeIpcData('message', data) + curSocket.write(massages) +} + +function _onMessage (massage) { + const { type, data } = massage + if (type === 'ack') { + log('IPC ack', dumpObject(data)) + if (data.done) { + curSocket.write(encodeIpcData('ack', { ok: true })) + } + } else { + log('IPC message', dumpObject(data)) + listeners.forEach((resolve, reject) => { + if (type === 'error') { + reject({ + data, + emit: data => { + curSocket.write(encodeIpcData('error', data)) + } + }) + } else { + resolve({ + data, + emit: data => { + curSocket.write(encodeIpcData('message', data)) + } + }) + } + }) + } } module.exports = { diff --git a/packages/@vue/cli-ui/package.json b/packages/@vue/cli-ui/package.json index e44cf6834e..f06825f57d 100644 --- a/packages/@vue/cli-ui/package.json +++ b/packages/@vue/cli-ui/package.json @@ -34,7 +34,6 @@ "graphql-server.js" ], "dependencies": { - "@achrinza/node-ipc": "9.2.2", "@akryum/winattr": "^3.0.0", "@vue/cli-shared-utils": "^5.0.4", "apollo-server-express": "^2.21.0", From 2bc581a50dfbd9ab9e1ed7575c6030aae5dac1ed Mon Sep 17 00:00:00 2001 From: ygj6 Date: Sat, 26 Mar 2022 15:27:01 +0800 Subject: [PATCH 2/3] fix: Using absolute paths on Unix. --- packages/@vue/cli-shared-utils/lib/env.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/@vue/cli-shared-utils/lib/env.js b/packages/@vue/cli-shared-utils/lib/env.js index 4a64a25148..62e614e4fb 100644 --- a/packages/@vue/cli-shared-utils/lib/env.js +++ b/packages/@vue/cli-shared-utils/lib/env.js @@ -224,6 +224,8 @@ exports.getPipePath = (id) => { id = id.replace(/^\//, '') id = id.replace(/\//g, '-') id = `\\\\.\\pipe\\${id}` + } else { + id = `/tmp/app.${id}` } return id } From af5363fa2528b30775ff1b5548d0dad5f0cb3c01 Mon Sep 17 00:00:00 2001 From: ygj6 Date: Sat, 9 Apr 2022 12:06:52 +0800 Subject: [PATCH 3/3] fix: compatile with older version of cli-service --- packages/@vue/cli-shared-utils/lib/env.js | 79 ++++-------- packages/@vue/cli-shared-utils/lib/ipc.js | 63 ++++----- .../@vue/cli-ui/apollo-server/util/ipc.js | 122 ++++++++---------- 3 files changed, 105 insertions(+), 159 deletions(-) diff --git a/packages/@vue/cli-shared-utils/lib/env.js b/packages/@vue/cli-shared-utils/lib/env.js index 62e614e4fb..3c9674ea34 100644 --- a/packages/@vue/cli-shared-utils/lib/env.js +++ b/packages/@vue/cli-shared-utils/lib/env.js @@ -15,7 +15,7 @@ const _gitProjects = new LRU({ max: 10, maxAge: 1000 }) -const CRLF = '\r\n' +const DELIMITER = '\f' // env detection exports.hasYarn = () => { @@ -219,70 +219,43 @@ exports.getInstalledBrowsers = () => { return browsers } -exports.getPipePath = (id) => { - if (exports.isWindows && !id.startsWith('\\\\.\\pipe\\')) { +exports.getIpcPath = (id) => { + id = '/tmp/app.' + id + if (exports.isWindows) { id = id.replace(/^\//, '') id = id.replace(/\//g, '-') id = `\\\\.\\pipe\\${id}` - } else { - id = `/tmp/app.${id}` } return id } exports.encodeIpcData = (type, data) => { - const jsonstr = JSON.stringify({ - data, - type - }) - const massage = `Content-Length: ${Buffer.byteLength(jsonstr)}${CRLF + CRLF}${jsonstr}` - return Buffer.from(massage) + if (!data && data !== false && data !== 0) { + data = {} + } + if (data._maxListeners) { + data = {} + } + const message = JSON.stringify({ type, data }) + return Buffer.from(message + DELIMITER) } -exports.parseIpcData = (data, reserveData) => { - let { contentLength, rawData } = reserveData - rawData += data +exports.decodeIpcData = (data) => { + if (data.slice(-1) !== DELIMITER || data.indexOf(DELIMITER) === -1) { + return + } const messages = [] - while (true) { - if (contentLength >= 0) { - if (rawData.length >= contentLength) { - const message = rawData.slice(0, contentLength) - rawData = rawData.slice(contentLength) - contentLength = -1 - if (message.length > 0) { - let msg - try { - msg = JSON.parse(message) - } catch (error) { - msg = { - type: 'error', - data: `Error handling data: ${error}` - } - } - messages.push(msg) - } - continue - } - } else { - const idx = rawData.indexOf(CRLF + CRLF) - if (idx !== -1) { - const header = rawData.slice(0, idx) - const lines = header.split(CRLF) - for (let i = 0; i < lines.length; i++) { - const pair = lines[i].split(/: +/) - if (pair[0] === 'Content-Length') { - contentLength = +pair[1] - } - } - rawData = rawData.slice(idx + (CRLF + CRLF).length) - continue - } + const lines = data.split(DELIMITER) + lines.pop() + for (const line of lines) { + try { + messages.push(JSON.parse(line)) + } catch (error) { + messages.push({ + type: 'error', + data: `Error handling data: ${error}` + }) } - break } - - reserveData.contentLength = contentLength - reserveData.rawData = rawData - return messages } diff --git a/packages/@vue/cli-shared-utils/lib/ipc.js b/packages/@vue/cli-shared-utils/lib/ipc.js index 7ccea35640..7a960e7546 100644 --- a/packages/@vue/cli-shared-utils/lib/ipc.js +++ b/packages/@vue/cli-shared-utils/lib/ipc.js @@ -1,4 +1,4 @@ -const { getPipePath, encodeIpcData, parseIpcData } = require('./env') +const { getIpcPath, encodeIpcData, decodeIpcData } = require('./env') const net = require('net') const DEFAULT_ID = process.env.VUE_CLI_IPC || 'vue-cli' @@ -19,15 +19,11 @@ exports.IpcMessenger = class IpcMessenger { this.id = options.networkId this.retry = 1500 this.ipcTimer = null - this.reserveData = { - contentLength: -1, - rawData: '' - } this.socket = null this.connected = false this.connecting = false - this.disconnected = false + this.explicitlyDisconnected = false this.disconnecting = false this.queue = null this.options = options @@ -62,8 +58,8 @@ exports.IpcMessenger = class IpcMessenger { } } - const massages = encodeIpcData(type, data) - this.socket.write(massages) + const message = encodeIpcData(type, data) + this.socket.write(message) clearTimeout(this.idleTimer) if (this.options.disconnectOnIdle) { @@ -92,6 +88,7 @@ exports.IpcMessenger = class IpcMessenger { if (!this.connected || this.disconnecting) return this.disconnecting = true this.connecting = false + this.explicitlyDisconnected = true this.ipcTimer = setTimeout(() => { this._disconnect() @@ -116,25 +113,23 @@ exports.IpcMessenger = class IpcMessenger { } _disconnect () { - if (!this.socket) { - return - } this.connected = false this.disconnecting = false - this.disconnected = true - this.socket.destroy() + if (this.socket) { + this.socket.destroy() + } this._reset() } - _onMessage (massage) { - let { type, data } = massage + _onMessage (message) { + let { type, data } = message if (type === 'ack') { if (data.ok) { clearTimeout(this.ipcTimer) this._disconnect() } - } else { - this.listeners.forEach((resolve, reject) => { + } else if (type === 'message') { + this.listeners.forEach((fn) => { if (this.options.namespaceOnProject && data._projectId) { if (data._projectId === PROJECT_ID) { data = data._data @@ -142,18 +137,14 @@ exports.IpcMessenger = class IpcMessenger { return } } - if (type === 'error') { - reject(data) - } else { - resolve(data) - } + fn(data) }) } } _connectTo () { - const pipPath = getPipePath(this.id) - const socket = net.createConnection({ path: pipPath }) + const ipcPath = getIpcPath(this.id) + const socket = net.createConnection({ path: ipcPath }) socket.setEncoding('utf-8') socket.on('connect', () => { @@ -163,32 +154,28 @@ exports.IpcMessenger = class IpcMessenger { this.queue = null }) - socket.on('data', (massages) => { - const queue = parseIpcData(massages, this.reserveData) - queue.forEach(massage => { - this._onMessage(massage) + socket.on('data', (data) => { + const messages = decodeIpcData(data) + messages.forEach(message => { + this._onMessage(message) }) }) socket.on('close', () => { - if (this.disconnected) { + if (this.explicitlyDisconnected) { + this._disconnect() return } setTimeout(() => { - if (this.disconnected) { - this._disconnect() - return - } this._connectTo() }, this.retry) }) - socket.on('error', (error) => { - const massage = { + socket.on('error', (err) => { + this._onMessage({ type: 'error', - data: error - } - this._onMessage(massage) + data: err + }) }) this.socket = socket diff --git a/packages/@vue/cli-ui/apollo-server/util/ipc.js b/packages/@vue/cli-ui/apollo-server/util/ipc.js index b1b1be2320..fca414b5aa 100644 --- a/packages/@vue/cli-ui/apollo-server/util/ipc.js +++ b/packages/@vue/cli-ui/apollo-server/util/ipc.js @@ -3,59 +3,72 @@ const fs = require('fs') // Utils const { log, dumpObject } = require('../util/logger') -const { getPipePath, encodeIpcData, parseIpcData } = require('@vue/cli-shared-utils') +const { getIpcPath, encodeIpcData, decodeIpcData } = require('@vue/cli-shared-utils') const id = process.env.VUE_CLI_IPC || 'vue-cli' const listeners = [] +let ipcSocket = null -const pipePath = getPipePath(id) +function start () { + const ipcPath = getIpcPath(id) -let curSocket = null - -let reserveData = { - contentLength: -1, - rawData: '' -} + fs.unlink(ipcPath, () => { + const server = net.createServer((socket) => { + ipcSocket = socket + if (socket.setEncoding) { + socket.setEncoding('utf-8') + } -fs.unlink(pipePath, () => { - const server = net.createServer((socket) => { - curSocket = socket - if (socket.setEncoding) { - socket.setEncoding('utf-8') - } + socket.on('data', (data) => { + const messages = decodeIpcData(data) + messages.forEach(message => { + _onMessage(message) + }) + }) - socket.on('data', (massages) => { - const queue = parseIpcData(massages, reserveData) - queue.forEach(massage => { - _onMessage(massage) + socket.on('close', () => { + if (socket && socket.destroy) { + socket.destroy() + } + ipcSocket = null }) - }) - socket.on('close', () => { - if (curSocket && curSocket.destroy) { - curSocket.destroy() - } - reserveData = { - contentLength: -1, - rawData: '' - } - curSocket = null + socket.on('error', (error) => { + _onMessage({ + type: 'error', + data: error + }, socket) + }) }) - socket.on('error', (error) => { - const massage = { - type: 'error', - data: error - } - _onMessage(massage) + server.listen({ + path: ipcPath }) }) +} - server.listen({ - path: pipePath - }) -}) +function _onMessage (massage, socket) { + const { type, data } = massage + if (type === 'ack') { + log('IPC ack', dumpObject(data)) + if (data.done) { + socket.write(encodeIpcData('ack', { ok: true })) + } + } else if (type === 'message') { + log('IPC message', dumpObject(data)) + for (const listener of listeners) { + listener({ + data, + emit: data => { + socket.write(encodeIpcData('message', data)) + } + }) + } + } +} + +start() function on (cb) { listeners.push(cb) @@ -69,36 +82,9 @@ function off (cb) { function send (data) { log('IPC send', dumpObject(data)) - const massages = encodeIpcData('message', data) - curSocket.write(massages) -} - -function _onMessage (massage) { - const { type, data } = massage - if (type === 'ack') { - log('IPC ack', dumpObject(data)) - if (data.done) { - curSocket.write(encodeIpcData('ack', { ok: true })) - } - } else { - log('IPC message', dumpObject(data)) - listeners.forEach((resolve, reject) => { - if (type === 'error') { - reject({ - data, - emit: data => { - curSocket.write(encodeIpcData('error', data)) - } - }) - } else { - resolve({ - data, - emit: data => { - curSocket.write(encodeIpcData('message', data)) - } - }) - } - }) + const message = encodeIpcData('message', data) + if (ipcSocket) { + ipcSocket.write(message) } }