Skip to content

Commit 9288306

Browse files
committed
initial commit of web2vector
0 parents  commit 9288306

29 files changed

Lines changed: 5233 additions & 0 deletions

.github/workflows/ci.yml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
build-and-test:
11+
runs-on: ubuntu-latest
12+
13+
steps:
14+
- uses: actions/checkout@v4
15+
16+
- uses: actions/setup-node@v4
17+
with:
18+
node-version: 20
19+
cache: npm
20+
21+
- run: npm ci
22+
23+
- name: Generate icons
24+
run: npm run build:icons
25+
26+
- name: Build extension
27+
run: npm run build
28+
29+
- name: Run tests
30+
run: npm test
31+
32+
- name: Package extension ZIP
33+
run: node scripts/package-extension.mjs
34+
35+
- name: Upload build artifact
36+
uses: actions/upload-artifact@v4
37+
with:
38+
name: web2vector-extension
39+
path: "*.zip"

.github/workflows/publish.yml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
name: Publish to Chrome Web Store
2+
3+
on:
4+
release:
5+
types: [published]
6+
workflow_dispatch:
7+
8+
jobs:
9+
publish:
10+
runs-on: ubuntu-latest
11+
12+
steps:
13+
- uses: actions/checkout@v4
14+
15+
- uses: actions/setup-node@v4
16+
with:
17+
node-version: 20
18+
cache: npm
19+
20+
- run: npm ci
21+
22+
- name: Generate icons
23+
run: npm run build:icons
24+
25+
- name: Build extension
26+
run: npm run build
27+
28+
- name: Run tests
29+
run: npm test
30+
31+
- name: Package extension ZIP
32+
run: node scripts/package-extension.mjs
33+
34+
- name: Upload to Chrome Web Store
35+
env:
36+
CHROME_EXTENSION_ID: ${{ secrets.CHROME_EXTENSION_ID }}
37+
CHROME_CLIENT_ID: ${{ secrets.CHROME_CLIENT_ID }}
38+
CHROME_CLIENT_SECRET: ${{ secrets.CHROME_CLIENT_SECRET }}
39+
CHROME_REFRESH_TOKEN: ${{ secrets.CHROME_REFRESH_TOKEN }}
40+
run: npm run upload

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
node_modules/
2+
dist/
3+
*.zip
4+
.env
5+
.DS_Store
6+
Thumbs.db
7+
coverage/

README.md

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
# Web2Vector — Chrome Extension
2+
3+
A Chrome extension that exports any web page's rendered DOM to **10 output formats** using the [@node-projects/layout2vector](https://github.com/node-projects/layout2vector) library.
4+
5+
<p align="center">
6+
<img src="src/icons/icon.svg" width="128" alt="Web2Vector icon" />
7+
</p>
8+
9+
## Supported Formats
10+
11+
| Category | Format | Extension | Writer |
12+
|----------|--------|-----------|--------|
13+
| **Vector** | SVG | `.svg` | `SVGWriter` |
14+
| | DXF (Standard) | `.dxf` | `DXFWriter` (via `@tarikjabiri/dxf`) |
15+
| | DXF (AutoCAD) | `.dxf` | `AcadDXFWriter` (via `@node-projects/acad-ts`) |
16+
| | DWG | `.dwg` | `DWGWriter` (via `@node-projects/acad-ts`) |
17+
| | EMF | `.emf` | `EMFWriter` |
18+
| **Document** | PDF | `.pdf` | `PDFWriter` |
19+
| | HTML | `.html` | `HTMLWriter` |
20+
| **Image** | PNG | `.png` | `ImageWriter` |
21+
| | JPEG | `.jpg` | `ImageWriter` |
22+
| | WebP | `.webp` | `ImageWriter` |
23+
24+
## Usage
25+
26+
1. Click the **Web2Vector** icon in the Chrome toolbar.
27+
2. Pick an export format from the popup menu.
28+
3. A **Save As** dialog appears — choose where to save the file.
29+
30+
You can also **right-click** on any page and use the **Web2Vector Export** context menu.
31+
32+
## Lazy Loading
33+
34+
Heavy third-party dependencies are split into separate bundles and only loaded when the user selects a format that needs them:
35+
36+
| Bundle | Loaded when | Dependency |
37+
|--------|-------------|------------|
38+
| `core-lib.js` | Always (first export) | `@node-projects/layout2vector` core + built-in writers |
39+
| `dxf-writer.js` | DXF (Standard) selected | `@tarikjabiri/dxf` |
40+
| `acad-writers.js` | DXF (AutoCAD) or DWG selected | `@node-projects/acad-ts` |
41+
42+
## Development
43+
44+
### Prerequisites
45+
46+
- Node.js ≥ 20
47+
- npm
48+
49+
### Setup
50+
51+
```bash
52+
npm install
53+
```
54+
55+
### Build
56+
57+
```bash
58+
# Generate icon PNGs from SVG source
59+
npm run build:icons
60+
61+
# Bundle the extension into dist/
62+
npm run build
63+
64+
# Or both in one step
65+
npm run build:all
66+
```
67+
68+
### Load in Chrome
69+
70+
1. Navigate to `chrome://extensions`
71+
2. Enable **Developer mode** (top right)
72+
3. Click **Load unpacked** → select the `dist/` folder
73+
74+
### Test
75+
76+
```bash
77+
npm test
78+
```
79+
80+
### Package for Distribution
81+
82+
```bash
83+
npm run package # creates web2vector-<version>.zip
84+
```
85+
86+
## Publishing to Chrome Web Store
87+
88+
### Manual
89+
90+
1. Go to the [Chrome Web Store Developer Dashboard](https://chrome.google.com/webstore/devconsole).
91+
2. Upload the ZIP created by `npm run package`.
92+
93+
### Automated (GitHub Actions)
94+
95+
The **Publish** workflow runs on GitHub Releases or manual dispatch. Configure these repository secrets:
96+
97+
| Secret | Description |
98+
|--------|-------------|
99+
| `CHROME_EXTENSION_ID` | Your extension ID from the CWS dashboard |
100+
| `CHROME_CLIENT_ID` | Google OAuth2 client ID |
101+
| `CHROME_CLIENT_SECRET` | Google OAuth2 client secret |
102+
| `CHROME_REFRESH_TOKEN` | OAuth2 refresh token (see [guide](https://developer.chrome.com/docs/webstore/using-api)) |
103+
104+
Then create a GitHub Release to trigger the publish workflow, or use the manual dispatch.
105+
106+
## Project Structure
107+
108+
```
109+
├── manifest.json Chrome extension manifest (v3)
110+
├── esbuild.config.mjs Build configuration
111+
├── src/
112+
│ ├── shared/formats.js Format definitions (shared by popup + background)
113+
│ ├── popup/ Extension popup UI
114+
│ │ ├── popup.html
115+
│ │ ├── popup.css
116+
│ │ └── popup.js
117+
│ ├── background/
118+
│ │ └── service-worker.js Background service worker
119+
│ ├── content/
120+
│ │ ├── core-lib.js Core library bundle
121+
│ │ ├── dxf-writer.js DXF lazy chunk
122+
│ │ ├── acad-writers.js DWG / AcadDXF lazy chunk
123+
│ │ └── run-export.js Export orchestration (injected per export)
124+
│ └── icons/
125+
│ └── icon.svg Source icon
126+
├── scripts/
127+
│ ├── build-icons.mjs SVG → PNG conversion
128+
│ ├── package-extension.mjs ZIP packaging
129+
│ └── upload-to-store.mjs Chrome Web Store upload
130+
├── tests/
131+
│ ├── formats.test.js
132+
│ ├── manifest.test.js
133+
│ └── service-worker.test.js
134+
└── .github/workflows/
135+
├── ci.yml Build + test on push/PR
136+
└── publish.yml Publish to Chrome Web Store
137+
```
138+
139+
## License
140+
141+
MIT

esbuild.config.mjs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import * as esbuild from 'esbuild';
2+
import fs from 'node:fs';
3+
import path from 'node:path';
4+
5+
const DIST = 'dist';
6+
7+
// Ensure clean dist/
8+
fs.rmSync(DIST, { recursive: true, force: true });
9+
fs.mkdirSync(DIST, { recursive: true });
10+
fs.mkdirSync(path.join(DIST, 'icons'), { recursive: true });
11+
12+
const commonOptions = {
13+
bundle: true,
14+
format: 'iife',
15+
platform: 'browser',
16+
target: 'chrome120',
17+
treeShaking: true,
18+
minify: false, // keep readable for Chrome Web Store review
19+
sourcemap: false,
20+
};
21+
22+
// ── Content-script bundles (injected via chrome.scripting) ──
23+
const contentEntries = [
24+
'src/content/core-lib.js',
25+
'src/content/dxf-writer.js',
26+
'src/content/acad-writers.js',
27+
'src/content/run-export.js',
28+
];
29+
30+
for (const entry of contentEntries) {
31+
const outName = path.basename(entry);
32+
await esbuild.build({
33+
...commonOptions,
34+
entryPoints: [entry],
35+
outfile: path.join(DIST, outName),
36+
});
37+
console.log(` ✔ ${outName}`);
38+
}
39+
40+
// ── Popup ──
41+
await esbuild.build({
42+
...commonOptions,
43+
entryPoints: ['src/popup/popup.js'],
44+
outfile: path.join(DIST, 'popup.js'),
45+
});
46+
console.log(' ✔ popup.js');
47+
48+
// ── Service worker ──
49+
await esbuild.build({
50+
...commonOptions,
51+
entryPoints: ['src/background/service-worker.js'],
52+
outfile: path.join(DIST, 'service-worker.js'),
53+
});
54+
console.log(' ✔ service-worker.js');
55+
56+
// ── Copy static assets ──
57+
const statics = [
58+
['manifest.json', 'manifest.json'],
59+
['src/popup/popup.html', 'popup.html'],
60+
['src/popup/popup.css', 'popup.css'],
61+
];
62+
63+
for (const [src, dest] of statics) {
64+
fs.copyFileSync(src, path.join(DIST, dest));
65+
console.log(` ✔ ${dest} (copy)`);
66+
}
67+
68+
// Copy icons if they exist (generated by build:icons)
69+
const iconsDir = path.join(DIST, 'icons');
70+
for (const size of [16, 32, 48, 128]) {
71+
const name = `icon${size}.png`;
72+
const src = path.join('src', 'icons', name);
73+
if (fs.existsSync(src)) {
74+
fs.copyFileSync(src, path.join(iconsDir, name));
75+
console.log(` ✔ icons/${name} (copy)`);
76+
}
77+
}
78+
79+
console.log('\nBuild complete → dist/');

manifest.json

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"manifest_version": 3,
3+
"name": "Web2Vector",
4+
"description": "Export any web page to SVG, DXF, DWG, EMF, PDF, HTML, PNG, JPEG, or WebP using layout2vector",
5+
"version": "1.0.0",
6+
"icons": {
7+
"16": "icons/icon16.png",
8+
"32": "icons/icon32.png",
9+
"48": "icons/icon48.png",
10+
"128": "icons/icon128.png"
11+
},
12+
"action": {
13+
"default_popup": "popup.html",
14+
"default_icon": {
15+
"16": "icons/icon16.png",
16+
"32": "icons/icon32.png",
17+
"48": "icons/icon48.png"
18+
},
19+
"default_title": "Web2Vector — Export page"
20+
},
21+
"permissions": [
22+
"activeTab",
23+
"downloads",
24+
"scripting",
25+
"contextMenus"
26+
],
27+
"background": {
28+
"service_worker": "service-worker.js"
29+
}
30+
}

0 commit comments

Comments
 (0)