Improve performance when rendering many emoji

This commit is contained in:
Evan Hahn 2021-08-30 11:39:03 -05:00 committed by GitHub
parent 6f242eca57
commit 7f50fcdb54
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 32 additions and 17 deletions

View file

@ -96,3 +96,19 @@ story.add('Custom Text Render', () => {
return <Emojify {...props} />; return <Emojify {...props} />;
}); });
story.add('Tens of thousands of emoji', () => {
const props = createProps({
text: '💅'.repeat(40000),
});
return <Emojify {...props} />;
});
story.add('Tens of thousands of emoji, interspersed with text', () => {
const props = createProps({
text: '💅 hi '.repeat(40000),
});
return <Emojify {...props} />;
});

View file

@ -28,25 +28,15 @@ describe('emoji', () => {
{ type: 'emoji', value: '😛' }, { type: 'emoji', value: '😛' },
{ type: 'text', value: 'world' }, { type: 'text', value: 'world' },
{ type: 'emoji', value: '😎' }, { type: 'emoji', value: '😎' },
{ type: 'text', value: '' },
{ type: 'emoji', value: '😛' }, { type: 'emoji', value: '😛' },
{ type: 'text', value: '!' }, { type: 'text', value: '!' },
]); ]);
}); });
it('should return empty string after split at the end', () => { it('returns emojis as text after 5,000 emojis are found', () => {
assert.deepStrictEqual(splitByEmoji('hello😛'), [ assert.deepStrictEqual(splitByEmoji('💬'.repeat(5002)), [
{ type: 'text', value: 'hello' }, ...Array(5000).fill({ type: 'emoji', value: '💬' }),
{ type: 'emoji', value: '😛' }, { type: 'text', value: '💬💬' },
{ type: 'text', value: '' },
]);
});
it('should return empty string before the split at the start', () => {
assert.deepStrictEqual(splitByEmoji('😛hello'), [
{ type: 'text', value: '' },
{ type: 'emoji', value: '😛' },
{ type: 'text', value: 'hello' },
]); ]);
}); });
}); });

View file

@ -4,8 +4,10 @@
import emojiRegex from 'emoji-regex/es2015/RGI_Emoji'; import emojiRegex from 'emoji-regex/es2015/RGI_Emoji';
import { assert } from './assert'; import { assert } from './assert';
import { take } from './iterables';
const REGEXP = emojiRegex(); const REGEXP = emojiRegex();
const MAX_EMOJI_TO_MATCH = 5000;
export function replaceEmojiWithSpaces(value: string): string { export function replaceEmojiWithSpaces(value: string): string {
return value.replace(REGEXP, ' '); return value.replace(REGEXP, ' ');
@ -17,19 +19,26 @@ export type SplitElement = Readonly<{
}>; }>;
export function splitByEmoji(value: string): ReadonlyArray<SplitElement> { export function splitByEmoji(value: string): ReadonlyArray<SplitElement> {
const emojis = value.matchAll(REGEXP); const emojis = take(value.matchAll(REGEXP), MAX_EMOJI_TO_MATCH);
const result: Array<SplitElement> = []; const result: Array<SplitElement> = [];
let lastIndex = 0; let lastIndex = 0;
for (const match of emojis) { for (const match of emojis) {
result.push({ type: 'text', value: value.slice(lastIndex, match.index) }); const nonEmojiText = value.slice(lastIndex, match.index);
if (nonEmojiText) {
result.push({ type: 'text', value: nonEmojiText });
}
result.push({ type: 'emoji', value: match[0] }); result.push({ type: 'emoji', value: match[0] });
assert(match.index !== undefined, '`matchAll` should provide indices'); assert(match.index !== undefined, '`matchAll` should provide indices');
lastIndex = match.index + match[0].length; lastIndex = match.index + match[0].length;
} }
result.push({ type: 'text', value: value.slice(lastIndex) }); const finalNonEmojiText = value.slice(lastIndex);
if (finalNonEmojiText) {
result.push({ type: 'text', value: finalNonEmojiText });
}
return result; return result;
} }