diff --git a/packages/@vue/cli-shared-utils/lib/env.js b/packages/@vue/cli-shared-utils/lib/env.js index d45616dee3..3c9674ea34 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 DELIMITER = '\f' // env detection exports.hasYarn = () => { @@ -216,3 +218,44 @@ exports.getInstalledBrowsers = () => { return browsers } + +exports.getIpcPath = (id) => { + id = '/tmp/app.' + id + if (exports.isWindows) { + id = id.replace(/^\//, '') + id = id.replace(/\//g, '-') + id = `\\\\.\\pipe\\${id}` + } + return id +} + +exports.encodeIpcData = (type, data) => { + if (!data && data !== false && data !== 0) { + data = {} + } + if (data._maxListeners) { + data = {} + } + const message = JSON.stringify({ type, data }) + return Buffer.from(message + DELIMITER) +} + +exports.decodeIpcData = (data) => { + if (data.slice(-1) !== DELIMITER || data.indexOf(DELIMITER) === -1) { + return + } + const messages = [] + 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}` + }) + } + } + return messages +} diff --git a/packages/@vue/cli-shared-utils/lib/ipc.js b/packages/@vue/cli-shared-utils/lib/ipc.js index 1f7f88d331..7a960e7546 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 { getIpcPath, encodeIpcData, decodeIpcData } = require('./env') +const net = require('net') const DEFAULT_ID = process.env.VUE_CLI_IPC || 'vue-cli' const DEFAULT_IDLE_TIMEOUT = 3000 @@ -15,12 +16,14 @@ 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.socket = null this.connected = false this.connecting = false + this.explicitlyDisconnected = false this.disconnecting = false this.queue = null this.options = options @@ -40,7 +43,7 @@ exports.IpcMessenger = class IpcMessenger { } checkConnection () { - if (!ipc.of[this.id]) { + if (!this.socket) { this.connected = false } } @@ -55,7 +58,8 @@ exports.IpcMessenger = class IpcMessenger { } } - ipc.of[this.id].emit(type, data) + const message = encodeIpcData(type, data) + this.socket.write(message) clearTimeout(this.idleTimer) if (this.options.disconnectOnIdle) { @@ -76,14 +80,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 () { @@ -91,19 +88,13 @@ exports.IpcMessenger = class IpcMessenger { if (!this.connected || this.disconnecting) return this.disconnecting = true this.connecting = false + this.explicitlyDisconnected = true - 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 +109,75 @@ exports.IpcMessenger = class IpcMessenger { _reset () { this.queue = [] this.connected = false + this.socket = null } _disconnect () { this.connected = false this.disconnecting = false - ipc.disconnect(this.id) + if (this.socket) { + 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 - } else { - return + _onMessage (message) { + let { type, data } = message + if (type === 'ack') { + if (data.ok) { + clearTimeout(this.ipcTimer) + this._disconnect() + } + } else if (type === 'message') { + this.listeners.forEach((fn) => { + if (this.options.namespaceOnProject && data._projectId) { + if (data._projectId === PROJECT_ID) { + data = data._data + } else { + return + } } + fn(data) + }) + } + } + + _connectTo () { + const ipcPath = getIpcPath(this.id) + const socket = net.createConnection({ path: ipcPath }) + 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', (data) => { + const messages = decodeIpcData(data) + messages.forEach(message => { + this._onMessage(message) + }) + }) + + socket.on('close', () => { + if (this.explicitlyDisconnected) { + this._disconnect() + return } - fn(data) + setTimeout(() => { + this._connectTo() + }, this.retry) }) + + socket.on('error', (err) => { + this._onMessage({ + type: 'error', + data: err + }) + }) + + this.socket = socket } } diff --git a/packages/@vue/cli-shared-utils/package.json b/packages/@vue/cli-shared-utils/package.json index 02c56a9794..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.3", "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..ce41ad2ad1 100644 --- a/packages/@vue/cli-ui/apollo-server/util/ipc.js +++ b/packages/@vue/cli-ui/apollo-server/util/ipc.js @@ -1,35 +1,74 @@ -const ipc = require('@achrinza/node-ipc') +const net = require('net') +const fs = require('fs') + // Utils const { log, dumpObject } = require('../util/logger') +const { getIpcPath, encodeIpcData, decodeIpcData } = 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 = [] +let ipcSocket = null + +function start () { + const ipcPath = getIpcPath(id) + + fs.unlink(ipcPath, () => { + const server = net.createServer((socket) => { + ipcSocket = socket + if (socket.setEncoding) { + socket.setEncoding('utf-8') + } + + socket.on('data', (data) => { + const messages = decodeIpcData(data) + messages.forEach(message => { + _onMessage(message, socket) + }) + }) + + socket.on('close', () => { + if (socket && socket.destroy) { + socket.destroy() + } + ipcSocket = null + }) + + socket.on('error', (error) => { + _onMessage({ + type: 'error', + data: error + }, socket) + }) + }) -ipc.serve(() => { - ipc.server.on('message', (data, socket) => { + server.listen({ + path: ipcPath + }) + }) +} + +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 => { - ipc.server.emit(socket, 'message', data) + socket.write(encodeIpcData('message', data)) } }) } - }) - - ipc.server.on('ack', (data, socket) => { - log('IPC ack', dumpObject(data)) - if (data.done) { - ipc.server.emit(socket, 'ack', { ok: true }) - } - }) -}) + } +} -ipc.server.start() +start() function on (cb) { listeners.push(cb) @@ -43,7 +82,10 @@ function off (cb) { function send (data) { log('IPC send', dumpObject(data)) - ipc.server.broadcast('message', data) + const message = encodeIpcData('message', data) + if (ipcSocket) { + ipcSocket.write(message) + } } module.exports = { diff --git a/packages/@vue/cli-ui/package.json b/packages/@vue/cli-ui/package.json index 77218d96d2..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.3", "@akryum/winattr": "^3.0.0", "@vue/cli-shared-utils": "^5.0.4", "apollo-server-express": "^2.21.0",