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 { Config, ConfigPaths } from "./config";
|
||||||
import { renderPage } from "./render";
|
import { renderPage } from "./render";
|
||||||
import { purgeModuleAndDepsFromCache, walkDir } from "./utils";
|
import { decacheModule, walkDir } from "./utils";
|
||||||
|
|
||||||
const renderPagesToHtml = async ({
|
const renderPagesToHtml = async ({
|
||||||
pagesDir,
|
pagesDir,
|
||||||
|
|
@ -22,7 +22,7 @@ const renderPagesToHtml = async ({
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that we don't cache page modules when running in dev server.
|
// 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
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||||
const pageSrc = require(srcPath);
|
const pageSrc = require(srcPath);
|
||||||
if (!("page" in pageSrc)) {
|
if (!("page" in pageSrc)) {
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@
|
||||||
|
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
|
|
||||||
import { purgeModuleAndDepsFromCache } from "./utils";
|
import { decacheModule } from "./utils";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Paths used during configuration.
|
* 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.
|
// Attempt to load a websnacks.ts/js file in rootDir.
|
||||||
try {
|
try {
|
||||||
configPath = require.resolve(path.resolve(rootDir, "websnacks"));
|
configPath = require.resolve(path.resolve(rootDir, "websnacks"));
|
||||||
purgeModuleAndDepsFromCache(configPath);
|
decacheModule(configPath);
|
||||||
// TODO: validate user config.
|
// TODO: validate user config.
|
||||||
userConfig = await import(configPath);
|
userConfig = await import(configPath);
|
||||||
} catch (error) {
|
} 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 { promises as fs } from "fs";
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
|
|
||||||
|
export { decacheModule } from "./decache-module";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Recursively walk a directory, returning the files it finds.
|
* 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>>;
|
export type Flattenable<T> = Array<T | Flattenable<T>>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Loading…
Add table
Add a link
Reference in a new issue