diff --git a/package.json b/package.json index e5f7592..0215f9a 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,8 @@ "@types/encodeurl": "^1.0.3", "@types/mime-types": "^3.0.1", "@types/node": "^24.10.0", + "@types/react": "^19.2.7", + "@types/react-dom": "^19.2.3", "cross-spawn": "^7.0.6", "eslint": "^9.39.0", "eslint-config-prettier": "^10.1.8", @@ -38,6 +40,9 @@ "lint-fix": "eslint --fix --ext .js,.ts .", "test": "vitest run", "build": "tsdown", + "build:ui": "yarn workspace @dobsjs/dev vite build", + "dev": "tsdown --watch", + "dev:ui": "yarn workspace @dobsjs/dev vite dev", "run-script": "node -r @swc-node/register", "publish": "lerna version && yarn build && lerna publish from-package" }, diff --git a/packages/create-dobs/common-template/package.json b/packages/create-dobs/common-template/package.json index dd5cf21..b1596bc 100644 --- a/packages/create-dobs/common-template/package.json +++ b/packages/create-dobs/common-template/package.json @@ -7,6 +7,7 @@ "build": "dobs build" }, "devDependencies": { + "@dobsjs/dev": "latest", "dobs": "latest" } } diff --git a/packages/create-dobs/with-javascript/dobs.config.js b/packages/create-dobs/with-javascript/dobs.config.js index f053ebf..3c931e5 100644 --- a/packages/create-dobs/with-javascript/dobs.config.js +++ b/packages/create-dobs/with-javascript/dobs.config.js @@ -1 +1,3 @@ -module.exports = {}; +module.exports = { + devtool: true, +}; diff --git a/packages/create-dobs/with-typescript/dobs.config.ts b/packages/create-dobs/with-typescript/dobs.config.ts index 1358253..f7eb368 100644 --- a/packages/create-dobs/with-typescript/dobs.config.ts +++ b/packages/create-dobs/with-typescript/dobs.config.ts @@ -1,3 +1,5 @@ import { defineConfig } from 'dobs'; -export default defineConfig({}); +export default defineConfig({ + devtool: true, +}); diff --git a/packages/dobs-dev/compiled-ui/index.html b/packages/dobs-dev/compiled-ui/index.html new file mode 100644 index 0000000..f8936b8 --- /dev/null +++ b/packages/dobs-dev/compiled-ui/index.html @@ -0,0 +1,12663 @@ + + + + + + Document + + + + + +
+ + +d diff --git a/packages/dobs-dev/index.html b/packages/dobs-dev/index.html new file mode 100644 index 0000000..c21860f --- /dev/null +++ b/packages/dobs-dev/index.html @@ -0,0 +1,15 @@ + + + + + + Document + + + +
+ + + diff --git a/packages/dobs-dev/package.json b/packages/dobs-dev/package.json new file mode 100644 index 0000000..939ac2a --- /dev/null +++ b/packages/dobs-dev/package.json @@ -0,0 +1,33 @@ +{ + "name": "@dobsjs/dev", + "version": "0.1.0-beta.3", + "main": "./dist/index.js", + "module": "./dist/index.mjs", + "types": "./dist/index.d.ts", + "repository": "https://github.com/zely-js/dobs", + "description": "Devtool for dobs", + "files": [ + "dist", + "compiled-ui" + ], + "exports": { + ".": { + "import": { + "types": "./dist/index.d.mts", + "default": "./dist/index.mjs" + }, + "require": { + "types": "./dist/index.d.ts", + "default": "./dist/index.js" + } + } + }, + "devDependencies": { + "@vitejs/plugin-react": "^5.1.1", + "react": "^19.2.0", + "react-dom": "^19.2.0", + "sass": "^1.94.2", + "vite": "^7.2.4", + "vite-plugin-singlefile": "^2.3.0" + } +} diff --git a/packages/dobs-dev/src/index.ts b/packages/dobs-dev/src/index.ts new file mode 100644 index 0000000..728fd7a --- /dev/null +++ b/packages/dobs-dev/src/index.ts @@ -0,0 +1,5 @@ +import { join } from 'node:path'; + +const path = join(__dirname, '../compiled-ui/index.html'); + +export { path, path as default }; diff --git a/packages/dobs-dev/src/render.tsx b/packages/dobs-dev/src/render.tsx new file mode 100644 index 0000000..7193256 --- /dev/null +++ b/packages/dobs-dev/src/render.tsx @@ -0,0 +1,4 @@ +import reactDOM from 'react-dom/client'; +import App from './ui'; + +reactDOM.createRoot(document.getElementById('main') as any).render(); diff --git a/packages/dobs-dev/src/styles/global.scss b/packages/dobs-dev/src/styles/global.scss new file mode 100644 index 0000000..a1001cb --- /dev/null +++ b/packages/dobs-dev/src/styles/global.scss @@ -0,0 +1,63 @@ +@import url('https://fonts.googleapis.com/css2?family=Inter:opsz,wght@14..32,100..900&display=swap'); + +@import './variables'; + +* { + box-sizing: border-box; +} + +body { + margin: 0; + background: var(--background); + color: var(--foreground); + font-family: 'Inter', sans-serif; +} + +button, +input { + font-family: 'Inter', sans-serif; +} + +.card { + background: var(--card); + border: 1px solid var(--border); + border-radius: var(--radius); + padding: 20px; + max-width: 470px; + overflow: auto; +} + +.button { + padding: 10px 14px; + background: var(--primary); + color: var(--primary-foreground); + border: none; + border-radius: var(--radius); + cursor: pointer; + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + } +} + +.input { + width: 100%; + padding: 10px; + border: 1px solid var(--border); + border-radius: var(--radius); +} + +.textarea { + width: 100%; + min-height: 160px; + padding: 10px; + border: 1px solid var(--border); + border-radius: var(--radius); +} + +.header { + border-bottom: 1px solid var(--border); + padding: 20px; + background: var(--card); +} diff --git a/packages/dobs-dev/src/styles/variables.scss b/packages/dobs-dev/src/styles/variables.scss new file mode 100644 index 0000000..7d3ea04 --- /dev/null +++ b/packages/dobs-dev/src/styles/variables.scss @@ -0,0 +1,21 @@ +:root { + --background: oklch(0.99 0 0); + --foreground: oklch(0.15 0 0); + --primary: oklch(0.28 0 0); + --primary-foreground: oklch(0.99 0 0); + --border: oklch(0.9 0 0); + --card: oklch(1 0 0); + --card-foreground: oklch(0.15 0 0); + + --radius: 8px; +} + +.dark { + --background: oklch(0.11 0 0); + --foreground: oklch(0.98 0 0); + --primary: oklch(0.98 0 0); + --primary-foreground: oklch(0.11 0 0); + --border: oklch(0.24 0 0); + --card: oklch(0.14 0 0); + --card-foreground: oklch(0.98 0 0); +} diff --git a/packages/dobs-dev/src/ui/components/RequestBuilder.tsx b/packages/dobs-dev/src/ui/components/RequestBuilder.tsx new file mode 100644 index 0000000..94488a6 --- /dev/null +++ b/packages/dobs-dev/src/ui/components/RequestBuilder.tsx @@ -0,0 +1,138 @@ +import { useState } from 'react'; + +interface Header { + key: string; + value: string; +} + +export default function RequestBuilder({ onResponse, isLoading, setIsLoading }) { + const [method, setMethod] = useState('GET'); + const [url, setUrl] = useState('/'); + const [headers, setHeaders] = useState([ + { key: 'Content-Type', value: 'application/json' }, + ]); + const [body, setBody] = useState( + `{\n "title": "foo",\n "body": "bar",\n "userId": 1\n}`, + ); + + const addHeader = () => setHeaders([...headers, { key: '', value: '' }]); + const removeHeader = (i: number) => setHeaders(headers.filter((_, idx) => idx !== i)); + + const updateHeader = (index: number, field: 'key' | 'value', val: string) => { + const newHeaders = [...headers]; + newHeaders[index][field] = val; + setHeaders(newHeaders); + }; + + const sendRequest = async () => { + setIsLoading(true); + const start = Date.now(); + + try { + const headerObj = Object.fromEntries( + headers.filter((h) => h.key && h.value).map((h) => [h.key, h.value]), + ); + + const opts: RequestInit = { + method, + headers: headerObj, + body: method !== 'GET' && method !== 'HEAD' ? body : undefined, + }; + + const res = await fetch(url, opts); + const end = Date.now(); + + const contentType = res.headers.get('content-type'); + const data = contentType?.includes('json') ? await res.json() : await res.text(); + + onResponse({ + status: res.status, + statusText: res.statusText, + headers: Object.fromEntries(res.headers.entries()), + data, + time: end - start, + }); + } catch (err: any) { + onResponse({ + status: 0, + statusText: 'Error', + headers: {}, + data: err.message, + time: Date.now() - start, + }); + } finally { + setIsLoading(false); + } + }; + + const config = JSON.parse(document.getElementById('_config').textContent); + + return ( +
+
+ +
+ + setUrl(e.target.value)} /> +
+
+ +

Headers

+ + {headers.map((h, i) => ( +
+ updateHeader(i, 'key', e.target.value)} + /> + updateHeader(i, 'value', e.target.value)} + /> + +
+ ))} + + + +

Body

+