wowlua:meta tells your code about the addon and the build it is part of:
the profile, the version, your own settings, and the game flavor. The
compiler knows these values, so it folds them into the output and removes
code that a build doesn't need.
Know which build you are in
addon holds the addon's details from wowlua.json. build describes the
current build.
import addon, build from "wowlua:meta"import event from "wowlua:events"import print as chat from "wowlua:log" @event("PLAYER_LOGIN")local function onLogin() chat(`{addon.title} {build.version} loaded ({build.profile} build)`)endThe values are folded in at compile time, so the game gets a plain string. From the dev build:
chat("MyAddon 0.1.0-dev+1100829 loaded (dev build)")addonhasname(the folder and TOC name),package,title,notes,version,authors,license,homepage,interface,flavors,savedVariables,savedVariablesPerCharacter,dependencies, andmetafor your ownaddon.metaentries.buildhasprofile("dev","release","test"or one of yours),debug,version,commit,dirty,time,timestamp,compiler,id,bundledandminify.- Everything is read-only (E2010), and a field that doesn't exist, such as
addon.titel, is a compile error (E2003). - Code that uses a value at run time, for example
ipairs(addon.authors), reads it from a small table in the build that holds only the values used that way:
for _, name in ipairs(__wl.meta.addon.authors) do print(name)endThe full list of fields is in Meta §6.1.
Code that only exists in dev builds
Put code you only want while developing in if build.debug then. In a build
where build.debug is false, the block is removed, and it isn't type-checked
either.
import addon, build from "wowlua:meta" if build.debug then print("Loaded", addon.name, addon.version)endThe dev build keeps the block:
do print("Loaded", "MyAddon", "0.1.0")endThe release build has nothing in its place.
This works for if/elseif chains whose conditions only read wowlua:meta
values, const constants and literals, combined with not, and, or,
==, ~=, arithmetic and number comparisons. A branch whose condition is
false is removed, a branch whose condition is true is kept on its own. For
whole functions there is
@debugOnly, and log.debug(…)
calls are removed with their arguments when the build's log level is above
debug.
Leave out a declaration or an import
@if(condition) removes a declaration when its condition is false: a
function, a class or one of its members, an enum, a local or const, a
type, an interface, or an import.
export function show(value: unknown) print("state:", value)endimport build from "wowlua:meta" @if(build.debug)import * as diagnostics from "/Diagnostics/panel" export class Bar value: number = 0 @if(build.debug) function dumpState() diagnostics.show(self.value) end function refresh() self.value += 1 endendThe condition of @if must be known at compile time (E1040). On an import it
may only use wowlua:meta values and literals, not constants. Code that is
kept and still uses something @if removed is an error that shows both
places. Here refresh calls diagnostics.show, in a release build:
error[E1020]: `diagnostics` was removed by `@if` for this build ┌─ src/bar.wowlua:16:9 │ 3 │ ╭ @if(build.debug) 4 │ │ import * as diagnostics from "/Diagnostics/panel" │ ╰─────────────────────────────────────────────────' removed here · 16 │ diagnostics.show(self.value) │ ^^^^^^^^^^^ used here
Profiles: dev, release, test and your own
A profile decides what goes into a build. The usual commands pick one for you:
wowlua devbuildsdev: one Lua file per source with its line numbers kept, every function profiled, the/wldev window, error capture anddebuglogging.wowlua build --releaseandwowlua publishbuildrelease: bundled into one file, comments and whitespace stripped,warnlogging and none of the dev tools.wowlua testbuildstest: assertions, every runtime check andtracelogging.
wowlua build and wowlua check use dev unless you pass --release or
--profile <name>.
Change a built-in profile, or add your own on top of one, under profiles in
wowlua.json. A profile of your own names the one it builds on with
inherits (E4315 without it), and lists only what differs. Here the release
leaves a developer folder out and minifies a step further, and beta is a
release that keeps info logging:
"profiles": { "release": { "minify": "compact", "exclude": ["src/Diagnostics/**"] }, "beta": { "inherits": "release", "log-level": "info" }}$ wowlua build --profile beta Finished MyAddon 0.1.0-alpha+1100829 (beta: standard) in 0.11s → dist/beta/MyAddon
In this build build.profile is "beta", and build.debug is false, taken
from release. Arrays such as exclude are inherited as a whole, so beta
leaves src/Diagnostics/ out too. Dev builds and tests keep it. Guard every
import of an excluded file with @if(build.debug), as above.
The keys of a profile are listed in the manifest reference; the dev tools they switch on are on Debugging helpers.
Your own settings per profile
Define values in a top-level config and override them in a profile:
"config": { "logChannel": "SYSTEM", "maxHistory": 50},"profiles": { "dev": { "config": { "maxHistory": 500 } }}In code, config has the type of what you wrote, here
{ logChannel: string, maxHistory: number }, and each value is folded in:
import config from "wowlua:meta" -- 500 in dev builds, 50 in all othersexport local history: string[] = {} export function remember(line: string) history[#history + 1] = line if #history > config.maxHistory then table.remove(history, 1) endendA profile can't change the type of a value, so the code checks the same in every build:
error[E4012]: profile `dev`: config key `maxHistory` is a string but the top-level `config` defines a number ┌─ wowlua.json:33:9 │ 33 │ "maxHistory": "lots" │ ^^^^^^^^^^^^ invalid config key
Optional features
Features are named switches in wowlua.json. Each one lists other features
it turns on, or dep:<name> to turn on an optional dependency. The features
in default, and those they include, are on. Feature names are kebab-case in
the manifest and camelCase in code. The manifest of this page, with the
settings and profiles from above:
{ "package": { "name": "my-addon", "version": "0.1.0", "wowlua": "^0.13" }, "addon": { "title": "MyAddon" }, "features": { "default": ["minimap"], "minimap": [], "experimental-ui": [] }, "config": { "logChannel": "SYSTEM", "maxHistory": 50 }, "profiles": { "dev": { "config": { "maxHistory": 500 } }, "release": { "minify": "compact", "exclude": ["src/Diagnostics/**"] }, "beta": { "inherits": "release", "log-level": "info" } }}import features from "wowlua:meta" @if(features.experimentalUi)import Bar from "/bar" if features.minimap then print("minimap button on")end if features.experimentalUi then Bar.new():refresh()endfeatures.minimap is true and features.experimentalUi is false, so the
build keeps the print and leaves out the import and the second if. A
library's features are switched on by the packages that depend on it, with
features in their dependency entry (see
Libraries and dependencies).
Versions that tell builds apart
addon.version is always package.version from wowlua.json.
build.version, which also goes into the TOC's ## Version:, says what kind
of build it is:
0.1.0: a release-type build of a commit taggedv0.1.0(or0.1.0). If the tag doesn't matchpackage.version, the build stops with E4319.0.1.0-alpha+1100829: a release-type build of any other commit, or with uncommitted changes.0.1.0-dev+1100829: a dev or test build.
Without git the +hash part is left out. A bug report that names
0.1.0-alpha+1100829 therefore points at one commit.
$ wowlua build --release Finished MyAddon 0.1.0-alpha+1100829 (release: standard) in 0.10s → dist/release/MyAddon $ git tag v0.1.0 $ wowlua build --release Finished MyAddon 0.1.0 (release: standard) in 0.10s → dist/release/MyAddon
Paths to your textures and sounds
asset(path) turns a path in your package into the full in-game path, and
checks that the file is one of the addon's assets (build.assets in
wowlua.json). The path is relative to the package root and uses /.
import asset from "wowlua:meta" local icon = UIParent:CreateTexture()icon:SetTexture(asset("media/icon.tga"))icon:SetTexture("Interface\\AddOns\\MyAddon\\media\\icon.tga")A file that isn't an asset, a typo in its name or a difference in case is error E1038 at compile time, instead of a green square in the game. The path is built from the addon's folder name, so it follows a rename.
The game you build for
A flavor is one of WoW's rulesets. The flavors your addon supports are the
keys of flavors in wowlua.json, and wowlua new --flavors fills it in.
Without flavors, an addon builds for standard only.
| Flavor | Also called | Game | Support |
|---|---|---|---|
camelot |
forever |
WoW Forever | Supported |
standard |
retail, mainline |
Retail | Coming soon |
vanilla |
classic-era |
Classic Era | Coming soon |
tbc |
anniversary |
TBC Anniversary | Coming soon |
mists |
MoP Classic | Coming soon |
A supported flavor runs in every part of wowlua: wowlua test, the emulated
client, the debugger and Blizzard's UI. A flavor coming soon is checked and
built like the others (types, TOCs, packages), but wowlua can't run it yet.
Wrath of the Lich King Classic and Cataclysm Classic are not flavors of
wowlua.
Each flavor brings its own typed WoW API, so your code
is checked against the functions that client has. The TOC's interface number
comes from the same place and is pinned in wowlua.lock.
flavor is the flavor being built, and addon.interface its interface
numbers. By default the compiler runs once per flavor, so flavor is a
constant and a branch for another flavor is removed. A removed branch isn't
checked either, so it may call an API the other flavor lacks:
import addon, flavor from "wowlua:meta" local frame = CreateFrame("Frame", "MyAddonFrame", UIParent) if flavor == "standard" then EditModeManagerFrame:RegisterSystemFrame(frame)else frame:SetMovable(true)end print(flavor, addon.interface[1])With "flavors": { "standard": {}, "vanilla": {} }, the build has one
copy of the file per flavor, and a single TOC loads the right one:
-- wowlua/flavors/standard/src/main.luado EditModeManagerFrame:RegisterSystemFrame(frame)end print("standard", 120100) -- wowlua/flavors/vanilla/src/main.luado frame:SetMovable(true)end print("vanilla", 11509)## Interface: 120100, 11509wowlua\flavors\standard\src\main.lua [AllowLoadGameType mainline]wowlua\flavors\vanilla\src\main.lua [AllowLoadGameType classic]With "build": { "flavor-mode": "shared" } the addon is compiled once for
all its flavors. Then flavor is read at run time, the code is checked
against the API all of them share, and a call into an API that only some
flavors have must be guarded with a flavor check or an existence check such
as if C_Garrison then (E2030 otherwise). See
Meta §6.3 and
§6.5.
Where to go next
- Debugging helpers: what dev builds measure, and
assertanddump. - Building and publishing: release builds, tags and publish targets.
- wowlua.json: every key of the manifest.