vite-plugin-react-pages
Version:
<p> <a href="https://www.npmjs.com/package/vite-plugin-react-pages" target="_blank" rel="noopener"><img src="https://img.shields.io/npm/v/vite-plugin-react-pages.svg" alt="npm package" /></a> </p>
181 lines (159 loc) • 4.95 kB
text/typescript
import * as path from 'path'
import chokidar, { FSWatcher } from 'chokidar'
import slash from 'slash'
import {
ModuleListener,
VirtualModuleAPIs,
VirtualModuleGraph,
} from './VirtualModules'
import { PendingTaskCounter } from './utils'
import { File } from './utils'
let nextWatcherId = 0
/**
* watch fs and update corresponding virtual module when a file changed
*/
export class VirtualModulesManager {
private watchers = new Set<FSWatcher>()
private virtualModules = new VirtualModuleGraph()
private fileCache: { [path: string]: File } = {}
/**
* don't return half-finished data when there are pending tasks
*/
private pendingTaskCounter = new PendingTaskCounter()
constructor() {
this.pendingTaskCounter.countPendingState(
this.virtualModules.updateExecutingState
)
this.pendingTaskCounter.countPendingState(
this.virtualModules.updateQueueEmptyState
)
}
public addFSWatcher(
baseDir: string,
globs: string[],
fileHandler: FileHandler
) {
const watcherId = String(nextWatcherId++)
// should wait for a complete fs scan
// before returning the page data
const fsScanFinish = this.pendingTaskCounter.countTask()
this.watchers.add(
chokidar
.watch(globs, {
cwd: baseDir,
ignored: ['**/node_modules/**/*', '**/.git/**'],
})
.on('add', this.handleFileChange(baseDir, fileHandler, watcherId))
.on('change', this.handleFileChange(baseDir, fileHandler, watcherId))
.on('unlink', this.handleFileUnLink(baseDir, watcherId))
.on('ready', () => {
setTimeout(() => {
// ready event may be fired too early,
// before initial scan callbacks are called
// https://github.com/paulmillr/chokidar/issues/1011
fsScanFinish()
}, 10)
})
)
}
public getModules(
cb: (modules: { [id: string]: any[] }) => void,
filter?: (moduleId: string) => boolean
) {
this.callOnceWhenIdle(() => {
cb(this.virtualModules.getModules(filter))
})
}
public getModule(moduleId: string, cb: (moduleData: any[]) => void) {
this.callOnceWhenIdle(() => {
cb(this.virtualModules.getModuleData(moduleId))
})
}
/**
* Idle means:
* fs watcher is ready
* no update is executing
* update queue is empty
*/
public callOnceWhenIdle(cb: () => void) {
this.pendingTaskCounter.callOnceWhenIdle(cb)
}
/**
* return the current state of modules.
* it doesn't wait for update task to finish
* so it may see intermediate state.
* use it carefully.
*/
public _getModulesNow(filter?: (moduleId: string) => boolean) {
return this.virtualModules.getModules(filter)
}
/**
* return the current state of module.
* it doesn't wait for update task to finish
* so it may see intermediate state.
* use it carefully.
*/
public _getModuleDataNow(moduleId: string) {
return this.virtualModules.getModuleData(moduleId)
}
public addModuleListener(
handler: ModuleListener,
filter?: (moduleId: string) => boolean
) {
return this.virtualModules.addModuleListener(handler, filter)
}
public close() {
this.watchers.forEach((w) => w.close())
}
public scheduleUpdate(
updaterId: string,
updater: (apis: VirtualModuleAPIs) => void | Promise<void>
): void {
return this.virtualModules.scheduleUpdate(updaterId, updater)
}
private handleFileChange(
baseDir: string,
fileHandler: FileHandler,
watcherId: string
) {
return (filePath: string) => {
filePath = slash(path.join(baseDir, filePath))
const file =
this.fileCache[filePath] ||
(this.fileCache[filePath] = new File(filePath, baseDir))
// update content cache
file.content = null
file.read()
this.virtualModules.scheduleUpdate(
`${watcherId}-${filePath}`,
async (apis) => {
const handlerAPI: FileHandlerAPIs = {
addModuleData(moduleId: string, data: any) {
apis.addModuleData(moduleId, data, filePath)
},
getModuleData: apis.getModuleData,
}
await fileHandler(file, handlerAPI)
}
)
}
}
private handleFileUnLink(baseDir: string, watcherId: string) {
return (filePath: string) => {
filePath = slash(path.join(baseDir, filePath))
this.virtualModules.scheduleUpdate(
`${watcherId}-${filePath}-unlink`,
async (apis) => {
// delete the node that represent this fs file in the virtual modules graph
// also delete all outcome edges
apis.deleteModule(filePath)
}
)
}
}
}
type FileHandler = (file: File, api: FileHandlerAPIs) => void | Promise<void>
export interface FileHandlerAPIs {
addModuleData(moduleId: string, data: any): void
getModuleData(moduleId: string): any[]
}