mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-06 07:49:52 +03:00
optimizations, use md5 for hash
This commit is contained in:
@@ -2,8 +2,7 @@ import preact from '@preact/preset-vite';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { visualizer } from 'rollup-plugin-visualizer';
|
||||
import { defineConfig } from 'vite';
|
||||
import { Plugin } from 'vite';
|
||||
import { defineConfig, Plugin } from 'vite';
|
||||
import viteImagemin from 'vite-plugin-imagemin';
|
||||
import viteTsconfigPaths from 'vite-tsconfig-paths';
|
||||
import zlib from 'zlib';
|
||||
@@ -11,6 +10,29 @@ import zlib from 'zlib';
|
||||
// @ts-expect-error - mock server doesn't have type declarations
|
||||
import mockServer from '../mock-api/mockServer.js';
|
||||
|
||||
// Constants
|
||||
const KB_DIVISOR = 1024;
|
||||
const REPEAT_CHAR = '=';
|
||||
const REPEAT_COUNT = 50;
|
||||
const DEFAULT_OUT_DIR = 'dist';
|
||||
const ES_TARGET = 'es2020';
|
||||
const CHUNK_SIZE_WARNING_LIMIT = 512;
|
||||
const ASSETS_INLINE_LIMIT = 4096;
|
||||
|
||||
// Common resolve aliases
|
||||
const RESOLVE_ALIASES = {
|
||||
react: 'preact/compat',
|
||||
'react-dom': 'preact/compat',
|
||||
'react/jsx-runtime': 'preact/jsx-runtime'
|
||||
};
|
||||
|
||||
// Bundle file interface
|
||||
interface BundleFile {
|
||||
name: string;
|
||||
size: number;
|
||||
gzipSize: number;
|
||||
}
|
||||
|
||||
// Plugin to display bundle size information
|
||||
const bundleSizeReporter = (): Plugin => {
|
||||
return {
|
||||
@@ -18,99 +40,190 @@ const bundleSizeReporter = (): Plugin => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
writeBundle(options: any, bundle: any) {
|
||||
console.log('\n📦 Bundle Size Report:');
|
||||
console.log('='.repeat(50));
|
||||
console.log(REPEAT_CHAR.repeat(REPEAT_COUNT));
|
||||
|
||||
let totalSize = 0;
|
||||
const files: Array<{ name: string; size: number; gzipSize?: number }> = [];
|
||||
|
||||
for (const [fileName, chunk] of Object.entries(
|
||||
bundle as Record<string, unknown>
|
||||
)) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
if ((chunk as any).type === 'chunk' || (chunk as any).type === 'asset') {
|
||||
const filePath = path.join((options.dir as string) || 'dist', fileName);
|
||||
let size = 0;
|
||||
let gzipSize = 0;
|
||||
const files: BundleFile[] = [];
|
||||
const outDir = options.dir || DEFAULT_OUT_DIR;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument
|
||||
const bundleEntries: Array<[string, any]> = Object.entries(bundle);
|
||||
for (const [fileName, chunk] of bundleEntries) {
|
||||
if (chunk?.type === 'chunk' || chunk?.type === 'asset') {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-argument
|
||||
const filePath = path.join(outDir, fileName);
|
||||
try {
|
||||
const stats = fs.statSync(filePath);
|
||||
size = stats.size;
|
||||
totalSize += size;
|
||||
|
||||
// Calculate gzip size
|
||||
const size = stats.size;
|
||||
const fileContent = fs.readFileSync(filePath);
|
||||
gzipSize = zlib.gzipSync(fileContent).length;
|
||||
const gzipSize = zlib.gzipSync(fileContent).length;
|
||||
|
||||
files.push({
|
||||
name: fileName,
|
||||
size,
|
||||
gzipSize
|
||||
});
|
||||
files.push({ name: fileName, size, gzipSize });
|
||||
} catch (error) {
|
||||
console.warn(`Could not read file ${fileName}:`, error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sort files by size (largest first)
|
||||
files.sort((a, b) => b.size - a.size);
|
||||
|
||||
// Display individual file sizes
|
||||
files.forEach((file) => {
|
||||
const sizeKB = (file.size / 1024).toFixed(2);
|
||||
const gzipKB = file.gzipSize ? (file.gzipSize / 1024).toFixed(2) : 'N/A';
|
||||
const sizeKB = (file.size / KB_DIVISOR).toFixed(2);
|
||||
const gzipKB = (file.gzipSize / KB_DIVISOR).toFixed(2);
|
||||
console.log(
|
||||
`📄 ${file.name.padEnd(30)} ${sizeKB.padStart(8)} KB (${gzipKB} KB gzipped)`
|
||||
);
|
||||
});
|
||||
|
||||
console.log('='.repeat(50));
|
||||
console.log(`📊 Total Bundle Size: ${(totalSize / 1024).toFixed(2)} KB`);
|
||||
const totalSize = files.reduce((sum, file) => sum + file.size, 0);
|
||||
const totalGzipSize = files.reduce((sum, file) => sum + file.gzipSize, 0);
|
||||
const compressionRatio = ((totalSize - totalGzipSize) / totalSize) * 100;
|
||||
|
||||
// Calculate and display gzip total
|
||||
const totalGzipSize = files.reduce(
|
||||
(sum, file) => sum + (file.gzipSize || 0),
|
||||
0
|
||||
);
|
||||
console.log(`🗜️ Total Gzipped Size: ${(totalGzipSize / 1024).toFixed(2)} KB`);
|
||||
|
||||
// Show compression ratio
|
||||
const compressionRatio = (
|
||||
((totalSize - totalGzipSize) / totalSize) *
|
||||
100
|
||||
).toFixed(1);
|
||||
console.log(`📈 Compression Ratio: ${compressionRatio}%`);
|
||||
|
||||
console.log('='.repeat(50));
|
||||
console.log(REPEAT_CHAR.repeat(REPEAT_COUNT));
|
||||
console.log(`📊 Total Bundle Size: ${(totalSize / KB_DIVISOR).toFixed(2)} KB`);
|
||||
console.log(`🗜️ Total Gzipped Size: ${(totalGzipSize / KB_DIVISOR).toFixed(2)} KB`);
|
||||
console.log(`📈 Compression Ratio: ${compressionRatio.toFixed(1)}%`);
|
||||
console.log(REPEAT_CHAR.repeat(REPEAT_COUNT));
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Common preact plugin config
|
||||
const createPreactPlugin = (devToolsEnabled: boolean) =>
|
||||
preact({
|
||||
devToolsEnabled,
|
||||
prefreshEnabled: false
|
||||
});
|
||||
|
||||
// Common base plugins
|
||||
const createBasePlugins = (devToolsEnabled: boolean, includeBundleReporter = true) => {
|
||||
const plugins = [
|
||||
createPreactPlugin(devToolsEnabled),
|
||||
viteTsconfigPaths()
|
||||
];
|
||||
if (includeBundleReporter) {
|
||||
plugins.push(bundleSizeReporter());
|
||||
}
|
||||
return plugins;
|
||||
};
|
||||
|
||||
// Manual chunk splitting strategy
|
||||
const createManualChunks = (detailed = false) => {
|
||||
return (id: string): string | undefined => {
|
||||
if (id.includes('node_modules')) {
|
||||
if (id.includes('preact')) return '@preact';
|
||||
if (detailed) {
|
||||
if (id.includes('react-router')) return '@react-router';
|
||||
if (id.includes('@mui/material')) return '@mui-material';
|
||||
if (id.includes('@mui/icons-material')) return '@mui-icons';
|
||||
if (id.includes('alova')) return '@alova';
|
||||
if (id.includes('typesafe-i18n')) return '@i18n';
|
||||
if (id.includes('react-toastify')) return '@toastify';
|
||||
if (id.includes('@table-library')) return '@table-library';
|
||||
if (id.includes('uuid')) return '@uuid';
|
||||
if (id.includes('axios') || id.includes('fetch')) return '@http';
|
||||
if (id.includes('lodash') || id.includes('ramda')) return '@utils';
|
||||
}
|
||||
return 'vendor';
|
||||
}
|
||||
if (detailed) {
|
||||
if (id.includes('components/')) return 'components';
|
||||
if (id.includes('app/')) return 'app';
|
||||
if (id.includes('utils/')) return 'utils';
|
||||
if (id.includes('api/')) return 'api';
|
||||
}
|
||||
return undefined;
|
||||
};
|
||||
};
|
||||
|
||||
// Common build base configuration
|
||||
const createBaseBuildConfig = () => ({
|
||||
target: ES_TARGET,
|
||||
chunkSizeWarningLimit: CHUNK_SIZE_WARNING_LIMIT,
|
||||
cssMinify: true,
|
||||
assetsInlineLimit: ASSETS_INLINE_LIMIT
|
||||
});
|
||||
|
||||
// Terser options for hosted builds
|
||||
const createHostedTerserOptions = () => ({
|
||||
compress: {
|
||||
passes: 3,
|
||||
drop_console: true,
|
||||
drop_debugger: true,
|
||||
dead_code: true,
|
||||
unused: true
|
||||
},
|
||||
mangle: {
|
||||
toplevel: true
|
||||
},
|
||||
ecma: 2020 as const
|
||||
});
|
||||
|
||||
// Terser options for production builds
|
||||
const createProductionTerserOptions = () => ({
|
||||
compress: {
|
||||
passes: 6,
|
||||
arrows: true,
|
||||
drop_console: true,
|
||||
drop_debugger: true,
|
||||
sequences: true
|
||||
},
|
||||
mangle: {
|
||||
toplevel: true,
|
||||
module: true
|
||||
},
|
||||
ecma: 2020 as const,
|
||||
enclose: false,
|
||||
keep_classnames: false,
|
||||
keep_fnames: false,
|
||||
ie8: false,
|
||||
module: false,
|
||||
safari10: false,
|
||||
toplevel: true
|
||||
});
|
||||
|
||||
// Image optimization plugin
|
||||
const imageOptimizationPlugin = {
|
||||
...viteImagemin({
|
||||
verbose: false,
|
||||
gifsicle: {
|
||||
optimizationLevel: 7,
|
||||
interlaced: false
|
||||
},
|
||||
optipng: {
|
||||
optimizationLevel: 7
|
||||
},
|
||||
mozjpeg: {
|
||||
quality: 20
|
||||
},
|
||||
pngquant: {
|
||||
quality: [0.8, 0.9],
|
||||
speed: 4
|
||||
},
|
||||
svgo: {
|
||||
plugins: [
|
||||
{ name: 'removeViewBox' },
|
||||
{ name: 'removeEmptyAttrs', active: false }
|
||||
]
|
||||
}
|
||||
}),
|
||||
enforce: 'pre' as const
|
||||
};
|
||||
|
||||
export default defineConfig(
|
||||
({ command, mode }: { command: string; mode: string }) => {
|
||||
if (command === 'serve') {
|
||||
console.log('Preparing for standalone build with server, mode=' + mode);
|
||||
console.log(`Preparing for standalone build with server, mode=${mode}`);
|
||||
return {
|
||||
plugins: [
|
||||
preact({
|
||||
// Keep dev tools enabled for development
|
||||
devToolsEnabled: true,
|
||||
prefreshEnabled: false
|
||||
}),
|
||||
viteTsconfigPaths(),
|
||||
bundleSizeReporter(), // Add bundle size reporting
|
||||
...createBasePlugins(true, true),
|
||||
mockServer()
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
react: 'preact/compat',
|
||||
'react-dom': 'preact/compat',
|
||||
'react/jsx-runtime': 'preact/jsx-runtime'
|
||||
}
|
||||
alias: RESOLVE_ALIASES
|
||||
},
|
||||
server: {
|
||||
open: true,
|
||||
port: mode == 'production' ? 4173 : 3000,
|
||||
port: mode === 'production' ? 4173 : 3000,
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://localhost:3080',
|
||||
@@ -118,14 +231,13 @@ export default defineConfig(
|
||||
secure: false
|
||||
},
|
||||
'/rest': 'http://localhost:3080',
|
||||
'/gh': 'http://localhost:3080' // mock for GitHub API
|
||||
'/gh': 'http://localhost:3080'
|
||||
}
|
||||
},
|
||||
// Optimize development builds
|
||||
build: {
|
||||
target: 'es2020',
|
||||
minify: false, // Disable minification for faster dev builds
|
||||
sourcemap: true // Enable source maps for debugging
|
||||
target: ES_TARGET,
|
||||
minify: false,
|
||||
sourcemap: true
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -133,55 +245,20 @@ export default defineConfig(
|
||||
if (mode === 'hosted') {
|
||||
console.log('Preparing for hosted build');
|
||||
return {
|
||||
plugins: [
|
||||
preact({
|
||||
// Enable Preact optimizations for hosted build
|
||||
devToolsEnabled: false,
|
||||
prefreshEnabled: false
|
||||
}),
|
||||
viteTsconfigPaths(),
|
||||
bundleSizeReporter() // Add bundle size reporting
|
||||
],
|
||||
plugins: createBasePlugins(false, true),
|
||||
resolve: {
|
||||
alias: {
|
||||
react: 'preact/compat',
|
||||
'react-dom': 'preact/compat',
|
||||
'react/jsx-runtime': 'preact/jsx-runtime'
|
||||
}
|
||||
alias: RESOLVE_ALIASES
|
||||
},
|
||||
build: {
|
||||
target: 'es2020',
|
||||
chunkSizeWarningLimit: 512,
|
||||
minify: 'terser',
|
||||
cssMinify: true,
|
||||
assetsInlineLimit: 4096,
|
||||
terserOptions: {
|
||||
compress: {
|
||||
passes: 3,
|
||||
drop_console: true,
|
||||
drop_debugger: true,
|
||||
dead_code: true,
|
||||
unused: true
|
||||
},
|
||||
mangle: {
|
||||
toplevel: true
|
||||
},
|
||||
ecma: 2020
|
||||
},
|
||||
...createBaseBuildConfig(),
|
||||
minify: 'terser' as const,
|
||||
terserOptions: createHostedTerserOptions(),
|
||||
rollupOptions: {
|
||||
treeshake: {
|
||||
moduleSideEffects: false
|
||||
},
|
||||
output: {
|
||||
manualChunks(id: string) {
|
||||
if (id.includes('node_modules')) {
|
||||
if (id.includes('preact')) {
|
||||
return '@preact';
|
||||
}
|
||||
return 'vendor';
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
manualChunks: createManualChunks(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -192,118 +269,24 @@ export default defineConfig(
|
||||
|
||||
return {
|
||||
plugins: [
|
||||
preact({
|
||||
// Enable Preact optimizations
|
||||
devToolsEnabled: false,
|
||||
prefreshEnabled: false
|
||||
}),
|
||||
viteTsconfigPaths(),
|
||||
// Enable image optimization for size reduction
|
||||
{
|
||||
...viteImagemin({
|
||||
verbose: false,
|
||||
gifsicle: {
|
||||
optimizationLevel: 7,
|
||||
interlaced: false
|
||||
},
|
||||
optipng: {
|
||||
optimizationLevel: 7
|
||||
},
|
||||
mozjpeg: {
|
||||
quality: 20
|
||||
},
|
||||
pngquant: {
|
||||
quality: [0.8, 0.9],
|
||||
speed: 4
|
||||
},
|
||||
svgo: {
|
||||
plugins: [
|
||||
{
|
||||
name: 'removeViewBox'
|
||||
},
|
||||
{
|
||||
name: 'removeEmptyAttrs',
|
||||
active: false
|
||||
}
|
||||
]
|
||||
}
|
||||
}),
|
||||
enforce: 'pre'
|
||||
},
|
||||
...createBasePlugins(false, true),
|
||||
imageOptimizationPlugin,
|
||||
visualizer({
|
||||
template: 'treemap', // or sunburst
|
||||
template: 'treemap',
|
||||
open: false,
|
||||
gzipSize: true,
|
||||
brotliSize: true,
|
||||
filename: '../analyse.html' // will be saved in project's root
|
||||
}),
|
||||
bundleSizeReporter() // Add bundle size reporting
|
||||
filename: '../analyse.html'
|
||||
})
|
||||
],
|
||||
|
||||
resolve: {
|
||||
alias: {
|
||||
react: 'preact/compat',
|
||||
'react-dom': 'preact/compat',
|
||||
'react/jsx-runtime': 'preact/jsx-runtime'
|
||||
}
|
||||
alias: RESOLVE_ALIASES
|
||||
},
|
||||
|
||||
build: {
|
||||
// Target modern browsers for smaller bundles
|
||||
target: 'es2020',
|
||||
chunkSizeWarningLimit: 512,
|
||||
minify: 'terser',
|
||||
// Enable CSS minification
|
||||
cssMinify: true,
|
||||
// Optimize asset handling
|
||||
assetsInlineLimit: 4096, // Inline small assets
|
||||
terserOptions: {
|
||||
compress: {
|
||||
passes: 6,
|
||||
arrows: true,
|
||||
drop_console: true,
|
||||
drop_debugger: true,
|
||||
sequences: true
|
||||
// Additional aggressive compression options
|
||||
// dead_code: true,
|
||||
// hoist_funs: true,
|
||||
// hoist_vars: true,
|
||||
// if_return: true,
|
||||
// join_vars: true,
|
||||
// loops: true,
|
||||
// pure_getters: true,
|
||||
// reduce_vars: true,
|
||||
// side_effects: false,
|
||||
// switches: true,
|
||||
// unsafe: true,
|
||||
// unsafe_arrows: true,
|
||||
// unsafe_comps: true,
|
||||
// unsafe_Function: true,
|
||||
// unsafe_math: true,
|
||||
// unsafe_proto: true,
|
||||
// unsafe_regexp: true,
|
||||
// unsafe_undefined: true,
|
||||
// unused: true
|
||||
},
|
||||
mangle: {
|
||||
toplevel: true, // Enable top-level mangling
|
||||
module: true // Enable module mangling
|
||||
// properties: {
|
||||
// regex: /^_/ // Mangle properties starting with _
|
||||
// }
|
||||
},
|
||||
ecma: 2020, // Updated to modern ECMAScript
|
||||
enclose: false,
|
||||
keep_classnames: false,
|
||||
keep_fnames: false,
|
||||
ie8: false,
|
||||
module: false,
|
||||
safari10: false,
|
||||
toplevel: true // Enable top-level optimization
|
||||
},
|
||||
|
||||
...createBaseBuildConfig(),
|
||||
minify: 'terser' as const,
|
||||
terserOptions: createProductionTerserOptions(),
|
||||
rollupOptions: {
|
||||
// Enable aggressive tree shaking
|
||||
treeshake: {
|
||||
moduleSideEffects: false,
|
||||
propertyReadSideEffects: false,
|
||||
@@ -311,65 +294,11 @@ export default defineConfig(
|
||||
unknownGlobalSideEffects: false
|
||||
},
|
||||
output: {
|
||||
// Optimize chunk naming for better caching
|
||||
chunkFileNames: 'assets/[name]-[hash].js',
|
||||
entryFileNames: 'assets/[name]-[hash].js',
|
||||
assetFileNames: 'assets/[name]-[hash].[ext]',
|
||||
manualChunks(id: string) {
|
||||
if (id.includes('node_modules')) {
|
||||
// More granular chunk splitting for better caching
|
||||
if (id.includes('react-router')) {
|
||||
return '@react-router';
|
||||
}
|
||||
if (id.includes('preact')) {
|
||||
return '@preact';
|
||||
}
|
||||
if (id.includes('@mui/material')) {
|
||||
return '@mui-material';
|
||||
}
|
||||
if (id.includes('@mui/icons-material')) {
|
||||
return '@mui-icons';
|
||||
}
|
||||
if (id.includes('alova')) {
|
||||
return '@alova';
|
||||
}
|
||||
if (id.includes('typesafe-i18n')) {
|
||||
return '@i18n';
|
||||
}
|
||||
if (id.includes('react-toastify')) {
|
||||
return '@toastify';
|
||||
}
|
||||
if (id.includes('@table-library')) {
|
||||
return '@table-library';
|
||||
}
|
||||
if (id.includes('uuid')) {
|
||||
return '@uuid';
|
||||
}
|
||||
if (id.includes('axios') || id.includes('fetch')) {
|
||||
return '@http';
|
||||
}
|
||||
if (id.includes('lodash') || id.includes('ramda')) {
|
||||
return '@utils';
|
||||
}
|
||||
return 'vendor';
|
||||
}
|
||||
// Split large application modules
|
||||
if (id.includes('components/')) {
|
||||
return 'components';
|
||||
}
|
||||
if (id.includes('app/')) {
|
||||
return 'app';
|
||||
}
|
||||
if (id.includes('utils/')) {
|
||||
return 'utils';
|
||||
}
|
||||
if (id.includes('api/')) {
|
||||
return 'api';
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
// Enable source maps for debugging (optional)
|
||||
sourcemap: false // Disable for production to save space
|
||||
manualChunks: createManualChunks(true),
|
||||
sourcemap: false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user