Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
Quick and dirty implementation of runtime type profiling in Visual Studio Code
const vscode = require('vscode');
const path = require('path');
// puppeteer is great because it's easy to use, but it comes with 30MB headless Chrome that
// we can't use as it doesn't have `Profiler.startTypeProfile` yet. We have to point to a
// locally installed Chrome Canary instead. It's fine for a POC, but puppeteer isn't probably
// a good fit in a long run.
const puppeteer = require('puppeteer');
const PATH_TO_CANARY = '/Applications/Google\ Chrome\\ Chrome\ Canary';
let browser = null;
let page = null;
function activate(context) {
const collection = vscode.languages.createDiagnosticCollection('javascript');
const disposable = vscode.commands.registerCommand('extension.typeProfile', async () => {
if (!browser) {
browser = await puppeteer.launch({ executablePath: PATH_TO_CANARY });
page = await browser.newPage();
await page.reload();
// I haven't found an official way to send CDP commands directly via puppeteer, so I'm using
// a private API here (remember it's a POC!)
await page._client.send('Runtime.enable');
await page._client.send('Profiler.enable');
await page._client.send('Profiler.startTypeProfile');
const document = vscode.window.activeTextEditor.document;
const fileName = path.basename(document.uri.toString());
// Compile script
const { scriptId, exceptionDetails } = await page._client.send('Runtime.compileScript', {
expression: document.getText(),
sourceURL: fileName,
persistScript: true
if (exceptionDetails) {
// Exception lineNumber and columnNumber can be used to highlight offending code.
vscode.window.showErrorMessage(`Error compiling script: ${exceptionDetails.text} ${exceptionDetails.lineNumber}:${exceptionDetails.columnNumber}`);
// Execute script
await page._client.send('Runtime.runScript', { scriptId });
const { result } = await page._client.send('Profiler.takeTypeProfile');
const script = result.find(script => script.url === fileName);
if (script) {
const document = vscode.window.activeTextEditor.document;
const diagnostics = => {
// I'm highlighting only 1 character, it'd be better to highlight whole symbol
const typePositionStart = document.positionAt(entry.offset);
const typePositionEnd = new vscode.Position(typePositionStart.line, typePositionStart.character + 1);
const range = new vscode.Range(typePositionStart, typePositionEnd);
const typesString = =>' or ');
return new vscode.Diagnostic(range, `V8 says it's a ${typesString}`, vscode.DiagnosticSeverity.Information);
collection.set(document.uri, diagnostics);
await page._client.send('Profiler.stopTypeProfile');
await page._client.send('Profiler.disable');
await page._client.send('Runtime.disable');
exports.activate = activate;
async function deactivate() {
if (browser) {
await browser.close();
exports.deactivate = deactivate;

kdzwinel commented Sep 20, 2017

That's the core of the extension, all other files that are needed to run it can be generated with Yeoman (docs).

You can see it working in this preview.

Some thoughts:

  • my implementation uses browser to execute code, but it could use nodejs (see hinkel/type-profile),
  • running single, self contained file works great, but making file with dependencies work will be much more challenging,
  • ideally this information should be collected while running tests,
  • Chrome Debugging Protocol docs are here.

nojvek commented Sep 20, 2017

@kdzwinel, do you reckon this is also available in any of the latest nodejs builds?


kdzwinel commented Sep 20, 2017

@nojvek yes, you have to use TOT though: . Also, see .

[Edit] I've updated the first comment with some more thoughts/info

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment