LegacyShell WikiLegacyShell Wiki
Back to LegacyShell
Home
Wiki
Plugins
Docs
Back to LegacyShell
Home
Wiki
Plugins
Docs
  • Documentation

    • Getting Started

      • What is LegacyShell?
      • Speed Setup
      • Requirements
      • Installation
      • First Run
      • Config Files
      • Making an Account
      • Troubleshooting (Getting Started)
    • Running a Server

      • Architecture Overview
      • The Database
      • Users and Ranks
      • Adding Game Servers
      • Client Mirrors
      • Perpetual
      • Backups
      • Rate Limiting
      • Moderation
      • Closed Mode
      • Deployment
      • Troubleshooting (Running a Server)
      • Hosting for Someone Else's Instance
    • Content Creation

      • Maps
      • Dealing with Babylon Models
      • Map Blocks
      • Items and Skins
      • Hats and Stamps
      • Sounds
      • Gamemodes
      • Seasonal Events
    • Plugin Development

      • Quickstart
      • I Want To...
      • Anatomy of a Plugin
      • Lifecycle
      • Dependencies
      • Events (Concept)
      • Event Reference

        • services: events
        • game: events — shared logic (src/shell/)
        • game: events — main-thread server process
        • game: events — per-connection client object
        • game: events — room lifecycle & tick loop
        • game: events — in-browser gameplay
        • client: events — client server & build pipeline
      • Commands
      • Client-Side Code
      • Static Assets
      • Content Packs
      • Networking
      • Workers and State
      • Prediction and Authority
      • Recipes

        • Recipe: Killstreaks
        • Recipe: New Pickup Item
        • Recipe: New Gamemode
        • Recipe: Custom Weapon
        • Recipe: UI Modification
        • Recipe: Discord Integration
        • Recipe: Replacing Core Behaviour
        • Recipe: Persistent Plugin Storage
        • Recipe: Rewarding Players with Currency
        • Recipe: Custom Per-Player Data
        • Recipe: Custom Theme
      • Publishing
      • Pitfalls
      • Modifiers
      • Sound and Apollo
    • Codebase Reference

      • Repo Layout
      • Shared Shell Layer
      • Server-Only Markers
      • The ss Object
      • Build Pipeline
      • Stamps and Babylons
      • Game Loop
      • Rooms and Workers
      • Wire Protocol
      • Generated

        • Wire Protocol Opcodes
        • Enums & Lookup Tables
        • Database Schema
        • Config Reference
        • Slash Command Reference
      • Services Internals
      • Catalog and Items
      • Permissions Internals
      • Physics and Collision
      • Known Quirks
      • Codebase Anecdotes
      • Development Timeline
    • Contributing

      • Documentation Style Guide
      • Generators
      • For AI Agents

Build Pipeline

Audience: Core contributors, AI agents · Prereqs: Shared Shell Layer, Server-Only Markers

Canonical source: server-client/src/prepare-modified.js

How src/client-static/src/shellshock.min.js (the hand-maintained, not actually minified browser game source) becomes the real, served, minified bundle in store/client-modified/.

At a glance

Client server boot: Promise.all([...])modifyFiles()Pass one: replacementsBefore (~35)inlines #hashtag shared modules+ plugin code (pluginSourceInsertion)IIFE wrap (optional - config.client.iif)Minification (optional - config.client.minify)UglifyJS.minify()cancellable via minificationBeforePass two: replacementsAfterinlines items/maps JSON +babylon.js library + shadersprepareBabylons()merges per-item .babylonmodel files - see Stampsand Babylonsstore/client-modified/served by the client server's Express app

Runs on every client server boot

prepareModified() is called as part of server-client's own startup (alongside stampsGenerator.js and the wiki build - see Architecture Overview), running two things in parallel:

await Promise.all([
    prepareBabylons(path.join(ss.rootDir, 'server-client', 'store', 'client-modified', 'models')),
    modifyFiles(),
]);

prepareBabylons handles model merging - see Stamps and Babylons. modifyFiles() is the actual JS/HTML build step this page covers.

What modifyFiles() processes

Five source files get read and transformed, written into store/client-modified/: src/client-static/src/shellshock.min.js (the game itself), src/client-static/src/servers.js (server-list template), src/client-static/editor/js/mapEdit.js and editor/index.html (the map editor), and the root src/index.html.

The two-pass token replacement

The build works by literal string substitution against LEGACYSHELLXXX-style placeholder tokens baked into the source files, applied in two separate passes:

Pass one (replacementsBefore, ~35 entries) happens first, and is what actually inlines src/shell/* shared modules - each entry maps a token to a #hashtag:

{ pattern: /LEGACYSHELLPLUGINMANAGER/g, file: "#plugins" },
{ pattern: /LEGACYSHELLPICKUPS/g, file: "#items" },
{ pattern: /LEGACYSHELLCOMM/g, file: "#comm" },
// ...

For a file: entry, the actual inserted content is misc.hashtagToString(hashtag) - the shared module's live source, transformed by prepareForClient (see Server-Only Markers). This pass is also where plugin-injected client code lands, via the pluginSourceInsertion emit (see Client-Side Code) resolving the LEGACYSHELLPLUGINSBEFOREBEFORE/LEGACYSHELLPLUGINSBEFORE/LEGACYSHELLPLUGINSAFTER tokens. replacementsBefore itself is also a documented plugin extension point - a listener can push its own entries onto the array before this pass runs.

Pass two (replacementsAfter) happens near the end, after minification - it inlines the final items/maps JSON (now annotated with stamp grid coordinates by stampsGenerator.js, hence waiting until after that's done - see Stamps and Babylons), plus the raw Babylon.js library source and its GLSL shaders.

IIFE wrapping

If config.client.iif is true, the whole assembled script gets wrapped in an immediately-invoked function expression - the config comment calls this a mitigation against "console crackers" (people poking at global variables via the browser console). It hides top-level const/let/function declarations from becoming actual window globals, though the source is explicit that this is not remotely foolproof - it raises the bar for casual tampering, nothing more.

Minification

If config.client.minify is true, the assembled (and possibly IIFE-wrapped) script is run through UglifyJS.minify(...), with minificationBefore/minificationAfter/minificationSkipped plugin hooks around the step - minificationBefore is the one to cancel (plugins.cancel = true) if you want to substitute an entirely different minifier/obfuscation pipeline. See Events (concept).

Cache-busting hashes

A SHA-256 hash of the built servers.js (SERVERJSHASH) gets computed and embedded into index.html, and the hashes event fires twice - once early, once again after the final build pass - giving plugins two points to observe or extend the hash set used for cache-busting.

Common Issues

My change to a shared src/shell/* file isn't showing up in the browser. The client bundle only rebuilds when the client server (re)starts - there's no watch mode or hot reload for this pipeline. Restart npm run client after every change.

A plugin's client-injected code runs before something it depends on is defined. See Client-Side Code - beforebefore/before insertion points run before most src/shell/* globals exist in the concatenated script.


This page was drafted with AI assistance and reviewed for accuracy. If something looks wrong, please open a PR or flag it.

Edit this page on GitHub
Prev
The ss Object
Next
Stamps and Babylons