Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(Webpack): Centralize and refactor Webpack patching #2485

Open
wants to merge 125 commits into
base: dev
Choose a base branch
from

Conversation

Nuckyz
Copy link
Collaborator

@Nuckyz Nuckyz commented May 20, 2024

Centralizes Webpack patching around a single thing: Patching the module factories object of the WebpackRequire (wreq.m). This wraps the modules object with a proxy to intercept the addition of new modules, and allow us to decide what to do with them.

A new internal setting was added, eagerPatches, and it decides what to do with the newly added modules. With it enabled, we patch the factories as soon as they are set, and with it disabled we only patch them once they are accessed for the first time.

For correctly handling patching, because of multiple WebpackInstances, and to allow for patching factories only when they are accessed, our proxy defines getters and setters for each module factory added, instead of doing a simple Reflect.set on the module factories object.

Obtaining the main WebpackRequire instance is now also handled by the same way we patch wreq.m, as inside of the wreq.O setter we define logic to check if this wreq instance is the main one, and initialize our references if it is.

Additionally, this PR does a lot of clean up related to the main Webpack patching things. This includes changes to the reporter code (which was also modified to fit the removal of the beforeListeners API), variable names, stringifying of modules and QOL changes for better DevTools analyzing.

I also took the opportunity to correctly document all of the WebpackRequire instance, including full typings of it and docs of its internal methods (all of the ones present in the main Discord WebpackRequire). Our code utilizes the newer and correct typings now.

The patchedFactory wrapper is more future proof now and has a fallback logic if we fail to acquire the main WebpackRequire instance, for whatever the reason is. The first time it is called, it will attempt to use the value of the require argument of the factory as our internal reference to the WebpackRequire.

and yes, this completely removes the refactor I did 2 months ago

@Nuckyz Nuckyz changed the base branch from main to dev May 20, 2024 01:51
src/webpack/patchWebpack.ts Fixed Show fixed Hide fixed
src/webpack/patchWebpack.ts Fixed Show fixed Hide fixed
@Nuckyz
Copy link
Collaborator Author

Nuckyz commented May 21, 2024

@CodiumAI-Agent /review

@Nuckyz
Copy link
Collaborator Author

Nuckyz commented May 21, 2024

@CodiumAI-Agent /improve

@CodiumAI-Agent
Copy link

PR Code Suggestions ✨

CategorySuggestion                                                                                                                                    Score
Possible bug
Add null checks for wreq and wreq.m to prevent runtime errors

Consider checking if wreq and wreq.m are not null or undefined before iterating over
wreq.m. This will prevent potential runtime errors if wreq or wreq.m are not properly
initialized.

scripts/generateReport.ts [470-471]

-for (const id in wreq!.m) {
-    wreq!.m[id];
+if (wreq && wreq.m) {
+    for (const id in wreq.m) {
+        wreq.m[id];
+    }
 }
 
Suggestion importance[1-10]: 10

Why: Adding null checks for wreq and wreq.m is crucial to prevent potential runtime errors if these objects are not properly initialized. This suggestion addresses a possible bug and enhances the robustness of the code.

10
Add a check to ensure the target value is not undefined before getting its property descriptor

The condition in handler.getOwnPropertyDescriptor should check if targetkGET is not
undefined before attempting to get its property descriptor to avoid potential runtime
errors.

src/utils/lazy.ts [70]

-const descriptor = Reflect.getOwnPropertyDescriptor(target[kGET](), p);
+const value = target[kGET]();
+const descriptor = value ? Reflect.getOwnPropertyDescriptor(value, p) : undefined;
 
Suggestion importance[1-10]: 9

Why: Adding a check to ensure that target[kGET]() is not undefined before attempting to get its property descriptor is important to prevent potential runtime errors. This suggestion addresses a possible bug and improves code safety.

9
Add error handling for cases where patchFactory returns null or undefined

Consider handling the case where patchedMod might be undefined or null after the
patchFactory call, to prevent potential runtime errors.

src/webpack/patchWebpack.ts [30-31]

 const patchedMod = patchFactory(p, mod);
-Reflect.set(target, p, patchedMod);
+if (patchedMod) {
+    Reflect.set(target, p, patchedMod);
+} else {
+    logger.warn(`Failed to patch module with id: ${p}`);
+}
 
Suggestion importance[1-10]: 9

Why: This suggestion adds important error handling to prevent potential runtime errors if patchFactory returns null or undefined, enhancing the robustness of the code.

9
Enhancement
Use a functional approach to combine arrays for better clarity and immutability

Instead of directly modifying the keys array within the loop, consider using a more
functional approach with Array.concat or spread syntax to enhance code clarity and
immutability.

src/utils/lazy.ts [60-61]

-for (const key of UNCONFIGURABLE_PROPERTIES) {
-    if (!keys.includes(key)) keys.push(key);
-}
+const allKeys = [...keys, ...UNCONFIGURABLE_PROPERTIES.filter(key => !keys.includes(key))];
 
Suggestion importance[1-10]: 7

Why: Using a functional approach with Array.concat or spread syntax can improve code clarity and maintainability by avoiding direct modification of the keys array. This is a good enhancement but not critical.

7
Improve readability and adopt functional programming practices by using array methods

Replace the for loop with Array.prototype.filter and Array.prototype.concat for better
readability and functional programming practices.

src/webpack/patchWebpack.ts [37-40]

 const keys = Reflect.ownKeys(target);
-for (const key of UNCONFIGURABLE_PROPERTIES) {
-    if (!keys.includes(key)) keys.push(key);
-}
+const additionalKeys = UNCONFIGURABLE_PROPERTIES.filter(key => !keys.includes(key));
+const finalKeys = keys.concat(additionalKeys);
 
Suggestion importance[1-10]: 6

Why: The suggestion improves readability and adopts functional programming practices, but the performance impact of this change is minimal, making it a minor enhancement.

6
Maintainability
Improve type safety by replacing any with more specific types or interfaces

Replace the use of any type with more specific types or interfaces to improve type safety
and maintainability of the code.

src/webpack/patchWebpack.ts [20-42]

-const modulesProxyhandler: ProxyHandler<any> = {
+interface Module {
+    [key: string]: any; // Define more specific types if possible
+}
+
+const modulesProxyhandler: ProxyHandler<Module> = {
     ...Object.fromEntries(Object.getOwnPropertyNames(Reflect).map(propName =>
-        [propName, (target: any, ...args: any[]) => Reflect[propName](target, ...args)]
+        [propName, (target: Module, ...args: any[]) => Reflect[propName](target, ...args)]
     )),
-    get: (target, p: string) => {
+    get: (target: Module, p: string) => {
         const mod = Reflect.get(target, p);
         ...
     },
-    set: (target, p, newValue) => Reflect.set(target, p, newValue),
-    ownKeys: target => {
+    set: (target: Module, p: string, newValue: any) => Reflect.set(target, p, newValue),
+    ownKeys: (target: Module) => {
         const keys = Reflect.ownKeys(target);
         ...
     }
 };
 
Suggestion importance[1-10]: 7

Why: The suggestion improves type safety and maintainability by replacing any with a more specific type. However, the suggested Module interface still uses any for its properties, which only partially addresses the issue.

7
Best practice
Use an enum for unconfigurable properties to enhance type safety and maintainability

Consider using a TypeScript enum for UNCONFIGURABLE_PROPERTIES instead of an array for
better type safety and easier refactoring in future.

src/utils/misc.tsx [104]

-export const UNCONFIGURABLE_PROPERTIES = ["arguments", "caller", "prototype"];
+export enum UnconfigurableProperties {
+    Arguments = "arguments",
+    Caller = "caller",
+    Prototype = "prototype"
+}
 
Suggestion importance[1-10]: 6

Why: Using a TypeScript enum for UNCONFIGURABLE_PROPERTIES can enhance type safety and make future refactoring easier. This is a best practice suggestion that improves code maintainability but is not critical.

6

src/webpack/patchWebpack.ts Fixed Show fixed Hide fixed
src/webpack/patchWebpack.ts Fixed Show fixed Hide fixed
src/webpack/patchWebpack.ts Dismissed Show dismissed Hide dismissed
src/webpack/patchWebpack.ts Dismissed Show dismissed Hide dismissed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants