main
ts 103 lines 3.71 KB
Raw
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 }