Skip to main content

Structure & Sharing

One entry, any number of modules​

A module is registered when a defineModule call runs. So one script can declare as many modules as you like, either in the entry file or in files the entry imports:

// src/index.ts
import { defineModule } from "@protohax/userscript";

defineModule(
{ name: "AutoSprint" },
{
speed: { type: "number", def: 1, min: 0, max: 5, step: 0.1 },
},
(ctx) => {
ctx.on("movement_tick", (state) => state.strafe(ctx.options.speed.value, 1));
},
);

How you split files is up to you. The bundler inlines everything the entry reaches either way. If a file only registers a module and exports nothing, import it for its side effect:

import "./modules/auto-sprint";
warning

Don't add a second bundler entry point. That produces a second output file.

Importing another script​

A phaxuser: import reaches a different file in the client's scripts folder. It's resolved at load time, just like @protohax/userscript. The path is relative to the scripts folder:

// <scripts>/lib/math.js
import { clamp } from "phaxuser:lib/math.js";

// <scripts>/shared.js
import shared from "phaxuser:shared.js";

Use it to share code between scripts, like a helper library or a common config. Don't use it to split one script across files. Anything inside your own src/ should be a normal relative import so it gets bundled; phaxuser: is for things that ship as their own file.

This affects your build in two ways.

Keep the specifier external. Like the facade, it has to survive into the output. If it gets bundled, there's nothing left to resolve at load time:

// rollup.config.mjs
external: ["@protohax/userscript", /^phaxuser:/],

Declare it for TypeScript, which can't resolve the scheme on its own. A shorthand ambient declaration types every such import as any:

// src/phaxuser.d.ts
declare module "phaxuser:*";

For a library you control, declare its shape too. The specific declaration wins over the wildcard, so you get real type checking back:

declare module "phaxuser:lib/math.js" {
export function clamp(value: number, min: number, max: number): number;
}

The target script exports things like any other ES module. Just make sure the file you import actually ships to the scripts folder alongside the script that imports it.