Skip to content

Commit dc3b900

Browse files
rauchgmatheuss
authored andcommitted
Add perf optimizations (vercel#1541)
* pass `uid` to term * term: keep track of term instance references * sessions: remvoe `write` object overhead * hterm: cache measurement of codepoints for single char strings * sessions: merge less eagerly when we receive a PTY_DATA action * store: handle the side effect of writing to the terminal from a middleware * terms: add terms instance cache * lint
1 parent 1cd2620 commit dc3b900

File tree

9 files changed

+54
-26
lines changed

9 files changed

+54
-26
lines changed

lib/components/term-group.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,8 @@ class TermGroup_ extends Component {
7676
onData: this.bind(this.props.onData, null, uid),
7777
onURLAbort: this.bind(this.props.onURLAbort, null, uid),
7878
borderColor: this.props.borderColor,
79-
quickEdit: this.props.quickEdit
79+
quickEdit: this.props.quickEdit,
80+
uid
8081
});
8182

8283
// This will create a new ref_ function for every render,

lib/components/term.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import uuid from 'uuid';
55
import hterm from '../hterm';
66
import Component from '../component';
77
import getColorList from '../utils/colors';
8+
import terms from '../terms';
89
import notify from '../utils/notify';
910

1011
export default class Term extends Component {
@@ -97,6 +98,7 @@ export default class Term extends Component {
9798

9899
this.getScreenNode().addEventListener('mouseup', this.handleMouseUp);
99100
this.getScreenNode().addEventListener('mousedown', this.handleMouseDown);
101+
terms[this.props.uid] = this;
100102
}
101103

102104
handleWheel(e) {
@@ -364,6 +366,7 @@ export default class Term extends Component {
364366
}
365367

366368
componentWillUnmount() {
369+
terms[this.props.uid] = this;
367370
// turn blinking off to prevent leaking a timeout when disposing terminal
368371
const prefs = this.term.getPrefs();
369372
prefs.set('cursor-blink', false);

lib/components/terms.js

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,6 @@ export default class Terms extends Component {
1616
props.ref_(this);
1717
}
1818

19-
componentWillReceiveProps(next) {
20-
const {write} = next;
21-
if (write && this.props.write !== write) {
22-
this.getTermByUid(write.uid).write(write.data);
23-
}
24-
}
25-
2619
shouldComponentUpdate(nextProps) {
2720
for (const i in nextProps) {
2821
if (i === 'write') {

lib/hterm.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,12 @@ hterm.TextAttributes.splitWidecharString = function (str) {
5757
};
5858

5959
// hterm Unicode patch
60+
const cache = [];
6061
lib.wc.strWidth = function (str) {
62+
const shouldCache = str.length === 1;
63+
if (shouldCache && cache[str] !== undefined) {
64+
return cache[str];
65+
}
6166
const chars = runes(str);
6267
let width = 0;
6368
let rv = 0;
@@ -70,6 +75,9 @@ lib.wc.strWidth = function (str) {
7075
}
7176
rv += width * ((codePoint <= 0xffff) ? 1 : 2);
7277
}
78+
if (shouldCache) {
79+
cache[str] = rv;
80+
}
7381
return rv;
7482
};
7583

lib/reducers/sessions.js

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ import {
1616

1717
const initialState = Immutable({
1818
sessions: {},
19-
write: null,
2019
activeUid: null
2120
});
2221

@@ -26,21 +25,13 @@ function Session(obj) {
2625
title: '',
2726
cols: null,
2827
rows: null,
29-
write: null,
3028
url: null,
3129
cleared: false,
3230
shell: '',
3331
pid: null
3432
}).merge(obj);
3533
}
3634

37-
function Write(obj) {
38-
return Immutable({
39-
uid: '',
40-
data: ''
41-
}).merge(obj);
42-
}
43-
4435
const reducer = (state = initialState, action) => {
4536
switch (action.type) {
4637
case SESSION_ADD:
@@ -73,15 +64,20 @@ const reducer = (state = initialState, action) => {
7364
}, {deep: true});
7465

7566
case SESSION_PTY_DATA:
76-
return state
77-
.set('write', Write(action))
78-
.merge({
79-
sessions: {
80-
[action.uid]: {
81-
cleared: false
67+
// we avoid a direct merge for perf reasons
68+
// as this is the most common action
69+
if (state.sessions[action.uid] &&
70+
state.sessions[action.uid].cleared) {
71+
return state
72+
.merge({
73+
sessions: {
74+
[action.uid]: {
75+
cleared: false
76+
}
8277
}
83-
}
84-
}, {deep: true});
78+
}, {deep: true});
79+
}
80+
return state;
8581

8682
case SESSION_PTY_EXIT:
8783
if (state.sessions[action.uid]) {

lib/store/configure-store.dev.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import createLogger from 'redux-logger';
44
import rootReducer from '../reducers/index';
55
import effects from '../utils/effects';
66
import * as plugins from '../utils/plugins';
7+
import writeMiddleware from './write-middleware';
78

89
export default () => {
910
const logger = createLogger({
@@ -17,6 +18,7 @@ export default () => {
1718
plugins.middleware,
1819
thunk,
1920
effects,
21+
writeMiddleware,
2022
logger
2123
),
2224
window.devToolsExtension()

lib/store/configure-store.prod.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import thunk from 'redux-thunk';
33
import rootReducer from '../reducers/index';
44
import effects from '../utils/effects';
55
import * as plugins from '../utils/plugins';
6+
import writeMiddleware from './write-middleware';
67

78
export default () =>
89
createStore(
@@ -11,6 +12,7 @@ export default () =>
1112
thunk,
1213
plugins.middleware,
1314
thunk,
14-
effects
15+
effects,
16+
writeMiddleware
1517
)
1618
);

lib/store/write-middleware.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import terms from '../terms';
2+
3+
// the only side effect we perform from middleware
4+
// is to write to the react term instance directly
5+
// to avoid a performance hit
6+
export default () => next => action => {
7+
if (action.type === 'SESSION_PTY_DATA') {
8+
const term = terms[action.uid];
9+
if (term) {
10+
term.write(action.data);
11+
}
12+
}
13+
next(action);
14+
};

lib/terms.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// react Term components add themselves
2+
// to this object upon mounting / unmounting
3+
// this is to allow imperative access to the
4+
// term API, which is a performance
5+
// optimization for the most common action
6+
// within the system
7+
8+
const terms = {};
9+
export default terms;

0 commit comments

Comments
 (0)