to-userscript

repository·main·Indexed 42 days ago

https://github.com/explosion-scratch/to-userscript

A CLI tool (also available as extension-to-userscript) that converts Chrome and Firefox browser extensions into standalone, portable userscripts (.user.js) or vanilla JavaScript files. It provides polyfills for WebExtension APIs including chrome.storage, chrome.runtime, chrome.tabs, chrome.notifications, and chrome.contextMenus, and handles the inlining of assets and UI elements like options and popup pages.

Tokens
9.3K
Snippets
25
Records
38
Agent score
52%

What's inside to-userscript

  1. Understand the architecture of the generated userscript

    main

    The output of to-userscript is a single, self-contained userscript that uses an internal architecture to emulate a WebExtension environment. The architecture consists of three main stages:

    1. Unified Polyfill: A set of templates are merged to create a runtime environment. This includes a message bus (createEventBus, createRuntime), an abstraction layer for storage and fetching (_storageSet, _fetch), an asset management system (EXTENSION_ASSETS_MAP, _createAssetUrl), and a core polyfill (buildPolyfill()) that provides the chrome.* namespace.
    2. Background Script Environment: All background scripts are executed immediately within the polyfill's scope upon script start.
    3. Orchestration Logic: The script determines its execution context based on location.href:
      • Content Scripts: If the URL matches a content script pattern, executeAllScripts() is called. This manages CSS injection and executes JavaScript in three distinct phases: document-start, document-end, and document-idle.
      • Options/Popup Pages: These are rendered within a modal using an iframe. The iframe is populated with inlined HTML and a specialized postmessage version of the polyfill to allow communication.
    // ==UserScript==
    // ... Metadata Block ...
    // ==/UserScript==
    
    (function() { // IIFE for scope isolation
        'use strict';
    
        // 1. UNIFIED POLYFILL is defined here
        //    - messaging.template.js -> createEventBus, createRuntime
        //    - abstractionLayer.*.template.js -> _storageSet, _fetch, etc.
        //    - assetsGenerator code -> EXTENSION_ASSETS_MAP, _createAssetUrl
        //    - polyfill.template.js -> buildPolyfill() which creates chrome.*
    
        // 2. BACKGROUND SCRIPT ENVIRONMENT is defined and executed
        //    - Runs all background scripts inside the polyfill's scope.
        //    - This happens immediately on script start.
    
        // 3. ORCHESTRATION LOGIC is defined and executed
        //    - Checks if location.href matches a content_script pattern.
        //    - If it matches:
        //        - Calls `executeAllScripts()`.
        //        - This function injects CSS and runs JS in phases:
        //          - document-start
        //          - document-end
        //          - document-idle
        //    - Registers GM_registerMenuCommand for options/popup pages.
        //    - Options/Popup pages are rendered in a modal with an iframe.
        //    - The iframe's content is populated with the inlined HTML and
        //      a specialized 'postmessage' version of the polyfill.
    })();
  2. How execution orchestration and `run_at` work

    main

    The generated userscript emulates the WebExtension execution lifecycle by following the run_at rules defined in the manifest.

    • Matching: The orchestration logic (the main function of the generated script) checks if the current page URL matches any content_scripts patterns.
    • Phased Execution: If a match is found, code is executed in the following order:
      1. document-start
      2. document-end
      3. document-idle
    • Assembly: The scriptAssembler.js module generates an executeAllScripts function that orders the JS and CSS injections for each specific phase.
  3. How the `to-userscript` conversion works

    main

    The to-userscript tool converts a standard WebExtension into a single, self-contained userscript (.user.js) or a vanilla JavaScript file. It achieves this through a multi-step pipeline:

    1. Parsing: Reads the extension's manifest.json and handles localization via _locales.
    2. Resource Loading: Reads all specified JavaScript and CSS files.
    3. Asset Inlining: Recursively scans files for assets (images, fonts, HTML, CSS) and converts them into Base64 Data URLs or Blobs, stored in an EXTENSION_ASSETS_MAP.
    4. Polyfilling: Constructs a chrome and browser object that mimics WebExtension APIs (e.g., chrome.storage, chrome.runtime) using an abstraction layer.
    5. Orchestration: Generates execution logic that respects the manifest's run_at rules (document-start, document-end, document-idle).
    6. Generation: Produces a single output file containing a userscript metadata block, the polyfills, the inlined assets, and the orchestrated scripts.
  4. How the Abstraction Layer (Adapter Pattern) works

    main

    To ensure the polyfilled WebExtension APIs work across different environments, to-userscript uses an Adapter Pattern. An internal interface (e.g., _storageSet, _storageGet, _fetch, _openTab) is defined, and the implementation changes based on the target environment:

    • userscript target: Maps calls to userscript-specific functions like GM_setValue or GM_xmlhttpRequest (defined in templates/abstractionLayer.userscript.template.js).
    • vanilla target: Maps calls to browser-native APIs like IndexedDB or fetch (defined in templates/abstractionLayer.vanilla.template.js).
    • postmessage target: Used for code running inside an iframe (like options or popup pages). It forwards API calls to the parent window via postMessage (defined in templates/abstractionLayer.postmessage.template.js).

    This decoupling allows the WebExtension API polyfill to remain agnostic of the underlying execution environment.

  5. Understand the resulting userscript output format

    main

    The conversion process produces a single, self-contained userscript with the following structural lifecycle:

    1. Metadata Block: Standard // ==UserScript== header.
    2. Unified Polyfill: A single block containing all necessary polyfills (messaging templates, abstraction layers, asset generators, and chrome.* API polyfills).
    3. Background Script Environment: All background scripts are executed immediately within the polyfill's scope.
    4. Orchestration Logic:
      • Matches location.href against content_script patterns.
      • If a match occurs, executeAllScripts() is called to inject CSS and run JS across three phases: document-start, document-end, and document-idle.
      • Registers GM_registerMenuCommand for options/popup pages.
      • Renders Options/Popup pages using a modal containing an iframe populated with inlined HTML and a specialized postmessage version of the polyfill.
    // ==UserScript==
    // ... Metadata Block ...
    // ==/UserScript==
    
    (function () {
      "use strict";
    
      // 1. UNIFIED POLYFILL
      // 2. BACKGROUND SCRIPT ENVIRONMENT
      // 3. ORCHESTRATION LOGIC
    })();
  6. How asset inlining and `runtime.getURL` work

    main

    The tool makes the resulting script self-contained by inlining all local assets.

    1. Recursive Scanning: The assetsGenerator.js engine scans top-level files (like HTML) for references (src, href, url()) and recursively processes them.
    2. Conversion: Binary assets (images, fonts) are converted to Base64 Data URLs. Text assets (CSS, HTML) are read and scanned for further dependencies before being inlined.
    3. Asset Map: All assets are stored in a JavaScript object called EXTENSION_ASSETS_MAP injected into the final script.
    4. runtime.getURL Polyfill: Instead of returning a relative path, the polyfilled chrome.runtime.getURL looks up the path in the EXTENSION_ASSETS_MAP and returns a blob: or data: URL. This allows the extension's code to access resources as if they were local files.
  7. Understand userscript conversion features and limitations

    main

    When converting extensions to userscripts, the tool handles several complex transformations:

    Included Features:

    • Content Scripts: Full support with proper execution timing.
    • Background Scripts: Automatically executed within a polyfilled environment.
    • UI Elements: Options pages and Popup pages are implemented using modal iframes.
    • Assets: Automatic inlining of resources.
    • Permissions: Smart detection of permissions and metadata generation.
    • Icons: Embedded directly into the userscript metadata.
    • Polyfills: Provides polyfills for chrome.storage, chrome.runtime, chrome.tabs, chrome.notifications, and chrome.contextMenus.

    Limitations:

    • Manifest V3: Only basic support is provided; Service Workers have limited support.
    • Privileged APIs: Some browser-privileged APIs cannot be polyfilled.
    • CORS: Cross-origin requests require proper @connect grants in the userscript metadata.
    • Vanilla Target: If using -t vanilla, there are significant CORS limitations and reduced functionality compared to the userscript target.
  8. How inter-context communication (Message Bus) works

    main

    Because UI elements like options or popup pages are rendered in sandboxed iframes, the tool implements a message bus to emulate chrome.runtime.sendMessage.

    • Mechanism: It uses window.postMessage to facilitate communication between the main userscript context and any iframe contexts.
    • createEventBus: Sets up the low-level on/emit listeners.
    • createRuntime: Builds a chrome.runtime-like object on top of the event bus to handle the request/response logic required for sendMessage calls.
  9. Convert a browser extension to a userscript

    main

    The convert command is the primary way to transform a browser extension into a standalone .user.js file.

    Supported Sources:

    • URLs: Chrome Web Store or Firefox Add-ons URLs.
    • Direct URLs: Links to .crx, .xpi, or .zip files.
    • Local Directories: Paths to unpacked extension folders.
    • Local Archives: Paths to local .crx, .xpi, or .zip files.

    Common Options:

    • --output, -o: Specify the output file path.
    • --target, -t: Set build target. userscript (default) includes metadata; vanilla outputs plain JS.
    • --minify: Minify the output using terser.
    • --beautify: Beautify the output using prettier.
    • --locale, -l: Preferred locale (e.g., en, fr).
    • --force, -f: Overwrite existing output files.

    Examples

    Convert from Chrome Web Store with minification:

    to-userscript convert "https://chromewebstore.google.com/detail/modern-for-wikipedia/emdkdnnopdnajipoapepbeeiemahbjcn?hl=en" -o modern-wikipedia.user.js --minify

    Convert a local directory with French localization:

    to-userscript convert ./my-unpacked-extension/ -o my-script.user.js --locale fr

    Convert a local XPI to vanilla JS (using pnpm dlx):

    pnpm dlx to-userscript convert ./my-addon.xpi --target vanilla -o my-addon.js
  10. Convert a Chrome extension to a userscript

    main

    Use the convert command to transform a Chrome Web Store extension into a single .user.js file. You can provide a URL to the extension and use the --minify flag to reduce file size. Use the -o flag to specify the output filename.

    to-userscript convert "https://chrome.google.com/webstore/detail/..." --minify -o my-script.user.js
  11. The `convert` workflow overview

    main

    The convert workflow is the primary path for transforming an extension into a userscript. The process follows these steps:

    1. Source Analysis: Determines if the source is a URL, a local archive (.crx, .xpi, .zip), or a directory.
    2. Preparation:
      • If a URL, it downloads the extension archive (using logic to find direct download links for Chrome/Firefox stores).
      • If an archive, it unpacks the contents into a temporary directory.
    3. Core Conversion:
      • Parses and normalizes the manifest.json.
      • Processes JS and CSS files into memory maps.
      • Generates the EXTENSION_ASSETS_MAP via the asset inlining engine.
      • Creates the userscript metadata block (including the icon).
      • Builds the polyfill string (combining the abstraction layer and polyfill templates).
      • Assembles the final script using the orchestration template.
    4. Post-processing: Optionally minifies the script with terser or beautifies it with prettier.
    5. Output: Writes the final string to the specified file.
  12. Install to-userscript

    main

    to-userscript requires Node.js v16 or higher. You can install it globally using your preferred package manager or run it directly using a runner like npx, pnpm dlx, or bunx.

    Global Installation

    # npm
    npm install -g to-userscript
    
    # pnpm
    pnpm add -g to-userscript
    
    # bun
    bun install -g to-userscript
    npm install -g to-userscript