vite-plugin-react18-pages
Version:
<p> <a href="https://www.npmjs.com/package/vite-plugin-react-pages" target="_blank" rel="noopener"><img src="https://img.shields.io/npm/v/vite-plugin-react-pages.svg" alt="npm package" /></a> </p>
323 lines • 11.8 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.VirtualModuleGraph = void 0;
const utils_1 = require("./utils");
class VirtualModuleGraph {
constructor() {
/**
* the module inside this graph may be virtule module or real fs module
*/
this.modules = new Map();
/**
* Serialize the update works (instead of doing them concurrently)
* to make the result more predictable.
*
* If there is already a queuing update with same updaterId,
* it won't schedule a new one.
*
* Before executing an updater, it will automatically cleanup the effects of
* previous update with same updaterId.
* Example:
* When find module1 for the first time:
* the updater set data for module2 and module3 (upstreamModule is module1)
* Then, when observe that module1 is updated:
* the updater set data for module2 (upstreamModule is module1)
* At this time, the data in module3 should be automatically cleanup!
* So the updater(users) don't need to manually delete the old data in module3.
*/
this.updateQueue = new UpdateQueue();
/** track updateQueue empty state (isPending means not empty) */
this.updateQueueEmptyState = new utils_1.PendingState();
this.moduleUpdateListeners = [];
// executeUpdates_Inner is not reentrant
// use a state(lock) to prevent concurrent execution
this.updateExecutingState = new utils_1.PendingState();
}
getModuleIds(filter) {
const ids = Array.from(this.modules.keys());
if (filter)
return ids.filter(filter);
return ids;
}
getModuleData(moduleId) {
const module = this.modules.get(moduleId);
if (!module)
return [];
return module.getData();
}
getModules(filter) {
let entries = Array.from(this.modules.entries());
// filter is a performance optimization:
// don't call module.getData() for filtered-out modules
if (filter)
entries = entries.filter(([moduleId]) => filter(moduleId));
const modules = {};
entries.forEach(([moduleId, module]) => {
modules[moduleId] = module.getData();
});
return modules;
}
/**
* This is the only way to update virtule modules
*/
scheduleUpdate(updaterId, updater) {
this.updateQueue.push(updaterId, updater);
this.updateQueueEmptyState.isPending = true;
// don't schedule setTimeout if there is already one
if (this.updateQueue.size === 1) {
setTimeout(() => {
this.executeUpdates();
}, 0);
}
}
addModuleListener(handler, filter) {
return this._addModuleListener((moduleId, data, prevData) => {
if (filter && !filter(moduleId))
return;
handler(moduleId, data, prevData);
});
}
/**
* listen to virtule module updates.
* users can scheduleUpdate in these listeners, creating dependency chain of
* virtule modules.
* (.i.e when a virtule module changes, it will update another virtule module)
*
* users will reveive new module data and previous module data,
* so users can diff them to decide whether the module has "really" changed.
* if users think they are the same, the can skip updating other virtule modules.
* VirtualModuleGraph works on a very low level. It don't know what module data means. So it send updates event to users very often and let users to interpret module data.
*
* @return unsubscribe function
*/
_addModuleListener(cb) {
this.moduleUpdateListeners.push(cb);
return () => {
const index = this.moduleUpdateListeners.indexOf(cb);
if (index === -1)
return;
this.moduleUpdateListeners.splice(index, 1);
};
}
callModuleUpdateListeners(updatedModules) {
updatedModules.forEach(({ prevData }, module) => {
const data = module.getData();
this.moduleUpdateListeners.forEach((moduleUpdateListener) => {
moduleUpdateListener(module.id, data, prevData);
});
});
}
async executeUpdates() {
if (this.updateExecutingState.isPending)
return;
this.updateExecutingState.isPending = true;
try {
await this.executeUpdates_Inner();
}
finally {
this.updateExecutingState.isPending = false;
if (this.updateQueue.size === 0)
this.updateQueueEmptyState.isPending = false;
}
}
async executeUpdates_Inner(depth = 1) {
if (this.updateQueue.size === 0)
return;
if (depth > MAX_CASCADE_UPDATE_DEPTH)
throw new Error(`Cascaded updates exceed max depth ${MAX_CASCADE_UPDATE_DEPTH}. Probably because the depth of the virtule module tree is too high, or there is a cycle in the virtule module graph.`);
// record the updatedModules so that we can notify listeners in the end
// also store prevData so users can diff it with new data
const updatedModules = new Map();
/** it must be called before updating data so that it can record prevData */
const recordAffectedModule = (module) => {
if (updatedModules.has(module))
return;
updatedModules.set(module, { prevData: module.getData() });
};
while (true) {
const update = this.updateQueue.pop();
if (!update)
break;
// cleanup the effects of previous update with same updaterId
cleanupEdgesWithUpdaterId(update.updaterId, recordAffectedModule);
const { disableAPIs, ...apis } = this.createUpdateAPIs(update.updaterId, recordAffectedModule);
await update.updater(apis);
disableAPIs();
}
this.callModuleUpdateListeners(updatedModules);
// if the listeners schedule more updates,
// execute them synchronously and recursively
await this.executeUpdates_Inner(depth + 1);
}
createUpdateAPIs(updaterId, recordAffectedModule) {
let outdated = false;
const _this = this;
const OUTDATED_ERROR_MSG = `You should not call update APIs after the updater async function.`;
return {
addModuleData(moduleId, data, upstreamModuleId) {
if (outdated)
throw new Error(OUTDATED_ERROR_MSG);
if (moduleId === upstreamModuleId)
throw new Error(`addModuleData param error: source and target modules are the same`);
// upstreamModuleId may be real file in fs
const fromModule = _this.ensureModule(upstreamModuleId);
const toModule = _this.ensureModule(moduleId);
recordAffectedModule(toModule);
Edge.addEdge(fromModule, toModule, data, updaterId);
},
getModuleData(moduleId) {
if (outdated)
throw new Error(OUTDATED_ERROR_MSG);
return _this.getModuleData(moduleId);
},
deleteModule(moduleId) {
if (outdated)
throw new Error(OUTDATED_ERROR_MSG);
const module = _this.modules.get(moduleId);
if (!module)
return;
module.delete(recordAffectedModule);
_this.modules.delete(moduleId);
},
disableAPIs() {
outdated = true;
},
};
}
ensureModule(moduleId) {
let result = this.modules.get(moduleId);
if (!result) {
result = new Module(moduleId);
this.modules.set(moduleId, result);
}
return result;
}
}
exports.VirtualModuleGraph = VirtualModuleGraph;
/**
* Modules are nodes in the graph
*/
class Module {
constructor(id) {
this.id = id;
/**
* incoming edges of the node
* indicating the data of this virtule module
*
* real fs module won't need this
*/
this.data = new Set();
/**
* outcoming edges of the node
* indicating which modules depend on this module
*
* it is needed because we need to update downstream modules
* when a fs module is deleted
*/
this.downstream = new Set();
}
getData() {
return Array.from(this.data).map(({ data }) => data);
}
/** unlink this module */
delete(recordAffectedModule) {
if (this.data.size > 0) {
// there are upstream modules that are "piping" data to this module
throw new Error(`This module has upstream modules. You should delete modules in topological order. moduleID: ${this.id}`);
}
recordAffectedModule(this);
this.downstream.forEach((edge) => {
recordAffectedModule(edge.to);
edge.unlink();
});
}
}
class Edge {
constructor(from, to, data, updaterId) {
this.from = from;
this.to = to;
this.data = data;
this.updaterId = updaterId;
this.hasUnlinked = false;
}
static addEdge(from, to, data, updaterId) {
const edge = new Edge(from, to, data, updaterId);
from.downstream.add(edge);
to.data.add(edge);
bindEdgeWithUpdaterId(edge);
}
unlink() {
if (this.hasUnlinked) {
return;
}
// set private fields of modules
;
this.from.downstream.delete(this);
this.to.data.delete(this);
unbindEdgeWithUpdaterId(this);
this.hasUnlinked = true;
}
}
const mapUpdaterIdToEdges = new Map();
function bindEdgeWithUpdaterId(edge) {
const { updaterId } = edge;
let edges = mapUpdaterIdToEdges.get(updaterId);
if (!edges) {
edges = new Set();
mapUpdaterIdToEdges.set(updaterId, edges);
}
edges.add(edge);
}
function unbindEdgeWithUpdaterId(edge) {
const { updaterId } = edge;
const edges = mapUpdaterIdToEdges.get(updaterId);
if (!edges || !edges.has(edge))
throw new Error(`assertion fail: unlinkEdgeWithUpdaterId`);
edges.delete(edge);
}
function cleanupEdgesWithUpdaterId(updaterId, recordAffectedModule) {
const edges = mapUpdaterIdToEdges.get(updaterId);
if (!edges)
return;
edges.forEach((edge) => {
recordAffectedModule(edge.to);
edge.unlink();
});
if (edges.size > 0)
throw new Error(`assertion fail: all edges with updaterId should already be unlinked`);
edges.clear();
}
class Update {
constructor(updaterId, updater) {
this.updaterId = updaterId;
this.updater = updater;
}
}
class UpdateQueue {
constructor() {
this.queue = [];
this.map = new Map();
}
get size() {
return this.queue.length;
}
push(updaterId, updater) {
// ignore it if the updaterId already exists in the queue
if (this.map.has(updaterId))
return;
const update = new Update(updaterId, updater);
this.queue.push(update);
this.map.set(updaterId, update);
}
pop() {
const update = this.queue.shift();
if (!update)
return null;
const { updaterId } = update;
this.map.delete(updaterId);
return update;
}
}
// it indicates the depth of virtule modules
const MAX_CASCADE_UPDATE_DEPTH = 10;
//# sourceMappingURL=VirtualModules.js.map