There are more demos in Slides.
The debugger requires runtime monkey patching and it isn't fully and properly done yet. So in big projects, it is quite unlikely everything works effortlessly (this, however, reduces debugging efforts).
Performance is obviously worse comparing to not instrumented code, especially when time-traveling is enabled.
Some libraries may depend on functions sources, this won't work here, it tries to keep at least parameters names though.
Install through VS Code Marketplace.
Or start VS Code Quick Open (Ctrl-P/Command-P), and enter:
By default it adds NodeJS debugging configuration, to add browser's debugger, press "Add Configuration..." button in the left bottom corner of
Here is an example from
After configured, start debugging (F5). The first run takes more time because it needs to install its runtime.
The common parameters in the configurations:
The debugger requires all the sources (including third parties from node_modules) to be transpiled. The configuration may be tricky but there are a few zero-config options available.
Other specific parameters:
To enable specify
Other specific parameters:
If Zero config options aren't enough it is possible to configure everything manually.
For example, Jest carefully prevents
Custom configuration can be used to improve modules loading performance. When NodeJS zero-config is used it transpile modules only when they are loaded, this may be annoying, so instead, we can have babel in a watch mode running separately, and loading already transpiled modules.
The code should be transpiled with babel's plugin is "@effectful/debugger/transform".
Other modern JS features should be trasnpiled. For example, for now, it supports only CommonJS modules, and it doesn't support rest/spread for objects and arrays. There are a few preset with a few such plugins, along with the debugger's plugin, already applied:
Another tricky thing is to make it work properly not only your sources should be transpile but all the dependencies (from node_modules), for the dependencies compilation pass
If it isn't possible to transpile, because, say, it is a native module, it is still possible to use the debugger, but this requires more hacking with its API.
The port number along with a few other options can be changed in require("@effectful/debugger/config"). It should be loaded and the options should be changed before the runtime is loaded, for example using some specific not-transpiled module.
The transpiled code calls debugger API functions. This API is installed separately from the plugin into the plugins directory. This may be inconvenient, but you can install it manually. However, the API dependencies should be hoisted in
Here are some of them:
The runtime package can be changed by specifying
If we need some own runtime which adjusts some things we can make a package which just re-exports modules from "@effectful/debugger" changing anything we need.
The debugger's API can be accessed by
The whole application state is stored in
The state can be accessed via "Debug Console" in VSCode, or from some dev scripts. This way may be used to mock functions or even a part of the functions or run some custom debugging scenarios.
We can capture and restore the whole application state.
Not everything is serializable by default, but everything can be made serializable by providing special handlers. Among the not serializable by default values, there are sockets, sessions auth tokens, native modules states. If there are no serialization handlers provided, they are serialized into values that are ignored in the restoring state. This is fine if we don't mean to resurrect the program and only want to replay the time-traveling trace.
By default it uses @effectful/serialization library, but any other can be used instead because all we need to store is
But any program still can be resurrected with additional efforts. We need to provide handlers which are either reconnect the socket, re-authenticate, restore the native modules states, etc. This way, we can even restore the whole state of multitier applications.
This can be done by specifying @effectful/serialization descriptor, either in code:
Or externally because it is easy to hot-mock functions and state, lie it for example done in "react" special import for storing
So to make react state to be fully serializable just add "runtime" in
Check @effectful/serialization for more details.
The state's saving is done by calling
For example for storing stage in localStorage:
This can be run from VSCode debug console or some script.
The reverse is done by
DOM is also serializable, for now, only events added with
There is an optional parameter to set serialization options from @effectful/serialization. For
If the custom configuration is used the
The time-traveling trace is stored in
For example, to disable traveling through the trace and just make the program to run from the current point, run
There are a lot of more advanced usages, for example, comparing different runs of the same code, for example, failed test run with some last successful run.
The journal object is stored and restored by
By default, it tracks only local variables, properties, and DOM changes. If something is changed in something external (e.g. DB, file, native module, etc) it won't be tracked and changed, though, we'll still be able to travel through the program except resetting won't work for DBs.
External states may be still tracked using special handlers. For example, when we change DB we just call
The debugger will load new sources when their file is saved. but will try to keep the old application state.
For now, the state merging isn't very efficient - variables are saved by their positions and the current execution position may be shifted into a wrong location. This should be improved in some future version. Anyway, for small changes, this is works well enough.
There is also an option to restart the process from some point after some heavy loading. This is especially useful when debugging node. We can skip long-running operations of process restart and modules loading.
To specify the way the debugger handles file changes set
To make it restart faster set
To restart from some later location (for example, after some heavy initialization), we can run something like this snippet in code (or even in a conditional breakpoint).
This snippet calls
Data breakpoints require time traveling to work. It can be disabled if not needed with
Interoperating with runtime/native modules
The compilation isn't always required, it is needed if not instrumented modules call JS functions and we want to trace through them. Even if the native third party still calls functions synchronously and we cannot polyfill its type-traveling trace will still be collected. So we'll be able to walk through it after.
DOM event handler is one of the examples of calling JS code from a native part (a browser). They are roughly monkey patched now in this library, but still, when we stop on breakpoint there, it will release the main JS thread, so DOM runtime thinks the event execution is finished and proceed with event propagation even if after the breakpoint there is
Calling JS asynchronously works fine and doesn't need a wrapper. Only synchronous code may not work because it needs thread blocking. For example, it cannot stop inside the event handler called by
Even now, even if our program's logic heavily depends on the propagation of events we can still use time-traveling, and debug after the event handling is finished.
For preventing Spectre attack Firefox disabled
Stepping into casts and other functions called implicitly by runtime synchronously isn't supported yet.
Function constructors, eval expression, and "vm" node module are supported. However
Not yet done
Distributed under the terms of The MIT License (MIT).