Graduation
/
ui
/node_modules
/@sveltejs
/vite-plugin-svelte
/src
/utils
/vite-plugin-svelte-cache.js
import { readFileSync } from 'node:fs'; | |
import { dirname } from 'node:path'; | |
import { findClosestPkgJsonPath } from 'vitefu'; | |
import { normalizePath } from 'vite'; | |
/** | |
* @typedef {{ | |
* name: string; | |
* version: string; | |
* svelte?: string; | |
* path: string; | |
* }} PackageInfo | |
*/ | |
/** | |
* @class | |
*/ | |
export class VitePluginSvelteCache { | |
/** @type {Map<string, import('../types/compile.d.ts').Code>} */ | |
#css = new Map(); | |
/** @type {Map<string, import('../types/compile.d.ts').Code>} */ | |
#js = new Map(); | |
/** @type {Map<string, string[]>} */ | |
#dependencies = new Map(); | |
/** @type {Map<string, Set<string>>} */ | |
#dependants = new Map(); | |
/** @type {Map<string, any>} */ | |
#errors = new Map(); | |
/** @type {PackageInfo[]} */ | |
#packageInfos = []; | |
/** | |
* @param {import('../types/compile.d.ts').CompileData} compileData | |
*/ | |
update(compileData) { | |
this.#errors.delete(compileData.normalizedFilename); | |
this.#updateCSS(compileData); | |
this.#updateJS(compileData); | |
this.#updateDependencies(compileData); | |
} | |
/** | |
* @param {import('../types/id.d.ts').SvelteRequest} svelteRequest | |
* @returns {boolean} | |
*/ | |
has(svelteRequest) { | |
const id = svelteRequest.normalizedFilename; | |
return this.#errors.has(id) || this.#js.has(id) || this.#css.has(id); | |
} | |
/** | |
* @param {import('../types/id.d.ts').SvelteRequest} svelteRequest | |
* @param {any} error | |
*/ | |
setError(svelteRequest, error) { | |
// keep dependency info, otherwise errors in dependants would not trigger an update after fixing | |
// because they are no longer watched | |
this.remove(svelteRequest, true); | |
this.#errors.set(svelteRequest.normalizedFilename, error); | |
} | |
/** | |
* @param {import('../types/compile.d.ts').CompileData} compileData | |
*/ | |
#updateCSS(compileData) { | |
this.#css.set(compileData.normalizedFilename, compileData.compiled.css); | |
} | |
/** | |
* @param {import('../types/compile.d.ts').CompileData} compileData | |
*/ | |
#updateJS(compileData) { | |
if (!compileData.ssr) { | |
// do not cache SSR js | |
this.#js.set(compileData.normalizedFilename, compileData.compiled.js); | |
} | |
} | |
/** | |
* @param {import('../types/compile.d.ts').CompileData} compileData | |
*/ | |
#updateDependencies(compileData) { | |
const id = compileData.normalizedFilename; | |
const prevDependencies = this.#dependencies.get(id) || []; | |
const dependencies = compileData.dependencies; | |
this.#dependencies.set(id, dependencies); | |
const removed = prevDependencies.filter((d) => !dependencies.includes(d)); | |
const added = dependencies.filter((d) => !prevDependencies.includes(d)); | |
added.forEach((d) => { | |
if (!this.#dependants.has(d)) { | |
this.#dependants.set(d, new Set()); | |
} | |
/** @type {Set<string>} */ (this.#dependants.get(d)).add(compileData.filename); | |
}); | |
removed.forEach((d) => { | |
/** @type {Set<string>} */ (this.#dependants.get(d)).delete(compileData.filename); | |
}); | |
} | |
/** | |
* @param {import('../types/id.d.ts').SvelteRequest} svelteRequest | |
* @param {boolean} [keepDependencies] | |
* @returns {boolean} | |
*/ | |
remove(svelteRequest, keepDependencies = false) { | |
const id = svelteRequest.normalizedFilename; | |
let removed = false; | |
if (this.#errors.delete(id)) { | |
removed = true; | |
} | |
if (this.#js.delete(id)) { | |
removed = true; | |
} | |
if (this.#css.delete(id)) { | |
removed = true; | |
} | |
if (!keepDependencies) { | |
const dependencies = this.#dependencies.get(id); | |
if (dependencies) { | |
removed = true; | |
dependencies.forEach((d) => { | |
const dependants = this.#dependants.get(d); | |
if (dependants && dependants.has(svelteRequest.filename)) { | |
dependants.delete(svelteRequest.filename); | |
} | |
}); | |
this.#dependencies.delete(id); | |
} | |
} | |
return removed; | |
} | |
/** | |
* @param {import('../types/id.d.ts').SvelteRequest} svelteRequest | |
* @returns {import('../types/compile.d.ts').Code | undefined} | |
*/ | |
getCSS(svelteRequest) { | |
return this.#css.get(svelteRequest.normalizedFilename); | |
} | |
/** | |
* @param {import('../types/id.d.ts').SvelteRequest} svelteRequest | |
* @returns {import('../types/compile.d.ts').Code | undefined} | |
*/ | |
getJS(svelteRequest) { | |
if (!svelteRequest.ssr) { | |
// SSR js isn't cached | |
return this.#js.get(svelteRequest.normalizedFilename); | |
} | |
} | |
/** | |
* @param {import('../types/id.d.ts').SvelteRequest} svelteRequest | |
* @returns {any} | |
*/ | |
getError(svelteRequest) { | |
return this.#errors.get(svelteRequest.normalizedFilename); | |
} | |
/** | |
* @param {string} path | |
* @returns {string[]} | |
*/ | |
getDependants(path) { | |
const dependants = this.#dependants.get(path); | |
return dependants ? [...dependants] : []; | |
} | |
/** | |
* @param {string} file | |
* @returns {Promise<PackageInfo>} | |
*/ | |
async getPackageInfo(file) { | |
let info = this.#packageInfos.find((pi) => file.startsWith(pi.path)); | |
if (!info) { | |
info = await findPackageInfo(file); | |
this.#packageInfos.push(info); | |
} | |
return info; | |
} | |
} | |
/** | |
* utility to get some info from the closest package.json with a "name" set | |
* | |
* @param {string} file to find info for | |
* @returns {Promise<PackageInfo>} | |
*/ | |
async function findPackageInfo(file) { | |
/** @type {PackageInfo} */ | |
const info = { | |
name: '$unknown', | |
version: '0.0.0-unknown', | |
path: '$unknown' | |
}; | |
let path = await findClosestPkgJsonPath(file, (pkgPath) => { | |
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8')); | |
if (pkg.name != null) { | |
info.name = pkg.name; | |
if (pkg.version != null) { | |
info.version = pkg.version; | |
} | |
info.svelte = pkg.svelte; | |
return true; | |
} | |
return false; | |
}); | |
// return normalized path with appended '/' so .startsWith works for future file checks | |
path = normalizePath(dirname(path ?? file)) + '/'; | |
info.path = path; | |
return info; | |
} | |