Merge branch 'modules-proxy-patches' into immediate-finds-modules-proxy

This commit is contained in:
Nuckyz 2024-06-02 18:49:21 -03:00
commit 05717d6bc3
No known key found for this signature in database
GPG key ID: 440BF8296E1C4AD9
4 changed files with 38 additions and 28 deletions

View file

@ -8,6 +8,7 @@ import { Logger } from "@utils/Logger";
import { canonicalizeMatch } from "@utils/patches"; import { canonicalizeMatch } from "@utils/patches";
import * as Webpack from "@webpack"; import * as Webpack from "@webpack";
import { wreq } from "@webpack"; import { wreq } from "@webpack";
import { AnyModuleFactory, ModuleFactory } from "webpack";
const LazyChunkLoaderLogger = new Logger("LazyChunkLoader"); const LazyChunkLoaderLogger = new Logger("LazyChunkLoader");
@ -109,21 +110,22 @@ export async function loadLazyChunks() {
}, 0); }, 0);
} }
Webpack.factoryListeners.add(factory => { function factoryListener(factory: AnyModuleFactory | ModuleFactory) {
let isResolved = false; let isResolved = false;
searchAndLoadLazyChunks(String(factory)).then(() => isResolved = true); searchAndLoadLazyChunks(String(factory))
.then(() => isResolved = true)
chunksSearchPromises.push(() => isResolved); .catch(() => isResolved = true);
});
for (const factoryId in wreq.m) {
let isResolved = false;
searchAndLoadLazyChunks(String(wreq.m[factoryId])).then(() => isResolved = true);
chunksSearchPromises.push(() => isResolved); chunksSearchPromises.push(() => isResolved);
} }
Webpack.factoryListeners.add(factoryListener);
for (const factoryId in wreq.m) {
factoryListener(wreq.m[factoryId]);
}
await chunksSearchingDone; await chunksSearchingDone;
Webpack.factoryListeners.delete(factoryListener);
// Require deferred entry points // Require deferred entry points
for (const deferredRequire of deferredRequires) { for (const deferredRequire of deferredRequires) {

View file

@ -12,15 +12,7 @@ import { PatchReplacement } from "@utils/types";
import { traceFunction } from "../debug/Tracer"; import { traceFunction } from "../debug/Tracer";
import { patches } from "../plugins"; import { patches } from "../plugins";
import { _initWebpack, factoryListeners, ModuleFactory, moduleListeners, waitForSubscriptions, WebpackRequire, wreq } from "."; import { _initWebpack, AnyModuleFactory, AnyWebpackRequire, factoryListeners, moduleListeners, PatchedModuleFactories, PatchedModuleFactory, waitForSubscriptions, WebpackRequire, wreq } from ".";
type AnyWebpackRequire = Partial<WebpackRequire> & Pick<WebpackRequire, "m">;
type PatchedModuleFactory = ModuleFactory & {
$$vencordOriginal?: ModuleFactory;
};
type PatchedModuleFactories = Record<PropertyKey, PatchedModuleFactory>;
const logger = new Logger("WebpackInterceptor", "#8caaee"); const logger = new Logger("WebpackInterceptor", "#8caaee");
@ -56,7 +48,7 @@ const define: Define = (target, p, attributes) => {
define(Function.prototype, "O", { define(Function.prototype, "O", {
enumerable: false, enumerable: false,
set(this: WebpackRequire, onChunksLoaded: WebpackRequire["O"]) { set(this: AnyWebpackRequire, onChunksLoaded: AnyWebpackRequire["O"]) {
define(this, "O", { value: onChunksLoaded }); define(this, "O", { value: onChunksLoaded });
const { stack } = new Error(); const { stack } = new Error();
@ -104,7 +96,7 @@ define(Function.prototype, "O", {
const proxiedModuleFactories = new Proxy(this.m, moduleFactoriesHandler); const proxiedModuleFactories = new Proxy(this.m, moduleFactoriesHandler);
/* /*
If Discord ever decides to set module factories using the variable of the modules object directly, instead of wreq.m, switch the proxy to the prototype If Discord ever decides to set module factories using the variable of the modules object directly, instead of wreq.m, switch the proxy to the prototype
Reflect.setPrototypeOf(moduleFactories, new Proxy(moduleFactories, moduleFactoriesHandler)); define(this, "m", { value: Reflect.setPrototypeOf(this.m, new Proxy(this.m, moduleFactoriesHandler)) });
*/ */
define(this, "m", { value: proxiedModuleFactories }); define(this, "m", { value: proxiedModuleFactories });
@ -133,7 +125,7 @@ function defineModulesFactoryGetter(id: PropertyKey, factory: PatchedModuleFacto
return (factory = patchFactory(id, factory)); return (factory = patchFactory(id, factory));
}, },
set(v: ModuleFactory) { set(v: AnyModuleFactory) {
if (factory.$$vencordOriginal != null) { if (factory.$$vencordOriginal != null) {
factory.$$vencordOriginal = v; factory.$$vencordOriginal = v;
} else { } else {
@ -153,7 +145,7 @@ function defineModulesFactoryGetter(id: PropertyKey, factory: PatchedModuleFacto
* @param ignoreExistingInTarget Whether to ignore checking if the factory already exists in the moduleFactoriesTarget * @param ignoreExistingInTarget Whether to ignore checking if the factory already exists in the moduleFactoriesTarget
* @returns Whether the original factory was updated, or false if it doesn't exist in any Webpack instance * @returns Whether the original factory was updated, or false if it doesn't exist in any Webpack instance
*/ */
function updateExistingFactory(moduleFactoriesTarget: AnyWebpackRequire["m"], id: PropertyKey, newFactory: ModuleFactory, ignoreExistingInTarget: boolean = false) { function updateExistingFactory(moduleFactoriesTarget: AnyWebpackRequire["m"], id: PropertyKey, newFactory: AnyModuleFactory, ignoreExistingInTarget: boolean = false) {
let existingFactory: TypedPropertyDescriptor<PatchedModuleFactory> | undefined; let existingFactory: TypedPropertyDescriptor<PatchedModuleFactory> | undefined;
for (const wreq of allWebpackInstances) { for (const wreq of allWebpackInstances) {
if (ignoreExistingInTarget && wreq.m === moduleFactoriesTarget) continue; if (ignoreExistingInTarget && wreq.m === moduleFactoriesTarget) continue;
@ -228,7 +220,7 @@ const moduleFactoriesHandler: ProxyHandler<PatchedModuleFactories> = {
* @param factory The original or patched module factory * @param factory The original or patched module factory
* @returns The wrapper for the patched module factory * @returns The wrapper for the patched module factory
*/ */
function patchFactory(id: PropertyKey, factory: ModuleFactory) { function patchFactory(id: PropertyKey, factory: AnyModuleFactory) {
const originalFactory = factory; const originalFactory = factory;
for (const factoryListener of factoryListeners) { for (const factoryListener of factoryListeners) {
@ -347,7 +339,7 @@ function patchFactory(id: PropertyKey, factory: ModuleFactory) {
// The patched factory wrapper, define it in an object to preserve the name after minification // The patched factory wrapper, define it in an object to preserve the name after minification
const patchedFactory: PatchedModuleFactory = { const patchedFactory: PatchedModuleFactory = {
PatchedFactory(...args: Parameters<ModuleFactory>) { PatchedFactory(...args: Parameters<AnyModuleFactory>) {
// Restore the original factory in all the module factories objects, // Restore the original factory in all the module factories objects,
// because we want to make sure the original factory is restored properly, no matter what is the Webpack instance // because we want to make sure the original factory is restored properly, no matter what is the Webpack instance
for (const wreq of allWebpackInstances) { for (const wreq of allWebpackInstances) {
@ -370,7 +362,7 @@ function patchFactory(id: PropertyKey, factory: ModuleFactory) {
`id: ${String(id)}` + interpolateIfDefined`, WebpackInstance origin: ${webpackInstanceFileName}` + `id: ${String(id)}` + interpolateIfDefined`, WebpackInstance origin: ${webpackInstanceFileName}` +
")" ")"
); );
_initWebpack(require); _initWebpack(require as WebpackRequire);
} else if (IS_DEV) { } else if (IS_DEV) {
logger.error("WebpackRequire was not initialized, running modules without patches instead."); logger.error("WebpackRequire was not initialized, running modules without patches instead.");
} }

View file

@ -13,7 +13,7 @@ import { AnyObject } from "@utils/types";
import { traceFunction } from "../debug/Tracer"; import { traceFunction } from "../debug/Tracer";
import { GenericStore } from "./common"; import { GenericStore } from "./common";
import { ModuleExports, ModuleFactory, WebpackRequire } from "./wreq"; import { AnyModuleFactory, ModuleExports, ModuleFactory, WebpackRequire } from "./wreq";
const logger = new Logger("Webpack"); const logger = new Logger("Webpack");
@ -90,7 +90,7 @@ export type ModCallbackFnWithId = (module: ModuleExports, id: PropertyKey) => vo
export const waitForSubscriptions = new Map<FilterFn, ModCallbackFn>(); export const waitForSubscriptions = new Map<FilterFn, ModCallbackFn>();
export const moduleListeners = new Set<ModCallbackFnWithId>(); export const moduleListeners = new Set<ModCallbackFnWithId>();
export const factoryListeners = new Set<(factory: ModuleFactory) => void>(); export const factoryListeners = new Set<(factory: AnyModuleFactory) => void>();
export function _initWebpack(webpackRequire: WebpackRequire) { export function _initWebpack(webpackRequire: WebpackRequire) {
wreq = webpackRequire; wreq = webpackRequire;

18
src/webpack/wreq.d.ts vendored
View file

@ -53,7 +53,7 @@ export type OnChunksLoaded = ((this: WebpackRequire, result: any, chunkIds: Prop
j: (this: OnChunksLoaded, chunkId: PropertyKey) => boolean; j: (this: OnChunksLoaded, chunkId: PropertyKey) => boolean;
}; };
export type WebpackRequire = ((moduleId: PropertyKey) => Module) & { export type WebpackRequire = ((moduleId: PropertyKey) => ModuleExports) & {
/** The module factories, where all modules that have been loaded are stored (pre-loaded or loaded by lazy chunks) */ /** The module factories, where all modules that have been loaded are stored (pre-loaded or loaded by lazy chunks) */
m: Record<PropertyKey, ModuleFactory>; m: Record<PropertyKey, ModuleFactory>;
/** The module cache, where all modules which have been WebpackRequire'd are stored */ /** The module cache, where all modules which have been WebpackRequire'd are stored */
@ -182,3 +182,19 @@ export type WebpackRequire = ((moduleId: PropertyKey) => Module) & {
/** Document baseURI or WebWorker location.href */ /** Document baseURI or WebWorker location.href */
b: string; b: string;
}; };
// Utility section for Vencord
export type AnyWebpackRequire = ((moduleId: PropertyKey) => ModuleExports) & Partial<Omit<WebpackRequire, "m" | "O">> & Pick<WebpackRequire, "O"> & {
/** The module factories, where all modules that have been loaded are stored (pre-loaded or loaded by lazy chunks) */
m: Record<PropertyKey, AnyModuleFactory>;
};
/** exports can be anything, however initially it is always an empty object */
export type AnyModuleFactory = (this: ModuleExports, module: Module, exports: ModuleExports, require: AnyWebpackRequire) => void;
export type PatchedModuleFactory = AnyModuleFactory & {
$$vencordOriginal?: AnyModuleFactory;
};
export type PatchedModuleFactories = Record<PropertyKey, PatchedModuleFactory>;