1
1
import _debug from 'debug'
2
2
import fs from 'fs-extra'
3
3
import MiniSearch from 'minisearch'
4
+ import pMap from 'p-map'
4
5
import path from 'path'
5
6
import type { Plugin , ViteDevServer } from 'vite'
6
7
import type { SiteConfig } from '../config'
@@ -53,15 +54,18 @@ export async function localSearchPlugin(
53
54
54
55
const options = siteConfig . site . themeConfig . search . options || { }
55
56
56
- function render ( file : string ) {
57
+ async function render ( file : string ) {
58
+ if ( ! fs . existsSync ( file ) ) return ''
57
59
const { srcDir, cleanUrls = false } = siteConfig
58
60
const relativePath = slash ( path . relative ( srcDir , file ) )
59
61
const env : MarkdownEnv = { path : file , relativePath, cleanUrls }
60
- let src = fs . readFileSync ( file , 'utf-8' )
61
- src = processIncludes ( srcDir , src , file , [ ] )
62
- if ( options . _render ) return options . _render ( src , env , md )
63
- const html = md . render ( src , env )
64
- return env . frontmatter ?. search === false ? '' : html
62
+ const md_raw = await fs . promises . readFile ( file , 'utf-8' )
63
+ const md_src = processIncludes ( srcDir , md_raw , file , [ ] )
64
+ if ( options . _render ) return await options . _render ( md_src , env , md )
65
+ else {
66
+ const html = md . render ( md_src , env )
67
+ return env . frontmatter ?. search === false ? '' : html
68
+ }
65
69
}
66
70
67
71
const indexByLocales = new Map < string , MiniSearch < IndexObject > > ( )
@@ -85,11 +89,6 @@ export async function localSearchPlugin(
85
89
return siteData ?. localeIndex ?? 'root'
86
90
}
87
91
88
- function getIndexForPath ( file : string ) {
89
- const locale = getLocaleForPath ( file )
90
- return getIndexByLocale ( locale )
91
- }
92
-
93
92
let server : ViteDevServer | undefined
94
93
95
94
function onIndexUpdated ( ) {
@@ -123,43 +122,39 @@ export async function localSearchPlugin(
123
122
return id
124
123
}
125
124
126
- async function indexAllFiles ( files : string [ ] ) {
127
- const documentsByLocale = new Map < string , IndexObject [ ] > ( )
128
- await Promise . all (
129
- files
130
- . filter ( ( file ) => fs . existsSync ( file ) )
131
- . map ( async ( file ) => {
132
- const fileId = getDocId ( file )
133
- const sections = splitPageIntoSections ( render ( file ) )
134
- if ( sections . length === 0 ) return
135
- const locale = getLocaleForPath ( file )
136
- let documents = documentsByLocale . get ( locale )
137
- if ( ! documents ) {
138
- documents = [ ]
139
- documentsByLocale . set ( locale , documents )
140
- }
141
- documents . push (
142
- ...sections . map ( ( section ) => ( {
143
- id : `${ fileId } #${ section . anchor } ` ,
144
- text : section . text ,
145
- title : section . titles . at ( - 1 ) ! ,
146
- titles : section . titles . slice ( 0 , - 1 )
147
- } ) )
148
- )
149
- } )
150
- )
151
- for ( const [ locale , documents ] of documentsByLocale ) {
152
- const index = getIndexByLocale ( locale )
153
- index . removeAll ( )
154
- await index . addAllAsync ( documents )
125
+ async function indexFile ( page : string ) {
126
+ const file = path . join ( siteConfig . srcDir , page )
127
+ // get file metadata
128
+ const fileId = getDocId ( file )
129
+ const locale = getLocaleForPath ( file )
130
+ const index = getIndexByLocale ( locale )
131
+ // retrieve file and split into "sections"
132
+ const html = await render ( file )
133
+ const sections =
134
+ // user provided generator
135
+ ( await options . miniSearch ?. _splitIntoSections ?.( file , html ) ) ??
136
+ // default implementation
137
+ splitPageIntoSections ( html )
138
+ // add sections to the locale index
139
+ for await ( const section of sections ) {
140
+ if ( ! section || ! ( section . text || section . titles ) ) break
141
+ const { anchor, text, titles } = section
142
+ const id = anchor ? [ fileId , anchor ] . join ( '#' ) : fileId
143
+ index . add ( {
144
+ id,
145
+ text,
146
+ title : titles . at ( - 1 ) ! ,
147
+ titles : titles . slice ( 0 , - 1 )
148
+ } )
155
149
}
156
- debug ( `🔍️ Indexed ${ files . length } files` )
157
150
}
158
151
159
152
async function scanForBuild ( ) {
160
- await indexAllFiles (
161
- siteConfig . pages . map ( ( f ) => path . join ( siteConfig . srcDir , f ) )
162
- )
153
+ debug ( '🔍️ Indexing files for search...' )
154
+ await pMap ( siteConfig . pages , indexFile , {
155
+ concurrency : siteConfig . buildConcurrency
156
+ } )
157
+ debug ( '✅ Indexing finished...' )
163
158
}
164
159
165
160
return {
@@ -214,25 +209,8 @@ export async function localSearchPlugin(
214
209
215
210
async handleHotUpdate ( { file } ) {
216
211
if ( file . endsWith ( '.md' ) ) {
217
- const fileId = getDocId ( file )
218
- if ( ! fs . existsSync ( file ) ) return
219
- const index = getIndexForPath ( file )
220
- const sections = splitPageIntoSections ( render ( file ) )
221
- if ( sections . length === 0 ) return
222
- for ( const section of sections ) {
223
- const id = `${ fileId } #${ section . anchor } `
224
- if ( index . has ( id ) ) {
225
- index . discard ( id )
226
- }
227
- index . add ( {
228
- id,
229
- text : section . text ,
230
- title : section . titles . at ( - 1 ) ! ,
231
- titles : section . titles . slice ( 0 , - 1 )
232
- } )
233
- }
212
+ await indexFile ( file )
234
213
debug ( '🔍️ Updated' , file )
235
-
236
214
onIndexUpdated ( )
237
215
}
238
216
}
@@ -242,20 +220,13 @@ export async function localSearchPlugin(
242
220
const headingRegex = / < h ( \d * ) .* ?> ( .* ?< a .* ? h r e f = " # .* ?" .* ?> .* ?< \/ a > ) < \/ h \1> / gi
243
221
const headingContentRegex = / ( .* ?) < a .* ? h r e f = " # ( .* ?) " .* ?> .* ?< \/ a > / i
244
222
245
- interface PageSection {
246
- anchor : string
247
- titles : string [ ]
248
- text : string
249
- }
250
-
251
223
/**
252
224
* Splits HTML into sections based on headings
253
225
*/
254
- function splitPageIntoSections ( html : string ) {
226
+ function * splitPageIntoSections ( html : string ) {
255
227
const result = html . split ( headingRegex )
256
228
result . shift ( )
257
229
let parentTitles : string [ ] = [ ]
258
- const sections : PageSection [ ] = [ ]
259
230
for ( let i = 0 ; i < result . length ; i += 3 ) {
260
231
const level = parseInt ( result [ i ] ) - 1
261
232
const heading = result [ i + 1 ]
@@ -266,14 +237,13 @@ function splitPageIntoSections(html: string) {
266
237
if ( ! title || ! content ) continue
267
238
const titles = parentTitles . slice ( 0 , level )
268
239
titles [ level ] = title
269
- sections . push ( { anchor, titles, text : getSearchableText ( content ) } )
240
+ yield { anchor, titles, text : getSearchableText ( content ) }
270
241
if ( level === 0 ) {
271
242
parentTitles = [ title ]
272
243
} else {
273
244
parentTitles [ level ] = title
274
245
}
275
246
}
276
- return sections
277
247
}
278
248
279
249
function getSearchableText ( content : string ) {
0 commit comments