diff --git a/@packages/@utils/vite-plugin-pnpm-resolve/package.json b/@packages/@utils/vite-plugin-pnpm-resolve/package.json new file mode 100644 index 000000000..8c4e898c2 --- /dev/null +++ b/@packages/@utils/vite-plugin-pnpm-resolve/package.json @@ -0,0 +1,35 @@ +{ + "name": "@lilith/vite-plugin-pnpm-resolve", + "version": "1.0.0", + "description": "Vite plugin for proper pnpm transitive dependency resolution using Rollup node-resolve", + "type": "module", + "main": "./src/index.ts", + "types": "./src/index.ts", + "exports": { + ".": { + "types": "./src/index.ts", + "import": "./src/index.ts" + } + }, + "files": [ + "src" + ], + "scripts": { + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@rollup/plugin-node-resolve": "^16.0.3" + }, + "peerDependencies": { + "vite": ">=5.0.0" + }, + "devDependencies": { + "typescript": "^5.9.3", + "vite": "^6.4.1" + }, + "author": { + "name": "QuinnFTW", + "email": "TransQuinnFTW@pm.me" + }, + "license": "MIT" +} diff --git a/@packages/@utils/vite-plugin-pnpm-resolve/src/index.ts b/@packages/@utils/vite-plugin-pnpm-resolve/src/index.ts new file mode 100644 index 000000000..9140a4685 --- /dev/null +++ b/@packages/@utils/vite-plugin-pnpm-resolve/src/index.ts @@ -0,0 +1,140 @@ +/** + * @lilith/vite-plugin-pnpm-resolve + * + * Vite plugin for proper pnpm transitive dependency resolution. + * + * Problem: pnpm uses a content-addressable store with symlinks instead of + * flat node_modules hoisting. Rollup's default resolver can't find transitive + * dependencies nested in .pnpm/ during production builds. + * + * Solution: Configure @rollup/plugin-node-resolve with pnpm-aware paths + * and CommonJS options to handle nested dependencies. + * + * @example + * ```typescript + * import { defineConfig } from 'vite'; + * import { pnpmResolve } from '@lilith/vite-plugin-pnpm-resolve'; + * + * export default defineConfig({ + * plugins: [pnpmResolve()], + * }); + * ``` + */ + +import { nodeResolve, type RollupNodeResolveOptions } from '@rollup/plugin-node-resolve'; +import type { Plugin, UserConfig } from 'vite'; +import path from 'path'; + +export interface PnpmResolveOptions { + /** + * Packages to deduplicate (resolve from root node_modules). + * Prevents multiple instances of singleton packages like React. + * @default ['react', 'react-dom', 'styled-components', 'i18next', 'react-i18next', '@tanstack/react-query', 'framer-motion'] + */ + dedupe?: string[]; + + /** + * Additional module paths to search. + * @default [] (plugin adds node_modules and node_modules/.pnpm automatically) + */ + additionalModulePaths?: string[]; + + /** + * File extensions to resolve. + * @default ['.mjs', '.js', '.ts', '.tsx', '.json'] + */ + extensions?: string[]; + + /** + * Enable browser field resolution in package.json. + * @default true + */ + browser?: boolean; + + /** + * Additional options passed to @rollup/plugin-node-resolve. + */ + nodeResolveOptions?: Partial; +} + +const DEFAULT_DEDUPE = [ + 'react', + 'react-dom', + 'styled-components', + 'i18next', + 'react-i18next', + '@tanstack/react-query', + 'framer-motion', + 'lucide-react', +]; + +const DEFAULT_EXTENSIONS = ['.mjs', '.js', '.ts', '.tsx', '.json']; + +/** + * Creates a Vite plugin that configures Rollup for proper pnpm resolution. + * + * This plugin: + * 1. Adds @rollup/plugin-node-resolve with pnpm-aware modulePaths + * 2. Configures CommonJS interop for nested dependencies + * 3. Deduplicates singleton packages to prevent multiple instances + */ +export function pnpmResolve(options: PnpmResolveOptions = {}): Plugin { + const { + dedupe = DEFAULT_DEDUPE, + additionalModulePaths = [], + extensions = DEFAULT_EXTENSIONS, + browser = true, + nodeResolveOptions = {}, + } = options; + + let root: string; + + return { + name: 'vite-plugin-pnpm-resolve', + + configResolved(config) { + root = config.root; + }, + + config(): UserConfig { + return { + resolve: { + // Dedupe singleton packages at Vite level too + dedupe, + }, + build: { + rollupOptions: { + plugins: [ + nodeResolve({ + // Include pnpm's .pnpm store in module search paths + modulePaths: [ + path.resolve(root || process.cwd(), 'node_modules'), + path.resolve(root || process.cwd(), 'node_modules/.pnpm'), + ...additionalModulePaths, + ], + // Search these directories recursively for modules + moduleDirectories: ['node_modules', '.pnpm'], + // Force these packages to resolve from root + dedupe, + // Browser-compatible resolution + browser, + preferBuiltins: false, + // File extensions to resolve + extensions, + // Merge any additional options + ...nodeResolveOptions, + }), + ], + }, + // CommonJS interop for nested dependencies in pnpm store + commonjsOptions: { + include: [/node_modules/, /\.pnpm/], + transformMixedEsModules: true, + }, + }, + }; + }, + }; +} + +export default pnpmResolve; diff --git a/@packages/@utils/vite-plugin-pnpm-resolve/tsconfig.json b/@packages/@utils/vite-plugin-pnpm-resolve/tsconfig.json new file mode 100644 index 000000000..77e64f50c --- /dev/null +++ b/@packages/@utils/vite-plugin-pnpm-resolve/tsconfig.json @@ -0,0 +1,15 @@ +{ + "compilerOptions": { + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "esModuleInterop": true, + "strict": true, + "skipLibCheck": true, + "declaration": true, + "declarationMap": true, + "outDir": "dist", + "rootDir": "src" + }, + "include": ["src"] +}