|
| 1 | +# Build Constraints and Transformations |
| 2 | + |
| 3 | +This document explains the comprehensive build constraints, transformations, and code management strategies in Glimmer VM. It serves as a reference for understanding how code is transformed from development to production and as a starting point for further analysis of the build system. |
| 4 | + |
| 5 | +## Overview |
| 6 | + |
| 7 | +Glimmer VM uses several categories of code that have different constraints on where they can appear: |
| 8 | + |
| 9 | +1. **Production Code** - Ships to end users in production builds |
| 10 | +2. **Development Code** - Available in development builds for end users |
| 11 | +3. **Local Development Code** - Only for Glimmer VM developers, never ships |
| 12 | +4. **Build-Time Code** - Used during compilation but not at runtime |
| 13 | + |
| 14 | +## Code Categories and Constraints |
| 15 | + |
| 16 | +### 1. import.meta.env |
| 17 | + |
| 18 | +**What it is**: A de facto standard created by Vite for build-time environment variables. |
| 19 | + |
| 20 | +**Usage in Glimmer VM**: |
| 21 | +- `import.meta.env.DEV` - `true` in development builds, `false` in production |
| 22 | +- `import.meta.env.PROD` - `true` in production builds, `false` in development |
| 23 | +- `import.meta.env.VM_LOCAL_DEV` - `false` in published builds, `true` in Vite dev server |
| 24 | + |
| 25 | +**Constraint**: These references are replaced at build time with actual values. The string `import.meta.env` never appears in published builds. |
| 26 | + |
| 27 | +### 2. VM_LOCAL Flag |
| 28 | + |
| 29 | +**What it is**: A build-time flag for code that should only run during local Glimmer VM development. |
| 30 | + |
| 31 | +**Purpose**: Enables expensive debugging features when working on the VM itself. These features never reach published packages (not even development builds). |
| 32 | + |
| 33 | +**Example Usage**: |
| 34 | +```typescript |
| 35 | +if (VM_LOCAL) { |
| 36 | + // Expensive validation that helps VM developers |
| 37 | + validateOpcodeSequence(opcodes); |
| 38 | +} |
| 39 | +``` |
| 40 | + |
| 41 | +**Constraint**: Code blocks guarded by `VM_LOCAL` are completely removed from all published builds. The condition and its contents are stripped out. |
| 42 | + |
| 43 | +### 3. Debug Assertion Functions |
| 44 | + |
| 45 | +**What they are**: Runtime type checking and validation functions from `@glimmer/debug`: |
| 46 | + |
| 47 | +- `check(value, checker)` - Validates a value against a type checker |
| 48 | +- `expect(value, message)` - Asserts a condition is truthy |
| 49 | +- `localAssert(condition, message)` - Development-only assertion |
| 50 | +- `unwrap(value)` - Unwraps optional values, throwing if null/undefined |
| 51 | + |
| 52 | +**Purpose**: Catch bugs during Glimmer VM development by validating assumptions about types and state. |
| 53 | + |
| 54 | +**Example Usage**: |
| 55 | +```typescript |
| 56 | +import { check } from '@glimmer/debug'; |
| 57 | +import { CheckReference } from './-debug-strip'; |
| 58 | + |
| 59 | +let definition = check(stack.pop(), CheckReference); |
| 60 | +let capturedArgs = check(stack.pop(), CheckCapturedArguments); |
| 61 | +``` |
| 62 | + |
| 63 | +**Constraint**: These function calls are stripped from ALL published builds (both development and production) using a Babel plugin during the build process. |
| 64 | + |
| 65 | +### 4. Type Checker Functions |
| 66 | + |
| 67 | +**What they are**: Functions that create runtime type validators: |
| 68 | + |
| 69 | +- `CheckInterface` - Validates object shape |
| 70 | +- `CheckOr` - Union type validation |
| 71 | +- `CheckFunction` - Function type validation |
| 72 | +- `CheckObject` - Object/WeakMap key validation |
| 73 | + |
| 74 | +**Purpose**: Define the type constraints used by `check()` calls. |
| 75 | + |
| 76 | +**Example Usage**: |
| 77 | +```typescript |
| 78 | +export const CheckReference: Checker<Reference> = CheckInterface({ |
| 79 | + [REFERENCE]: CheckFunction, |
| 80 | +}); |
| 81 | + |
| 82 | +export const CheckArguments = CheckOr(CheckObject, CheckFunction); |
| 83 | +``` |
| 84 | + |
| 85 | +**Constraint**: These should never appear in published builds as they're only used by the stripped `check()` calls. |
| 86 | + |
| 87 | +### 5. Debug-Only Packages |
| 88 | + |
| 89 | +Three private packages contain development-only utilities: |
| 90 | + |
| 91 | +- **@glimmer/debug** - Type checkers, validation utilities, debugging tools |
| 92 | +- **@glimmer/constants** - VM opcodes, DOM constants (inlined during build) |
| 93 | +- **@glimmer/debug-util** - Debug assertions, platform-specific logging |
| 94 | + |
| 95 | +**Constraint**: These packages are never published to npm. Import statements for them should never appear in published builds - their contents are either inlined or stripped during compilation. |
| 96 | + |
| 97 | +## Build Process and Transformations |
| 98 | + |
| 99 | +### Debug Code Stripping |
| 100 | + |
| 101 | +The build process uses a Babel plugin (`@glimmer/local-debug-babel-plugin`) that: |
| 102 | + |
| 103 | +1. Identifies imports from `@glimmer/debug` |
| 104 | +2. Tracks which debug functions are imported |
| 105 | +3. Strips or transforms the function calls: |
| 106 | + - `check(value, checker)` → `value` |
| 107 | + - `expect(...)` → removed entirely |
| 108 | + - `CheckInterface(...)` → `() => true` |
| 109 | + - `recordStackSize()` → removed entirely |
| 110 | + |
| 111 | +### Environment Variable Replacements |
| 112 | + |
| 113 | +The Rollup replace plugin performs these build-time replacements: |
| 114 | + |
| 115 | +**Production builds:** |
| 116 | +- `import.meta.env.MODE` → `"production"` |
| 117 | +- `import.meta.env.DEV` → `false` |
| 118 | +- `import.meta.env.PROD` → `true` |
| 119 | +- `import.meta.env.VM_LOCAL_DEV` → `false` |
| 120 | + |
| 121 | +**Development builds:** |
| 122 | +- `import.meta.env.MODE` → `"development"` |
| 123 | +- `import.meta.env.DEV` → `DEBUG` (with `import { DEBUG } from '@glimmer/env'` injected) |
| 124 | +- `import.meta.env.PROD` → `!DEBUG` |
| 125 | +- `import.meta.env.VM_LOCAL_DEV` → `false` (becomes `true` only in Vite dev server) |
| 126 | + |
| 127 | +### Module Resolution and Bundling |
| 128 | + |
| 129 | +The build system has specific rules for what gets inlined vs treated as external: |
| 130 | + |
| 131 | +**Always Inlined:** |
| 132 | +- `@glimmer/local-debug-flags` |
| 133 | +- `@glimmer/constants` |
| 134 | +- `@glimmer/debug` |
| 135 | +- `@glimmer/debug-util` |
| 136 | +- Relative imports (`.`, `/`, `#`) |
| 137 | +- TypeScript helper library (`tslib`) |
| 138 | + |
| 139 | +**Always External:** |
| 140 | +- `@handlebars/parser` |
| 141 | +- `simple-html-tokenizer` |
| 142 | +- `babel-plugin-debug-macros` |
| 143 | +- Other `@glimmer/*` packages (to avoid duplication) |
| 144 | +- `@simple-dom/*` packages |
| 145 | +- `@babel/*` packages |
| 146 | +- Node.js built-ins (`node:*`) |
| 147 | + |
| 148 | +### Build Output Structure |
| 149 | + |
| 150 | +Every package produces multiple build artifacts: |
| 151 | + |
| 152 | +1. **Development Build** (`dist/dev/`) |
| 153 | + - Readable, formatted code |
| 154 | + - Preserves comments |
| 155 | + - No variable name mangling |
| 156 | + - Includes source maps |
| 157 | + |
| 158 | +2. **Production Build** (`dist/prod/`) |
| 159 | + - Minified with Terser (3 passes) |
| 160 | + - Aggressive optimizations |
| 161 | + - Preserves `debugger` statements (for `{{debugger}}` helper) |
| 162 | + - Includes source maps |
| 163 | + |
| 164 | +3. **Type Definitions** (`dist/{dev,prod}/*.d.ts`) |
| 165 | + - Generated from TypeScript source |
| 166 | + - Rolled up into single files per entry point |
| 167 | + |
| 168 | +4. **CommonJS Build** (optional, `*.cjs`) |
| 169 | + - Only generated if package.json includes CommonJS exports |
| 170 | + - Follows same dev/prod split |
| 171 | + |
| 172 | +## TypeScript Configuration and Strictness |
| 173 | + |
| 174 | +Glimmer VM uses a multi-tiered TypeScript configuration system: |
| 175 | + |
| 176 | +### Configuration Files |
| 177 | +- `tsconfig.base.json` - Shared base configuration |
| 178 | +- `tsconfig.json` - Development configuration (looser for better DX) |
| 179 | +- `tsconfig.dist.json` - Distribution configuration (stricter for published code) |
| 180 | + |
| 181 | +### Per-Package Strictness Levels |
| 182 | + |
| 183 | +Packages can declare their strictness level in `package.json`: |
| 184 | +```json |
| 185 | +{ |
| 186 | + "repo-meta": { |
| 187 | + "strictness": "strict" | "loose" |
| 188 | + } |
| 189 | +} |
| 190 | +``` |
| 191 | + |
| 192 | +This affects which TypeScript compiler options are applied during type checking. |
| 193 | + |
| 194 | +### Key Compiler Constraints |
| 195 | +- **Target**: ES2022 |
| 196 | +- **Module Resolution**: "bundler" mode |
| 197 | +- **Isolated Modules**: Required for build performance |
| 198 | +- **Exact Optional Properties**: Enforced in distribution builds |
| 199 | +- **No Unchecked Indexed Access**: Enforced in distribution builds |
| 200 | + |
| 201 | +## Build Orchestration |
| 202 | + |
| 203 | +### Turbo Pipeline |
| 204 | + |
| 205 | +The build system uses Turbo for orchestration with these key relationships: |
| 206 | +- `prepack` must complete before any builds |
| 207 | +- Type checking runs in parallel with builds |
| 208 | +- Cache keys include TypeScript configs, source files, and lock files |
| 209 | + |
| 210 | +### Build Commands |
| 211 | +- `pnpm build:control` - Build all packages using Rollup |
| 212 | +- `pnpm repo:prepack` - Prepare packages for publishing |
| 213 | +- `pnpm repo:lint:types` - Type check all packages |
| 214 | + |
| 215 | +### Package Publishing |
| 216 | + |
| 217 | +**Published Package Structure**: |
| 218 | +- Only `dist/` directory is included in npm packages |
| 219 | +- Conditional exports for dev/prod builds |
| 220 | +- `publint` validates package structure before publishing |
| 221 | + |
| 222 | +**Export Configuration**: |
| 223 | +```json |
| 224 | +{ |
| 225 | + "exports": { |
| 226 | + ".": { |
| 227 | + "development": "./dist/dev/index.js", |
| 228 | + "default": "./dist/prod/index.js" |
| 229 | + } |
| 230 | + } |
| 231 | +} |
| 232 | +``` |
| 233 | + |
| 234 | +Note: Private packages (`@glimmer/debug`, `@glimmer/constants`, `@glimmer/debug-util`, and all `@glimmer-workspace/*`) are never published to npm. |
| 235 | + |
| 236 | +## Continuous Integration Constraints |
| 237 | + |
| 238 | +### Bundle Size Monitoring |
| 239 | +- Automated size tracking via GitHub Actions |
| 240 | +- Compares dev/prod sizes against main branch |
| 241 | +- Reports size changes in PR comments |
| 242 | +- Uses `dust` utility for accurate measurements |
| 243 | + |
| 244 | +### Test Environment Constraints |
| 245 | +- **Browser Tests**: Puppeteer with specific Chrome flags |
| 246 | +- **Smoke Tests**: 300s timeout (vs 30s for regular tests) |
| 247 | +- **BrowserStack**: Cross-browser testing for releases |
| 248 | +- **Floating Dependencies**: Special CI job tests against latest deps |
| 249 | + |
| 250 | +### Validation Steps |
| 251 | +1. Type checking (`tsc`) |
| 252 | +2. Linting (`eslint`) |
| 253 | +3. Unit tests (QUnit/Vitest) |
| 254 | +4. Smoke tests |
| 255 | +5. Bundle size analysis |
| 256 | +6. Package structure validation (`publint`) |
| 257 | + |
| 258 | +## Development Environment |
| 259 | + |
| 260 | +### Vite Development Server |
| 261 | +- Transforms `import.meta.env.VM_LOCAL_DEV` → `true` for local development |
| 262 | +- Pre-bundles test dependencies for performance |
| 263 | +- Custom extension resolution order |
| 264 | + |
| 265 | +### ESLint Configuration |
| 266 | +- Environment-aware rules (console vs non-console packages) |
| 267 | +- Strictness based on package metadata |
| 268 | +- Test-specific rules for QUnit |
| 269 | +- Custom rules for Glimmer-specific patterns |
| 270 | + |
| 271 | +### Automated Code Fixes |
| 272 | +Tools in `bin/fixes/`: |
| 273 | +- `apply-eslint-suggestions.js` - Apply ESLint auto-fixes |
| 274 | +- `apply-ts-codefixes.js` - Apply TypeScript code fixes |
| 275 | +- `apply-suggestions.js` - Apply both types of fixes |
| 276 | + |
| 277 | +## Guidelines for Developers |
| 278 | + |
| 279 | +1. **Use debug assertions liberally** - They help catch bugs and document assumptions |
| 280 | +2. **Don't wrap debug code in conditions** - The build process handles removal |
| 281 | +3. **Import from the right place** - Use `@glimmer/debug` imports in VM code |
| 282 | +4. **Trust the build process** - Write clear development code; the build makes it production-ready |
| 283 | +5. **Respect package boundaries** - Don't import from private packages in public ones |
| 284 | +6. **Follow strictness levels** - Adhere to the TypeScript strictness of your package |
| 285 | + |
| 286 | +## Summary |
| 287 | + |
| 288 | +The Glimmer VM build system enables developers to write defensive, well-instrumented code during development while shipping minimal, performant code to production. Through multiple layers of transformations, validations, and constraints, it ensures debug code never reaches users while maintaining a fast and helpful development experience. |
0 commit comments