From e801c71fdfb77565a4311f03c2b06be8ba2d2ec9 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Sat, 3 Aug 2024 14:33:03 -0300 Subject: [PATCH] Add back top level destructuring for backwards compabitlity --- src/debug/loadLazyChunks.ts | 4 ++-- src/debug/runReporter.ts | 4 ++-- src/utils/lazy.ts | 39 ++++++++++++++++++++++++++++++++++--- src/utils/proxyInner.ts | 39 ++++++++++++++++++++++++++++++++++--- src/webpack/api.tsx | 6 +++--- 5 files changed, 79 insertions(+), 13 deletions(-) diff --git a/src/debug/loadLazyChunks.ts b/src/debug/loadLazyChunks.ts index a8e005250..c53b5050c 100644 --- a/src/debug/loadLazyChunks.ts +++ b/src/debug/loadLazyChunks.ts @@ -10,9 +10,9 @@ import * as Webpack from "@webpack"; import { wreq } from "@webpack"; import { AnyModuleFactory, ModuleFactory } from "webpack"; -const LazyChunkLoaderLogger = new Logger("LazyChunkLoader"); - export async function loadLazyChunks() { + const LazyChunkLoaderLogger = new Logger("LazyChunkLoader"); + try { LazyChunkLoaderLogger.log("Loading all chunks..."); diff --git a/src/debug/runReporter.ts b/src/debug/runReporter.ts index eb4b653cd..4932d2260 100644 --- a/src/debug/runReporter.ts +++ b/src/debug/runReporter.ts @@ -12,9 +12,9 @@ import { addPatch, patches } from "plugins"; import { loadLazyChunks } from "./loadLazyChunks"; -const ReporterLogger = new Logger("Reporter"); - async function runReporter() { + const ReporterLogger = new Logger("Reporter"); + try { ReporterLogger.log("Starting test..."); diff --git a/src/utils/lazy.ts b/src/utils/lazy.ts index 01670d53c..cd043361f 100644 --- a/src/utils/lazy.ts +++ b/src/utils/lazy.ts @@ -69,11 +69,22 @@ const handler: ProxyHandler = { * * @param factory Factory returning the result * @param attempts How many times to try to evaluate the factory before giving up + * @param errMsg The error message to throw when the factory fails + * @param primitiveErrMsg The error message to throw when factory result is a primitive * @returns Result of factory function */ -export function proxyLazy(factory: () => T, attempts = 5): T { +export function proxyLazy( + factory: () => T, + attempts = 5, + errMsg: string | (() => string) = `proxyLazy factory failed:\n\n${factory}`, + primitiveErrMsg = "proxyLazy called on a primitive value.", + isChild = false +): T { const get = makeLazy(factory, attempts, { isIndirect: true }); + let isSameTick = true; + if (!isChild) setTimeout(() => isSameTick = false, 0); + const proxyDummy = Object.assign(function () { }, { [SYM_LAZY_GET]() { if (!proxyDummy[SYM_LAZY_CACHED]) { @@ -82,7 +93,7 @@ export function proxyLazy(factory: () => T, attempts = 5): T { } if (!proxyDummy[SYM_LAZY_CACHED]) { - throw new Error(`proxyLazy factory failed:\n\n${factory}`); + throw new Error(typeof errMsg === "string" ? errMsg : errMsg()); } else { if (typeof proxyDummy[SYM_LAZY_CACHED] === "function") { proxy.toString = proxyDummy[SYM_LAZY_CACHED].toString.bind(proxyDummy[SYM_LAZY_CACHED]); @@ -102,12 +113,34 @@ export function proxyLazy(factory: () => T, attempts = 5): T { return Reflect.get(target, p, receiver); } + // 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 + // `const { meow } = proxyLazy(() => ({ meow: [] }));` + if (!isChild && isSameTick) { + console.warn( + "Destructuring webpack finds/proxyInner/proxyLazy at top level is deprecated. For more information read https://github.com/Vendicated/Vencord/pull/2409#issue-2277161516" + + "\nConsider not destructuring, using findProp or if you really need to destructure, using mapMangledModule instead." + ); + + return proxyLazy( + () => { + const lazyTarget = target[SYM_LAZY_GET](); + return Reflect.get(lazyTarget, p, lazyTarget); + }, + attempts, + errMsg, + primitiveErrMsg, + true + ); + } + const lazyTarget = target[SYM_LAZY_GET](); if (typeof lazyTarget === "object" || typeof lazyTarget === "function") { return Reflect.get(lazyTarget, p, lazyTarget); } - throw new Error("proxyLazy called on a primitive value."); + throw new Error(primitiveErrMsg); } }); diff --git a/src/utils/proxyInner.ts b/src/utils/proxyInner.ts index 98d9cb11b..fdb7dda57 100644 --- a/src/utils/proxyInner.ts +++ b/src/utils/proxyInner.ts @@ -42,14 +42,18 @@ const handler: ProxyHandler = { * IMPORTANT: * Destructuring at top level is not supported for proxyInner. * - * @param err The error message to throw when the inner value is not set - * @param primitiveErr The error message to throw when the inner value is a primitive + * @param errMsg The error message to throw when the inner value is not set + * @param primitiveErrMsg The error message to throw when the inner value is a primitive * @returns A proxy which will act like the inner value when accessed */ export function proxyInner( errMsg: string | (() => string) = "Proxy inner value is undefined, setInnerValue was never called.", - primitiveErrMsg = "proxyInner called on a primitive value." + primitiveErrMsg = "proxyInner called on a primitive value. This can happen if you try to destructure a primitive at the same tick as the proxy was created.", + isChild = false ): [proxy: T, setInnerValue: (innerValue: T) => void] { + let isSameTick = true; + if (!isChild) setTimeout(() => isSameTick = false, 0); + const proxyDummy = Object.assign(function () { }, { [SYM_PROXY_INNER_GET]: function () { if (proxyDummy[SYM_PROXY_INNER_VALUE] == null) { @@ -68,6 +72,29 @@ export function proxyInner( return Reflect.get(target, p, receiver); } + // If we're still in the same tick, it means the proxy was immediately used. + // And, if the inner value is still nullish, it means the proxy was used before setInnerValue was called. + // So, proxy the get access to make things like destructuring work as expected. + // We dont need to proxy if the inner value is available, and recursiveSetInnerValue won't ever be called anyways, + // because the top setInnerValue was called before we proxied the get access + // example here will also be a proxy: + // `const { example } = findByProps("example");` + if (isSameTick && !isChild && proxyDummy[SYM_PROXY_INNER_VALUE] == null) { + console.warn( + "Destructuring webpack finds/proxyInner/proxyLazy at top level is deprecated. For more information read https://github.com/Vendicated/Vencord/pull/2409#issue-2277161516" + + "\nConsider not destructuring, using findProp or if you really need to destructure, using mapMangledModule instead." + ); + + const [recursiveProxy, recursiveSetInnerValue] = proxyInner(errMsg, primitiveErrMsg, true); + + recursiveSetInnerValues.push((innerValue: T) => { + // Set the inner value of the destructured value as the prop value p of the parent + recursiveSetInnerValue(Reflect.get(innerValue as object, p, innerValue)); + }); + + return recursiveProxy; + } + const innerTarget = target[SYM_PROXY_INNER_GET](); if (typeof innerTarget === "object" || typeof innerTarget === "function") { return Reflect.get(innerTarget, p, innerTarget); @@ -77,8 +104,14 @@ export function proxyInner( } }); + // Values destructured in the same tick the proxy was created will push their setInnerValue here + const recursiveSetInnerValues = [] as Array<(innerValue: T) => void>; + + // Once we set the parent inner value, we will call the setInnerValue functions of the destructured values, + // for them to get the proper value from the parent and use as their inner instead function setInnerValue(innerValue: T) { proxyDummy[SYM_PROXY_INNER_VALUE] = innerValue; + recursiveSetInnerValues.forEach(setInnerValue => setInnerValue(innerValue)); // Avoid binding toString if the inner value is null. // This can happen if we are setting the inner value as another instance of proxyInner, which will cause that proxy to instantly evaluate and throw an error diff --git a/src/webpack/api.tsx b/src/webpack/api.tsx index 97d03dde2..6b5c34ef8 100644 --- a/src/webpack/api.tsx +++ b/src/webpack/api.tsx @@ -254,7 +254,7 @@ export function find(filter: FilterFn, parse: (module: ModuleExports) = if (typeof parse !== "function") throw new Error("Invalid find parse. Expected a function got " + typeof parse); - const [proxy, setInnerValue] = proxyInner(`Webpack find matched no module. Filter: ${printFilter(filter)}`, "Webpack find with proxy called on a primitive value."); + const [proxy, setInnerValue] = proxyInner(`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, m => setInnerValue(parse(m)), { isIndirect: true }); if (IS_REPORTER && !isIndirect) { @@ -552,7 +552,7 @@ export function mapMangledModule(code: string | RegExp | export function findModuleFactory(...code: CodeFilter) { const filter = filters.byFactoryCode(...code); - const [proxy, setInnerValue] = proxyInner(`Webpack module factory find matched no module. Filter: ${printFilter(filter)}`, "Webpack find with proxy called on a primitive value."); + const [proxy, setInnerValue] = proxyInner(`Webpack module factory 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, (_, { factory }) => setInnerValue(factory)); if (proxy[SYM_PROXY_INNER_VALUE] != null) return proxy[SYM_PROXY_INNER_VALUE] as AnyModuleFactory; @@ -573,7 +573,7 @@ export function findModuleFactory(...code: CodeFilter) { export function webpackDependantLazy(factory: () => T, attempts?: number) { if (IS_REPORTER) webpackSearchHistory.push(["webpackDependantLazy", [factory]]); - return proxyLazy(factory, attempts); + return proxyLazy(factory, attempts, `Webpack dependant lazy factory failed:\n\n${factory}`, "Webpack dependant lazy called on a primitive value. This can happen if you try to destructure a primitive in the top level definition of the lazy."); } /**