diff --git a/scripts/generateReport.ts b/scripts/generateReport.ts index 164e409d..8bb87d81 100644 --- a/scripts/generateReport.ts +++ b/scripts/generateReport.ts @@ -243,19 +243,27 @@ page.on("console", async e => { } } - if (isDebug) { - console.error(e.text()); - } else if (level === "error") { - const text = await Promise.all( - e.args().map(async a => { - try { + async function getText() { + try { + return await Promise.all( + e.args().map(async a => { return await maybeGetError(a) || await a.jsonValue(); - } catch (e) { - return a.toString(); - } - }) - ).then(a => a.join(" ").trim()); + }) + ).then(a => a.join(" ").trim()); + } catch { + return e.text(); + } + } + + if (isDebug) { + const text = await getText(); + console.error(text); + if (text.includes("A fatal error occurred:")) { + process.exit(1); + } + } else if (level === "error") { + const text = await getText(); if (text.length && !text.startsWith("Failed to load resource: the server responded with a status of") && !text.includes("Webpack")) { console.error("[Unexpected Error]", text); @@ -322,22 +330,31 @@ async function runtime(token: string) { 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\.\i\(".+?"\).+?\])\).then\(\i\.bind\(\i,"(.+?)"\)\)/g); - const chunkIdsRegex = canonicalizeMatch(/\("(.+?)"\)/g); + + const LazyChunkRegex = canonicalizeMatch(/(?:Promise\.all\(\[(\i\.\i\("[^)]+?"\)[^\]]+?)\]\)|(\i\.\i\("[^)]+?"\)))\.then\(\i\.bind\(\i,"([^)]+?)"\)\)/g); async function searchAndLoadLazyChunks(factoryCode: string) { - const lazyChunks = factoryCode.matchAll(lazyChunkRegex); + const lazyChunks = factoryCode.matchAll(LazyChunkRegex); const validChunkGroups = new Set<[chunkIds: string[], entryPoint: string]>(); - await Promise.all(Array.from(lazyChunks).map(async ([, rawChunkIds, entryPoint]) => { - const chunkIds = Array.from(rawChunkIds.matchAll(chunkIdsRegex)).map(m => m[1]); - if (chunkIds.length === 0) return; + // 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 ([, rawChunkIdsArray, rawChunkIdsSingle, entryPoint]) => { + const rawChunkIds = rawChunkIdsArray ?? rawChunkIdsSingle; + const chunkIds = rawChunkIds ? Array.from(rawChunkIds.matchAll(Vencord.Webpack.ChunkIdsRegex)).map(m => m[1]) : []; + + if (chunkIds.length === 0) { + return; + } let invalidChunkGroup = false; @@ -373,6 +390,11 @@ async function runtime(token: string) { // 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); @@ -435,6 +457,11 @@ async function runtime(token: string) { 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[]; @@ -514,7 +541,6 @@ async function runtime(token: string) { setTimeout(() => console.log("[PUPPETEER_TEST_DONE_SIGNAL]"), 1000); } catch (e) { console.log("[PUP_DEBUG]", "A fatal error occurred:", e); - process.exit(1); } } diff --git a/src/webpack/webpack.ts b/src/webpack/webpack.ts index 0bee08f3..85482085 100644 --- a/src/webpack/webpack.ts +++ b/src/webpack/webpack.ts @@ -402,7 +402,8 @@ export function findExportedComponentLazy(...props: stri }); } -const DefaultExtractAndLoadChunksRegex = /(?:Promise\.all\((\[\i\.\i\(".+?"\).+?\])\)|Promise\.resolve\(\)).then\(\i\.bind\(\i,"(.+?)"\)\)/; +export const DefaultExtractAndLoadChunksRegex = /(?:Promise\.all\(\[(\i\.\i\("[^)]+?"\)[^\]]+?)\]\)|(\i\.\i\("[^)]+?"\))|Promise\.resolve\(\))\.then\(\i\.bind\(\i,"([^)]+?)"\)\)/; +export const ChunkIdsRegex = /\("(.+?)"\)/g; /** * Extract and load chunks using their entry point @@ -431,7 +432,7 @@ export async function extractAndLoadChunks(code: string[], matcher: RegExp = Def return; } - const [, rawChunkIds, entryPointId] = match; + const [, rawChunkIdsArray, rawChunkIdsSingle, entryPointId] = match; if (Number.isNaN(Number(entryPointId))) { const err = new Error("extractAndLoadChunks: Matcher didn't return a capturing group with the chunk ids array, or the entry point id returned as the second group wasn't a number"); logger.warn(err, "Code:", code, "Matcher:", matcher); @@ -443,8 +444,9 @@ export async function extractAndLoadChunks(code: string[], matcher: RegExp = Def return; } + const rawChunkIds = rawChunkIdsArray ?? rawChunkIdsSingle; if (rawChunkIds) { - const chunkIds = Array.from(rawChunkIds.matchAll(/\("(.+?)"\)/g)).map((m: any) => m[1]); + const chunkIds = Array.from(rawChunkIds.matchAll(ChunkIdsRegex)).map((m: any) => m[1]); await Promise.all(chunkIds.map(id => wreq.e(id))); }