Utility function for loading Discord chunks (#2017)

This commit is contained in:
Nuckyz 2023-11-27 02:56:57 -03:00 committed by GitHub
parent 867730a478
commit fc10bc1e69
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 118 additions and 27 deletions

View file

@ -406,15 +406,21 @@ function runTime(token: string) {
if (method === "proxyLazyWebpack" || method === "LazyComponentWebpack") { if (method === "proxyLazyWebpack" || method === "LazyComponentWebpack") {
const [factory] = args; const [factory] = args;
result = factory(); result = factory();
} else if (method === "extractAndLoadChunks") {
const [code, matcher] = args;
const module = Vencord.Webpack.findModuleFactory(...code);
if (module) result = module.toString().match(matcher);
} else { } else {
// @ts-ignore // @ts-ignore
result = Vencord.Webpack[method](...args); result = Vencord.Webpack[method](...args);
} }
if (result == null || ("$$get" in result && result.$$get() == null)) throw "a rock at ben shapiro"; if (result == null || ("$$vencordInternal" in result && result.$$vencordInternal() == null)) throw "a rock at ben shapiro";
} catch (e) { } catch (e) {
let logMessage = searchType; let logMessage = searchType;
if (method === "find" || method === "proxyLazyWebpack" || method === "LazyComponentWebpack") logMessage += `(${args[0].toString().slice(0, 147)}...)`; if (method === "find" || method === "proxyLazyWebpack" || method === "LazyComponentWebpack") logMessage += `(${args[0].toString().slice(0, 147)}...)`;
else if (method === "extractAndLoadChunks") logMessage += `([${args[0].map(arg => `"${arg}"`).join(", ")}], ${args[1].toString()})`;
else logMessage += `(${args.map(arg => `"${arg}"`).join(", ")})`; else logMessage += `(${args.map(arg => `"${arg}"`).join(", ")})`;
console.log("[PUP_WEBPACK_FIND_FAIL]", logMessage); console.log("[PUP_WEBPACK_FIND_FAIL]", logMessage);

View file

@ -23,7 +23,7 @@ export function LazyComponent<T extends object = any>(factory: () => React.Compo
return <Component {...props} />; return <Component {...props} />;
}; };
LazyComponent.$$get = get; LazyComponent.$$vencordInternal = get;
return LazyComponent as ComponentType<T>; return LazyComponent as ComponentType<T>;
} }

View file

@ -152,11 +152,9 @@ function patchFactories(factories: Record<string | number, (module: { exports: a
return; return;
} }
const numberId = Number(id);
for (const callback of listeners) { for (const callback of listeners) {
try { try {
callback(exports, numberId); callback(exports, id);
} catch (err) { } catch (err) {
logger.error("Error in webpack listener", err); logger.error("Error in webpack listener", err);
} }
@ -166,10 +164,10 @@ function patchFactories(factories: Record<string | number, (module: { exports: a
try { try {
if (filter(exports)) { if (filter(exports)) {
subscriptions.delete(filter); subscriptions.delete(filter);
callback(exports, numberId); callback(exports, id);
} else if (exports.default && filter(exports.default)) { } else if (exports.default && filter(exports.default)) {
subscriptions.delete(filter); subscriptions.delete(filter);
callback(exports.default, numberId); callback(exports.default, id);
} }
} catch (err) { } catch (err) {
logger.error("Error while firing callback for webpack chunk", err); logger.error("Error while firing callback for webpack chunk", err);

View file

@ -19,6 +19,7 @@
import { proxyLazy } from "@utils/lazy"; import { proxyLazy } from "@utils/lazy";
import { LazyComponent } from "@utils/lazyReact"; import { LazyComponent } from "@utils/lazyReact";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { canonicalizeMatch } from "@utils/patches";
import type { WebpackInstance } from "discord-types/other"; import type { WebpackInstance } from "discord-types/other";
import { traceFunction } from "../debug/Tracer"; import { traceFunction } from "../debug/Tracer";
@ -69,7 +70,7 @@ export const filters = {
export const subscriptions = new Map<FilterFn, CallbackFn>(); export const subscriptions = new Map<FilterFn, CallbackFn>();
export const listeners = new Set<CallbackFn>(); export const listeners = new Set<CallbackFn>();
export type CallbackFn = (mod: any, id: number) => void; export type CallbackFn = (mod: any, id: string) => void;
export function _initWebpack(instance: typeof window.webpackChunkdiscord_app) { export function _initWebpack(instance: typeof window.webpackChunkdiscord_app) {
if (cache !== void 0) throw "no."; if (cache !== void 0) throw "no.";
@ -111,12 +112,12 @@ export const find = traceFunction("find", function find(filter: FilterFn, { isIn
if (!mod?.exports) continue; if (!mod?.exports) continue;
if (filter(mod.exports)) { if (filter(mod.exports)) {
return isWaitFor ? [mod.exports, Number(key)] : mod.exports; return isWaitFor ? [mod.exports, key] : mod.exports;
} }
if (mod.exports.default && filter(mod.exports.default)) { if (mod.exports.default && filter(mod.exports.default)) {
const found = mod.exports.default; const found = mod.exports.default;
return isWaitFor ? [found, Number(key)] : found; return isWaitFor ? [found, key] : found;
} }
} }
@ -214,18 +215,21 @@ export const findBulk = traceFunction("findBulk", function findBulk(...filterFns
}); });
/** /**
* Find the id of a module by its code * Find the id of the first module factory that includes all the given code
* @param code Code * @returns string or null
* @returns number or null
*/ */
export const findModuleId = traceFunction("findModuleId", function findModuleId(code: string) { export const findModuleId = traceFunction("findModuleId", function findModuleId(...code: string[]) {
outer:
for (const id in wreq.m) { for (const id in wreq.m) {
if (wreq.m[id].toString().includes(code)) { const str = wreq.m[id].toString();
return Number(id);
for (const c of code) {
if (!str.includes(c)) continue outer;
} }
return id;
} }
const err = new Error("Didn't find module with code:\n" + code); const err = new Error("Didn't find module with code(s):\n" + code.join("\n"));
if (IS_DEV) { if (IS_DEV) {
if (!devToolsOpen) if (!devToolsOpen)
// Strict behaviour in DevBuilds to fail early and make sure the issue is found // Strict behaviour in DevBuilds to fail early and make sure the issue is found
@ -237,7 +241,18 @@ export const findModuleId = traceFunction("findModuleId", function findModuleId(
return null; return null;
}); });
export const lazyWebpackSearchHistory = [] as Array<["find" | "findByProps" | "findByCode" | "findStore" | "findComponent" | "findComponentByCode" | "findExportedComponent" | "waitFor" | "waitForComponent" | "waitForStore" | "proxyLazyWebpack" | "LazyComponentWebpack", any[]]>; /**
* Find the first module factory that includes all the given code
* @returns The module factory or null
*/
export function findModuleFactory(...code: string[]) {
const id = findModuleId(...code);
if (!id) return null;
return wreq.m[id];
}
export const lazyWebpackSearchHistory = [] as Array<["find" | "findByProps" | "findByCode" | "findStore" | "findComponent" | "findComponentByCode" | "findExportedComponent" | "waitFor" | "waitForComponent" | "waitForStore" | "proxyLazyWebpack" | "LazyComponentWebpack" | "extractAndLoadChunks", any[]]>;
/** /**
* This is just a wrapper around {@link proxyLazy} to make our reporter test for your webpack finds. * This is just a wrapper around {@link proxyLazy} to make our reporter test for your webpack finds.
@ -272,7 +287,7 @@ export function LazyComponentWebpack<T extends object = any>(factory: () => any,
} }
/** /**
* find but lazy * Find the first module that matches the filter, lazily
*/ */
export function findLazy(filter: FilterFn) { export function findLazy(filter: FilterFn) {
if (IS_DEV) lazyWebpackSearchHistory.push(["find", [filter]]); if (IS_DEV) lazyWebpackSearchHistory.push(["find", [filter]]);
@ -291,7 +306,7 @@ export function findByProps(...props: string[]) {
} }
/** /**
* findByProps but lazy * Find the first module that has the specified properties, lazily
*/ */
export function findByPropsLazy(...props: string[]) { export function findByPropsLazy(...props: string[]) {
if (IS_DEV) lazyWebpackSearchHistory.push(["findByProps", props]); if (IS_DEV) lazyWebpackSearchHistory.push(["findByProps", props]);
@ -300,7 +315,7 @@ export function findByPropsLazy(...props: string[]) {
} }
/** /**
* Find a function by its code * Find the first function that includes all the given code
*/ */
export function findByCode(...code: string[]) { export function findByCode(...code: string[]) {
const res = find(filters.byCode(...code), { isIndirect: true }); const res = find(filters.byCode(...code), { isIndirect: true });
@ -310,7 +325,7 @@ export function findByCode(...code: string[]) {
} }
/** /**
* findByCode but lazy * Find the first function that includes all the given code, lazily
*/ */
export function findByCodeLazy(...code: string[]) { export function findByCodeLazy(...code: string[]) {
if (IS_DEV) lazyWebpackSearchHistory.push(["findByCode", code]); if (IS_DEV) lazyWebpackSearchHistory.push(["findByCode", code]);
@ -329,7 +344,7 @@ export function findStore(name: string) {
} }
/** /**
* findStore but lazy * Find a store by its displayName, lazily
*/ */
export function findStoreLazy(name: string) { export function findStoreLazy(name: string) {
if (IS_DEV) lazyWebpackSearchHistory.push(["findStore", [name]]); if (IS_DEV) lazyWebpackSearchHistory.push(["findStore", [name]]);
@ -353,7 +368,13 @@ export function findComponentByCode(...code: string[]) {
export function findComponentLazy<T extends object = any>(filter: FilterFn) { export function findComponentLazy<T extends object = any>(filter: FilterFn) {
if (IS_DEV) lazyWebpackSearchHistory.push(["findComponent", [filter]]); if (IS_DEV) lazyWebpackSearchHistory.push(["findComponent", [filter]]);
return LazyComponent<T>(() => find(filter));
return LazyComponent<T>(() => {
const res = find(filter, { isIndirect: true });
if (!res)
handleModuleNotFound("findComponent", filter);
return res;
});
} }
/** /**
@ -362,7 +383,12 @@ export function findComponentLazy<T extends object = any>(filter: FilterFn) {
export function findComponentByCodeLazy<T extends object = any>(...code: string[]) { export function findComponentByCodeLazy<T extends object = any>(...code: string[]) {
if (IS_DEV) lazyWebpackSearchHistory.push(["findComponentByCode", code]); if (IS_DEV) lazyWebpackSearchHistory.push(["findComponentByCode", code]);
return LazyComponent<T>(() => findComponentByCode(...code)); return LazyComponent<T>(() => {
const res = find(filters.componentByCode(...code), { isIndirect: true });
if (!res)
handleModuleNotFound("findComponentByCode", ...code);
return res;
});
} }
/** /**
@ -371,7 +397,68 @@ export function findComponentByCodeLazy<T extends object = any>(...code: string[
export function findExportedComponentLazy<T extends object = any>(...props: string[]) { export function findExportedComponentLazy<T extends object = any>(...props: string[]) {
if (IS_DEV) lazyWebpackSearchHistory.push(["findExportedComponent", props]); if (IS_DEV) lazyWebpackSearchHistory.push(["findExportedComponent", props]);
return LazyComponent<T>(() => findByProps(...props)?.[props[0]]); return LazyComponent<T>(() => {
const res = find(filters.byProps(...props), { isIndirect: true });
if (!res)
handleModuleNotFound("findExportedComponent", ...props);
return res[props[0]];
});
}
/**
* Extract and load chunks using their entry point
* @param code An array of all the code the module factory containing the entry point (as of using it to load chunks) must include
* @param matcher A RegExp that returns the entry point id as the first capture group. Defaults to a matcher that captures the first entry point found in the module factory
*/
export async function extractAndLoadChunks(code: string[], matcher: RegExp = /\.el\("(.+?)"\)(?<=(\i)\.el.+?)\.then\(\2\.bind\(\2,"\1"\)\)/) {
const module = findModuleFactory(...code);
if (!module) {
const err = new Error("extractAndLoadChunks: Couldn't find module factory");
logger.warn(err, "Code:", code, "Matcher:", matcher);
return;
}
const match = module.toString().match(canonicalizeMatch(matcher));
if (!match) {
const err = new Error("extractAndLoadChunks: Couldn't find entry point id in module factory code");
logger.warn(err, "Code:", code, "Matcher:", matcher);
// Strict behaviour in DevBuilds to fail early and make sure the issue is found
if (IS_DEV && !devToolsOpen)
throw err;
return;
}
const [, id] = match;
if (!id || !Number(id)) {
const err = new Error("extractAndLoadChunks: Matcher didn't return a capturing group with the entry point, or the entry point returned wasn't a number");
logger.warn(err, "Code:", code, "Matcher:", matcher);
// Strict behaviour in DevBuilds to fail early and make sure the issue is found
if (IS_DEV && !devToolsOpen)
throw err;
return;
}
await (wreq as any).el(id);
return wreq(id as any);
}
/**
* This is just a wrapper around {@link extractAndLoadChunks} to make our reporter test for your webpack finds.
*
* Extract and load chunks using their entry point
* @param code An array of all the code the module factory containing the entry point (as of using it to load chunks) must include
* @param matcher A RegExp that returns the entry point id as the first capture group. Defaults to a matcher that captures the first entry point found in the module factory
* @returns A function that loads the chunks on first call
*/
export function extractAndLoadChunksLazy(code: string[], matcher: RegExp = /\.el\("(.+?)"\)(?<=(\i)\.el.+?)\.then\(\2\.bind\(\2,"\1"\)\)/) {
if (IS_DEV) lazyWebpackSearchHistory.push(["extractAndLoadChunks", [code, matcher]]);
return () => extractAndLoadChunks(code, matcher);
} }
/** /**
@ -433,7 +520,7 @@ export function search(...filters: Array<string | RegExp>) {
* so putting breakpoints or similar will have no effect. * so putting breakpoints or similar will have no effect.
* @param id The id of the module to extract * @param id The id of the module to extract
*/ */
export function extract(id: number) { export function extract(id: string | number) {
const mod = wreq.m[id] as Function; const mod = wreq.m[id] as Function;
if (!mod) return null; if (!mod) return null;