Skip to content

Commit 7733f37

Browse files
authored
docs(dev): add dev:docs workflow for local SuperDoc development (#2165)
* docs(dev): add dev:docs workflow for local SuperDoc development Enable docs contributors to preview interactive widgets with local source changes. `pnpm dev:docs` starts the Vite dev server, UMD watcher, and Mintlify in parallel. - Refactor SuperDocEditor widget to load from local dev server on localhost - Add serve-dist-for-docs Vite plugin to serve UMD bundle over CORS - Add watch:umd script with debounced rebuilds (300ms) - Document the workflow in apps/docs/README.md * fix: fallback to unpkg bundle when the superdoc dev server is not running * fix: conditionally apply watch buildDelay only for watch mode Set build.watch.buildDelay only when --watch flag is passed via CLI. This prevents 'pnpm build' from hanging in watch mode while still allowing buildDelay to debounce rebuilds in watch:umd and dev:docs. * fix(docs): remove void operator and add proper error handling in superdoc-editor snippet Move error handling into the async boot() function to properly catch and log initialization errors instead of discarding the Promise with void. * fix(docs): add defensive check after script query in superdoc-editor snippet Check if SuperDocLibrary loaded between outer check and DOM query to handle edge case where library finishes loading during synchronous code execution. * fix(docs): add error rejection handling to script load in superdoc-editor snippet
1 parent 07fecd8 commit 7733f37

8 files changed

Lines changed: 159 additions & 24 deletions

File tree

apps/docs/README.md

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,10 +37,26 @@ pnpm dev
3737

3838
## Development
3939

40+
### Developing with local SuperDoc changes
41+
42+
To preview docs with your local SuperDoc source (instead of the published npm version), run from the **repo root**:
43+
44+
```bash
45+
pnpm dev:docs
46+
```
47+
48+
This starts three processes:
49+
50+
- **Vite dev server** (port 9094) — serves the built UMD bundle at `/dist`
51+
- **UMD watcher** — rebuilds `dist/superdoc.umd.js` automatically when source files change
52+
- **Mintlify** (port 3001) — the docs dev server
53+
54+
The `<SuperDocEditor>` widget detects `localhost` and loads SuperDoc from the local Vite server instead of unpkg. After saving a source file, the UMD watcher rebuilds automatically — refresh the docs page to see the changes.
55+
4056
### Available Scripts
4157

42-
- `pnpm dev` - Start Mintlify development server
43-
- `pnpm build` - Build documentation for production
58+
- `pnpm dev` - Start Mintlify development server (uses unpkg, no local changes)
59+
- `pnpm dev:docs` - Start full local dev environment (**run from repo root**)
4460
- `pnpm sync:api` - Sync API documentation from OpenAPI spec
4561
- `pnpm sync:sdk` - Sync SDK documentation from TypeDoc
4662
- `pnpm sync:all` - Sync both API and SDK documentation

apps/docs/snippets/components/superdoc-editor.jsx

Lines changed: 82 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,33 +9,95 @@ export const SuperDocEditor = ({
99
const [ready, setReady] = useState(false);
1010
const editorRef = useRef(null);
1111
const containerIdRef = useRef(`editor-${Math.random().toString(36).substr(2, 9)}`);
12+
const DEV_DIST_URL = 'http://localhost:9094/dist';
13+
const UNPKG_DIST_URL = 'https://unpkg.com/superdoc@latest/dist';
14+
15+
const getBaseUrl = async () => {
16+
const isDev = typeof window !== 'undefined' && window.location.hostname === 'localhost';
17+
18+
if (isDev) {
19+
try {
20+
const res = await fetch(`${DEV_DIST_URL}/superdoc.umd.js`, { method: 'HEAD' });
21+
if (res.ok) return DEV_DIST_URL;
22+
} catch {}
23+
}
24+
25+
return UNPKG_DIST_URL;
26+
};
27+
28+
const ensureStyle = (baseUrl) => {
29+
const styleHref = `${baseUrl}/style.css`;
30+
if (document.querySelector(`link[href="${styleHref}"]`)) return;
1231

13-
useEffect(() => {
1432
const link = document.createElement('link');
1533
link.rel = 'stylesheet';
16-
link.href = 'https://unpkg.com/superdoc@latest/dist/style.css';
34+
link.href = styleHref;
1735
document.head.appendChild(link);
36+
};
1837

19-
const script = document.createElement('script');
20-
script.src = 'https://unpkg.com/superdoc@latest/dist/superdoc.umd.js';
21-
script.onload = () => {
22-
setTimeout(() => {
23-
if (window.SuperDocLibrary) {
24-
editorRef.current = new window.SuperDocLibrary.SuperDoc({
25-
selector: `#${containerIdRef.current}`,
26-
html,
27-
rulers: true,
28-
onReady: () => {
29-
setReady(true);
30-
if (onReady) onReady(editorRef.current);
31-
},
32-
});
33-
}
34-
}, 100);
38+
const loadSuperDocLibrary = (baseUrl) => {
39+
if (window.SuperDocLibrary) return Promise.resolve();
40+
41+
const scriptSrc = `${baseUrl}/superdoc.umd.js`;
42+
const existingScript = document.querySelector(`script[src="${scriptSrc}"]`);
43+
44+
if (existingScript) {
45+
if (window.SuperDocLibrary) return Promise.resolve();
46+
47+
return new Promise((resolve, reject) => {
48+
existingScript.addEventListener('load', resolve, { once: true });
49+
existingScript.addEventListener('error', reject, { once: true });
50+
});
51+
}
52+
53+
return new Promise((resolve, reject) => {
54+
const script = document.createElement('script');
55+
script.src = scriptSrc;
56+
script.onload = resolve;
57+
script.onerror = reject;
58+
document.body.appendChild(script);
59+
});
60+
};
61+
62+
const initEditor = () => {
63+
setTimeout(() => {
64+
if (!window.SuperDocLibrary) return;
65+
if (!document.getElementById(containerIdRef.current)) return;
66+
if (editorRef.current) return;
67+
68+
editorRef.current = new window.SuperDocLibrary.SuperDoc({
69+
selector: `#${containerIdRef.current}`,
70+
html,
71+
rulers: true,
72+
onReady: () => {
73+
setReady(true);
74+
if (onReady) onReady(editorRef.current);
75+
},
76+
});
77+
}, 100);
78+
};
79+
80+
useEffect(() => {
81+
let cancelled = false;
82+
83+
const boot = async () => {
84+
try {
85+
const baseUrl = await getBaseUrl();
86+
ensureStyle(baseUrl);
87+
await loadSuperDocLibrary(baseUrl);
88+
if (!cancelled) initEditor();
89+
} catch (error) {
90+
console.error('Failed to boot SuperDoc:', error);
91+
}
3592
};
36-
document.body.appendChild(script);
3793

38-
return () => editorRef.current?.destroy?.();
94+
boot();
95+
96+
return () => {
97+
cancelled = true;
98+
editorRef.current?.destroy?.();
99+
editorRef.current = null;
100+
};
39101
}, []);
40102

41103
const exportDocx = () => {

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
"dev:super-editor": "pnpm --prefix packages/super-editor run dev",
3333
"dev:collab": "pnpm --prefix packages/superdoc run dev:collab",
3434
"word-benchmark-sidecar": "pnpm --prefix packages/superdoc run word-benchmark-sidecar",
35-
"dev:docs": "pnpm --prefix apps/docs run dev",
35+
"dev:docs": "concurrently -k -n VITE,UMD,DOCS -c cyan,yellow,green \"pnpm --prefix packages/superdoc run dev\" \"pnpm --prefix packages/superdoc run watch:umd\" \"pnpm --prefix apps/docs run dev\"",
3636
"build:superdoc": "pnpm --prefix packages/superdoc run build",
3737
"build:super-editor": "pnpm --prefix packages/super-editor run build",
3838
"build": "pnpm run build:superdoc && pnpm run type-check",
@@ -86,6 +86,7 @@
8686
"devDependencies": {
8787
"@clack/prompts": "^1.0.1",
8888
"@commitlint/cli": "catalog:",
89+
"concurrently": "catalog:",
8990
"@commitlint/config-conventional": "catalog:",
9091
"@eslint/js": "catalog:",
9192
"@semantic-release/changelog": "catalog:",

packages/superdoc/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
"build:es": "vite build && node ./scripts/ensure-types.cjs",
7070
"watch:es": "vite build --watch",
7171
"build:umd": "vite build --config vite.config.umd.js",
72+
"watch:umd": "vite build --watch --config vite.config.umd.js",
7273
"clean": "rm -rf dist",
7374
"pack:local": "pnpm run pack",
7475
"pack": "pnpm run build:es && pnpm pack && mv $(ls superdoc-*.tgz) ./superdoc.tgz",
@@ -99,9 +100,9 @@
99100
"@superdoc/common": "workspace:*",
100101
"@superdoc-dev/superdoc-yjs-collaboration": "workspace:*",
101102
"@superdoc/super-editor": "workspace:*",
102-
"concurrently": "catalog:",
103103
"@vitejs/plugin-vue": "catalog:",
104104
"@vue/test-utils": "catalog:",
105+
"concurrently": "catalog:",
105106
"jszip": "catalog:",
106107
"nodemon": "catalog:",
107108
"pdfjs-dist": "catalog:",
@@ -110,6 +111,7 @@
110111
"prosemirror-model": "catalog:",
111112
"prosemirror-state": "catalog:",
112113
"rollup-plugin-visualizer": "catalog:",
114+
"sirv": "catalog:",
113115
"typescript": "catalog:",
114116
"vite": "catalog:",
115117
"vite-plugin-dts": "catalog:",

packages/superdoc/vite.config.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import path from 'path';
22
import copy from 'rollup-plugin-copy'
33
import dts from 'vite-plugin-dts'
4+
import sirv from 'sirv';
45
import { defineConfig } from 'vite'
56
import { configDefaults } from 'vitest/config'
67
import { createRequire } from 'node:module';
@@ -89,6 +90,21 @@ export default defineConfig(({ mode, command }) => {
8990
hook: 'writeBundle'
9091
}),
9192
// visualizer(visualizerConfig)
93+
{
94+
// Serve dist/ as static files so the docs dev server can load the local UMD build - Development only.
95+
name: 'serve-dist-for-docs',
96+
configureServer(server) {
97+
server.middlewares.use(
98+
'/dist',
99+
sirv(path.resolve(__dirname, 'dist'), {
100+
dev: true,
101+
setHeaders(res) {
102+
res.setHeader('Access-Control-Allow-Origin', '*');
103+
},
104+
}),
105+
);
106+
},
107+
},
92108
].filter(Boolean);
93109
if (mode !== 'test') plugins.push(nodePolyfills());
94110
const isDev = command === 'serve';

packages/superdoc/vite.config.umd.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export default defineConfig(({ command }) => {
1919
conditions: ['source'],
2020
},
2121
build: {
22+
...(process.argv.includes('--watch') && { watch: { buildDelay: 300 } }),
2223
emptyOutDir: false,
2324
target: 'es2022',
2425
cssCodeSplit: false,

pnpm-lock.yaml

Lines changed: 36 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pnpm-workspace.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ catalog:
9898
semantic-release: ^24.2.7
9999
semantic-release-commit-filter: ^1.0.2
100100
semantic-release-linear-app: ^0.7.1
101+
sirv: ^3.0.2
101102
tippy.js: ^6.3.7
102103
tsup: ^8.5.1
103104
tsx: ^4.20.6

0 commit comments

Comments
 (0)