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
// 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];
const WEBPACK_SEARCH_HISTORY_WITH_FILTER_NAME_PROP = ["find", "findComponent", "waitFor"];
let method = searchType as string;
if (searchType === "waitFor") method = "cacheFind";
// eslint-disable-next-line prefer-const
for (let [searchType, args] of Vencord.Webpack.webpackSearchHistory) {
args = [...args];
try {
let result: any;
let result = null as any;
if (method === "webpackDependantLazy" || method === "webpackDependantLazyComponent") {
if (searchType === "webpackDependantLazy" || searchType === "webpackDependantLazyComponent") {
const [factory] = args;
result = factory();
if (result != null && "$$vencordGetter" in result) {
result = result.$$vencordGetter();
}
} else if (method === "extractAndLoadChunks") {
} else if (searchType === "extractAndLoadChunks") {
const [code, matcher] = args;
const module = Vencord.Webpack.findModuleFactory(...code);
@ -498,14 +491,20 @@ async function runtime(token: string) {
result = module.toString().match(Vencord.Util.canonicalizeMatch(matcher));
}
} 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 &&
(result[Vencord.Util.proxyInnerGet] != null || "$$vencordGetter" in result)
) {
result = undefined;
if (result != null) {
if (result.$$vencordCallbackCalled != null && !result.$$vencordCallbackCalled()) {
result = null;
}
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 parsedArgs = args;
if ("$$vencordProps" in args[0]) {
if (
searchType === "find" ||
searchType === "findComponent" ||
searchType === "waitFor"
) {
filterName = args[0].$$vencordProps[0];
if (args[0].$$vencordProps != null) {
if (WEBPACK_SEARCH_HISTORY_WITH_FILTER_NAME_PROP.includes(searchType)) {
filterName = args[0].$$vencordProps.shift();
}
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 (
parsedArgs === args && searchType === "waitFor" ||
searchType === "find" ||
searchType === "findComponent" ||
searchType === "webpackDependantLazy" ||
searchType === "webpackDependantLazyComponent"
parsedArgs === args && (searchType === "waitFor" ||
searchType === "find" ||
searchType === "findComponent" ||
searchType === "webpackDependantLazy" ||
searchType === "webpackDependantLazyComponent")
) {
logMessage += `(${parsedArgs[0].toString().slice(0, 147)}...)`;
logMessage += `(${args[0].toString().slice(0, 147)}...)`;
} 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 {
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
*/
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 cache: T;
const getter = () => {
if (!cache && attempts > tries) {
cache = factory();
if (!cache && attempts === ++tries) {
if (!cache && attempts === ++tries && !isIndirect) {
console.error(`Lazy factory failed:\n\n${factory}`);
}
}
return cache;
};
@ -48,8 +49,8 @@ const handler: ProxyHandler<any> = {
}
};
const proxyLazyGet = Symbol.for("vencord.lazy.get");
const proxyLazyCache = Symbol.for("vencord.lazy.cached");
export const proxyLazyGet = Symbol.for("vencord.lazy.get");
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.
@ -59,7 +60,7 @@ const proxyLazyCache = Symbol.for("vencord.lazy.cached");
* @returns Result of factory function
*/
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;
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, {
...handler,
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.
// thus, we lazy proxy the get access to make things like destructuring work as expected
// meow here will also be a lazy

View file

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

View file

@ -22,6 +22,8 @@ import { checkIntersecting } from "./misc";
export * from "./lazyReact";
export const NoopComponent = () => null;
/**
* Check if an element is on screen
* @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")
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) {
const existing = cacheFind(filter);
@ -165,11 +180,13 @@ export function find<T = any>(filter: FilterFn, callback: (mod: any) => any = m
if (typeof callback !== "function")
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.");
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;
return proxy;
@ -187,9 +204,7 @@ export function findComponent<T extends object = any>(filter: FilterFn, parse: (
if (typeof parse !== "function")
throw new Error("Invalid component parse. Expected a function got " + typeof parse);
if (IS_DEV && !isIndirect) webpackSearchHistory.push(["findComponent", [filter]]);
let InnerComponent = null as null | React.ComponentType<T>;
let InnerComponent = null as React.ComponentType<T> | null;
let findFailedLogged = false;
const WrapperComponent = (props: T) => {
@ -201,14 +216,20 @@ export function findComponent<T extends object = any>(filter: FilterFn, parse: (
return InnerComponent && <InnerComponent {...props} />;
};
WrapperComponent.$$vencordGetter = () => InnerComponent;
waitFor(filter, (v: any) => {
const parsedComponent = parse(v);
InnerComponent = parsedComponent;
Object.assign(InnerComponent, parsedComponent);
}, { isIndirect: true });
if (IS_DEV) {
WrapperComponent.$$vencordInner = () => InnerComponent;
if (!isIndirect) {
webpackSearchHistory.push(["findComponent", [WrapperComponent, filter]]);
}
}
if (InnerComponent !== null) return InnerComponent;
return WrapperComponent;
@ -230,9 +251,7 @@ export function findExportedComponent<T extends object = any>(...props: string[]
const filter = filters.byProps(...newProps);
if (IS_DEV) webpackSearchHistory.push(["findExportedComponent", props]);
let InnerComponent = null as null | React.ComponentType<T>;
let InnerComponent = null as React.ComponentType<T> | null;
let findFailedLogged = false;
const WrapperComponent = (props: T) => {
@ -244,7 +263,6 @@ export function findExportedComponent<T extends object = any>(...props: string[]
return InnerComponent && <InnerComponent {...props} />;
};
WrapperComponent.$$vencordGetter = () => InnerComponent;
waitFor(filter, (v: any) => {
const parsedComponent = parse(v[newProps[0]]);
@ -252,6 +270,11 @@ export function findExportedComponent<T extends object = any>(...props: string[]
Object.assign(InnerComponent, parsedComponent);
}, { isIndirect: true });
if (IS_DEV) {
WrapperComponent.$$vencordInner = () => InnerComponent;
webpackSearchHistory.push(["findExportedComponent", [WrapperComponent, ...props]]);
}
if (InnerComponent !== null) return InnerComponent;
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 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
*/
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
*/
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
*/
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;
}
/**