Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .github/workflows/code-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,20 @@ jobs:
- name: Main ESlint check
run: pnpm lint

vite-optimize-deps:
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
name: Vite optimizeDeps audit
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- name: Set up Node.js environment
uses: ./.github/actions/node_env_setup
with:
node-version: ${{ env.node-version }}

- name: Check optimizeDeps declarations are up to date
run: pnpm check:vite-optimize-deps

stylelint:
if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name
name: Stylelint ${{ matrix.name }}
Expand Down
1 change: 1 addition & 0 deletions apps/aurora/news/+vite-optimize-deps-ssr-exclude.internal
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Excluded `i18next-fs-backend` and `remix-i18next/server` from the client `optimizeDeps` bundle — they were still being picked up by Vite's client-side dependency scanner despite only being declared in `ssr.optimizeDeps.include`. @sneridagh
1 change: 1 addition & 0 deletions apps/aurora/news/+vite-optimize-deps.internal
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added `optimizeDeps.include` entries for non-addon workspace packages and app-level deps to reduce lazy dependency discovery reloads on dev server startup. @arybakov05
46 changes: 46 additions & 0 deletions apps/aurora/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,52 @@ export default defineConfig(({ command, mode, isSsrBuild }) => {
]
: []),
] as PluginOption[],
optimizeDeps: {
// Server-only deps that Vite would otherwise still pick up for the
// client bundle — keep in sync with ssr.optimizeDeps.include below
exclude: [
'i18next-fs-backend',
'i18next-fs-backend/cjs',
'remix-i18next/server',
],
include: [
// App-level deps (in apps/aurora/package.json)
'i18next',
'i18next-browser-languagedetector',
'i18next-http-backend',
Comment thread
sneridagh marked this conversation as resolved.
'react-i18next',
// Injected by babel-plugin-react-compiler, not in any package.json
'react/compiler-runtime',
'remix-i18next/client',
'remix-i18next/react',
// @plone/components and @plone/helpers are not registered add-ons, so
// their deps can't be declared in vite.extend.js — list them here
'@plone/components > @internationalized/date',
'@plone/components > @react-aria/utils',
'@plone/components > @react-spectrum/utils',
'@plone/components > clsx',
'@plone/components > react-aria',
'@plone/components > react-aria-components',
'@plone/components > react-aria-components/DropZone',
'@plone/components > react-aria-components/Form',
'@plone/components > react-aria-components/Group',
'@plone/components > react-aria-components/Modal',
'@plone/components > react-aria-components/Table',
'@plone/components > react-aria-components/Tooltip',
'@plone/components > react-aria-components/composeRenderProps',
'@plone/components > react-stately',
'@plone/components > tailwind-merge',
'@plone/components > tailwind-variants',
'@plone/helpers > jotai',
'@plone/helpers > jotai/utils',
'@plone/helpers > jotai-optics',
],
},
Comment thread
sneridagh marked this conversation as resolved.
ssr: {
optimizeDeps: {
include: ['i18next-fs-backend/cjs', 'isbot', 'remix-i18next/server'],
},
},
resolve: {
tsconfigPaths: true,
},
Expand Down
1 change: 1 addition & 0 deletions docs/development/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,5 @@ i18n
editor-slash-menu
block-anatomy
configure-editor-block-widths
vite-optimize-deps
```
128 changes: 128 additions & 0 deletions docs/development/vite-optimize-deps.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
---
myst:
html_meta:
"description": "How to declare Vite optimizeDeps for add-ons in Aurora"
"property=og:description": "How to declare Vite optimizeDeps for add-ons in Aurora"
"property=og:title": "Vite dependency pre-bundling"
"keywords": "Plone Aurora, Vite, optimizeDeps, pnpm, add-ons"
---

# Vite dependency pre-bundling

This guide shows you how to declare third-party dependencies for pre-bundling so Vite resolves more of them at startup instead of discovering them lazily during dev.

Aurora runs in a pnpm monorepo.
Vite treats workspace packages as source files and does not scan them upfront for their third-party dependencies.
Instead, it discovers them the first time a page imports them, triggering a re-bundle and a browser reload.
In a large project, this causes several reload cycles and noticeably slows down the first page load in dev mode.

The fix is to declare those third-party dependencies in `optimizeDeps.include`.
Aurora uses two places for these declarations depending on whether the package is a registered add-on.

```{important}
Only declare a package as a registered add-on if it configures the application — registering slots, routes, or components into the Aurora framework.
Pure libraries such as component kits and utility packages must not be add-ons.
Registering them as add-ons causes the addon system to generate unnecessary loaders and blurs the boundary between framework participants and libraries.
If your package is a library, add its entries to `vite.config.ts` in the app that uses it.
```

## Registered add-ons: `vite.extend.js`

Any package that is a registered Aurora add-on can ship a `vite.extend.js` file in its root.
`PloneRegistryVitePlugin` picks these up automatically and merges them into the Vite config at startup — no changes to the app are needed.

The file must export a default function that receives the current config and returns a modified copy:

```js
// packages/my-addon/vite.extend.js
export default function (config) {
return {
...config,
optimizeDeps: {
...config.optimizeDeps,
include: [
...(config.optimizeDeps?.include ?? []),
// Use "pkg > dep" syntax for pnpm — resolves the dep through the
// workspace package that owns it
'@plone/my-addon > some-library',
'@plone/my-addon > some-library/subpath',
],
},
};
}
```

### When to add an entry

Add a `"pkg > dep"` entry for every direct dependency of your add-on that is not itself a workspace package.
You do not need entries for:

- Other Aurora add-ons or workspace packages — Vite excludes them from
optimization automatically because it treats them as source files.
- Dev dependencies.
- Peer dependencies that the host app provides.

If a dependency exports subpaths you use (for example `some-lib/react` or `some-lib/client`), add a separate entry for each subpath.
Vite does not discover subpath exports automatically from the main entry.
Comment thread
sneridagh marked this conversation as resolved.

If a dependency is only used in server-side code (for example `*.server.*` files), add it to `ssr.optimizeDeps.include` instead of `optimizeDeps.include` so it isn't pre-bundled for the browser.
Declaring it in `ssr.optimizeDeps.include` alone is not always enough — Vite's client-side dependency scanner can still pick it up through re-exports.
If that happens, also add it to the top-level `optimizeDeps.exclude` in `apps/aurora/vite.config.ts` so it's explicitly kept out of the client bundle.

To keep the lists from drifting, run:

```sh
pnpm check:vite-optimize-deps
```

The script inspects runtime imports in the workspace packages that require explicit declarations and reports missing or stale `pkg > dep` entries.

Run it when:

- you add or remove a runtime import in `@plone/components`, `@plone/helpers`,
`@plone/cmsui`, `@plone/layout`, or `@plone/plate`
- you switch an import to a different package subpath such as
`some-lib/react`
- you move an import between browser-only and server-only code and may need to
switch between `optimizeDeps.include` and `ssr.optimizeDeps.include`
- you touch one of the existing optimize-deps config files and want to verify
the list before opening a PR

Recommended workflow:

1. Make the code change that introduces or removes runtime imports.
2. Run `pnpm check:vite-optimize-deps`.
3. Copy the reported missing entries into the relevant `vite.extend.js` file or
`apps/aurora/vite.config.ts`.
4. Remove reported stale entries unless you have a concrete reason to keep
them.
5. Re-run the check until it passes.

### Existing core add-on files

| Add-on | File |
|--------|------|
| `@plone/plate` | `packages/plate/vite.extend.js` |
| `@plone/layout` | `packages/layout/vite.extend.js` |
| `@plone/cmsui` | `packages/cmsui/vite.extend.js` |

## Non-add-on workspace packages and app deps: `vite.config.ts`

Packages that are not registered add-ons — currently `@plone/components` and `@plone/helpers` — cannot use `vite.extend.js` because `PloneRegistryVitePlugin` never loads their files.
List their dependencies directly in `apps/aurora/vite.config.ts` under `optimizeDeps.include`.

The same rule applies to packages that are direct dependencies of the app itself (listed in `apps/aurora/package.json`).
Those use plain names without the `>` syntax because Vite resolves them directly from the app root.

```ts
// apps/aurora/vite.config.ts
optimizeDeps: {
include: [
// App-level dep — plain name works
'some-app-dep',
// Non-add-on workspace package dep — requires "pkg > dep" syntax
'@plone/components > react-aria-components',
'@plone/helpers > jotai',
]
}
```
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"test": "pnpm --filter @plone/aurora run test",
"test:all": "pnpm test:ci",
"test:ci": "pnpm --filter @plone/* --filter \"!@plone/scripts\" --filter \"!@plone/theming\" run test --run",
"check:vite-optimize-deps": "node packages/scripts/check-vite-optimize-deps.js",
"prettier": "prettier --check '{apps,packages}/**/*.{js,jsx,ts,tsx}'",
"prettier:fix": "prettier --write '{apps,packages}/**/*.{js,jsx,ts,tsx}'",
"stylelint": "stylelint '{apps,packages}/**/*.{css,scss,less}'",
Expand Down
1 change: 1 addition & 0 deletions packages/cmsui/news/+vite-optimize-deps.internal
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added `vite.extend.js` and a dependency audit path to pre-bundle CMS UI runtime dependencies, reducing dev server startup reloads. @arybakov05
31 changes: 31 additions & 0 deletions packages/cmsui/vite.extend.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
export default function (config) {
return {
...config,
optimizeDeps: {
...config.optimizeDeps,
include: [
...(config.optimizeDeps?.include ?? []),
'@plone/cmsui > @platejs/floating',
'@plone/cmsui > @platejs/link/react',
'@plone/cmsui > @tanstack/react-form',
'@plone/cmsui > class-variance-authority',
'@plone/cmsui > clsx',
'@plone/cmsui > jotai',
'@plone/cmsui > jotai-optics',
'@plone/cmsui > jotai/utils',
'@plone/cmsui > jwt-decode',
'@plone/cmsui > lucide-react',
'@plone/cmsui > platejs',
'@plone/cmsui > platejs/react',
'@plone/cmsui > react-aria',
'@plone/cmsui > react-aria-components',
'@plone/cmsui > react-i18next',
'@plone/cmsui > react-router',
'@plone/cmsui > rrule',
'@plone/cmsui > tailwind-merge',
'@plone/cmsui > tailwind-variants',
'@plone/cmsui > usehooks-ts',
],
},
};
}
1 change: 1 addition & 0 deletions packages/layout/news/+vite-optimize-deps.internal
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added `vite.extend.js` and a dependency audit path to pre-bundle layout runtime dependencies, reducing dev server startup reloads. @arybakov05
19 changes: 19 additions & 0 deletions packages/layout/vite.extend.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export default function (config) {
return {
...config,
optimizeDeps: {
...config.optimizeDeps,
include: [
...(config.optimizeDeps?.include ?? []),
'@plone/layout > clsx',
'@plone/layout > lodash.sortby',
'@plone/layout > pretty-bytes',
'@plone/layout > react-aria',
'@plone/layout > react-aria-components',
'@plone/layout > react-i18next',
'@plone/layout > react-router',
'@plone/layout > rrule',
],
},
};
}
1 change: 1 addition & 0 deletions packages/plate/news/+vite-optimize-deps.internal
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added `vite.extend.js` and a dependency audit path to pre-bundle Plate runtime dependencies, reducing dev server startup reloads. @arybakov05
91 changes: 91 additions & 0 deletions packages/plate/vite.extend.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
export default function (config) {
return {
...config,
optimizeDeps: {
...config.optimizeDeps,
include: [
...(config.optimizeDeps?.include ?? []),
'@plone/plate > @ai-sdk/react',
'@plone/plate > @ariakit/react',
'@plone/plate > @platejs/ai',
'@plone/plate > @platejs/ai/react',
'@plone/plate > @platejs/autoformat',
'@plone/plate > @platejs/basic-nodes',
'@plone/plate > @platejs/basic-nodes/react',
'@plone/plate > @platejs/basic-styles',
'@plone/plate > @platejs/basic-styles/react',
'@plone/plate > @platejs/callout',
'@plone/plate > @platejs/callout/react',
'@plone/plate > @platejs/caption',
'@plone/plate > @platejs/caption/react',
'@plone/plate > @platejs/code-block',
'@plone/plate > @platejs/code-block/react',
'@plone/plate > @platejs/combobox',
'@plone/plate > @platejs/combobox/react',
'@plone/plate > @platejs/comment',
'@plone/plate > @platejs/comment/react',
'@plone/plate > @platejs/dnd',
'@plone/plate > @platejs/docx',
'@plone/plate > @platejs/emoji/react',
'@plone/plate > @platejs/floating',
'@plone/plate > @platejs/indent',
'@plone/plate > @platejs/indent/react',
'@plone/plate > @platejs/juice',
'@plone/plate > @platejs/layout',
'@plone/plate > @platejs/layout/react',
'@plone/plate > @platejs/link',
'@plone/plate > @platejs/link/react',
'@plone/plate > @platejs/list',
'@plone/plate > @platejs/list/react',
'@plone/plate > @platejs/markdown',
'@plone/plate > @platejs/media',
'@plone/plate > @platejs/media/react',
'@plone/plate > @platejs/mention',
'@plone/plate > @platejs/mention/react',
'@plone/plate > @platejs/playwright',
'@plone/plate > @platejs/resizable',
'@plone/plate > @platejs/selection/react',
'@plone/plate > @platejs/slash-command/react',
'@plone/plate > @platejs/suggestion',
'@plone/plate > @platejs/table',
'@plone/plate > @platejs/table/react',
'@plone/plate > @platejs/toc',
'@plone/plate > @platejs/toc/react',
'@plone/plate > @platejs/toggle',
'@plone/plate > @platejs/toggle/react',
'@plone/plate > @radix-ui/react-alert-dialog',
'@plone/plate > @radix-ui/react-avatar',
'@plone/plate > @radix-ui/react-checkbox',
'@plone/plate > @radix-ui/react-context-menu',
'@plone/plate > @radix-ui/react-dialog',
'@plone/plate > @radix-ui/react-dropdown-menu',
'@plone/plate > @radix-ui/react-popover',
'@plone/plate > @radix-ui/react-separator',
'@plone/plate > @radix-ui/react-slot',
'@plone/plate > @radix-ui/react-toolbar',
'@plone/plate > @radix-ui/react-tooltip',
'@plone/plate > @udecode/cn',
'@plone/plate > ai',
'@plone/plate > class-variance-authority',
'@plone/plate > clsx',
'@plone/plate > cmdk',
'@plone/plate > html2canvas-pro',
'@plone/plate > jotai',
'@plone/plate > lodash.debounce',
'@plone/plate > lowlight',
'@plone/plate > lucide-react',
'@plone/plate > pdf-lib',
'@plone/plate > platejs',
'@plone/plate > platejs/react',
'@plone/plate > react-dnd',
'@plone/plate > react-dnd-html5-backend',
'@plone/plate > react-lite-youtube-embed',
'@plone/plate > remark-gfm',
'@plone/plate > sonner',
'@plone/plate > tailwind-merge',
'@plone/plate > use-file-picker',
'@plone/plate > zod',
],
},
};
}
1 change: 1 addition & 0 deletions packages/registry/news/+vite-extend-support.internal
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Check whether `.plone/vite.loader.js` content has changed before writing it in `PloneRegistryVitePlugin`. @arybakov05
9 changes: 8 additions & 1 deletion packages/registry/vite-plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,14 @@ const load = (config, context = {}) => {
export default load;
`;

fs.writeFileSync(viteLoaderPath, code);
// Only write if content changed — vite.loader.js is imported by vite.config.ts
// so any write triggers a Vite server restart, causing an infinite loop
const existing = fs.existsSync(viteLoaderPath)
? fs.readFileSync(viteLoaderPath, 'utf-8')
: null;
if (existing !== code) {
fs.writeFileSync(viteLoaderPath, code);
}
return viteLoaderPath;
}

Expand Down
Loading
Loading