import react from '@vitejs/plugin-react-swc'; import * as fs from 'fs'; import * as path from 'path'; import { defineConfig } from 'vite'; import checker from 'vite-plugin-checker'; import dts, { type PluginOptions } from 'vite-plugin-dts'; import sassDts from 'vite-plugin-sass-dts'; import svgr from 'vite-plugin-svgr'; type Checkers = Parameters[0]; import { THEME_CSS_FILE_NAME_BY_SCHEME } from './design-tokens/themeCssFileNameByScheme'; import packageJson from './package.json'; import { isDefined } from './src/utilities/utils/isDefined'; const isVitest = isDefined(process.env.VITEST); const entries = Object.fromEntries( Object.keys(packageJson.exports) .filter((subpath) => !subpath.endsWith('.css')) .map((subpath) => [ subpath === '.' ? 'index' : subpath.slice(2), `src/${subpath}/index.ts`, ]), ); export default defineConfig(({ command }) => { const isBuildCommand = command === 'build'; const tsConfigPath = isBuildCommand ? path.resolve(__dirname, './tsconfig.lib.json') : path.resolve(__dirname, './tsconfig.json'); const checkersConfig: Checkers = { typescript: { tsconfigPath: tsConfigPath, }, }; const dtsConfig: PluginOptions = { entryRoot: 'src', tsconfigPath: tsConfigPath, }; const BUNDLED_DEPS: string[] = []; const externalDeps = Object.keys({ ...(packageJson.dependencies || {}), ...(packageJson.peerDependencies || {}), }).filter((dep) => !BUNDLED_DEPS.includes(dep)); return { resolve: { tsconfigPaths: true, alias: { '@ui/': path.resolve(__dirname, 'src') + '/', '@assets/': path.resolve(__dirname, 'src/assets') + '/', '@styles/': path.resolve(__dirname, 'src/styles') + '/', }, }, css: { modules: { localsConvention: 'camelCaseOnly', }, preprocessorOptions: { scss: { api: 'modern-compiler', loadPaths: [path.resolve(__dirname, 'src/styles')], additionalData: [ `@use 'abstracts/functions' as *;`, `@use 'abstracts/mixins' as *;`, `@use 'abstracts/breakpoints' as *;`, '', ].join('\n'), }, }, }, optimizeDeps: { // Pre-bundle React and Jotai so Vite's dep optimizer doesn't re-bundle them // mid-run during browser-mode Storybook tests — re-bundling rotates the // optimized chunk hash and 404s in-flight dynamic imports (vite 8 / rolldown). include: [ '@base-ui/utils/store', 'react', 'react-dom', 'react-dom/client', 'react/jsx-runtime', 'react/jsx-dev-runtime', ], }, root: __dirname, cacheDir: 'node_modules/.vite', assetsInclude: ['src/**/*.svg'], plugins: [ react(), svgr(), // Generates typed *.module.scss.d.ts siblings (dev mode only — backed by // sass-embedded). CI/build relies on the ambient src/scss-modules.d.ts. sassDts({ esmExport: true, legacyFileFormat: true }), dts(dtsConfig), ...(isVitest ? [] : [checker(checkersConfig)]), { name: 'copy-theme-css', apply: 'build', closeBundle() { const distDir = path.resolve(__dirname, 'dist'); fs.mkdirSync(distDir, { recursive: true }); for (const file of Object.values(THEME_CSS_FILE_NAME_BY_SCHEME)) { fs.copyFileSync( path.resolve(__dirname, `src/theme-constants/${file}`), path.resolve(distDir, file), ); } }, }, ], build: { cssCodeSplit: false, minify: 'esbuild', sourcemap: false, emptyOutDir: false, outDir: './dist', reportCompressedSize: true, commonjsOptions: { transformMixedEsModules: true, interopDefault: true, defaultIsModuleExports: true, requireReturnsDefault: 'auto', }, lib: { entry: entries, name: 'twenty-ui', }, rollupOptions: { external: (id: string) => externalDeps.some((dep) => id === dep || id.startsWith(dep + '/')), output: [ { assetFileNames: 'style.css', globals: { react: 'React', 'react-dom': 'ReactDOM', }, format: 'es', entryFileNames: '[name].mjs', }, { assetFileNames: 'style.css', format: 'cjs', globals: { react: 'React', 'react-dom': 'ReactDOM', }, esModule: true, exports: 'named', entryFileNames: '[name].cjs', }, ], }, }, logLevel: 'error', }; });