Compilation pipeline
When you run a build, Comind transforms your TypeScript source into a compiled schema that the platform can execute. The pipeline has five stages: load, parse, bundle, compile, and deploy.
Pipeline overview
1. LOAD SOURCE
loadFileSystemAppCodeFiles(appName)
├── Read app's TS/TSX files from disk
├── Recursively load dependencies (base, plugins, parent)
└── Return virtual filesystem map: { path → source }
2. BUILD SCHEMA OBJECT
buildAppSchemaObject(appName)
├── Parse fields/index.ts → AppField[]
├── Parse actions/index.ts → AppAction[]
├── Parse views/layouts/*.tsx → AppLayout{}
├── Parse settings/index.ts → AppSettings
├── Compose from all dependency layers (base → plugins → app)
├── Apply app-mutator.ts transformations
└── Return: AppSchemaObject
3. BUNDLE CODE
For each entry point, run webpack with virtual FS (memfs):
├── fields/calc-fields/index.ts → bundled calc field code
├── actions/logic/index.ts → bundled action logic
├── views/layouts/*.tsx → bundled JSX→XML layouts
└── views/logic/index.ts → bundled UI hooks
4. COMPILE TO JS
buildAppSchemaCompiledJsCode(appSchemaObject)
├── Serialize options, fields, actions, layouts, settings
├── Create getter functions for each component
└── Output: apps["v2-app-id"] = { getFields(), getActions(), ... }
5. DEPLOY
comind.schema.deploy(compiledJsCode, workspaceAlias, position)
├── Backend registers the app in the workspace
├── Schema stored in MongoDB
└── Frontend loads schema on next page load
Stage details
1. Load source
The pipeline begins by reading the app's TypeScript and TSX files from disk. It then walks the dependency chain - base app, plugins, and parent apps - loading each layer recursively. The result is a virtual filesystem map that pairs every file path with its source content. See Package structure for how files are organized on disk.
2. Build schema object
The loader output feeds into the schema builder, which parses each well-known entry point into a typed structure:
fields/index.tsproduces the field definitionsactions/index.tsproduces the action definitionsviews/layouts/*.tsxproduces the layout descriptorssettings/index.tsproduces app-level settings
The builder composes these structures across all dependency layers, applying app-mutator.ts transformations last. The final AppSchemaObject contains everything the bundler needs.
3. Bundle code
Webpack processes each entry point using a virtual filesystem (memfs + unionfs), so no temporary files touch disk during compilation. The bundler lives in api/schema/bundler/ and relies on esbuild-loader for fast TypeScript transpilation.
Several custom webpack loaders handle platform-specific concerns:
cmw-import-remover.ts- strips server-only imports from client bundlescmw-module-scope-checker.ts- ensures code stays within allowed module boundariesmoment-alias.ts/lodash-alias.ts- aliases heavy libraries for smaller output
4. Compile to JavaScript
The compiler serializes the schema object into executable JavaScript. It creates getter functions for each component - fields, actions, layouts, settings - and wraps them in a single app registration call. The output looks like apps["v2-app-id"] = { getFields(), getActions(), ... }.
5. Deploy
The compiled code is sent to the backend via comind.schema.deploy(). The platform registers the app in the target workspace, stores the schema in MongoDB, and the frontend picks it up on the next page load. You can trigger a deploy from the CLI or through the builder server.
Local vs remote bundling
The bundler can run in two modes:
- Locally - webpack runs inside the CLI process on your machine
- Remotely - the CLI sends a POST request to the builder server at
http://localhost:3300/bundler
The builder server is a Koa HTTP service that exposes several endpoints beyond bundling:
| Endpoint | Purpose |
|---|---|
POST /bundler | Webpack bundling |
POST /app-builder | Full schema build |
POST /type-checker | TypeScript type checking |
POST /test-runner | Jest test execution |
POST /linter | ESLint linting |
POST /code-prettier | Prettier formatting |
Dual execution
The compiled output runs in two places:
- Browser - JavaScript executes natively for form rendering, field visibility, validation, and calc fields
- C# backend - a Jint interpreter (JavaScript for .NET) runs action logic, data manipulation, and server-side operations
This dual-execution model is why apps are written in TypeScript. The same code must work in both environments, so the pipeline produces plain JavaScript that both runtimes can consume. Keep this constraint in mind when writing action logic - avoid browser-only or Node-only APIs. See App lifecycle for how the compiled schema behaves at runtime.