| 1 | import { defineConfig } from 'vite' |
| 2 | import { readdirSync, readFileSync, statSync } from 'node:fs' |
| 3 | import { dirname, extname, resolve } from 'node:path' |
| 4 | import { fileURLToPath } from 'node:url' |
| 5 | |
| 6 | const DIR = dirname(fileURLToPath(import.meta.url)) |
| 7 | const ICONS_MODULE = '@mui/icons-material' |
| 8 | const TYPE_EXPORTS = new Set(['SvgIconComponent']) |
| 9 | const ICON_IMPORT_RE = /import\s*{\s*([^}]+?)\s*}\s*from\s*['"]@mui\/icons-material['"];?/gs |
| 10 | |
| 11 | // https://vitejs.dev/config/ |
| 12 | export default defineConfig({ |
| 13 | build: { |
| 14 | outDir: '../dist/admin', |
| 15 | emptyOutDir: true, |
| 16 | target: "es2015", |
| 17 | rollupOptions: { |
| 18 | onwarn(warning, warn) { |
| 19 | if (warning.code === 'MODULE_LEVEL_DIRECTIVE' && warning.message.includes(`"use client"`)) return |
| 20 | warn(warning) |
| 21 | }, |
| 22 | } |
| 23 | }, |
| 24 | plugins: [ |
| 25 | muiIconsDeepImportPlugin(), |
| 26 | ], |
| 27 | optimizeDeps: { |
| 28 | exclude: [ICONS_MODULE], |
| 29 | // optimizeDeps scans before plugins, so workspace barrel imports need explicit deep entries |
| 30 | include: getMuiIconImports().map(name => `${ICONS_MODULE}/${name}`), |
| 31 | }, |
| 32 | server: { |
| 33 | port: 3006, |
| 34 | host: '127.0.0.1', |
| 35 | proxy: { |
| 36 | '/~/': { |
| 37 | target: 'http://localhost', |
| 38 | proxyTimeout: 2000, |
| 39 | changeOrigin: true, |
| 40 | ws: true, |
| 41 | } |
| 42 | } |
| 43 | } |
| 44 | }) |
| 45 | |
| 46 | function muiIconsDeepImportPlugin() { |
| 47 | return { |
| 48 | name: 'mui-icons-deep-import', |
| 49 | enforce: 'pre' as const, |
| 50 | transform(code: string, id: string) { |
| 51 | if (!/\.[jt]sx?$/.test(id) || !code.includes(ICONS_MODULE)) |
| 52 | return |
| 53 | // deep imports keep Vite from pre-bundling the full icons barrel |
| 54 | const replaced = code.replace(ICON_IMPORT_RE, (full, specifiers) => { |
| 55 | const imports = specifiers.split(',') |
| 56 | .map(x => x.trim()) |
| 57 | .filter(Boolean) |
| 58 | .map(parseSpecifier) |
| 59 | const typeImports = imports.filter(x => TYPE_EXPORTS.has(x.imported)) |
| 60 | const valueImports = imports.filter(x => !TYPE_EXPORTS.has(x.imported)) |
| 61 | return [ |
| 62 | ...typeImports.length ? [`import type { ${typeImports.map(formatTypeImport).join(', ')} } from '${ICONS_MODULE}'`] : [], |
| 63 | ...valueImports.map(({ imported, local }) => `import ${local} from '${ICONS_MODULE}/${imported}'`), |
| 64 | ].join('\n') |
| 65 | }) |
| 66 | return replaced === code ? undefined : { code: replaced, map: null } |
| 67 | }, |
| 68 | } |
| 69 | |
| 70 | function parseSpecifier(specifier: string) { |
| 71 | const [imported, local = imported] = specifier.split(/\s+as\s+/) |
| 72 | return { imported: imported.trim(), local: local.trim() } |
| 73 | } |
| 74 | |
| 75 | function formatTypeImport({ imported, local }: ReturnType<typeof parseSpecifier>) { |
| 76 | return local === imported ? imported : `${imported} as ${local}` |
| 77 | } |
| 78 | } |
| 79 | |
| 80 | function getMuiIconImports() { |
| 81 | const icons = new Set<string>() |
| 82 | for (const dir of ['src', '../mui-grid-form']) |
| 83 | scan(resolve(DIR, dir)) |
| 84 | return [...icons].sort() |
| 85 | |
| 86 | function scan(path: string) { |
| 87 | const stat = statSync(path) |
| 88 | if (stat.isDirectory()) { |
| 89 | for (const name of readdirSync(path)) |
| 90 | scan(resolve(path, name)) |
| 91 | return |
| 92 | } |
| 93 | if (!['.js', '.jsx', '.ts', '.tsx'].includes(extname(path))) |
| 94 | return |
| 95 | for (const match of readFileSync(path, 'utf8').matchAll(ICON_IMPORT_RE)) { |
| 96 | for (const specifier of match[1].split(',')) { |
| 97 | const imported = specifier.trim().split(/\s+as\s+/)[0] |
| 98 | if (imported && !TYPE_EXPORTS.has(imported)) |
| 99 | icons.add(imported) |
| 100 | } |
| 101 | } |
| 102 | } |
| 103 | } |