How to Create a V8 Heap Snapshot of a Javascript File and Use It in Electron

V8 allows a creation of a heap snapshots. By using the mksnapshot tool provided by the V8 team you can create your own custom snapshots.

The advantage of a snapshot is that it loads faster than regular javascript and also the source is hidden in the snapshot.

The atom team has published their work in which they used V8 snapshots to speed up the initial load time of the editor.

Packages needed

The following packages are needed:

package description
electron Runtime.
electron-link Preprocess the javascript source file.
mksnapshot Create the snapshot.

There are 3 steps in creating a snapshot.

The first problem you encounter when creating a snapshot is that the snapshot itself is created in an “empty” V8 runtime.

This means in the context the snapshot is created there are no globals / object that you otherwise might have Node.js or in the browser. So there is no module object or window object.

In order to solve this the atom team have create the electron-link package. This package parses the source with babel (AST) and “replaces” the global objects that can be later or replaced.

According to this github comment this step is not needed anymore but i could not get it to work without it.

The output of this process is a generated js file that creates an object called snapshotResult that exposes the setGlobals and customRequire methods.

Step 2 Creating the snapshot with mksnapshot

The creation of the snapshot is pretty trivial. All you have to do is to call the mksnapshot binary with the linked js file and set an output.

One thing to note that your packages electron version decides which binary version will be downloaded.

Another important aspect that the blobs name must be snapshot_blob.bin.

Step 3 Loading the snapshot in electron

According to this forum post embedders can enable V8 to use snapshot_blob.bin and natives_blob.bin to “load” in snapshot and other native code.

By default there is a snapshot_blob.bin in your node_modules/electron/dist folder.
So you cannot require or import the blob, you have to overwrite the one electron is using. This is the way you can load in things to V8.

Code

This is pseudo implementation of the process.

1
npm install electron electron-link mksnapshot

First we need to create the snapshot with npm run snapshot than use it with npm run start.

1
2
3
4
5
// package.json
"scripts": {
"snapshot": "node snapshot.js",
"start": "electron index.js"
}

The file we want to create a snapshot of.

1
2
3
4
5
6
7
// snapshotme.js
function methodInTheSnapshot(){
console.log('Hello from the snapshot');
}

// note dont use the module.exports form
exports.methodInTheSnapshot = methodInTheSnapshot;

Create the snapshot and move it to the correct location.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// snapshot.js
class SnapshotCreator {
constructor(source, linked, snapshot ){
this.source = source;
this.linked = linked;
this.linkedContent = null;
this.snapshot = snapshot;
this.mksnapshot = path.join('node_modules', 'electron-mksnapshot', 'bin', 'mksnapshot');
}

async link() {
let response = await electronLink({
baseDirPath: __dirname,
mainPath: this.source,
cachePath: __dirname,
shouldExcludeModule: modulePath => false
});

this.linkedContent = response.snapshotScript;
}

createSnapshot() {
childProcess.execFileSync(
this.mksnapshot,
['--no-use-ic', this.linked, '--startup-blob', this.snapshot]);
}

saveLinkedFile() {
fs.writeFileSync(this.linked, this.linkedContent);
}
}

async function main(){
const pathToSource = "./snapshot-me.js";
const pathToLinked = "./linked.js";
const pathToSnapshot = "./node_modules/electron/dist/snapshot_blob.bin";
const snapshotCreator = new SnapshotCreator(pathToSource, pathToLinked, pathToSnapshot);
await snapshotCreator.link();
snapshotCreator.saveLinkedFile();
snapshotCreator.createSnapshot();
}

main();

Use in the created snapshot.

1
2
3
4
5
6
// index.js
if (typeof snapshotResult !== 'undefined'){
snapshotResult.setGlobals(global, process, global, {}, console, require);
const snapshotModule = snapshotResult.customRequire('./snapshotme.js');
snapshotModule.methodInTheSnapshot(); //Hello from the snapshot
}

This should give you a basic idea about the process.

Notable links