Skip to content

Commit 8222284

Browse files
authored
Merge pull request #8570 from quarto-dev/bugfix/8567
Website Search - fuse.js performance
2 parents 88d695e + d76ba79 commit 8222284

File tree

1 file changed

+52
-13
lines changed

1 file changed

+52
-13
lines changed

src/resources/projects/website/search/quarto-search.js

+52-13
Original file line numberDiff line numberDiff line change
@@ -675,6 +675,18 @@ function showCopyLink(query, options) {
675675
// create the index
676676
var fuseIndex = undefined;
677677
var shownWarning = false;
678+
679+
// fuse index options
680+
const kFuseIndexOptions = {
681+
keys: [
682+
{ name: "title", weight: 20 },
683+
{ name: "section", weight: 20 },
684+
{ name: "text", weight: 10 },
685+
],
686+
ignoreLocation: true,
687+
threshold: 0.1,
688+
};
689+
678690
async function readSearchData() {
679691
// Initialize the search index on demand
680692
if (fuseIndex === undefined) {
@@ -685,17 +697,7 @@ async function readSearchData() {
685697
shownWarning = true;
686698
return;
687699
}
688-
// create fuse index
689-
const options = {
690-
keys: [
691-
{ name: "title", weight: 20 },
692-
{ name: "section", weight: 20 },
693-
{ name: "text", weight: 10 },
694-
],
695-
ignoreLocation: true,
696-
threshold: 0.1,
697-
};
698-
const fuse = new window.Fuse([], options);
700+
const fuse = new window.Fuse([], kFuseIndexOptions);
699701

700702
// fetch the main search.json
701703
const response = await fetch(offsetURL("search.json"));
@@ -1226,8 +1228,34 @@ function algoliaSearch(query, limit, algoliaOptions) {
12261228
});
12271229
}
12281230

1229-
function fuseSearch(query, fuse, fuseOptions) {
1230-
return fuse.search(query, fuseOptions).map((result) => {
1231+
let subSearchTerm = undefined;
1232+
let subSearchFuse = undefined;
1233+
const kFuseMaxWait = 125;
1234+
1235+
async function fuseSearch(query, fuse, fuseOptions) {
1236+
let index = fuse;
1237+
// Fuse.js using the Bitap algorithm for text matching which runs in
1238+
// O(nm) time (no matter the structure of the text). In our case this
1239+
// means that long search terms mixed with large index gets very slow
1240+
//
1241+
// This injects a subIndex that will be used once the terms get long enough
1242+
// Usually making this subindex is cheap since there will typically be
1243+
// a subset of results matching the existing query
1244+
if (subSearchFuse !== undefined && query.startsWith(subSearchTerm)) {
1245+
// Use the existing subSearchFuse
1246+
index = subSearchFuse;
1247+
} else if (subSearchFuse !== undefined) {
1248+
// The term changed, discard the existing fuse
1249+
subSearchFuse = undefined;
1250+
subSearchTerm = undefined;
1251+
}
1252+
1253+
// Search using the active fuse
1254+
const then = performance.now();
1255+
const resultsRaw = await index.search(query, fuseOptions);
1256+
const now = performance.now();
1257+
1258+
const results = resultsRaw.map((result) => {
12311259
const addParam = (url, name, value) => {
12321260
const anchorParts = url.split("#");
12331261
const baseUrl = anchorParts[0];
@@ -1244,4 +1272,15 @@ function fuseSearch(query, fuse, fuseOptions) {
12441272
crumbs: result.item.crumbs,
12451273
};
12461274
});
1275+
1276+
// If we don't have a subfuse and the query is long enough, go ahead
1277+
// and create a subfuse to use for subsequent queries
1278+
if (now - then > kFuseMaxWait && subSearchFuse === undefined) {
1279+
subSearchTerm = query;
1280+
subSearchFuse = new window.Fuse([], kFuseIndexOptions);
1281+
resultsRaw.forEach((rr) => {
1282+
subSearchFuse.add(rr.item);
1283+
});
1284+
}
1285+
return results;
12471286
}

0 commit comments

Comments
 (0)