diff --git a/scripts/generateReport.ts b/scripts/generateReport.ts
index 8864c78e4..2bc3b7902 100644
--- a/scripts/generateReport.ts
+++ b/scripts/generateReport.ts
@@ -242,16 +242,23 @@ page.on("console", async e => {
});
break;
+ case "LazyChunkLoader:":
+ console.error(await getText());
+
+ switch (message) {
+ case "A fatal error occurred:":
+ process.exit(1);
+ }
case "Reporter:":
console.error(await getText());
switch (message) {
+ case "A fatal error occurred:":
+ process.exit(1);
case "Webpack Find Fail:":
process.exitCode = 1;
report.badWebpackFinds.push(otherMessage);
break;
- case "A fatal error occurred:":
- process.exit(1);
case "Finished test":
await browser.close();
await printReport();
diff --git a/src/api/Notifications/NotificationComponent.tsx b/src/api/Notifications/NotificationComponent.tsx
index caa4b64ef..d07143c45 100644
--- a/src/api/Notifications/NotificationComponent.tsx
+++ b/src/api/Notifications/NotificationComponent.tsx
@@ -113,7 +113,7 @@ export default ErrorBoundary.wrap(function NotificationComponent({
{timeout !== 0 && !permanent && (
)}
diff --git a/src/debug/loadLazyChunks.ts b/src/debug/loadLazyChunks.ts
new file mode 100644
index 000000000..d8f84335c
--- /dev/null
+++ b/src/debug/loadLazyChunks.ts
@@ -0,0 +1,167 @@
+/*
+ * Vencord, a Discord client mod
+ * Copyright (c) 2024 Vendicated and contributors
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+import { Logger } from "@utils/Logger";
+import { canonicalizeMatch } from "@utils/patches";
+import * as Webpack from "@webpack";
+import { wreq } from "@webpack";
+
+const LazyChunkLoaderLogger = new Logger("LazyChunkLoader");
+
+export async function loadLazyChunks() {
+ try {
+ LazyChunkLoaderLogger.log("Loading all chunks...");
+
+ const validChunks = new Set();
+ const invalidChunks = new Set();
+ const deferredRequires = new Set();
+
+ let chunksSearchingResolve: (value: void | PromiseLike) => void;
+ const chunksSearchingDone = new Promise(r => chunksSearchingResolve = r);
+
+ // True if resolved, false otherwise
+ const chunksSearchPromises = [] as Array<() => boolean>;
+
+ const LazyChunkRegex = canonicalizeMatch(/(?:(?:Promise\.all\(\[)?(\i\.e\("[^)]+?"\)[^\]]*?)(?:\]\))?)\.then\(\i\.bind\(\i,"([^)]+?)"\)\)/g);
+
+ async function searchAndLoadLazyChunks(factoryCode: string) {
+ const lazyChunks = factoryCode.matchAll(LazyChunkRegex);
+ const validChunkGroups = new Set<[chunkIds: string[], entryPoint: string]>();
+
+ // Workaround for a chunk that depends on the ChannelMessage component but may be be force loaded before
+ // the chunk containing the component
+ const shouldForceDefer = factoryCode.includes(".Messages.GUILD_FEED_UNFEATURE_BUTTON_TEXT");
+
+ await Promise.all(Array.from(lazyChunks).map(async ([, rawChunkIds, entryPoint]) => {
+ const chunkIds = rawChunkIds ? Array.from(rawChunkIds.matchAll(Webpack.ChunkIdsRegex)).map(m => m[1]) : [];
+
+ if (chunkIds.length === 0) {
+ return;
+ }
+
+ let invalidChunkGroup = false;
+
+ for (const id of chunkIds) {
+ if (wreq.u(id) == null || wreq.u(id) === "undefined.js") continue;
+
+ const isWasm = await fetch(wreq.p + wreq.u(id))
+ .then(r => r.text())
+ .then(t => (IS_WEB && t.includes(".module.wasm")) || !t.includes("(this.webpackChunkdiscord_app=this.webpackChunkdiscord_app||[]).push"));
+
+ if (isWasm && IS_WEB) {
+ invalidChunks.add(id);
+ invalidChunkGroup = true;
+ continue;
+ }
+
+ validChunks.add(id);
+ }
+
+ if (!invalidChunkGroup) {
+ validChunkGroups.add([chunkIds, entryPoint]);
+ }
+ }));
+
+ // Loads all found valid chunk groups
+ await Promise.all(
+ Array.from(validChunkGroups)
+ .map(([chunkIds]) =>
+ Promise.all(chunkIds.map(id => wreq.e(id as any).catch(() => { })))
+ )
+ );
+
+ // Requires the entry points for all valid chunk groups
+ for (const [, entryPoint] of validChunkGroups) {
+ try {
+ if (shouldForceDefer) {
+ deferredRequires.add(entryPoint);
+ continue;
+ }
+
+ if (wreq.m[entryPoint]) wreq(entryPoint as any);
+ } catch (err) {
+ console.error(err);
+ }
+ }
+
+ // setImmediate to only check if all chunks were loaded after this function resolves
+ // We check if all chunks were loaded every time a factory is loaded
+ // If we are still looking for chunks in the other factories, the array will have that factory's chunk search promise not resolved
+ // But, if all chunk search promises are resolved, this means we found every lazy chunk loaded by Discord code and manually loaded them
+ setTimeout(() => {
+ let allResolved = true;
+
+ for (let i = 0; i < chunksSearchPromises.length; i++) {
+ const isResolved = chunksSearchPromises[i]();
+
+ if (isResolved) {
+ // Remove finished promises to avoid having to iterate through a huge array everytime
+ chunksSearchPromises.splice(i--, 1);
+ } else {
+ allResolved = false;
+ }
+ }
+
+ if (allResolved) chunksSearchingResolve();
+ }, 0);
+ }
+
+ Webpack.factoryListeners.add(factory => {
+ let isResolved = false;
+ searchAndLoadLazyChunks(factory.toString()).then(() => isResolved = true);
+
+ chunksSearchPromises.push(() => isResolved);
+ });
+
+ for (const factoryId in wreq.m) {
+ let isResolved = false;
+ searchAndLoadLazyChunks(wreq.m[factoryId].toString()).then(() => isResolved = true);
+
+ chunksSearchPromises.push(() => isResolved);
+ }
+
+ await chunksSearchingDone;
+
+ // Require deferred entry points
+ for (const deferredRequire of deferredRequires) {
+ wreq!(deferredRequire as any);
+ }
+
+ // All chunks Discord has mapped to asset files, even if they are not used anymore
+ const allChunks = [] as string[];
+
+ // Matches "id" or id:
+ for (const currentMatch of wreq!.u.toString().matchAll(/(?:"(\d+?)")|(?:(\d+?):)/g)) {
+ const id = currentMatch[1] ?? currentMatch[2];
+ if (id == null) continue;
+
+ allChunks.push(id);
+ }
+
+ if (allChunks.length === 0) throw new Error("Failed to get all chunks");
+
+ // Chunks that are not loaded (not used) by Discord code anymore
+ const chunksLeft = allChunks.filter(id => {
+ return !(validChunks.has(id) || invalidChunks.has(id));
+ });
+
+ await Promise.all(chunksLeft.map(async id => {
+ const isWasm = await fetch(wreq.p + wreq.u(id))
+ .then(r => r.text())
+ .then(t => (IS_WEB && t.includes(".module.wasm")) || !t.includes("(this.webpackChunkdiscord_app=this.webpackChunkdiscord_app||[]).push"));
+
+ // Loads and requires a chunk
+ if (!isWasm) {
+ await wreq.e(id as any);
+ if (wreq.m[id]) wreq(id as any);
+ }
+ }));
+
+ LazyChunkLoaderLogger.log("Finished loading all chunks!");
+ } catch (e) {
+ LazyChunkLoaderLogger.log("A fatal error occurred:", e);
+ }
+}
diff --git a/src/debug/runReporter.ts b/src/debug/runReporter.ts
index d2bae3365..bfedc1b00 100644
--- a/src/debug/runReporter.ts
+++ b/src/debug/runReporter.ts
@@ -5,178 +5,31 @@
*/
import { Logger } from "@utils/Logger";
-import { canonicalizeMatch } from "@utils/patches";
import { SYM_PROXY_INNER_GET, SYM_PROXY_INNER_VALUE } from "@utils/proxyInner";
import * as Webpack from "@webpack";
-import { wreq } from "@webpack";
import { patches } from "plugins";
+import { loadLazyChunks } from "./loadLazyChunks";
+
+
const ReporterLogger = new Logger("Reporter");
async function runReporter() {
- ReporterLogger.log("Starting test...");
-
try {
- const validChunks = new Set();
- const invalidChunks = new Set();
- const deferredRequires = new Set();
+ ReporterLogger.log("Starting test...");
- let chunksSearchingResolve: (value: void | PromiseLike) => void;
- const chunksSearchingDone = new Promise(r => chunksSearchingResolve = r);
+ let loadLazyChunksResolve: (value: void | PromiseLike) => void;
+ const loadLazyChunksDone = new Promise(r => loadLazyChunksResolve = r);
- // True if resolved, false otherwise
- const chunksSearchPromises = [] as Array<() => boolean>;
-
- const LazyChunkRegex = canonicalizeMatch(/(?:(?:Promise\.all\(\[)?(\i\.e\("[^)]+?"\)[^\]]*?)(?:\]\))?)\.then\(\i\.bind\(\i,"([^)]+?)"\)\)/g);
-
- async function searchAndLoadLazyChunks(factoryCode: string) {
- const lazyChunks = factoryCode.matchAll(LazyChunkRegex);
- const validChunkGroups = new Set<[chunkIds: string[], entryPoint: string]>();
-
- // Workaround for a chunk that depends on the ChannelMessage component but may be be force loaded before
- // the chunk containing the component
- const shouldForceDefer = factoryCode.includes(".Messages.GUILD_FEED_UNFEATURE_BUTTON_TEXT");
-
- await Promise.all(Array.from(lazyChunks).map(async ([, rawChunkIds, entryPoint]) => {
- const chunkIds = rawChunkIds ? Array.from(rawChunkIds.matchAll(Webpack.ChunkIdsRegex)).map(m => m[1]) : [];
-
- if (chunkIds.length === 0) {
- return;
- }
-
- let invalidChunkGroup = false;
-
- for (const id of chunkIds) {
- if (wreq.u(id) == null || wreq.u(id) === "undefined.js") continue;
-
- const isWasm = await fetch(wreq.p + wreq.u(id))
- .then(r => r.text())
- .then(t => (IS_WEB && t.includes(".module.wasm")) || !t.includes("(this.webpackChunkdiscord_app=this.webpackChunkdiscord_app||[]).push"));
-
- if (isWasm && IS_WEB) {
- invalidChunks.add(id);
- invalidChunkGroup = true;
- continue;
- }
-
- validChunks.add(id);
- }
-
- if (!invalidChunkGroup) {
- validChunkGroups.add([chunkIds, entryPoint]);
- }
- }));
-
- // Loads all found valid chunk groups
- await Promise.all(
- Array.from(validChunkGroups)
- .map(([chunkIds]) =>
- Promise.all(chunkIds.map(id => wreq.e(id as any).catch(() => { })))
- )
- );
-
- // Requires the entry points for all valid chunk groups
- for (const [, entryPoint] of validChunkGroups) {
- try {
- if (shouldForceDefer) {
- deferredRequires.add(entryPoint);
- continue;
- }
-
- if (wreq.m[entryPoint]) wreq(entryPoint as any);
- } catch (err) {
- console.error(err);
- }
- }
-
- // setImmediate to only check if all chunks were loaded after this function resolves
- // We check if all chunks were loaded every time a factory is loaded
- // If we are still looking for chunks in the other factories, the array will have that factory's chunk search promise not resolved
- // But, if all chunk search promises are resolved, this means we found every lazy chunk loaded by Discord code and manually loaded them
- setTimeout(() => {
- let allResolved = true;
-
- for (let i = 0; i < chunksSearchPromises.length; i++) {
- const isResolved = chunksSearchPromises[i]();
-
- if (isResolved) {
- // Remove finished promises to avoid having to iterate through a huge array everytime
- chunksSearchPromises.splice(i--, 1);
- } else {
- allResolved = false;
- }
- }
-
- if (allResolved) chunksSearchingResolve();
- }, 0);
- }
-
- Webpack.beforeInitListeners.add(async () => {
- ReporterLogger.log("Loading all chunks...");
-
- Webpack.factoryListeners.add(factory => {
- let isResolved = false;
- searchAndLoadLazyChunks(factory.toString()).then(() => isResolved = true);
-
- chunksSearchPromises.push(() => isResolved);
- });
-
- // setImmediate to only search the initial factories after Discord initialized the app
- // our beforeInitListeners are called before Discord initializes the app
- setTimeout(() => {
- for (const factoryId in wreq.m) {
- let isResolved = false;
- searchAndLoadLazyChunks(wreq.m[factoryId].toString()).then(() => isResolved = true);
-
- chunksSearchPromises.push(() => isResolved);
- }
- }, 0);
- });
-
- await chunksSearchingDone;
-
- // Require deferred entry points
- for (const deferredRequire of deferredRequires) {
- wreq!(deferredRequire as any);
- }
-
- // All chunks Discord has mapped to asset files, even if they are not used anymore
- const allChunks = [] as string[];
-
- // Matches "id" or id:
- for (const currentMatch of wreq!.u.toString().matchAll(/(?:"(\d+?)")|(?:(\d+?):)/g)) {
- const id = currentMatch[1] ?? currentMatch[2];
- if (id == null) continue;
-
- allChunks.push(id);
- }
-
- if (allChunks.length === 0) throw new Error("Failed to get all chunks");
-
- // Chunks that are not loaded (not used) by Discord code anymore
- const chunksLeft = allChunks.filter(id => {
- return !(validChunks.has(id) || invalidChunks.has(id));
- });
-
- await Promise.all(chunksLeft.map(async id => {
- const isWasm = await fetch(wreq.p + wreq.u(id))
- .then(r => r.text())
- .then(t => (IS_WEB && t.includes(".module.wasm")) || !t.includes("(this.webpackChunkdiscord_app=this.webpackChunkdiscord_app||[]).push"));
-
- // Loads and requires a chunk
- if (!isWasm) {
- await wreq.e(id as any);
- if (wreq.m[id]) wreq(id as any);
- }
- }));
-
- ReporterLogger.log("Finished loading all chunks!");
+ Webpack.beforeInitListeners.add(() => loadLazyChunks().then(() => loadLazyChunksResolve()));
+ await loadLazyChunksDone;
for (const patch of patches) {
if (!patch.all) {
new Logger("WebpackInterceptor").warn(`Patch by ${patch.plugin} found no module (Module id is -): ${patch.find}`);
}
}
+
await Promise.all(Webpack.webpackSearchHistory.map(async ([searchType, args]) => {
args = [...args];
diff --git a/src/plugins/consoleShortcuts/index.ts b/src/plugins/consoleShortcuts/index.ts
index 65e469e7e..9b164dd10 100644
--- a/src/plugins/consoleShortcuts/index.ts
+++ b/src/plugins/consoleShortcuts/index.ts
@@ -26,6 +26,7 @@ import definePlugin, { PluginNative, StartAt } from "@utils/types";
import * as Webpack from "@webpack";
import { cacheFindAll, extract, filters, findModuleId, search } from "@webpack";
import * as Common from "@webpack/common";
+import { loadLazyChunks } from "debug/loadLazyChunks";
import type { ComponentType } from "react";
const DESKTOP_ONLY = (f: string) => () => {
@@ -83,6 +84,7 @@ function makeShortcuts() {
wpsearch: search,
wpex: extract,
wpexs: (code: string) => extract(findModuleId(code)!),
+ loadLazyChunks: IS_DEV ? loadLazyChunks : () => { throw new Error("loadLazyChunks is dev only."); },
find,
findAll: cacheFindAll,
findByProps,
diff --git a/src/plugins/partyMode/index.ts b/src/plugins/partyMode/index.ts
index 56c19c02c..c40f2e3c7 100644
--- a/src/plugins/partyMode/index.ts
+++ b/src/plugins/partyMode/index.ts
@@ -18,7 +18,7 @@
import { definePluginSettings, migratePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants";
-import definePlugin, { OptionType } from "@utils/types";
+import definePlugin, { OptionType, ReporterTestable } from "@utils/types";
import { FluxDispatcher } from "@webpack/common";
const enum Intensity {
@@ -46,6 +46,7 @@ export default definePlugin({
name: "PartyMode",
description: "Allows you to use party mode cause the party never ends ✨",
authors: [Devs.UwUDev],
+ reporterTestable: ReporterTestable.None,
settings,
start() {
diff --git a/src/utils/Logger.ts b/src/utils/Logger.ts
index 5296184d4..22a381360 100644
--- a/src/utils/Logger.ts
+++ b/src/utils/Logger.ts
@@ -32,7 +32,7 @@ export class Logger {
constructor(public name: string, public color: string = "white") { }
private _log(level: "log" | "error" | "warn" | "info" | "debug", levelColor: string, args: any[], customFmt = "") {
- if (IS_REPORTER) {
+ if (IS_REPORTER && IS_WEB) {
console[level]("[Vencord]", this.name + ":", ...args);
return;
}