On this page
Debugging TypeScript in Defold
Step through your TypeScript with breakpoints set directly in the .ts source, even though Defold runs the transpiled Lua. The toolchain already emits the load-bearing piece: every build writes a <name>.ts.script.map source map next to the chunk and appends a --# sourceMappingURL= trailer. The Local Lua Debugger extension reads that map, so a breakpoint in a .ts file resolves to the right generated line with no extra wiring.
What init scaffolds
Running bunx @defold-typescript/cli@latest init . sets up the whole debug path:
extensions.jsonrecommendstomblind.local-lua-debugger-vscode— the one required third-party extension for debugging. (Defold Kit is no longer recommended; sumneko Lua is an optional aid for reading generated Lua. See editor setup.)launch.jsonadds alua-localconfiguration named Defold: Debug (TypeScript) whoseprogram.commandisbun. It also setsscriptFiles(src/**/*.ts.script) andscriptRoots(.,src). Local Lua Debugger (>=0.3.0) pre-scansscriptFilesfor the emitted--# sourceMappingURL=trailers to resolve.tsbreakpoints ahead of time — without it, no source-mapped breakpoint binds — and usesscriptRootsto resolve the running Defold chunk path and the map's baresourcesentry back to files on disk.defold-debug.tsis a self-contained Bun launcher that downloads and runs a stockdmengine(or your native-extension build engine) with its stdio inherited — the pipe Local Lua Debugger attaches over.
The launcher is Bun, not a shell script. The upstream lua-local template runs a bash script and, on Windows, routes it through Git Bash. This toolchain already mandates Bun and targets Windows, so the launcher uses process.platform for the OS, fetch for the engine download, and Bun.spawn for the run — no bash, no Git Bash dependency. All .vscode debug files merge additively into any config you already have.
Automated setup
init scaffolds the .vscode debug files, but two steps touch your own project files and so are left to a dedicated command:
bunx @defold-typescript/cli setup-debug
setup-debug is idempotent and does three things init cannot:
- Adds the
lldebuggerlibrary dependency togame.project(at the next freedependencies#Nindex, skipped if already present). - Writes the ambient
src/lldebugger.debug.d.ts(the@noResolution declare module) alongside the entry-script edit, regenerated whole and skipped when already current. - Injects the gated
lldebugger.start()bootstrap into your entry script inside a managedBEGIN/ENDblock. A re-run refreshes the block if its wording drifted and is otherwise a no-op; a legacy single-marker block from an older version is upgraded in place. The block is kept in exactly one script: any stale managed block in anothersrc/script is stripped on each run.
It selects the entry script from the Defold boot path: starting at game.project's [bootstrap] main_collection, it walks the referenced .collection files (including nested collection: references) and collects every .ts.script component, mapping each back to its source .ts. A single boot-path script is wired automatically; with several, pass --script <path> to choose one or run interactively (without --json) to pick from a prompt, and --json errors naming the candidates. When the boot path reaches no .ts.script (or there is no [bootstrap]), it falls back to scanning src/**/*.ts for a lifecycle-factory call (defineScript/defineGuiScript/defineRenderScript). The plain output names the script added to, any scripts the block was removed from, and the boot-path trace behind the choice; --json emits a machine-readable {command, ok, written, actions, manualSteps, addedTo, removedFrom, bootPath} result. The same command is available as the defold-typescript:setup-debug mise task.
Two steps still require the Defold editor and VS Code and are reported as remaining manual steps:
- Install the Local Lua Debugger VS Code extension.
- Run Project -> Fetch Libraries so the
lldebuggermodule is downloaded.
The manual walkthrough below remains the fallback and documents exactly what setup-debug automates.
One-time setup
-
Install the recommended extension. Open the project in VS Code and accept the Local Lua Debugger recommendation (or install
tomblind.local-lua-debugger-vscodedirectly). -
Add the
lldebuggerlibrary to Defold. Ingame.project, add the dependency:https://github.com/defold-typescript/toolchain/releases/download/lldebugger-v1/lldebugger.zipThis is our vendored, MIT-licensed snapshot of
ts-defold/defold-lldebugger, hosted from this repo's releases — that is why the URL differs from the upstream docs. Then run Project -> Fetch Libraries in the Defold editor so thelldebuggerLua module is available torequire. -
Start the debugger from your entry script. First create an ambient declaration at
src/lldebugger.debug.d.tsso TypeScript and TypeScriptToLua (TSTL) both know the module without resolving it:/** @noResolution */ declare module "lldebugger.debug" { export function start(): void; }Then add the debugger entry near the top of your main script, inside the managed block, gated so it only runs in a debug build:
// defold-typescript:setup-debug BEGIN — managed block, do not edit import * as lldebugger from "lldebugger.debug"; if (sys.get_engine_info().is_debug) { lldebugger.start(); } // defold-typescript:setup-debug ENDThe
@noResolutionambient declaration must live in a.d.ts, not inline in a.ts— undermoduleResolution: "Bundler"an inlinedeclare moduleaugmentation that the entry script also imports fails to type-check. With the declaration in the ambient file, TypeScript leaves the module unresolved and TypeScriptToLua (TSTL) keeps the literal path, so the emitted Lua isrequire("lldebugger.debug")followed bylldebugger.start(). Theis_debugguard keeps the call inert in release builds, so the entry is safe to leave in shipped code — it only activates in a debug build with the debugger attached.setup-debugwrites both files for you; theBEGIN/ENDsentinels let a re-run refresh the block if its wording changes.
Launching a debug session
The launcher runs whatever already sits under build/; it does not compile the Defold project itself. The CLI build loop produces both artifacts headlessly — no editor required:
-
Transpile your TypeScript so the
.ts.scriptand.ts.script.mapfiles are current (bunx @defold-typescript/cli build, or keepwatchrunning). -
Compile the Defold project so
build/default/game.projectcexists:bunx @defold-typescript/cli defold resolve # first time / after editing dependencies bunx @defold-typescript/cli defold build # debug build into build/defaultdefold buildruns Defold's headlessbobtool — see Build for the JVM and cache details. Native-extension projects must add--build-server <url>sobobcan compile the engine remotely. -
In VS Code, select the Defold: Debug (TypeScript) launch configuration and start it (F5).
-
The Bun launcher resolves the engine, then runs
build/default/game.projectc. Set breakpoints in your.tsfiles; they resolve through the emitted<name>.ts.script.map.
The launcher prefers the native-extension build engine at build/<platform>/dmengine when it exists and otherwise downloads a stock engine from d.defold.com next to the launcher (.vscode/dmengine). The download is a one-time fetch per platform, and the scaffolded .gitignore keeps that binary out of version control.
Native-extension runtime libraries
On a build-engine run the launcher checks each supported native extension's declared runtime libraries and warns-and-continues if any are missing — it never fetches them, because no extension has a fetchable source yet. OpenAL on Windows is currently the only declared extension: native-extension builds need OpenAL32.dll and wrap_oal.dll placed by hand next to the build engine (in build/x86_64-win32/). The Defold build server does not ship these runtime DLLs and no Defold-hosted archive serves them, so the launcher cannot fetch them; the copy fix is tracked upstream at defold/defold#11860. When they are missing on a Windows build-engine run the launcher prints a one-line reminder and continues. macOS and Linux declare no native-extension runtime libraries and resolve OpenAL from the system, so nothing needs placing.
Building from the editor instead
The CLI build loop above is the primary path and needs no editor. If you prefer, you can still produce build/default/game.projectc (and any native-extension engine) by building from the Defold editor before launching from VS Code — the launcher runs whatever is already under build/ either way.
See also
- Script lifecycle — typing
selfand the lifecycle hooks you will step through. - Code editor setup — the rest of the scaffolded
.vscode/config and the watch loop.