fix: stack size exceed error on purging module cache
This commit is contained in:
parent
7404c9aacf
commit
3c1024a071
4 changed files with 75 additions and 26 deletions
|
|
@ -8,7 +8,7 @@ import * as path from "path";
|
|||
|
||||
import { Config, ConfigPaths } from "./config";
|
||||
import { renderPage } from "./render";
|
||||
import { purgeModuleAndDepsFromCache, walkDir } from "./utils";
|
||||
import { decacheModule, walkDir } from "./utils";
|
||||
|
||||
const renderPagesToHtml = async ({
|
||||
pagesDir,
|
||||
|
|
@ -22,7 +22,7 @@ const renderPagesToHtml = async ({
|
|||
}
|
||||
|
||||
// Ensure that we don't cache page modules when running in dev server.
|
||||
purgeModuleAndDepsFromCache(srcPath);
|
||||
decacheModule(srcPath);
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const pageSrc = require(srcPath);
|
||||
if (!("page" in pageSrc)) {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
import * as path from "path";
|
||||
|
||||
import { purgeModuleAndDepsFromCache } from "./utils";
|
||||
import { decacheModule } from "./utils";
|
||||
|
||||
/**
|
||||
* Paths used during configuration.
|
||||
|
|
@ -63,7 +63,7 @@ export const loadConfig = async (rootDir: string): Promise<Config> => {
|
|||
// Attempt to load a websnacks.ts/js file in rootDir.
|
||||
try {
|
||||
configPath = require.resolve(path.resolve(rootDir, "websnacks"));
|
||||
purgeModuleAndDepsFromCache(configPath);
|
||||
decacheModule(configPath);
|
||||
// TODO: validate user config.
|
||||
userConfig = await import(configPath);
|
||||
} catch (error) {
|
||||
|
|
|
|||
69
src/utils/decache-module.ts
Normal file
69
src/utils/decache-module.ts
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
const resolveModulePath = (importPath: string): string | undefined => {
|
||||
try {
|
||||
return require.resolve(importPath);
|
||||
} catch (error) {
|
||||
if (error.code === "MODULE_NOT_FOUND") {
|
||||
return;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const removeParentModuleRef = (mod: NodeModule): void => {
|
||||
const parent = mod.parent;
|
||||
if (parent == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
const siblings = parent.children;
|
||||
const nSiblings = siblings.length;
|
||||
for (let i = nSiblings - 1; i >= 0; i--) {
|
||||
const sibling = siblings[i];
|
||||
if (sibling.id === mod.id) {
|
||||
siblings.splice(i, 1);
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Clear a module and its dependencies from node's module cache, ensuring that
|
||||
* requiring the module again will reload the code from disk.
|
||||
*
|
||||
* @param importPath Path or name of the module to resolve (same as
|
||||
* {@see require}).
|
||||
*
|
||||
* @throws Error if the module could not be resolved due to filesystem error.
|
||||
*/
|
||||
export const decacheModule = (importPath: string): void => {
|
||||
const modulePath = resolveModulePath(importPath);
|
||||
if (modulePath == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
// DFS the module dependency tree, using iteration to avoid stack size
|
||||
// exceeded exceptions.
|
||||
const modsToCheck: NodeModule[] = [];
|
||||
const visited: Set<string> = new Set();
|
||||
let currentMod: NodeModule | undefined = require.cache[modulePath];
|
||||
while (currentMod != null) {
|
||||
if (visited.has(currentMod.id)) {
|
||||
currentMod = modsToCheck.pop();
|
||||
continue;
|
||||
}
|
||||
|
||||
removeParentModuleRef(currentMod);
|
||||
delete require.cache[currentMod.id];
|
||||
for (const childMod of currentMod.children) {
|
||||
modsToCheck.push(childMod);
|
||||
}
|
||||
|
||||
visited.add(currentMod.id);
|
||||
currentMod = modsToCheck.pop();
|
||||
}
|
||||
};
|
||||
|
|
@ -6,6 +6,8 @@
|
|||
import { promises as fs } from "fs";
|
||||
import * as path from "path";
|
||||
|
||||
export { decacheModule } from "./decache-module";
|
||||
|
||||
/**
|
||||
* Recursively walk a directory, returning the files it finds.
|
||||
*
|
||||
|
|
@ -27,28 +29,6 @@ export const walkDir = async function* (
|
|||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Purge cached versions of a node module and all of its dependencies from the
|
||||
* global require cache, ensuring that future imports reload the module from
|
||||
* disk.
|
||||
*
|
||||
* @param modName Name of the module to purge from the require cache.
|
||||
*/
|
||||
export const purgeModuleAndDepsFromCache = (modName: string): void => {
|
||||
const modPath = require.resolve(modName);
|
||||
if (modPath == null) {
|
||||
return;
|
||||
}
|
||||
const mod = require.cache[modPath];
|
||||
if (mod == null) {
|
||||
return;
|
||||
}
|
||||
for (const child of mod.children) {
|
||||
purgeModuleAndDepsFromCache(child.id);
|
||||
}
|
||||
delete require.cache[modPath];
|
||||
};
|
||||
|
||||
export type Flattenable<T> = Array<T | Flattenable<T>>;
|
||||
|
||||
/**
|
||||
Loading…
Add table
Add a link
Reference in a new issue