dx: avoid big .vite files slowing down safari

Massimo Melina committed May 22, 2026 at 18:29 UTC 4f43d6233eab9253d8cd5997592896339e99573d
1 file changed +75 -12
admin/vite.config.ts
+75 -12
@@ -1,6 +1,12 @@
1 import { defineConfig } from 'vite'
2 -//import vitePluginImport from 'vite-plugin-babel-import';
3 -// package.json/devDependencies: "vite-plugin-babel-import": "github:rejetto/vite-plugin-babel-import"
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({
@@ -9,22 +15,20 @@ export default defineConfig({
15 emptyOutDir: true,
16 target: "es2015",
17 rollupOptions: {
12 - /* plugins: [
13 - vitePluginImport([
14 - { // speed up build process (~2s on my M1) by bringing "modules transformed" from 11k+ down to 1.7k+
15 - libraryName: '@mui/icons-material',
16 - libraryDirectory: '',
17 - libraryChangeCase: "camelCase",
18 - ignoreStyles: [],
19 - },
20 - ])
21 - ],*/
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',
@@ -39,3 +43,62 @@ export default defineConfig({
43 }
44 }
45 })
46 +
47 +function muiIconsDeepImportPlugin() {
48 + return {
49 + name: 'mui-icons-deep-import',
50 + enforce: 'pre' as const,
51 + transform(code: string, id: string) {
52 + if (!/\.[jt]sx?$/.test(id) || !code.includes(ICONS_MODULE))
53 + return
54 + // deep imports keep Vite from pre-bundling the full icons barrel
55 + const replaced = code.replace(ICON_IMPORT_RE, (full, specifiers) => {
56 + const imports = specifiers.split(',')
57 + .map(x => x.trim())
58 + .filter(Boolean)
59 + .map(parseSpecifier)
60 + const typeImports = imports.filter(x => TYPE_EXPORTS.has(x.imported))
61 + const valueImports = imports.filter(x => !TYPE_EXPORTS.has(x.imported))
62 + return [
63 + ...typeImports.length ? [`import type { ${typeImports.map(formatTypeImport).join(', ')} } from '${ICONS_MODULE}'`] : [],
64 + ...valueImports.map(({ imported, local }) => `import ${local} from '${ICONS_MODULE}/${imported}'`),
65 + ].join('\n')
66 + })
67 + return replaced === code ? undefined : { code: replaced, map: null }
68 + },
69 + }
70 +
71 + function parseSpecifier(specifier: string) {
72 + const [imported, local = imported] = specifier.split(/\s+as\s+/)
73 + return { imported: imported.trim(), local: local.trim() }
74 + }
75 +
76 + function formatTypeImport({ imported, local }: ReturnType<typeof parseSpecifier>) {
77 + return local === imported ? imported : `${imported} as ${local}`
78 + }
79 +}
80 +
81 +function getMuiIconImports() {
82 + const icons = new Set<string>()
83 + for (const dir of ['src', '../mui-grid-form'])
84 + scan(resolve(DIR, dir))
85 + return [...icons].sort()
86 +
87 + function scan(path: string) {
88 + const stat = statSync(path)
89 + if (stat.isDirectory()) {
90 + for (const name of readdirSync(path))
91 + scan(resolve(path, name))
92 + return
93 + }
94 + if (!['.js', '.jsx', '.ts', '.tsx'].includes(extname(path)))
95 + return
96 + for (const match of readFileSync(path, 'utf8').matchAll(ICON_IMPORT_RE)) {
97 + for (const specifier of match[1].split(',')) {
98 + const imported = specifier.trim().split(/\s+as\s+/)[0]
99 + if (imported && !TYPE_EXPORTS.has(imported))
100 + icons.add(imported)
101 + }
102 + }
103 + }
104 +}