Make Webpack finds not need to re-search

This commit is contained in:
Nuckyz 2024-05-07 01:13:46 -03:00
parent 886071af4d
commit 4a89fadedd
No known key found for this signature in database
GPG key ID: 440BF8296E1C4AD9
5 changed files with 123 additions and 78 deletions

View file

@ -471,26 +471,19 @@ async function runtime(token: string) {
} }
} }
// Must evaluate the len outside of the loop, as the array will be modified by the find methods called inside of it const WEBPACK_SEARCH_HISTORY_WITH_FILTER_NAME_PROP = ["find", "findComponent", "waitFor"];
// This will avoid an infinite loop
const len = Vencord.Webpack.webpackSearchHistory.length;
for (let i = 0; i < len; i++) {
const [searchType, args] = Vencord.Webpack.webpackSearchHistory[i];
let method = searchType as string; // eslint-disable-next-line prefer-const
if (searchType === "waitFor") method = "cacheFind"; for (let [searchType, args] of Vencord.Webpack.webpackSearchHistory) {
args = [...args];
try { try {
let result: any; let result = null as any;
if (method === "webpackDependantLazy" || method === "webpackDependantLazyComponent") { if (searchType === "webpackDependantLazy" || searchType === "webpackDependantLazyComponent") {
const [factory] = args; const [factory] = args;
result = factory(); result = factory();
} else if (searchType === "extractAndLoadChunks") {
if (result != null && "$$vencordGetter" in result) {
result = result.$$vencordGetter();
}
} else if (method === "extractAndLoadChunks") {
const [code, matcher] = args; const [code, matcher] = args;
const module = Vencord.Webpack.findModuleFactory(...code); const module = Vencord.Webpack.findModuleFactory(...code);
@ -498,14 +491,20 @@ async function runtime(token: string) {
result = module.toString().match(Vencord.Util.canonicalizeMatch(matcher)); result = module.toString().match(Vencord.Util.canonicalizeMatch(matcher));
} }
} else { } else {
result = Vencord.Webpack[method](...args); result = args.shift();
// If the result is our Proxy or ComponentWrapper, this means the search failed if (result != null) {
if ( if (result.$$vencordCallbackCalled != null && !result.$$vencordCallbackCalled()) {
result != null && result = null;
(result[Vencord.Util.proxyInnerGet] != null || "$$vencordGetter" in result) }
) {
result = undefined; if (result[Vencord.Util.proxyInnerGet] != null) {
result = result[Vencord.Util.proxyInnerValue];
}
if (result.$$vencordInner != null) {
result = result.$$vencordInner();
}
} }
} }
@ -517,28 +516,27 @@ async function runtime(token: string) {
let filterName = ""; let filterName = "";
let parsedArgs = args; let parsedArgs = args;
if ("$$vencordProps" in args[0]) {
if ( if (args[0].$$vencordProps != null) {
searchType === "find" || if (WEBPACK_SEARCH_HISTORY_WITH_FILTER_NAME_PROP.includes(searchType)) {
searchType === "findComponent" || filterName = args[0].$$vencordProps.shift();
searchType === "waitFor"
) {
filterName = args[0].$$vencordProps[0];
} }
parsedArgs = args[0].$$vencordProps.slice(1); parsedArgs = args[0].$$vencordProps;
} }
// if parsedArgs is the same as args, it means vencordProps of the filter was not available (like in normal filter functions),
// so log the filter function instead
if ( if (
parsedArgs === args && searchType === "waitFor" || parsedArgs === args && (searchType === "waitFor" ||
searchType === "find" || searchType === "find" ||
searchType === "findComponent" || searchType === "findComponent" ||
searchType === "webpackDependantLazy" || searchType === "webpackDependantLazy" ||
searchType === "webpackDependantLazyComponent" searchType === "webpackDependantLazyComponent")
) { ) {
logMessage += `(${parsedArgs[0].toString().slice(0, 147)}...)`; logMessage += `(${args[0].toString().slice(0, 147)}...)`;
} else if (searchType === "extractAndLoadChunks") { } else if (searchType === "extractAndLoadChunks") {
logMessage += `([${parsedArgs[0].map((arg: any) => `"${arg}"`).join(", ")}], ${parsedArgs[1].toString()})`; logMessage += `([${args[0].map((arg: any) => `"${arg}"`).join(", ")}], ${args[1].toString()})`;
} else { } else {
logMessage += `(${filterName.length ? `${filterName}(` : ""}${parsedArgs.map(arg => `"${arg}"`).join(", ")})${filterName.length ? ")" : ""}`; logMessage += `(${filterName.length ? `${filterName}(` : ""}${parsedArgs.map(arg => `"${arg}"`).join(", ")})${filterName.length ? ")" : ""}`;
} }

View file

@ -4,17 +4,18 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
export function makeLazy<T>(factory: () => T, attempts = 5): () => T { export function makeLazy<T>(factory: () => T, attempts = 5, { isIndirect = false }: { isIndirect?: boolean; } = {}): () => T {
let tries = 0; let tries = 0;
let cache: T; let cache: T;
const getter = () => { const getter = () => {
if (!cache && attempts > tries) { if (!cache && attempts > tries) {
cache = factory(); cache = factory();
if (!cache && attempts === ++tries) { if (!cache && attempts === ++tries && !isIndirect) {
console.error(`Lazy factory failed:\n\n${factory}`); console.error(`Lazy factory failed:\n\n${factory}`);
} }
} }
return cache; return cache;
}; };
@ -48,8 +49,8 @@ const handler: ProxyHandler<any> = {
} }
}; };
const proxyLazyGet = Symbol.for("vencord.lazy.get"); export const proxyLazyGet = Symbol.for("vencord.lazy.get");
const proxyLazyCache = Symbol.for("vencord.lazy.cached"); export const proxyLazyCache = Symbol.for("vencord.lazy.cached");
/** /**
* Wraps the result of factory in a Proxy you can consume as if it wasn't lazy. * Wraps the result of factory in a Proxy you can consume as if it wasn't lazy.
@ -59,7 +60,7 @@ const proxyLazyCache = Symbol.for("vencord.lazy.cached");
* @returns Result of factory function * @returns Result of factory function
*/ */
export function proxyLazy<T = any>(factory: () => T, attempts = 5, isChild = false): T { export function proxyLazy<T = any>(factory: () => T, attempts = 5, isChild = false): T {
const get = makeLazy(factory, attempts) as any; const get = makeLazy(factory, attempts, { isIndirect: true }) as any;
let isSameTick = true; let isSameTick = true;
if (!isChild) setTimeout(() => isSameTick = false, 0); if (!isChild) setTimeout(() => isSameTick = false, 0);
@ -84,6 +85,9 @@ export function proxyLazy<T = any>(factory: () => T, attempts = 5, isChild = fal
return new Proxy(proxyDummy, { return new Proxy(proxyDummy, {
...handler, ...handler,
get(target, p, receiver) { get(target, p, receiver) {
if (p === proxyLazyGet) return target[proxyLazyGet];
if (p === proxyLazyCache) return target[proxyLazyCache];
// If we're still in the same tick, it means the lazy was immediately used. // If we're still in the same tick, it means the lazy was immediately used.
// thus, we lazy proxy the get access to make things like destructuring work as expected // thus, we lazy proxy the get access to make things like destructuring work as expected
// meow here will also be a lazy // meow here will also be a lazy

View file

@ -4,12 +4,8 @@
* SPDX-License-Identifier: GPL-3.0-or-later * SPDX-License-Identifier: GPL-3.0-or-later
*/ */
import { ComponentType } from "react";
import { makeLazy } from "./lazy"; import { makeLazy } from "./lazy";
export const NoopComponent = () => null;
/** /**
* A lazy component. The factory method is called on first render. * A lazy component. The factory method is called on first render.
* @param factory Function returning a component * @param factory Function returning a component
@ -17,25 +13,31 @@ export const NoopComponent = () => null;
* @returns Result of factory function * @returns Result of factory function
*/ */
export function LazyComponent<T extends object = any>(factory: () => React.ComponentType<T>, attempts = 5) { export function LazyComponent<T extends object = any>(factory: () => React.ComponentType<T>, attempts = 5) {
const get = makeLazy(factory, attempts); const get = makeLazy(factory, attempts, { isIndirect: true });
let InnerComponent = null as React.ComponentType<T> | null;
let lazyFailedLogged = false;
const LazyComponent = (props: T) => { const LazyComponent = (props: T) => {
let Component = (() => {
console.error(`LazyComponent factory failed:\n\n${factory}`);
return null;
}) as React.ComponentType<T>;
// @ts-ignore // @ts-ignore
if (!get.$$vencordLazyFailed()) { if (!get.$$vencordLazyFailed()) {
const result = get(); const ResultComponent = get();
if (result != null) Component = result; if (ResultComponent != null) {
InnerComponent = ResultComponent;
}
} }
return <Component {...props} />; if (InnerComponent === null && !lazyFailedLogged) {
// @ts-ignore
if (get.$$vencordLazyFailed()) {
lazyFailedLogged = true;
}
console.error(`LazyComponent factory failed:\n\n${factory}`);
}
return InnerComponent && <InnerComponent {...props} />;
}; };
LazyComponent.$$vencordGetter = get; return LazyComponent as React.ComponentType<T>;
return LazyComponent as ComponentType<T>;
} }

View file

@ -22,6 +22,8 @@ import { checkIntersecting } from "./misc";
export * from "./lazyReact"; export * from "./lazyReact";
export const NoopComponent = () => null;
/** /**
* Check if an element is on screen * Check if an element is on screen
* @param intersectOnly If `true`, will only update the state when the element comes into view * @param intersectOnly If `true`, will only update the state when the element comes into view

View file

@ -133,7 +133,22 @@ export function waitFor(filter: FilterFn, callback: ModCallbackFn, { isIndirect
if (typeof callback !== "function") if (typeof callback !== "function")
throw new Error("Invalid callback. Expected a function got " + typeof callback); throw new Error("Invalid callback. Expected a function got " + typeof callback);
if (IS_DEV && !isIndirect) webpackSearchHistory.push(["waitFor", [filter]]); if (IS_DEV && !isIndirect) {
const originalCallback = callback;
let callbackCalled = false;
callback = function () {
callbackCalled = true;
// @ts-ignore
originalCallback(...arguments);
};
// @ts-ignore
callback.$$vencordCallbackCalled = () => callbackCalled;
webpackSearchHistory.push(["waitFor", [callback, filter]]);
}
if (cache != null) { if (cache != null) {
const existing = cacheFind(filter); const existing = cacheFind(filter);
@ -165,11 +180,13 @@ export function find<T = any>(filter: FilterFn, callback: (mod: any) => any = m
if (typeof callback !== "function") if (typeof callback !== "function")
throw new Error("Invalid callback. Expected a function got " + typeof callback); throw new Error("Invalid callback. Expected a function got " + typeof callback);
if (IS_DEV && !isIndirect) webpackSearchHistory.push(["find", [filter]]);
const [proxy, setInnerValue] = proxyInner<T>(`Webpack find matched no module. Filter: ${printFilter(filter)}`, "Webpack find with proxy called on a primitive value. This can happen if you try to destructure a primitive in the top level definition of the find."); const [proxy, setInnerValue] = proxyInner<T>(`Webpack find matched no module. Filter: ${printFilter(filter)}`, "Webpack find with proxy called on a primitive value. This can happen if you try to destructure a primitive in the top level definition of the find.");
waitFor(filter, mod => setInnerValue(callback(mod)), { isIndirect: true }); waitFor(filter, mod => setInnerValue(callback(mod)), { isIndirect: true });
if (IS_DEV && !isIndirect) {
webpackSearchHistory.push(["find", [proxy, filter]]);
}
if (proxy[proxyInnerValue] != null) return proxy[proxyInnerValue] as T; if (proxy[proxyInnerValue] != null) return proxy[proxyInnerValue] as T;
return proxy; return proxy;
@ -187,9 +204,7 @@ export function findComponent<T extends object = any>(filter: FilterFn, parse: (
if (typeof parse !== "function") if (typeof parse !== "function")
throw new Error("Invalid component parse. Expected a function got " + typeof parse); throw new Error("Invalid component parse. Expected a function got " + typeof parse);
if (IS_DEV && !isIndirect) webpackSearchHistory.push(["findComponent", [filter]]); let InnerComponent = null as React.ComponentType<T> | null;
let InnerComponent = null as null | React.ComponentType<T>;
let findFailedLogged = false; let findFailedLogged = false;
const WrapperComponent = (props: T) => { const WrapperComponent = (props: T) => {
@ -201,14 +216,20 @@ export function findComponent<T extends object = any>(filter: FilterFn, parse: (
return InnerComponent && <InnerComponent {...props} />; return InnerComponent && <InnerComponent {...props} />;
}; };
WrapperComponent.$$vencordGetter = () => InnerComponent;
waitFor(filter, (v: any) => { waitFor(filter, (v: any) => {
const parsedComponent = parse(v); const parsedComponent = parse(v);
InnerComponent = parsedComponent; InnerComponent = parsedComponent;
Object.assign(InnerComponent, parsedComponent); Object.assign(InnerComponent, parsedComponent);
}, { isIndirect: true }); }, { isIndirect: true });
if (IS_DEV) {
WrapperComponent.$$vencordInner = () => InnerComponent;
if (!isIndirect) {
webpackSearchHistory.push(["findComponent", [WrapperComponent, filter]]);
}
}
if (InnerComponent !== null) return InnerComponent; if (InnerComponent !== null) return InnerComponent;
return WrapperComponent; return WrapperComponent;
@ -230,9 +251,7 @@ export function findExportedComponent<T extends object = any>(...props: string[]
const filter = filters.byProps(...newProps); const filter = filters.byProps(...newProps);
if (IS_DEV) webpackSearchHistory.push(["findExportedComponent", props]); let InnerComponent = null as React.ComponentType<T> | null;
let InnerComponent = null as null | React.ComponentType<T>;
let findFailedLogged = false; let findFailedLogged = false;
const WrapperComponent = (props: T) => { const WrapperComponent = (props: T) => {
@ -244,7 +263,6 @@ export function findExportedComponent<T extends object = any>(...props: string[]
return InnerComponent && <InnerComponent {...props} />; return InnerComponent && <InnerComponent {...props} />;
}; };
WrapperComponent.$$vencordGetter = () => InnerComponent;
waitFor(filter, (v: any) => { waitFor(filter, (v: any) => {
const parsedComponent = parse(v[newProps[0]]); const parsedComponent = parse(v[newProps[0]]);
@ -252,6 +270,11 @@ export function findExportedComponent<T extends object = any>(...props: string[]
Object.assign(InnerComponent, parsedComponent); Object.assign(InnerComponent, parsedComponent);
}, { isIndirect: true }); }, { isIndirect: true });
if (IS_DEV) {
WrapperComponent.$$vencordInner = () => InnerComponent;
webpackSearchHistory.push(["findExportedComponent", [WrapperComponent, ...props]]);
}
if (InnerComponent !== null) return InnerComponent; if (InnerComponent !== null) return InnerComponent;
return WrapperComponent as React.ComponentType<T>; return WrapperComponent as React.ComponentType<T>;
@ -271,9 +294,13 @@ export function findComponentByCode<T extends object = any>(...code: string[] |
const parse = (typeof code.at(-1) === "function" ? code.pop() : m => m) as (component: any) => React.ComponentType<T>; const parse = (typeof code.at(-1) === "function" ? code.pop() : m => m) as (component: any) => React.ComponentType<T>;
const newCode = code as string[]; const newCode = code as string[];
if (IS_DEV) webpackSearchHistory.push(["findComponentByCode", code]); const ComponentResult = findComponent<T>(filters.componentByCode(...newCode), parse, { isIndirect: true });
return findComponent<T>(filters.componentByCode(...newCode), parse, { isIndirect: true }); if (IS_DEV) {
webpackSearchHistory.push(["findComponentByCode", [ComponentResult, ...code]]);
}
return ComponentResult;
} }
/** /**
@ -282,9 +309,13 @@ export function findComponentByCode<T extends object = any>(...code: string[] |
* @param props A list of props to search the exports for * @param props A list of props to search the exports for
*/ */
export function findByProps<T = any>(...props: string[]) { export function findByProps<T = any>(...props: string[]) {
if (IS_DEV) webpackSearchHistory.push(["findByProps", props]); const result = find<T>(filters.byProps(...props), m => m, { isIndirect: true });
return find<T>(filters.byProps(...props), m => m, { isIndirect: true }); if (IS_DEV) {
webpackSearchHistory.push(["findByProps", [result, ...props]]);
}
return result;
} }
/** /**
@ -293,9 +324,13 @@ export function findByProps<T = any>(...props: string[]) {
* @param code A list of code to search each export for * @param code A list of code to search each export for
*/ */
export function findByCode<T = any>(...code: string[]) { export function findByCode<T = any>(...code: string[]) {
if (IS_DEV) webpackSearchHistory.push(["findByCode", code]); const result = find<T>(filters.byCode(...code), m => m, { isIndirect: true });
return find<T>(filters.byCode(...code), m => m, { isIndirect: true }); if (IS_DEV) {
webpackSearchHistory.push(["findByCode", [result, ...code]]);
}
return result;
} }
/** /**
@ -304,9 +339,13 @@ export function findByCode<T = any>(...code: string[]) {
* @param name The store name * @param name The store name
*/ */
export function findStore<T = any>(name: string) { export function findStore<T = any>(name: string) {
if (IS_DEV) webpackSearchHistory.push(["findStore", [name]]); const result = find<T>(filters.byStoreName(name), m => m, { isIndirect: true });
return find<T>(filters.byStoreName(name), m => m, { isIndirect: true }); if (IS_DEV) {
webpackSearchHistory.push(["findStore", [result, name]]);
}
return result;
} }
/** /**