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\ Canary.app/Contents/MacOS/Google\ 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}`); | |
return; | |
} | |
// 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 = script.entries.map(entry => { | |
// 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 = entry.types.map(type => type.name).join(' 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'); | |
}); | |
context.subscriptions.push(disposable); | |
} | |
exports.activate = activate; | |
async function deactivate() { | |
if (browser) { | |
await browser.close(); | |
} | |
} | |
exports.deactivate = deactivate; |
This comment has been minimized.
This comment has been minimized.
@kdzwinel, do you reckon this is also available in any of the latest nodejs builds? |
This comment has been minimized.
This comment has been minimized.
@nojvek yes, you have to use TOT though: https://github.com/v8/node . Also, see https://github.com/fhinkel/type-profile . [Edit] I've updated the first comment with some more thoughts/info |
This comment has been minimized.
This comment has been minimized.
@kdzwinel @novek I've created a repo for this extension, https://github.com/auchenberg/vscode-javascript-type-profiler |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This comment has been minimized.
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: