Dynamic website snapshot & offline mirror generator, built on TypeScript + Playwright.
It renders pages in headless Chromium, captures every asset the page actually loads (images, JS, CSS, fonts, media — including lazy-loaded and JS-injected resources) via network interception, then rewrites the HTML/CSS so the result opens directly from disk, fully offline.
- Node.js >= 20
- Chromium via Playwright:
npx playwright install chromium(on Linux alsosudo npx playwright install-deps chromium)
npm install
# Snapshot a single URL, no config needed
npx tsx src/index.ts https://example.com
# Or configure multiple pages in cornerstone.config.ts, then:
npm startOutput goes to output/<name>/index.html with assets under output/<name>/assets/<host>/....
All captured URLs are rewritten to relative paths, so index.html opens directly from disk (file://) fully offline. For an environment closer to a real deployment, serve it instead:
npx serve output/<name>
# or
python3 -m http.server 8080 --directory output/<name>URLs that were never captured (e.g. API calls made after the snapshot) are left untouched and will simply fail to load offline — graceful degradation is intentional. Heavily JS-driven sites will only show what was rendered at capture time.
import type { CornerstoneConfig } from './src/config.js';
const config: CornerstoneConfig = {
pages: {
index: 'https://www.bilibili.com',
list: 'https://www.bilibili.com/v/dance/',
},
};
export default config;| Option | Default | Description |
|---|---|---|
pages |
— | map of name → URL to snapshot |
outputDir |
output |
output root |
waitUntil |
networkidle |
Playwright navigation wait condition |
extraWaitMs |
1000 |
extra settle time after load |
scrollToBottom |
true |
auto-scroll to trigger lazy loading |
randomDelay |
false |
random 0–2s pause between pages |
assetTypes |
all | subset of image / script / stylesheet / font / media |
headless |
true |
run Chromium headless |
- Attaches a network response listener before navigation and buffers every image/script/stylesheet/font/media body (
src/assets.ts). - Renders the page in headless Chromium, optionally auto-scrolling to trigger lazy loading (
src/snapshot.ts). - Rewrites captured URLs in the serialized HTML to relative local paths (longest-first plain string replacement — handles srcset, inline styles, etc.) and resolves
url(...)refs inside captured CSS (src/rewrite.ts). - Writes everything to
output/<name>/, mapping each URL to a safe deterministic local path (src/util.ts).
npm test # vitest: unit + local-fixture integration test
npm run typecheckMIT