Pitfalls
Web vs Node.js
Node.js code cannot be imported directly from the web environment. To share code between the two environments, use moonlight.getNatives
. See the cookbook for more information.
Webpack require is not Node.js require
The require
function used in Webpack modules and patches is not the same as the function in Node.js. Instead, it’s specific to Webpack, and only works inside of Webpack modules. See here for more information.
The web entrypoint is not a Webpack module
You cannot use Webpack modules inside of index.ts
, because it is loaded before Webpack is initialized. Instead, create your own Webpack module and use that instead.
Webpack modules only load when required
By default, Webpack modules will not load unless they are required by another module or the entrypoint
flag is set. If you need a module to run as soon as possible, set the entrypoint flag.
Using ESM features
ESM-specific features (like top-level await
) are not supported in Webpack modules or the Node or Host environments. The index.ts
file in your extension (and only that file) can be compiled to ESM by modifying your build script.
Spacepack findByCode matching itself
When using the findByCode
function in Spacepack while inside of a Webpack module, you can sometimes accidentally match yourself. It is suggested to fragment the string in source but have it evaluate to the same string:
const { something } = spacepack.findByCode(`__SECRET_INTERNALS_DO_NOT${""}_USE_OR_YOU_WILL_BE_FIRED`)[0].exports;
Note that esbuild will merge string concatenation, so you must be creative!
Using JSX
JSX (and its TypeScript version, TSX) is an extension of JavaScript that allows you to write HTML-like syntax in your code. Webpack modules with a .tsx
filename can use JSX directly. The default configuration of the build script is to convert the JSX to React.createElement
calls:
const myElement = <span>Hi!</span>;// becomesconst myElement = React.createElement("span", null, "Hi!");
Because of this, you need to make sure the React variable is in scope. react
is automatically populated as a Webpack module name with mappings:
import React from "@moonlight-mod/wp/react";const myElement = <span>Hi!</span>;
More information on using React in extensions can be found here. Remember to add React to your module dependencies.
Using the wrong moonlight global
What moonlight global exists depends on the entrypoint. See the API documentation for more details.
Each Webpack module is an entrypoint
Say you were writing a Webpack module that shared some state:
export const someState = 1;
and you wanted to use it in another Webpack module. Do not directly import the Webpack module as a file:
import { someState } from "./someModule";
as it will make a copy of the module, duplicating your state and causing issues. Each Webpack module is treated as its own entrypoint, and all imports will be duplicated between them. Instead, require it at runtime:
import { someState } from "@moonlight-mod/wp/yourExtension_someModule";// orconst { someState } = require("yourExtension_someModule");
Remember to type your module when using import statements.
Restarting dev mode is required in some scenarios
You must restart the pnpm run dev
command if you make changes to certain files:
- Adding a new extension
- Editing an extension manifest
- Adding/removing a Webpack module or entrypoint
If you delete anything, it is suggested to run pnpm run clean
so that they don’t stick around in the dist
folder.