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:
@@ -31,6 +31,7 @@
|
|||||||
"@table-library/react-table-library": "4.1.15",
|
"@table-library/react-table-library": "4.1.15",
|
||||||
"alova": "3.3.4",
|
"alova": "3.3.4",
|
||||||
"async-validator": "^4.2.5",
|
"async-validator": "^4.2.5",
|
||||||
|
"etag": "^1.8.1",
|
||||||
"formidable": "^3.5.4",
|
"formidable": "^3.5.4",
|
||||||
"jwt-decode": "^4.0.0",
|
"jwt-decode": "^4.0.0",
|
||||||
"magic-string": "^0.30.21",
|
"magic-string": "^0.30.21",
|
||||||
|
|||||||
9
interface/pnpm-lock.yaml
generated
9
interface/pnpm-lock.yaml
generated
@@ -35,6 +35,9 @@ importers:
|
|||||||
async-validator:
|
async-validator:
|
||||||
specifier: ^4.2.5
|
specifier: ^4.2.5
|
||||||
version: 4.2.5
|
version: 4.2.5
|
||||||
|
etag:
|
||||||
|
specifier: ^1.8.1
|
||||||
|
version: 1.8.1
|
||||||
formidable:
|
formidable:
|
||||||
specifier: ^3.5.4
|
specifier: ^3.5.4
|
||||||
version: 3.5.4
|
version: 3.5.4
|
||||||
@@ -1552,6 +1555,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
|
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
etag@1.8.1:
|
||||||
|
resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
exec-buffer@3.2.0:
|
exec-buffer@3.2.0:
|
||||||
resolution: {integrity: sha512-wsiD+2Tp6BWHoVv3B+5Dcx6E7u5zky+hUwOHjuH2hKSLR3dvRmX8fk8UD8uqQixHs4Wk6eDmiegVrMPjKj7wpA==}
|
resolution: {integrity: sha512-wsiD+2Tp6BWHoVv3B+5Dcx6E7u5zky+hUwOHjuH2hKSLR3dvRmX8fk8UD8uqQixHs4Wk6eDmiegVrMPjKj7wpA==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
@@ -4563,6 +4570,8 @@ snapshots:
|
|||||||
|
|
||||||
esutils@2.0.3: {}
|
esutils@2.0.3: {}
|
||||||
|
|
||||||
|
etag@1.8.1: {}
|
||||||
|
|
||||||
exec-buffer@3.2.0:
|
exec-buffer@3.2.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
execa: 0.7.0
|
execa: 0.7.0
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import crypto from 'crypto';
|
import etag from 'etag';
|
||||||
import {
|
import {
|
||||||
createWriteStream,
|
createWriteStream,
|
||||||
existsSync,
|
existsSync,
|
||||||
readFileSync,
|
readFileSync,
|
||||||
readdirSync,
|
readdirSync,
|
||||||
statSync,
|
|
||||||
unlinkSync
|
unlinkSync
|
||||||
} from 'fs';
|
} from 'fs';
|
||||||
import mime from 'mime-types';
|
import mime from 'mime-types';
|
||||||
@@ -36,7 +35,7 @@ const generateWWWClass =
|
|||||||
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((f) => `${INDENT.repeat(3)}handler("${f.uri}", "${f.mimeType}", ${f.variable}, ${f.size}, "${f.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)}}
|
||||||
};
|
};
|
||||||
`;
|
`;
|
||||||
@@ -71,7 +70,8 @@ const writeFile = (relativeFilePath, buffer) => {
|
|||||||
writeStream.write(`const uint8_t ${variable}[] = {`);
|
writeStream.write(`const uint8_t ${variable}[] = {`);
|
||||||
|
|
||||||
const zipBuffer = zlib.gzipSync(buffer, { level: 9 });
|
const zipBuffer = zlib.gzipSync(buffer, { level: 9 });
|
||||||
const hash = crypto.createHash('sha256').update(zipBuffer).digest('hex');
|
// const hash = crypto.createHash('sha256').update(zipBuffer).digest('hex');
|
||||||
|
const hash = etag(zipBuffer); // use smaller md5 instead of sha256
|
||||||
|
|
||||||
zipBuffer.forEach((b) => {
|
zipBuffer.forEach((b) => {
|
||||||
if (!(size % bytesPerLine)) {
|
if (!(size % bytesPerLine)) {
|
||||||
|
|||||||
@@ -2,8 +2,7 @@ import preact from '@preact/preset-vite';
|
|||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { visualizer } from 'rollup-plugin-visualizer';
|
import { visualizer } from 'rollup-plugin-visualizer';
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig, Plugin } from 'vite';
|
||||||
import { Plugin } from 'vite';
|
|
||||||
import viteImagemin from 'vite-plugin-imagemin';
|
import viteImagemin from 'vite-plugin-imagemin';
|
||||||
import viteTsconfigPaths from 'vite-tsconfig-paths';
|
import viteTsconfigPaths from 'vite-tsconfig-paths';
|
||||||
import zlib from 'zlib';
|
import zlib from 'zlib';
|
||||||
@@ -11,6 +10,29 @@ import zlib from 'zlib';
|
|||||||
// @ts-expect-error - mock server doesn't have type declarations
|
// @ts-expect-error - mock server doesn't have type declarations
|
||||||
import mockServer from '../mock-api/mockServer.js';
|
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
|
// Plugin to display bundle size information
|
||||||
const bundleSizeReporter = (): Plugin => {
|
const bundleSizeReporter = (): Plugin => {
|
||||||
return {
|
return {
|
||||||
@@ -18,144 +40,111 @@ const bundleSizeReporter = (): Plugin => {
|
|||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
writeBundle(options: any, bundle: any) {
|
writeBundle(options: any, bundle: any) {
|
||||||
console.log('\n📦 Bundle Size Report:');
|
console.log('\n📦 Bundle Size Report:');
|
||||||
console.log('='.repeat(50));
|
console.log(REPEAT_CHAR.repeat(REPEAT_COUNT));
|
||||||
|
|
||||||
let totalSize = 0;
|
const files: BundleFile[] = [];
|
||||||
const files: Array<{ name: string; size: number; gzipSize?: number }> = [];
|
const outDir = options.dir || DEFAULT_OUT_DIR;
|
||||||
|
|
||||||
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;
|
|
||||||
|
|
||||||
|
// 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 {
|
try {
|
||||||
const stats = fs.statSync(filePath);
|
const stats = fs.statSync(filePath);
|
||||||
size = stats.size;
|
const size = stats.size;
|
||||||
totalSize += size;
|
|
||||||
|
|
||||||
// Calculate gzip size
|
|
||||||
const fileContent = fs.readFileSync(filePath);
|
const fileContent = fs.readFileSync(filePath);
|
||||||
gzipSize = zlib.gzipSync(fileContent).length;
|
const gzipSize = zlib.gzipSync(fileContent).length;
|
||||||
|
|
||||||
files.push({
|
files.push({ name: fileName, size, gzipSize });
|
||||||
name: fileName,
|
|
||||||
size,
|
|
||||||
gzipSize
|
|
||||||
});
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.warn(`Could not read file ${fileName}:`, error);
|
console.warn(`Could not read file ${fileName}:`, error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort files by size (largest first)
|
|
||||||
files.sort((a, b) => b.size - a.size);
|
files.sort((a, b) => b.size - a.size);
|
||||||
|
|
||||||
// Display individual file sizes
|
|
||||||
files.forEach((file) => {
|
files.forEach((file) => {
|
||||||
const sizeKB = (file.size / 1024).toFixed(2);
|
const sizeKB = (file.size / KB_DIVISOR).toFixed(2);
|
||||||
const gzipKB = file.gzipSize ? (file.gzipSize / 1024).toFixed(2) : 'N/A';
|
const gzipKB = (file.gzipSize / KB_DIVISOR).toFixed(2);
|
||||||
console.log(
|
console.log(
|
||||||
`📄 ${file.name.padEnd(30)} ${sizeKB.padStart(8)} KB (${gzipKB} KB gzipped)`
|
`📄 ${file.name.padEnd(30)} ${sizeKB.padStart(8)} KB (${gzipKB} KB gzipped)`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
console.log('='.repeat(50));
|
const totalSize = files.reduce((sum, file) => sum + file.size, 0);
|
||||||
console.log(`📊 Total Bundle Size: ${(totalSize / 1024).toFixed(2)} KB`);
|
const totalGzipSize = files.reduce((sum, file) => sum + file.gzipSize, 0);
|
||||||
|
const compressionRatio = ((totalSize - totalGzipSize) / totalSize) * 100;
|
||||||
|
|
||||||
// Calculate and display gzip total
|
console.log(REPEAT_CHAR.repeat(REPEAT_COUNT));
|
||||||
const totalGzipSize = files.reduce(
|
console.log(`📊 Total Bundle Size: ${(totalSize / KB_DIVISOR).toFixed(2)} KB`);
|
||||||
(sum, file) => sum + (file.gzipSize || 0),
|
console.log(`🗜️ Total Gzipped Size: ${(totalGzipSize / KB_DIVISOR).toFixed(2)} KB`);
|
||||||
0
|
console.log(`📈 Compression Ratio: ${compressionRatio.toFixed(1)}%`);
|
||||||
);
|
console.log(REPEAT_CHAR.repeat(REPEAT_COUNT));
|
||||||
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(
|
// Common preact plugin config
|
||||||
({ command, mode }: { command: string; mode: string }) => {
|
const createPreactPlugin = (devToolsEnabled: boolean) =>
|
||||||
if (command === 'serve') {
|
|
||||||
console.log('Preparing for standalone build with server, mode=' + mode);
|
|
||||||
return {
|
|
||||||
plugins: [
|
|
||||||
preact({
|
preact({
|
||||||
// Keep dev tools enabled for development
|
devToolsEnabled,
|
||||||
devToolsEnabled: true,
|
|
||||||
prefreshEnabled: false
|
prefreshEnabled: false
|
||||||
}),
|
});
|
||||||
viteTsconfigPaths(),
|
|
||||||
bundleSizeReporter(), // Add bundle size reporting
|
|
||||||
mockServer()
|
|
||||||
],
|
|
||||||
resolve: {
|
|
||||||
alias: {
|
|
||||||
react: 'preact/compat',
|
|
||||||
'react-dom': 'preact/compat',
|
|
||||||
'react/jsx-runtime': 'preact/jsx-runtime'
|
|
||||||
}
|
|
||||||
},
|
|
||||||
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') {
|
// Common base plugins
|
||||||
console.log('Preparing for hosted build');
|
const createBasePlugins = (devToolsEnabled: boolean, includeBundleReporter = true) => {
|
||||||
return {
|
const plugins = [
|
||||||
plugins: [
|
createPreactPlugin(devToolsEnabled),
|
||||||
preact({
|
viteTsconfigPaths()
|
||||||
// Enable Preact optimizations for hosted build
|
];
|
||||||
devToolsEnabled: false,
|
if (includeBundleReporter) {
|
||||||
prefreshEnabled: false
|
plugins.push(bundleSizeReporter());
|
||||||
}),
|
|
||||||
viteTsconfigPaths(),
|
|
||||||
bundleSizeReporter() // Add bundle size reporting
|
|
||||||
],
|
|
||||||
resolve: {
|
|
||||||
alias: {
|
|
||||||
react: 'preact/compat',
|
|
||||||
'react-dom': 'preact/compat',
|
|
||||||
'react/jsx-runtime': 'preact/jsx-runtime'
|
|
||||||
}
|
}
|
||||||
},
|
return plugins;
|
||||||
build: {
|
};
|
||||||
target: 'es2020',
|
|
||||||
chunkSizeWarningLimit: 512,
|
// Manual chunk splitting strategy
|
||||||
minify: 'terser',
|
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,
|
cssMinify: true,
|
||||||
assetsInlineLimit: 4096,
|
assetsInlineLimit: ASSETS_INLINE_LIMIT
|
||||||
terserOptions: {
|
});
|
||||||
|
|
||||||
|
// Terser options for hosted builds
|
||||||
|
const createHostedTerserOptions = () => ({
|
||||||
compress: {
|
compress: {
|
||||||
passes: 3,
|
passes: 3,
|
||||||
drop_console: true,
|
drop_console: true,
|
||||||
@@ -166,40 +155,34 @@ export default defineConfig(
|
|||||||
mangle: {
|
mangle: {
|
||||||
toplevel: true
|
toplevel: true
|
||||||
},
|
},
|
||||||
ecma: 2020
|
ecma: 2020 as const
|
||||||
},
|
});
|
||||||
rollupOptions: {
|
|
||||||
treeshake: {
|
|
||||||
moduleSideEffects: false
|
|
||||||
},
|
|
||||||
output: {
|
|
||||||
manualChunks(id: string) {
|
|
||||||
if (id.includes('node_modules')) {
|
|
||||||
if (id.includes('preact')) {
|
|
||||||
return '@preact';
|
|
||||||
}
|
|
||||||
return 'vendor';
|
|
||||||
}
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('Preparing for production, optimized build');
|
// 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
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
// Image optimization plugin
|
||||||
plugins: [
|
const imageOptimizationPlugin = {
|
||||||
preact({
|
|
||||||
// Enable Preact optimizations
|
|
||||||
devToolsEnabled: false,
|
|
||||||
prefreshEnabled: false
|
|
||||||
}),
|
|
||||||
viteTsconfigPaths(),
|
|
||||||
// Enable image optimization for size reduction
|
|
||||||
{
|
|
||||||
...viteImagemin({
|
...viteImagemin({
|
||||||
verbose: false,
|
verbose: false,
|
||||||
gifsicle: {
|
gifsicle: {
|
||||||
@@ -218,92 +201,92 @@ export default defineConfig(
|
|||||||
},
|
},
|
||||||
svgo: {
|
svgo: {
|
||||||
plugins: [
|
plugins: [
|
||||||
{
|
{ name: 'removeViewBox' },
|
||||||
name: 'removeViewBox'
|
{ name: 'removeEmptyAttrs', active: false }
|
||||||
},
|
|
||||||
{
|
|
||||||
name: 'removeEmptyAttrs',
|
|
||||||
active: false
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
enforce: 'pre'
|
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}`);
|
||||||
|
return {
|
||||||
|
plugins: [
|
||||||
|
...createBasePlugins(true, true),
|
||||||
|
mockServer()
|
||||||
|
],
|
||||||
|
resolve: {
|
||||||
|
alias: RESOLVE_ALIASES
|
||||||
},
|
},
|
||||||
|
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'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
target: ES_TARGET,
|
||||||
|
minify: false,
|
||||||
|
sourcemap: true
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode === 'hosted') {
|
||||||
|
console.log('Preparing for hosted build');
|
||||||
|
return {
|
||||||
|
plugins: createBasePlugins(false, true),
|
||||||
|
resolve: {
|
||||||
|
alias: RESOLVE_ALIASES
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
...createBaseBuildConfig(),
|
||||||
|
minify: 'terser' as const,
|
||||||
|
terserOptions: createHostedTerserOptions(),
|
||||||
|
rollupOptions: {
|
||||||
|
treeshake: {
|
||||||
|
moduleSideEffects: false
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
manualChunks: createManualChunks(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Preparing for production, optimized build');
|
||||||
|
|
||||||
|
return {
|
||||||
|
plugins: [
|
||||||
|
...createBasePlugins(false, true),
|
||||||
|
imageOptimizationPlugin,
|
||||||
visualizer({
|
visualizer({
|
||||||
template: 'treemap', // or sunburst
|
template: 'treemap',
|
||||||
open: false,
|
open: false,
|
||||||
gzipSize: true,
|
gzipSize: true,
|
||||||
brotliSize: true,
|
brotliSize: true,
|
||||||
filename: '../analyse.html' // will be saved in project's root
|
filename: '../analyse.html'
|
||||||
}),
|
})
|
||||||
bundleSizeReporter() // Add bundle size reporting
|
|
||||||
],
|
],
|
||||||
|
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: RESOLVE_ALIASES
|
||||||
react: 'preact/compat',
|
|
||||||
'react-dom': 'preact/compat',
|
|
||||||
'react/jsx-runtime': 'preact/jsx-runtime'
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
build: {
|
build: {
|
||||||
// Target modern browsers for smaller bundles
|
...createBaseBuildConfig(),
|
||||||
target: 'es2020',
|
minify: 'terser' as const,
|
||||||
chunkSizeWarningLimit: 512,
|
terserOptions: createProductionTerserOptions(),
|
||||||
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
|
|
||||||
},
|
|
||||||
|
|
||||||
rollupOptions: {
|
rollupOptions: {
|
||||||
// Enable aggressive tree shaking
|
|
||||||
treeshake: {
|
treeshake: {
|
||||||
moduleSideEffects: false,
|
moduleSideEffects: false,
|
||||||
propertyReadSideEffects: false,
|
propertyReadSideEffects: false,
|
||||||
@@ -311,65 +294,11 @@ export default defineConfig(
|
|||||||
unknownGlobalSideEffects: false
|
unknownGlobalSideEffects: false
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
// Optimize chunk naming for better caching
|
|
||||||
chunkFileNames: 'assets/[name]-[hash].js',
|
chunkFileNames: 'assets/[name]-[hash].js',
|
||||||
entryFileNames: 'assets/[name]-[hash].js',
|
entryFileNames: 'assets/[name]-[hash].js',
|
||||||
assetFileNames: 'assets/[name]-[hash].[ext]',
|
assetFileNames: 'assets/[name]-[hash].[ext]',
|
||||||
manualChunks(id: string) {
|
manualChunks: createManualChunks(true),
|
||||||
if (id.includes('node_modules')) {
|
sourcemap: false
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,47 +19,49 @@ ESP32React::ESP32React(AsyncWebServer * server, FS * fs)
|
|||||||
// Serve static web resources
|
// Serve static web resources
|
||||||
//
|
//
|
||||||
|
|
||||||
// Populate the last modification date based on build datetime
|
ArRequestHandlerFunction indexHtmlHandler = nullptr;
|
||||||
static char last_modified[50];
|
|
||||||
sprintf(last_modified, "%s %s CET", __DATE__, __TIME__);
|
|
||||||
|
|
||||||
WWWData::registerRoutes([server](const char * uri, const String & contentType, const uint8_t * content, size_t len, const String & hash) {
|
WWWData::registerRoutes([server, &indexHtmlHandler](const char * uri, const String & contentType, const uint8_t * content, size_t len, const String & hash) {
|
||||||
ArRequestHandlerFunction requestHandler = [contentType, content, len, hash](AsyncWebServerRequest * request) {
|
ArRequestHandlerFunction requestHandler = [contentType, content, len, hash](AsyncWebServerRequest * request) {
|
||||||
|
AsyncWebServerResponse * response;
|
||||||
|
|
||||||
// Check if the client already has the same version and respond with a 304 (Not modified)
|
// Check if the client already has the same version and respond with a 304 (Not modified)
|
||||||
if (request->header("If-Modified-Since").indexOf(last_modified) > 0) {
|
if (request->header("If-None-Match").equals(hash)) {
|
||||||
return request->send(304);
|
response = request->beginResponse(304);
|
||||||
} else if (request->header("If-None-Match").equals(hash)) {
|
} else {
|
||||||
return request->send(304);
|
response = request->beginResponse(200, contentType, content, len);
|
||||||
|
response->addHeader("Content-Encoding", "gzip"); // not br for brotlin only works over HTTPS
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncWebServerResponse * response = request->beginResponse(200, contentType, content, len);
|
// always send these headers - see https://datatracker.ietf.org/doc/html/rfc7232#section-4.1
|
||||||
|
|
||||||
response->addHeader("Content-Encoding", "gzip");
|
|
||||||
// response->addHeader("Content-Encoding", "br"); // only works over HTTPS
|
|
||||||
// response->addHeader("Cache-Control", "public, immutable, max-age=31536000");
|
|
||||||
response->addHeader("Cache-Control", "must-revalidate"); // ensure that a client will check the server for a change
|
|
||||||
response->addHeader("Last-Modified", last_modified);
|
|
||||||
response->addHeader("ETag", hash);
|
response->addHeader("ETag", hash);
|
||||||
|
response->addHeader("Cache-Control", "no-cache"); // Requires revalidation before using cached content (ETags enable 304 responses)
|
||||||
|
|
||||||
request->send(response);
|
request->send(response);
|
||||||
};
|
};
|
||||||
|
|
||||||
server->on(uri, HTTP_GET, requestHandler);
|
server->on(uri, HTTP_GET, requestHandler);
|
||||||
|
|
||||||
|
// Capture index.html handler to set onNotFound once after all routes are registered
|
||||||
|
if (strcmp(uri, "/index.html") == 0) {
|
||||||
|
indexHtmlHandler = requestHandler;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set onNotFound handler once after all routes are registered
|
||||||
// Serving non matching get requests with "/index.html"
|
// Serving non matching get requests with "/index.html"
|
||||||
// OPTIONS get a straight up 200 response
|
// OPTIONS get a straight up 200 response
|
||||||
if (strncmp(uri, "/index.html", 11) == 0) {
|
if (indexHtmlHandler != nullptr) {
|
||||||
server->onNotFound([requestHandler](AsyncWebServerRequest * request) {
|
server->onNotFound([indexHtmlHandler](AsyncWebServerRequest * request) {
|
||||||
if (request->method() == HTTP_GET) {
|
if (request->method() == HTTP_GET) {
|
||||||
requestHandler(request);
|
indexHtmlHandler(request);
|
||||||
} else if (request->method() == HTTP_OPTIONS) {
|
} else if (request->method() == HTTP_OPTIONS) {
|
||||||
request->send(200);
|
request->send(200);
|
||||||
} else {
|
} else {
|
||||||
request->send(404);
|
request->send(404); // not found
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ESP32React::begin() {
|
void ESP32React::begin() {
|
||||||
|
|||||||
Reference in New Issue
Block a user