--- sidebarDepth: 5 --- # Plugin Development Guide ::: warning Notice The API is only available in Vue Devtools 6+ ::: ## Architecture A Vue Devtools Plugin is registered from your Vue package code in the user app. It interacts with the Vue Devtools Backend, *via* [the public API](./api-reference.md). The Backend is a script injected to the web page when Vue Devtools are opened by the user - it handles registering Vue applications and communicating with the Vue Devtools Frontend. The Frontend is the Vue application displayed in the browser devtools pane. The Hook is a global variable added to the page so that the Vue application and your plugin can send messages to the Backend. There are 3 main API categories: - **Components Inspector**: your plugin can add more information to the component tree and state. - **Custom Inspectors**: you can add new inspectors to display any kind of state. For example: routes, store current state... - **Timeline**: you can add custom layers and send events. Using the API, you can show information to the user and improve the app debugging experience. Official libraries like `vue-router` and `vuex` already use this API! ### Architecture schema ![Vue Devtools Architectue](../assets/vue-devtools-architecture.png) ## Examples - [Vue 3 plugin](https://github.com/Akryum/vue3-devtools-plugin-example) - [Vue 2 plugin](https://github.com/Akryum/vue2-devtools-plugin-example) ## Setup In your package, install `@vue/devtools-api` as a dependency: ```bash yarn add @vue/devtools-api ``` This package will let you register a new Vue Devtools Plugin from your code and comes with full TypeScript typings. Your `package.json` file should look similar to this: ```json { "name": "my-awesome-plugin", "version": "0.0.0", "main": "dist/index.js", "dependencies": { "@vue/devtools-api": "^6.0.0-beta.14" }, "peerDependencies": { "vue": "^3.1.0" }, "devDependencies": { "vue": "^3.1.0" } } ``` It's a good idea to also specify `vue` as a peer dependency to inform the user which version of Vue your package is compatible with. ### TypeScript If you are using TS, your `package.json` file should look similar to this: ```json { "name": "my-awesome-plugin", "version": "0.0.0", "main": "dist/index.js", "scripts": { "dev": "tsc --watch -d", "build": "tsc -d" }, "dependencies": { "@vue/devtools-api": "^6.0.0-beta.14" }, "peerDependencies": { "vue": "^3.1.0" }, "devDependencies": { "@types/node": "^20.11.16", "typescript": "^5.3.3", "vue": "^3.1.0" } } ``` Here is an example `tsconfig.json` file to put next to the `package.json` file: ```json { "include": [ "src/global.d.ts", "src/**/*.ts", "__tests__/**/*.ts" ], "compilerOptions": { "outDir": "dist", "sourceMap": false, "target": "esnext", "module": "esnext", "moduleResolution": "node", "allowJs": false, "skipLibCheck": true, "noUnusedLocals": true, "strictNullChecks": true, "noImplicitAny": true, "noImplicitThis": true, "noImplicitReturns": false, "strict": true, "isolatedModules": true, "experimentalDecorators": true, "resolveJsonModule": true, "esModuleInterop": true, "removeComments": false, "jsx": "preserve", "lib": [ "esnext", "dom" ], "types": [ "node" ] } } ``` ### Rollup [Rollup](https://rollupjs.org/) is a general-purpose bundler. You can use it to compile your package for easier consumption. It's also very handy if you have `.vue` files to compile! ```bash yarn add -D rollup rollup-plugin-vue @rollup/plugin-commonjs @rollup/plugin-node-resolve @rollup/plugin-replace rollup-plugin-terser pascalcase rimraf ``` ::: tip Notice about the packages - `rollup-plugin-vue` compiles the `.vue` files. - `@rollup/plugin-commonjs` converts CommonJS modules to ES2015 modules. - `@rollup/plugin-node-resolve` locates and bundles third-party dependencies in `node_modules`. - `@rollup/plugin-replace` allow us to replace some source code text like `process.env.NODE_ENV` with the values we want at build time. - `rollup-plugin-terser` minimizes the output for production. - `pascalcase` is used to convert your package name (from `package.json`) into Pascal case, for example `my-plugin` to `MyPlugin`. - `rimraf` is useful to clear the `dist` folder before building. ::: #### Rollup config Create a `rollup.config.js` file next to the `package.json` file: [See example](https://gist.github.com/Akryum/b200b92689c6d7e3bb3871da906be01e) #### Rollup package Add the main fields, `exports` and `scripts` to your `package.json`: ```json { "name": "my-plugin", "version": "0.0.0", "description": "A demo Vue 3 plugin with devtools integration", "author": { "name": "Guillaume Chau", "email": "guillaume.b.chau@gmail.com" }, "main": "dist/my-plugin.cjs.js", "module": "dist/my-plugin.esm-bundler.js", "unpkg": "dist/my-plugin.global.js", "jsdelivr": "dist/my-plugin.global.js", "exports": { ".": { "require": "./dist/my-plugin.cjs.js", "browser": "./dist/my-plugin.esm-browser.js", "import": "./dist/my-plugin.esm-bundler.js", "module": "./dist/my-plugin.esm-bundler.js" }, "./package.json": "./package.json" }, "sideEffects": false, "scripts": { "build": "rimraf dist && rollup -c rollup.config.js" } // ... } ``` Don't forget to replace `my-plugin` with your package name. You can now use the `build` script to compile the package: ```bash yarn build ``` #### Rollup with TypeScript Install the Rollup TS plugin: ```bash yarn add -D rollup-plugin-typescript2 ``` Modify the Rollup config to compile the TS files: [See example](https://gist.github.com/Akryum/cb0a396904169bd55ddf964e3b20452a) And add the `types` field to your `package.json` file: ```json{13} { "name": "my-plugin", "version": "0.0.0", "description": "A demo Vue 3 plugin with devtools integration", "author": { "name": "Guillaume Chau", "email": "guillaume.b.chau@gmail.com" }, "main": "dist/my-plugin.cjs.js", "module": "dist/my-plugin.esm-bundler.js", "unpkg": "dist/my-plugin.global.js", "jsdelivr": "dist/my-plugin.global.js", "types": "dist/index.d.ts", "exports": { ".": { "require": "./dist/my-plugin.cjs.js", "browser": "./dist/my-plugin.esm-browser.js", "import": "./dist/my-plugin.esm-bundler.js", "module": "./dist/my-plugin.esm-bundler.js" }, "./package.json": "./package.json" }, "sideEffects": false, "scripts": { "build": "rimraf dist && rollup -c rollup.config.js" } ... } ``` See [TypeScript](#typescript) for a `tsconfig.json` example. ## Registering your plugin Create a new `devtools.js` file in your source folder. ### Plugin setup We are going to import [`setupDevtoolsPlugin`](./api-reference.md#setupdevtoolsplugin) from the `@vue/devtools-api` package: ```js import { setupDevtoolsPlugin } from '@vue/devtools-api' ``` Then we export a function to setup our Vue Devtools plugin: ```js export function setupDevtools() { setupDevtoolsPlugin({ /* Options... */}, (api) => { // Logic... }) } ``` Add the plugin options in the first argument: ```js{2-5} setupDevtoolsPlugin({ id: 'my-awesome-devtools-plugin', label: 'My Awesome Plugin', packageName: 'my-awesome-plugin', homepage: 'https://vuejs.org' }, api => { // Logic... }) ``` Every plugin is bound to a Vue application. You need to pass the user application to `setupDevtoolsPlugin` - the same that your plugin `install` method gets as the first argument. ```js{1,7} export function setupDevtools (app) { setupDevtoolsPlugin({ id: 'my-awesome-devtools-plugin', label: 'My Awesome Plugin', packageName: 'my-awesome-plugin', homepage: 'https://vuejs.org', app }, api => { // Logic... }) } ``` The second argument of `setupDevtoolsPlugin` is a callback which will get the Vue Devtools API as the first argument: ```js{5-7} export function setupDevtools (app) { setupDevtoolsPlugin({ id: 'my-awesome-devtools-plugin', // ... }, api => { // Use the API here... }) } ``` We can now import and use our `setupDevtools` function in our Vue plugin: ```js import { setupDevtools } from './devtools' export default { install(app, options = {}) { // Our Vue plugin logic setupDevtools(app) } } ``` ### Vue 2 In a Vue 2 app, you need to pass the root component instance as the `app` parameter: ```js import { setupDevtools } from './devtools' export default { install(Vue) { Vue.mixin({ beforeCreate() { if (this.$options.myPlugin) { setupDevtools(this) } } }) } } ``` In the user's app: ```js import Vue from 'vue' import App from './App.vue' import DevtoolsPlugin from './DevtoolsPlugin' Vue.use(DevtoolsPlugin) new Vue({ render: h => h(App), myPlugin: true, }).$mount('#app') ``` ### Plugin settings With the `settings` option, your plugin can expose some settings to the user. This can be very useful to allow some customization! All settings items must have the following properties: - `type` (see below) - `label`: a string to describe the settings item - `defaultValue` Available settings types: - `boolean` - `text` - `choice` - `options`: list of objects with type `{ value: any, label: string }` - `component`: (optional) can be either `'select'` (default) or `'button-group'` Example: ```js setupDevtoolsPlugin({ id: 'my-awesome-devtools-plugin', settings: { test1: { label: 'I like vue devtools', type: 'boolean', defaultValue: true }, test2: { label: 'Quick choice', type: 'choice', defaultValue: 'a', options: [ { value: 'a', label: 'A' }, { value: 'b', label: 'B' }, { value: 'c', label: 'C' } ], component: 'button-group' }, test3: { label: 'Long choice', type: 'choice', defaultValue: 'a', options: [ { value: 'a', label: 'A' }, { value: 'b', label: 'B' }, { value: 'c', label: 'C' }, { value: 'd', label: 'D' }, { value: 'e', label: 'E' } ] }, test4: { label: 'What is your name?', type: 'text', defaultValue: '' } }, }, (api) => { // Use `api.getSettings()` to get the current settings for the plugin console.log(api.getSettings()) }) ``` ![screenshot of the plugin settings](../assets/plugin-settings.png) You can listen for changes made to the settings by the user with the [`api.on.setPluginSettings`](./api-reference.md#on-setpluginsettings) hook: ```js api.on.setPluginSettings((payload) => { // Do something... }) ``` ### Tree-shaking for production As we are going to write code only for integrating for the Vue Devtools, it would be a good idea to strip it for the production versions of our package - thus improving size and performance. By default, Vue 3 doesn't include the devtools-related code in production. [It uses](https://github.com/vuejs/vue-next/tree/master/packages/vue#bundler-build-feature-flags) the `__VUE_PROD_DEVTOOLS__` environment variable as a compile flag to force enable this code. We can use the same one for the same purpose, and we can also check for `NODE_ENV` to automatically include the devtools plugin in development. ```js if (process.env.NODE_ENV === 'development' || __VUE_PROD_DEVTOOLS__) { setupDevtools(app) } ``` In your build setup you should replace `process.env.NODE_ENV` and `__VUE_PROD_DEVTOOLS__` for the builds of your library that don't target bundlers. See [Rollup setup](#rollup) that includes an example usage of `@rollup/plugin-replace`. ## Components The Components API allows you to: - Add tags to the components tree. - Display additional data in the component state inspector. ### Hooks The Vue Devtools API includes **Hooks** which are available *via* `api.on`. Hooks are useful to add new debugging information to existing elements, such as components tree nodes, component state, timeline events... A new callback for a specific hook can be registered with `api.on.hookName(callback)`. Every hook expects the callback function to have those same arguments: - `payload` which holds the state related to the hook. It can be modified to pass back additional information. - `context` exposing data about the devtools Example: ```js{6-8} export function setupDevtools (app) { setupDevtoolsPlugin({ id: 'my-awesome-devtools-plugin', // ... }, api => { api.on.hookNameHere((payload, context) => { // Do something... }) }) } ``` Every hook handle asynchronous callbacks: ```js{6-8} export function setupDevtools (app) { setupDevtoolsPlugin({ id: 'my-awesome-devtools-plugin', // ... }, api => { api.on.hookNameHere(async (payload, context) => { await something() }) }) } ``` All the registered callbacks will be called sequentially including the asynchronous ones, in the order they were registered on the page. ### Components tree To add tags to the components tree, we can use the [`visitComponentTree`](./api-reference.md#on-visitcomponenttree) hook: ```js{6-14} export function setupDevtools (app) { setupDevtoolsPlugin({ id: 'my-awesome-devtools-plugin', // ... }, api => { api.on.visitComponentTree((payload, context) => { const node = payload.treeNode if (payload.componentInstance.type.meow) { node.tags.push({ label: 'meow', textColor: 0x000000, backgroundColor: 0xff984f }) } }) }) } ``` This example will add a `meow` tag to any component that add the `meow: true` option to its definition: ```js export default { meow: true } ``` Here is an example result: ![screenshot with example](../assets/plugin-components-tags.png) ### Colors Colors in the Vue Devtools API are encoded as numbers instead of strings. You can use `0x` in JavaScript to write an hexadecimal value: ```js 0x000000 // black 0xFFFFFF // white 0xFF984F // orange 0x41B86A // green ``` Example: ```js{6-7} api.on.visitComponentTree((payload, context) => { const node = payload.treeNode if (payload.componentInstance.type.meow) { node.tags.push({ label: 'meow', textColor: 0x000000, backgroundColor: 0xff984f }) } }) ``` ### Notify a change By default the Vue Devtools will determine when to update the components tree (and thus calling the hook again). We can force a refresh with the [`api.notifyComponentUpdate`](./api-reference.md#notifycomponentupdate) function: ```js api.notifyComponentUpdate(componentInstance) ``` That way, if we know that the tags of a specific component should be changed, we can notify the Vue Devtools and then our hook callback will be called again. ### Component state Adding new data to the component state inspector can be achieved with the [`inspectComponent`](./api-reference.md#on-inspectcomponent) hook: ```js api.on.inspectComponent((payload, context) => { // ... }) ``` Each new field should be added to the `payload.instanceData.state` array: ```js{2-6,8-12} api.on.inspectComponent((payload, context) => { payload.instanceData.state.push({ type: 'My Awesome Plugin state', key: '$hello', value: data.message }) payload.instanceData.state.push({ type: 'My Awesome Plugin state', key: 'time counter', value: data.counter }) }) ``` Notice how we are passing some data to the Vue Devtools plugin from our library with the `data` argument of `setupDevtools` (see below). The `type` will be used to create a new section in the state inspector. It is recommended to use a variable: ```js{1,10,16} const stateType = 'My Awesome Plugin state' export function setupDevtools (app, data) { setupDevtoolsPlugin({ id: 'my-awesome-devtools-plugin', // ... }, api => { api.on.inspectComponent((payload, context) => { payload.instanceData.state.push({ type: stateType, key: '$hello', value: data.message }) payload.instanceData.state.push({ type: stateType, key: 'time counter', value: data.counter }) }) }) } ``` Then, use this variable to declare your data type in the Vue Devtools plugin options using `componentStateTypes`: ```js{7-9} export function setupDevtools (app) { setupDevtoolsPlugin({ id: 'my-awesome-devtools-plugin', label: 'My Awesome Plugin', packageName: 'my-awesome-plugin', homepage: 'https://vuejs.org', componentStateTypes: [ stateType ], app }, api => { // ... }) } ``` Here is an example result: ![screenshot with example](../assets/pluygin-components-state.png) You can again use `notifyComponentUpdate` to force a state refresh of the current component if you don't pass any argument: ```js api.notifyComponentUpdate() ``` ### Editing component state You can mark some of your state fields as `editable` to allow editing by the user: ```js payload.instanceData.state.push({ type: stateType, key: '$hello', value: data.message, editable: true }) ``` You can then handle the edition submit with the [`editComponentState`](./api-reference.md#on-editcomponentstate) hook: ```js api.on.editComponentState((payload) => { // ... }) ``` Then, you have to check if the field being edited is coming from your plugin with `payload.type`: ```js{2} api.on.editComponentState(payload => { if (payload.type === stateType) { // ... } }) ``` This way we don't do anything if the user is editing something else (like data properties). An edited field can be modified in the following ways: - assigning a new value, - adding a new property (object) or item (array), - removing a property or item. You can then use the `payload.set` helper function to apply the edition. The argument to `payload.set` should be the object holding our editable state: ```js{3} api.on.editComponentState(payload => { if (payload.type === stateType) { payload.set(data) } }) ``` In this example, we execute `payload.set(data)` because we are sending the `data.message` editable state: ```js{4} payload.instanceData.state.push({ type: stateType, key: '$hello', value: data.message, editable: true }) ``` You can also use a separate object to hold your editable state ([example](./api-reference.md#on-editcomponentstate)). ## Custom inspector The Vue Devtools only have one inspector by default: the Components inspector. Vue Devtools plugins can introduce new inspectors alongside it to display more debugging information. To setup a custom inspector, your plugin must: 1. Register the custom inspector with `addInspector`. 2. Handle the `getInspectorTree` hook to populate the inspector tree (on the left or top of the devtools). 3. Handle the `getInspectorState` hook to send state (on the right or bottom of the devtools). ### Register the custom inspector Let's start by regiustering our custom inspector with [`addInspector`](./api-reference.md#addinspector): ```js api.addInspector({ id: 'my-awesome-plugin', label: 'Awesome!', icon: 'pets', }) ``` It is recommended to use a variable to store the inspector id as it will be needed in the hooks later: ```js{1,9} const inspectorId = 'my-awesome-plugin' export function setupDevtools (app, data) { setupDevtoolsPlugin({ id: 'my-awesome-devtools-plugin', // ... }, api => { api.addInspector({ id: inspectorId, label: 'Awesome!', icon: 'pets', }) }) } ``` You can use any [Material Icon](https://fonts.google.com/icons) code for the icon, for example `note_add` or `drag_indicator`. Here is an example result: ![screenshot of custom inspector](../assets/plugin-custom-inspector-menu.png) ### Send inspector tree With the [`getInspectorTree`](./api-reference.md#on-getinspectortree), we can display a tree of nodes in our inspector: ```js api.on.getInspectorTree((payload, context) => { // ... }) ``` But before that, we need to check if hook is called for the correct inspector (ours): ```js{2} api.on.getInspectorTree((payload, context) => { if (payload.inspectorId === inspectorId) { // ... } }) ``` We can then put the nodes tree into `payload.rootNodes`: ```js{3-29} api.on.getInspectorTree((payload, context) => { if (payload.inspectorId === inspectorId) { payload.rootNodes = [ { id: 'root', label: 'Awesome root', children: [ { id: 'child-1', label: 'Child 1', tags: [ { label: 'awesome', textColor: 0xffffff, backgroundColor: 0x000000 } ] }, { id: 'child-2', label: 'Child 2' } ] }, { id: 'root2', label: 'Amazing root' } ] } }) ``` Here is an example result: ![screenshot of custom inspector](../assets/plugin-custom-inspector-tree.png) ### Send the inspector state Use the [`getInspectorState`](./api-reference.md#on-getinspectorstate) hook to send the state depending on the selected node (check `payload.nodeId`): ```js api.on.getInspectorState((payload, context) => { if (payload.inspectorId === inspectorId) { if (payload.nodeId === 'child-1') { payload.state = { 'my section': [ { key: 'cat', value: 'meow', } ] } } else if (payload.nodeId === 'child-2') { payload.state = { 'my section': [ { key: 'dog', value: 'waf', } ] } } } }) ``` Don't forget to check if the inspector is the right one with `payload.inspectorId`! Here is an example result when selecting "Child 1": ![screenshot of custom inspector](../assets/plugin-custom-inspector-state.png) ### Refresh the inspector The Vue Devtools will not automatically refresh your custom inspector. To send updates, you have two different available functions: - [`sendInspectorTree`](./api-reference.md#sendinspectortree) to refresh the nodes tree, - [`sendInspectorState`](./api-reference.md#sendinspectorstate) to refersh the state inspector. Example: ```js // Update tree api.sendInspectorTree(inspectorId) // Update state api.sendInspectorState(inspectorId) ``` Calling those functions will call the `getInspectorTree` and `getInspectorState` hooks respectively. ## Timeline The Vue Devtools comes with a few built-in layers on the Timeline, such as the Mouse & Keyboard events, the Component events and the Performance flamechart. You can add custom Timeline layers with the following steps: 1. Register the layer with `addTimelineLayer`. 2. Send events with `addTimelineEvent`. ### Register a layer Use the [`addTimelineLayer`](./api-reference.md#addtimelinelayer) function to register your custom Timeline layer: ```js{1,8-12} const timelineLayerId = 'my-awesome-plugin' export function setupDevtools (app, data) { setupDevtoolsPlugin({ id: 'my-awesome-devtools-plugin', // ... }, api => { api.addTimelineLayer({ id: timelineLayerId, color: 0xff984f, label: 'Awesome!' }) }) } ``` Here is an example result: ![screenshot of layer](../assets/plugin-timeline-layer.png) ### Send an event Adding new events is done with the [`addTimelineEvent`](./api-reference.md#addtimelineevent) function: ```js{2-11} window.addEventListener('click', event => { api.addTimelineEvent({ layerId: timelineLayerId, event: { time: api.now(), data: { mouseX: event.clientX, mouseY: event.clientY } } }) }) ``` You can set the `enableEarlyProxy` plugin option to `true` to be able to send timeline events before the Vue devtools are opened and connected: ```js{2-5} setupDevtoolsPlugin({ id: 'my-awesome-devtools-plugin', app, enableEarlyProxy: true }, api => { // `api` will be a proxy waiting for the real API to be available }) ``` ### Event group You can group events together. It will display them inside a pill-shaped rectangle, and will display the total duration of the group in the event inspector. Set the `groupId` option on the `event` with an identical value to create a group. Here is an example of a group with 3 events: ```js const groupId = 'group-1' devtoolsApi.addTimelineEvent({ layerId: timelineLayerId, event: { time: api.now(), data: { label: 'group test' }, title: 'group test', groupId } }) devtoolsApi.addTimelineEvent({ layerId: timelineLayerId, event: { time: api.now() + 10, data: { label: 'group test (event 2)', }, title: 'group test', groupId } }) devtoolsApi.addTimelineEvent({ layerId: timelineLayerId, event: { time: api.now() + 20, data: { label: 'group test (event 3)', }, title: 'group test', groupId } }) ``` Here is the result on the timeline with the first event of the group selected: ![screenshot of the timeline group](../assets/plugin-timeline-group.png) And the event inspector view with the "Group" tab: ![screenshot of the timline group inspector](../assets/plugin-timeline-group-state.png) Notice the "group info" section with additional information about the group. ### Events from the package Let's use the `addTimelineEvent` API to track an asynchronous method expose by our Vue plugin. We will call it `$doSomething`: ```js{5-15} import { setupDevtools } from './devtools' export default { install (app, options = {}) { app.mixin({ methods: { $doSomething () { return new Promise(resolve => { setTimeout(() => { resolve('done') }, 1000) }) } } }) if (process.env.NODE_ENV === 'development' || __VUE_PROD_DEVTOOLS__) { setupDevtools(app, data) } } } ``` We can now call this method from any component in the user application: ```js this.$doSomething() ``` We would like to track this method from our library code, so we need to return a set of helper functions from our `setupDevtools` function. The `devtoolsApi` variable will hold the Vue Devtools API and the `devtools` variable will be an object with helper functions, which we will return: ```js{2,4-6,12,17} export function setupDevtools (app, data) { let devtoolsApi const devtools = { // Our helpers here } setupDevtoolsPlugin({ id: 'my-awesome-devtools-plugin', // ... }, api => { devtoolsApi = api // ... }) return devtools } ``` Back in our library code, we can create a `devtools` variable to retrieve the result of `setupDevtools`, i.e. the object that will contain our helper functions: ```js{5,12} import { setupDevtools } from './devtools' export default { install (app, options = {}) { let devtools app.mixin({ // ... }) if (process.env.NODE_ENV === 'development' || __VUE_PROD_DEVTOOLS__) { devtools = setupDevtools(app) } } } ``` Let's implement a `trackStart` helper function to create one event when we want to start tracking a call, and another event when the call is finished: ```js{3,6-37} export function setupDevtools (app, data) { let devtoolsApi let trackId = 0 const devtools = { trackStart: (label: string) => { const groupId = 'track' + trackId++ // Start devtoolsApi.addTimelineEvent({ layerId: timelineLayerId, event: { time: api.now(), data: { label }, title: label, groupId } }) return () => { // End devtoolsApi.addTimelineEvent({ layerId: timelineLayerId, event: { time: api.now(), data: { label, done: true }, title: label, groupId } }) } } } setupDevtoolsPlugin({ id: 'my-awesome-devtools-plugin', // ... }, api => { devtoolsApi = api // ... }) return devtools } ``` The `trackId` is used to generate a unique group ID for each time we are going to call `startTrack`. By using the same ID, we will create a group with the two events, even if they are not both created at the same time. We can use this `trackStart` helper function like this: ```js const trackEnd = devtools.trackStart('some-label') // Later trackEnd() ``` We can now use this `trackStart` function in our library code: ```js{6,9} let devtools app.mixin({ methods: { $doSomething () { const trackEnd = devtools ? devtools.trackStart('$doSomething') : null return new Promise(resolve => { setTimeout(() => { if (trackEnd) trackEnd() resolve('done') }, 1000) }) } } }) if (process.env.NODE_ENV === 'development' || __VUE_PROD_DEVTOOLS__) { devtools = setupDevtools(app, data) } ``` ::: warning We must be careful of checking if our `devtools` variable is defined, because we strip the devtools code out in production! See [Tree-shaking for production](#tree-shaking-for-production). ::: Here is an example result: ![screenshot of the timeline](../assets/plugin-timeline-example1.png) And the event inspector: ![screenshot of the timeline](../assets/plugin-timeline-example2.png) --- Thank you for following the Vue Devtools Plugin development guide! You can find more detailed descriptions of the API in the [API Reference](./api-reference.md). Happy building! 😸