Skip to content

Commit ada419b

Browse files
authored
Merge pull request #43 from kycutler/kycutler/linkifyperf
Perf: Reduce number of nodes created when using linkify
2 parents 960789b + 17adf48 commit ada419b

File tree

2 files changed

+64
-33
lines changed

2 files changed

+64
-33
lines changed

__tests__/index.spec.tsx

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,34 @@ describe("Ansi", () => {
135135
);
136136
});
137137

138+
test("can linkify multiple links", () => {
139+
const el = shallow(
140+
React.createElement(
141+
Ansi,
142+
{ linkify: true },
143+
"this is a link: www.google.com and this is a second link: www.microsoft.com"
144+
)
145+
);
146+
expect(el).not.toBeNull();
147+
expect(el.text()).toBe("this is a link: www.google.com and this is a second link: www.microsoft.com");
148+
expect(el.html()).toBe(
149+
'<code><span>this is a link: <a href=\"http://www.google.com\" target=\"_blank\">www.google.com</a> and this is a second link: <a href=\"http://www.microsoft.com\" target=\"_blank\">www.microsoft.com</a></span></code>'
150+
);
151+
});
152+
153+
test("creates a minimal number of nodes when using linkify", () => {
154+
const el = shallow(
155+
React.createElement(
156+
Ansi,
157+
{ linkify: true },
158+
"this is a link: www.google.com and this is text after"
159+
)
160+
);
161+
expect(el).not.toBeNull();
162+
expect(el.text()).toBe("this is a link: www.google.com and this is text after");
163+
expect(el.childAt(0).children()).toHaveLength(3);
164+
});
165+
138166
describe("useClasses options", () => {
139167
test("can add the font color class", () => {
140168
const el = shallow(

src/index.ts

Lines changed: 36 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ import Anser, { AnserJsonEntry } from "anser";
22
import { escapeCarriageReturn } from "escape-carriage";
33
import * as React from "react";
44

5-
const LINK_REGEX = /^(https?:\/\/(?:www\.|(?!www))[^\s.]+\.[^\s]{2,}|www\.[^\s]+\.[^\s]{2,})$/;
6-
75
/**
86
* Converts ANSI strings into JSON output.
97
* @name ansiToJSON
@@ -102,37 +100,42 @@ function convertBundleIntoReact(
102100
);
103101
}
104102

105-
const content = bundle.content
106-
.split(/(\s+)/)
107-
.reduce((words: React.ReactNode[], word: string, index: number) => {
108-
// If this is a separator, re-add the space removed from split.
109-
if (index % 2 === 1) {
110-
words.push(word);
111-
return words;
112-
}
113-
114-
// If this isn't a link, just return the word as-is.
115-
if (!LINK_REGEX.test(word)) {
116-
words.push(word);
117-
return words;
118-
}
119-
120-
// Make sure the href we generate from the link is fully qualified. We assume http
121-
// if it starts with a www because many sites don't support https
122-
const href = word.startsWith("www.") ? `http://${word}` : word;
123-
words.push(
124-
React.createElement(
125-
"a",
126-
{
127-
key: index,
128-
href,
129-
target: "_blank"
130-
},
131-
`${word}`
132-
)
133-
);
134-
return words;
135-
}, [] as React.ReactNode[]);
103+
const content: React.ReactNode[] = [];
104+
const linkRegex = /(\s+|^)(https?:\/\/(?:www\.|(?!www))[^\s.]+\.[^\s]{2,}|www\.[^\s]+\.[^\s]{2,})(\s+|$)/g;
105+
106+
let index = 0;
107+
let match: RegExpExecArray | null;
108+
while ((match = linkRegex.exec(bundle.content)) !== null) {
109+
const [ , pre, url, post ] = match;
110+
111+
const startIndex = match.index + pre.length;
112+
if (startIndex > index) {
113+
content.push(bundle.content.substring(index, startIndex));
114+
}
115+
116+
// Make sure the href we generate from the link is fully qualified. We assume http
117+
// if it starts with a www because many sites don't support https
118+
const href = url.startsWith("www.") ? `http://${url}` : url;
119+
content.push(
120+
React.createElement(
121+
"a",
122+
{
123+
key: index,
124+
href,
125+
target: "_blank"
126+
},
127+
`${url}`
128+
)
129+
);
130+
131+
const endIndex = linkRegex.lastIndex - post.length;
132+
index = endIndex;
133+
}
134+
135+
if (index < bundle.content.length) {
136+
content.push(bundle.content.substring(index));
137+
}
138+
136139
return React.createElement("span", { style, key, className }, content);
137140
}
138141

0 commit comments

Comments
 (0)