UNPKG

fuse-box

Version:

Fuse-Box a bundler that does it right

388 lines (387 loc) • 15.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.createCache = void 0; const fs_1 = require("fs"); const path = require("path"); const module_1 = require("../moduleResolver/module"); const package_1 = require("../moduleResolver/package"); const utils_1 = require("../utils/utils"); const watcher_1 = require("../watcher/watcher"); const META_MODULES_CACHE = {}; const META_JSON_CACHE = {}; function createCache(ctx, bundleContext) { const prefix = utils_1.fastHash(ctx.config.entries.toString()); const CACHE_ROOT = path.join(ctx.config.cache.root, prefix); const isFileStrategy = ctx.config.cache.strategy === 'fs'; const META_FILE = path.join(CACHE_ROOT, 'meta.json'); const CACHE_MODULES_FOLDER = path.join(CACHE_ROOT, 'files'); function moduleMetaCache() { const moduleWriters = []; const self = { getMeta: () => { let meta; if (META_JSON_CACHE[META_FILE]) meta = META_JSON_CACHE[META_FILE]; else if (isFileStrategy && fs_1.existsSync(META_FILE)) { try { meta = utils_1.readJSONFile(META_FILE); } catch (e) { } } if (!meta) { META_JSON_CACHE[META_FILE] = meta = { currentId: 0, modules: {}, packages: {} }; } bundleContext.currentId = meta.currentId; return meta; }, persist: async (metaChanged, meta) => { await Promise.all(moduleWriters); if (isFileStrategy && metaChanged) { await utils_1.writeFile(META_FILE, JSON.stringify(meta, null, 2)); } }, read: (meta) => { const cachedFile = path.join(CACHE_MODULES_FOLDER, meta.id + '.json'); if (META_MODULES_CACHE[cachedFile]) return META_MODULES_CACHE[cachedFile]; if (!isFileStrategy) return; if (!fs_1.existsSync(cachedFile)) return; const data = utils_1.readJSONFile(cachedFile); META_MODULES_CACHE[cachedFile] = data; return data; }, write: (module) => { const cachedFile = path.join(CACHE_MODULES_FOLDER, `${module.id}.json`); const data = { contents: module.contents, sourceMap: module.sourceMap }; META_MODULES_CACHE[cachedFile] = data; if (!isFileStrategy) return; const contents = JSON.stringify(data); moduleWriters.push(utils_1.writeFile(cachedFile, contents)); }, }; return self; } const metaCache = moduleMetaCache(); const meta = metaCache.getMeta(); const modules = meta.modules; const packages = meta.packages; const verifiedPackages = {}; const verifiedModules = {}; // restore context cachable if (meta.ctx) { for (const key in meta.ctx) ctx[key] = meta.ctx[key]; } function verifyLinkedReferences() { for (const absPath in ctx.linkedReferences) { const item = ctx.linkedReferences[absPath]; if (!utils_1.fileExists(absPath)) { // cleaning up ctx.linkedReferences[absPath] = undefined; } else { const mtime = utils_1.getFileModificationTime(absPath); if (mtime !== item.mtime) { // the referenced file was modified, so // force all modules that depend on this file to be detected as modified for (const depId of item.deps) { if (modules[depId]) modules[depId].mtime = -1; } // our work here is done until the next time it is modified item.mtime = mtime; } } } } // first thing we need to verify linked referneces // if dependant files have changed we need to break cache on targeted modules verifyLinkedReferences(); /** * * Restoring module * If module cache data is present we can safely restore * the modules. This function should be called on a verified module (mtime matches) * @param meta * @param cachedPackage */ function restoreModule(meta, cachedPackage) { if (!cachedPackage) return; const moduleCacheData = metaCache.read(meta); if (!moduleCacheData) return; const module = module_1.createModule({ absPath: meta.absPath, ctx: ctx }); module.initFromCache(meta, moduleCacheData); if (bundleContext.packages[cachedPackage.publicName]) { module.pkg = bundleContext.packages[cachedPackage.publicName]; } else { // restore package and assign it to module const pkg = package_1.createPackageFromCache(cachedPackage); bundleContext.packages[cachedPackage.publicName] = pkg; module.pkg = pkg; } return module; } /** * FInding a module in meta * @param absPath */ function findModuleMeta(absPath) { for (const moduleId in modules) { if (modules[moduleId].absPath === absPath) return modules[moduleId]; } } /** * Veifying module * @param meta * @param mrc */ function restoreModuleDependencies(meta, mrc) { if (verifiedModules[meta.absPath]) return true; verifiedModules[meta.absPath] = true; const pkg = packages[meta.packageId]; if (!pkg) return; if (pkg.meta) if (!restorePackage(pkg, mrc)) return; for (const dependencyId of meta.dependencies) { const target = modules[dependencyId]; if (!target) return; if (!restoreModuleDependencies(target, mrc)) return; } return true; } function restorePackage(pkg, mrc) { if (verifiedPackages[pkg.publicName]) { return true; } verifiedPackages[pkg.publicName] = true; const packageJSONLocation = pkg.meta.packageJSONLocation; if (!utils_1.fileExists(packageJSONLocation)) { // flush the package if package.json doesn't exist anymore packages[pkg.publicName] = undefined; return false; } // version changed or anything else. Drop the package from meta // but leave the files to preserved assigned IDS (required for the HMR) if (utils_1.getFileModificationTime(packageJSONLocation) !== pkg.mtime) { // here we reset the cache of that entry point const bustedPackage = packages[pkg.publicName]; const pkgName = bustedPackage.publicName; //bundleContext.packages[pkgName] = undefined; verifiedPackages[pkgName] = undefined; packages[pkgName] = undefined; return false; } const collection = []; // package is in tact pulling out all the files for (const moduleId of pkg.deps) { const meta = modules[moduleId]; if (meta.mtime === -1) return false; const depPackage = packages[meta.packageId]; // a required dependency is missing // verifying and external package of the current package if (!depPackage) return false; // meta might be missing ?! const target = restoreModule(meta, pkg); // cache might be missing? if (!target) return false; if (!restoreModuleDependencies(meta, mrc)) return; collection.push(target); } // finally populating the bundle context for (const restored of collection) { bundleContext.modules[restored.absPath] = restored; } return true; } function restoreModuleSafely(absPath, mrc) { if (verifiedModules[absPath]) return bundleContext.modules[absPath]; verifiedModules[absPath] = true; const meta = findModuleMeta(absPath); const metaPackage = packages[meta.packageId]; // file was removed if (!utils_1.fileExists(meta.absPath)) { // need to break dependants cache //return shouldResolve(absPath, metaPackage); for (const id in modules) { const x = modules[id]; // package is no longer verified if (x.dependencies.includes(meta.id)) { verifiedModules[x.absPath] = false; x.mtime = -1; if (!restoreModuleSafely(x.absPath, mrc)) return; } } } // check if that module depends on some other dependencies that need to be consistent let shouldBreakCachedModule = utils_1.getFileModificationTime(meta.absPath) !== meta.mtime; if (meta.v) { for (const id of meta.v) { const target = modules[id]; const restored = restoreModuleSafely(target.absPath, mrc); if (!target || !restored) { shouldBreakCachedModule = true; break; } } } // if any of our dependencies relied on the "main" field of a package.json // and that package.json has changed, // then the absPath of that dependency is possibly no longer valid and so we have to re-resolve everything for (const depId of meta.dependencies) { const target = modules[depId]; const pkg = target && packages[target.packageId]; if (pkg && !pkg.isExternalPackage && pkg.mtime && pkg.meta && pkg.meta.packageJSONLocation && pkg.mtime !== utils_1.getFileModificationTime(pkg.meta.packageJSONLocation)) { shouldBreakCachedModule = true; break; } } if (shouldBreakCachedModule) { // should be resolved bundleContext.modules[absPath] = undefined; mrc.modulesRequireResolution.push({ absPath, pkg: metaPackage }); return; } for (const depId of meta.dependencies) { const target = modules[depId]; if (!target) return; const pkg = packages[target.packageId]; if (pkg && pkg.isExternalPackage) { if (!restorePackage(pkg, mrc)) { // package has failed // interrupt everything mrc.modulesRequireResolution.push({ absPath, pkg: metaPackage }); return; } } else restoreModuleSafely(target.absPath, mrc); } const module = restoreModule(meta, metaPackage); if (module) bundleContext.modules[module.absPath] = module; return module; } function getModuleByPath(absPath) { const moduleMeta = findModuleMeta(absPath); const mrc = { modulesRequireResolution: [], }; const busted = { mrc }; // if a module was not found in cache we do nothing if (!moduleMeta) return busted; const targetPackageId = moduleMeta.packageId; const modulePackage = packages[targetPackageId]; if (!modulePackage) return busted; if (modulePackage.isExternalPackage) { if (!restorePackage(modulePackage, mrc)) return busted; } else { // restore local files (check the modification time on each) return { module: restoreModuleSafely(absPath, mrc), mrc }; } } async function write() { let shouldWriteMeta = false; meta.currentId = bundleContext.currentId; for (const packageId in bundleContext.packages) { const pkg = bundleContext.packages[packageId]; if (!packages[pkg.publicName]) { shouldWriteMeta = true; if (pkg.meta) { pkg.deps = []; pkg.mtime = utils_1.getFileModificationTime(pkg.meta.packageJSONLocation); } packages[pkg.publicName] = pkg; } } const breakingCacheIds = []; for (const absPath in bundleContext.modules) { const module = bundleContext.modules[absPath]; if (!module.isCached && !module.errored) { shouldWriteMeta = true; const fileMeta = module.getMeta(); modules[module.id] = fileMeta; const pkg = packages[module.pkg.publicName]; if (pkg.meta) if (!pkg.deps.includes(module.id)) pkg.deps.push(module.id); if (module.breakDependantsCache) { breakingCacheIds.push(module.id); } metaCache.write(module); } } for (const breakId of breakingCacheIds) { for (const id in meta.modules) { const target = meta.modules[id]; if (target.dependencies.includes(breakId)) { if (!target.v) target.v = []; target.v.push(breakId); modules[id] = target; } } } // fast and ugly check if cache context needs to be written const cachable = ctx.getCachable(); if (isFileStrategy) { if (JSON.stringify(meta.ctx) !== JSON.stringify(cachable)) { shouldWriteMeta = true; meta.ctx = cachable; } } else meta.ctx = cachable; if (!isFileStrategy) shouldWriteMeta = false; await metaCache.persist(shouldWriteMeta, meta); } const self = { meta, write, nuke: () => utils_1.removeFolder(CACHE_ROOT), restore: (absPath) => getModuleByPath(absPath), }; if (isFileStrategy) { // destroying the cache folder only in case of a file staregy // memory strategy should not be affected since the process is closed const nukableReactions = [watcher_1.WatcherReaction.TS_CONFIG_CHANGED, watcher_1.WatcherReaction.FUSE_CONFIG_CHANGED]; ctx.ict.on('watcher_reaction', ({ reactionStack }) => { for (const item of reactionStack) { if (nukableReactions.includes(item.reaction)) { self.nuke(); break; } } }); } return self; } exports.createCache = createCache;