mirror of
https://github.com/Vendicated/Vencord.git
synced 2025-05-11 01:45:48 +02:00
WebpackPatcher: Use way less closures (#3217)
This commit is contained in:
parent
5d482ff3bf
commit
306890aa13
10 changed files with 236 additions and 244 deletions
|
@ -134,7 +134,7 @@ export default tseslint.config(
|
|||
"no-unsafe-optional-chaining": "error",
|
||||
"no-useless-backreference": "error",
|
||||
"use-isnan": "error",
|
||||
"prefer-const": "error",
|
||||
"prefer-const": ["error", { destructuring: "all" }],
|
||||
"prefer-spread": "error",
|
||||
|
||||
// Plugin Rules
|
||||
|
|
|
@ -16,11 +16,7 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
/* eslint-disable no-fallthrough */
|
||||
|
||||
// eslint-disable-next-line spaced-comment
|
||||
/// <reference types="../src/globals" />
|
||||
// eslint-disable-next-line spaced-comment
|
||||
/// <reference types="../src/modules" />
|
||||
|
||||
import { createHmac } from "crypto";
|
||||
|
|
|
@ -8,7 +8,7 @@ import { Logger } from "@utils/Logger";
|
|||
import { canonicalizeMatch } from "@utils/patches";
|
||||
import * as Webpack from "@webpack";
|
||||
import { wreq } from "@webpack";
|
||||
import { AnyModuleFactory, ModuleFactory } from "webpack";
|
||||
import { AnyModuleFactory, ModuleFactory } from "@webpack/wreq.d";
|
||||
|
||||
export async function loadLazyChunks() {
|
||||
const LazyChunkLoaderLogger = new Logger("LazyChunkLoader");
|
||||
|
@ -140,8 +140,8 @@ export async function loadLazyChunks() {
|
|||
}
|
||||
|
||||
Webpack.factoryListeners.add(factoryListener);
|
||||
for (const factoryId in wreq.m) {
|
||||
factoryListener(wreq.m[factoryId]);
|
||||
for (const moduleId in wreq.m) {
|
||||
factoryListener(wreq.m[moduleId]);
|
||||
}
|
||||
|
||||
await chunksSearchingDone;
|
||||
|
|
|
@ -6,9 +6,9 @@
|
|||
|
||||
import { Logger } from "@utils/Logger";
|
||||
import * as Webpack from "@webpack";
|
||||
import { addPatch, patches } from "plugins";
|
||||
import { getBuildNumber } from "webpack/patchWebpack";
|
||||
import { getBuildNumber, patchTimings } from "@webpack/patcher";
|
||||
|
||||
import { addPatch, patches } from "../plugins";
|
||||
import { loadLazyChunks } from "./loadLazyChunks";
|
||||
|
||||
async function runReporter() {
|
||||
|
@ -51,7 +51,7 @@ async function runReporter() {
|
|||
}
|
||||
}
|
||||
|
||||
for (const [plugin, moduleId, match, totalTime] of Vencord.WebpackPatcher.patchTimings) {
|
||||
for (const [plugin, moduleId, match, totalTime] of patchTimings) {
|
||||
if (totalTime > 5) {
|
||||
new Logger("WebpackInterceptor").warn(`Patch by ${plugin} took ${Math.round(totalTime * 100) / 100}ms (Module id is ${String(moduleId)}): ${match}`);
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@ import { definePluginSettings } from "@api/Settings";
|
|||
import { Devs } from "@utils/constants";
|
||||
import { Logger } from "@utils/Logger";
|
||||
import definePlugin, { OptionType, StartAt } from "@utils/types";
|
||||
import { WebpackRequire } from "webpack";
|
||||
import { WebpackRequire } from "@webpack/wreq.d";
|
||||
|
||||
const settings = definePluginSettings({
|
||||
disableAnalytics: {
|
||||
|
|
|
@ -31,6 +31,7 @@ import { Logger } from "@utils/Logger";
|
|||
import { canonicalizeFind, canonicalizeReplacement } from "@utils/patches";
|
||||
import { Patch, Plugin, PluginDef, ReporterTestable, StartAt } from "@utils/types";
|
||||
import { FluxDispatcher } from "@webpack/common";
|
||||
import { patches } from "@webpack/patcher";
|
||||
import { FluxEvents } from "@webpack/types";
|
||||
|
||||
import Plugins from "~plugins";
|
||||
|
@ -41,7 +42,7 @@ const logger = new Logger("PluginManager", "#a6d189");
|
|||
|
||||
export const PMLogger = logger;
|
||||
export const plugins = Plugins;
|
||||
export const patches = [] as Patch[];
|
||||
export { patches };
|
||||
|
||||
/** Whether we have subscribed to flux events of all the enabled plugins when FluxDispatcher was ready */
|
||||
let enabledPluginsSubscribedFlux = false;
|
||||
|
|
|
@ -9,32 +9,23 @@ import { makeLazy } from "@utils/lazy";
|
|||
import { Logger } from "@utils/Logger";
|
||||
import { interpolateIfDefined } from "@utils/misc";
|
||||
import { canonicalizeReplacement } from "@utils/patches";
|
||||
import { PatchReplacement } from "@utils/types";
|
||||
import { Patch, PatchReplacement } from "@utils/types";
|
||||
|
||||
import { traceFunctionWithResults } from "../debug/Tracer";
|
||||
import { patches } from "../plugins";
|
||||
import { _initWebpack, _shouldIgnoreModule, AnyModuleFactory, AnyWebpackRequire, factoryListeners, findModuleId, MaybeWrappedModuleFactory, ModuleExports, moduleListeners, waitForSubscriptions, WebpackRequire, WrappedModuleFactory, wreq } from ".";
|
||||
import { _initWebpack, _shouldIgnoreModule, factoryListeners, findModuleId, moduleListeners, waitForSubscriptions, wreq } from "./webpack";
|
||||
import { AnyModuleFactory, AnyWebpackRequire, MaybePatchedModuleFactory, ModuleExports, PatchedModuleFactory, WebpackRequire } from "./wreq.d";
|
||||
|
||||
export const patches = [] as Patch[];
|
||||
|
||||
export const SYM_ORIGINAL_FACTORY = Symbol("WebpackPatcher.originalFactory");
|
||||
export const SYM_PATCHED_SOURCE = Symbol("WebpackPatcher.patchedSource");
|
||||
export const SYM_PATCHED_BY = Symbol("WebpackPatcher.patchedBy");
|
||||
/** A set with all the Webpack instances */
|
||||
export const allWebpackInstances = new Set<AnyWebpackRequire>();
|
||||
export const patchTimings = [] as Array<[plugin: string, moduleId: PropertyKey, match: string | RegExp, totalTime: number]>;
|
||||
|
||||
const logger = new Logger("WebpackInterceptor", "#8caaee");
|
||||
/** Whether we tried to fallback to factory WebpackRequire, or disabled patches */
|
||||
let wreqFallbackApplied = false;
|
||||
/** Whether we should be patching factories.
|
||||
*
|
||||
* This should be disabled if we start searching for the module to get the build number, and then resumed once it's done.
|
||||
* */
|
||||
let shouldPatchFactories = true;
|
||||
export const patchTimings = [] as Array<[plugin: string, moduleId: PropertyKey, match: PatchReplacement["match"], totalTime: number]>;
|
||||
|
||||
export const getBuildNumber = makeLazy(() => {
|
||||
try {
|
||||
shouldPatchFactories = false;
|
||||
|
||||
try {
|
||||
if (wreq.m[128014]?.toString().includes("Trying to open a changelog for an invalid build number")) {
|
||||
const hardcodedGetBuildNumber = wreq(128014).b as () => number;
|
||||
|
@ -59,13 +50,23 @@ export const getBuildNumber = makeLazy(() => {
|
|||
return typeof buildNumber === "number" ? buildNumber : -1;
|
||||
} catch {
|
||||
return -1;
|
||||
} finally {
|
||||
shouldPatchFactories = true;
|
||||
}
|
||||
});
|
||||
|
||||
type Define = typeof Reflect.defineProperty;
|
||||
const define: Define = (target, p, attributes) => {
|
||||
export function getFactoryPatchedSource(moduleId: PropertyKey, webpackRequire = wreq as AnyWebpackRequire) {
|
||||
return webpackRequire.m[moduleId]?.[SYM_PATCHED_SOURCE];
|
||||
}
|
||||
|
||||
export function getFactoryPatchedBy(moduleId: PropertyKey, webpackRequire = wreq as AnyWebpackRequire) {
|
||||
return webpackRequire.m[moduleId]?.[SYM_PATCHED_BY];
|
||||
}
|
||||
|
||||
const logger = new Logger("WebpackInterceptor", "#8caaee");
|
||||
|
||||
/** Whether we tried to fallback to the WebpackRequire of the factory, or disabled patches */
|
||||
let wreqFallbackApplied = false;
|
||||
|
||||
const define: typeof Reflect.defineProperty = (target, p, attributes) => {
|
||||
if (Object.hasOwn(attributes, "value")) {
|
||||
attributes.writable = true;
|
||||
}
|
||||
|
@ -77,22 +78,17 @@ const define: Define = (target, p, attributes) => {
|
|||
});
|
||||
};
|
||||
|
||||
export function getOriginalFactory(id: PropertyKey, webpackRequire = wreq as AnyWebpackRequire) {
|
||||
const moduleFactory = webpackRequire.m[id];
|
||||
return (moduleFactory?.[SYM_ORIGINAL_FACTORY] ?? moduleFactory) as AnyModuleFactory | undefined;
|
||||
}
|
||||
// wreq.m is the Webpack object containing module factories. It is pre-populated with factories, and is also populated via webpackGlobal.push
|
||||
// We use this setter to intercept when wreq.m is defined and apply patching to its factories.
|
||||
|
||||
export function getFactoryPatchedSource(id: PropertyKey, webpackRequire = wreq as AnyWebpackRequire) {
|
||||
return webpackRequire.m[id]?.[SYM_PATCHED_SOURCE];
|
||||
}
|
||||
// Factories can be patched in two ways. Eagerly or lazily.
|
||||
// If we are patching eagerly, pre-populated factories are patched immediately and new factories are patched when set.
|
||||
// Else, we only patch them when called.
|
||||
|
||||
export function getFactoryPatchedBy(id: PropertyKey, webpackRequire = wreq as AnyWebpackRequire) {
|
||||
return webpackRequire.m[id]?.[SYM_PATCHED_BY];
|
||||
}
|
||||
// Factories are always wrapped in a proxy, which allows us to intercept the call to them, patch if they werent eagerly patched,
|
||||
// and call them with our wrapper which notifies our listeners.
|
||||
|
||||
// wreq.m is the Webpack object containing module factories. It is pre-populated with module factories, and is also populated via webpackGlobal.push
|
||||
// We use this setter to intercept when wreq.m is defined and apply the patching in its module factories.
|
||||
// We wrap wreq.m with our proxy, which is responsible for patching the module factories when they are set, or defining getters for the patched versions.
|
||||
// wreq.m is also wrapped in a proxy to intercept when new factories are set, patch them eargely, if enabled, and wrap them in the factory proxy.
|
||||
|
||||
// If this is the main Webpack, we also set up the internal references to WebpackRequire.
|
||||
define(Function.prototype, "m", {
|
||||
|
@ -131,13 +127,17 @@ define(Function.prototype, "m", {
|
|||
const setterTimeout = setTimeout(() => Reflect.deleteProperty(this, "e"), 0);
|
||||
|
||||
// Patch the pre-populated factories
|
||||
for (const id in originalModules) {
|
||||
if (updateExistingFactory(originalModules, id, originalModules[id], true)) {
|
||||
for (const moduleId in originalModules) {
|
||||
const originalFactory = originalModules[moduleId];
|
||||
|
||||
if (updateExistingFactory(originalModules, moduleId, originalFactory, originalModules, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
notifyFactoryListeners(originalModules[id]);
|
||||
defineModulesFactoryGetter(id, Settings.eagerPatches && shouldPatchFactories ? wrapAndPatchFactory(id, originalModules[id]) : originalModules[id]);
|
||||
notifyFactoryListeners(moduleId, originalFactory);
|
||||
|
||||
const proxiedFactory = new Proxy(Settings.eagerPatches ? patchFactory(moduleId, originalFactory) : originalFactory, moduleFactoryHandler);
|
||||
define(originalModules, moduleId, { value: proxiedFactory });
|
||||
}
|
||||
|
||||
define(originalModules, Symbol.toStringTag, {
|
||||
|
@ -145,7 +145,6 @@ define(Function.prototype, "m", {
|
|||
enumerable: false
|
||||
});
|
||||
|
||||
// The proxy responsible for patching the module factories when they are set, or defining getters for the patched versions
|
||||
const proxiedModuleFactories = new Proxy(originalModules, moduleFactoriesHandler);
|
||||
/*
|
||||
If Webpack ever decides to set module factories using the variable of the modules object directly, instead of wreq.m, switch the proxy to the prototype
|
||||
|
@ -156,6 +155,7 @@ define(Function.prototype, "m", {
|
|||
}
|
||||
});
|
||||
|
||||
// The proxy for patching eagerly and/or wrapping factories in their proxy.
|
||||
const moduleFactoriesHandler: ProxyHandler<AnyWebpackRequire["m"]> = {
|
||||
/*
|
||||
If Webpack ever decides to set module factories using the variable of the modules object directly instead of wreq.m, we need to switch the proxy to the prototype
|
||||
|
@ -172,57 +172,96 @@ const moduleFactoriesHandler: ProxyHandler<AnyWebpackRequire["m"]> = {
|
|||
},
|
||||
*/
|
||||
|
||||
// The set trap for patching or defining getters for the module factories when new module factories are loaded
|
||||
set(target, p, newValue, receiver) {
|
||||
if (updateExistingFactory(target, p, newValue)) {
|
||||
if (updateExistingFactory(target, p, newValue, receiver)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
notifyFactoryListeners(newValue);
|
||||
defineModulesFactoryGetter(p, Settings.eagerPatches && shouldPatchFactories ? wrapAndPatchFactory(p, newValue) : newValue);
|
||||
notifyFactoryListeners(p, newValue);
|
||||
|
||||
return true;
|
||||
const proxiedFactory = new Proxy(Settings.eagerPatches ? patchFactory(p, newValue) : newValue, moduleFactoryHandler);
|
||||
return Reflect.set(target, p, proxiedFactory, receiver);
|
||||
}
|
||||
};
|
||||
|
||||
// The proxy for patching lazily and/or running factories with our wrapper.
|
||||
const moduleFactoryHandler: ProxyHandler<MaybePatchedModuleFactory> = {
|
||||
apply(target, thisArg: unknown, argArray: Parameters<AnyModuleFactory>) {
|
||||
// SAFETY: Factories have `name` as their key in the module factories object, and that is always their module id
|
||||
const moduleId = target.name;
|
||||
|
||||
// SYM_ORIGINAL_FACTORY means the factory has already been patched
|
||||
if (target[SYM_ORIGINAL_FACTORY] != null) {
|
||||
return runFactoryWithWrap(moduleId, target as PatchedModuleFactory, thisArg, argArray);
|
||||
}
|
||||
|
||||
const patchedFactory = patchFactory(moduleId, target);
|
||||
return runFactoryWithWrap(moduleId, patchedFactory, thisArg, argArray);
|
||||
},
|
||||
|
||||
get(target, p, receiver) {
|
||||
if (target[SYM_ORIGINAL_FACTORY] != null && (p === SYM_PATCHED_SOURCE || p === SYM_PATCHED_BY)) {
|
||||
return Reflect.get(target[SYM_ORIGINAL_FACTORY], p, target[SYM_ORIGINAL_FACTORY]);
|
||||
}
|
||||
|
||||
const v = Reflect.get(target, p, receiver);
|
||||
|
||||
// Make proxied factories `toString` return their original factory `toString`
|
||||
if (p === "toString") {
|
||||
return v.bind(target[SYM_ORIGINAL_FACTORY] ?? target);
|
||||
}
|
||||
|
||||
return v;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Update a factory that exists in any Webpack instance with a new original factory.
|
||||
*
|
||||
* @target The module factories where this new original factory is being set
|
||||
* @param id The id of the module
|
||||
* @param moduleFactoriesTarget The module factories where this new original factory is being set
|
||||
* @param moduleId The id of the module
|
||||
* @param newFactory The new original factory
|
||||
* @param receiver The receiver of the new factory
|
||||
* @param ignoreExistingInTarget Whether to ignore checking if the factory already exists in the moduleFactoriesTarget
|
||||
* @returns Whether the original factory was updated, or false if it doesn't exist in any Webpack instance
|
||||
*/
|
||||
function updateExistingFactory(moduleFactoriesTarget: AnyWebpackRequire["m"], id: PropertyKey, newFactory: AnyModuleFactory, ignoreExistingInTarget: boolean = false) {
|
||||
function updateExistingFactory(moduleFactoriesTarget: AnyWebpackRequire["m"], moduleId: PropertyKey, newFactory: AnyModuleFactory, receiver: any, ignoreExistingInTarget: boolean = false) {
|
||||
let existingFactory: TypedPropertyDescriptor<AnyModuleFactory> | undefined;
|
||||
let moduleFactoriesWithFactory: AnyWebpackRequire["m"] | undefined;
|
||||
for (const wreq of allWebpackInstances) {
|
||||
if (ignoreExistingInTarget && wreq.m === moduleFactoriesTarget) continue;
|
||||
if (ignoreExistingInTarget && wreq.m === moduleFactoriesTarget) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Object.hasOwn(wreq.m, id)) {
|
||||
existingFactory = Reflect.getOwnPropertyDescriptor(wreq.m, id);
|
||||
if (Object.hasOwn(wreq.m, moduleId)) {
|
||||
existingFactory = Reflect.getOwnPropertyDescriptor(wreq.m, moduleId);
|
||||
moduleFactoriesWithFactory = wreq.m;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (existingFactory != null) {
|
||||
// If existingFactory exists in any Webpack instance, it's either wrapped in defineModuleFactoryGetter, or it has already been required.
|
||||
// So define the descriptor of it on this current Webpack instance (if it doesn't exist already), call Reflect.set with the new original,
|
||||
// and let the correct logic apply (normal set, or defineModuleFactoryGetter setter)
|
||||
|
||||
// If existingFactory exists in any Webpack instance, it's either wrapped in our proxy, or it has already been required.
|
||||
// In the case it is wrapped in our proxy, we need the Webpack instance with this new original factory to also have our proxy.
|
||||
// So, define the descriptor of the existing factory on it.
|
||||
if (moduleFactoriesWithFactory !== moduleFactoriesTarget) {
|
||||
Reflect.defineProperty(moduleFactoriesTarget, id, existingFactory);
|
||||
Reflect.defineProperty(receiver, moduleId, existingFactory);
|
||||
}
|
||||
|
||||
// Persist patched source and patched by in the new original factory, if the patched one has already been required
|
||||
if (IS_DEV && existingFactory.value != null) {
|
||||
newFactory[SYM_PATCHED_SOURCE] = existingFactory.value[SYM_PATCHED_SOURCE];
|
||||
newFactory[SYM_PATCHED_BY] = existingFactory.value[SYM_PATCHED_BY];
|
||||
const existingFactoryValue = moduleFactoriesWithFactory![moduleId];
|
||||
|
||||
// Update with the new original factory, if it does have a current original factory
|
||||
if (existingFactoryValue[SYM_ORIGINAL_FACTORY] != null) {
|
||||
existingFactoryValue[SYM_ORIGINAL_FACTORY] = newFactory;
|
||||
}
|
||||
|
||||
return Reflect.set(moduleFactoriesTarget, id, newFactory, moduleFactoriesTarget);
|
||||
// Persist patched source and patched by in the new original factory
|
||||
if (IS_DEV) {
|
||||
newFactory[SYM_PATCHED_SOURCE] = existingFactoryValue[SYM_PATCHED_SOURCE];
|
||||
newFactory[SYM_PATCHED_BY] = existingFactoryValue[SYM_PATCHED_BY];
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -231,12 +270,13 @@ function updateExistingFactory(moduleFactoriesTarget: AnyWebpackRequire["m"], id
|
|||
/**
|
||||
* Notify all factory listeners.
|
||||
*
|
||||
* @param moduleId The id of the module
|
||||
* @param factory The original factory to notify for
|
||||
*/
|
||||
function notifyFactoryListeners(factory: AnyModuleFactory) {
|
||||
function notifyFactoryListeners(moduleId: PropertyKey, factory: AnyModuleFactory) {
|
||||
for (const factoryListener of factoryListeners) {
|
||||
try {
|
||||
factoryListener(factory);
|
||||
factoryListener(factory, moduleId);
|
||||
} catch (err) {
|
||||
logger.error("Error in Webpack factory listener:\n", err, factoryListener);
|
||||
}
|
||||
|
@ -244,64 +284,27 @@ function notifyFactoryListeners(factory: AnyModuleFactory) {
|
|||
}
|
||||
|
||||
/**
|
||||
* Define the getter for returning the patched version of the module factory.
|
||||
* Run a (possibly) patched module factory with a wrapper which notifies our listeners.
|
||||
*
|
||||
* If eagerPatches is enabled, the factory argument should already be the patched version, else it will be the original
|
||||
* and only be patched when accessed for the first time.
|
||||
*
|
||||
* @param id The id of the module
|
||||
* @param factory The original or patched module factory
|
||||
* @param moduleId The id of the module
|
||||
* @param patchedFactory The (possibly) patched module factory
|
||||
* @param thisArg The `value` of the call to the factory
|
||||
* @param argArray The arguments of the call to the factory
|
||||
*/
|
||||
function defineModulesFactoryGetter(id: PropertyKey, factory: MaybeWrappedModuleFactory) {
|
||||
const descriptor: PropertyDescriptor = {
|
||||
get() {
|
||||
// SYM_ORIGINAL_FACTORY means the factory is already patched
|
||||
if (!shouldPatchFactories || factory[SYM_ORIGINAL_FACTORY] != null) {
|
||||
return factory;
|
||||
function runFactoryWithWrap(moduleId: PropertyKey, patchedFactory: PatchedModuleFactory, thisArg: unknown, argArray: Parameters<MaybePatchedModuleFactory>) {
|
||||
const originalFactory = patchedFactory[SYM_ORIGINAL_FACTORY];
|
||||
|
||||
if (patchedFactory === originalFactory) {
|
||||
// @ts-expect-error Clear up ORIGINAL_FACTORY if the factory did not have any patch applied
|
||||
delete patchedFactory[SYM_ORIGINAL_FACTORY];
|
||||
}
|
||||
|
||||
return (factory = wrapAndPatchFactory(id, factory));
|
||||
},
|
||||
set(newFactory: MaybeWrappedModuleFactory) {
|
||||
if (IS_DEV) {
|
||||
newFactory[SYM_PATCHED_SOURCE] = factory[SYM_PATCHED_SOURCE];
|
||||
newFactory[SYM_PATCHED_BY] = factory[SYM_PATCHED_BY];
|
||||
}
|
||||
|
||||
if (factory[SYM_ORIGINAL_FACTORY] != null) {
|
||||
factory.toString = newFactory.toString.bind(newFactory);
|
||||
factory[SYM_ORIGINAL_FACTORY] = newFactory;
|
||||
} else {
|
||||
factory = newFactory;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Define the getter in all the module factories objects. Patches are only executed once, so make sure all module factories object
|
||||
// have the patched version
|
||||
// Restore the original factory in all the module factories objects, discarding our proxy and allowing it to be garbage collected
|
||||
for (const wreq of allWebpackInstances) {
|
||||
define(wreq.m, id, descriptor);
|
||||
}
|
||||
define(wreq.m, moduleId, { value: originalFactory });
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps and patches a module factory.
|
||||
*
|
||||
* @param id The id of the module
|
||||
* @param factory The original or patched module factory
|
||||
* @returns The wrapper for the patched module factory
|
||||
*/
|
||||
function wrapAndPatchFactory(id: PropertyKey, originalFactory: AnyModuleFactory) {
|
||||
const [patchedFactory, patchedSource, patchedBy] = patchFactory(id, originalFactory);
|
||||
|
||||
const wrappedFactory: WrappedModuleFactory = function (...args) {
|
||||
// Restore the original factory in all the module factories objects. We want to make sure the original factory is restored properly, no matter what is the Webpack instance
|
||||
for (const wreq of allWebpackInstances) {
|
||||
define(wreq.m, id, { value: wrappedFactory[SYM_ORIGINAL_FACTORY] });
|
||||
}
|
||||
|
||||
// eslint-disable-next-line prefer-const
|
||||
let [module, exports, require] = args;
|
||||
let [module, exports, require] = argArray;
|
||||
|
||||
if (wreq == null) {
|
||||
if (!wreqFallbackApplied) {
|
||||
|
@ -313,33 +316,33 @@ function wrapAndPatchFactory(id: PropertyKey, originalFactory: AnyModuleFactory)
|
|||
const webpackInstanceFileName = stack?.match(/\/assets\/(.+?\.js)/)?.[1];
|
||||
|
||||
logger.warn(
|
||||
"WebpackRequire was not initialized, falling back to WebpackRequire passed to the first called patched module factory (" +
|
||||
`id: ${String(id)}` + interpolateIfDefined`, WebpackInstance origin: ${webpackInstanceFileName}` +
|
||||
"WebpackRequire was not initialized, falling back to WebpackRequire passed to the first called wrapped module factory (" +
|
||||
`id: ${String(moduleId)}` + interpolateIfDefined`, WebpackInstance origin: ${webpackInstanceFileName}` +
|
||||
")"
|
||||
);
|
||||
|
||||
// Could technically be wrong, but it's better than nothing
|
||||
_initWebpack(require as WebpackRequire);
|
||||
} else if (IS_DEV) {
|
||||
logger.error("WebpackRequire was not initialized, running modules without patches instead.");
|
||||
return wrappedFactory[SYM_ORIGINAL_FACTORY].apply(this, args);
|
||||
return originalFactory.apply(thisArg, argArray);
|
||||
}
|
||||
} else if (IS_DEV) {
|
||||
return wrappedFactory[SYM_ORIGINAL_FACTORY].apply(this, args);
|
||||
return originalFactory.apply(thisArg, argArray);
|
||||
}
|
||||
}
|
||||
|
||||
let factoryReturn: unknown;
|
||||
try {
|
||||
// Call the patched factory
|
||||
factoryReturn = patchedFactory.apply(this, args);
|
||||
factoryReturn = patchedFactory.apply(thisArg, argArray);
|
||||
} catch (err) {
|
||||
// Just re-throw Discord errors
|
||||
if (patchedFactory === wrappedFactory[SYM_ORIGINAL_FACTORY]) {
|
||||
if (patchedFactory === originalFactory) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
logger.error("Error in patched module factory:\n", err);
|
||||
return wrappedFactory[SYM_ORIGINAL_FACTORY].apply(this, args);
|
||||
return originalFactory.apply(thisArg, argArray);
|
||||
}
|
||||
|
||||
exports = module.exports;
|
||||
|
@ -352,8 +355,8 @@ function wrapAndPatchFactory(id: PropertyKey, originalFactory: AnyModuleFactory)
|
|||
|
||||
if (shouldIgnoreModule) {
|
||||
if (require.c != null) {
|
||||
Object.defineProperty(require.c, id, {
|
||||
value: require.c[id],
|
||||
Object.defineProperty(require.c, moduleId, {
|
||||
value: require.c[moduleId],
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
writable: true
|
||||
|
@ -366,7 +369,7 @@ function wrapAndPatchFactory(id: PropertyKey, originalFactory: AnyModuleFactory)
|
|||
|
||||
for (const callback of moduleListeners) {
|
||||
try {
|
||||
callback(exports, id);
|
||||
callback(exports, moduleId);
|
||||
} catch (err) {
|
||||
logger.error("Error in Webpack module listener:\n", err, callback);
|
||||
}
|
||||
|
@ -376,7 +379,7 @@ function wrapAndPatchFactory(id: PropertyKey, originalFactory: AnyModuleFactory)
|
|||
try {
|
||||
if (filter(exports)) {
|
||||
waitForSubscriptions.delete(filter);
|
||||
callback(exports, id);
|
||||
callback(exports, moduleId);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -389,7 +392,7 @@ function wrapAndPatchFactory(id: PropertyKey, originalFactory: AnyModuleFactory)
|
|||
|
||||
if (exportValue != null && filter(exportValue)) {
|
||||
waitForSubscriptions.delete(filter);
|
||||
callback(exportValue, id);
|
||||
callback(exportValue, moduleId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -399,35 +402,20 @@ function wrapAndPatchFactory(id: PropertyKey, originalFactory: AnyModuleFactory)
|
|||
}
|
||||
|
||||
return factoryReturn;
|
||||
};
|
||||
|
||||
wrappedFactory.toString = originalFactory.toString.bind(originalFactory);
|
||||
wrappedFactory[SYM_ORIGINAL_FACTORY] = originalFactory;
|
||||
|
||||
if (IS_DEV && patchedFactory !== originalFactory) {
|
||||
wrappedFactory[SYM_PATCHED_SOURCE] = patchedSource;
|
||||
wrappedFactory[SYM_PATCHED_BY] = patchedBy;
|
||||
originalFactory[SYM_PATCHED_SOURCE] = patchedSource;
|
||||
originalFactory[SYM_PATCHED_BY] = patchedBy;
|
||||
}
|
||||
|
||||
// @ts-expect-error Allow GC to get into action, if possible
|
||||
originalFactory = undefined;
|
||||
return wrappedFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Patches a module factory.
|
||||
*
|
||||
* @param id The id of the module
|
||||
* @param factory The original module factory
|
||||
* @returns The patched module factory, the patched source of it, and the plugins that patched it
|
||||
* @param moduleId The id of the module
|
||||
* @param originalFactory The original module factory
|
||||
* @returns The patched module factory
|
||||
*/
|
||||
function patchFactory(id: PropertyKey, factory: AnyModuleFactory): [patchedFactory: AnyModuleFactory, patchedSource: string, patchedBy: Set<string>] {
|
||||
function patchFactory(moduleId: PropertyKey, originalFactory: AnyModuleFactory): PatchedModuleFactory {
|
||||
// 0, prefix to turn it into an expression: 0,function(){} would be invalid syntax without the 0,
|
||||
let code: string = "0," + String(factory);
|
||||
let code: string = "0," + String(originalFactory);
|
||||
let patchedSource = code;
|
||||
let patchedFactory = factory;
|
||||
let patchedFactory = originalFactory;
|
||||
|
||||
const patchedBy = new Set<string>();
|
||||
|
||||
|
@ -442,8 +430,8 @@ function patchFactory(id: PropertyKey, factory: AnyModuleFactory): [patchedFacto
|
|||
continue;
|
||||
}
|
||||
|
||||
// Reporter eagerly patches and cannot retrieve the build number because this code runs before the module for it is loaded
|
||||
const buildNumber = IS_REPORTER ? -1 : getBuildNumber();
|
||||
// Eager patches cannot retrieve the build number because this code runs before the module for it is loaded
|
||||
const buildNumber = Settings.eagerPatches ? -1 : getBuildNumber();
|
||||
const shouldCheckBuildNumber = !Settings.eagerPatches && buildNumber !== -1;
|
||||
|
||||
if (
|
||||
|
@ -463,7 +451,7 @@ function patchFactory(id: PropertyKey, factory: AnyModuleFactory): [patchedFacto
|
|||
});
|
||||
|
||||
const previousCode = code;
|
||||
const previousFactory = factory;
|
||||
const previousFactory = originalFactory;
|
||||
let markedAsPatched = false;
|
||||
|
||||
// We change all patch.replacement to array in plugins/index
|
||||
|
@ -482,18 +470,18 @@ function patchFactory(id: PropertyKey, factory: AnyModuleFactory): [patchedFacto
|
|||
}
|
||||
|
||||
const lastCode = code;
|
||||
const lastFactory = factory;
|
||||
const lastFactory = originalFactory;
|
||||
|
||||
try {
|
||||
const [newCode, totalTime] = executePatch(replacement.match, replacement.replace as string);
|
||||
|
||||
if (IS_REPORTER) {
|
||||
patchTimings.push([patch.plugin, id, replacement.match, totalTime]);
|
||||
patchTimings.push([patch.plugin, moduleId, replacement.match, totalTime]);
|
||||
}
|
||||
|
||||
if (newCode === code) {
|
||||
if (!patch.noWarn) {
|
||||
logger.warn(`Patch by ${patch.plugin} had no effect (Module id is ${String(id)}): ${replacement.match}`);
|
||||
logger.warn(`Patch by ${patch.plugin} had no effect (Module id is ${String(moduleId)}): ${replacement.match}`);
|
||||
if (IS_DEV) {
|
||||
logger.debug("Function Source:\n", code);
|
||||
}
|
||||
|
@ -515,7 +503,7 @@ function patchFactory(id: PropertyKey, factory: AnyModuleFactory): [patchedFacto
|
|||
}
|
||||
|
||||
code = newCode;
|
||||
patchedSource = `// Webpack Module ${String(id)} - Patched by ${[...patchedBy, patch.plugin].join(", ")}\n${newCode}\n//# sourceURL=WebpackModule${String(id)}`;
|
||||
patchedSource = `// Webpack Module ${String(moduleId)} - Patched by ${[...patchedBy, patch.plugin].join(", ")}\n${newCode}\n//# sourceURL=WebpackModule${String(moduleId)}`;
|
||||
patchedFactory = (0, eval)(patchedSource);
|
||||
|
||||
if (!patchedBy.has(patch.plugin)) {
|
||||
|
@ -523,7 +511,7 @@ function patchFactory(id: PropertyKey, factory: AnyModuleFactory): [patchedFacto
|
|||
markedAsPatched = true;
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error(`Patch by ${patch.plugin} errored (Module id is ${String(id)}): ${replacement.match}\n`, err);
|
||||
logger.error(`Patch by ${patch.plugin} errored (Module id is ${String(moduleId)}): ${replacement.match}\n`, err);
|
||||
|
||||
if (IS_DEV) {
|
||||
diffErroredPatch(code, lastCode, lastCode.match(replacement.match)!);
|
||||
|
@ -550,7 +538,14 @@ function patchFactory(id: PropertyKey, factory: AnyModuleFactory): [patchedFacto
|
|||
}
|
||||
}
|
||||
|
||||
return [patchedFactory, patchedSource, patchedBy];
|
||||
patchedFactory[SYM_ORIGINAL_FACTORY] = originalFactory;
|
||||
|
||||
if (IS_DEV && patchedFactory !== originalFactory) {
|
||||
originalFactory[SYM_PATCHED_SOURCE] = patchedSource;
|
||||
originalFactory[SYM_PATCHED_BY] = patchedBy;
|
||||
}
|
||||
|
||||
return patchedFactory as PatchedModuleFactory;
|
||||
}
|
||||
|
||||
function diffErroredPatch(code: string, lastCode: string, match: RegExpMatchArray) {
|
||||
|
|
|
@ -90,7 +90,7 @@ export const filters = {
|
|||
};
|
||||
|
||||
export type CallbackFn = (module: ModuleExports, id: PropertyKey) => void;
|
||||
export type FactoryListernFn = (factory: AnyModuleFactory) => void;
|
||||
export type FactoryListernFn = (factory: AnyModuleFactory, moduleId: PropertyKey) => void;
|
||||
|
||||
export const waitForSubscriptions = new Map<FilterFn, CallbackFn>();
|
||||
export const moduleListeners = new Set<CallbackFn>();
|
||||
|
|
6
src/webpack/wreq.d.ts
vendored
6
src/webpack/wreq.d.ts
vendored
|
@ -200,12 +200,10 @@ export type AnyModuleFactory = ((this: ModuleExports, module: Module, exports: M
|
|||
[SYM_PATCHED_BY]?: Set<string>;
|
||||
};
|
||||
|
||||
export type WrappedModuleFactory = AnyModuleFactory & {
|
||||
export type PatchedModuleFactory = AnyModuleFactory & {
|
||||
[SYM_ORIGINAL_FACTORY]: AnyModuleFactory;
|
||||
[SYM_PATCHED_SOURCE]?: string;
|
||||
[SYM_PATCHED_BY]?: Set<string>;
|
||||
};
|
||||
|
||||
export type MaybeWrappedModuleFactory = AnyModuleFactory | WrappedModuleFactory;
|
||||
|
||||
export type WrappedModuleFactories = Record<PropertyKey, WrappedModuleFactory>;
|
||||
export type MaybePatchedModuleFactory = PatchedModuleFactory | AnyModuleFactory;
|
||||
|
|
|
@ -29,7 +29,9 @@
|
|||
"@shared/*": ["./shared/*"],
|
||||
"@webpack/types": ["./webpack/common/types"],
|
||||
"@webpack/common": ["./webpack/common"],
|
||||
"@webpack": ["./webpack/webpack"]
|
||||
"@webpack": ["./webpack/webpack"],
|
||||
"@webpack/patcher": ["./webpack/patchWebpack"],
|
||||
"@webpack/wreq.d": ["./webpack/wreq.d"],
|
||||
},
|
||||
|
||||
"plugins": [
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue