DEV Community

Cover image for I built a GUI-powered Userscript manager for faster userscript creation!
orangishcat
orangishcat

Posted on

I built a GUI-powered Userscript manager for faster userscript creation!

I made Page Proxy, a Chrome and Firefox extension that serves as a userscript manager with GUI tools to help making userscript creation easier.

This is my first project I've ever released. Please try it out and give feedback! Would be really appreciated.

Why?

I've always found messing around with userscript managers like Tampermonkey quite fun; I could fix websites and change their behavior to my liking. But it took me roughly 15 minutes to go from idea to code, then 30 minutes to an hour just to debug and polish the script.

The first issue: juggling with DevTools windows, copy-pasting CSS selectors, and debugging my scripts was incredibly tedious.
I'd mostly have to rely on print debugging, then I would have to reload the page every time and search through the console for errors.

So I wanted a way to quickly generate CSS selectors and code. I wanted something similar to DevTools, but has integration with code and selector generation to speed up userscript development.

Second of all, there were many reoccurring patterns in all of my scripts. For example:

Do something on key press:

document.addEventListener("keydown" ev => {
    if (!ev.altKey) return;
    if (ev.code === "KeyV") {
        runFunction();
    }
});
Enter fullscreen mode Exit fullscreen mode

Press a key using JS (e.g. Escape):

const escEvent = new KeyboardEvent("keydown", {
    key: "Escape",
    keyCode: 27,
    which: 27,
    bubbles: true,
    cancelable: true,
});

document.dispatchEvent(escEvent);
Enter fullscreen mode Exit fullscreen mode

So I wanted to make an API that included all of these common usage patterns out of the box.

My philosophy

The product should solve:

  1. GUI tools for userscript creation to make it less tedious
  2. API covering common use cases and code patterns

The product must remain:

  1. Accessibility: The extension can be complicated, but it should be easy to use. Add help docs (or links to them) everywhere.
  2. Break-resistant: If a website's CSS classes change, the script should still work or at least be easy to migrate.
  3. Integration: The extension should not replace any other tools. Keep Tampermonkey compatibility, integrate with DevTools.

Planning and design

Note: this was the first ever design sketch of the project. The extension has probably strayed quite far from it now.

I learned how to use Figma and some common design patterns. Designing my own UI has always been a goal of mine as a programmer, so I'm glad I was able to do that for this project.

Plus, I really disliked the overused blue-and-purple palette signature of AI website design. So using Realtime Colors, I chose a simple orange and lime color palette.

If you're interested, here's a raw, unedited flow chart of the features I wanted in Page Proxy that I threw up in an hour or so.

Initial design

I originally wanted to make Page Proxy a web app, mainly so it would "be unique". I quickly realized that unique does not always mean that it's good.

From a technical standpoint, the web app quickly became a terrible idea. Websites won't function due to CSP, and they certainly won't keep cookies, rendering pretty much all content behind a login screen completely inaccessible.

Besides, it just didn't make sense at all. It would be much more convenient to create an extension side panel, allowing it to be easily accessed, like DevTools.

Old design:

New extension design

After some designing, I came up with this:

I finally loved the design. It was significantly cleaner, and I also was incredibly proud of myself that I could make my color palette work so well.

It's similar to DevTools, but I kept the top toolbar as buttons. This fits the design very well, keeping the UI large-scale and with self-explanatory icons.

I liked this design for its simplicity. While DevTools, for example, was feature rich, it also had quite a steep learning curve, and a very dense, packed UI. I wanted to do the opposite; with the features I am planning, I wanted to prioritize ease of use, and I had no need for such densely packed features.

So the natural next question was: what tools do I want to add?

Tools

  • Select tool: Similar to DevTools' inspect element, but provides additional information like selectors, attributes, and more.
  • Create tool: Create components (like in web frameworks like React and Svelte) in scripts
  • Selectors tool: List CSS selectors and their properties
  • Help tool: Help links and stuff
  • Export tool: Export the script in a variety of different formats.

Now for the bottom half of the sidepanel.

Code editor

  • JS code editor
  • Has highlighting and autocomplete
  • Inspiration: Tampermonkey editor

Total time spent in design phase: 10 days. Probably only a few hours overall, but designing kinda felt like a chore since I was so new to it, which is why I didn't have the motivation to spend much time on it per day.

Implementation

Setup

I split the monorepo into web and extension directories.

Some library choices:

  • Package manager: bun
  • Web framework: Svelte
  • Extension framework: WXT
  • Components: bits-ui
  • CSS: tailwindcss
  • Icons: lucide-svelte
  • Network: axios (no I was not affected by the breach, because I update my packages like once a month)
  • Test: vitest
  • Code editor: CodeMirror

Actual coding

And implementation began. It was a bit rough at the start; the first implementation did not look good.

Along with the selector panel, I added a popup centered on the main page that provides element information and a selector builder. This helped identifying elements much easier, as unlike DevTools, all the selector information was all laid out already for me to convert into code.

The API

Original API layout (changed now):

  • pq: stands for pp-query, contains methods related to querying and traversing through DOM elements.
  • ps: pp-style: A module for style helpers
  • pv: pp-event: A module for callback-based event selectors For the methods that were added, view the old documentation, there's too many for me to list here.

The three files was placed in the extension directory, serving as a library containing the modules.

Also at the top of each userscript, there must be metadata. Similar to Tampermonkey, it had @title, @website, and @description to describe the script, and it is autogenerated upon new script creation but is very much editable.

// ==Page Proxy==
// @title Page Proxy
// @website
// @description
// ==/Page Proxy==
Enter fullscreen mode Exit fullscreen mode

The editor

Looking at the above screenshot, the editor looked, well, not great. Code syntax highlighting broke often, and required a lot of maintenance. There were no line numbers. The entire container was offset for some reason. It was overall a huge pain to fix.

So I switched to Monaco editor, the same editor that powers VSCode. The extension instantly became a few MB heavier, but it was definitely worth it. Monaco editor was extremely powerful, with all the standard editor features such as renaming variables, syntax highlighting, and more just out of the box. I'm so glad I didn't have to implement any of that myself, yet it's available for everyone to use.

For autocomplete, I eventually hacked together a solution that generated type stubs from the API and passed it into Monaco editor. That temporary solution is still being used today. If it ain't broke, I ain't fixing it. It's probably staying in the codebase for good.

Nearly two months of coding later

The extension looks much more polished now. It was also functional enough that I was able to actually make and run userscripts on it.
I had spent way longer than expected on the project, and decided to cut some features for the initial release.
The select tool was finished fully, but the create tool was marked "Coming soon", and I settled on a simple display for the selectors tool.
At this point, I just needed to publish it and get some initial validation.

Demo website

It was quite plain, and didn't even have a feature list. I struggled a lot to explain the premise of the system in just 1-2 sentences. Even after publishing, the landing page remained incomplete for another two months.

Final polishes

Oh... no one could figure out how to use the extension. I've already tried to make the extension pretty self-explanatory, but I guess I still need to write some documentation.

Documentation

I used Docusaurus to host my documentation website. Although it used mdx (based on React) while the rest of my website was using Svelte, there just wasn't a solution that worked nearly as well out of the box. There I made some basic tutorials and wrote documentation for the API.

It was very primitive, but it got the job done, and eventually it was good enough for publish.

Bugs

I initially used script injection to run user JS in the page content, but that would be blocked by CSP on certain websites, rendering them completely inaccessible for userscripts. This was fixed easily by migrating to the UserScript API, but now users will have to enable permissions to run scripts on the main page.

Also, there were annoying Firefox-specific bugs that I had to fix, but they overall weren't too important.

The end of v0.1.x

For the rest of v0.1.x I just did some chores, bug fixes, and small features. I had made a basic sandbox for the userscript using @endojs/lockdown, but it didn't really work as it was extremely easy to bypass. I wanted to add functions, network access controls, etc., but it couldn't be done reliably without OS-native controls so I removed it in the last version of v0.1.x.

Initial validation

I published to Hack Club, and everyone who viewed my project thought it was pretty cool :D

Final thoughts

Whew! This is probably the longest piece of media I have written in my entire life. Evidently I have a lot to learn.

I'm ending the devlog at v0.1.x because as my first post ever, this took me like 3-4 hours to write. I've already added way more features in v0.2.x and v0.3.x, but I will get around to writing about them another time.

I learned a lot over this journey. This was my first introduction to multiple new webdev (and also non-webdev) concepts I had never interacted with before.

  • Figma: Probably my biggest reward from seeing the project through. I used to never plan out my projects, working things out as I go. Terrible idea. I eventually lost track of where I was going, leading me to lose motivation, and it's much easier to change a wireframe or Figma design than website code. Without UI planning, the UI doesn't look good and is quite clunky. I definitely also wanted to avoid the AI-generated UI look, so rolling my own would be a great learning process and would also look decent.
  • Svelte: I used React for a project once, but I hated it. I eventually became trapped in useEffect hell and gave up. Svelte gave me way less trouble; it handled all the dependencies and states easily without me ever needing to use a $effect.
  • WXT: Last time I tried making a Chrome extension, I gave up because Chrome refused to recognize my manifest no matter what I tried. WXT allows me to build extensions that are not only cross-browser using a unified API, but simplify the configuration as well. WXT gets a glowing recommendation from me.
  • Docusaurus: Not much else to say, other than it is insanely cool and works very well for hosting documentation.
  • GitHub actions: I learned to set up Actions workflows for basically whatever I wanted. I wrote build, check, deploy to GitHub Pages workflows using myself and some AI.
  • Dependency management: I configured Renovate to check for package updates. After all the package breaches that have been happening lately, I probably won't be updating any packages until they're each at least three days old.
  • Search Engine Optimization: I learned to add some basic metadata to the website for SEO.
  • Coding with AI: I learned to code alongside AI (no, letting the AI do everything didn't work) and speed up development, while I reviewed the code and kept the code quality as best as I could.

And as of v0.1.x, am I following my original goals for the project?

  1. GUI tools: yes
  2. API for common use cases: yes
  3. Accessibility: yes
  4. Break-resistant: no, not really; there were matchers based on other attributes like inner text, but CSS selectors were still much better
  5. Integration: no, there's not much integration

But this is just the beginning, so I'd say this is a great starting point. I still have a lot of blogging to do for v0.2.x and v0.3.x.

If you think my extension is interesting, you can give it a try and maybe stick around as I devlog more of my journey!

This being my first project, I've probably never even written a blog post before, so I'm still figuring things out. Any feedback you have would be greatly appreciated!

Top comments (0)