AddOns
TSWoW supports creating addons using the TypeScript programming language, and to write special message classes that allow high-level communication between addons and Live scripts. When an addon is built, the code is transpiled into lua and automatically installed at the configured client path. The purpose of this document is to explain how addon development works and the differences from normal addon development.
Please note: The sources TSWoW has used to generate type definitions come from very poorly structured web pages and may not be entirely accurate. An exact specification of the Blizzard API for 3.3.5 is very hard to come by, so we appreciate reports on any inaccuracies.
- While TSWoW itself is released under a GPLv3 license, all our client libraries are exceptions to this and instead released under MIT, meaning servers have no obligation to keep their addons open source. TSWoW is server-friendly, even with addons.
Files
A single module can contain at most a single addon, which is located at modulename/addons
. Users can generate this directory using the command create addon modulename
.
The entrypoint for all addons is the file addons/modulename-addon.ts
, but users may create any other amount typescript files under this directory and import them as you normally would in TypeScript.
Addons can also access files in the modulename/shared
directory. Since the shared
directory is also used by live scripts, the coding style here must be valid for both addons and live scripts.
Special Files
When an addon is generated, the following necessary files are automatically created in the new addons directory. Users should not remove these files.
-
global.d.ts
: Contains type definitions for all Blizzard functions and types accessible from lua. -
lib/Events.ts
: Contains TSWoWs custom addon event and communications systems. -
addon.ts
: Is always the first real entrypoint of your addon. It is the only transpiled file that is automatically executed.
TypeScript
TypeScript files are used just like lua files are in normal addon development, with the exception that it’s only addons/modulename-addon.ts
that is automatically executed.
For any other file to be executed, it must be imported by this file or any file that is itself imported.
The type declarations allow users to write code that’s usually very similar to the way normal lua addon development works:
// The Blizzard API is available globally without importing anything
let myframe = CreateFrame('Frame');
myframe.SetSize(10,10);
myframe.Show();
Event Registry
TSWoW has a special system for more easily handle events, which is very similar to how events are handled on LiveScripts.
import { Events } from "./events";
// All we need to set up an event listener is a frame with a unique name.
const frame = CreateFrame('Frame','UniqueName');
// We can now easily register an events using the "Events" interface.
Events.ChatInfo.OnChatMsgSay(frame,(text)=>{
console.log("A player said something:"+text);
});
// We can register listeners for multiple events.
Events.ChatInfo.OnChatMsgChannel(frame,(text,playername)=>{
console.log("Channel message: "+text);
});
// We can register multiple listeners to the same event.
Events.ChatInfo.OnChatMsgSay(frame,(text)=>{
console.log("Event 2:"+text);
});
Internally, the Events interface uses frame.SetScript('OnEvent',...)
to listen for events, meaning users cannot use both the Events API and manually apply an OnEvent listener to the same frame.
XML
XML works more or less identical to how they work in normal addon development, but the way they call scripts is slightly different:
addon/some-file.ts
export function ExampleFunction() {
console.log("Hello from ExampleFunction!");
}
shared/some-shared-file.ts
export function SharedFunction() {
console.log("Hello from SharedFunction!");
}
ui-file.xml
<Ui xmlns="http://www.blizzard.com/wow/ui/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.blizzard.com/wow/ui/ ..\FrameXML\UI.xsd">
<Frame name="MyAddon_Frame">
<Scripts>
<!-- We must "require" the library file. -->
<!-- This does not work for functions in "modulename-addon.ts" -->
<!-- This code itself is Lua, rather than TypeScript, even though it calls a TypeScript function. -->
<OnLoad>
require('TSAddons.modulename.addon.some-file').ExampleFunction()
require('TSAddons.modulename.shared.some-shared-file').SharedFunction()
</OnLoad>
</Scripts>
<Anchors>
<Anchor point="CENTER"/>
</Anchors>
</Frame>
</Ui>
Using Lua
It is possible to write lua instead of TypeScript directly into your AddOns. If no TypeScript/Lua interop is desired, this is as simple as just creating Lua files instead of TypeScript files and add them to a special file called after.toc
, that works just like a normal FrameXML or AddOn toc file.
Lua Modules (Calling Typescript from Lua / Calling Lua from TypeScript)
Normally in WoW AddOns, a .toc
file is used to specify what order lua files are loaded. AddOns written in TypeScript automatically generates this file, since they instead rely on es6-style imports just like a normal TypeScript project.
With raw lua files, we can use a special boilerplate syntax to register modules that allow us to both export and import modules, just like we would in a TypeScript file:
First, we create a simple TypeScript function:
tsfile.ts:
export function tsfunction() { console.log("Hello from tsfunction()"); }
Then, we create a lua function that will call it. To call TypeScript from lua, we must set it up with the tstl_register_module
boilerplate function.
luafile.lua:
-- This boilerplate function creates our module.
tstl_register_module(
-- This is the name of our module.
-- This should always be on the form "TSAddons.my-module.addon.path.to.my.module" (don't forget the ".addon." part)
"TSAddons.my-module.addon.luafile",
function()
local exports = {}
-- Here, we can import a TypeScript module file and use its functions.
-- Note how we must also prepend with "TSAddons.my-module.addon", just like when registering.
-- (again, don't forget the ".addon." part)
local tstest = require("TSAddons.my-module.addon.tsfile")
function exports.luafunction()
print("Hello from luafunction!")
-- Call the tsfunction we defined in tsfile.ts above
tstest.tsfunction()
end
return exports
end
)
Now, we can create a definition file corresponding to the lua file we just created to be able to access its functions from TypeScript again:
luafile.d.ts:
export declare function luafunction();
tsfile2.ts:
import { luafunction } from "./luafile"
luafunction();
Custom Load orders
As mentioned previously, when building TypeScript AddOns tswow automatically generates the .toc file to ensure all modules are included and always load before the modules entry point.
Sometimes, we might not want to use the module syntax, or for some other reason specify that some lua files should be loaded before others. For this, we can use any of three special .toc files, placed in the addon root directory of our module (same as the entrypoint):
beforelib.toc
: Files listed here are loaded before even library modules and any other modules normal addon files.before.toc
: Files listed here are loaded before any of the module files in this addon.after.toc
: Files listed here are loaded after any of the module files in this addon.
If you wish to use any functions from files in beforelib.toc
or before.toc
in your TypeScript files, you can declare them in a global.d.ts
placed in your modules addons folder.