Skip to content

Commit 76a7876

Browse files
authored
feat(superdoc): ship IIFE CDN bundle with working vanilla example (#2835)
* 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. * refactor(superdoc): drop UMD deprecation shim The old superdoc.umd.js was broken for browser use (wrong external globals, ReferenceError on load). No deprecation window needed for something that didn't work. Remove emit-umd-alias.cjs, the UMD alias test, and the build:cdn pipe to it. Fix AGENTS.md to note that Yjs is inlined (only pdfjs-dist stays external). * docs: switch CDN examples to superdoc.global.js Update docs to use the new global bundle: - Live <SuperDocEditor> and <DocCounter> components (embedded in Mintlify pages) now load superdoc.global.js and use window.SuperDoc instead of the removed SuperDocLibrary namespace. - vanilla-js.mdx and quickstart.mdx lead with the <script> tag approach; ES modules + import map kept as the secondary option. - Swap unpkg URLs for jsdelivr. * refactor(superdoc): rename superdoc.global.js to superdoc.min.js The `.global.js` suffix was borrowed from Vue's build convention, but SuperDoc is framework-agnostic to consumers — we shouldn't default internal conventions to Vue. `.min.js` is the widely-understood name used by Quill, TinyMCE, lodash, jQuery, and Three.js. - Rename output file and all references across vite config, audit script, package.json exports (including ./min subpath), smoke tests, CDN example, AGENTS.md, and live docs components. - Tidy `loadSuperDocLibrary` -> `loadSuperDoc` in the live Mintlify SuperDocEditor component. * fix(superdoc): address review feedback on CDN bundle Round-1 review surfaced one blocker and several real issues. All verified before changing. Blocker: - Regenerate pnpm-lock.yaml — the cdn-smoke rename left the lockfile pointing at the old umd-smoke entry, so `pnpm install --frozen-lockfile` failed in CI before the build step. Smoke test was a false-positive engine: - `tests/cdn-smoke/index.html` set `__SUPERDOC_READY__` synchronously on the line after `new SuperDoc(...)`, so async init failures still passed the test. Move the flag into `onReady` and switch the test to `page.waitForFunction`, matching the CDN-example pattern. - Strengthen the CDN example assertion to check for "Lorem ipsum" from the bundled sample DOCX, so editor chrome alone can't satisfy it. Docs / packaging cleanup: - Drop the `./min` and `./min.js` subpath exports from package.json — they advertised an IIFE as an importable module, but `require()` returns `{}`. The `unpkg`/`jsdelivr` fields cover real CDN delivery. - Replace `@1.27` pins (the package is at 1.26.0) with `@latest` in AGENTS.md and the cdn example README; document version pinning in the production-pinning section instead. - Make Quickstart "Mount the editor" step tab-aware so a CDN reader doesn't fall into a `<script type="module">` snippet they can't run. - Update apps/docs/README.md and root `dev:docs` script — both still referenced the removed `watch:umd` / UMD wording. Hardening: - Audit-bundle now fails loudly if `superdoc.min.js`, `superdoc.es.js`, or `style.css` is missing after a full build instead of silently skipping the size check. - prepare.mjs leads with the fix command before the missing-file list, drops the dead `mkdirSync` guard. * fix(superdoc): rename cdn example prepare→setup to avoid pnpm lifecycle hook `prepare` is a reserved npm/pnpm lifecycle script — it auto-runs after every `pnpm install`. The cdn example's `prepare` copies built artifacts from `packages/superdoc/dist/`, but those don't exist at install time on a fresh clone (or in CI before the build step), so install fails with exit 1 before anything can build. Rename to `setup` so it only runs when explicitly invoked by `pnpm dev` or the Playwright config. * fix(superdoc): invoke cdn example setup via node directly The CI `getting-started` job restores a cached workspace but doesn't install pnpm on PATH, so Playwright's webServer command `pnpm --dir ... run setup` exits with "pnpm: not found". Other examples avoid this because their runner falls back to `npm run --prefix` when local node_modules exist. Call `node setup.mjs` directly — no package manager dependency, no script-name coupling, works identically in dev and CI. * fix(superdoc): don't fail audit-bundle on partial builds The missing-file gate I added in round-2 of review broke the SDK validation pipeline, which runs `build:es` (producing superdoc.es.js only, not superdoc.min.js). The gate assumed "es.js present = full build, so min.js must exist too" — wrong. `build:cdn` fails loudly on its own if the CDN bundle is broken, so a second gate here was double-counting. Revert to the plain `continue` behaviour for missing files. Size budgets still enforce hard failures when files ARE present and too big. * test(superdoc): unit test cdn-entry namespace attachment Codecov flagged src/cdn-entry.js as 0% covered because its only exercisers are the Playwright smoke tests, which Codecov doesn't see. The round-1 review also asked for a direct test of the attach loop — if a named export silently drops due to a Function intrinsic collision, the smoke test's spot-checks (createTheme, DOCX) wouldn't catch an unrelated export going missing. Three checks: default export is the SuperDoc constructor, every non-default/non-SuperDoc named export from index.js lands on the class as a static property, and Function intrinsics (name, prototype) aren't clobbered. * fix: update demos and tighten cdn-entry test for round-2 review Final review flagged three loose ends before merge: 1. In-repo demos still referenced the removed `superdoc.umd.js` and `window.SuperDocLibrary`. They passed CI because `@latest` on unpkg/jsDelivr still resolves to the pre-merge 1.26.0 tarball, but they'd 404 and `ReferenceError` the moment a newer release lands without those names. Fixed: - `demos/cdn/index.html` — jsDelivr URL, `<script>` (not module), `new SuperDoc(...)`. - `demos/word-addin/server/public/{editor.html,editor.js}` — same treatment, plus swapped the nonsensical pinned `superdoc@.21.0-next.1` for the current package alias. - `demos/chrome-extension/` — replaced the vendored 4.8 MB `lib/superdoc.umd.js` with a fresh `lib/superdoc.min.js`, updated manifest + content.js + README to match. All `SuperDocLibrary.*` calls (and a couple of error strings) now reference `SuperDoc`. 2. `cdn-entry.test.js` was checking `!== undefined` for each attached static, which a stale wrapper or wrong-identity re-export would silently pass. Assert `SuperDoc[key] === namespace[key]` instead, and add a check that `SuperDoc.SuperDoc` / `SuperDoc.default` don't leak through the attach loop. 3. PR body still claimed the old `superdoc.umd.js` "still works for now" — that was true when the shim existed; it was dropped in `91725cd0a`. Updated the PR body separately to reflect the actual shipped migration story. * fix(demo): use local build in demos/cdn so smoke test passes pre-release Pointing the CDN demo at jsDelivr @latest 404s until a superdoc release containing superdoc.min.js publishes — 1.26.0 only has superdoc.umd.js. CI's DEMO=cdn smoke test caught this (three console errors from the missing script and stylesheet). Mirror the pattern from examples/getting-started/cdn: add setup.mjs that copies the locally built bundle + style.css into the demo dir, point index.html at the local copies, and keep the jsDelivr URLs as an inline comment + in the README as the production recipe. Verified locally: DEMO=cdn playwright test passes (1/1).
1 parent 84b450c commit 76a7876

40 files changed

Lines changed: 1480 additions & 99027 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:

apps/docs/README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,11 @@ pnpm dev:docs
4747

4848
This starts three processes:
4949

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
50+
- **Vite dev server** (port 9094) — serves the built CDN bundle at `/dist`
51+
- **CDN watcher** — rebuilds `dist/superdoc.min.js` automatically when source files change
5252
- **Mintlify** (port 3001) — the docs dev server
5353

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.
54+
The `<SuperDocEditor>` widget detects `localhost` and loads SuperDoc from the local Vite server instead of jsDelivr. After saving a source file, the CDN watcher rebuilds automatically — refresh the docs page to see the changes.
5555

5656
### Available Scripts
5757

apps/docs/getting-started/frameworks/vanilla-js.mdx

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ new SuperDoc({
9696
<head>
9797
<link href="https://cdn.jsdelivr.net/npm/superdoc/dist/style.css"
9898
rel="stylesheet">
99+
<script src="https://cdn.jsdelivr.net/npm/superdoc/dist/superdoc.min.js"></script>
99100
</head>
100101
<body>
101102
<div class="controls">
@@ -107,9 +108,7 @@ new SuperDoc({
107108

108109
<div id="editor" style="height: 700px"></div>
109110

110-
<script type="module">
111-
import { SuperDoc } from 'https://cdn.jsdelivr.net/npm/superdoc/dist/superdoc.es.js';
112-
111+
<script>
113112
let superdoc = null;
114113
115114
document.getElementById('file-input').addEventListener('change', (e) => {

apps/docs/getting-started/quickstart.mdx

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -123,27 +123,41 @@ Done. Ask your agent to open a `.docx` file and make an edit.
123123
<Tab title="CDN">
124124
```html
125125
<link href="https://cdn.jsdelivr.net/npm/superdoc/dist/style.css" rel="stylesheet">
126-
<script type="module">
127-
import { SuperDoc } from 'https://cdn.jsdelivr.net/npm/superdoc/dist/superdoc.es.js';
128-
</script>
126+
<script src="https://cdn.jsdelivr.net/npm/superdoc/dist/superdoc.min.js"></script>
129127
```
128+
`SuperDoc` becomes a global — use `new SuperDoc({...})` directly.
130129
</Tab>
131130
</Tabs>
132131
</Step>
133132

134133
<Step title="Mount the editor">
135-
```html
136-
<div id="editor" style="height: 100vh"></div>
134+
<Tabs>
135+
<Tab title="npm">
136+
```html
137+
<div id="editor" style="height: 100vh"></div>
137138

138-
<script type="module">
139-
import { SuperDoc } from 'superdoc';
140-
import 'superdoc/style.css';
139+
<script type="module">
140+
import { SuperDoc } from 'superdoc';
141+
import 'superdoc/style.css';
141142
142-
const superdoc = new SuperDoc({
143-
selector: '#editor',
144-
});
145-
</script>
146-
```
143+
const superdoc = new SuperDoc({
144+
selector: '#editor',
145+
});
146+
</script>
147+
```
148+
</Tab>
149+
<Tab title="CDN">
150+
```html
151+
<div id="editor" style="height: 100vh"></div>
152+
153+
<script>
154+
const superdoc = new SuperDoc({
155+
selector: '#editor',
156+
});
157+
</script>
158+
```
159+
</Tab>
160+
</Tabs>
147161
That's a blank editor. Now give it a file.
148162
</Step>
149163

apps/docs/snippets/components/doc-counter.jsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,17 @@ export const DocCounter = ({ height = '350px' }) => {
1313
useEffect(() => {
1414
const link = document.createElement('link');
1515
link.rel = 'stylesheet';
16-
link.href = 'https://unpkg.com/superdoc@latest/dist/style.css';
16+
link.href = 'https://cdn.jsdelivr.net/npm/superdoc@latest/dist/style.css';
1717
document.head.appendChild(link);
1818

19-
// Buffer polyfill — required by the UMD build for document hashing
19+
// Buffer polyfill — required for document hashing
2020
const bufferScript = document.createElement('script');
2121
bufferScript.src = 'https://cdn.jsdelivr.net/npm/buffer@6/index.min.js';
2222
bufferScript.onload = () => {
2323
window.Buffer = window.buffer.Buffer;
2424

2525
const script = document.createElement('script');
26-
script.src = 'https://unpkg.com/superdoc@latest/dist/superdoc.umd.js';
26+
script.src = 'https://cdn.jsdelivr.net/npm/superdoc@latest/dist/superdoc.min.js';
2727
script.onload = () => setTimeout(() => initializeSuperdoc(), 100);
2828
document.body.appendChild(script);
2929
};
@@ -95,8 +95,8 @@ export const DocCounter = ({ height = '350px' }) => {
9595
config.html = '<p>Upload a DOCX file to see how document counting works.</p>';
9696
}
9797

98-
if (window.SuperDocLibrary) {
99-
superdocRef.current = new window.SuperDocLibrary.SuperDoc(config);
98+
if (window.SuperDoc) {
99+
superdocRef.current = new window.SuperDoc(config);
100100
}
101101
};
102102

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

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export const SuperDocEditor = ({
1717

1818
if (isDev) {
1919
try {
20-
const res = await fetch(`${DEV_DIST_URL}/superdoc.umd.js`, { method: 'HEAD' });
20+
const res = await fetch(`${DEV_DIST_URL}/superdoc.min.js`, { method: 'HEAD' });
2121
if (res.ok) {
2222
console.info('[SuperDoc Docs] Using local build from', DEV_DIST_URL);
2323
return DEV_DIST_URL;
@@ -45,14 +45,14 @@ export const SuperDocEditor = ({
4545
document.head.appendChild(link);
4646
};
4747

48-
const loadSuperDocLibrary = (baseUrl) => {
49-
if (window.SuperDocLibrary) return Promise.resolve();
48+
const loadSuperDoc = (baseUrl) => {
49+
if (window.SuperDoc) return Promise.resolve();
5050

51-
const scriptSrc = `${baseUrl}/superdoc.umd.js`;
51+
const scriptSrc = `${baseUrl}/superdoc.min.js`;
5252
const existingScript = document.querySelector(`script[src="${scriptSrc}"]`);
5353

5454
if (existingScript) {
55-
if (window.SuperDocLibrary) return Promise.resolve();
55+
if (window.SuperDoc) return Promise.resolve();
5656

5757
return new Promise((resolve, reject) => {
5858
existingScript.addEventListener('load', resolve, { once: true });
@@ -71,11 +71,11 @@ export const SuperDocEditor = ({
7171

7272
const initEditor = () => {
7373
setTimeout(() => {
74-
if (!window.SuperDocLibrary) return;
74+
if (!window.SuperDoc) return;
7575
if (!document.getElementById(containerIdRef.current)) return;
7676
if (editorRef.current) return;
7777

78-
editorRef.current = new window.SuperDocLibrary.SuperDoc({
78+
editorRef.current = new window.SuperDoc({
7979
selector: `#${containerIdRef.current}`,
8080
html,
8181
rulers: true,
@@ -95,7 +95,7 @@ export const SuperDocEditor = ({
9595
try {
9696
const baseUrl = await getBaseUrl();
9797
ensureStyle(baseUrl);
98-
await loadSuperDocLibrary(baseUrl);
98+
await loadSuperDoc(baseUrl);
9999
if (!cancelled) initEditor();
100100
} catch (error) {
101101
console.error('Failed to boot SuperDoc:', error);

demos/cdn/.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
superdoc.min.js
2+
style.css

demos/cdn/index.html

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
11
<!DOCTYPE html>
22
<html lang="en">
33
<head>
4-
<link rel="stylesheet" href="https://unpkg.com/superdoc/dist/style.css">
5-
<script type="module" src="https://unpkg.com/superdoc/dist/superdoc.umd.js"></script>
4+
<!--
5+
For production use, load from jsDelivr and pin a version:
6+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/superdoc/dist/style.css">
7+
<script src="https://cdn.jsdelivr.net/npm/superdoc/dist/superdoc.min.js"></script>
8+
This demo uses local copies (populated by setup.mjs) so it runs against
9+
in-tree builds in CI before a release is published.
10+
-->
11+
<link rel="stylesheet" href="./style.css">
12+
<script src="./superdoc.min.js"></script>
613
<link rel="stylesheet" href="./file-upload.css">
714
<meta charset="UTF-8">
815
<meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -28,7 +35,7 @@
2835

2936
<div id="superdoc"></div>
3037

31-
<script type="module">
38+
<script>
3239
const config = {
3340
selector: '#superdoc',
3441
toolbar: '#my-toolbar',
@@ -44,10 +51,10 @@
4451
},
4552
};
4653

47-
const superdoc = new SuperDocLibrary.SuperDoc(config);
54+
let superdoc = new SuperDoc(config);
4855

4956
window.addEventListener('file-upload', (event) => {
50-
new SuperDocLibrary.SuperDoc({...config, document: event.detail});
57+
superdoc = new SuperDoc({ ...config, document: event.detail });
5158
});
5259
</script>
5360
<script type="module" src="./file-upload.js"></script>

demos/cdn/package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
"description": "This is a very basic example of loading SuperDoc from CDN without any bundlers.",
55
"main": "index.js",
66
"scripts": {
7-
"dev": "http-server",
8-
"start": "http-server",
7+
"dev": "node ./setup.mjs && http-server",
8+
"start": "node ./setup.mjs && http-server",
99
"test": "echo \"Error: no test specified\" && exit 1"
1010
},
1111
"keywords": [],

demos/cdn/setup.mjs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
// Copies the locally built CDN bundle into this demo dir so `index.html` is
2+
// self-contained and can be served with `http-server`. The README still shows
3+
// the jsDelivr URLs as the recommended production recipe.
4+
5+
import { copyFileSync, existsSync } 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+
12+
const assets = [
13+
[resolve(dist, 'superdoc.min.js'), resolve(here, 'superdoc.min.js')],
14+
[resolve(dist, 'style.css'), resolve(here, 'style.css')],
15+
];
16+
17+
const missing = assets.filter(([src]) => !existsSync(src));
18+
if (missing.length) {
19+
console.error('[cdn-demo/setup] Build the SuperDoc bundle first:');
20+
console.error(' pnpm --filter superdoc build');
21+
console.error('Missing files:');
22+
for (const [src] of missing) console.error(' ' + src);
23+
process.exit(1);
24+
}
25+
26+
for (const [src, dst] of assets) {
27+
copyFileSync(src, dst);
28+
console.log('[cdn-demo/setup] copied', dst.replace(here + '/', ''));
29+
}

0 commit comments

Comments
 (0)