Webpack modules & patching
Webpack is a library used by Discord to turn their codebase into a bundled JavaScript file. Code gets converted into Webpack “modules”, which are individual functions that can require and load each other. You can think of them as individual files, each with their own code and exports, but converted into single functions inside of the client.
Patching
Patching is the process of altering the code of Webpack modules. Each module is minified, which means that its source has no whitespace and variable names are often short. Patching allows extensions to find and replace snippets of minified code.
To create a patch, export them from your extension’s web entrypoint:
A patch consists of three parts:
find
dictates what Webpack module we want to patch. It is matched against the code of all Webpack modules, and once the match is found, it patches that module. Because of this, the match must be specific to a single module.match
is used to find the piece of code we want to patch inside of the target Webpack module. The area of code that is matched is replaced with thereplacement
.replacement
is the code that replaces the matched code.
find
and match
can both be regular expressions, and replacement
can either be a string or a function that returns a string. When replacement
is a function, the first argument will be the matched string, and the subsequent arguments will be the matched groups (if any).
You can also set the type
field in replace
to PatchReplaceType.Module
, in which case the replacement
will be used as the entire module’s code. This completely overrides it, breaking other extensions that patch the same module, so use it wisely.
When match
is a regex and replacement
is a function, it will be passed the groups matched in the regex. This is useful for capturing and reusing the minified variable names.
Inside of patches, you have access to module
, exports
, and require
. You can use the Webpack require function to load your own Webpack modules.
Examples
Using a regex for a match
Using a function as a replacement
Replacing an entire module
Writing good patches
It is suggested to follow some guidelines when writing patches:
- Never hardcode minified variable names. Use
.
(or.{1,2}
for longer names), so the patch still functions when the names change. - Use capture groups (e.g.
(.)
) to use previous variable names and snippets of code in your patches. - Keep logic inside of the patch to a minimum, and instead use
require
to load your own Webpack module.
Webpack module insertion
Similar to patching, extensions can also insert their own Webpack modules. These can be required like normal modules (which means they can be used inside of patches and other extensions). Extension Webpack modules take the form of ${ext.id}_${webpackModule.name}
(e.g. the stores
Webpack module in the common
extension has the ID common_stores
).
Webpack modules can be created in the webpackModules
folder of your extension:
./src/<your extension>/webpackModules/<webpack module name>.ts
./src/<your extension>/webpackModules/<webpack module name>/index.ts
Define the module in index.ts
, and restart the dev server:
You can also specify the Webpack module as a function, if you don’t like ESM Webpack modules:
Specify entrypoint
to run on startup. If you need to run after a certain Webpack module, or use another module as a library, specify dependencies
:
When using export default
or export something
in a ESM Webpack module, you will need to do require().default
or require().something
to access it. You can also use module.exports
from inside of the Webpack module, but it is not recommended.
Webpack module dependencies
As seen above, extension Webpack modules must specify their dependencies. They will not be inserted until these dependencies have been loaded.
You can specify:
- Extension IDs and Webpack module names (e.g.
{ ext: "common", id: "stores" }
)- Use this if you’re using an extension library.
- Make sure to mark the extension as a dependency in your extension manifest.
- Webpack module names (e.g.
{ id: "discord/Dispatcher" }
)- Use this if you’re using mappings.
- Strings and regexes to match against other modules
- Use this if you’re using Spacepack to find modules dynamically.
Importing other Webpack modules
You can import other Webpack modules by prefixing the ID with @moonlight-mod/wp
:
Custom extensions and libraries need to be typed with a declare module
in a .d.ts
:
If you need to require
a module, you should specify the type as the Webpack require:
Importing Discord’s Webpack modules
There are two ways to import a Webpack module from Discord:
Remember to add the module as a dependency.
Common patterns
You will see familiar things when reading Discord Webpack modules:
- The pattern
n(/* some number */)
represents a require, and is another module ID inside of the argument. You can pass that module ID tospacepack.inspect
to read the required module source. Z
andZP
usually correspond todefault
exports.- Mentions of
jsx
andcreateElement
imply construction of React components.