Skip to content

superdesigndev/superdesign-extension

Repository files navigation

Superdesign Extension

A Chromium (Manifest V3) browser extension that clones any website into a single, self-contained HTML file — capturing the page exactly as it renders, then producing a lightweight, editable version you can open in Superdesign or download.

Built on top of SingleFile (single-file-core).


What it does

Click Clone page on any site and the extension freezes it into a single, self-contained HTML file, cleans it up, and uploads that lightweight copy to Superdesign, which opens a preview tab where you can turn it into an editable project. The extension's job ends at the upload — preview and editing are the platform's, not this repo's.

By default the clone goes straight to the preview tab (a loader, then the result). An alternate on-page exact-preview modal — the pixel-exact, fully inlined capture with a download button, shown before anything is uploaded — is available behind the CLONE_DIRECT_TO_PREVIEW flag in injector-main.js.

How it works

The whole clone is essentially one call to SingleFile, wrapped by page-injector/capture/singlefile-capture.js:

  1. Wake lazy content. Scroll the page top-to-bottom first, so scroll-driven images and IntersectionObserver content actually load before we freeze the DOM.
  2. Freeze. SingleFile's getPageData() walks the live DOM and inlines it into one self-contained HTML string. We give it our own fetch: page-context first (keeps same-origin credentials), falling back to a service-worker proxy for assets the page can't read cross-origin (the SW holds <all_urls>).
  3. Media policy — don't inline the heavy stuff. Base64-inlined media is ~85% of a naive clone's bytes, so for media/fonts at or above a size threshold our fetch deliberately throws — which makes SingleFile keep the asset's original remote URL instead of embedding it. Only tiny assets inline; inline <svg> is never fetched, so vectors stay crisp. This drops clones from ~10–25MB to ~1–2MB with no per-site logic. (Trade-off: an image whose origin later blocks hotlinking may not load — acceptable for a design snapshot.)
  4. Settle images. On the frozen output, promote any still-unresolved lazy <img> to a real URL, or drop a dimension-preserving grey placeholder (tagged data-sd-placeholder) so the layout doesn't collapse on uncapturable images.
  5. Strip + guard. Remove inert cruft (tracking pixels, ld+json, comments, CSP <meta>, dead @font-face, injected third-party extension UI), then embed a small render-time link guard: cloned links stay inert (they're placeholders in a design tool) but keep their :hover/styling, and in-page #anchor links still scroll.
  6. Upload. The cleaned HTML is zipped and HMAC-signed by the service worker (the device is registered once via core/auth), then uploaded; the platform opens the preview tab.

Everything above is this repo. What happens after the upload — preview, "create project," AI editing — runs on a separate service that does not ship SingleFile. That boundary matters for the license (below).

Features

  • 🧊 Faithful capture via SingleFile — self-contained HTML, no external runtime dependencies.
  • 🖼️ Media & font externalization — large assets become links, keeping the cleaned output small.
  • 🩹 Placeholders for images that can't be captured (lazy-loaded through custom JS) — tidy grey boxes instead of broken images, tagged (data-sd-placeholder) for later replacement.
  • 🧹 Cleanup pipeline — strips inert cruft (tracking pixels, ld+json, comments), CSP <meta>, dead @font-face rules, and injected third‑party extension UI (WXT/Plasmo/Grammarly).
  • 🔗 Link guard — cloned links stay inert (in a design tool they're placeholders, not live destinations); in-page #anchor links still scroll.
  • 📸 Lazy-image loading via a MAIN-world hooks content script so deferred images are captured before the page is frozen.
  • 👀 Exact-preview modal (optional, behind CLONE_DIRECT_TO_PREVIEW = false) — the pixel-exact, fully-inlined capture shown on-page with expand, download, and "Open in Superdesign".

Architecture

src/
├── config/                 # Environment config (endpoints — see Configuration)
├── common-assets/          # Shared constants / assets
├── core/                   # Device registration (auth) + secure upload
├── page-injector/          # The on-page UI + capture pipeline
│   ├── capture/            #   SingleFile wrapper (singlefile-capture.js)
│   ├── components/         #   Toolbar, modals, exact-preview
│   ├── core/               #   Interactive element picker
│   └── singlefile-hooks-main.js   # MAIN-world lazy-image hooks
└── service-orchestrator/   # Service worker (install, messaging, device)

Getting started

Prerequisites

  • Node.js + npm

Build

npm install
npm run build         # production build → dist/
# or: npm run build:dev

Load in Chrome

  1. Open chrome://extensions.
  2. Enable Developer mode (top-right).
  3. Load unpacked → select the dist/ folder.

Already had the target site open before installing? Reload that tab — Chrome only activates the extension on pages loaded after install. New tabs work immediately.

Configuration

Endpoints are read from Vite env vars at build time — no source edits. Copy the committed template, fill in your own services, and the build mode picks the target:

cp .env.example .env.development   # then set your URLs
npm run build:dev                  # dev  (vite --mode development → .env.development)
npm run build                      # prod (vite --mode production  → .env.production)

.env.example documents every variable:

VITE_USE_SINGLEFILE=true
VITE_SD_API_URL=https://your-backend-api.example.com
VITE_SD_WEB_URL=https://your-web-app.example.com
VITE_UI2WEB_URL=https://your-ui2web-service.example.com
VITE_API_BASE_URL=https://your-web-app.example.com

src/config/environment.js reads these through import.meta.env (Vite inlines them at build). The .env* files are gitignored (public URLs, no secrets); only .env.example is committed.

One value lives outside the env files: manifest.jsonexternally_connectable.matches must list the origin of the web app that messages the extension (it ships as a your-web-app.example.com placeholder). A manifest is static JSON and can't read env vars, so set it there by hand.

No secrets are stored in the repo — device credentials for uploads are fetched at runtime during registration.

License

This project is licensed under AGPL-3.0-or-later — see LICENSE.

It bundles SingleFile (single-file-core), which is licensed under AGPL-3.0. Because SingleFile's code is compiled into this extension, the combined work is a derivative and is therefore distributed under AGPL-3.0-or-later as well.

If you want to use SingleFile in a closed-source / commercial product, the SingleFile author offers commercial licensing — contact gildas.lormeau [at] gmail.com.

Why it's AGPL — the reasoning

Not legal advice, just how we reason about it:

  • single-file-core is AGPL-3.0, and we compile it into the extension — so the extension is a derivative work, and AGPL means it has to be AGPL too. That's why this repo is public under the same license.
  • Only the extension bundles SingleFile. The Superdesign platform/backend it uploads to is a separate program that does not include SingleFile, so it isn't a derivative and isn't bound by AGPL. The split — capture (AGPL, this repo) vs. everything after the upload (separate service) — is deliberate, and the "How it works" flow above is where that line sits.
  • AGPL's network clause (§13) is about running a modified copy as a service without offering its source. This is an ordinary distributed program: users run it in their own browser and the source is right here, so the obligation is the plain "ship the program, ship the source" — which publishing this repo satisfies.

If any of that actually gates a decision for you, run it past counsel — the derivative-work and §13 lines are subtler than a README can settle.

Credits

  • SingleFile by Gildas Lormeau — the page-capture engine at the heart of this extension.

SingleFile (and therefore this extension) incorporates code from these third-party projects:

About

Browser extension that clones any website into a single self-contained HTML file (SingleFile-based). AGPL-3.0.

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages