refactor: bundle the browser and renderer process electron code (#18553)

* refactor: bundle the browser and renderer process electron code

* Bundles browser/init and renderer/init
  * Improves load performance of main process by ~40%
  * Improves load performance of renderer process by ~30%
* Prevents users from importing our "requiring" our internal logic such
as ipc-main-internal.  This makes those message buses safer as they are
less accessible, there is still some more work to be done though to lock
down those buses completely.
* The electron.asar file now only contains 2 files, as a future
improvement maybe we can use atom_natives to ship these two files
embedded in the binary
* This also removes our dependency on browserify which had some strange
edge cases that caused us to have to hack around require-order and
stopped us using certain ES6/7 features we should have been able to use
(async / await in some files in the sandboxed renderer init script)

TLDR: Things are faster and better :)

* fix: I really do not want to talk about it

* chore: add performance improvements from debugging

* fix: resolve the provided path so webpack thinks it is absolute

* chore: fixup per PR review

* fix: use webpacks ProvidePlugin to keep global, process and Buffer alive after deletion from global scope for use in internal code

* fix: bundle worker/init as well to make node-in-workers work

* chore: update wording as per feedback

* chore: make the timers hack work when yarn is not used
This commit is contained in:
Samuel Attard 2019-06-02 13:03:03 -07:00 committed by GitHub
parent a19e55a902
commit bc527f6b51
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
40 changed files with 1612 additions and 920 deletions

View file

@ -0,0 +1,2 @@
process.env.PRINT_WEBPACK_GRAPH = true
require('./run-compiler')

View file

@ -0,0 +1,22 @@
const path = require('path')
const webpack = require('webpack')
const configPath = process.argv[2]
const outPath = path.resolve(process.argv[3])
const config = require(configPath)
config.output = {
path: path.dirname(outPath),
filename: path.basename(outPath)
}
webpack(config, (err, stats) => {
if (err) {
console.error(err)
process.exit(1)
} else if (stats.hasErrors()) {
console.error(stats.toString('normal'))
process.exit(1)
} else {
process.exit(0)
}
})

View file

@ -0,0 +1,80 @@
const fs = require('fs')
const path = require('path')
const webpack = require('webpack')
const electronRoot = path.resolve(__dirname, '../..')
const onlyPrintingGraph = !!process.env.PRINT_WEBPACK_GRAPH
class AccessDependenciesPlugin {
apply(compiler) {
// Only hook into webpack when we are printing the dependency graph
if (!onlyPrintingGraph) return
compiler.hooks.compilation.tap('AccessDependenciesPlugin', compilation => {
compilation.hooks.finishModules.tap('AccessDependenciesPlugin', modules => {
const filePaths = modules.map(m => m.resource).filter(p => p).map(p => path.relative(electronRoot, p))
console.info(JSON.stringify(filePaths))
})
})
}
}
module.exports = ({
alwaysHasNode,
loadElectronFromAlternateTarget,
targetDeletesNodeGlobals,
target
}) => {
let entry = path.resolve(electronRoot, 'lib', target, 'init.ts')
if (!fs.existsSync(entry)) {
entry = path.resolve(electronRoot, 'lib', target, 'init.js')
}
return ({
mode: 'development',
devtool: 'inline-source-map',
entry,
target: alwaysHasNode ? 'node' : 'web',
output: {
filename: `${target}.bundle.js`
},
resolve: {
alias: {
'@electron/internal': path.resolve(electronRoot, 'lib'),
'electron': path.resolve(electronRoot, 'lib', loadElectronFromAlternateTarget || target, 'api', 'exports', 'electron.js'),
// Force timers to resolve to our dependency that doens't use window.postMessage
'timers': path.resolve(electronRoot, 'node_modules', 'timers-browserify', 'main.js')
},
extensions: ['.ts', '.js']
},
module: {
rules: [{
test: /\.ts$/,
loader: 'ts-loader',
options: {
configFile: path.resolve(electronRoot, 'tsconfig.electron.json'),
transpileOnly: onlyPrintingGraph,
ignoreDiagnostics: [6059]
}
}]
},
node: {
__dirname: false,
__filename: false,
// We provide our own "timers" import above, any usage of setImmediate inside
// one of our renderer bundles should import it from the 'timers' package
setImmediate: false,
},
plugins: [
new AccessDependenciesPlugin(),
...(targetDeletesNodeGlobals ? [
new webpack.ProvidePlugin({
process: ['@electron/internal/renderer/webpack-provider', 'process'],
global: ['@electron/internal/renderer/webpack-provider', '_global'],
Buffer: ['@electron/internal/renderer/webpack-provider', 'Buffer'],
})
] : [])
]
})
}

View file

@ -0,0 +1,4 @@
module.exports = require('./webpack.config.base')({
target: 'browser',
alwaysHasNode: true
})

View file

@ -0,0 +1,4 @@
module.exports = require('./webpack.config.base')({
target: 'content_script',
alwaysHasNode: false
})

View file

@ -0,0 +1,4 @@
module.exports = require('./webpack.config.base')({
target: 'isolated_renderer',
alwaysHasNode: false
})

View file

@ -0,0 +1,5 @@
module.exports = require('./webpack.config.base')({
target: 'renderer',
alwaysHasNode: true,
targetDeletesNodeGlobals: true
})

View file

@ -0,0 +1,4 @@
module.exports = require('./webpack.config.base')({
target: 'sandboxed_renderer',
alwaysHasNode: false
})

View file

@ -0,0 +1,6 @@
module.exports = require('./webpack.config.base')({
target: 'worker',
loadElectronFromAlternateTarget: 'renderer',
alwaysHasNode: true,
targetDeletesNodeGlobals: true
})

35
build/webpack/webpack.gni Normal file
View file

@ -0,0 +1,35 @@
import("../npm.gni")
template("webpack_build") {
assert(defined(invoker.config_file), "Need webpack config file to run")
assert(defined(invoker.out_file), "Need output file to run")
assert(defined(invoker.inputs), "Need webpack inputs to run")
npm_action(target_name) {
forward_variables_from(invoker,
[
"deps",
"public_deps",
])
script = "webpack"
inputs = [
invoker.config_file,
"//electron/build/webpack/webpack.config.base.js",
"//electron/tsconfig.json",
"//electron/yarn.lock",
"//electron/typings/internal-ambient.d.ts",
"//electron/typings/internal-electron.d.ts",
"//electron/typings/internal-helpers.d.ts",
] + invoker.inputs
args = [
rebase_path(invoker.config_file),
rebase_path(invoker.out_file),
]
outputs = [
invoker.out_file,
]
}
}