[RFC] perf: use an internal module resolver to improve require performance (#14633)

* perf: use an internal module resolver instead of relative requires

* perf: memoize the results of getting exported Electron properties

* perf: make internal module changes consistent across sandboxed / bundled files
This commit is contained in:
Samuel Attard 2018-09-20 13:43:26 +10:00 committed by GitHub
parent 73d1b76b54
commit 54ef906832
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
32 changed files with 156 additions and 73 deletions

View file

@ -94,6 +94,8 @@ npm_action("atom_browserify_sandbox") {
"./lib/sandboxed_renderer/api/exports/path.js:path",
"-r",
"./lib/sandboxed_renderer/api/exports/child_process.js:child_process",
"-t",
"aliasify"
]
inputs = [
@ -129,6 +131,8 @@ npm_action("atom_browserify_isolated") {
script = "browserify"
args = inputs + [
"-t",
"aliasify",
"-o",
rebase_path(outputs[0]),
]

View file

@ -1,5 +1,5 @@
if (process.platform === 'win32') {
module.exports = require('./auto-updater/auto-updater-win')
module.exports = require('@electron/internal/browser/api/auto-updater/auto-updater-win')
} else {
module.exports = require('./auto-updater/auto-updater-native')
module.exports = require('@electron/internal/browser/api/auto-updater/auto-updater-native')
}

View file

@ -2,7 +2,7 @@
const { app } = require('electron')
const { EventEmitter } = require('events')
const squirrelUpdate = require('./squirrel-update-win')
const squirrelUpdate = require('@electron/internal/browser/api/auto-updater/squirrel-update-win')
class AutoUpdater extends EventEmitter {
quitAndInstall () {

View file

@ -1,6 +1,6 @@
const common = require('../../../common/api/exports/electron')
const common = require('@electron/internal/common/api/exports/electron')
// since browser module list is also used in renderer, keep it separate.
const moduleList = require('../module-list')
const moduleList = require('@electron/internal/browser/api/module-list')
// Import common modules.
common.defineProperties(exports)
@ -8,6 +8,6 @@ common.defineProperties(exports)
for (const module of moduleList) {
Object.defineProperty(exports, module.name, {
enumerable: !module.private,
get: () => require(`../${module.file}`)
get: common.memoizedGetter(() => require(`@electron/internal/browser/api/${module.file}.js`))
})
}

View file

@ -1,6 +1,6 @@
'use strict'
const roles = require('./menu-item-roles')
const roles = require('@electron/internal/browser/api/menu-item-roles')
let nextCommandId = 0

View file

@ -1,7 +1,7 @@
'use strict'
const { TopLevelWindow, MenuItem, webContents } = require('electron')
const { sortMenuItems } = require('./menu-utils')
const { sortMenuItems } = require('@electron/internal/browser/api/menu-utils')
const EventEmitter = require('events').EventEmitter
const v8Util = process.atomBinding('v8_util')
const bindings = process.atomBinding('menu')

View file

@ -6,7 +6,7 @@ const path = require('path')
const url = require('url')
const { app, ipcMain, session, NavigationController, deprecate } = electron
const errorUtils = require('../../common/error-utils')
const errorUtils = require('@electron/internal/common/error-utils')
// session is not used here, the purpose is to make sure session is initalized
// before the webContents module.

View file

@ -1,7 +1,7 @@
'use strict'
const { ipcMain, webContents } = require('electron')
const parseFeaturesString = require('../common/parse-features-string')
const parseFeaturesString = require('@electron/internal/common/parse-features-string')
// Doesn't exist in early initialization.
let webViewManager = null

View file

@ -2,7 +2,7 @@
const { BrowserWindow, ipcMain, webContents } = require('electron')
const { isSameOrigin } = process.atomBinding('v8_util')
const parseFeaturesString = require('../common/parse-features-string')
const parseFeaturesString = require('@electron/internal/common/parse-features-string')
const hasProp = {}.hasOwnProperty
const frameToGuest = new Map()

View file

@ -15,7 +15,7 @@ process.argv.splice(1, 1)
require('../common/reset-search-paths')
// Import common settings.
require('../common/init')
require('@electron/internal/common/init')
var globalPaths = Module.globalPaths
@ -94,11 +94,11 @@ if (process.platform === 'win32') {
process.exit = app.exit
// Load the RPC server.
require('./rpc-server')
require('@electron/internal/browser/rpc-server')
// Load the guest view manager.
require('./guest-view-manager')
require('./guest-window-manager')
require('@electron/internal/browser/guest-view-manager')
require('@electron/internal/browser/guest-window-manager')
// Now we try to load app's package.json.
let packagePath = null
@ -151,16 +151,16 @@ app.setPath('userCache', path.join(app.getPath('cache'), app.getName()))
app.setAppPath(packagePath)
// Load the chrome extension support.
require('./chrome-extension')
require('@electron/internal/browser/chrome-extension')
const features = process.atomBinding('features')
if (features.isDesktopCapturerEnabled()) {
// Load internal desktop-capturer module.
require('./desktop-capturer')
require('@electron/internal/browser/desktop-capturer')
}
// Load protocol module to ensure it is populated on app ready
require('./api/protocol')
require('@electron/internal/browser/api/protocol')
// Set main startup script of the app.
const mainStartupScript = packageJson.main || 'index.js'

View file

@ -7,8 +7,8 @@ const v8Util = process.atomBinding('v8_util')
const { ipcMain, isPromise } = electron
const objectsRegistry = require('./objects-registry')
const bufferUtils = require('../common/buffer-utils')
const objectsRegistry = require('@electron/internal/browser/objects-registry')
const bufferUtils = require('@electron/internal/common/buffer-utils')
const hasProp = {}.hasOwnProperty
@ -367,13 +367,13 @@ handleRemoteCommand('ELECTRON_BROWSER_CONTEXT_RELEASE', (event, contextId) => {
})
handleRemoteCommand('ELECTRON_BROWSER_GUEST_WEB_CONTENTS', function (event, contextId, guestInstanceId) {
let guestViewManager = require('./guest-view-manager')
let guestViewManager = require('@electron/internal/browser/guest-view-manager')
return valueToMeta(event.sender, contextId, guestViewManager.getGuest(guestInstanceId))
})
ipcMain.on('ELECTRON_BROWSER_ASYNC_CALL_TO_GUEST_VIEW', function (event, contextId, requestId, guestInstanceId, method, ...args) {
try {
let guestViewManager = require('./guest-view-manager')
let guestViewManager = require('@electron/internal/browser/guest-view-manager')
let guest = guestViewManager.getGuest(guestInstanceId)
if (requestId) {
const responseCallback = function (result) {

View file

@ -1,13 +1,29 @@
const moduleList = require('../module-list')
const moduleList = require('@electron/internal/common/api/module-list')
// Attaches properties to |exports|.
exports.defineProperties = function (exports) {
exports.memoizedGetter = (getter) => {
/*
* It's ok to leak this value as it would be leaked by the global
* node module cache anyway at `Module._cache`. This memoization
* is dramatically faster than relying on nodes module cache however
*/
let memoizedValue = null
return () => {
if (memoizedValue === null) {
memoizedValue = getter()
}
return memoizedValue
}
}
// Attaches properties to |targetExports|.
exports.defineProperties = function (targetExports) {
const descriptors = {}
for (const module of moduleList) {
descriptors[module.name] = {
enumerable: !module.private,
get: () => require(`../${module.file}`)
get: exports.memoizedGetter(() => require(`@electron/internal/common/api/${module.file}`))
}
}
return Object.defineProperties(exports, descriptors)
return Object.defineProperties(targetExports, descriptors)
}

View file

@ -1,7 +1,7 @@
const timers = require('timers')
const util = require('util')
process.atomBinding = require('./atom-binding-setup')(process.binding, process.type)
process.atomBinding = require('@electron/internal/common/atom-binding-setup')(process.binding, process.type)
// setImmediate and process.nextTick makes use of uv_check and uv_prepare to
// run the callbacks, however since we only run uv loop on requests, the

View file

@ -24,6 +24,9 @@ Module._nodeModulePaths = function (from) {
}
}
const BASE_INTERNAL_PATH = path.resolve(__dirname, '..')
const INTERNAL_MODULE_PREFIX = '@electron/internal/'
// Patch Module._resolveFilename to always require the Electron API when
// require('electron') is done.
const electronPath = path.join(__dirname, '..', process.type, 'api', 'exports', 'electron.js')
@ -31,6 +34,9 @@ const originalResolveFilename = Module._resolveFilename
Module._resolveFilename = function (request, parent, isMain) {
if (request === 'electron') {
return electronPath
} else if (request.startsWith(INTERNAL_MODULE_PREFIX) && request.length > INTERNAL_MODULE_PREFIX.length) {
const slicedRequest = request.slice(INTERNAL_MODULE_PREFIX.length)
return path.resolve(BASE_INTERNAL_PATH, `${slicedRequest}${slicedRequest.endsWith('.js') ? '' : '.js'}`)
} else {
return originalResolveFilename(request, parent, isMain)
}

View file

@ -22,4 +22,4 @@ let { guestInstanceId, hiddenPage, openerId, nativeWindowOpen } = binding
if (guestInstanceId != null) guestInstanceId = parseInt(guestInstanceId)
if (openerId != null) openerId = parseInt(openerId)
require('../renderer/window-setup')(ipcRenderer, guestInstanceId, openerId, hiddenPage, nativeWindowOpen)
require('@electron/internal/renderer/window-setup')(ipcRenderer, guestInstanceId, openerId, hiddenPage, nativeWindowOpen)

View file

@ -1,5 +1,5 @@
const common = require('../../../common/api/exports/electron')
const moduleList = require('../module-list')
const common = require('@electron/internal/common/api/exports/electron')
const moduleList = require('@electron/internal/renderer/api/module-list')
// Import common modules.
common.defineProperties(exports)
@ -16,6 +16,6 @@ for (const {
Object.defineProperty(exports, name, {
enumerable: !isPrivate,
get: () => require(`../${file}`)
get: common.memoizedGetter(() => require(`@electron/internal/renderer/api/${file}`))
})
}

View file

@ -4,8 +4,8 @@ const v8Util = process.atomBinding('v8_util')
const { ipcRenderer, isPromise } = require('electron')
const resolvePromise = Promise.resolve.bind(Promise)
const CallbacksRegistry = require('../callbacks-registry')
const bufferUtils = require('../../common/buffer-utils')
const CallbacksRegistry = require('@electron/internal/renderer/callbacks-registry')
const bufferUtils = require('@electron/internal/common/buffer-utils')
const callbacksRegistry = new CallbacksRegistry()
const remoteObjectCache = v8Util.createIDWeakMap()
@ -349,8 +349,8 @@ const addBuiltinProperty = (name) => {
}
const browserModules =
require('../../common/api/module-list').concat(
require('../../browser/api/module-list'))
require('@electron/internal/common/api/module-list').concat(
require('@electron/internal/browser/api/module-list'))
// And add a helper receiver for each one.
browserModules

View file

@ -1,5 +1,5 @@
const { ipcRenderer } = require('electron')
const Event = require('./extensions/event')
const Event = require('@electron/internal/renderer/extensions/event')
const url = require('url')
let nextId = 0
@ -175,7 +175,7 @@ exports.injectTo = function (extensionId, isBackgroundPage, context) {
onMessage: chrome.runtime.onMessage
}
chrome.storage = require('./extensions/storage').setup(extensionId)
chrome.storage = require('@electron/internal/renderer/extensions/storage').setup(extensionId)
chrome.pageAction = {
show () {},
@ -187,6 +187,6 @@ exports.injectTo = function (extensionId, isBackgroundPage, context) {
getPopup () {}
}
chrome.i18n = require('./extensions/i18n').setup(extensionId)
chrome.webNavigation = require('./extensions/web-navigation').setup()
chrome.i18n = require('@electron/internal/renderer/extensions/i18n').setup(extensionId)
chrome.webNavigation = require('@electron/internal/renderer/extensions/web-navigation').setup()
}

View file

@ -13,7 +13,7 @@ const matchesPattern = function (pattern) {
// Run the code with chrome API integrated.
const runContentScript = function (extensionId, url, code) {
const context = {}
require('./chrome-api').injectTo(extensionId, false, context)
require('@electron/internal/renderer/chrome-api').injectTo(extensionId, false, context)
const wrapper = `((chrome) => {\n ${code}\n })`
const compiledWrapper = runInThisContext(wrapper, {
filename: url,

View file

@ -1,4 +1,4 @@
const Event = require('./event')
const Event = require('@electron/internal/renderer/extensions/event')
const { ipcRenderer } = require('electron')
class WebNavigation {

View file

@ -12,7 +12,7 @@ process.argv.splice(1, 1)
require('../common/reset-search-paths')
// Import common settings.
require('../common/init')
require('@electron/internal/common/init')
var globalPaths = Module.globalPaths
@ -37,9 +37,9 @@ const {
warnAboutInsecureCSP,
warnAboutAllowedPopups,
shouldLogSecurityWarnings
} = require('./security-warnings')
} = require('@electron/internal/renderer/security-warnings')
require('./web-frame-init')()
require('@electron/internal/renderer/web-frame-init')()
// Process command line arguments.
let nodeIntegration = 'false'
@ -77,26 +77,26 @@ if (preloadScript) {
if (window.location.protocol === 'chrome-devtools:') {
// Override some inspector APIs.
require('./inspector')
require('@electron/internal/renderer/inspector')
nodeIntegration = 'false'
} else if (window.location.protocol === 'chrome-extension:') {
// Add implementations of chrome API.
require('./chrome-api').injectTo(window.location.hostname, isBackgroundPage, window)
require('@electron/internal/renderer/chrome-api').injectTo(window.location.hostname, isBackgroundPage, window)
nodeIntegration = 'false'
} else if (window.location.protocol === 'chrome:') {
// Disable node integration for chrome UI scheme.
nodeIntegration = 'false'
} else {
// Override default web functions.
require('./override')
require('@electron/internal/renderer/override')
// Inject content scripts.
require('./content-scripts-injector')
require('@electron/internal/renderer/content-scripts-injector')
// Load webview tag implementation.
if (webviewTag === 'true' && process.guestInstanceId == null) {
require('./web-view/web-view')
require('./web-view/web-view-attributes')
require('@electron/internal/renderer/web-view/web-view')
require('@electron/internal/renderer/web-view/web-view-attributes')
}
}

View file

@ -6,4 +6,4 @@ const { guestInstanceId, openerId } = process
const hiddenPage = process.argv.includes('--hidden-page')
const usesNativeWindowOpen = process.argv.includes('--native-window-open')
require('./window-setup')(ipcRenderer, guestInstanceId, openerId, hiddenPage, usesNativeWindowOpen)
require('@electron/internal/renderer/window-setup')(ipcRenderer, guestInstanceId, openerId, hiddenPage, usesNativeWindowOpen)

View file

@ -1,5 +1,5 @@
const { ipcRenderer, webFrame } = require('electron')
const errorUtils = require('../common/error-utils')
const errorUtils = require('@electron/internal/common/error-utils')
module.exports = () => {
// Call webFrame method

View file

@ -1,7 +1,7 @@
'use strict'
const WebViewImpl = require('./web-view')
const webViewConstants = require('./web-view-constants')
const WebViewImpl = require('@electron/internal/renderer/web-view/web-view')
const webViewConstants = require('@electron/internal/renderer/web-view/web-view-constants')
const { remote } = require('electron')
// Helper function to resolve url set in attribute.

View file

@ -3,8 +3,8 @@
const { ipcRenderer, remote, webFrame } = require('electron')
const v8Util = process.atomBinding('v8_util')
const guestViewInternal = require('./guest-view-internal')
const webViewConstants = require('./web-view-constants')
const guestViewInternal = require('@electron/internal/renderer/web-view/guest-view-internal')
const webViewConstants = require('@electron/internal/renderer/web-view/web-view-constants')
// An unique ID that can represent current context.
const contextId = v8Util.getHiddenValue(global, 'contextId')

View file

@ -1,4 +1,4 @@
const moduleList = require('../module-list')
const moduleList = require('@electron/internal/sandboxed_renderer/api/module-list')
for (const {
name,

View file

@ -1,4 +1,4 @@
const ipcRenderer = require('../../renderer/api/ipc-renderer')
const ipcRenderer = require('@electron/internal/renderer/api/ipc-renderer')
const v8Util = process.atomBinding('v8_util')
const ipcNative = process.atomBinding('ipc')

View file

@ -3,32 +3,32 @@ const features = process.atomBinding('features')
module.exports = [
{
name: 'crashReporter',
load: () => require('../../common/api/crash-reporter')
load: () => require('@electron/internal/common/api/crash-reporter')
},
{
name: 'desktopCapturer',
load: () => require('../../renderer/api/desktop-capturer'),
load: () => require('@electron/internal/renderer/api/desktop-capturer'),
enabled: features.isDesktopCapturerEnabled()
},
{
name: 'ipcRenderer',
load: () => require('./ipc-renderer')
load: () => require('@electron/internal/sandboxed_renderer/api/ipc-renderer')
},
{
name: 'isPromise',
load: () => require('../../common/api/is-promise'),
load: () => require('@electron/internal/common/api/is-promise'),
private: true
},
{
name: 'nativeImage',
load: () => require('../../common/api/native-image')
load: () => require('@electron/internal/common/api/native-image')
},
{
name: 'remote',
load: () => require('../../renderer/api/remote')
load: () => require('@electron/internal/renderer/api/remote')
},
{
name: 'webFrame',
load: () => require('../../renderer/api/web-frame')
load: () => require('@electron/internal/renderer/api/web-frame')
}
]

View file

@ -2,7 +2,7 @@
/* global binding, Buffer */
const events = require('events')
process.atomBinding = require('../common/atom-binding-setup')(binding.get, 'renderer')
process.atomBinding = require('@electron/internal/common/atom-binding-setup')(binding.get, 'renderer')
// The electron module depends on process.atomBinding
const electron = require('electron')
@ -41,7 +41,7 @@ const {
preloadSrc, preloadError, process: processProps
} = electron.ipcRenderer.sendSync('ELECTRON_BROWSER_SANDBOX_LOAD')
require('../renderer/web-frame-init')()
require('@electron/internal/renderer/web-frame-init')()
// Pass different process object to the preload script(which should not have
// access to things like `process.atomBinding`).
@ -76,7 +76,7 @@ function preloadRequire (module) {
if (window.location.protocol === 'chrome-devtools:') {
// Override some inspector APIs.
require('../renderer/inspector')
require('@electron/internal/renderer/inspector')
}
if (binding.guestInstanceId) {
@ -85,8 +85,8 @@ if (binding.guestInstanceId) {
if (!process.guestInstanceId && preloadProcess.argv.indexOf('--webview-tag=true') !== -1) {
// don't allow recursive `<webview>`
require('../renderer/web-view/web-view')
require('../renderer/web-view/web-view-attributes')
require('@electron/internal/renderer/web-view/web-view')
require('@electron/internal/renderer/web-view/web-view-attributes')
}
// Wrap the script into a function executed in global scope. It won't have

View file

@ -11,7 +11,7 @@ process.argv.splice(1, 1)
require('../common/reset-search-paths')
// Import common settings.
require('../common/init')
require('@electron/internal/common/init')
// Expose public APIs.
Module.globalPaths.push(path.join(__dirname, 'api', 'exports'))

51
package-lock.json generated
View file

@ -230,6 +230,15 @@
"integrity": "sha1-6GuBnGAs+IIa1jdBNpjx3sAhhHo=",
"dev": true
},
"aliasify": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/aliasify/-/aliasify-2.1.0.tgz",
"integrity": "sha1-fDCCW5RQueYYW6J1M+r24gZ9S0I=",
"dev": true,
"requires": {
"browserify-transform-tools": "~1.7.0"
}
},
"align-text": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz",
@ -1098,6 +1107,16 @@
"parse-asn1": "^5.0.0"
}
},
"browserify-transform-tools": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/browserify-transform-tools/-/browserify-transform-tools-1.7.0.tgz",
"integrity": "sha1-g+J3Ih9jJZvtLn6yooOpcKUB9MQ=",
"dev": true,
"requires": {
"falafel": "^2.0.0",
"through": "^2.3.7"
}
},
"browserify-zlib": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.1.4.tgz",
@ -3607,6 +3626,32 @@
"integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=",
"dev": true
},
"falafel": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/falafel/-/falafel-2.1.0.tgz",
"integrity": "sha1-lrsXdh2rqU9G0AFzizzt86Z/4Gw=",
"dev": true,
"requires": {
"acorn": "^5.0.0",
"foreach": "^2.0.5",
"isarray": "0.0.1",
"object-keys": "^1.0.6"
},
"dependencies": {
"acorn": {
"version": "5.7.3",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.3.tgz",
"integrity": "sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw==",
"dev": true
},
"object-keys": {
"version": "1.0.12",
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.12.tgz",
"integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag==",
"dev": true
}
}
},
"fast-deep-equal": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz",
@ -4115,6 +4160,12 @@
"for-in": "^1.0.1"
}
},
"foreach": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz",
"integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=",
"dev": true
},
"forever-agent": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",

View file

@ -4,6 +4,7 @@
"repository": "https://github.com/electron/electron",
"description": "Build cross platform desktop apps with JavaScript, HTML, and CSS",
"devDependencies": {
"aliasify": "^2.1.0",
"asar": "^0.11.0",
"browserify": "^13.1.0",
"check-for-leaks": "^1.0.2",
@ -65,5 +66,10 @@
"author": "Electron Community",
"keywords": [
"electron"
]
],
"aliasify": {
"replacements": {
"@electron/internal/(.+)": "./lib/$1"
}
}
}