Webpack Patcher
This script is a runtime patching helper script for applications that use Webpack (e.g., Electron renderer processes or browser contexts).
It allows you to intercept and modify webpack module code.
It works by hooking into webpack’s internal module system (__webpack_require__
), intercepting the moment modules are defined and modifying their factory functions before they execute.
Overview
Since this script patches modules before they are loaded/executed, it is necessary to initialize this script as early as possible.
That's why this is a standalone script and not a library or similar.
This also means that other scripts which utilize this script need to load and register patches as soon as possible. See example scripts for a way to do that.
Global interface
After initialization, the following is exposed on window.WebpackPatcher
:
Property |
Type |
Description |
register(options, patches) |
Function |
Registers patches. Returns the registrar object. |
addEventListener(event, callback) |
Function |
Adds a listener for webpack_detected , module_registered , or module_patched . |
removeEventListener(event, callback) |
Function |
Removes an event listener. |
webpackRequire |
`Function \ |
null` |
moduleFactories |
`Object \ |
null` |
moduleCache |
Object |
Wrapper for interacting with the webpack cache. |
patches |
Array |
All patch configurations. |
patchedModules |
Set |
IDs of modules that can be patched. |
isWebpackDetected |
boolean |
True once webpack has been detected and hooked. |
Registrars |
Object |
Registry of all user-defined patches. |
placeholders |
Object |
Special placeholder values replaced in patched code. Used in replacements |
VERSION |
`number\ |
String` |
Registrar system
The main entry point for a script is the WebpackPatcher.register(options, patches)
function.
-
object
options - A simple centralized place for your script to store data:
-
object
options.data - Place to store variables etc.
-
object
options.functions - Place to store functions.
While it is not necessary for you to use the structure provided by the script, it's probably a good thing to use a standardized structure.
-
array
patches - The array of patches
This is the structure of a registration:
WebpackPatcher.register_patches(
{ // options
name: string, // required, creates/gets WebpackPatcher.Registrars[name]
data: object, // initial data object for the registrar
functions: object // initial functions object for the registrar
},
[ // patches
{
find: string | RegExp | Array<string|RegExp>, // substring or regex to match in module code
replacements: [
{
match: string | RegExp, // substring or regex to match
replace: string | Function, // replacement string or function (function receives same args as String.replace)
global: boolean // optional, default false, if true uses replaceAll
}
]
}
]
);
Using placeholders
WebpackPatcher provides special placeholder tokens you can use inside replacements. They are available at window.WebpackPatcher.placeholders
and are replaced with concrete references to your registrar before the patched module code is evaluated.
-
window.WebpackPatcher.placeholders.self
— replaced with window.WebpackPatcher.Registrars["<your-registrar-name>"]
-
window.WebpackPatcher.placeholders.functions
— replaced with window.WebpackPatcher.Registrars["<your-registrar-name>"].functions
-
window.WebpackPatcher.placeholders.data
— replaced with window.WebpackPatcher.Registrars["<your-registrar-name>"].data
Example: use a placeholder in a replacement so the patched module calls a helper from your registrar:
{
match: "SOME_GLOBAL",
replace: window.WebpackPatcher.placeholders.functions + ".myHelper(arg)"
}
Important: do not hard-code the internal token strings (they include a random suffix). Always read the values from window.WebpackPatcher.placeholders
so the correct tokens are used at runtime.
Usage in other scripts
An easy way to use this script is the following:
(function wait_for_webpack_patcher(){
if (window.WebpackPatcher) {
logger.debug("Registering webpack patches");
window.WebpackPatcher.register({
name: name
}, PATCHES);
} else if (!window.GLOBAL_WEBPACK_ARRAY) { // e.g. webpackChunkdiscord_app for discord.com
setTimeout(wait_for_webpack_patcher, 0);
} else {
logger.warn("Webpack array found, but not patcher, stopping");
}
})();
We wait for WebpackPatcher to be available, but stop if the global webpack array is found with WebpackPatcher still being unavailable. That's because if that case is true, then something broke in the Webpack Patcher.
Modifying the script for other sites
The script is meant to be very easily ported to other sites. The only thing which should need changing is the CONFIGURATIONS
array.
CONFIGURATIONS
array
The CONFIGURATIONS constant is an ordered array of configuration objects that let the patcher pick site-specific options automatically. The first configuration for which site_match() is true gets picked, so avoid ambigious filters.
Each entry has the shape:
- site_match
function
(required) - a function which returns true if this is a site we want to patch
- options
object
- options passed to the patcher when the site matches. See the initialize
function for more details.
Structure
{
site_match: () => location.hostname === "example.com",
options: {
filter_func: () => {}
}
}
/**
* @param {Object} logger - Logger instance for debug/error output
* @param {Object} options - Configuration options
* @param {boolean} options.enable_cache - Enable caching for performance (default: false). Is only useful in some cases, check yourself.
* @param {boolean} options.use_eval - Use eval instead of new Function for better debugging. Can be disabled by sites though. (default: true)
* @param {Function} options.on_detect - Callback when webpack is detected (default: null)
* @param {Function} options.filter_func - Filter function to select webpack instance: (webpack_require, stack_lines) => boolean. Should return true to allow the instance, false to reject it.
* @param {Object} options.webpack_property_names - Property names to hook: {modules: "m", cache: "c"} (default: {modules: "m", cache: "c"})
*/
function initialize(logger, options={}) {}
The most important part is the filter_func. This should filter out the main webpack instance, the best way is to check for the caller's file. It should be the file which initializes the main part of webpack. Often has "web" or "runtime" in it's name.
Examples
See the examples for different sites in examples.
Officially supported sites
Links
GitHub
Greazyfork
Credits
Vencord Web - Inspiration on the hooking method