Four Script Loading Stages

Estimated reading time: 4 minutes

ModLoader provides four script stages, each running at a different point in startup with different data access and capabilities.

Stage Overview

Stageboot.json FieldWhen it RunsAsyncAccessible Data
inject_earlyscriptFileList_inject_earlyImmediately after Mod loadSync onlyRaw unmodified data
earlyloadscriptFileList_earlyloadAfter inject_earlyAsync OKRaw unmodified data
preloadscriptFileList_preloadAfter data merged into tw-storydataAsync OKMerged final data
Main scriptsscriptFileListMerged into gameWith SC2Runtime game data

inject_early

Field: scriptFileList_inject_early

Scripts are injected into HTML as <script> immediately after the Mod loads and run by the browser.

Characteristics:

  • Can call ModLoader APIs
  • Can read unmodified SC2 data (including raw Passages)
  • Synchronous only; async operations are not awaited
  • Use for Mod initialization (register Addon, set modRef, etc.)

Typical uses:

  • Register Addon plugin (registerAddonPlugin)
  • Register load control callbacks (canLoadThisMod)
  • Set modRef to expose API

earlyload

Field: scriptFileList_earlyload

Runs after all inject_early scripts for the current Mod are injected, via ModLoader.

Characteristics:

  • Can call ModLoader APIs
  • Supports async (remote data, etc.)
  • Can read unmodified SC2 data (raw Passages)
  • Uses JsPreloader.JsRunner() for execution
Script Format

JsPreloader.JsRunner() wraps code as (async () => { return ${jsCode} })(). Because it adds return before the first line, only the first line or an IIFE from the first line runs by JS semantics.

Recommended pattern:

(async () => {
  // Your earlyload code
  const modName = window.modUtils.getNowRunningModName();
  console.log(`${modName} earlyload`);
})();

Typical uses:

  • Async initialization (remote data, etc.)
  • Reading and analyzing raw game data
  • Decrypting and releasing lazily-loaded Mods

preload

Field: scriptFileList_preload

Runs after all Mod data (CSS/JS/Twee) is merged into tw-storydata, before the SC2 engine starts.

Characteristics:

  • Can call ModLoader APIs
  • Supports async
  • Can read post-merge SC2 data (merged final data)
  • Can dynamically modify and override Passage content
Script Format

Same as earlyload: preload scripts use JsPreloader.JsRunner() and follow the same format rules.

Typical uses:

  • Operations that need merged data
  • Dynamically modifying merged Passages
  • Final validation and adjustments

scriptFileList (Main Scripts)

Field: scriptFileList

Merged directly into tw-storydata as part of the game scripts and executed with the game when the SC2 engine runs.

Characteristics:

  • Behaves like base game JS
  • Runs via standard SC2 execution
  • Identical JS filenames across Mods have their contents concatenated

Typical uses:

  • Extending game logic
  • Adding new features
  • Macro definitions

Execution Order Summary

All of this happens during the loading screen; SC2 starts the game afterward:

1. [inject_early]  → Sync injection, initialization
2. [earlyload]     → Async run, read raw data
3. [Data merge]    → CSS/JS/Twee merged into tw-storydata
4. [preload]       → Async run, read merged data
5. [SC2 start]     → scriptFileList runs with game

After SC2 Starts

All script stages above complete during the loading screen. After SC2 starts, the engine emits jQuery events that Mods can listen for to react to game state. These events include:

EventWhen Fired
:storyreadyGame fully started
:passageinitNew Passage context initializing
:passagestartNew Passage starting to render
:passagerenderNew Passage render complete
:passagedisplayNew Passage ready to insert into HTML
:passageendNew Passage handling complete

See the API Overview for full details and more examples.

Listening Example

// One-shot
$(document).one(":storyready", () => {
  // Post-game-start logic
});

// Every passage
$(document).on(":passageend", () => {
  // Logic after each Passage render
});