UNPKG

vike

Version:

The Framework *You* Control - Next.js & Nuxt alternative for unprecedented flexibility and dependability.

210 lines (209 loc) 9.12 kB
export { pluginVirtualFiles }; import { normalizePath } from 'vite'; import { getVirtualFilePageConfigLazy } from './pluginVirtualFiles/getVirtualFilePageConfigLazy.js'; import { getVirtualFileEntry } from './pluginVirtualFiles/getVirtualFileEntry.js'; import { assert, assertPosixPath, isScriptFile } from '../utils.js'; import { resolveVirtualFileId, isVirtualFileId, getVirtualFileId } from '../../shared/virtualFiles.js'; import { isVirtualFileIdPageConfigLazy } from '../../shared/virtualFiles/virtualFilePageConfigLazy.js'; import { isVirtualFileIdEntry } from '../../shared/virtualFiles/virtualFileEntry.js'; import { reloadVikeConfig, isV1Design, getVikeConfigInternalOptional } from '../shared/resolveVikeConfigInternal.js'; import pc from '@brillout/picocolors'; import { logConfigInfo } from '../shared/loggerNotProd.js'; import { getModuleFilePathAbsolute } from '../shared/getFilePath.js'; import { updateUserFiles } from '../../runtime/globalContext.js'; import { isPlusFile } from '../shared/resolveVikeConfigInternal/crawlPlusFiles.js'; import { isTemporaryBuildFile } from '../shared/resolveVikeConfigInternal/transpileAndExecuteFile.js'; import { getVikeConfigError } from '../../shared/getVikeConfigError.js'; function pluginVirtualFiles() { let config; return { name: 'vike:pluginVirtualFiles', async configResolved(config_) { config = config_; // TODO/v1-release: remove if (!isV1Design()) config.experimental.importGlobRestoreExtension = true; }, resolveId(id) { if (isVirtualFileId(id)) { return resolveVirtualFileId(id); } }, async handleHotUpdate(ctx) { try { return await handleHotUpdate(ctx, config); } catch (err) { // Vite swallows errors thrown by handleHotUpdate() console.error(err); throw err; } }, async load(id, options) { if (!isVirtualFileId(id)) return undefined; id = getVirtualFileId(id); const isDev = config._isDev; assert(typeof isDev === 'boolean'); if (isVirtualFileIdPageConfigLazy(id)) { const code = await getVirtualFilePageConfigLazy(id, isDev, config); return code; } if (isVirtualFileIdEntry(id)) { const code = await getVirtualFileEntry(id, options, config, isDev); return code; } }, configureServer(server) { handleFileAddRemove(server, config); }, }; } function handleFileAddRemove(server, config) { server.watcher.prependListener('add', (f) => listener(f, false)); server.watcher.prependListener('unlink', (f) => listener(f, true)); return; async function listener(file, isRemove) { file = normalizePath(file); if (isTemporaryBuildFile(file)) return; const { moduleGraph } = server; const isVikeConfigDep = await isVikeConfigDependency(file, moduleGraph); const reload = () => reloadConfig(file, config, isRemove ? 'removed' : 'created', server); // Config code if (isVikeConfigDep && !isVikeConfigDep.isProcessedByVite) { reload(); return; } // New or deleted + file if (isPlusFile(file)) { reload(); return; } // Runtime code => let Vite handle it if (isVikeConfigDep && isVikeConfigDep.isProcessedByVite) { assert(existsInViteModuleGraph(file, moduleGraph)); return; } // Trick: when importing a file that doesn't exist => we don't know whether `file` is that missing file => we take a leap of faith when the conditions below are met. // - Not sure how reliable that trick is. // - Reloading Vike's config is cheap and file creation/removal is rare => the trick is worth it. // - Reproduction: // ```bash // rm someDep.js && sleep 2 && git checkout someDep.js // ``` if (isScriptFile(file) && getVikeConfigError() && !existsInViteModuleGraph(file, moduleGraph)) { reload(); return; } } } function invalidateVikeVirtualFiles(server) { const vikeVirtualFiles = getVikeVirtualFiles(server); vikeVirtualFiles.forEach((mod) => { server.moduleGraph.invalidateModule(mod); }); } // Vite calls its hook handleHotUpdate() whenever *any file* is modified — including files that aren't in Vite's module graph such as `pages/+config.js` async function handleHotUpdate(ctx, config) { const { file, server } = ctx; const isVikeConfigDep = await isVikeConfigDependency(ctx.file, ctx.server.moduleGraph); if (isVikeConfigDep) { if (!isVikeConfigDep.isProcessedByVite) { /* Tailwind breaks this assertion, see https://github.com/vikejs/vike/discussions/1330#discussioncomment-7787238 const isViteModule = ctx.modules.length > 0 assert(!isViteModule) */ reloadConfig(file, config, 'modified', server); // Files such as `pages/+config.js` can potentially modify Vike's virtual files. // Triggers a full page reload const vikeVirtualFiles = getVikeVirtualFiles(server); return vikeVirtualFiles; } else { // Ensure we invalidate `file` *before* server.ssrLoadModule() in updateUserFiles() // Vite already invalidates it, but *after* handleHotUpdate() and thus after server.ssrLoadModule() ctx.modules.forEach((mod) => server.moduleGraph.invalidateModule(mod)); updateUserFiles(); } } } async function isVikeConfigDependency(filePathAbsoluteFilesystem, moduleGraph) { // Non-runtime Vike config files such as `pages/+config.js` which aren't processed by Vite. // - They're missing in Vite's module graph. // - Potentially modifies Vike's virtual files. // - Same for all `pages/+config.js` dependencies. assertPosixPath(filePathAbsoluteFilesystem); const vikeConfigObject = await getVikeConfigInternalOptional(); if (vikeConfigObject) { const { _vikeConfigDependencies: vikeConfigDependencies } = vikeConfigObject; vikeConfigDependencies.forEach((f) => assertPosixPath(f)); if (vikeConfigDependencies.has(filePathAbsoluteFilesystem)) return { isProcessedByVite: false }; } // Runtime Vike config files such as +data.js which are processed by Vite. // - They're included in Vite's module graph. // - They never modify Vike's virtual files. // - Same for all `+data.js` dependencies. const importers = getImporters(filePathAbsoluteFilesystem, moduleGraph); const isPlusValueFileDependency = Array.from(importers).some((importer) => importer.file && isPlusFile(importer.file)); if (isPlusValueFileDependency) return { isProcessedByVite: true }; return null; } function reloadConfig(filePath, config, op, server) { // Ensure server.ssrLoadModule() loads fresh Vike virtual files (`reloadConfig()` > `updateUserFiles()` > `server.ssrLoadModule()`) invalidateVikeVirtualFiles(server); { const filePathToShowToUserResolved = getModuleFilePathAbsolute(filePath, config); const msg = `${op} ${pc.dim(filePathToShowToUserResolved)}`; logConfigInfo(msg, 'info'); } reloadVikeConfig(); updateUserFiles(); } function getVikeVirtualFiles(server) { const vikeVirtualFiles = Array.from(server.moduleGraph.urlToModuleMap.keys()) .filter((url) => isVirtualFileIdPageConfigLazy(url) || isVirtualFileIdEntry(url)) .map((url) => { const mod = server.moduleGraph.urlToModuleMap.get(url); assert(mod); return mod; }); return vikeVirtualFiles; } // Get all transitive importers (including the module itself) function getImporters(file, moduleGraph) { const importers = new Set(); const mods = moduleGraph.getModulesByFile(file); if (!mods) return importers; for (const mod of mods) { getModuleImporters(mod).forEach((importer) => { if (importer) importers.add(importer); }); } return importers; } function getModuleImporters(mod, seen = new Set()) { if (seen.has(mod)) return new Set(); seen.add(mod); const importers = new Set(); if (mod.id) importers.add(mod); // Traverse through the importers (modules that import this module) for (const importer of mod.importers) { if (importer.id) importers.add(importer); getModuleImporters(importer, seen).forEach((importerTransitive) => { if (importerTransitive) importers.add(importerTransitive); }); } return importers; } function existsInViteModuleGraph(file, moduleGraph) { return !!moduleGraph.getModulesByFile(file); }