Skip to content

Commit

Permalink
feat(search-pro): improve createWorker api
Browse files Browse the repository at this point in the history
  • Loading branch information
Mister-Hope committed Mar 7, 2024
1 parent a5d3649 commit 7789c89
Show file tree
Hide file tree
Showing 9 changed files with 167 additions and 67 deletions.
16 changes: 14 additions & 2 deletions docs/search-pro/src/config.md
Expand Up @@ -427,10 +427,22 @@ interface SearchResult {
}

interface SearchWorker {
all: (
query: string,
locale?: string,
options?: SearchOptions,
) => Promise<QueryResult>;

suggest: (
query: string,
locale?: string,
options?: SearchOptions,
) => Promise<string[]>;

search: (
query: string,
locale: string,
searchOptions?: SearchOptions,
locale?: string,
options?: SearchOptions,
) => Promise<SearchResult[]>;
terminate: () => void;
}
Expand Down
16 changes: 13 additions & 3 deletions docs/search-pro/src/guide.md
Expand Up @@ -287,11 +287,21 @@ If you want to use the search API, you need to import the `createSearchWorker` f
import { defineClientConfig } from "vuepress/client";
import { createSearchWorker } from "vuepress-plugin-search-pro/client";

const { search, terminate } = createSearchWorker();
const { all, suggest, search, terminate } = createSearchWorker();

// use search API
// suggest something
suggest("key").then((suggestions) => {
// display search suggestions
});

// search something
search("keyword").then((results) => {
// use search results
// display search results
});

// return both suggestions and results
all("key").then(({ suggestions, results }) => {
// display search suggestions and results
});

// terminate the worker when you don't need it
Expand Down
16 changes: 14 additions & 2 deletions docs/search-pro/src/zh/config.md
Expand Up @@ -403,10 +403,22 @@ interface SearchResult {
}

interface SearchWorker {
all: (
query: string,
locale?: string,
options?: SearchOptions,
) => Promise<QueryResult>;

suggest: (
query: string,
locale?: string,
options?: SearchOptions,
) => Promise<string[]>;

search: (
query: string,
locale: string,
searchOptions?: SearchOptions,
locale?: string,
options?: SearchOptions,
) => Promise<SearchResult[]>;
terminate: () => void;
}
Expand Down
16 changes: 13 additions & 3 deletions docs/search-pro/src/zh/guide.md
Expand Up @@ -296,11 +296,21 @@ export default defineClientConfig({
```ts
import { createSearchWorker } from "vuepress-plugin-search-pro/client";

const { search, terminate } = createSearchWorker();
const { all, suggest, search, terminate } = createSearchWorker();

// 使用搜索 API
// 自动建议
suggest("key").then((suggestions) => {
// 显示建议
});

// 搜素
search("keyword").then((results) => {
// 使用结果
// 显示搜索结果
});

// 同时返回建议和搜索结果
all("key").then(({ suggestions, results }) => {
// 显示建议和搜索结果
});

// 当不需要时终止 Worker
Expand Down
10 changes: 3 additions & 7 deletions packages/search-pro/src/client/composables/search.ts
Expand Up @@ -16,12 +16,13 @@ export interface SearchRef {
export const useSearchResult = (query: Ref<string>): SearchRef => {
const searchOptions = useSearchOptions();
const routeLocale = useRouteLocale();
const { search, terminate } = createSearchWorker();

const searching = ref(false);
const results = shallowRef<SearchResult[]>([]);

onMounted(() => {
const { search, terminate } = createSearchWorker();

const endSearch = (): void => {
results.value = [];
searching.value = false;
Expand All @@ -31,12 +32,7 @@ export const useSearchResult = (query: Ref<string>): SearchRef => {
searching.value = true;

if (queryString)
void search({
type: "search",
query: queryString,
locale: routeLocale.value,
options: searchOptions.value,
})
void search(queryString, routeLocale.value, searchOptions.value)
.then((searchResults) => {
results.value = searchResults;
searching.value = false;
Expand Down
11 changes: 3 additions & 8 deletions packages/search-pro/src/client/composables/suggestions.ts
Expand Up @@ -20,14 +20,11 @@ export const useSearchSuggestions = (query: Ref<string>): SuggestionsRef => {
const routeLocale = useRouteLocale();

onMounted(() => {
const { suggest, terminate } = createSearchWorker();

const performAutoSuggest = useDebounceFn((queryString: string): void => {
if (queryString)
void search({
type: "suggest",
query: queryString,
locale: routeLocale.value,
options: searchOptions.value,
})
void suggest(queryString, routeLocale.value, searchOptions.value)
.then((_suggestions) => {
suggestions.value = _suggestions.length
? startsWith(_suggestions[0], queryString) &&
Expand All @@ -42,8 +39,6 @@ export const useSearchSuggestions = (query: Ref<string>): SuggestionsRef => {
else suggestions.value = [];
}, searchProOptions.suggestDelay);

const { search, terminate } = createSearchWorker();

watch([query, routeLocale], () => performAutoSuggest(query.value), {
immediate: true,
});
Expand Down
1 change: 1 addition & 0 deletions packages/search-pro/src/client/typings/worker.ts
Expand Up @@ -28,4 +28,5 @@ export interface MessageData {
query: string;
locale: string;
options?: SearchOptions;
id: number;
}
128 changes: 92 additions & 36 deletions packages/search-pro/src/client/utils/searchWorker.ts
@@ -1,23 +1,37 @@
import { values } from "@vuepress/helper/client";
import type { SearchOptions } from "slimsearch";

import { clientWorker, searchProOptions } from "../define.js";
import type {
MessageData,
QueryResult,
SearchResult,
} from "../typings/index.js";
import type { QueryResult, SearchResult } from "../typings/index.js";

declare const __VUEPRESS_BASE__: string;
declare const __VUEPRESS_DEV__: boolean;

interface PromiseItem {
id: number;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
resolve: (args: any) => void;
reject: (err: Error) => void;
}

export interface SearchWorker {
search: <T extends MessageData>(
options: T,
) => Promise<
T["type"] extends "search"
? SearchResult[]
: T["type"] extends "suggest"
? string[]
: QueryResult
>;
all: (
query: string,
locale?: string,
options?: SearchOptions,
) => Promise<QueryResult>;

suggest: (
query: string,
locale?: string,
options?: SearchOptions,
) => Promise<string[]>;

search: (
query: string,
locale?: string,
options?: SearchOptions,
) => Promise<SearchResult[]>;
terminate: () => void;
}

Expand All @@ -29,40 +43,82 @@ export const createSearchWorker = (): SearchWorker => {
: `${__VUEPRESS_BASE__}${searchProOptions.worker}`,
__VUEPRESS_DEV__ ? { type: "module" } : {},
);
const queue: {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
resolve: (args: any) => void;
reject: (err: Error) => void;
}[] = [];
const queues = {
suggest: [] as PromiseItem[],
search: [] as PromiseItem[],
all: [] as PromiseItem[],
};

worker.addEventListener(
"message",
({ data }: MessageEvent<SearchResult[]>) => {
const { resolve } = queue.shift()!;
({
data,
}: MessageEvent<
| ["suggest", number, string[]]
| ["search", number, SearchResult[]]
| ["all", number, QueryResult]
>) => {
const [type, timestamp, result] = data;
const queue = queues[type];
const index = queue.findIndex(({ id }) => id === timestamp);

resolve(data);
if (index > -1) {
const { resolve } = queue[index];

queue.forEach((item, i) => {
if (i > index) item.reject(new Error("Search has been canceled."));
});
queues[type] = queue.slice(index + 1);
resolve(result);
}
},
);

return {
search: <T extends MessageData>(
options: T,
): Promise<
T["type"] extends "search"
? SearchResult[]
: T["type"] extends "suggest"
? string[]
: QueryResult
> =>
suggest: (
query: string,
locale?: string,
options?: SearchOptions,
): Promise<string[]> =>
new Promise((resolve, reject) => {
worker.postMessage(options);
queue.push({ resolve, reject });
const id = Date.now();

worker.postMessage({ type: "suggest", id, query, locale, options });
queues.suggest.push({ id, resolve, reject });
}),

search: (
query: string,
locale?: string,
options?: SearchOptions,
): Promise<SearchResult[]> =>
new Promise<SearchResult[]>((resolve, reject) => {
const id = Date.now();

worker.postMessage({ type: "search", id, query, locale, options });
queues.search.push({ id, resolve, reject });
}),

all: (
query: string,
locale?: string,
options?: SearchOptions,
): Promise<QueryResult> =>
new Promise<QueryResult>((resolve, reject) => {
const id = Date.now();

worker.postMessage({ type: "all", id, query, locale, options });
queues.all.push({ id, resolve, reject });
}),

terminate: (): void => {
worker.terminate();
queue.forEach(({ reject }) =>
reject(new Error("Worker has been terminated.")),
);

values(queues).forEach((item) => {
item.forEach(({ reject }) =>
reject(new Error("Worker has been terminated.")),
);
});
},
};
};
20 changes: 14 additions & 6 deletions packages/search-pro/src/client/worker/index.ts
Expand Up @@ -8,9 +8,9 @@ import type { IndexItem } from "../../shared/index.js";
import type { MessageData } from "../typings/index.js";

self.onmessage = async ({
data: { type = "all", query, locale, options },
data: { type = "all", query, locale, options, id },
}: MessageEvent<MessageData>): Promise<void> => {
const { default: localeIndex } = await database[locale]();
const { default: localeIndex } = await database[locale ?? "/"]();

const searchLocaleIndex = loadJSONIndex<string, IndexItem, IndexItem>(
localeIndex,
Expand All @@ -25,12 +25,20 @@ self.onmessage = async ({
);

if (type === "suggest")
self.postMessage(getSuggestions(query, searchLocaleIndex, options));
self.postMessage([
type,
id,
getSuggestions(query, searchLocaleIndex, options),
]);
else if (type === "search")
self.postMessage(getResults(query, searchLocaleIndex, options));
self.postMessage([type, id, getResults(query, searchLocaleIndex, options)]);
else
self.postMessage({
suggestions: getSuggestions(query, searchLocaleIndex, options),
results: getResults(query, searchLocaleIndex, options),
suggestions: [
type,
id,
getSuggestions(query, searchLocaleIndex, options),
],
results: [type, id, getResults(query, searchLocaleIndex, options)],
});
};

0 comments on commit 7789c89

Please sign in to comment.