UNPKG

@strapi/strapi

Version:

An open source headless CMS solution to create and manage your own API. It provides a powerful dashboard and features to make your life easier. Databases supported: MySQL, MariaDB, PostgreSQL, SQLite

187 lines (184 loc) 7.5 kB
import path from 'node:path'; import fs from 'node:fs/promises'; import readPkgUp from 'read-pkg-up'; import { ADMIN_VITE_ALIAS_MODULES } from './admin-vite-alias-modules.mjs'; import { getModule } from './dependencies.mjs'; import { ADMIN_VITE_SINGLETON_MODULES } from './admin-vite-singleton-modules.mjs'; const REACT_PEER_DEPENDENCIES = new Set([ 'react', 'react-dom' ]); /** * Packages explicitly pre-bundled or aliased for the admin singleton contract. * Never auto-exclude these — they must stay on the optimizeDeps.include / dedupe path. * * The admin entry host (@strapi/strapi) must never land in optimizeDeps.exclude (#26944, #27014). * CJS-only deps imported by @strapi/admin (e.g. invariant, lodash) belong in optimizeDeps.include * (see vite/config.ts — #26964, #26944, #27014). * The CodeMirror singletons (e.g. @uiw/react-codemirror — ESM with a React peer) match the * exclude heuristic but must stay pre-bundled so the admin keeps a single instance */ const PINNED_OPTIMIZE_MODULES = new Set([ ...ADMIN_VITE_ALIAS_MODULES, ...ADMIN_VITE_SINGLETON_MODULES, '@strapi/strapi' ]); /** * Thin shared plugin UI kits that are known to break when Vite re-optimizes their pre-built * dist (original #26944 victims). Only these names — plus packages that opt in via package.json — * may be auto-excluded. Broad heuristic matching alone was too aggressive for large plugins * (e.g. CKEditor) and orphaned their transitive CJS/UMD deps (#27136). * * @internal exported for tests */ const ADMIN_VITE_OPTIMIZE_DEPS_EXCLUDE_ALLOWLIST = new Set([ 'strapi-design-extended' ]); const isOfficialStrapiPackage = (name)=>name.startsWith('@strapi/'); const getRootPackageExport = (pkg)=>{ const exportsField = pkg.exports; if (!exportsField || typeof exportsField === 'string') { return undefined; } return exportsField['.']; }; /** * @internal exported for tests */ const isEsmPackage = (pkg)=>{ if (pkg.type === 'module') { return true; } const rootExport = getRootPackageExport(pkg); return typeof rootExport === 'object' && rootExport !== null && 'import' in rootExport && typeof rootExport.import === 'string'; }; /** * @internal exported for tests */ const hasReactPeerDependency = (pkg)=>Object.keys(pkg.peerDependencies ?? {}).some((name)=>REACT_PEER_DEPENDENCIES.has(name)); /** * Targets libraries that ship a pre-built dist bundle (e.g. plugin UI kits) rather than * source packages like react-intl that still benefit from Vite's dep optimizer. * * @internal exported for tests */ const shipsPreBuiltDist = (pkg)=>{ const rootExport = getRootPackageExport(pkg); const exportImport = typeof rootExport === 'object' && rootExport !== null && 'import' in rootExport ? rootExport.import : undefined; const entryPaths = [ pkg.module, pkg.main, exportImport ].filter((entry)=>typeof entry === 'string'); if (entryPaths.some((entry)=>entry.includes('/dist/') || entry.startsWith('./dist/') || entry.startsWith('dist/'))) { return true; } return Array.isArray(pkg.files) && pkg.files.some((file)=>file === 'dist' || file.startsWith('dist/')); }; /** * Shape check for packages that *could* be excluded (pre-built ESM + React peer + dist). * Matching alone is not enough to exclude — see {@link isEligibleForOptimizeDepsExclude}. * * @internal exported for tests */ const shouldExcludeFromOptimizeDeps = (pkg)=>hasReactPeerDependency(pkg) && isEsmPackage(pkg) && shipsPreBuiltDist(pkg); /** * Packages may opt into optimizeDeps.exclude via package.json: * `{ "strapi": { "admin": { "vite": { "optimizeDepsExclude": true } } } }` * * @internal exported for tests */ const packageOptsIntoOptimizeDepsExclude = (pkg)=>{ const strapi = pkg.strapi; return strapi?.admin?.vite?.optimizeDepsExclude === true; }; /** * A candidate is excluded only when it matches the UI-kit shape AND is either on the * known-safe allowlist or explicitly opts in. This keeps #26944 working for thin kits * without auto-excluding large editor/chart plugins (#27136). * * @internal exported for tests */ const isEligibleForOptimizeDepsExclude = (name, pkg)=>{ if (!shouldExcludeFromOptimizeDeps(pkg)) { return false; } return ADMIN_VITE_OPTIMIZE_DEPS_EXCLUDE_ALLOWLIST.has(name) || packageOptsIntoOptimizeDepsExclude(pkg); }; /** * @internal exported for tests */ const getPluginPackageName = (modulePath)=>{ if (modulePath.startsWith('@')) { const [scope, name] = modulePath.split('/'); return `${scope}/${name}`; } return modulePath.split('/')[0] ?? modulePath; }; const collectDependencyNames = (pkg)=>{ const names = new Set(); for (const section of [ pkg.dependencies, pkg.devDependencies ]){ if (section) { for (const name of Object.keys(section)){ names.add(name); } } } return [ ...names ]; }; const getPluginPackageJson = async (plugin, cwd)=>{ if (plugin.type === 'local' && plugin.path) { try { const content = await fs.readFile(path.join(plugin.path, 'package.json'), 'utf8'); return JSON.parse(content); } catch { return null; } } return getModule(getPluginPackageName(plugin.modulePath), cwd); }; const loadAppPackageJson = async (cwd)=>{ const result = await readPkgUp({ cwd }); return result?.packageJson ?? null; }; /** * Pre-built ESM libraries with React peers (shared plugin UI kits) are incompatible with * Strapi's React/design-system pre-bundling. Skip dep optimization so they resolve through * the admin resolve aliases instead of being re-bundled by Vite. * * Scans app and plugin dependency trees (#26944). Only allowlisted or opt-in packages that * also match the UI-kit shape are excluded (#27136). Official @strapi/* packages and pinned * singletons are never auto-excluded — @strapi/strapi matches the heuristic but must stay on * the optimizeDeps.include path (#26944, #27014). * * Apps can still exclude additional packages via `src/admin/vite.config` optimizeDeps.exclude. * * @internal */ const collectAdminOptimizeDepsExclude = async (cwd, plugins)=>{ const candidateNames = new Set(); const appPkg = await loadAppPackageJson(cwd); if (appPkg) { for (const name of collectDependencyNames(appPkg)){ candidateNames.add(name); } } for (const plugin of plugins){ const pluginPkg = await getPluginPackageJson(plugin, cwd); if (pluginPkg) { for (const name of collectDependencyNames(pluginPkg)){ candidateNames.add(name); } } } const exclude = []; for (const name of candidateNames){ if (PINNED_OPTIMIZE_MODULES.has(name) || isOfficialStrapiPackage(name)) { continue; } const pkg = await getModule(name, cwd); if (pkg && isEligibleForOptimizeDepsExclude(name, pkg)) { exclude.push(name); } } return exclude.sort(); }; export { ADMIN_VITE_OPTIMIZE_DEPS_EXCLUDE_ALLOWLIST, collectAdminOptimizeDepsExclude, getPluginPackageName, hasReactPeerDependency, isEligibleForOptimizeDepsExclude, isEsmPackage, packageOptsIntoOptimizeDepsExclude, shipsPreBuiltDist, shouldExcludeFromOptimizeDeps }; //# sourceMappingURL=admin-vite-optimize-exclude.mjs.map