How Alibaba’s IDE Co‑Development Revamped Tree Components for Lightning‑Fast Performance
This article details the evolution of Alibaba’s IDE co‑development project, focusing on the design, data structures, event model, rendering, and performance optimizations of a high‑performance Tree component used for file navigation and other hierarchical views within the KAITIAN framework.
IDE Co‑Development Deep Dive
In early 2019 the author joined Alibaba’s IDE co‑development project to build a public IDE core for the ecosystem. The Tree component, a core UI element for file, debug, and other views, was refactored twice; this article explains the latest high‑performance implementation in the KAITIAN framework.
What does a Tree component look like?
Typical Tree components consist of regular and collapsible nodes, with various decorations such as selection state and icons.
All examples below use a file‑tree as the reference.
Expected Data Structure
The basic data structure for a Tree is a flattened two‑dimensional array (FlattenedBranch) that stores each node’s position (index) and a unique identifier (node ID).
Event‑Driven Model
Parent‑child communication is handled through an event‑watcher interface, allowing the root to listen to any change in the tree.
<code>export interface ITreeWatcher {
// Listen to watchEvent such as node move, create, delete
onWatchEvent(path: string, callback: IWatcherCallback): IWatchTerminator;
// Listen to all events
on(event: TreeNodeEvent, callback: any);
// Notify parent will change
notifyWillChangeParent(target: ITreeNodeOrCompositeTreeNode, prevParent: ICompositeTreeNode, newParent: ICompositeTreeNode);
// Notify parent changed
notifyDidChangeParent(target: ITreeNodeOrCompositeTreeNode, prevParent: ICompositeTreeNode, newParent: ICompositeTreeNode);
// Notify node will be disposed
notifyWillDispose(target: ITreeNodeOrCompositeTreeNode);
// Notify node disposed
notifyDidDispose(target: ITreeNodeOrCompositeTreeNode);
// Notify node will process watch event (used in file tree)
notifyWillProcessWatchEvent(target: ICompositeTreeNode, event: IWatcherEvent);
// Notify node did process watch event
notifyDidProcessWatchEvent(target: ICompositeTreeNode, event: IWatcherEvent);
// Notify expansion state will change
notifyWillChangeExpansionState(target: ICompositeTreeNode, nowExpanded: boolean);
// Notify expansion state did change
notifyDidChangeExpansionState(target: ICompositeTreeNode, nowExpanded: boolean);
// Notify children will resolve
notifyWillResolveChildren(target: ICompositeTreeNode, nowExpanded: boolean);
// Notify children resolved
notifyDidResolveChildren(target: ICompositeTreeNode, nowExpanded: boolean);
// Notify path changed
notifyDidChangePath(target: ITreeNodeOrCompositeTreeNode);
// Notify metadata changed
notifyDidChangeMetadata(target: ITreeNodeOrCompositeTreeNode, change: IMetadataChange);
// Notify branch updated
notifyDidUpdateBranch();
// Dispose function
dispose: IDisposable;
}
</code>All nodes hold a reference to this watcher, so a single
oncall at the root can capture changes across the entire tree, enabling features such as snapshots and undo/redo.
Node Rendering Model
The Tree is essentially a long list; each node renders with indentation to appear hierarchical. Hardware acceleration can be triggered by using
transform: translateY(0)in CSS.
For large trees (thousands of nodes) only a subset of nodes is rendered at any time, with pre‑rendered buffers before and after the visible range to improve scrolling.
When the scroll reaches a boundary, the start index is updated and React’s diff algorithm re‑uses DOM nodes, achieving node recycling.
Data Service Layer
Each collapsible node must be able to fetch its children, which is provided by a
TreeServiceimplementation.
File‑tree nodes are defined by extending
CompositeTreeNode(for directories) and
TreeNode(for files):
<code>export class Directory extends CompositeTreeNode {
constructor(
tree: ITree,
public readonly parent: CompositeTreeNode | undefined,
public uri: URI = new URI(''),
public name: string = '',
public filestat: FileStat = { children: [], isDirectory: false, uri: '', lastModification: 0 },
public tooltip: string,
) {
super(tree, parent);
if (!parent) {
// Root node expands by default
this.setExpanded();
}
}
}
export class File extends TreeNode {
constructor(
tree: ITree,
public readonly parent: CompositeTreeNode | undefined,
public uri: URI = new URI(''),
public name: string = '',
public filestat: FileStat = { children: [], isDirectory: false, uri: '', lastModification: 0 },
public tooltip: string,
) {
super(tree, parent);
}
}
</code>The service that resolves children and sorts nodes:
<code>export class FileTreeService extends Tree {
async resolveChildren(parent?: Directory) {
// ... fetch child nodes logic
}
sortComparator(a: ITreeNodeOrCompositeTreeNode, b: ITreeNodeOrCompositeTreeNode) {
// ... node sorting logic
}
}
</code>The model separates data from rendering, allowing pre‑processing before the view consumes it.
<code>export class FileTreeModel extends TreeModel {
constructor(@Optional() root: Directory) {
super();
// additional initialization if needed
}
}
</code>Rendering Function Example
<code>const renderFileTree = () => {
if (isReady) {
return (
<RecycleTree
height={filterMode ? height - FILTER_AREA_HEIGHT : height}
width={width}
itemHeight={FILE_TREE_NODE_HEIGHT}
onReady={handleTreeReady}
model={fileTreeModelService.treeModel}
filter={filter}
>
{(props: INodeRendererProps) => (
<FileTreeNode
item={props.item}
itemType={props.itemType}
decorationService={decorationService}
labelService={labelService}
dndService={fileTreeModelService.dndService}
decorations={fileTreeModelService.decorations.getDecorations(props.item)}
onClick={handleItemClicked}
onTwistierClick={handleTwistierClick}
onContextMenu={handlerContextMenu}
/>
)}
</RecycleTree>
);
}
};
</code>Performance Comparison
New file tree vs. old file tree performance.
New file tree vs. VS Code file tree performance.
After the second refactor, the new implementation outperforms both the previous version and VS Code’s tree component.
Key reasons for the performance gap:
React’s virtual DOM diff provides advantages over direct DOM manipulation in large‑file scenarios.
Flattened, non‑recursive data model reduces traversal cost.
Rendering model defined in the parent allows on‑demand allocation without redundant checks in the core tree.
Beyond Performance
The redesigned Tree component also offers extensibility: developers can quickly create SearchTree, GitTree, TodoTree, etc., by defining a model, node data, and rendering logic. Data‑model separation enables pre‑loading data before the page renders, improving first‑paint times. Combined with dependency injection, multiple tree instances can be generated from a single token.
Conclusion
The current KAITIAN framework’s Tree component has only undergone the first phase (file‑tree) of refactoring. Ongoing optimizations will eventually replace all tree‑like views in the framework, providing a more reusable and high‑performance foundation for future scenarios.
Taobao Frontend Technology
The frontend landscape is constantly evolving, with rapid innovations across familiar languages. Like us, your understanding of the frontend is continually refreshed. Join us on Taobao, a vibrant, all‑encompassing platform, to uncover limitless potential.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.