Skip to main content

ForceCompile.ts

ForceCompile.ts ensures that classes resolved dynamically at runtime (by namespace string) are included in the compiled bundle, even when no direct import chain exists.

Why It Exists​

TypeScript's tree-shaking removes unused code. But Oidis uses dynamic resolution for:

  • LCP/RPC — Backend classes called by namespace string from the frontend
  • Resolvers — Controllers matched by URL path at runtime
  • Connectors — Mapped via ConnectorsRegister symbol mappings

Without ForceCompile, these classes would be eliminated during compilation because no other code directly imports them.

Structure​

Each side of the application has its own ForceCompile.ts:

source/typescript/Io/Oidis/YourProject/
├── ForceCompile.ts # Backend
└── Gui/
└── ForceCompile.ts # Frontend

File Format​

/*! License header... */

// Manual imports — maintained by developer
import "./Utils/AuthManager.js";

// generated-code-start
import "./Connectors/InventoryConnector.js";
import "./HttpProcessor/Resolvers/ReportResolver.js";
import "./Utils/InventoryManager.js";
// generated-code-end

The Generated Section​

The section between generated-code-start and generated-code-end markers is managed entirely by the Oidis Builder:

// generated-code-start
// ... Builder writes here
// generated-code-end
Never edit between markers

Manual changes between generated-code-start and generated-code-end will be overwritten on the next build. The Builder regenerates this section by scanning the project sources for decorators that register at load time.

The Builder writes side-effect imports (import "./Path/To/File.js";) — loading the module runs its decorators, which register the connector methods, resolvers and routes. See When Classes Are Auto-Added.

Manual Section​

The top of the file (before the generated section) is for manual imports maintained by the developer. These are typically:

  • External integration connectors
  • Classes the Builder can't auto-detect
  • Explicit registrations added during development
// Manual — developer maintains these
import { AuthManager } from "./Utils/AuthManager.js";
import { CustomExternalService } from "./Connectors/CustomExternalService.js";

In practice, most entries end up in the auto-generated section. The manual section is a safety net for edge cases.

Do not use ForceCompile as a class registry

ForceCompile is not meant to be a comprehensive list of all project classes. Only add classes here if they genuinely have no import chain from any compiled entry point (controllers, resolvers, loaders).

Do NOT add manually if the class is:

  • Imported by a panel, controller, or any other compiled file (the compiler finds it through the import chain)
  • A GUI model imported by a table, panel, or controller (e.g. Company.ts imported by RegistryPanelController)
  • A converter referenced in @Property({ converter: ... }) of a compiled model
  • A validator referenced in @Property({ validator: ... }) of a compiled model
  • A binding or UserControl imported by a panel or controller

Do NOT add manually either a file that contains @Extern(, @Resolver( or @Route( — the Builder imports it automatically (see below).

DO add manually only if the class is:

  • A runtime-bootstrapped Loader (e.g. Gui/Loader.js) not imported by any other file
  • A backend model used by the auth/session system but not directly imported by project code

When Classes Are Auto-Added​

Every build that compiles TypeScript (oidis build dev, prod, eap) runs the typescript-reference:source step (io-oidis-builder Tasks/TypeScript/TypeScriptReference.ts). For ES-module projects it:

  1. Reads every project source file (source/**/*.{ts,tsx} without *.d.ts, dependencies/ and ForceCompile.ts itself)
  2. Selects files whose text contains @Extern(, @Resolver( or @Route( — and skips files containing @Partial() (see @Partial)
  3. Adds import "./<relative path>.js"; to the generated block for each selected file that is not already mentioned anywhere else in ForceCompile.ts, and rewrites the block only when its content changed

A new connector with @Extern methods, a @Resolver class or a @Route handler therefore needs no manual import — the next build adds it. The detection is a plain text match, so the decorator must appear literally in that file; a class that only inherits decorated methods from a base class in another file is not detected. Without any ForceCompile.ts under source/typescript/ the step only logs a warning, and when a repository has more than one, all imports go into the first file the search returns.

Both Sides Have ForceCompile​

Backend ForceCompile.ts — Ensures backend connector classes and service managers are compiled.

Frontend Gui/ForceCompile.ts — Ensures frontend connectors, page controllers, and panel controllers are compiled.

Checklist​

  • Add manual imports above the generated section if the Builder doesn't auto-detect your class
  • Never edit between generated-code-start and generated-code-end
  • Run oidis build dev (or any other compiling build) to regenerate the auto section
  • Check for duplicate imports (they can sneak in — remove duplicates from the manual section if the Builder also generates them)

GUI Loader Import​

If your project depends on a package that provides its own GUI Loader (e.g., io-oidis-localhost), you must:

  1. Set guiLoaderClass in package.conf.jsonp:

    "guiLoaderClass": "Io.Oidis.Localhost.Gui.Loader"

    Use the namespace of the GUI Loader class from your dependency. This tells the Builder which class to use as the GUI entry point, enabling the correct module wrapping format.

  2. Import the Loader in Gui/ForceCompile.ts:

    // Manual section — above generated-code-start
    import "@io-oidis-localhost/Io/Oidis/Localhost/Gui/Loader.js";

    Without this import, the Loader class is tree-shaken out of the bundle and the frontend cannot initialize.

warning

Omitting either step causes a build error. Without guiLoaderClass, the Builder wraps the frontend in IIFE format which fails on top-level await. Without the import, the Loader is removed by tree-shaking and the frontend won't start.

See also: Troubleshooting | Configuration


See also: Architecture Overview | Development Markers | LCP & RPC