diff --git a/apps/aurora/news/+vite-optimize-deps.internal b/apps/aurora/news/+vite-optimize-deps.internal new file mode 100644 index 000000000..a9ab4d7d1 --- /dev/null +++ b/apps/aurora/news/+vite-optimize-deps.internal @@ -0,0 +1 @@ +Added `optimizeDeps.include` entries for non-addon workspace packages and app-level deps, eliminating lazy dependency discovery reloads on dev server startup. @arybakov05 diff --git a/apps/aurora/vite.config.ts b/apps/aurora/vite.config.ts index c7e9478f6..14a6ee0a8 100644 --- a/apps/aurora/vite.config.ts +++ b/apps/aurora/vite.config.ts @@ -46,6 +46,41 @@ export default defineConfig(({ command, mode, isSsrBuild }) => { ] : []), ] as PluginOption[], + optimizeDeps: { + include: [ + // App-level deps (in apps/aurora/package.json) + 'i18next', + 'i18next-browser-languagedetector', + 'i18next-fs-backend/cjs', + 'i18next-http-backend', + 'react-i18next', + // Injected by babel-plugin-react-compiler, not in any package.json + 'react/compiler-runtime', + 'remix-i18next/client', + 'remix-i18next/react', + 'remix-i18next/server', + // @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/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', + ], + }, resolve: { tsconfigPaths: true, }, diff --git a/docs/development/index.md b/docs/development/index.md index b04cd7580..e3bf6635a 100644 --- a/docs/development/index.md +++ b/docs/development/index.md @@ -19,4 +19,5 @@ images i18n editor-slash-menu configure-editor-block-widths +vite-optimize-deps ``` diff --git a/docs/development/vite-optimize-deps.md b/docs/development/vite-optimize-deps.md new file mode 100644 index 000000000..c7ed78f1f --- /dev/null +++ b/docs/development/vite-optimize-deps.md @@ -0,0 +1,95 @@ +--- +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 them at startup rather than 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. + +### 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', + ] +} +``` diff --git a/packages/cmsui/news/+vite-optimize-deps.internal b/packages/cmsui/news/+vite-optimize-deps.internal new file mode 100644 index 000000000..10750a5ab --- /dev/null +++ b/packages/cmsui/news/+vite-optimize-deps.internal @@ -0,0 +1 @@ +Added `vite.extend.js` to pre-bundle CMS UI dependencies, reducing dev server startup reloads. @arybakov05 \ No newline at end of file diff --git a/packages/cmsui/vite.extend.js b/packages/cmsui/vite.extend.js new file mode 100644 index 000000000..17024e6b5 --- /dev/null +++ b/packages/cmsui/vite.extend.js @@ -0,0 +1,13 @@ +export default function (config) { + return { + ...config, + optimizeDeps: { + ...config.optimizeDeps, + include: [ + ...(config.optimizeDeps?.include ?? []), + '@plone/cmsui > jwt-decode', + '@plone/cmsui > usehooks-ts', + ], + }, + }; +} diff --git a/packages/layout/news/+vite-optimize-deps.internal b/packages/layout/news/+vite-optimize-deps.internal new file mode 100644 index 000000000..e0077f0cc --- /dev/null +++ b/packages/layout/news/+vite-optimize-deps.internal @@ -0,0 +1 @@ +Added `vite.extend.js` to pre-bundle layout dependencies, reducing dev server startup reloads. @arybakov05 \ No newline at end of file diff --git a/packages/layout/vite.extend.js b/packages/layout/vite.extend.js new file mode 100644 index 000000000..467c1313c --- /dev/null +++ b/packages/layout/vite.extend.js @@ -0,0 +1,14 @@ +export default function (config) { + return { + ...config, + optimizeDeps: { + ...config.optimizeDeps, + include: [ + ...(config.optimizeDeps?.include ?? []), + '@plone/layout > lodash.sortby', + '@plone/layout > pretty-bytes', + '@plone/layout > rrule', + ], + }, + }; +} diff --git a/packages/plate/news/+vite-optimize-deps.internal b/packages/plate/news/+vite-optimize-deps.internal new file mode 100644 index 000000000..35c6c680d --- /dev/null +++ b/packages/plate/news/+vite-optimize-deps.internal @@ -0,0 +1 @@ +Added `vite.extend.js` to pre-bundle platejs and Radix UI dependencies, reducing dev server startup reloads. @arybakov05 \ No newline at end of file diff --git a/packages/plate/vite.extend.js b/packages/plate/vite.extend.js new file mode 100644 index 000000000..6e5e7dfe5 --- /dev/null +++ b/packages/plate/vite.extend.js @@ -0,0 +1,48 @@ +export default function (config) { + return { + ...config, + optimizeDeps: { + ...config.optimizeDeps, + include: [ + ...(config.optimizeDeps?.include ?? []), + '@plone/plate > @platejs/ai/react', + '@plone/plate > @platejs/basic-nodes', + '@plone/plate > @platejs/basic-nodes/react', + '@plone/plate > @platejs/basic-styles', + '@plone/plate > @platejs/callout', + '@plone/plate > @platejs/caption', + '@plone/plate > @platejs/code-block', + '@plone/plate > @platejs/comment', + '@plone/plate > @platejs/comment/react', + '@plone/plate > @platejs/floating', + '@plone/plate > @platejs/indent', + '@plone/plate > @platejs/layout', + '@plone/plate > @platejs/link', + '@plone/plate > @platejs/link/react', + '@plone/plate > @platejs/list', + '@plone/plate > @platejs/list/react', + '@plone/plate > @platejs/media', + '@plone/plate > @platejs/mention', + '@plone/plate > @platejs/selection/react', + '@plone/plate > @platejs/suggestion', + '@plone/plate > @platejs/table', + '@plone/plate > @platejs/table/react', + '@plone/plate > @platejs/toc', + '@plone/plate > @platejs/toggle', + '@plone/plate > @platejs/toggle/react', + '@plone/plate > @radix-ui/react-avatar', + '@plone/plate > @radix-ui/react-dropdown-menu', + '@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 > class-variance-authority', + '@plone/plate > lowlight', + '@plone/plate > lucide-react', + '@plone/plate > platejs', + '@plone/plate > platejs/react', + ], + }, + }; +} diff --git a/packages/registry/news/+vite-extend-support.internal b/packages/registry/news/+vite-extend-support.internal new file mode 100644 index 000000000..039f17f01 --- /dev/null +++ b/packages/registry/news/+vite-extend-support.internal @@ -0,0 +1 @@ +Check whether `.plone/vite.loader.js` content has changed before writing it in `PloneRegistryVitePlugin`. @arybakov05 \ No newline at end of file diff --git a/packages/registry/vite-plugin.js b/packages/registry/vite-plugin.js index c8c8dcd8f..1bbf0d7d2 100644 --- a/packages/registry/vite-plugin.js +++ b/packages/registry/vite-plugin.js @@ -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; }