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

Server-Only Markers

Audience: Core contributors, AI agents · Prereqs: Shared Shell Layer

Canonical source: src/shell/general/misc.js (prepareForClient)

The //(server-only-start) / //(server-only-end) comment convention - a real build-time code-stripping directive, not decoration.

What it does

From misc.prepareForClient (see Shared Shell Layer for the full four-step transform this is one part of):

file = file.replaceAll("\n//(server-only-start)", "\n/*(server-only-start)");
file = file.replaceAll("\n//(server-only-end)", "\n(server-only-end)*/");

Server-side, these are just two ordinary // line comments - inert, the code between them runs normally as part of the real ES module. Client-side, the build step turns them into the open and close of one real /* ... */ block comment - everything between them is deleted from the browser bundle.

When to use it

Anything in a src/shell/*.js file that would break once flattened into the browser's concatenated script:

  • Node-only imports - fs, path, child_process, native modules like @napi-rs/canvas.
  • Module-local variables that collide with a client-side global - the browser bundle already declares certain variables elsewhere in shellshock.min.js; a shared file re-declaring the same name at its own top level would collide once everything's flattened into one script scope.
  • Server-only logic embedded inside a function the client also uses - a block that only makes sense with isServer true, wrapped defensively even though the client could never reach it via the isServer check alone.
  • A export default a module-based Node consumer wants but the client bundle has no use for (the client doesn't have "the default export of this concatenated script" as a meaningful concept).

Real examples

plugins.js wraps its entire Node-only import block (fs, path, child_process, module) at the very top of the file:

//(server-only-start)
import fs from 'fs';
import path from 'path';
// ...
import { exec, execSync } from 'child_process';
// ...
//(server-only-end)

bullets.js wraps module-local variable declarations and a whole function, right after its own (always-present) imports:

//(server-only-start)
var room, Collider;
var tv1 = new BABYLON.Vector3;
var tv2 = new BABYLON.Vector3;

function checkExplosionCollisions (explosion) { /* ... */ };
//(server-only-end)

bullets.js also wraps a server-only block inside collidesWithPlayer (lines 197-223, a damage-calculation branch that only makes sense server-side) and a trailing export default { Bullet, Rocket, Grenade }; (lines 377-385) - three separate marker pairs in one file, each protecting a different kind of server-only content.

stringWidth.js wraps the @napi-rs/canvas import and canvas creation, since the browser has its own native Canvas and doesn't need (or have) the Node package.

collider.js wraps a small module-local variable declaration the same way bullets.js does.

Empty marker pairs are harmless, not a bug

A few files (catalog.js, comm.js, math.js, permissions.js, constants.js) have marker pairs with nothing between them - apparently boilerplate left in even where nothing ended up needing to be stripped. These are inert either side of the build - don't "clean them up" expecting to fix anything; they're not broken, just unused.

Common Issues

Something works server-side but throws in the browser console after a build. Check whether the failing code references something Node-only, or a variable name that collides with an existing browser global - if so, it needs wrapping in a marker pair (or the collision needs a different variable name if the code has to run client-side too).

I added a marker pair but the client build didn't change. Confirm you're actually looking at rebuilt output (store/client-modified/, only regenerated when the client server restarts - see Build Pipeline), not a stale bundle from before your edit.


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
Shared Shell Layer
Next
The ss Object