Split common into primitives and runtime layers

Reorganize src/common per issue #5963: move building blocks under
primitives/ and services/integration under runtime/. Preserve existing
common/* import paths via TypeScript path mapping. Add src/ARCHITECTURE.md
documenting modules and goals.

Fix two relative imports broken by the directory depth change.

Co-authored-by: Daniel Imms <Tyriar@users.noreply.github.com>
This commit is contained in:
Cursor Agent
2026-05-30 23:47:30 +00:00
co-authored by Daniel Imms
parent 43e8365f0e
commit 9a65429a30
122 changed files with 161 additions and 12 deletions
+2
View File
@@ -11,6 +11,8 @@
"removeComments": true,
"paths": {
"common/*": [
"../../../src/common/primitives/*",
"../../../src/common/runtime/*",
"../../../src/common/*"
],
"browser/*": [
@@ -11,6 +11,8 @@
"removeComments": true,
"paths": {
"common/*": [
"../../../src/common/primitives/*",
"../../../src/common/runtime/*",
"../../../src/common/*"
],
"browser/*": [
+2
View File
@@ -11,6 +11,8 @@
"removeComments": true,
"paths": {
"common/*": [
"../../../src/common/primitives/*",
"../../../src/common/runtime/*",
"../../../src/common/*"
],
"browser/*": [
+5 -1
View File
@@ -13,7 +13,11 @@
],
"paths": {
"browser/*": [ "../../../src/browser/*" ],
"common/*": [ "../../../src/common/*" ],
"common/*": [
"../../../src/common/primitives/*",
"../../../src/common/runtime/*",
"../../../src/common/*"
],
"@xterm/addon-image": [ "../typings/addon-image.d.ts" ],
"*": [ "./*" ]
}
+2
View File
@@ -11,6 +11,8 @@
"removeComments": true,
"paths": {
"common/*": [
"../../../src/common/primitives/*",
"../../../src/common/runtime/*",
"../../../src/common/*"
],
"browser/*": [
+2
View File
@@ -16,6 +16,8 @@
],
"paths": {
"common/*": [
"../../../src/common/primitives/*",
"../../../src/common/runtime/*",
"../../../src/common/*"
],
"browser/*": [
+2
View File
@@ -11,6 +11,8 @@
"removeComments": true,
"paths": {
"common/*": [
"../../../src/common/primitives/*",
"../../../src/common/runtime/*",
"../../../src/common/*"
],
"browser/*": [
+2
View File
@@ -16,6 +16,8 @@
],
"paths": {
"common/*": [
"../../../src/common/primitives/*",
"../../../src/common/runtime/*",
"../../../src/common/*"
],
"browser/*": [
+2
View File
@@ -11,6 +11,8 @@
"removeComments": true,
"paths": {
"common/*": [
"../../../src/common/primitives/*",
"../../../src/common/runtime/*",
"../../../src/common/*"
],
"browser/*": [
@@ -12,7 +12,11 @@
"target": "es2021",
"module": "nodenext",
"paths": {
"common/*": ["../../../src/common/*"],
"common/*": [
"../../../src/common/primitives/*",
"../../../src/common/runtime/*",
"../../../src/common/*"
],
"browser/*": ["../../../src/browser/*"],
"SerializeAddon": ["../src/SerializeAddon"],
"@xterm/addon-serialize": [
+2
View File
@@ -12,6 +12,8 @@
"removeComments": true,
"paths": {
"common/*": [
"../../../src/common/primitives/*",
"../../../src/common/runtime/*",
"../../../src/common/*"
],
"browser/*": [
@@ -11,6 +11,8 @@
"removeComments": true,
"paths": {
"common/*": [
"../../../src/common/primitives/*",
"../../../src/common/runtime/*",
"../../../src/common/*"
],
"browser/*": [
@@ -9,7 +9,11 @@
"target": "es2015",
"module": "nodenext",
"paths": {
"common/*": ["../../../src/common/*"],
"common/*": [
"../../../src/common/primitives/*",
"../../../src/common/runtime/*",
"../../../src/common/*"
],
"browser/*": ["../../../src/browser/*"],
"UnicodeGraphemeProvider": ["../src/UnicodeGraphemeProvider"],
"@xterm/addon-unicode-graphemes": [
@@ -13,6 +13,8 @@
"strict": true,
"paths": {
"common/*": [
"../../../src/common/primitives/*",
"../../../src/common/runtime/*",
"../../../src/common/*"
],
"@xterm/addon-unicode-graphemes": [
@@ -11,6 +11,8 @@
"removeComments": true,
"paths": {
"common/*": [
"../../../src/common/primitives/*",
"../../../src/common/runtime/*",
"../../../src/common/*"
],
"browser/*": [
+2
View File
@@ -13,6 +13,8 @@
"strict": true,
"paths": {
"common/*": [
"../../../src/common/primitives/*",
"../../../src/common/runtime/*",
"../../../src/common/*"
],
"@xterm/addon-unicode11": [
@@ -11,6 +11,8 @@
"removeComments": true,
"paths": {
"common/*": [
"../../../src/common/primitives/*",
"../../../src/common/runtime/*",
"../../../src/common/*"
],
"browser/*": [
@@ -11,6 +11,8 @@
"removeComments": true,
"paths": {
"common/*": [
"../../../src/common/primitives/*",
"../../../src/common/runtime/*",
"../../../src/common/*"
],
"browser/*": [
@@ -11,6 +11,8 @@
"removeComments": true,
"paths": {
"common/*": [
"../../../src/common/primitives/*",
"../../../src/common/runtime/*",
"../../../src/common/*"
],
"browser/*": [
+2
View File
@@ -12,6 +12,8 @@
"removeComments": true,
"paths": {
"common/*": [
"../../../src/common/primitives/*",
"../../../src/common/runtime/*",
"../../../src/common/*"
],
"browser/*": [
+2
View File
@@ -11,6 +11,8 @@
"removeComments": true,
"paths": {
"common/*": [
"../../../src/common/primitives/*",
"../../../src/common/runtime/*",
"../../../src/common/*"
],
"browser/*": [
+3 -1
View File
@@ -190,7 +190,9 @@ if (config.addon) {
...outConfig,
entryPoints: [
'src/browser/**/*.ts',
'src/common/**/*.ts',
'src/common/primitives/**/*.ts',
'src/common/runtime/**/*.ts',
'src/common/*.ts',
'src/headless/**/*.ts'
],
outdir: 'out-esbuild/'
+1 -1
View File
@@ -81,7 +81,7 @@ function checkAndPublishPackage(packageDir, repoCommit, peerDependencies, update
// Update Version.ts with the new version
if (updateVersionTs) {
const versionTsPath = path.join(packageDir, 'src/common/Version.ts');
const versionTsPath = path.join(packageDir, 'src/common/primitives/Version.ts');
const versionTsContent = fs.readFileSync(versionTsPath, 'utf8');
const updatedVersionTs = versionTsContent.replace(
/export const XTERM_VERSION = '[^']+';/,
+79
View File
@@ -0,0 +1,79 @@
# xterm.js source architecture
This document describes how `src/` is organized, with emphasis on the shared core under `src/common/`. The layout follows the refined proposal in [issue #5963](https://github.com/xtermjs/xterm.js/issues/5963): separate **primitives** (building blocks) from **runtime** (services and integration), while **browser** and **headless** remain target-specific entry points.
## Layer overview
```mermaid
flowchart BT
primitives["common/primitives<br/>building blocks, high test coverage expected"]
runtime["common/runtime<br/>services, integration"]
browser["browser<br/>DOM terminal target"]
headless["headless<br/>Node terminal target"]
runtime --> primitives
browser --> runtime
headless --> runtime
```
| Layer | Path | Role |
| --- | --- | --- |
| Primitives | `src/common/primitives/` | Low-level, mostly stateless building blocks: buffer, parser, escape/input helpers, static data, and shared utilities. These modules should be easy to unit test in isolation. |
| Runtime | `src/common/runtime/` | Stateful services (DI), `CoreTerminal`, `InputHandler`, public API adapters under `public/`, and other code that wires primitives into a working terminal core. |
| Shared types | `src/common/Types.ts` | Cross-cutting interfaces and types used by both layers (kept at the `common/` root until a stricter module API is introduced). |
| Browser target | `src/browser/` | Browser-only rendering, input, and the public `Terminal` wrapper. |
| Headless target | `src/headless/` | Node-oriented terminal without DOM; same public API shape as the browser build where applicable. |
## `common/primitives/`
| Area | Contents |
| --- | --- |
| Root utilities | `Async`, `CircularList`, `Color`, `Event`, `Lifecycle`, `MultiKeyMap`, `Platform`, `SortedList`, `StringBuilder`, `TaskQueue`, `Version` |
| `buffer/` | Screen buffer, cells, lines, markers, reflow |
| `data/` | Static tables (`Charsets`, `EscapeSequences`) |
| `input/` | UTF decoding, keyboard helpers, write buffer, color parsing |
| `parser/` | Escape-sequence parser (CSI, OSC, DCS, APC) |
**Goals**
- Prefer comprehensive unit tests here; behavior should be understandable without constructing the full terminal stack.
- Avoid importing from `runtime/` when possible. Some legacy edges still reach into `common/services` (for example buffer logging hooks); tightening that is follow-up work, not a blocker for the folder split.
- Future work may split primitives into finer TypeScript projects (`base`, `encoding`, `buffer`, …) with enforced one-way references.
## `common/runtime/`
| Area | Contents |
| --- | --- |
| `services/` | Dependency-injection services (`BufferService`, `OptionsService`, `CoreService`, …) |
| `public/` | Public API surface adapters (`AddonManager`, buffer/parser API views) |
| Root integration | `CoreTerminal`, `InputHandler`, `WindowsMode`, test helpers (`TestUtils.test.ts`) |
**Goals**
- Own composition: instantiation, service registry, and the large integration classes (`InputHandler`).
- May depend on `primitives/` and on `common/Types.ts`; must not be depended on by primitives in the steady state (existing cycles are technical debt to remove incrementally).
- Integration and addon-facing behavior is validated by unit tests here and by browser/headless/integration tests at the target layer.
## Import paths
Source continues to use the existing `common/...` import prefix (for example `common/buffer/Buffer`, `common/services/Services`). TypeScript `paths` in `tsconfig.json` files map that prefix onto `primitives/`, `runtime/`, and the `common/` root so this restructure did not require mass import rewrites.
## Targets vs core
```mermaid
flowchart LR
subgraph core["Shared core"]
primitives
runtime
end
browser --> runtime
headless --> runtime
```
Browser and headless add platform code (renderers, DOM, Node specifics) on top of the same runtime core. Addons and the demo build against the published packages; their tsconfigs use the same `common/*` path mapping.
## Related issues
- [#5963](https://github.com/xtermjs/xterm.js/issues/5963) — module split and TypeScript project structure
- [#5896](https://github.com/xtermjs/xterm.js/issues/5896) — relative imports (possible follow-up)
- [#5897](https://github.com/xtermjs/xterm.js/issues/5897) — `import/no-cycle` lint enforcement
+1 -1
View File
@@ -53,7 +53,7 @@ import { C0, C1ESCAPED } from 'common/data/EscapeSequences';
import { toRgbString } from 'common/input/XParseColor';
import { DecorationService } from 'common/services/DecorationService';
import { IDecorationService } from 'common/services/Services';
import { WindowsOptionsReportType } from '../common/InputHandler';
import { WindowsOptionsReportType } from 'common/InputHandler';
import { AccessibilityManager } from './AccessibilityManager';
import { Linkifier } from './Linkifier';
import { Emitter, EventUtils, type IEvent } from 'common/Event';
+5 -1
View File
@@ -11,7 +11,11 @@
],
"skipLibCheck": true,
"paths": {
"common/*": [ "./../common/*" ],
"common/*": [
"./../common/primitives/*",
"./../common/runtime/*",
"./../common/*"
],
"*": [ "./../*" ]
}
},
@@ -6,7 +6,7 @@ import { NULL_CELL_CHAR, NULL_CELL_WIDTH, NULL_CELL_CODE, DEFAULT_ATTR, Content,
import { BufferLine } from 'common/buffer//BufferLine';
import { BufferLineStringCache } from 'common/buffer/BufferLineStringCache';
import { CellData } from 'common/buffer/CellData';
import { CharData, IBufferLine, ICellData } from '../Types';
import { CharData, IBufferLine, ICellData } from 'common/Types';
import { assert } from 'chai';
import { AttributeData } from 'common/buffer/AttributeData';
import { createCellData, NULL_CELL_DATA, extendedAttributes } from 'common/TestUtils.test';

Some files were not shown because too many files have changed in this diff Show More