From b572112ae794b88b4b1c529f88877cf41a82eb3e Mon Sep 17 00:00:00 2001 From: DualFroz Date: Sat, 3 Oct 2026 01:37:49 +0200 Subject: [PATCH] Allow merged indexes to be marked optional A merged index that failed to load made mergeIndex throw, and the failed instance stayed in the list, so search stopped working for every index. Indexes passed with `optional: true` are now dropped with a warning when they fail to load, and search runs over the indexes that did load. Fixes #887 --- docs/content/docs/multisite.md | 24 +++++++++++ ...nal-index-that-fails-to-load.toolproof.yml | 33 +++++++++++++++ ...nal-index-that-fails-to-load.toolproof.yml | 42 +++++++++++++++++++ pagefind_web_js/lib/coupled_search.ts | 17 ++++++-- pagefind_web_js/types/index.d.ts | 6 +++ 5 files changed, 118 insertions(+), 4 deletions(-) create mode 100644 pagefind/integration_tests/multisite/multisite_base/pagefind-skips-an-optional-index-that-fails-to-load.toolproof.yml create mode 100644 pagefind/integration_tests/multisite/multisite_base/pagefind-ui-skips-an-optional-index-that-fails-to-load.toolproof.yml diff --git a/docs/content/docs/multisite.md b/docs/content/docs/multisite.md index cdca2d5..87b334d 100644 --- a/docs/content/docs/multisite.md +++ b/docs/content/docs/multisite.md @@ -155,6 +155,30 @@ await pagefind.mergeIndex("https://docs.example.com/pagefind", { ``` {{< /diffcode >}} +## Skipping indexes that fail to load + +By default, a merged index that can't be loaded stops search from working. If some of your indexes might be missing, for example while one of the sites hasn't been built yet, pass an `optional` option for those indexes. Pagefind will log a warning and search across the indexes that did load: + +{{< diffcode >}} +```js +// Component UI: +const { configureInstance } = window.PagefindComponents; + +configureInstance("default", { + mergeIndex: [{ + bundlePath: "https://docs.example.com/pagefind", ++ optional: true + }] +}); + +// JS API: +const pagefind = await import("/pagefind/pagefind.js"); +await pagefind.mergeIndex("https://docs.example.com/pagefind", { ++ optional: true +}); +``` +{{< /diffcode >}} + ## Notes ### Cross origin indexes diff --git a/pagefind/integration_tests/multisite/multisite_base/pagefind-skips-an-optional-index-that-fails-to-load.toolproof.yml b/pagefind/integration_tests/multisite/multisite_base/pagefind-skips-an-optional-index-that-fails-to-load.toolproof.yml new file mode 100644 index 0000000..89be453 --- /dev/null +++ b/pagefind/integration_tests/multisite/multisite_base/pagefind-skips-an-optional-index-that-fails-to-load.toolproof.yml @@ -0,0 +1,33 @@ +name: Multisite Search > Pagefind skips an optional index that fails to load +steps: + - ref: ./background.toolproof.yml + - macro: I run Pagefind with "--site root/website_a" + - step: stdout should contain "Running Pagefind" + - step: The file "root/website_a/pagefind/pagefind.js" should not be empty + - macro: I run Pagefind with "--site root/website_b" + - step: stdout should contain "Running Pagefind" + - step: The file "root/website_b/pagefind/pagefind.js" should not be empty + - step: I serve the directory "root" + - step: In my browser, I load "/" + - step: In my browser, I evaluate {js} + js: >- + let pagefind = await import("/website_a/pagefind/pagefind.js"); + + await pagefind.mergeIndex("/website_c/pagefind/", { optional: true }); + + await pagefind.mergeIndex("/website_b/pagefind/"); + + + let search = await pagefind.search("web"); + + + let pages = await Promise.all(search.results.map(r => r.data())); + + document.querySelector('[data-result]').innerText = pages.map(p => + p.url).join(", "); + - step: In my browser, I evaluate {js} + js: >- + let val = await toolproof.querySelector("[data-result]"); + + toolproof.assert_eq(val.innerHTML, `/website_a/hello/, + /website_b/lorem/`); diff --git a/pagefind/integration_tests/multisite/multisite_base/pagefind-ui-skips-an-optional-index-that-fails-to-load.toolproof.yml b/pagefind/integration_tests/multisite/multisite_base/pagefind-ui-skips-an-optional-index-that-fails-to-load.toolproof.yml new file mode 100644 index 0000000..7920d19 --- /dev/null +++ b/pagefind/integration_tests/multisite/multisite_base/pagefind-ui-skips-an-optional-index-that-fails-to-load.toolproof.yml @@ -0,0 +1,42 @@ +name: Multisite Search > Pagefind UI skips an optional index that fails to load +steps: + - ref: ./background.toolproof.yml + - step: I have a "root/index.html" file with the content {html} + html: |- + + + - macro: I run Pagefind with "--site root/website_a" + - step: stdout should contain "Running Pagefind" + - step: The file "root/website_a/pagefind/pagefind.js" should not be empty + - macro: I run Pagefind with "--site root/website_b" + - step: stdout should contain "Running Pagefind" + - step: The file "root/website_b/pagefind/pagefind.js" should not be empty + - step: I serve the directory "root" + - step: In my browser, I load "/" + - step: In my browser, I evaluate {js} + js: |- + let pui = new PagefindUI({ + element: "#search", + mergeIndex: [{ + bundlePath: "/website_c/pagefind/", + optional: true + }, { + bundlePath: "/website_b/pagefind/" + }] + }); + pui.triggerSearch("web"); + - step: In my browser, I evaluate {js} + js: >- + let val = await + toolproof.querySelector(".pagefind-ui__result:nth-of-type(1) + .pagefind-ui__result-link"); + + toolproof.assert_eq(val.innerHTML, `web web world web + PAGEFIND_ROOT_SELECTOR`); + - step: In my browser, I evaluate {js} + js: >- + let val = await + toolproof.querySelector(".pagefind-ui__result:nth-of-type(2) + .pagefind-ui__result-link"); + + toolproof.assert_eq(val.innerHTML, `web ipsum`); diff --git a/pagefind_web_js/lib/coupled_search.ts b/pagefind_web_js/lib/coupled_search.ts index 91a0067..5981408 100644 --- a/pagefind_web_js/lib/coupled_search.ts +++ b/pagefind_web_js/lib/coupled_search.ts @@ -909,11 +909,20 @@ export class Pagefind { await asyncSleep(50); } - await newInstance.init(options.language || this.primaryLanguage, { - load_wasm: false, - }); + try { + await newInstance.init(options.language || this.primaryLanguage, { + load_wasm: false, + }); + } catch (e) { + if (!options.optional) throw e; + this.instances = this.instances.filter((i) => i !== newInstance); + console.warn( + `Skipping optional mergeIndex ${indexPath} that failed to load`, + ); + return; + } - const { language, ...remainingOptions } = options; + const { language, optional, ...remainingOptions } = options; await newInstance.options(remainingOptions); } diff --git a/pagefind_web_js/types/index.d.ts b/pagefind_web_js/types/index.d.ts index 2200ace..93bd77d 100644 --- a/pagefind_web_js/types/index.d.ts +++ b/pagefind_web_js/types/index.d.ts @@ -28,6 +28,12 @@ declare global { */ highlightParam?: string; language?: string; + /** + * If set, a merged index that fails to load is skipped instead of failing the whole search. + * + * Only applies in multisite setups, to indexes passed to mergeIndex. + */ + optional?: boolean; /** * Whether an instance of Pagefind is the primary index or not (for multisite). *