The real power of the transpiler is unlocked when combining it with declarations for your target environment. Declarations tell TypeScript which Lua API is available in your target context.
If you need tips or help writing declarations, feel free to join our Discord.
Declaration files end with the extension .d.ts. These contain pure ambient code.
For TypeScriptToLua, these files should contain information that describes the target Lua environment.
This means functions, modules, variables and other members of the target Lua environment are primarily described in these files.
They don't contain code that you would execute. Similar to how you'd write an interface in some other languages. TypeScriptToLua doesn't output any information from these files either.
You can write ambient declarations inside .ts files as well.
declare keyword is used to say that the following declaration defines something that exists within global scope. Like something within the
_G table in Lua.
This is useful for defining Lua's environment.
You can use
declare to write ambient declarations inside .ts files.
The export keyword indicates something is exported and can be used by external code.
This also includes ambient interfaces, types, modules and other items that don't result in any transpiled code.
If a file named lib.lua exists and returns a table with an
x field, you can write lib.d.t.s as follows to tell TypeScript that lib exists and what it provides.
If a namespace contains certain functions,
export tells TypeScript that those functions can be accessed within the namespace.
If a globally available module exists within the Lua environment. You can define what the module provides.
export keyword can be used in a
.d.ts file. It tells the transpiler and your editor (potentially) that something contains/provides something that you can either import (by using
import in TS or
require() in Lua) or access.
TypeScript has a hidden
this parameter attached to every function.
This causes TypeScriptToLua to treat every function as if
self exists as its first parameter.
This allows users to modify
But obviously Lua does not have a
self parameter for every function, so one of the three options must happen to tell TypeScriptToLua there is no "contextual parameter" (
this: voidas the first parameter of the function / method. This formally describes to TypeScript to not allow
thisto be modified inside this function. (you could also use the noImplicitThis option to disallow
thisto be modified if
thisis of an
@noSelfin the comments of the declaration's owner (the namespace, module, object, etc).
@noSelfInFileat the beginning of the file in a comment to make sure every function defined in this file does not use a "contextual parameter".
Below is three ways to make
table.remove not use a "contextual parameter".
By doing this, the transpiler also figures out if it needs to use
. when invoking a function / method.
If you're using an editor that seeks out information about functions, variables, etc. It will likely find the file where what it is analyzing is defined and check out the comment above it.
TypeScript uses TSDoc for its comments. TSDoc allows you to also use markdown in your comments! This means pictures, links, tables, code syntax highlighting and more markdown features are available. These may display differently depending on the editor in use.
Here are some commonly used TSDoc tags used in comments:
|Defines a parameter. e.g. A parameter for a function|
|Describes the return value of a function / method|
TypeScriptToLua takes this further. Some "tags" change how the transpiler translates certain pieces of code. These are referred to as annotations.
As an example,
@tupleReturn marks a function as something which returns multiple values instead of its array.
See Compiler Annotations page for more information.
By default, TypeScript includes global type declarations for both ECMAScript and web standards. TypeScriptToLua aims to support only standard ECMAScript feature set. To make TypeScript not suggest you to use unsupported browser builtins (including
setTimeout) you can specify a
It is also possible to use
noLib to remove every standard declaration (to use TypeScriptToLua only for syntactic features with Lua standard library) but TypeScript needs certain declarations to exist so they will have to be manually defined, so using
noLib is not recommended.
We recommend reading about Mapped and Conditional types. These things can be used as effective tools to describe some dynamic things that you may have in Lua.
Some examples of declaration merging have been shown in the above examples.
Some tables can use
__call to make themselves callable. Busted (the Lua testing suite) does this to
Because Lua doesn't have a strictly defined concept of a class, for TypeScriptToLua
class declaration implies a very specific structure, built specifically for TypeScript compatibility. Because of that, usually you shouldn't use
declare class for values coming from Lua.
Most of Lua patterns used to simulate classes can be declared using interfaces instead.
Example 1: a table with a static
new method to construct new instances
Example 2: a callable table with extra static methods
You may have to use the
@noResolution annotation to tell TypeScriptToLua to not try any path resolution methods when the specified module is imported.
Module declarations need to be kept in .d.ts files.
Unions can be used to tell TypeScript that a given type could be one of many other types. TypeScript can then pick up hints in the code to figure out what that type is at a given statement.
String and number values can be used as types too. In combination with union types it can be used to represent a known set of values.
Some functions in Lua can have names that are keywords in TypeScript (e.g.,
The parent to these kinds of functions will need to be represented as a JSON object.
Lua supports overloading of mathematical operators such as
*. This is performed using the metatable methods
__unm. Since TypeScript does not support operator overloading in its type system, this feature is hard to replicate. Unfortunately, this is not something that can be fixed properly right now without forking off our custom TypeScript version.
However, there are two possible workarounds. The first one is to declare a type as an intersection type with
number. It will then inherit all mathematical operators. Keep in mind that this is only partially type safe and may require some additional casting.
The second option was added in version 0.38.0. You can now use language extensions that allow declaring special functions which will transpile to operators. This will be completely type safe if the operators are declared correctly. See Operator Map Types for more information.
import can be important for making sure an index.d.ts file contains all the declarations needed.
It is also possible to place
import statements inside ambient modules and namespaces.
It is possible to publish a list of declarations for other users to easily download via npm.
Then the user can install this package using:
And link it to a tsconfig.json file.
If you have TypeScript installed, you can use the command below to list all files a tsconfig.json file targets.
This only works with TypeScript (tsc). TypeScriptToLua (tstl) may have support for this in the future.
Every TypeScript project points to a list of declarations. TypeScript is very generous with what files that includes.