FrançaisPlayground

App modules

What an app provides is declared once, in src/app.config.ts. @fluixi/start registers it before either entry runs, so entry-client and entry-server stay free of setup and cannot disagree about it.

// src/app.config.ts
import { defineApp } from '@fluixi/start/app';
import { AuthModule } from './services/auth.js';

export default defineApp({ imports: [AuthModule] });

Without it, a root provider had to be registered in both entries. Forgetting the server one is silent: the server render throws, falls back to the client, and the page is only slower.

Modules

A module is a named list of what a feature needs:

import { defineModule } from '@fluixi/start/app';
import { injectionToken } from '@fluixi/start/di';

export const Clock = injectionToken<() => Date>('Clock');

export const ClockModule = defineModule({
  name: 'clock',
  providers: [{ provide: Clock, useValue: () => new Date() }],
});

export const ReportsModule = defineModule({
  name: 'reports',
  imports: [ClockModule],
  providers: [],
});

imports are registered first, and each module once, however many modules import it. A cycle is an error that names the modules in it. The app's own providers come last, so they override a module's. Everything lands in the root injector: one instance in the browser, one per request on the server (see Dependency injection). A module is not a scope. For services scoped to part of the page, call provide() in a component.

A package can ship a module, which is the point of them: authModule from @fluixi/oauth2 makes an app's auth service injectable in one line. See OAuth2.

The server half

src/app.config.server.ts is the same shape and is registered on the server alone:

// src/app.config.server.ts
import { defineApp } from '@fluixi/start/app';
import { authServer } from './services/auth.server.js';

export default defineApp({ imports: [authServer()] });

It is a separate file because of what a browser build does with a reference. Server code named from app.config.ts, even through a lazy import(), is emitted as a chunk in dist/client, published whether it ever runs or not. Only the server build imports this file.

A module can carry middleware. It runs before src/middleware.ts, modules in imports order. Middleware only runs on the server, so a module with middleware belongs here; listed in app.config.ts, it warns in development.

.server files

Any file named *.server.* is server only. The browser build fails if app code imports one, naming the file that did, and a lazy import() counts:

src/routes/login.tsx imports src/services/auth/verify.server.ts into the browser bundle.

Put a token verifier, a database client or anything reading a secret in a .server file, and the mistake of importing it into a page stops the build instead of shipping. A file whose first statement is "use server" is allowed: the browser gets calls to the server, not the code. Dependencies in node_modules are not checked.

Next: Environment variables.