Skip to content

Commit d32aa7d

Browse files
authored
feat(superdoc/ui): dedicated UI-only entry + bundle audit (SD-2803) (#2999)
* feat(superdoc/ui): dedicated UI-only entry + bundle audit (SD-2803) `superdoc/ui` re-exported from the editor package's root barrel, so consumers pulled the entire editor app shell into their bundle even when they only wanted the UI controller — measured at ~7.7 MB across 11 side-effect chunks, dominated by the 4.4 MB main `src-*.es.js` barrel that carries SuperDoc.vue, the Vue editor app, and friends. Add a dedicated `./ui` exports entry on `@superdoc/super-editor` that points at `src/ui/index.ts` directly, plus a Vite alias so the workspace dev/build resolves it before falling through to the root. Reroute `packages/superdoc/src/{ui.js,ui.d.ts}` to consume the new entry. After: ~3.4 MB across 8 chunks. The 4.4 MB main barrel is gone. The remaining cost comes from the toolbar handle's `twipsToLines` import pulling in `super-converter` + `xml-js` + `jszip`; slimming that chain is a separate refactor. Add a regression guard to `audit-bundle.cjs` that fails the build if `ui.es.js` ever side-effect-imports a chunk matching the main barrel pattern again. * fix(super-editor): emit dist/ui.es.js as runtime target for ./ui (PR #2999 review) The new `./ui` exports entry had only `source` + `types`, which is the established pattern for other workspace-only subpaths (`./markdown`, `./blank-docx`, `./parts-runtime`, `./document-api-adapters`). At runtime the published `superdoc/ui` artifact never resolves through `@superdoc/super-editor/ui` — that path is a build-time alias hop only — so the source+types-only entry was sufficient for the actual consumer surface. Add a real `import` target anyway. Costs nothing, removes ambiguity for any direct consumer of `@superdoc/super-editor`, and matches the shape of `./editor`, `./toolbar`, `./headless-toolbar/*`, etc. which all carry a runtime artifact.
1 parent 26300bf commit d32aa7d

6 files changed

Lines changed: 51 additions & 9 deletions

File tree

packages/super-editor/package.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@
2929
"source": "./src/editors/v1/core/helpers/markdown/index.ts",
3030
"types": "./dist/src/editors/v1/core/helpers/markdown/index.d.ts"
3131
},
32+
"./ui": {
33+
"source": "./src/ui/index.ts",
34+
"types": "./dist/src/ui/index.d.ts",
35+
"import": "./dist/ui.es.js"
36+
},
3237
"./parts-runtime": {
3338
"source": "./src/editors/v1/core/parts/init-parts-runtime.ts",
3439
"types": "./dist/src/editors/v1/core/parts/init-parts-runtime.d.ts"

packages/super-editor/vite.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ export default defineConfig(({ mode }) => {
121121
'headless-toolbar-react': 'src/headless-toolbar/react.ts',
122122
'headless-toolbar-vue': 'src/headless-toolbar/vue.ts',
123123
'super-editor': 'src/index.ts',
124+
'ui': 'src/ui/index.ts',
124125
'types': 'src/types.ts',
125126
'editor': '@core/Editor',
126127
'converter': '@core/super-converter/SuperConverter',

packages/superdoc/scripts/audit-bundle.cjs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,3 +117,39 @@ if (sizeFailed) {
117117
console.error('[audit-bundle] Size budget exceeded — investigate before merging.');
118118
process.exit(1);
119119
}
120+
121+
// `superdoc/ui` is the browser-only UI controller. Importing it must not
122+
// drag in the editor's main barrel (which carries Vue components, SuperDoc
123+
// app shell, etc.). The signal is a side-effect import of the rolldown
124+
// chunk that holds the `superdoc` entry — historically `chunks/src-*.es.js`.
125+
//
126+
// SD-2803: the dedicated `@superdoc/super-editor/ui` entry removed this
127+
// dependency. Guard against regression by checking the emitted `ui.es.js`.
128+
const uiBundlePath = path.join(distRoot, 'ui.es.js');
129+
if (fs.existsSync(uiBundlePath)) {
130+
const uiSource = fs.readFileSync(uiBundlePath, 'utf8');
131+
const importRegex = /import\s+(?:[^"']*\s+from\s+)?["']([^"']+)["']/g;
132+
const violations = [];
133+
let match;
134+
while ((match = importRegex.exec(uiSource)) !== null) {
135+
const importPath = match[1];
136+
// Forbidden chunks: the main superdoc app entry (Vue components,
137+
// SuperDoc.vue), and any chunk whose source maps to the super-editor
138+
// root barrel rather than its `src/ui` sub-tree.
139+
if (/\/chunks\/(src|superdoc|super-editor|main|index)-[A-Za-z0-9_-]+\.es\.js$/.test(importPath)) {
140+
violations.push(importPath);
141+
}
142+
}
143+
if (violations.length > 0) {
144+
console.error(
145+
'[audit-bundle] ✗ ui.es.js side-effect-imports forbidden chunks (regression of SD-2803):',
146+
);
147+
for (const v of violations) console.error(` ${v}`);
148+
console.error(
149+
' The `superdoc/ui` sub-entry must route through `@superdoc/super-editor/ui`,',
150+
);
151+
console.error(' not the package root barrel. See packages/superdoc/src/ui.js.');
152+
process.exit(1);
153+
}
154+
console.log('[audit-bundle] ✓ ui.es.js does not pull in the editor main barrel');
155+
}

packages/superdoc/src/ui.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,4 @@ export {
2525
type ViewportHandle,
2626
type ViewportRect,
2727
type ViewportRectResult,
28-
} from '@superdoc/super-editor';
28+
} from '@superdoc/super-editor/ui';

packages/superdoc/src/ui.js

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
/**
22
* Public sub-entry: `superdoc/ui`
33
*
4-
* Re-exports the browser-only UI controller from
5-
* `@superdoc/super-editor`. A dedicated `@superdoc/super-editor/ui`
6-
* sub-export (with its own Vite build entry) would tighten bundle
7-
* + IDE-resolve hygiene for consumers; tracked as a follow-up. For
8-
* now consumers only pull the two named runtime exports below, so
9-
* tree-shaking already drops the rest at the consumer's bundler
10-
* step.
4+
* Re-exports the browser-only UI controller from the dedicated
5+
* `@superdoc/super-editor/ui` sub-export. This sub-export points at
6+
* `packages/super-editor/src/ui/index.ts` directly, so consumers
7+
* pull only the UI controller and its types — not the editor core,
8+
* SuperConverter, jszip, xml-js, headless-toolbar, etc. that the
9+
* package's main entry transitively imports.
1110
*
1211
* Source: `packages/super-editor/src/ui/`
1312
*/
14-
export { createSuperDocUI, shallowEqual } from '@superdoc/super-editor';
13+
export { createSuperDocUI, shallowEqual } from '@superdoc/super-editor/ui';

packages/superdoc/vite.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,7 @@ export const getAliases = (_isDev) => {
8585
{ find: '@superdoc/super-editor/headless-toolbar/react', replacement: path.resolve(__dirname, '../super-editor/src/headless-toolbar/react.ts') },
8686
{ find: '@superdoc/super-editor/headless-toolbar/vue', replacement: path.resolve(__dirname, '../super-editor/src/headless-toolbar/vue.ts') },
8787
{ find: '@superdoc/super-editor/presentation-editor', replacement: path.resolve(__dirname, '../super-editor/src/index.ts') },
88+
{ find: '@superdoc/super-editor/ui', replacement: path.resolve(__dirname, '../super-editor/src/ui/index.ts') },
8889
{ find: '@superdoc/super-editor', replacement: path.resolve(__dirname, '../super-editor/src/index.ts') },
8990

9091
// Map @superdoc/<name> to ./src/<name> for internal paths

0 commit comments

Comments
 (0)