From e409ece6fd11920500b038722966924a2752ab37 Mon Sep 17 00:00:00 2001 From: pengzhanbo Date: Tue, 20 Feb 2024 01:40:11 +0800 Subject: [PATCH] feat: add `plugin-search` (power by minisearch) --- plugins/plugin-search/LICENSE | 21 + plugins/plugin-search/README.md | 26 + plugins/plugin-search/package.json | 63 ++ .../src/client/components/Search.vue | 72 ++ .../src/client/components/SearchBox.vue | 703 ++++++++++++++++++ .../src/client/components/SearchButton.vue | 188 +++++ .../src/client/components/icons/BackIcon.vue | 17 + .../src/client/components/icons/ClearIcon.vue | 17 + .../client/components/icons/SearchIcon.vue | 19 + .../src/client/composables/index.ts | 1 + .../src/client/composables/locale.ts | 31 + .../src/client/composables/searchIndex.ts | 19 + plugins/plugin-search/src/client/config.ts | 21 + plugins/plugin-search/src/client/index.ts | 7 + plugins/plugin-search/src/client/shim.d.ts | 20 + plugins/plugin-search/src/client/utils/lru.ts | 39 + plugins/plugin-search/src/node/index.ts | 8 + .../src/node/prepareSearchIndex.ts | 196 +++++ .../plugin-search/src/node/searchPlugin.ts | 45 ++ plugins/plugin-search/src/shared/index.ts | 47 ++ plugins/plugin-search/tsconfig.build.json | 14 + 21 files changed, 1574 insertions(+) create mode 100644 plugins/plugin-search/LICENSE create mode 100644 plugins/plugin-search/README.md create mode 100644 plugins/plugin-search/package.json create mode 100644 plugins/plugin-search/src/client/components/Search.vue create mode 100644 plugins/plugin-search/src/client/components/SearchBox.vue create mode 100644 plugins/plugin-search/src/client/components/SearchButton.vue create mode 100644 plugins/plugin-search/src/client/components/icons/BackIcon.vue create mode 100644 plugins/plugin-search/src/client/components/icons/ClearIcon.vue create mode 100644 plugins/plugin-search/src/client/components/icons/SearchIcon.vue create mode 100644 plugins/plugin-search/src/client/composables/index.ts create mode 100644 plugins/plugin-search/src/client/composables/locale.ts create mode 100644 plugins/plugin-search/src/client/composables/searchIndex.ts create mode 100644 plugins/plugin-search/src/client/config.ts create mode 100644 plugins/plugin-search/src/client/index.ts create mode 100644 plugins/plugin-search/src/client/shim.d.ts create mode 100644 plugins/plugin-search/src/client/utils/lru.ts create mode 100644 plugins/plugin-search/src/node/index.ts create mode 100644 plugins/plugin-search/src/node/prepareSearchIndex.ts create mode 100644 plugins/plugin-search/src/node/searchPlugin.ts create mode 100644 plugins/plugin-search/src/shared/index.ts create mode 100644 plugins/plugin-search/tsconfig.build.json diff --git a/plugins/plugin-search/LICENSE b/plugins/plugin-search/LICENSE new file mode 100644 index 00000000..9f677c90 --- /dev/null +++ b/plugins/plugin-search/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (C) 2021 - PRESENT by pengzhanbo + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/plugins/plugin-search/README.md b/plugins/plugin-search/README.md new file mode 100644 index 00000000..5634a110 --- /dev/null +++ b/plugins/plugin-search/README.md @@ -0,0 +1,26 @@ +# `@vuepress-plume/plugin-search` + +使用 [`minisearch`](https://lucaong.github.io/minisearch/) 实现的本地 全文模糊搜索 插件。 + +## Install + +```sh +npm install @vuepress-plume/plugin-search +# or +pnpm add @vuepress-plume/plugin-search +# or +yarn add @vuepress-plume/plugin-search +``` +## Usage +``` js +// .vuepress/config.[jt]s +import { searchPlugin } from '@vuepress-plume/plugin-search' + +export default { + // ... + plugins: [ + searchPlugin() + ] + // ... +} +``` diff --git a/plugins/plugin-search/package.json b/plugins/plugin-search/package.json new file mode 100644 index 00000000..6162a825 --- /dev/null +++ b/plugins/plugin-search/package.json @@ -0,0 +1,63 @@ +{ + "name": "@vuepress-plume/plugin-search", + "type": "module", + "version": "1.0.0-rc.35", + "description": "The Plugin for VuePres 2 - mini search", + "author": "pengzhanbo ", + "license": "MIT", + "homepage": "https://github.com/pengzhanbo/vuepress-theme-plume#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/pengzhanbo/vuepress-theme-plume.git", + "directory": "plugins/plugin-search" + }, + "bugs": { + "url": "https://github.com/pengzhanbo/vuepress-theme-plume/issues" + }, + "exports": { + ".": { + "types": "./lib/node/index.d.ts", + "import": "./lib/node/index.js" + }, + "./client": { + "types": "./lib/client/index.d.ts", + "import": "./lib/client/index.js" + }, + "./package.json": "./package.json" + }, + "main": "lib/node/index.js", + "types": "./lib/node/index.d.ts", + "files": [ + "lib" + ], + "scripts": { + "build": "pnpm run clean && pnpm run copy && pnpm run ts", + "dev": "pnpm copy --watch", + "clean": "rimraf --glob ./lib ./*.tsbuildinfo", + "copy": "cpx \"src/**/*.{d.ts,vue,css,scss,jpg,png}\" lib", + "ts": "tsc -b tsconfig.build.json" + }, + "peerDependencies": { + "vuepress": "2.0.0-rc.7" + }, + "dependencies": { + "@vuepress/helper": "2.0.0-rc.14", + "@vueuse/core": "^10.7.2", + "@vueuse/integrations": "^10.7.2", + "chokidar": "^3.6.0", + "focus-trap": "^7.5.4", + "mark.js": "^8.11.1", + "minisearch": "^6.3.0", + "p-map": "^7.0.1", + "vue": "^3.4.19" + }, + "publishConfig": { + "access": "public" + }, + "keyword": [ + "VuePress", + "vuepress plugin", + "mini search", + "vuepress-plugin-search" + ] +} diff --git a/plugins/plugin-search/src/client/components/Search.vue b/plugins/plugin-search/src/client/components/Search.vue new file mode 100644 index 00000000..b7e7d671 --- /dev/null +++ b/plugins/plugin-search/src/client/components/Search.vue @@ -0,0 +1,72 @@ + + + + + diff --git a/plugins/plugin-search/src/client/components/SearchBox.vue b/plugins/plugin-search/src/client/components/SearchBox.vue new file mode 100644 index 00000000..ef94a45f --- /dev/null +++ b/plugins/plugin-search/src/client/components/SearchBox.vue @@ -0,0 +1,703 @@ + + + + + + + diff --git a/plugins/plugin-search/src/client/components/SearchButton.vue b/plugins/plugin-search/src/client/components/SearchButton.vue new file mode 100644 index 00000000..44252e63 --- /dev/null +++ b/plugins/plugin-search/src/client/components/SearchButton.vue @@ -0,0 +1,188 @@ + + + + + diff --git a/plugins/plugin-search/src/client/components/icons/BackIcon.vue b/plugins/plugin-search/src/client/components/icons/BackIcon.vue new file mode 100644 index 00000000..a20c906a --- /dev/null +++ b/plugins/plugin-search/src/client/components/icons/BackIcon.vue @@ -0,0 +1,17 @@ + diff --git a/plugins/plugin-search/src/client/components/icons/ClearIcon.vue b/plugins/plugin-search/src/client/components/icons/ClearIcon.vue new file mode 100644 index 00000000..c1fc86d8 --- /dev/null +++ b/plugins/plugin-search/src/client/components/icons/ClearIcon.vue @@ -0,0 +1,17 @@ + diff --git a/plugins/plugin-search/src/client/components/icons/SearchIcon.vue b/plugins/plugin-search/src/client/components/icons/SearchIcon.vue new file mode 100644 index 00000000..8a2b65a9 --- /dev/null +++ b/plugins/plugin-search/src/client/components/icons/SearchIcon.vue @@ -0,0 +1,19 @@ + diff --git a/plugins/plugin-search/src/client/composables/index.ts b/plugins/plugin-search/src/client/composables/index.ts new file mode 100644 index 00000000..f7bc56cf --- /dev/null +++ b/plugins/plugin-search/src/client/composables/index.ts @@ -0,0 +1 @@ +export * from './searchIndex.js' diff --git a/plugins/plugin-search/src/client/composables/locale.ts b/plugins/plugin-search/src/client/composables/locale.ts new file mode 100644 index 00000000..50aad5cf --- /dev/null +++ b/plugins/plugin-search/src/client/composables/locale.ts @@ -0,0 +1,31 @@ +import type { MaybeRef } from 'vue' +import { useRouteLocale } from 'vuepress/client' +import { computed, toRef } from 'vue' +import type { SearchBoxLocales } from '../../shared/index.js' + +const defaultLocales: SearchBoxLocales = { + '/': { + placeholder: 'Search', + resetButtonTitle: 'Reset search', + backButtonTitle: 'Close search', + noResultsText: 'No results for', + footer: { + selectText: 'to select', + selectKeyAriaLabel: 'enter', + navigateText: 'to navigate', + navigateUpKeyAriaLabel: 'up arrow', + navigateDownKeyAriaLabel: 'down arrow', + closeText: 'to close', + closeKeyAriaLabel: 'escape', + }, + }, +} + +export function useLocale(locales: MaybeRef) { + const localesRef = toRef(locales) + const routeLocale = useRouteLocale() + + const locale = computed(() => localesRef.value[routeLocale.value] ?? defaultLocales[routeLocale.value] ?? defaultLocales['/']) + + return locale +} diff --git a/plugins/plugin-search/src/client/composables/searchIndex.ts b/plugins/plugin-search/src/client/composables/searchIndex.ts new file mode 100644 index 00000000..5b136422 --- /dev/null +++ b/plugins/plugin-search/src/client/composables/searchIndex.ts @@ -0,0 +1,19 @@ +import { searchIndex } from '@internal/minisearchIndex' +import { shallowRef } from 'vue' + +declare const __VUE_HMR_RUNTIME__: Record + +const searchIndexData = shallowRef(searchIndex) + +export function useSearchIndex() { + return searchIndexData +} + +if (__VUEPRESS_DEV__ && (import.meta.webpackHot || import.meta.hot)) { + __VUE_HMR_RUNTIME__.updateSearchIndex = (data) => { + searchIndexData.value = data + } + __VUE_HMR_RUNTIME__.updateSearchIndex = (data) => { + searchIndexData.value = data + } +} diff --git a/plugins/plugin-search/src/client/config.ts b/plugins/plugin-search/src/client/config.ts new file mode 100644 index 00000000..57fda3e3 --- /dev/null +++ b/plugins/plugin-search/src/client/config.ts @@ -0,0 +1,21 @@ +import { defineClientConfig } from 'vuepress/client' +import type { ClientConfig } from 'vuepress/client' +import { h } from 'vue' +import type { SearchBoxLocales, SearchOptions } from '../shared/index.js' +import Search from './components/Search.vue' + +declare const __SEARCH_LOCALES__: SearchBoxLocales +declare const __SEARCH_OPTIONS__: SearchOptions + +const locales = __SEARCH_LOCALES__ +const searchOptions = __SEARCH_OPTIONS__ + +export default defineClientConfig({ + enhance({ app }) { + app.component('SearchBox', props => h(Search, { + locales, + options: searchOptions, + ...props, + })) + }, +}) as ClientConfig diff --git a/plugins/plugin-search/src/client/index.ts b/plugins/plugin-search/src/client/index.ts new file mode 100644 index 00000000..e56355ad --- /dev/null +++ b/plugins/plugin-search/src/client/index.ts @@ -0,0 +1,7 @@ +import SearchBox from './components/Search.vue' +import { useSearchIndex } from './composables/index.js' + +export { + SearchBox, + useSearchIndex, +} diff --git a/plugins/plugin-search/src/client/shim.d.ts b/plugins/plugin-search/src/client/shim.d.ts new file mode 100644 index 00000000..9174445f --- /dev/null +++ b/plugins/plugin-search/src/client/shim.d.ts @@ -0,0 +1,20 @@ +declare module '*.vue' { + import type { ComponentOptions } from 'vue' + + const comp: ComponentOptions + export default comp +} + +declare module '@internal/minisearchIndex' { + const searchIndex: Record Promise<{ default: string }>> + export { + searchIndex, + } +} + +declare module 'mark.js/src/vanilla.js' { + import type Mark from 'mark.js' + + const mark: typeof Mark + export default mark +} diff --git a/plugins/plugin-search/src/client/utils/lru.ts b/plugins/plugin-search/src/client/utils/lru.ts new file mode 100644 index 00000000..191a6f0c --- /dev/null +++ b/plugins/plugin-search/src/client/utils/lru.ts @@ -0,0 +1,39 @@ +// adapted from https://stackoverflow.com/a/46432113/11613622 + +export class LRUCache { + private max: number + private cache: Map + + constructor(max: number = 10) { + this.max = max + this.cache = new Map() + } + + get(key: K): V | undefined { + const item = this.cache.get(key) + if (item !== undefined) { + // refresh key + this.cache.delete(key) + this.cache.set(key, item) + } + return item + } + + set(key: K, val: V): void { + // refresh key + if (this.cache.has(key)) + this.cache.delete(key) + // evict oldest + else if (this.cache.size === this.max) + this.cache.delete(this.first()!) + this.cache.set(key, val) + } + + first(): K | undefined { + return this.cache.keys().next().value + } + + clear(): void { + this.cache.clear() + } +} diff --git a/plugins/plugin-search/src/node/index.ts b/plugins/plugin-search/src/node/index.ts new file mode 100644 index 00000000..3f3c644c --- /dev/null +++ b/plugins/plugin-search/src/node/index.ts @@ -0,0 +1,8 @@ +import { searchPlugin } from './searchPlugin.js' + +export { prepareSearchIndex } from './prepareSearchIndex.js' +export * from '../shared/index.js' + +export { + searchPlugin, +} diff --git a/plugins/plugin-search/src/node/prepareSearchIndex.ts b/plugins/plugin-search/src/node/prepareSearchIndex.ts new file mode 100644 index 00000000..ff531e98 --- /dev/null +++ b/plugins/plugin-search/src/node/prepareSearchIndex.ts @@ -0,0 +1,196 @@ +import type { App, Page } from 'vuepress/core' +import MiniSearch from 'minisearch' +import pMap from 'p-map' +import type { SearchOptions, SearchPluginOptions } from '../shared/index.js' + +export interface SearchIndexOptions { + app: App + searchOptions: SearchOptions + isSearchable: SearchPluginOptions['isSearchable'] +} + +interface IndexObject { + id: string + text: string + title: string + titles: string[] +} + +const SEARCH_INDEX_DIR = 'internal/minisearchIndex/' +const indexByLocales = new Map>() +const indexCache = new Map() + +function getIndexByLocale(locale: string, options: SearchIndexOptions['searchOptions']) { + let index = indexByLocales.get(locale) + if (!index) { + index = new MiniSearch({ + fields: ['title', 'titles', 'text'], + storeFields: ['title', 'titles'], + ...options.miniSearch?.options, + }) + indexByLocales.set(locale, index) + } + return index +} + +function getIndexCache(filepath: string) { + let index = indexCache.get(filepath) + if (!index) { + index = [] + indexCache.set(filepath, index) + } + return index +} + +export async function prepareSearchIndex({ + app, + isSearchable, + searchOptions, +}: SearchIndexOptions) { + const pages = isSearchable ? app.pages.filter(isSearchable) : app.pages + await pMap(pages, p => indexFile(p, searchOptions), { + concurrency: 64, + }) + await writeTemp(app) +} + +export async function onSearchIndexUpdated( + filepath: string, + { + app, + isSearchable, + searchOptions, + }: SearchIndexOptions, +) { + const pages = isSearchable ? app.pages.filter(isSearchable) : app.pages + if (pages.some(p => p.filePathRelative?.endsWith(filepath))) { + await indexFile(app.pages.find(p => p.filePathRelative?.endsWith(filepath))!, searchOptions) + await writeTemp(app) + } +} + +export async function onSearchIndexRemoved( + filepath: string, + { + app, + isSearchable, + searchOptions, + }: SearchIndexOptions, +) { + const pages = isSearchable ? app.pages.filter(isSearchable) : app.pages + if (pages.some(p => p.filePathRelative?.endsWith(filepath))) { + const page = app.pages.find(p => p.filePathRelative?.endsWith(filepath))! + const fileId = page.path + const locale = page.pathLocale + const index = getIndexByLocale(locale, searchOptions) + const cache = getIndexCache(fileId) + index.removeAll(cache) + await writeTemp(app) + } +} + +async function writeTemp(app: App) { + const records: string[] = [] + for (const [locale] of indexByLocales) { + const index = indexByLocales.get(locale)! + const localeName = locale.replace(/^\/|\/$/g, '').replace(/\//g, '_') || 'default' + const filename = `searchBox-${localeName}.js` + records.push(`${JSON.stringify(locale)}: () => import('@${SEARCH_INDEX_DIR}${filename}')`) + await app.writeTemp( + `${SEARCH_INDEX_DIR}${filename}`, + `export default ${JSON.stringify( + JSON.stringify(index) ?? {}, + )}`, + ) + } + await app.writeTemp( + `${SEARCH_INDEX_DIR}index.js`, + `export const searchIndex = {${records.join(',')}}${app.env.isDev ? `\n${genHmrCode('searchIndex')}` : ''}`, + ) +} + +async function indexFile(page: Page, options: SearchIndexOptions['searchOptions']) { + // get file metadata + const fileId = page.path + const locale = page.pathLocale + const index = getIndexByLocale(locale, options) + const cache = getIndexCache(fileId) + // retrieve file and split into "sections" + const html = page.contentRendered + const sections = splitPageIntoSections(html) + + if (cache && cache.length) + index.removeAll(cache) + + // add sections to the locale index + for await (const section of sections) { + if (!section || !(section.text || section.titles)) + break + const { anchor, text, titles } = section + const id = anchor ? [fileId, anchor].join('#') : fileId + const item = { + id, + text, + title: titles.at(-1)!, + titles: titles.slice(0, -1), + } + index.add(item) + cache.push(item) + } +} + +const headingRegex = /(.*?<\/a>)<\/h\1>/gi +const headingContentRegex = /(.*?)<\/a>/i + +/** + * Splits HTML into sections based on headings + */ +function* splitPageIntoSections(html: string) { + const result = html.split(headingRegex) + result.shift() + let parentTitles: string[] = [] + for (let i = 0; i < result.length; i += 3) { + const level = Number.parseInt(result[i]) - 1 + const heading = result[i + 1] + const headingResult = headingContentRegex.exec(heading) + const title = clearHtmlTags(headingResult?.[2] ?? '').trim() + const anchor = headingResult?.[1] ?? '' + const content = result[i + 2] + if (!title || !content) + continue + const titles = parentTitles.slice(0, level) + titles[level] = title + yield { anchor, titles, text: getSearchableText(content) } + if (level === 0) + parentTitles = [title] + else + parentTitles[level] = title + } +} + +function getSearchableText(content: string) { + content = clearHtmlTags(content) + return content +} + +function clearHtmlTags(str: string) { + return str.replace(/<[^>]*>/g, '') +} + +function genHmrCode(m: string) { + const func = `update${m[0].toUpperCase()}${m.slice(1)}` + return ` +if (import.meta.webpackHot) { + import.meta.webpackHot.accept() + if (__VUE_HMR_RUNTIME__.${m}) { + __VUE_HMR_RUNTIME__.${func}(${m}) + } +} + +if (import.meta.hot) { + import.meta.hot.accept(({ ${m} }) => { + __VUE_HMR_RUNTIME__.${func}(${m}) + }) +} +` +} diff --git a/plugins/plugin-search/src/node/searchPlugin.ts b/plugins/plugin-search/src/node/searchPlugin.ts new file mode 100644 index 00000000..134f9eea --- /dev/null +++ b/plugins/plugin-search/src/node/searchPlugin.ts @@ -0,0 +1,45 @@ +import chokidar from 'chokidar' +import type { Plugin } from 'vuepress/core' +import { getDirname, path } from 'vuepress/utils' +import { addViteOptimizeDepsInclude } from '@vuepress/helper' +import type { SearchPluginOptions } from '../shared/index.js' +import { onSearchIndexRemoved, onSearchIndexUpdated, prepareSearchIndex } from './prepareSearchIndex.js' + +const __dirname = getDirname(import.meta.url) + +export function searchPlugin({ + locales = {}, + isSearchable, + ...searchOptions +}: SearchPluginOptions = {}): Plugin { + return app => ({ + name: '@vuepress-plume/plugin-search', + clientConfigFile: path.resolve(__dirname, '../client/config.js'), + define: { + __SEARCH_LOCALES__: locales, + __SEARCH_OPTIONS__: searchOptions, + }, + extendsBundlerOptions(bundlerOptions) { + addViteOptimizeDepsInclude(bundlerOptions, app, ['mark.js/src/vanilla.js', '@vueuse/integrations/useFocusTrap', 'minisearch']) + }, + onPrepared: async (app) => { + await prepareSearchIndex({ app, isSearchable, searchOptions }) + }, + onWatched: async (app, watchers) => { + const searchIndexWatcher = chokidar.watch('pages/**/*.js', { + cwd: app.dir.temp(), + ignoreInitial: true, + }) + searchIndexWatcher.on('add', (filepath) => { + onSearchIndexUpdated(filepath, { app, isSearchable, searchOptions }) + }) + searchIndexWatcher.on('change', (filepath) => { + onSearchIndexUpdated(filepath, { app, isSearchable, searchOptions }) + }) + searchIndexWatcher.on('unlink', (filepath) => { + onSearchIndexRemoved(filepath, { app, isSearchable, searchOptions }) + }) + watchers.push(searchIndexWatcher) + }, + }) +} diff --git a/plugins/plugin-search/src/shared/index.ts b/plugins/plugin-search/src/shared/index.ts new file mode 100644 index 00000000..688c255b --- /dev/null +++ b/plugins/plugin-search/src/shared/index.ts @@ -0,0 +1,47 @@ +import type { LocaleConfig, Page } from 'vuepress/core' +import type { Options as MiniSearchOptions } from 'minisearch' + +export type SearchBoxLocales = LocaleConfig<{ + placeholder: string + buttonText: string + resetButtonTitle: string + backButtonTitle: string + noResultsText: string + footer: { + selectText: string + selectKeyAriaLabel: string + navigateText: string + navigateUpKeyAriaLabel: string + navigateDownKeyAriaLabel: string + closeText: string + closeKeyAriaLabel: string + } +}> + +export interface SearchPluginOptions extends SearchOptions { + locales?: SearchBoxLocales + + isSearchable?: (page: Page) => boolean + +} + +export interface SearchOptions { + /** + * @default false + */ + disableQueryPersistence?: boolean + + miniSearch?: { + /** + * @see https://lucaong.github.io/minisearch/modules/_minisearch_.html#options + */ + options?: Pick< + MiniSearchOptions, + 'extractField' | 'tokenize' | 'processTerm' + > + /** + * @see https://lucaong.github.io/minisearch/modules/_minisearch_.html#searchoptions-1 + */ + searchOptions?: MiniSearchOptions['searchOptions'] + } +} diff --git a/plugins/plugin-search/tsconfig.build.json b/plugins/plugin-search/tsconfig.build.json new file mode 100644 index 00000000..39382ae6 --- /dev/null +++ b/plugins/plugin-search/tsconfig.build.json @@ -0,0 +1,14 @@ +{ + "extends": "../tsconfig.build.json", + "compilerOptions": { + "baseUrl": ".", + "rootDir": "./src", + "types": [ + "vuepress/client-types", + "vite/client", + "webpack-env" + ], + "outDir": "./lib" + }, + "include": ["./src"] +}