diff --git a/packages/@vue/cli-service/lib/commands/serve.js b/packages/@vue/cli-service/lib/commands/serve.js index 571ea7fe50..714458241d 100644 --- a/packages/@vue/cli-service/lib/commands/serve.js +++ b/packages/@vue/cli-service/lib/commands/serve.js @@ -4,7 +4,7 @@ const { hasProjectYarn, hasProjectPnpm, openBrowser, - IpcMessenger + NewIpcMessenger } = require('@vue/cli-shared-utils') const defaults = { @@ -301,7 +301,7 @@ module.exports = (api, options) => { // Send final app URL if (args.dashboard) { - const ipc = new IpcMessenger() + const ipc = new NewIpcMessenger() ipc.send({ vueServe: { url: localUrlForBrowser diff --git a/packages/@vue/cli-service/lib/webpack/DashboardPlugin.js b/packages/@vue/cli-service/lib/webpack/DashboardPlugin.js index 21e318ae2b..8116ee6afb 100644 --- a/packages/@vue/cli-service/lib/webpack/DashboardPlugin.js +++ b/packages/@vue/cli-service/lib/webpack/DashboardPlugin.js @@ -7,14 +7,14 @@ const path = require('path') const fs = require('fs-extra') const webpack = require('webpack') -const { IpcMessenger } = require('@vue/cli-shared-utils') +const { NewIpcMessenger } = require('@vue/cli-shared-utils') const { analyzeBundle } = require('./analyzeBundle') const ID = 'vue-cli-dashboard-plugin' const ONE_SECOND = 1000 const FILENAME_QUERY_REGEXP = /\?.*$/ -const ipc = new IpcMessenger() +const ipc = new NewIpcMessenger() function getTimeMessage (timer) { let time = Date.now() - timer diff --git a/packages/@vue/cli-shared-utils/index.js b/packages/@vue/cli-shared-utils/index.js index 830a325459..72775e1a1b 100644 --- a/packages/@vue/cli-shared-utils/index.js +++ b/packages/@vue/cli-shared-utils/index.js @@ -2,6 +2,7 @@ 'env', 'exit', 'ipc', + 'NewIpcMessenger', 'logger', 'module', 'object', diff --git a/packages/@vue/cli-shared-utils/lib/NewIpcMessenger.js b/packages/@vue/cli-shared-utils/lib/NewIpcMessenger.js new file mode 100644 index 0000000000..1abad42827 --- /dev/null +++ b/packages/@vue/cli-shared-utils/lib/NewIpcMessenger.js @@ -0,0 +1,113 @@ +const net = require('net') +const path = require('path') + +const DEFAULT_ID = process.env.VUE_CLI_IPC || 'vue-cli' +const DEFAULT_IDLE_TIMEOUT = 3000 +const DEFAULT_OPTIONS = { + networkId: DEFAULT_ID, + autoConnect: true, + disconnectOnIdle: false, + idleTimeout: DEFAULT_IDLE_TIMEOUT, + namespaceOnProject: true +} +const PROJECT_ID = process.env.VUE_CLI_PROJECT_ID + +exports.NewIpcMessenger = class NewIpcMessenger { + constructor (options = {}) { + this.options = Object.assign({}, DEFAULT_OPTIONS, options) + + this.id = this.options.networkId + + // per the node-ipc documentation + // TODO: windows socket path + this.socketPath = path.join('/tmp/', `app.${this.id}`) + + this.connected = false + this.connecting = false + this.disconnecting = false + this.queue = [] + this.listeners = [] + + this.disconnectTimeout = 15000 + this.idleTimer = null + + // Prevent forced process exit + // (or else ipc messages may not be sent before kill) + process.exit = code => { + process.exitCode = code + } + } + + checkConnection () { + // TODO: not sure how to abstract this under the current implementation + } + + send (data, type = 'message') { + if (this.connected) { + if (this.options.namespaceOnProject && PROJECT_ID) { + data = { + _projectId: PROJECT_ID, + _data: data + } + } + + // the packet format is compatible with node-ipc default + this._client.write(JSON.stringify({ type, data }) + '\f', 'utf8') + + clearTimeout(this.idleTimer) + if (this.options.disconnectOnIdle) { + this.idleTimer = setTimeout(() => { + this.disconnect() + }, this.options.idleTimeout) + } + } else { + this.queue.push(data) + if (this.options.autoConnect && !this.connecting) { + this.connect() + } + } + } + + connect () { + if (this.connected || this.connecting) return + + this.connecting = true + this.disconnecting = false + + // TODO: check the socketPath, unlink if existed + // TODO: server side + // net.createServer().listen(this.socketPath) + + // client side + this._client = net.createConnection({ path: this.socketPath }, () => { + this.connected = true + this.connecting = false + this.queue && this.queue.forEach(data => this.send(data)) + this.queue = [] + }) + } + + disconnect () {} + + on (listener) { + this.listeners.push(listener) + } + + off (listener) { + const index = this.listeners.indexOf(listener) + if (index !== -1) this.listeners.splice(index, 1) + } + + _onMessage (data) { + this.listeners.forEach(fn => { + if (this.options.namespaceOnProject && data._projectId) { + if (data._projectId === PROJECT_ID) { + data = data._data + } else { + return + } + } + fn(data) + }) + } +} diff --git a/packages/@vue/cli-shared-utils/lib/ipc.js b/packages/@vue/cli-shared-utils/lib/ipc.js index 96b5d4afd8..b30a4c8f97 100644 --- a/packages/@vue/cli-shared-utils/lib/ipc.js +++ b/packages/@vue/cli-shared-utils/lib/ipc.js @@ -22,7 +22,7 @@ exports.IpcMessenger = class IpcMessenger { this.connected = false this.connecting = false this.disconnecting = false - this.queue = null + this.queue = [] this.options = options this.listeners = [] @@ -80,7 +80,7 @@ exports.IpcMessenger = class IpcMessenger { this.connected = true this.connecting = false this.queue && this.queue.forEach(data => this.send(data)) - this.queue = null + this.queue = [] ipc.of[this.id].on('message', this._onMessage) })