Skip to content

Client Loader

Language: JavaScript

The loader.js at src/client/loader.js is the bootstrap and dependency-injection root of the client. It builds the Lib container, wires helper modules, and provides the single point where cross-cutting dependencies enter the React tree. The pattern mirrors the server loader: every framework module is a factory, Lib is memoized, and nothing outside the loader reads environment configuration.

On This Page


What the Loader Builds

The loader runs once. It performs three tasks in order:

  1. Load static config from config.js and merge environment overrides
  2. Build Lib - attach helper modules, theme engine, font manifest, SDK, and React itself
  3. Return Lib and Config to the React tree via a context provider

After the loader returns, the rest of the client treats Lib as a read-only registry. No other file instantiates helper modules or reads configuration directly.


The Lib Container

The Lib container holds every dependency the React tree needs. Each entry is either a helper module loaded via its factory or a plain data object.

Lib keyWhat it holdsHow it enters
Lib.ReactThe React modulerequire('react') in the loader only
Lib.UtilsCore utility helperrequire('@superloomdev/js-helper-utils')(Lib)
Lib.DebugDebug logging helperrequire('@superloomdev/js-helper-debug')(Lib)
Lib.StylerTheme engine (assemble, derive, generateUtilities)require('@superloomdev/js-client-helper-styler')(Lib)
Lib.ThemeTemplateDefault styler template (data)require('@superloomdev/js-client-helper-styler/styler.template.js')
Lib.ThemesTheme values as frozen JS objectsDirect require of theme data files
Lib.FontsFont manifest and useFontsReady hookrequire('../fonts/fonts')(Lib)
Lib.SdkClient SDK (entity APIs)require('../../sdk')(Lib)

Every framework module follows the loader pattern: module.exports = function (shared_libs, config) { ... }. The loader calls each factory with Lib, and the factory returns its public interface. This is identical to how server-side helper modules work.

Themes are plain frozen data objects, not loaders. They are required directly in the loader and attached to Lib.Themes. A theme is a JSON-shaped value object (base + optional variant); it has no behavior and no dependencies.


React Boundary Rule

Dependency injection applies at the package boundary, not inside the app's own React files. The rule:

  • Helper modules and framework packages receive dependencies through Lib. They never call require('react') directly. The loader injects Lib.React into the styler adapter, for example
  • The app's own React files (screens, layouts, context providers) keep idiomatic require('react') or import statements. JSX and hooks are import-time bindings; injecting React into every component adds ceremony without benefit

The boundary is the package edge. Inside the app, React is a peer dependency resolved normally. Outside the app (in published helper modules and the component library), React enters through Lib.

This keeps helper modules testable in isolation (inject a mock Lib.React) while keeping app code ergonomic.


Folder Conventions

Two folders organize React context and theme data inside src/client/:

FolderHoldsConvention
contexts/React context objects and hook definitionsLibContext (provides Lib), ThemeContext (provides theme + controller)
providers/Provider components, if they grow complex enough to separate from the context fileReserved. Simple providers stay in contexts/

Both folders use plural names, matching React community convention. A generic context/ folder is avoided because it could be confused with non-React context code.

Theme data files live in themes/ as frozen JS objects. The loader is the single source of truth for which themes are wired:

js
Lib.Themes = {
  base:  require('../themes/base-theme'),
  tasks: require('../themes/tasks-theme'),
  notes: require('../themes/notes-theme')
};

Font manifest lives in fonts/ as a loader module receiving Lib.ExpoFont. The separation of theme data (names font families) from font manifest (loads font files) is deliberate: require('font.ttf') is bundler-bound, and a server-sent theme JSON cannot carry binaries. See Fonts.


Boot Chain

The entry chain from package.json to first render:

text
package.json "main": "expo-router/entry"
  |
  v  Expo Router scans app/ for routes
src/client/app/_layout.js          ← first app code to run (root layout)
  |
  v  mounts LibProvider
src/client/contexts/lib-context.js ← provides Lib via React context
  |
  v  calls loader() (memoized singleton)
src/client/loader.js               ← builds Lib + Config
  |
  v  mounts ThemeProvider
src/client/contexts/theme-context.js ← calls Lib.Styler.assemble(), provides theme
  |
  v  calls combineComponent()
src/components/index.js            ← builds themed component library
  |
  v
src/client/app/index.js            ← re-exports screen from src/screens/

_layout.js is the boot file. The loader is the DI root. Everything else is wired through Lib. The chain is linear: each step depends only on what precedes it.

The loader is memoized. Calling loader() a second time returns the same Lib instance. This lets context providers and test harnesses call loader() without rebuilding the container.


Further Reading

  • Client Architecture - Stack decision, project layout, bundler-agnostic rule
  • Theming - The styler pipeline and runtime re-theming
  • Fonts - Font delivery mechanisms and the theme-names/host-loads contract
  • Server Loader - The server-side counterpart (same pattern, different dependencies)

Released under the MIT License.