From a08d576851594d46a16d10f08b0a8e42907ec278 Mon Sep 17 00:00:00 2001 From: Feross Aboukhadijeh Date: Thu, 22 Sep 2016 16:33:26 -0700 Subject: [PATCH] startup perf: Reduce require() calls Every require() that we do before the users sees UI reduces startup time. I used the following code (added to index.js) to log every require() call in the main process: ```js var Module = require('module') var required = {} Module.prototype.require = function (orig) { return function (id) { if (!required[id]) { required[id] = true console.log(`${id} (from ${this.filename})`) } return orig.apply(this, arguments) } }(Module.prototype.require) ``` From this I was able to learn that lots of modules were being required that aren't actually used until later. I also sent this related PR to eliminate another few require()s: https://github.com/LinusU/node-application-config/pull/4 This increases startup time by 50ms. We'll probably realize much bigger gains by following this same procedure for the renderer process. --- src/main/index.js | 33 ++++++++------- src/main/ipc.js | 74 ++++++++++++++++++++++++++-------- src/main/menu.js | 37 ++++++++++++----- src/main/windows/main.js | 7 +++- src/renderer/lib/migrations.js | 6 ++- src/renderer/lib/state.js | 9 ++--- 6 files changed, 118 insertions(+), 48 deletions(-) diff --git a/src/main/index.js b/src/main/index.js index a1e5a3dc0b..cae3398f9b 100644 --- a/src/main/index.js +++ b/src/main/index.js @@ -2,23 +2,15 @@ console.time('init') const electron = require('electron') const app = electron.app -const ipcMain = electron.ipcMain const parallel = require('run-parallel') -const announcement = require('./announcement') const config = require('../config') const crashReporter = require('../crash-reporter') -const dialog = require('./dialog') -const dock = require('./dock') const ipc = require('./ipc') const log = require('./log') const menu = require('./menu') -const squirrelWin32 = require('./squirrel-win32') const State = require('../renderer/lib/state') -const tray = require('./tray') -const updater = require('./updater') -const userTasks = require('./user-tasks') const windows = require('./windows') let shouldQuit = false @@ -36,6 +28,7 @@ if (config.IS_PRODUCTION) { } if (process.platform === 'win32') { + const squirrelWin32 = require('./squirrel-win32') shouldQuit = squirrelWin32.handleEvent(argv[0]) argv = argv.filter((arg) => !arg.includes('--squirrel')) } @@ -58,6 +51,8 @@ function init () { app.setPath('userData', config.CONFIG_PATH) } + const ipcMain = electron.ipcMain + let isReady = false // app ready, windows can be created app.ipcReady = false // main window has finished loading and IPC is ready app.isQuitting = false @@ -121,6 +116,12 @@ function init () { } function delayedInit () { + const announcement = require('./announcement') + const dock = require('./dock') + const tray = require('./tray') + const updater = require('./updater') + const userTasks = require('./user-tasks') + announcement.init() dock.init() tray.init() @@ -169,12 +170,16 @@ function sliceArgv (argv) { function processArgv (argv) { let torrentIds = [] argv.forEach(function (arg) { - if (arg === '-n') { - dialog.openSeedDirectory() - } else if (arg === '-o') { - dialog.openTorrentFile() - } else if (arg === '-u') { - dialog.openTorrentAddress() + if (arg === '-n' || arg === '-o' || arg === '-u') { + // Critical path: Only load the 'dialog' package if it is needed + const dialog = require('./dialog') + if (arg === '-n') { + dialog.openSeedDirectory() + } else if (arg === '-o') { + dialog.openTorrentFile() + } else if (arg === '-u') { + dialog.openTorrentAddress() + } } else if (arg === '--hidden') { // Ignore hidden argument, already being handled } else if (arg.startsWith('-psn')) { diff --git a/src/main/ipc.js b/src/main/ipc.js index 436082ed7c..6728ff3de8 100644 --- a/src/main/ipc.js +++ b/src/main/ipc.js @@ -6,18 +6,9 @@ const electron = require('electron') const app = electron.app -const dialog = require('./dialog') -const dock = require('./dock') -const handlers = require('./handlers') const log = require('./log') const menu = require('./menu') -const powerSaveBlocker = require('./power-save-blocker') -const shell = require('./shell') -const shortcuts = require('./shortcuts') -const externalPlayer = require('./external-player') const windows = require('./windows') -const thumbar = require('./thumbar') -const startup = require('./startup') // Messages from the main process, to be sent once the WebTorrent process starts const messageQueueMainToWebTorrent = [] @@ -44,21 +35,37 @@ function init () { * Dialog */ - ipc.on('openTorrentFile', () => dialog.openTorrentFile()) - ipc.on('openFiles', () => dialog.openFiles()) + ipc.on('openTorrentFile', () => { + const dialog = require('./dialog') + dialog.openTorrentFile() + }) + ipc.on('openFiles', () => { + const dialog = require('./dialog') + dialog.openFiles() + }) /** * Dock */ - ipc.on('setBadge', (e, ...args) => dock.setBadge(...args)) - ipc.on('downloadFinished', (e, ...args) => dock.downloadFinished(...args)) + ipc.on('setBadge', (e, ...args) => { + const dock = require('./dock') + dock.setBadge(...args) + }) + ipc.on('downloadFinished', (e, ...args) => { + const dock = require('./dock') + dock.downloadFinished(...args) + }) /** * Events */ ipc.on('onPlayerOpen', function () { + const powerSaveBlocker = require('./power-save-blocker') + const shortcuts = require('./shortcuts') + const thumbar = require('./thumbar') + menu.togglePlaybackControls(true) powerSaveBlocker.enable() shortcuts.enable() @@ -66,11 +73,17 @@ function init () { }) ipc.on('onPlayerUpdate', function (e, ...args) { + const thumbar = require('./thumbar') + menu.onPlayerUpdate(...args) thumbar.onPlayerUpdate(...args) }) ipc.on('onPlayerClose', function () { + const powerSaveBlocker = require('./power-save-blocker') + const shortcuts = require('./shortcuts') + const thumbar = require('./thumbar') + menu.togglePlaybackControls(false) powerSaveBlocker.disable() shortcuts.disable() @@ -78,11 +91,17 @@ function init () { }) ipc.on('onPlayerPlay', function () { + const powerSaveBlocker = require('./power-save-blocker') + const thumbar = require('./thumbar') + powerSaveBlocker.enable() thumbar.onPlayerPlay() }) ipc.on('onPlayerPause', function () { + const powerSaveBlocker = require('./power-save-blocker') + const thumbar = require('./thumbar') + powerSaveBlocker.disable() thumbar.onPlayerPause() }) @@ -91,15 +110,26 @@ function init () { * Shell */ - ipc.on('openItem', (e, ...args) => shell.openItem(...args)) - ipc.on('showItemInFolder', (e, ...args) => shell.showItemInFolder(...args)) - ipc.on('moveItemToTrash', (e, ...args) => shell.moveItemToTrash(...args)) + ipc.on('openItem', (e, ...args) => { + const shell = require('./shell') + shell.openItem(...args) + }) + ipc.on('showItemInFolder', (e, ...args) => { + const shell = require('./shell') + shell.showItemInFolder(...args) + }) + ipc.on('moveItemToTrash', (e, ...args) => { + const shell = require('./shell') + shell.moveItemToTrash(...args) + }) /** * File handlers */ ipc.on('setDefaultFileHandler', (e, flag) => { + const handlers = require('./handlers') + if (flag) handlers.install() else handlers.uninstall() }) @@ -109,6 +139,8 @@ function init () { */ ipc.on('setStartup', (e, flag) => { + const startup = require('./startup') + if (flag) startup.install() else startup.uninstall() }) @@ -132,18 +164,26 @@ function init () { */ ipc.on('checkForExternalPlayer', function (e, path) { + const externalPlayer = require('./external-player') + externalPlayer.checkInstall(path, function (isInstalled) { windows.main.send('checkForExternalPlayer', isInstalled) }) }) ipc.on('openExternalPlayer', (e, ...args) => { + const externalPlayer = require('./external-player') + const thumbar = require('./thumbar') + menu.togglePlaybackControls(false) thumbar.disable() externalPlayer.spawn(...args) }) - ipc.on('quitExternalPlayer', () => externalPlayer.kill()) + ipc.on('quitExternalPlayer', () => { + const externalPlayer = require('./external-player') + externalPlayer.kill() + }) /** * Message passing diff --git a/src/main/menu.js b/src/main/menu.js index be594a8ab3..1b7fd47905 100644 --- a/src/main/menu.js +++ b/src/main/menu.js @@ -13,8 +13,6 @@ const electron = require('electron') const app = electron.app const config = require('../config') -const dialog = require('./dialog') -const shell = require('./shell') const windows = require('./windows') let menu = null @@ -90,17 +88,26 @@ function getMenuTemplate () { ? 'Create New Torrent...' : 'Create New Torrent from Folder...', accelerator: 'CmdOrCtrl+N', - click: () => dialog.openSeedDirectory() + click: () => { + const dialog = require('./dialog') + dialog.openSeedDirectory() + } }, { label: 'Open Torrent File...', accelerator: 'CmdOrCtrl+O', - click: () => dialog.openTorrentFile() + click: () => { + const dialog = require('./dialog') + dialog.openTorrentFile() + } }, { label: 'Open Torrent Address...', accelerator: 'CmdOrCtrl+U', - click: () => dialog.openTorrentAddress() + click: () => { + const dialog = require('./dialog') + dialog.openTorrentAddress() + } }, { type: 'separator' @@ -277,18 +284,27 @@ function getMenuTemplate () { submenu: [ { label: 'Learn more about ' + config.APP_NAME, - click: () => shell.openExternal(config.HOME_PAGE_URL) + click: () => { + const shell = require('./shell') + shell.openExternal(config.HOME_PAGE_URL) + } }, { label: 'Contribute on GitHub', - click: () => shell.openExternal(config.GITHUB_URL) + click: () => { + const shell = require('./shell') + shell.openExternal(config.GITHUB_URL) + } }, { type: 'separator' }, { label: 'Report an Issue...', - click: () => shell.openExternal(config.GITHUB_URL_ISSUES) + click: () => { + const shell = require('./shell') + shell.openExternal(config.GITHUB_URL_ISSUES) + } } ] } @@ -361,7 +377,10 @@ function getMenuTemplate () { // File menu (Windows, Linux) template[0].submenu.unshift({ label: 'Create New Torrent from File...', - click: () => dialog.openSeedFile() + click: () => { + const dialog = require('./dialog') + dialog.openSeedFile() + } }) // Edit menu (Windows, Linux) diff --git a/src/main/windows/main.js b/src/main/windows/main.js index b1c55308fe..5f46274c9c 100644 --- a/src/main/windows/main.js +++ b/src/main/windows/main.js @@ -21,7 +21,6 @@ const app = electron.app const config = require('../../config') const log = require('../log') const menu = require('../menu') -const tray = require('../tray') function init (state, options) { if (main.win) { @@ -81,6 +80,8 @@ function init (state, options) { }) win.on('close', function (e) { + const tray = require('../tray') + if (process.platform !== 'darwin' && !tray.hasTray()) { app.quit() } else if (!app.isQuitting) { @@ -221,11 +222,15 @@ function toggleFullScreen (flag) { } function onWindowBlur () { + const tray = require('../tray') + menu.setWindowFocus(false) tray.setWindowFocus(false) } function onWindowFocus () { + const tray = require('../tray') + menu.setWindowFocus(true) tray.setWindowFocus(true) } diff --git a/src/renderer/lib/migrations.js b/src/renderer/lib/migrations.js index c09a7a5e62..5433183e9b 100644 --- a/src/renderer/lib/migrations.js +++ b/src/renderer/lib/migrations.js @@ -4,10 +4,10 @@ module.exports = { run } +const fs = require('fs') const semver = require('semver') + const config = require('../../config') -const TorrentSummary = require('./torrent-summary') -const fs = require('fs') // Change `state.saved` (which will be saved back to config.json on exit) as // needed, for example to deal with config.json format changes across versions @@ -116,6 +116,8 @@ function migrate_0_11_0 (saved) { } function migrate_0_12_0 (saved) { + const TorrentSummary = require('./torrent-summary') + if (saved.prefs.openExternalPlayer == null && saved.prefs.playInVlc != null) { saved.prefs.openExternalPlayer = saved.prefs.playInVlc } diff --git a/src/renderer/lib/state.js b/src/renderer/lib/state.js index 6e37ff81bb..39758bdba6 100644 --- a/src/renderer/lib/state.js +++ b/src/renderer/lib/state.js @@ -181,19 +181,18 @@ function getExternalPlayerName () { } function load (cb) { - const state = getDefaultState() - appConfig.read(function (err, saved) { if (err || !saved.version) { console.log('Missing config file: Creating new one') - setupStateSaved(onSaved) + setupStateSaved(onSavedState) } else { - onSaved(null, saved) + onSavedState(null, saved) } }) - function onSaved (err, saved) { + function onSavedState (err, saved) { if (err) return cb(err) + const state = getDefaultState() state.saved = saved migrations.run(state) cb(null, state)