Skip to content

Commit d074039

Browse files
committed
feat(superdoc): ship IIFE CDN bundle with working vanilla example
Switch the CDN-facing build from UMD to IIFE, minify it, expose `window.SuperDoc` as the class directly, and verify end-to-end with a real DOCX rendering in the existing CDN example. - Rename vite.config.umd.js to vite.config.cdn.js; format=iife, name=SuperDoc, esbuild minify. Bundle drops from 8.8 MB unminified to 4.9 MB minified (1.46 MB gzipped). - Add src/cdn-entry.js so `window.SuperDoc` is the constructor and named exports attach as static properties (Quill pattern). - Inline yjs + hocuspocus into the IIFE (ESM-only — can't be loaded as script-tag globals). pdfjs-dist stays external; PDF viewing is documented as ESM + import-map only. - Flip package.json "main" from the fat UMD to the proper CJS build, add ./global subpath export plus unpkg/jsdelivr fields. Keep superdoc.umd.js for one minor cycle as a deprecation-warning shim via scripts/emit-umd-alias.cjs; removal scheduled for 2.0. - Extend scripts/audit-bundle.cjs with size budgets (global.js 5/6 MB, es.js 3/4 MB, style.css 150/200 KB). - Rework examples/getting-started/cdn/ to load the locally built bundle via a prepare step, auto-mount a bundled test_file.docx, and include jsdelivr snippets in the README. - Rename tests/umd-smoke -> tests/cdn-smoke; add a second HTML page + Playwright test asserting the deprecation warning fires on the old filename. Extend examples/__tests__/smoke.spec.ts with cdn-specific assertions (window.SuperDoc shape, real DOCX renders content). - Add CDN quickstart to packages/superdoc/AGENTS.md with script-tag and ESM + import-map copy-paste blocks and SRI guidance.
1 parent 45f1a11 commit d074039

21 files changed

Lines changed: 344 additions & 59 deletions

.github/workflows/ci-superdoc.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -171,13 +171,13 @@ jobs:
171171
if: matrix.name == 'other-packages'
172172
run: pnpm test:slow
173173

174-
- name: Install Playwright for UMD smoke test
174+
- name: Install Playwright for CDN smoke test
175175
if: matrix.name == 'other-packages'
176-
run: pnpm --filter @superdoc/umd-smoke-test exec playwright install --with-deps chromium
176+
run: pnpm --filter @superdoc/cdn-smoke-test exec playwright install --with-deps chromium
177177

178-
- name: Run UMD smoke test
178+
- name: Run CDN smoke test
179179
if: matrix.name == 'other-packages'
180-
working-directory: packages/superdoc/tests/umd-smoke
180+
working-directory: packages/superdoc/tests/cdn-smoke
181181
run: pnpm test
182182

183183
cli-tests:

examples/__tests__/playwright.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ const isLaravel = example === 'laravel';
4646
// Start command
4747
const isCdn = example === 'cdn';
4848
const command = isCdn
49-
? `npx serve ${examplePath} -l ${port}`
49+
? `pnpm --dir ${examplePath} run prepare && npx serve ${examplePath} -l ${port}`
5050
: isLaravel
5151
? `${run} start`
5252
: useConcurrently.includes(example)

examples/__tests__/smoke.spec.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { test, expect } from '@playwright/test';
22

3+
const example = process.env.EXAMPLE || 'react';
4+
35
test('example loads without errors', async ({ page }) => {
46
const errors: string[] = [];
57

@@ -26,3 +28,35 @@ test('example loads without errors', async ({ page }) => {
2628

2729
expect(errors).toEqual([]);
2830
});
31+
32+
test.describe('cdn example', () => {
33+
test.skip(example !== 'cdn', 'cdn-specific assertions');
34+
35+
test('window.SuperDoc is a constructor and the bundled sample renders', async ({ page }) => {
36+
await page.route('**/ingest.superdoc.dev/**', (route) => route.abort());
37+
await page.goto('/');
38+
39+
const globalShape = await page.evaluate(() => ({
40+
isFunction: typeof (window as any).SuperDoc === 'function',
41+
hasCreateTheme: typeof (window as any).SuperDoc?.createTheme === 'function',
42+
hasDOCX: typeof (window as any).SuperDoc?.DOCX !== 'undefined',
43+
}));
44+
expect(globalShape).toEqual({ isFunction: true, hasCreateTheme: true, hasDOCX: true });
45+
46+
await page.waitForFunction(() => (window as any).__SUPERDOC_READY__ === true, null, {
47+
timeout: 15_000,
48+
});
49+
50+
const rendered = await page.evaluate(() => {
51+
const el = document.querySelector('#editor');
52+
return {
53+
hasChildren: (el?.children.length || 0) > 0,
54+
innerHTMLLength: el?.innerHTML.length || 0,
55+
visibleText: (el as HTMLElement)?.innerText?.trim().slice(0, 200) || '',
56+
};
57+
});
58+
expect(rendered.hasChildren).toBe(true);
59+
expect(rendered.innerHTMLLength).toBeGreaterThan(1000);
60+
expect(rendered.visibleText.length).toBeGreaterThan(0);
61+
});
62+
});
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
superdoc.global.js
2+
style.css
3+
test_file.docx

examples/getting-started/cdn/README.md

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,27 @@
11
# SuperDoc — CDN
22

3-
Zero build tools. Open `index.html` in a browser or serve with any static server.
3+
Zero build tools. A single HTML file plus the SuperDoc global bundle.
44

5-
## Run
5+
## Run locally
66

77
```bash
8+
pnpm prepare
89
npx serve .
910
```
1011

12+
`pnpm prepare` copies the built `superdoc.global.js`, `style.css`, and a sample `test_file.docx` into this directory so the example is self-contained.
13+
14+
## Use from the public CDN
15+
16+
Replace the local `<script>` and `<link>` with jsDelivr URLs:
17+
18+
```html
19+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/superdoc@1.27/dist/style.css" />
20+
<script src="https://cdn.jsdelivr.net/npm/superdoc@1.27/dist/superdoc.global.js"></script>
21+
```
22+
23+
Pin to a minor (`@1.27`) in production and add [SRI hashes](https://developer.mozilla.org/docs/Web/Security/Subresource_Integrity) for integrity.
24+
1125
## Learn more
1226

1327
- [Vanilla JS Guide](https://docs.superdoc.dev/getting-started/frameworks/vanilla-js)

examples/getting-started/cdn/index.html

Lines changed: 28 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,32 +4,42 @@
44
<meta charset="UTF-8" />
55
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
66
<title>SuperDoc — CDN</title>
7-
<link
8-
href="https://cdn.jsdelivr.net/npm/superdoc/dist/style.css"
9-
rel="stylesheet"
10-
/>
11-
<script src="https://cdn.jsdelivr.net/npm/superdoc/dist/superdoc.umd.js"></script>
7+
<link href="./style.css" rel="stylesheet" />
8+
<script src="./superdoc.global.js"></script>
9+
<style>
10+
body { margin: 0; font-family: system-ui, sans-serif; }
11+
#controls { padding: .5rem 1rem; background: #f1f5f9; border-bottom: 1px solid #e2e8f0; display: flex; gap: .5rem; align-items: center; }
12+
#toolbar { border-bottom: 1px solid #e2e8f0; }
13+
#editor { height: calc(100vh - 92px); }
14+
</style>
1215
</head>
1316
<body>
14-
<div style="padding: 1rem; background: #f5f5f5">
17+
<div id="controls">
1518
<input type="file" id="file-input" accept=".docx" />
19+
<span style="color:#64748b;font-size:.875rem">or open the bundled sample</span>
1620
</div>
17-
<div id="editor" style="height: calc(100vh - 60px)"></div>
21+
<div id="toolbar"></div>
22+
<div id="editor"></div>
1823

1924
<script>
20-
var superdoc = new SuperDocLibrary.SuperDoc({
21-
selector: '#editor',
22-
});
23-
24-
document.getElementById('file-input').addEventListener('change', function (e) {
25-
var file = e.target.files[0];
26-
if (!file) return;
27-
28-
if (superdoc) superdoc.destroy();
29-
superdoc = new SuperDocLibrary.SuperDoc({
25+
function mount(source) {
26+
if (window.__sd) window.__sd.destroy();
27+
window.__sd = new SuperDoc({
3028
selector: '#editor',
31-
document: file,
29+
toolbar: '#toolbar',
30+
document: source,
31+
documentMode: 'editing',
32+
user: { name: 'Guest', email: 'guest@example.com' },
33+
onReady: () => (window.__SUPERDOC_READY__ = true),
3234
});
35+
}
36+
37+
// Load the bundled sample on first paint so the page shows a real editor.
38+
mount('./test_file.docx');
39+
40+
document.getElementById('file-input').addEventListener('change', function (e) {
41+
const file = e.target.files[0];
42+
if (file) mount(file);
3343
});
3444
</script>
3545
</body>
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"name": "@superdoc-example/cdn",
3+
"private": true,
4+
"scripts": {
5+
"prepare": "node ./prepare.mjs",
6+
"dev": "pnpm run prepare && npx serve . -l 3000",
7+
"test": "EXAMPLE=cdn playwright test --config ../../__tests__/playwright.config.ts"
8+
}
9+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Copies the locally built CDN bundle + a sample DOCX into this example dir
2+
// so `index.html` is self-contained and can be served with `npx serve .`.
3+
// Run before `dev` or the Playwright smoke test.
4+
5+
import { copyFileSync, existsSync, mkdirSync } from 'node:fs';
6+
import { dirname, resolve } from 'node:path';
7+
import { fileURLToPath } from 'node:url';
8+
9+
const here = dirname(fileURLToPath(import.meta.url));
10+
const dist = resolve(here, '../../../packages/superdoc/dist');
11+
const sampleSource = resolve(
12+
here,
13+
'../../advanced/headless-toolbar/vanilla/public/test_file.docx',
14+
);
15+
16+
const assets = [
17+
[resolve(dist, 'superdoc.global.js'), resolve(here, 'superdoc.global.js')],
18+
[resolve(dist, 'style.css'), resolve(here, 'style.css')],
19+
[sampleSource, resolve(here, 'test_file.docx')],
20+
];
21+
22+
const missing = assets.filter(([src]) => !existsSync(src)).map(([src]) => src);
23+
if (missing.length) {
24+
console.error('[cdn-example/prepare] Missing sources:\n ' + missing.join('\n '));
25+
console.error('Run `pnpm --filter superdoc build` first.');
26+
process.exit(1);
27+
}
28+
29+
if (!existsSync(here)) mkdirSync(here, { recursive: true });
30+
for (const [src, dst] of assets) {
31+
copyFileSync(src, dst);
32+
console.log('[cdn-example/prepare] copied', dst.replace(here + '/', ''));
33+
}

packages/superdoc/AGENTS.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,64 @@ npm install @superdoc-dev/react # React (includes superdoc)
2929
</script>
3030
```
3131

32+
## Embed editor — CDN (no build step)
33+
34+
Drop SuperDoc into any HTML page via `<script>` tag. No bundler, no `npm install`. Served from jsDelivr.
35+
36+
### Script tag (global)
37+
38+
```html
39+
<link
40+
rel="stylesheet"
41+
href="https://cdn.jsdelivr.net/npm/superdoc@1.27/dist/style.css"
42+
/>
43+
<div id="editor" style="height: 100vh"></div>
44+
<script src="https://cdn.jsdelivr.net/npm/superdoc@1.27/dist/superdoc.global.js"></script>
45+
<script>
46+
const superdoc = new SuperDoc({
47+
selector: '#editor',
48+
document: '/path/to/file.docx',
49+
documentMode: 'editing',
50+
});
51+
</script>
52+
```
53+
54+
`window.SuperDoc` is the class directly. Named exports are attached as static properties (`SuperDoc.createTheme`, `SuperDoc.DOCX`, etc.). Collaboration (Yjs) and PDF viewing peers are **not** bundled — use the ESM path below if you need them.
55+
56+
### ES modules + import map
57+
58+
For modern apps that want peer-dep control and smaller payload:
59+
60+
```html
61+
<link
62+
rel="stylesheet"
63+
href="https://cdn.jsdelivr.net/npm/superdoc@1.27/dist/style.css"
64+
/>
65+
<script type="importmap">
66+
{
67+
"imports": {
68+
"superdoc": "https://cdn.jsdelivr.net/npm/superdoc@1.27/dist/superdoc.es.js",
69+
"vue": "https://cdn.jsdelivr.net/npm/vue@3/dist/vue.esm-browser.prod.js"
70+
}
71+
}
72+
</script>
73+
<div id="editor" style="height: 100vh"></div>
74+
<script type="module">
75+
import { SuperDoc } from 'superdoc';
76+
new SuperDoc({ selector: '#editor', document: '/path/to/file.docx' });
77+
</script>
78+
```
79+
80+
Add `yjs`, `y-prosemirror`, `@hocuspocus/provider`, or `pdfjs-dist` to the import map if your build needs them.
81+
82+
### Production pinning and integrity
83+
84+
- **Pin to a minor** (`@1.27`) for patch updates, or **exact** (`@1.27.0`) if you're hash-pinning.
85+
- Add [SRI hashes](https://developer.mozilla.org/docs/Web/Security/Subresource_Integrity) for production: `openssl dgst -sha384 -binary superdoc.global.js | openssl base64 -A`. Include `integrity="sha384-..." crossorigin="anonymous"` on each `<script>` / `<link>`.
86+
- jsDelivr serves immutable, gzipped responses (~1.4 MB on the wire for `superdoc.global.js`).
87+
88+
Unpkg is mirrored automatically: replace `cdn.jsdelivr.net/npm/` with `unpkg.com/`.
89+
3290
## Embed editor — React
3391

3492
```tsx

packages/superdoc/package.json

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,8 @@
6060
"./file-zipper": {
6161
"import": "./dist/super-editor/file-zipper.es.js"
6262
},
63+
"./global": "./dist/superdoc.global.js",
64+
"./global.js": "./dist/superdoc.global.js",
6365
"./style.css": "./dist/style.css"
6466
},
6567
"types": "./dist/superdoc/src/index.d.ts",
@@ -82,20 +84,22 @@
8284
]
8385
}
8486
},
85-
"main": "./dist/superdoc.umd.js",
87+
"main": "./dist/superdoc.cjs",
8688
"module": "./dist/superdoc.es.js",
89+
"unpkg": "./dist/superdoc.global.js",
90+
"jsdelivr": "./dist/superdoc.global.js",
8791
"scripts": {
8892
"dev": "concurrently -k -n VITE,WORD -c cyan,magenta \"vite\" \"node ../../devtools/word-benchmark-sidecar/server.js --stay-alive-on-reuse\"",
8993
"dev:collab": "concurrently -k -n VITE,COLLAB,WORD -c cyan,green,magenta \"vite\" \"pnpm run collab-server\" \"node ../../devtools/word-benchmark-sidecar/server.js --stay-alive-on-reuse\"",
9094
"collab-server": "pnpm --filter @superdoc-dev/superdoc-yjs-collaboration build && tsx src/dev/collab-server.ts",
9195
"word-benchmark-sidecar": "node ../../devtools/word-benchmark-sidecar/server.js",
92-
"build": "vite build && pnpm run build:umd",
96+
"build": "vite build && pnpm run build:cdn",
9397
"build:dev": "SUPERDOC_SKIP_DTS=1 vite build",
9498
"postbuild": "node ./scripts/ensure-types.cjs && node ./scripts/audit-bundle.cjs",
9599
"build:es": "vite build && pnpm run postbuild",
96100
"watch:es": "vite build --watch",
97-
"build:umd": "vite build --config vite.config.umd.js",
98-
"watch:umd": "vite build --watch --config vite.config.umd.js",
101+
"build:cdn": "vite build --config vite.config.cdn.js && node ./scripts/emit-umd-alias.cjs",
102+
"watch:cdn": "vite build --watch --config vite.config.cdn.js",
99103
"clean": "rm -rf dist",
100104
"pack:local": "pnpm run pack",
101105
"pack": "pnpm run build:es && pnpm pack && mv $(ls superdoc-*.tgz) ./superdoc.tgz",

0 commit comments

Comments
 (0)