WebpackPatcher: Avoid patching wrong instances (like Stripe)
This commit is contained in:
parent
94d45780f9
commit
5ec564558e
4 changed files with 152 additions and 101 deletions
|
@ -20,8 +20,7 @@ export async function loadLazyChunks() {
|
|||
const invalidChunks = new Set<PropertyKey>();
|
||||
const deferredRequires = new Set<PropertyKey>();
|
||||
|
||||
let chunksSearchingResolve: (value: void) => void;
|
||||
const chunksSearchingDone = new Promise<void>(r => chunksSearchingResolve = r);
|
||||
const { promise: chunksSearchingDone, resolve: chunksSearchingResolve } = Promise.withResolvers<void>();
|
||||
|
||||
// True if resolved, false otherwise
|
||||
const chunksSearchPromises = [] as Array<() => boolean>;
|
||||
|
|
|
@ -17,8 +17,7 @@ async function runReporter() {
|
|||
try {
|
||||
ReporterLogger.log("Starting test...");
|
||||
|
||||
let loadLazyChunksResolve: (value: void) => void;
|
||||
const loadLazyChunksDone = new Promise<void>(r => loadLazyChunksResolve = r);
|
||||
const { promise: loadLazyChunksDone, resolve: loadLazyChunksResolve } = Promise.withResolvers<void>();
|
||||
|
||||
// The main patch for starting the reporter chunk loading
|
||||
addPatch({
|
||||
|
|
|
@ -17,6 +17,7 @@ import { AnyModuleFactory, AnyWebpackRequire, MaybePatchedModuleFactory, ModuleE
|
|||
|
||||
export const patches = [] as Patch[];
|
||||
|
||||
export const SYM_IS_PROXIED_FACTORY = Symbol("WebpackPatcher.isProxiedFactory");
|
||||
export const SYM_ORIGINAL_FACTORY = Symbol("WebpackPatcher.originalFactory");
|
||||
export const SYM_PATCHED_SOURCE = Symbol("WebpackPatcher.patchedSource");
|
||||
export const SYM_PATCHED_BY = Symbol("WebpackPatcher.patchedBy");
|
||||
|
@ -79,7 +80,8 @@ const define: typeof Reflect.defineProperty = (target, p, attributes) => {
|
|||
};
|
||||
|
||||
// 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.
|
||||
// We use this setter to intercept when wreq.m is defined and setup our setters which decide whether we should patch these module factories
|
||||
// and the Webpack instance where they are being defined.
|
||||
|
||||
// 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.
|
||||
|
@ -97,7 +99,7 @@ define(Function.prototype, "m", {
|
|||
set(this: AnyWebpackRequire, originalModules: AnyWebpackRequire["m"]) {
|
||||
define(this, "m", { value: originalModules });
|
||||
|
||||
// Ensure this is one of Discord main Webpack instances.
|
||||
// Ensure this is likely one of Discord main Webpack instances.
|
||||
// We may catch Discord bundled libs, React Devtools or other extensions Webpack instances here.
|
||||
const { stack } = new Error();
|
||||
if (!stack?.includes("http") || stack.match(/at \d+? \(/) || !String(this).includes("exports:{}")) {
|
||||
|
@ -105,53 +107,90 @@ define(Function.prototype, "m", {
|
|||
}
|
||||
|
||||
const fileName = stack.match(/\/assets\/(.+?\.js)/)?.[1];
|
||||
logger.info("Found Webpack module factories" + interpolateIfDefined` in ${fileName}`);
|
||||
|
||||
allWebpackInstances.add(this);
|
||||
|
||||
// Define a setter for the ensureChunk property of WebpackRequire. Only the main Webpack (which is the only that includes chunk loading) has this property.
|
||||
// So if the setter is called, this means we can initialize the internal references to WebpackRequire.
|
||||
define(this, "e", {
|
||||
// Define a setter for the bundlePath property of WebpackRequire. Only Webpack instances which include chunk loading functionality,
|
||||
// like the main Discord Webpack, have this property.
|
||||
// So if the setter is called with the Discord bundlePath, this means we should patch this instance and initialize the internal references to WebpackRequire.
|
||||
define(this, "p", {
|
||||
enumerable: false,
|
||||
|
||||
set(this: WebpackRequire, ensureChunk: WebpackRequire["e"]) {
|
||||
define(this, "e", { value: ensureChunk });
|
||||
clearTimeout(setterTimeout);
|
||||
set(this: AnyWebpackRequire, bundlePath: NonNullable<AnyWebpackRequire["p"]>) {
|
||||
define(this, "p", { value: bundlePath });
|
||||
clearTimeout(bundlePathTimeout);
|
||||
|
||||
logger.info("Main WebpackInstance found" + interpolateIfDefined` in ${fileName}` + ", initializing internal references to WebpackRequire");
|
||||
_initWebpack(this);
|
||||
if (bundlePath !== "/assets/") {
|
||||
return;
|
||||
}
|
||||
|
||||
patchThisInstance();
|
||||
|
||||
if (wreq == null && this.c != null) {
|
||||
logger.info("Main WebpackInstance found" + interpolateIfDefined` in ${fileName}` + ", initializing internal references to WebpackRequire");
|
||||
_initWebpack(this as WebpackRequire);
|
||||
}
|
||||
}
|
||||
});
|
||||
// setImmediate to clear this property setter if this is not the main Webpack.
|
||||
// If this is the main Webpack, wreq.e will always be set before the timeout runs.
|
||||
const setterTimeout = setTimeout(() => Reflect.deleteProperty(this, "e"), 0);
|
||||
|
||||
// Patch the pre-populated factories
|
||||
for (const moduleId in originalModules) {
|
||||
const originalFactory = originalModules[moduleId];
|
||||
// In the past, the sentry Webpack instance which we also wanted to patch used to rely on chunks being loaded before initting sentry.
|
||||
// This Webpack instance did not include actual chunk loading, and only awaited for them to be loaded, which means it did not include the bundlePath property.
|
||||
// To keep backwards compability, in case this is ever the case again, and keep patching this type of instance, we explicity patch instances which include wreq.O and not wreq.p.
|
||||
// Since we cannot check what is the bundlePath of the instance to filter for the Discord bundlePath, we only patch it if wreq.p is not included,
|
||||
// which means the instance relies on another instance which does chunk loading, and that makes it very likely to only target Discord Webpack instances like the old sentry.
|
||||
|
||||
if (updateExistingFactory(originalModules, moduleId, originalFactory, originalModules, true)) {
|
||||
continue;
|
||||
// Instead of patching when wreq.O is defined, wait for when wreq.O.j is defined, since that will be one of the last things to happen,
|
||||
// which can assure wreq.p could have already been defined before.
|
||||
define(this, "O", {
|
||||
enumerable: false,
|
||||
|
||||
set(this: AnyWebpackRequire, onChunksLoaded: NonNullable<AnyWebpackRequire["O"]>) {
|
||||
define(this, "O", { value: onChunksLoaded });
|
||||
clearTimeout(onChunksLoadedTimeout);
|
||||
|
||||
const wreq = this;
|
||||
define(onChunksLoaded, "j", {
|
||||
enumerable: false,
|
||||
|
||||
set(this: NonNullable<AnyWebpackRequire["O"]>, j: NonNullable<AnyWebpackRequire["O"]>["j"]) {
|
||||
define(this, "j", { value: j });
|
||||
|
||||
if (wreq.p == null) {
|
||||
patchThisInstance();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
notifyFactoryListeners(moduleId, originalFactory);
|
||||
|
||||
const proxiedFactory = new Proxy(Settings.eagerPatches ? patchFactory(moduleId, originalFactory) : originalFactory, moduleFactoryHandler);
|
||||
define(originalModules, moduleId, { value: proxiedFactory });
|
||||
}
|
||||
|
||||
define(originalModules, Symbol.toStringTag, {
|
||||
value: "ModuleFactories",
|
||||
enumerable: false
|
||||
});
|
||||
|
||||
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
|
||||
Reflect.setPrototypeOf(originalModules, new Proxy(originalModules, moduleFactoriesHandler));
|
||||
*/
|
||||
// If neither of these properties setters were triggered, delete them as they are not needed anymore.
|
||||
const bundlePathTimeout = setTimeout(() => Reflect.deleteProperty(this, "p"), 0);
|
||||
const onChunksLoadedTimeout = setTimeout(() => Reflect.deleteProperty(this, "O"), 0);
|
||||
|
||||
define(this, "m", { value: proxiedModuleFactories });
|
||||
/**
|
||||
* Patch the current Webpack instance assigned to `this` context.
|
||||
* This should only be called if this instance was later found to be one we need to patch.
|
||||
*/
|
||||
const patchThisInstance = () => {
|
||||
logger.info("Found Webpack module factories" + interpolateIfDefined` in ${fileName}`);
|
||||
allWebpackInstances.add(this);
|
||||
|
||||
// Proxy (and maybe patch) pre-populated factories
|
||||
for (const moduleId in originalModules) {
|
||||
updateExistingOrProxyFactory(originalModules, moduleId, originalModules[moduleId], originalModules, true);
|
||||
}
|
||||
|
||||
define(originalModules, Symbol.toStringTag, {
|
||||
value: "ModuleFactories",
|
||||
enumerable: false
|
||||
});
|
||||
|
||||
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
|
||||
Reflect.setPrototypeOf(originalModules, new Proxy(originalModules, moduleFactoriesHandler));
|
||||
*/
|
||||
|
||||
define(this, "m", { value: proxiedModuleFactories });
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
|
@ -172,93 +211,102 @@ const moduleFactoriesHandler: ProxyHandler<AnyWebpackRequire["m"]> = {
|
|||
},
|
||||
*/
|
||||
|
||||
set(target, p, newValue, receiver) {
|
||||
if (updateExistingFactory(target, p, newValue, receiver)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
notifyFactoryListeners(p, newValue);
|
||||
|
||||
const proxiedFactory = new Proxy(Settings.eagerPatches ? patchFactory(p, newValue) : newValue, moduleFactoryHandler);
|
||||
return Reflect.set(target, p, proxiedFactory, receiver);
|
||||
}
|
||||
set: updateExistingOrProxyFactory
|
||||
};
|
||||
|
||||
// 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);
|
||||
return runFactoryWithWrap(target as PatchedModuleFactory, thisArg, argArray);
|
||||
}
|
||||
|
||||
// SAFETY: Factories have `name` as their key in the module factories object, and that is always their module id
|
||||
const moduleId: string = target.name;
|
||||
|
||||
const patchedFactory = patchFactory(moduleId, target);
|
||||
return runFactoryWithWrap(moduleId, patchedFactory, thisArg, argArray);
|
||||
return runFactoryWithWrap(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]);
|
||||
if (p === SYM_IS_PROXIED_FACTORY) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const v = Reflect.get(target, p, receiver);
|
||||
const originalFactory: AnyModuleFactory = target[SYM_ORIGINAL_FACTORY] ?? target;
|
||||
|
||||
// Make proxied factories `toString` return their original factory `toString`
|
||||
if (p === "toString") {
|
||||
return v.bind(target[SYM_ORIGINAL_FACTORY] ?? target);
|
||||
// Redirect these properties to the original factory, including making `toString` return the original factory `toString`
|
||||
if (p === "toString" || p === SYM_PATCHED_SOURCE || p === SYM_PATCHED_BY) {
|
||||
const v = Reflect.get(originalFactory, p, originalFactory);
|
||||
return p === "toString" ? v.bind(originalFactory) : v;
|
||||
}
|
||||
|
||||
return v;
|
||||
return Reflect.get(target, p, receiver);
|
||||
}
|
||||
};
|
||||
|
||||
function updateExistingOrProxyFactory(moduleFactories: AnyWebpackRequire["m"], moduleId: PropertyKey, newFactory: AnyModuleFactory, receiver: any, ignoreExistingInTarget = false) {
|
||||
if (updateExistingFactory(moduleFactories, moduleId, newFactory, receiver, ignoreExistingInTarget)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
notifyFactoryListeners(moduleId, newFactory);
|
||||
|
||||
const proxiedFactory = new Proxy(Settings.eagerPatches ? patchFactory(moduleId, newFactory) : newFactory, moduleFactoryHandler);
|
||||
return Reflect.set(moduleFactories, moduleId, proxiedFactory, receiver);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a factory that exists in any Webpack instance with a new original factory.
|
||||
* Update a duplicated factory that exists in any of the Webpack instances we track with a new original factory.
|
||||
*
|
||||
* @param moduleFactoriesTarget The module factories where this new original factory is being set
|
||||
* @param moduleFactories 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
|
||||
* @param receiver The receiver of the factory
|
||||
* @param ignoreExistingInTarget Whether to ignore checking if the factory already exists in the moduleFactories where it is being set
|
||||
* @returns Whether the original factory was updated, or false if it doesn't exist in any of the tracked Webpack instances
|
||||
*/
|
||||
function updateExistingFactory(moduleFactoriesTarget: AnyWebpackRequire["m"], moduleId: PropertyKey, newFactory: AnyModuleFactory, receiver: any, ignoreExistingInTarget: boolean = false) {
|
||||
let existingFactory: TypedPropertyDescriptor<AnyModuleFactory> | undefined;
|
||||
function updateExistingFactory(moduleFactories: AnyWebpackRequire["m"], moduleId: PropertyKey, newFactory: AnyModuleFactory, receiver: any, ignoreExistingInTarget) {
|
||||
let existingFactory: AnyModuleFactory | undefined;
|
||||
let moduleFactoriesWithFactory: AnyWebpackRequire["m"] | undefined;
|
||||
for (const wreq of allWebpackInstances) {
|
||||
if (ignoreExistingInTarget && wreq.m === moduleFactoriesTarget) {
|
||||
if (ignoreExistingInTarget && wreq.m === moduleFactories) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (Object.hasOwn(wreq.m, moduleId)) {
|
||||
existingFactory = Reflect.getOwnPropertyDescriptor(wreq.m, moduleId);
|
||||
existingFactory = wreq.m[moduleId];
|
||||
moduleFactoriesWithFactory = wreq.m;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (existingFactory != null) {
|
||||
// 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(receiver, moduleId, existingFactory);
|
||||
// Sanity check to make sure these factories are equal
|
||||
if (String(newFactory) !== String(existingFactory)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const existingFactoryValue = moduleFactoriesWithFactory![moduleId];
|
||||
// If existingFactory exists in any of the Webpack instances we track, it's either wrapped in our proxy, or it has already been required.
|
||||
// In the case it is wrapped in our proxy, and the instance we are setting does not already have it, we need to make sure the instance contains our proxy too.
|
||||
if (moduleFactoriesWithFactory !== moduleFactories && existingFactory[SYM_IS_PROXIED_FACTORY]) {
|
||||
Reflect.set(moduleFactories, moduleId, existingFactory, receiver);
|
||||
}
|
||||
// Else, if it is not wrapped in our proxy, set this new original factory in all the instances
|
||||
else {
|
||||
defineInWebpackInstances(moduleId, newFactory);
|
||||
}
|
||||
|
||||
// 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;
|
||||
// Update existingFactory with the new original, if it does have a current original factory
|
||||
if (existingFactory[SYM_ORIGINAL_FACTORY] != null) {
|
||||
existingFactory[SYM_ORIGINAL_FACTORY] = newFactory;
|
||||
}
|
||||
|
||||
// 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];
|
||||
newFactory[SYM_PATCHED_SOURCE] = existingFactory[SYM_PATCHED_SOURCE];
|
||||
newFactory[SYM_PATCHED_BY] = existingFactory[SYM_PATCHED_BY];
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@ -267,6 +315,18 @@ function updateExistingFactory(moduleFactoriesTarget: AnyWebpackRequire["m"], mo
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a module factory in all the Webpack instances we track.
|
||||
*
|
||||
* @param moduleId The id of the module
|
||||
* @param factory The factory
|
||||
*/
|
||||
function defineInWebpackInstances(moduleId: PropertyKey, factory: AnyModuleFactory) {
|
||||
for (const wreq of allWebpackInstances) {
|
||||
define(wreq.m, moduleId, { value: factory });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify all factory listeners.
|
||||
*
|
||||
|
@ -286,12 +346,11 @@ function notifyFactoryListeners(moduleId: PropertyKey, factory: AnyModuleFactory
|
|||
/**
|
||||
* Run a (possibly) patched module factory with a wrapper which notifies our listeners.
|
||||
*
|
||||
* @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 runFactoryWithWrap(moduleId: PropertyKey, patchedFactory: PatchedModuleFactory, thisArg: unknown, argArray: Parameters<MaybePatchedModuleFactory>) {
|
||||
function runFactoryWithWrap(patchedFactory: PatchedModuleFactory, thisArg: unknown, argArray: Parameters<MaybePatchedModuleFactory>) {
|
||||
const originalFactory = patchedFactory[SYM_ORIGINAL_FACTORY];
|
||||
|
||||
if (patchedFactory === originalFactory) {
|
||||
|
@ -299,25 +358,23 @@ function runFactoryWithWrap(moduleId: PropertyKey, patchedFactory: PatchedModule
|
|||
delete patchedFactory[SYM_ORIGINAL_FACTORY];
|
||||
}
|
||||
|
||||
// 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, moduleId, { value: originalFactory });
|
||||
}
|
||||
|
||||
let [module, exports, require] = argArray;
|
||||
|
||||
// Restore the original factory in all the module factories objects, discarding our proxy and allowing it to be garbage collected
|
||||
defineInWebpackInstances(module.id, originalFactory);
|
||||
|
||||
if (wreq == null) {
|
||||
if (!wreqFallbackApplied) {
|
||||
wreqFallbackApplied = true;
|
||||
|
||||
// Make sure the require argument is actually the WebpackRequire function
|
||||
if (typeof require === "function" && require.m != null) {
|
||||
if (typeof require === "function" && require.m != null && require.c != null) {
|
||||
const { stack } = new Error();
|
||||
const webpackInstanceFileName = stack?.match(/\/assets\/(.+?\.js)/)?.[1];
|
||||
|
||||
logger.warn(
|
||||
"WebpackRequire was not initialized, falling back to WebpackRequire passed to the first called wrapped module factory (" +
|
||||
`id: ${String(moduleId)}` + interpolateIfDefined`, WebpackInstance origin: ${webpackInstanceFileName}` +
|
||||
`id: ${String(module.id)}` + interpolateIfDefined`, WebpackInstance origin: ${webpackInstanceFileName}` +
|
||||
")"
|
||||
);
|
||||
|
||||
|
@ -355,8 +412,8 @@ function runFactoryWithWrap(moduleId: PropertyKey, patchedFactory: PatchedModule
|
|||
|
||||
if (shouldIgnoreModule) {
|
||||
if (require.c != null) {
|
||||
Object.defineProperty(require.c, moduleId, {
|
||||
value: require.c[moduleId],
|
||||
Object.defineProperty(require.c, module.id, {
|
||||
value: require.c[module.id],
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
writable: true
|
||||
|
@ -369,7 +426,7 @@ function runFactoryWithWrap(moduleId: PropertyKey, patchedFactory: PatchedModule
|
|||
|
||||
for (const callback of moduleListeners) {
|
||||
try {
|
||||
callback(exports, moduleId);
|
||||
callback(exports, module.id);
|
||||
} catch (err) {
|
||||
logger.error("Error in Webpack module listener:\n", err, callback);
|
||||
}
|
||||
|
@ -379,7 +436,7 @@ function runFactoryWithWrap(moduleId: PropertyKey, patchedFactory: PatchedModule
|
|||
try {
|
||||
if (filter(exports)) {
|
||||
waitForSubscriptions.delete(filter);
|
||||
callback(exports, moduleId);
|
||||
callback(exports, module.id);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -392,7 +449,7 @@ function runFactoryWithWrap(moduleId: PropertyKey, patchedFactory: PatchedModule
|
|||
|
||||
if (exportValue != null && filter(exportValue)) {
|
||||
waitForSubscriptions.delete(filter);
|
||||
callback(exportValue, moduleId);
|
||||
callback(exportValue, module.id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -97,10 +97,6 @@ export const moduleListeners = new Set<CallbackFn>();
|
|||
export const factoryListeners = new Set<FactoryListernFn>();
|
||||
|
||||
export function _initWebpack(webpackRequire: WebpackRequire) {
|
||||
if (webpackRequire.c == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
wreq = webpackRequire;
|
||||
cache = webpackRequire.c;
|
||||
|
||||
|
|
Loading…
Reference in a new issue