The Lichess analysis tree — extracted, optimized, and packaged as a standalone npm library with full PGN import/export.
Built from the actual Lichess source code — chesstree is extracted from ui/lib/src/tree/ in the Lichess monorepo. The same tree data structure powering millions of games on the world's largest open-source chess platform, now available as a single npm install.
- 🏗️ Lichess DNA — Same
TreeWrapperAPI, same node ID encoding (scalachessCharPair), same battle-tested architecture - 🚀 Extended beyond Lichess — Full PGN import/export pipeline, 64-entry MRU path cache, zero-allocation
charCodeAttraversal — performance improvements you won't find in Lichess itself - 📦 One dependency — Only
chessops(by the same Lichess author). No framework lock-in, works everywhere
| Feature | @itshak/chesstree |
@mliebelt/pgn-parser |
chessops |
@jackstenglein/chess |
cm-chess |
|---|---|---|---|---|---|
| Parse nested variations (RAVs) | ✅ | ✅ | ✅ | ✅ | ✅ |
| Pre-computed FEN on every node | ✅ | ❌ (syntax only) | ✅ | ✅ | ✅ |
Interactive Navigation (nodeAtPath) |
✅ | ❌ | ❌ | ||
| Add moves to tree & branches | ✅ | ❌ | ❌ | ||
| Delete subtrees & variations | ✅ | ❌ | ❌ | ❌ | ❌ |
| Promote variation to mainline | ✅ | ❌ | ❌ | ❌ | ❌ |
| Merge variation trees | ✅ | ❌ | ❌ | ❌ | ❌ |
| Lossless PGN roundtrip export | ✅ | ❌ | |||
| Zero-allocation path traversal | ✅ | ❌ | ❌ | ❌ | ❌ |
| O(1) MRU path cache (120fps UI) | ✅ | ❌ | ❌ | ❌ | ❌ |
Board shapes & arrows ([%cal], [%csl]) |
✅ | ✅ | ❌ | ❌ | |
Clock annotations ([%clk]) & NAGs |
✅ | ✅ | ✅ | ✅ | ✅ |
| Strict TypeScript | ✅ | ✅ | ✅ | ❌ |
- 🌳 Complete Variation Tree Support: Parse and manipulate unlimited levels of nested variations (RAVs), move comments, starting comments, NAG glyphs (
!,?,!?), clock annotations ([%clk ...]), and board drawings ([%cal ...],[%csl ...]). - ⚡ Zero-Allocation Path Algebra: Iterative move lookups and path calculations eliminate substring memory allocations and recursive call frames in hot render loops.
- 🚀 64-Entry MRU Path Cache: Active move paths return in O(1) directly from memory, keeping 120fps UI navigation silky smooth.
- 🔁 Lossless Roundtrip Export: Serialize modified variation trees back to standard PGN with proper indentation, comment escaping, and tag preservation.
- 🛡️ Type-Safe: 100% strict TypeScript definitions with full autocomplete and type inference.
- 🌐 Framework Agnostic: Works seamlessly in Node.js, React, Vue, Svelte, Electron, Tauri, or vanilla browser JavaScript.
npm install @itshak/chesstreeOr with yarn / pnpm:
yarn add @itshak/chesstree
# or
pnpm add @itshak/chesstreeimport { pgnImport, buildTree, pgnExport } from '@itshak/chesstree';
const pgn = `[Event "World Championship"]
[Site "Reykjavik ISL"]
[Date "1972.07.23"]
[Round "6"]
[White "Fischer, Robert J."]
[Black "Spassky, Boris V."]
[Result "1-0"]
1. c4 e6 2. Nf3 d5 3. d4 Nf6 4. Nc3 Be7 5. Bg5 O-O 6. e3 h6 7. Bh4 b6 1-0`;
const parsed = pgnImport(pgn);
const tree = buildTree(parsed.treeParts[0]);
console.log(`White: ${parsed.game.white?.name}`);
console.log(`Black: ${parsed.game.black?.name}`);
console.log(`Mainline moves: ${tree.lastPly()} plies`);Move paths are encoded as compact 2-character ID sequences representing each step from the root:
// Find the first move (1. c4)
const firstMove = tree.root.children[0];
const path1 = firstMove.id;
// Find the second move (1... e6)
const secondMove = firstMove.children[0];
const path2 = path1 + secondMove.id;
// Query a node at a specific path
const node = tree.nodeAtPath(path2);
console.log(node?.san); // "e6"
console.log(node?.fen); // Current board FEN
// Add or edit annotations
tree.setCommentAt({ id: 'c1', text: 'Classic Tartakower defense setup.' }, path2);
// Add a sideline variation (e.g. 1... c5 after 1. c4)
const sidelineNode: Tree.Node = {
id: 'c7c5',
ply: 2,
san: 'c5',
fen: 'rnbqkbnr/pp1ppppp/8/2p5/2P5/8/PP1PPPPP/RNBQKBNR w KQkq c6 0 2',
uci: 'c7c5',
children: [],
};
const sidelinePath = tree.addNode(sidelineNode, path1);
// Promote variation to mainline
if (sidelinePath) {
tree.promoteAt(sidelinePath, true);
}const exportedPgn = pgnExport.renderFullTxt({
data: parsed,
tree,
});
console.log(exportedPgn);Parses a PGN string into structured game headers and a root node array (treeParts).
Creates an optimized navigation wrapper over the tree with the following methods:
| Method | Return Type | Description |
|---|---|---|
nodeAtPath(path: string) |
Tree.Node | undefined |
Cached, O(1) / O(N) node lookup. |
getNodeList(path: string) |
Tree.Node[] |
Ordered array of nodes from root to target path. |
longestValidPath(path: string) |
string |
Returns the longest prefix of path that exists in the tree. |
pathIsMainline(path: string) |
boolean |
Returns true if the path lies on the primary mainline. |
lastMainlineNode(path: string) |
Tree.Node |
Finds the last mainline node along a path. |
extendPath(path: string, isMainline: boolean) |
string |
Extends a path to the terminus of the line. |
addNode(node: Tree.Node, parentPath: string) |
string | undefined |
Inserts or merges a move at the specified parent path. |
promoteAt(path: string, toMainline: boolean) |
void |
Promotes a variation to mainline or parent rank. |
deleteNodeAt(path: string) |
void |
Removes a node and all descendant branches in-place. |
setCommentAt(comment: Tree.Comment, path: string) |
void |
Adds or updates move comments. |
deleteCommentAt(id: string, path: string) |
void |
Removes a comment by ID. |
setGlyphsAt(glyphs: Tree.Glyph[], path: string) |
void |
Assigns NAG annotation glyphs. |
setShapes(shapes: Tree.Shape[], path: string) |
void |
Assigns board arrows and highlights. |
For deep-dive details on the underlying data structures, zero-allocation algorithms, and benchmark comparisons, see docs/ARCHITECTURE.md.
We welcome community contributions, bug reports, and optimizations!
- Fork the repository on GitHub:
https://github.com/itshak/chesstree - Clone your fork and install dependencies:
git clone https://github.com/your-username/chesstree.git cd chesstree npm install - Run the test suite:
npm test - Make your changes in
src/, verify withnpm run buildandnpm test, and open a Pull Request.
Please see CONTRIBUTING.md for full development guidelines.
- License: GNU General Public License v3.0 or later (GPL-3.0-or-later).
- Attribution: Adapted from Lichess.org (GPL-3.0) and uses
chessopsby Niklas Fiekas (GPL-3.0).