mirror of
https://github.com/emsesp/EMS-ESP32.git
synced 2025-12-06 07:49:52 +03:00
optimizations
This commit is contained in:
@@ -4,6 +4,7 @@ import {
|
|||||||
existsSync,
|
existsSync,
|
||||||
readFileSync,
|
readFileSync,
|
||||||
readdirSync,
|
readdirSync,
|
||||||
|
statSync,
|
||||||
unlinkSync
|
unlinkSync
|
||||||
} from 'fs';
|
} from 'fs';
|
||||||
import mime from 'mime-types';
|
import mime from 'mime-types';
|
||||||
@@ -15,67 +16,79 @@ const INDENT = ' ';
|
|||||||
const outputPath = '../src/ESP32React/WWWData.h';
|
const outputPath = '../src/ESP32React/WWWData.h';
|
||||||
const sourcePath = './dist';
|
const sourcePath = './dist';
|
||||||
const bytesPerLine = 20;
|
const bytesPerLine = 20;
|
||||||
var totalSize = 0;
|
let totalSize = 0;
|
||||||
|
let bundleStats = {
|
||||||
|
js: { count: 0, uncompressed: 0, compressed: 0 },
|
||||||
|
css: { count: 0, uncompressed: 0, compressed: 0 },
|
||||||
|
html: { count: 0, uncompressed: 0, compressed: 0 },
|
||||||
|
svg: { count: 0, uncompressed: 0, compressed: 0 },
|
||||||
|
other: { count: 0, uncompressed: 0, compressed: 0 }
|
||||||
|
};
|
||||||
|
|
||||||
const generateWWWClass = () =>
|
const generateWWWClass =
|
||||||
`typedef std::function<void(const char * uri, const String & contentType, const uint8_t * content, size_t len, const String & hash)> RouteRegistrationHandler;
|
() => `typedef std::function<void(const char * uri, const String & contentType, const uint8_t * content, size_t len, const String & hash)> RouteRegistrationHandler;
|
||||||
// Total size is ${totalSize} bytes
|
// Bundle Statistics:
|
||||||
|
// - Total compressed size: ${(totalSize / 1000).toFixed(1)} KB
|
||||||
|
// - Total uncompressed size: ${(Object.values(bundleStats).reduce((sum, stat) => sum + stat.uncompressed, 0) / 1000).toFixed(1)} KB
|
||||||
|
// - Compression ratio: ${(((Object.values(bundleStats).reduce((sum, stat) => sum + stat.uncompressed, 0) - totalSize) / Object.values(bundleStats).reduce((sum, stat) => sum + stat.uncompressed, 0)) * 100).toFixed(1)}%
|
||||||
|
// - Generated on: ${new Date().toISOString()}
|
||||||
|
|
||||||
class WWWData {
|
class WWWData {
|
||||||
${indent}public:
|
${INDENT}public:
|
||||||
${indent.repeat(2)}static void registerRoutes(RouteRegistrationHandler handler) {
|
${INDENT.repeat(2)}static void registerRoutes(RouteRegistrationHandler handler) {
|
||||||
${fileInfo.map((file) => `${indent.repeat(3)}handler("${file.uri}", "${file.mimeType}", ${file.variable}, ${file.size}, "${file.hash}");`).join('\n')}
|
${fileInfo.map((f) => `${INDENT.repeat(3)}handler("${f.uri}", "${f.mimeType}", ${f.variable}, ${f.size}, "${f.hash}");`).join('\n')}
|
||||||
${indent.repeat(2)}}
|
${INDENT.repeat(2)}}
|
||||||
};
|
};
|
||||||
`;
|
`;
|
||||||
|
|
||||||
function getFilesSync(dir, files = []) {
|
const getFilesSync = (dir, files = []) => {
|
||||||
readdirSync(dir, { withFileTypes: true }).forEach((entry) => {
|
readdirSync(dir, { withFileTypes: true }).forEach((entry) => {
|
||||||
const entryPath = resolve(dir, entry.name);
|
const entryPath = resolve(dir, entry.name);
|
||||||
if (entry.isDirectory()) {
|
entry.isDirectory() ? getFilesSync(entryPath, files) : files.push(entryPath);
|
||||||
getFilesSync(entryPath, files);
|
|
||||||
} else {
|
|
||||||
files.push(entryPath);
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
return files;
|
return files;
|
||||||
}
|
};
|
||||||
|
|
||||||
function cleanAndOpen(path) {
|
const cleanAndOpen = (path) => {
|
||||||
if (existsSync(path)) {
|
existsSync(path) && unlinkSync(path);
|
||||||
unlinkSync(path);
|
|
||||||
}
|
|
||||||
return createWriteStream(path, { flags: 'w+' });
|
return createWriteStream(path, { flags: 'w+' });
|
||||||
}
|
};
|
||||||
|
|
||||||
|
const getFileType = (filePath) => {
|
||||||
|
const ext = filePath.split('.').pop().toLowerCase();
|
||||||
|
if (ext === 'js') return 'js';
|
||||||
|
if (ext === 'css') return 'css';
|
||||||
|
if (ext === 'html') return 'html';
|
||||||
|
if (ext === 'svg') return 'svg';
|
||||||
|
return 'other';
|
||||||
|
};
|
||||||
|
|
||||||
const writeFile = (relativeFilePath, buffer) => {
|
const writeFile = (relativeFilePath, buffer) => {
|
||||||
const variable = 'ESP_REACT_DATA_' + fileInfo.length;
|
const variable = `ESP_REACT_DATA_${fileInfo.length}`;
|
||||||
const mimeType = mime.lookup(relativeFilePath);
|
const mimeType = mime.lookup(relativeFilePath);
|
||||||
var size = 0;
|
const fileType = getFileType(relativeFilePath);
|
||||||
writeStream.write('const uint8_t ' + variable + '[] = {');
|
let size = 0;
|
||||||
// const zipBuffer = zlib.brotliCompressSync(buffer, { quality: 1 });
|
writeStream.write(`const uint8_t ${variable}[] = {`);
|
||||||
const zipBuffer = zlib.gzipSync(buffer, { level: 9 });
|
|
||||||
|
|
||||||
// create sha
|
const zipBuffer = zlib.gzipSync(buffer, { level: 9 });
|
||||||
const hashSum = crypto.createHash('sha256');
|
const hash = crypto.createHash('sha256').update(zipBuffer).digest('hex');
|
||||||
hashSum.update(zipBuffer);
|
|
||||||
const hash = hashSum.digest('hex');
|
|
||||||
|
|
||||||
zipBuffer.forEach((b) => {
|
zipBuffer.forEach((b) => {
|
||||||
if (!(size % bytesPerLine)) {
|
if (!(size % bytesPerLine)) {
|
||||||
writeStream.write('\n');
|
writeStream.write('\n' + INDENT);
|
||||||
writeStream.write(indent);
|
|
||||||
}
|
}
|
||||||
writeStream.write('0x' + ('00' + b.toString(16).toUpperCase()).slice(-2) + ',');
|
writeStream.write('0x' + b.toString(16).toUpperCase().padStart(2, '0') + ',');
|
||||||
size++;
|
size++;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (size % bytesPerLine) {
|
size % bytesPerLine && writeStream.write('\n');
|
||||||
writeStream.write('\n');
|
|
||||||
}
|
|
||||||
|
|
||||||
writeStream.write('};\n\n');
|
writeStream.write('};\n\n');
|
||||||
|
|
||||||
|
// Update bundle statistics
|
||||||
|
bundleStats[fileType].count++;
|
||||||
|
bundleStats[fileType].uncompressed += buffer.length;
|
||||||
|
bundleStats[fileType].compressed += zipBuffer.length;
|
||||||
|
|
||||||
fileInfo.push({
|
fileInfo.push({
|
||||||
uri: '/' + relativeFilePath.replace(sep, '/'),
|
uri: '/' + relativeFilePath.replace(sep, '/'),
|
||||||
mimeType,
|
mimeType,
|
||||||
@@ -84,32 +97,52 @@ const writeFile = (relativeFilePath, buffer) => {
|
|||||||
hash
|
hash
|
||||||
});
|
});
|
||||||
|
|
||||||
// console.log(relativeFilePath + ' (size ' + size + ' bytes)');
|
|
||||||
totalSize += size;
|
totalSize += size;
|
||||||
};
|
};
|
||||||
|
|
||||||
// start
|
console.log(`Generating ${outputPath} from ${sourcePath}`);
|
||||||
console.log('Generating ' + outputPath + ' from ' + sourcePath);
|
|
||||||
const includes = ARDUINO_INCLUDES;
|
|
||||||
const indent = INDENT;
|
|
||||||
const fileInfo = [];
|
const fileInfo = [];
|
||||||
const writeStream = cleanAndOpen(resolve(outputPath));
|
const writeStream = cleanAndOpen(resolve(outputPath));
|
||||||
|
|
||||||
// includes
|
writeStream.write(ARDUINO_INCLUDES);
|
||||||
writeStream.write(includes);
|
|
||||||
|
|
||||||
// process static files
|
|
||||||
const buildPath = resolve(sourcePath);
|
const buildPath = resolve(sourcePath);
|
||||||
for (const filePath of getFilesSync(buildPath)) {
|
for (const filePath of getFilesSync(buildPath)) {
|
||||||
const readStream = readFileSync(filePath);
|
writeFile(relative(buildPath, filePath), readFileSync(filePath));
|
||||||
const relativeFilePath = relative(buildPath, filePath);
|
|
||||||
writeFile(relativeFilePath, readStream);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// add class
|
|
||||||
writeStream.write(generateWWWClass());
|
writeStream.write(generateWWWClass());
|
||||||
|
|
||||||
// end
|
|
||||||
writeStream.end();
|
writeStream.end();
|
||||||
|
|
||||||
console.log('Total size: ' + totalSize / 1000 + ' KB');
|
// Calculate and display bundle statistics
|
||||||
|
const totalUncompressed = Object.values(bundleStats).reduce(
|
||||||
|
(sum, stat) => sum + stat.uncompressed,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
const totalCompressed = Object.values(bundleStats).reduce(
|
||||||
|
(sum, stat) => sum + stat.compressed,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
const compressionRatio = (
|
||||||
|
((totalUncompressed - totalCompressed) / totalUncompressed) *
|
||||||
|
100
|
||||||
|
).toFixed(1);
|
||||||
|
|
||||||
|
console.log('\n📊 Bundle Size Analysis:');
|
||||||
|
console.log('='.repeat(50));
|
||||||
|
console.log(`Total compressed size: ${(totalSize / 1000).toFixed(1)} KB`);
|
||||||
|
console.log(`Total uncompressed size: ${(totalUncompressed / 1000).toFixed(1)} KB`);
|
||||||
|
console.log(`Compression ratio: ${compressionRatio}%`);
|
||||||
|
console.log('\n📁 File Type Breakdown:');
|
||||||
|
Object.entries(bundleStats).forEach(([type, stats]) => {
|
||||||
|
if (stats.count > 0) {
|
||||||
|
const ratio = (
|
||||||
|
((stats.uncompressed - stats.compressed) / stats.uncompressed) *
|
||||||
|
100
|
||||||
|
).toFixed(1);
|
||||||
|
console.log(
|
||||||
|
`${type.toUpperCase().padEnd(4)}: ${stats.count} files, ${(stats.uncompressed / 1000).toFixed(1)} KB → ${(stats.compressed / 1000).toFixed(1)} KB (${ratio}% compression)`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log('='.repeat(50));
|
||||||
|
|||||||
@@ -1,31 +1,108 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ESNext",
|
// Target modern browsers for better performance
|
||||||
|
"target": "ES2022",
|
||||||
"useDefineForClassFields": true,
|
"useDefineForClassFields": true,
|
||||||
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
|
||||||
"types": ["node"],
|
// Optimized library selection
|
||||||
|
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||||
|
"types": ["node", "vite/client"],
|
||||||
|
|
||||||
|
// JavaScript handling
|
||||||
"allowJs": false,
|
"allowJs": false,
|
||||||
"skipLibCheck": true,
|
"checkJs": false,
|
||||||
"esModuleInterop": false,
|
|
||||||
"allowSyntheticDefaultImports": true,
|
// Module system optimized for Vite
|
||||||
"strict": true,
|
|
||||||
"forceConsistentCasingInFileNames": true,
|
|
||||||
"noFallthroughCasesInSwitch": true,
|
|
||||||
"composite": true,
|
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"moduleResolution": "bundler",
|
"moduleResolution": "bundler",
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
"resolveJsonModule": true,
|
"resolveJsonModule": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
|
|
||||||
|
// Emit configuration
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"useUnknownInCatchVariables": false,
|
"declaration": false,
|
||||||
|
"declarationMap": false,
|
||||||
|
"sourceMap": false,
|
||||||
|
|
||||||
|
// React/JSX configuration
|
||||||
"jsx": "react-jsx",
|
"jsx": "react-jsx",
|
||||||
"noImplicitAny": false,
|
"jsxImportSource": "react",
|
||||||
"baseUrl": "src",
|
|
||||||
|
// Strict type checking for better code quality
|
||||||
|
"strict": true,
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"strictNullChecks": true,
|
||||||
|
"strictFunctionTypes": true,
|
||||||
|
"strictBindCallApply": true,
|
||||||
|
"strictPropertyInitialization": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noImplicitThis": true,
|
||||||
|
"noUnusedLocals": true,
|
||||||
|
"noUnusedParameters": true,
|
||||||
|
"exactOptionalPropertyTypes": true,
|
||||||
|
|
||||||
|
// Additional checks
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"noUncheckedIndexedAccess": true,
|
||||||
|
"useUnknownInCatchVariables": true,
|
||||||
|
|
||||||
|
// Performance optimizations
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"skipDefaultLibCheck": true,
|
||||||
|
"incremental": true,
|
||||||
|
"tsBuildInfoFile": ".tsbuildinfo",
|
||||||
|
|
||||||
|
// Path mapping for cleaner imports
|
||||||
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@": ["src"],
|
"@/*": ["src/*"],
|
||||||
"@/*": ["src/*"]
|
"@/components/*": ["src/components/*"],
|
||||||
|
"@/utils/*": ["src/utils/*"],
|
||||||
|
"@/types/*": ["src/types/*"],
|
||||||
|
"@/hooks/*": ["src/hooks/*"],
|
||||||
|
"@/services/*": ["src/services/*"],
|
||||||
|
"@/assets/*": ["src/assets/*"],
|
||||||
|
// Support for bare imports from src directory
|
||||||
|
"App": ["src/App"],
|
||||||
|
"AppRouting": ["src/AppRouting"],
|
||||||
|
"CustomTheme": ["src/CustomTheme"],
|
||||||
|
"SignIn": ["src/SignIn"],
|
||||||
|
"AuthenticatedRouting": ["src/AuthenticatedRouting"],
|
||||||
|
"env": ["src/env"],
|
||||||
|
"components": ["src/components"],
|
||||||
|
"contexts": ["src/contexts"],
|
||||||
|
"i18n": ["src/i18n"],
|
||||||
|
"utils": ["src/utils"],
|
||||||
|
"validators": ["src/validators"],
|
||||||
|
"types": ["src/types"],
|
||||||
|
"api": ["src/api"],
|
||||||
|
"app": ["src/app"],
|
||||||
|
// Wildcard patterns for subdirectories
|
||||||
|
"components/*": ["src/components/*"],
|
||||||
|
"contexts/*": ["src/contexts/*"],
|
||||||
|
"i18n/*": ["src/i18n/*"],
|
||||||
|
"utils/*": ["src/utils/*"],
|
||||||
|
"validators/*": ["src/validators/*"],
|
||||||
|
"types/*": ["src/types/*"],
|
||||||
|
"api/*": ["src/api/*"],
|
||||||
|
"app/*": ["src/app/*"]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"include": ["src/**/*", "vite.config.ts"],
|
"include": ["src/**/*", "vite.config.ts", "progmem-generator.js"],
|
||||||
"exclude": ["node_modules", "dist"]
|
"exclude": [
|
||||||
|
"node_modules",
|
||||||
|
"dist",
|
||||||
|
"build",
|
||||||
|
".tsbuildinfo",
|
||||||
|
"**/*.test.ts",
|
||||||
|
"**/*.test.tsx",
|
||||||
|
"**/*.spec.ts",
|
||||||
|
"**/*.spec.tsx"
|
||||||
|
],
|
||||||
|
"ts-node": {
|
||||||
|
"esm": true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,130 +1,330 @@
|
|||||||
import preact from '@preact/preset-vite';
|
import preact from '@preact/preset-vite';
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
import { visualizer } from 'rollup-plugin-visualizer';
|
import { visualizer } from 'rollup-plugin-visualizer';
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
// import viteImagemin from 'vite-plugin-imagemin';
|
import { Plugin } from 'vite';
|
||||||
|
import viteImagemin from 'vite-plugin-imagemin';
|
||||||
import viteTsconfigPaths from 'vite-tsconfig-paths';
|
import viteTsconfigPaths from 'vite-tsconfig-paths';
|
||||||
|
import zlib from 'zlib';
|
||||||
|
|
||||||
|
// @ts-expect-error - mock server doesn't have type declarations
|
||||||
import mockServer from '../mock-api/mockServer.js';
|
import mockServer from '../mock-api/mockServer.js';
|
||||||
|
|
||||||
export default defineConfig(({ command, mode }) => {
|
// Plugin to display bundle size information
|
||||||
if (command === 'serve') {
|
const bundleSizeReporter = (): Plugin => {
|
||||||
console.log('Preparing for standalone build with server, mode=' + mode);
|
|
||||||
return {
|
|
||||||
plugins: [preact(), viteTsconfigPaths(), mockServer()],
|
|
||||||
server: {
|
|
||||||
open: true,
|
|
||||||
port: mode == 'production' ? 4173 : 3000,
|
|
||||||
proxy: {
|
|
||||||
'/api': {
|
|
||||||
target: 'http://localhost:3080',
|
|
||||||
changeOrigin: true,
|
|
||||||
secure: false
|
|
||||||
},
|
|
||||||
'/rest': 'http://localhost:3080',
|
|
||||||
'/gh': 'http://localhost:3080' // mock for GitHub API
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mode === 'hosted') {
|
|
||||||
console.log('Preparing for hosted build');
|
|
||||||
return {
|
|
||||||
plugins: [preact(), viteTsconfigPaths()],
|
|
||||||
build: {
|
|
||||||
chunkSizeWarningLimit: 1024
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('Preparing for production, optimized build');
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
plugins: [
|
name: 'bundle-size-reporter',
|
||||||
preact(),
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
viteTsconfigPaths(),
|
writeBundle(options: any, bundle: any) {
|
||||||
// {
|
console.log('\n📦 Bundle Size Report:');
|
||||||
// ...viteImagemin({
|
console.log('='.repeat(50));
|
||||||
// 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'
|
|
||||||
// },
|
|
||||||
visualizer({
|
|
||||||
template: 'treemap', // or sunburst
|
|
||||||
open: false,
|
|
||||||
gzipSize: true,
|
|
||||||
brotliSize: true,
|
|
||||||
filename: '../analyse.html' // will be saved in project's root
|
|
||||||
})
|
|
||||||
],
|
|
||||||
|
|
||||||
build: {
|
let totalSize = 0;
|
||||||
// target: 'es2022',
|
const files: Array<{ name: string; size: number; gzipSize?: number }> = [];
|
||||||
chunkSizeWarningLimit: 1024,
|
|
||||||
minify: 'terser',
|
|
||||||
terserOptions: {
|
|
||||||
compress: {
|
|
||||||
passes: 4,
|
|
||||||
arrows: true,
|
|
||||||
drop_console: true,
|
|
||||||
drop_debugger: true,
|
|
||||||
sequences: true
|
|
||||||
},
|
|
||||||
mangle: {
|
|
||||||
// toplevel: true
|
|
||||||
// module: true
|
|
||||||
// properties: {
|
|
||||||
// regex: /^_/
|
|
||||||
// }
|
|
||||||
},
|
|
||||||
ecma: 5,
|
|
||||||
enclose: false,
|
|
||||||
keep_classnames: false,
|
|
||||||
keep_fnames: false,
|
|
||||||
ie8: false,
|
|
||||||
module: false,
|
|
||||||
safari10: false,
|
|
||||||
toplevel: false
|
|
||||||
},
|
|
||||||
|
|
||||||
rollupOptions: {
|
for (const [fileName, chunk] of Object.entries(
|
||||||
output: {
|
bundle as Record<string, unknown>
|
||||||
manualChunks(id: string) {
|
)) {
|
||||||
if (id.includes('node_modules')) {
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
// creating a chunk to react routes deps. Reducing the vendor chunk size
|
if ((chunk as any).type === 'chunk' || (chunk as any).type === 'asset') {
|
||||||
if (id.includes('react-router')) {
|
const filePath = path.join((options.dir as string) || 'dist', fileName);
|
||||||
return '@react-router';
|
let size = 0;
|
||||||
}
|
let gzipSize = 0;
|
||||||
return 'vendor';
|
|
||||||
}
|
try {
|
||||||
|
const stats = fs.statSync(filePath);
|
||||||
|
size = stats.size;
|
||||||
|
totalSize += size;
|
||||||
|
|
||||||
|
// Calculate gzip size
|
||||||
|
const fileContent = fs.readFileSync(filePath);
|
||||||
|
gzipSize = zlib.gzipSync(fileContent).length;
|
||||||
|
|
||||||
|
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';
|
||||||
|
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`);
|
||||||
|
|
||||||
|
// 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));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
};
|
||||||
|
|
||||||
|
export default defineConfig(
|
||||||
|
({ command, mode }: { command: string; mode: string }) => {
|
||||||
|
if (command === 'serve') {
|
||||||
|
console.log('Preparing for standalone build with server, mode=' + mode);
|
||||||
|
return {
|
||||||
|
plugins: [
|
||||||
|
preact({
|
||||||
|
// Keep dev tools enabled for development
|
||||||
|
devToolsEnabled: true,
|
||||||
|
prefreshEnabled: true
|
||||||
|
}),
|
||||||
|
viteTsconfigPaths(),
|
||||||
|
bundleSizeReporter(), // Add bundle size reporting
|
||||||
|
mockServer()
|
||||||
|
],
|
||||||
|
server: {
|
||||||
|
open: true,
|
||||||
|
port: mode == 'production' ? 4173 : 3000,
|
||||||
|
proxy: {
|
||||||
|
'/api': {
|
||||||
|
target: 'http://localhost:3080',
|
||||||
|
changeOrigin: true,
|
||||||
|
secure: false
|
||||||
|
},
|
||||||
|
'/rest': 'http://localhost:3080',
|
||||||
|
'/gh': 'http://localhost:3080' // mock for GitHub API
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// Optimize development builds
|
||||||
|
build: {
|
||||||
|
target: 'es2020',
|
||||||
|
minify: false, // Disable minification for faster dev builds
|
||||||
|
sourcemap: true // Enable source maps for debugging
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
],
|
||||||
|
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
|
||||||
|
},
|
||||||
|
rollupOptions: {
|
||||||
|
treeshake: {
|
||||||
|
moduleSideEffects: false
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
manualChunks(id: string) {
|
||||||
|
if (id.includes('node_modules')) {
|
||||||
|
if (id.includes('preact')) {
|
||||||
|
return '@preact';
|
||||||
|
}
|
||||||
|
return 'vendor';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Preparing for production, optimized build');
|
||||||
|
|
||||||
|
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'
|
||||||
|
},
|
||||||
|
visualizer({
|
||||||
|
template: 'treemap', // or sunburst
|
||||||
|
open: false,
|
||||||
|
gzipSize: true,
|
||||||
|
brotliSize: true,
|
||||||
|
filename: '../analyse.html' // will be saved in project's root
|
||||||
|
}),
|
||||||
|
bundleSizeReporter() // Add bundle size reporting
|
||||||
|
],
|
||||||
|
|
||||||
|
build: {
|
||||||
|
// Target modern browsers for smaller bundles
|
||||||
|
target: 'es2020',
|
||||||
|
chunkSizeWarningLimit: 512, // Reduced warning threshold
|
||||||
|
minify: 'terser',
|
||||||
|
// Enable CSS minification
|
||||||
|
cssMinify: true,
|
||||||
|
// Optimize asset handling
|
||||||
|
assetsInlineLimit: 4096, // Inline small assets
|
||||||
|
terserOptions: {
|
||||||
|
compress: {
|
||||||
|
passes: 6, // Increased passes for better compression
|
||||||
|
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
|
||||||
|
},
|
||||||
|
|
||||||
|
rollupOptions: {
|
||||||
|
// Enable tree shaking
|
||||||
|
treeshake: {
|
||||||
|
moduleSideEffects: false,
|
||||||
|
propertyReadSideEffects: false,
|
||||||
|
tryCatchDeoptimization: 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('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('pages/') || id.includes('routes/')) {
|
||||||
|
return 'pages';
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
|
// Enable source maps for debugging (optional)
|
||||||
|
sourcemap: false // Disable for production to save space
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user