fix: stack size exceed error on purging module cache

This commit is contained in:
M. George Hansen 2020-06-14 12:06:59 -07:00
parent 7404c9aacf
commit 3c1024a071
Signed by: mgeorgehansen
SSH key fingerprint: SHA256:JlIGiQLPyQ2RHTH3a2oVlb20Xkh9Glr8DUF4YTXHJxM
4 changed files with 75 additions and 26 deletions

View file

@ -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)) {

View file

@ -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) {

View 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();
}
};

View file

@ -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>>;
/**