Move to tailwind-build instead of CDNs
@@ -11,3 +11,27 @@ torrents.json
|
|||||||
*.json
|
*.json
|
||||||
.ven/**
|
.ven/**
|
||||||
docs/**
|
docs/**
|
||||||
|
|
||||||
|
|
||||||
|
# Don't copy built assets to avoid conflicts
|
||||||
|
pkg/web/assets/build/
|
||||||
|
|
||||||
|
# Don't copy node modules
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Don't copy development files
|
||||||
|
.git/
|
||||||
|
.gitignore
|
||||||
|
*.md
|
||||||
|
.env*
|
||||||
|
*.log
|
||||||
|
|
||||||
|
# Don't copy source assets to the Go builder (they're copied from asset-builder)
|
||||||
|
pkg/web/assets/css/
|
||||||
|
pkg/web/assets/js/
|
||||||
|
pkg/web/assets/images/
|
||||||
|
|
||||||
|
# Build artifacts
|
||||||
|
decypharr
|
||||||
|
healthcheck
|
||||||
|
*.exe
|
||||||
@@ -7,6 +7,7 @@ on:
|
|||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: write
|
contents: write
|
||||||
|
packages: write
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
goreleaser:
|
goreleaser:
|
||||||
@@ -22,6 +23,29 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
go-version: '1.24'
|
go-version: '1.24'
|
||||||
|
|
||||||
|
- name: Set up Node.js
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: '18'
|
||||||
|
cache: 'npm'
|
||||||
|
|
||||||
|
- name: Install Node.js dependencies
|
||||||
|
run: npm ci
|
||||||
|
|
||||||
|
- name: Build and minify assets
|
||||||
|
run: npm run build-assets
|
||||||
|
|
||||||
|
- name: Verify assets were built
|
||||||
|
run: |
|
||||||
|
echo "🔍 Verifying built assets..."
|
||||||
|
ls -la pkg/web/assets/build/
|
||||||
|
echo ""
|
||||||
|
echo "📊 Asset sizes:"
|
||||||
|
du -sh pkg/web/assets/build/* 2>/dev/null || echo "No assets found"
|
||||||
|
echo ""
|
||||||
|
echo "📁 Total build directory size:"
|
||||||
|
du -sh pkg/web/assets/build/
|
||||||
|
|
||||||
- name: Run GoReleaser
|
- name: Run GoReleaser
|
||||||
uses: goreleaser/goreleaser-action@v5
|
uses: goreleaser/goreleaser-action@v5
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -13,3 +13,5 @@ torrents.json
|
|||||||
logs/**
|
logs/**
|
||||||
auth.json
|
auth.json
|
||||||
.ven/
|
.ven/
|
||||||
|
.env
|
||||||
|
node_modules/
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"name": "decypharr",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Media management tool",
|
||||||
|
"scripts": {
|
||||||
|
"build-css": "tailwindcss -i ./pkg/web/assets/styles.css -o ./pkg/web/assets/build/css/styles.css --minify",
|
||||||
|
"minify-js": "node scripts/minify-js.js",
|
||||||
|
"minify-css": "node scripts/minify-css.js",
|
||||||
|
"download-assets": "node scripts/download-assets.js",
|
||||||
|
"build": "npm run build-css && npm run minify-js && npm run minify-css && npm run download-assets",
|
||||||
|
"dev": "npm run build-assets && air"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"tailwindcss": "^3.4.0",
|
||||||
|
"daisyui": "^4.12.10",
|
||||||
|
"terser": "^5.24.0",
|
||||||
|
"clean-css": "^5.3.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
|
Before Width: | Height: | Size: 284 KiB After Width: | Height: | Size: 284 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 27 KiB |
|
Before Width: | Height: | Size: 665 B After Width: | Height: | Size: 665 B |
|
Before Width: | Height: | Size: 1.6 KiB After Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 184 KiB After Width: | Height: | Size: 184 KiB |
@@ -1,4 +1,7 @@
|
|||||||
/* Custom Styles for Decypharr */
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
|
||||||
/* Smooth transitions for all interactive elements */
|
/* Smooth transitions for all interactive elements */
|
||||||
* {
|
* {
|
||||||
@@ -6,7 +9,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* Context menu styles */
|
/* Context menu styles */
|
||||||
.context-menu {
|
.context-menu {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -9,12 +9,18 @@ import (
|
|||||||
func (wb *Web) Routes() http.Handler {
|
func (wb *Web) Routes() http.Handler {
|
||||||
r := chi.NewRouter()
|
r := chi.NewRouter()
|
||||||
|
|
||||||
staticFS, err := fs.Sub(content, "assets")
|
// Load static files from embedded filesystem
|
||||||
|
staticFS, err := fs.Sub(assetsEmbed, "assets/build")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
imagesFS, err := fs.Sub(imagesEmbed, "assets/images")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
r.Handle("/assets/*", http.StripPrefix("/assets/", http.FileServer(http.FS(staticFS))))
|
r.Handle("/assets/*", http.StripPrefix("/assets/", http.FileServer(http.FS(staticFS))))
|
||||||
|
r.Handle("/images/*", http.StripPrefix("/images/", http.FileServer(http.FS(imagesFS))))
|
||||||
|
|
||||||
r.Get("/login", wb.LoginHandler)
|
r.Get("/login", wb.LoginHandler)
|
||||||
r.Post("/login", wb.LoginHandler)
|
r.Post("/login", wb.LoginHandler)
|
||||||
|
|||||||
@@ -6,20 +6,13 @@
|
|||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Decypharr - {{.Title}}</title>
|
<title>Decypharr - {{.Title}}</title>
|
||||||
|
|
||||||
<!-- DaisyUI and Tailwind CSS -->
|
<link href="{{.URLBase}}assets/css/styles.css" rel="stylesheet" type="text/css" />
|
||||||
<link href="https://cdn.jsdelivr.net/npm/daisyui@4.12.10/dist/full.min.css" rel="stylesheet" type="text/css" />
|
<link href="{{.URLBase}}assets/css/bootstrap-icons.css" rel="stylesheet" type="text/css" />
|
||||||
<script src="https://cdn.tailwindcss.com"></script>
|
|
||||||
|
|
||||||
<!-- Icons -->
|
<link rel="apple-touch-icon" sizes="180x180" href="{{.URLBase}}images/favicon/apple-touch-icon.png">
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.2/font/bootstrap-icons.css" rel="stylesheet">
|
<link rel="icon" type="image/png" sizes="32x32" href="{{.URLBase}}images/favicon/favicon-32x32.png">
|
||||||
|
<link rel="icon" type="image/png" sizes="16x16" href="{{.URLBase}}images/favicon/favicon-16x16.png">
|
||||||
<!-- Custom Styles -->
|
<link rel="manifest" href="{{.URLBase}}images/favicon/site.webmanifest">
|
||||||
<link href="{{.URLBase}}assets/css/styles.css" rel="stylesheet">
|
|
||||||
|
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="{{.URLBase}}assetsfavicon/apple-touch-icon.png">
|
|
||||||
<link rel="icon" type="image/png" sizes="32x32" href="{{.URLBase}}assets/favicon/favicon-32x32.png">
|
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href="{{.URLBase}}assets/favicon/favicon-16x16.png">
|
|
||||||
<link rel="manifest" href="{{.URLBase}}assets/favicon/site.webmanifest">
|
|
||||||
|
|
||||||
<!-- Preload JavaScript -->
|
<!-- Preload JavaScript -->
|
||||||
<link rel="preload" href="{{.URLBase}}assets/js/common.js" as="script">
|
<link rel="preload" href="{{.URLBase}}assets/js/common.js" as="script">
|
||||||
@@ -79,7 +72,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<a class="btn btn-ghost text-xl font-bold text-primary group" href="{{.URLBase}}">
|
<a class="btn btn-ghost text-xl font-bold text-primary group" href="{{.URLBase}}">
|
||||||
<!-- Logo -->
|
<!-- Logo -->
|
||||||
<img src="{{.URLBase}}assets/logo.svg" alt="Decypharr Logo" class="w-8 h-8 inline-block mr-2">
|
<img src="{{.URLBase}}images/logo.svg" alt="Decypharr Logo" class="w-8 h-8 inline-block mr-2">
|
||||||
<span class="hidden sm:inline bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent">Decypharr</span>
|
<span class="hidden sm:inline bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent">Decypharr</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@@ -188,7 +181,7 @@
|
|||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
<!-- Scripts -->
|
<!-- Scripts -->
|
||||||
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
<script src="{{.URLBase}}assets/js/jquery-3.7.1.min.js"></script>
|
||||||
<script src="{{.URLBase}}assets/js/common.js"></script>
|
<script src="{{.URLBase}}assets/js/common.js"></script>
|
||||||
|
|
||||||
<!-- Page-specific scripts -->
|
<!-- Page-specific scripts -->
|
||||||
|
|||||||
@@ -47,9 +47,15 @@ type RepairRequest struct {
|
|||||||
AutoProcess bool `json:"autoProcess"`
|
AutoProcess bool `json:"autoProcess"`
|
||||||
}
|
}
|
||||||
|
|
||||||
//go:embed templates/* assets/*
|
//go:embed templates/*
|
||||||
var content embed.FS
|
var content embed.FS
|
||||||
|
|
||||||
|
//go:embed assets/build
|
||||||
|
var assetsEmbed embed.FS
|
||||||
|
|
||||||
|
//go:embed assets/images
|
||||||
|
var imagesEmbed embed.FS
|
||||||
|
|
||||||
type Web struct {
|
type Web struct {
|
||||||
logger zerolog.Logger
|
logger zerolog.Logger
|
||||||
cookie *sessions.CookieStore
|
cookie *sessions.CookieStore
|
||||||
|
|||||||
@@ -0,0 +1,112 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const https = require('https');
|
||||||
|
|
||||||
|
const buildDir = {
|
||||||
|
css: './pkg/web/assets/build/css',
|
||||||
|
js: './pkg/web/assets/build/js',
|
||||||
|
fonts: './pkg/web/assets/build/fonts'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create directories
|
||||||
|
Object.values(buildDir).forEach(dir => {
|
||||||
|
if (!fs.existsSync(dir)) {
|
||||||
|
fs.mkdirSync(dir, { recursive: true });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Download function
|
||||||
|
function downloadFile(url, filepath) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
console.log(`📥 Downloading ${path.basename(filepath)}...`);
|
||||||
|
|
||||||
|
const file = fs.createWriteStream(filepath);
|
||||||
|
|
||||||
|
https.get(url, (response) => {
|
||||||
|
if (response.statusCode === 200) {
|
||||||
|
response.pipe(file);
|
||||||
|
file.on('finish', () => {
|
||||||
|
file.close();
|
||||||
|
const stats = fs.statSync(filepath);
|
||||||
|
const size = (stats.size / 1024).toFixed(1) + 'KB';
|
||||||
|
console.log(` ✓ Downloaded ${path.basename(filepath)} (${size})`);
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
} else if (response.statusCode === 302 || response.statusCode === 301) {
|
||||||
|
downloadFile(response.headers.location, filepath).then(resolve).catch(reject);
|
||||||
|
} else {
|
||||||
|
reject(new Error(`Failed to download ${url}: ${response.statusCode}`));
|
||||||
|
}
|
||||||
|
}).on('error', reject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Download text content
|
||||||
|
function downloadText(url) {
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
https.get(url, (response) => {
|
||||||
|
let data = '';
|
||||||
|
response.on('data', chunk => data += chunk);
|
||||||
|
response.on('end', () => {
|
||||||
|
if (response.statusCode === 200) {
|
||||||
|
resolve(data);
|
||||||
|
} else {
|
||||||
|
reject(new Error(`Failed to download ${url}: ${response.statusCode}`));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}).on('error', reject);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Files to download
|
||||||
|
const downloads = [
|
||||||
|
{
|
||||||
|
url: 'https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.2/font/fonts/bootstrap-icons.woff',
|
||||||
|
path: path.join(buildDir.fonts, 'bootstrap-icons.woff')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: 'https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.2/font/fonts/bootstrap-icons.woff2',
|
||||||
|
path: path.join(buildDir.fonts, 'bootstrap-icons.woff2')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
url: 'https://code.jquery.com/jquery-3.7.1.min.js',
|
||||||
|
path: path.join(buildDir.js, 'jquery-3.7.1.min.js')
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
// Download all files
|
||||||
|
async function downloadAssets() {
|
||||||
|
console.log('📦 Downloading external assets...\n');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Download Bootstrap Icons CSS and fix paths
|
||||||
|
console.log('📥 Downloading Bootstrap Icons CSS...');
|
||||||
|
const biCSS = await downloadText('https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.2/font/bootstrap-icons.css');
|
||||||
|
|
||||||
|
// Fix font paths to point to our local fonts
|
||||||
|
const fixedCSS = biCSS.replace(
|
||||||
|
/url\("\.\/fonts\//g,
|
||||||
|
'url("../fonts/'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Write fixed CSS to source directory so it can be minified
|
||||||
|
const biCSSSourcePath = path.join('./pkg/web/assets/css', 'bootstrap-icons.css');
|
||||||
|
fs.writeFileSync(biCSSSourcePath, fixedCSS);
|
||||||
|
console.log(` ✓ Downloaded Bootstrap Icons CSS (${(fixedCSS.length/1024).toFixed(1)}KB)`);
|
||||||
|
|
||||||
|
// Download other assets
|
||||||
|
for (const download of downloads) {
|
||||||
|
await downloadFile(download.url, download.path);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('\n✅ External assets downloaded successfully!');
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('💥 Error downloading assets:', error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
downloadAssets();
|
||||||
@@ -0,0 +1,96 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const CleanCSS = require('clean-css');
|
||||||
|
|
||||||
|
const sourceDir = './pkg/web/assets/css';
|
||||||
|
const buildDir = './pkg/web/assets/build/css';
|
||||||
|
|
||||||
|
// Create build directory
|
||||||
|
if (!fs.existsSync(buildDir)) {
|
||||||
|
fs.mkdirSync(buildDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create source directory if it doesn't exist
|
||||||
|
if (!fs.existsSync(sourceDir)) {
|
||||||
|
fs.mkdirSync(sourceDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
const cleanCSS = new CleanCSS({
|
||||||
|
level: 2, // Aggressive optimization
|
||||||
|
returnPromise: false
|
||||||
|
});
|
||||||
|
|
||||||
|
function minifyFile(inputPath, outputPath) {
|
||||||
|
try {
|
||||||
|
console.log(`🎨 Minifying ${path.basename(inputPath)}...`);
|
||||||
|
|
||||||
|
const css = fs.readFileSync(inputPath, 'utf8');
|
||||||
|
const result = cleanCSS.minify(css);
|
||||||
|
|
||||||
|
if (result.errors.length > 0) {
|
||||||
|
throw new Error(result.errors.join('\n'));
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFileSync(outputPath, result.styles);
|
||||||
|
|
||||||
|
// Show size reduction
|
||||||
|
const originalSize = Buffer.byteLength(css, 'utf8');
|
||||||
|
const minifiedSize = Buffer.byteLength(result.styles, 'utf8');
|
||||||
|
const reduction = ((originalSize - minifiedSize) / originalSize * 100).toFixed(1);
|
||||||
|
|
||||||
|
console.log(` ✓ ${path.basename(inputPath)}: ${(originalSize/1024).toFixed(1)}KB → ${(minifiedSize/1024).toFixed(1)}KB (${reduction}% reduction)`);
|
||||||
|
|
||||||
|
return { original: originalSize, minified: minifiedSize };
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error(` ✗ Error minifying ${inputPath}:`, error.message);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function minifyAllCSS() {
|
||||||
|
console.log('🎨 Minifying additional CSS files...\n');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Get all CSS files from source directory (excluding the main styles.css which is built by Tailwind)
|
||||||
|
const cssFiles = fs.readdirSync(sourceDir).filter(file =>
|
||||||
|
file.endsWith('.css') && file !== 'styles.css'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (cssFiles.length === 0) {
|
||||||
|
console.log('ℹ️ No additional CSS files found to minify');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let totalOriginal = 0;
|
||||||
|
let totalMinified = 0;
|
||||||
|
let processedFiles = 0;
|
||||||
|
|
||||||
|
// Minify each file
|
||||||
|
cssFiles.forEach(file => {
|
||||||
|
const inputPath = path.join(sourceDir, file);
|
||||||
|
const outputPath = path.join(buildDir, file);
|
||||||
|
const result = minifyFile(inputPath, outputPath);
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
totalOriginal += result.original;
|
||||||
|
totalMinified += result.minified;
|
||||||
|
processedFiles++;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (processedFiles > 0) {
|
||||||
|
const totalReduction = ((totalOriginal - totalMinified) / totalOriginal * 100).toFixed(1);
|
||||||
|
console.log(`\n✅ Successfully minified ${processedFiles}/${cssFiles.length} additional CSS file(s)`);
|
||||||
|
console.log(`📊 Total: ${(totalOriginal/1024).toFixed(1)}KB → ${(totalMinified/1024).toFixed(1)}KB (${totalReduction}% reduction)`);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('💥 Error during CSS minification:', error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
minifyAllCSS();
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const { minify } = require('terser');
|
||||||
|
|
||||||
|
const sourceDir = './pkg/web/assets/js';
|
||||||
|
const buildDir = './pkg/web/assets/build/js';
|
||||||
|
|
||||||
|
// Create build directory
|
||||||
|
if (!fs.existsSync(buildDir)) {
|
||||||
|
fs.mkdirSync(buildDir, { recursive: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Minify options
|
||||||
|
const minifyOptions = {
|
||||||
|
compress: {
|
||||||
|
drop_console: false, // Keep console.log for debugging
|
||||||
|
drop_debugger: true,
|
||||||
|
dead_code: true,
|
||||||
|
unused: true,
|
||||||
|
sequences: true,
|
||||||
|
conditionals: true,
|
||||||
|
booleans: true,
|
||||||
|
if_return: true,
|
||||||
|
join_vars: true,
|
||||||
|
},
|
||||||
|
mangle: {
|
||||||
|
toplevel: false,
|
||||||
|
reserved: [
|
||||||
|
'$', 'jQuery', 'decypharrUtils', 'configManager', 'repairManager',
|
||||||
|
'RepairManager', 'RepairUtils', 'ConfigManager', 'window', 'document'
|
||||||
|
]
|
||||||
|
},
|
||||||
|
format: {
|
||||||
|
comments: false,
|
||||||
|
beautify: false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
async function minifyFile(inputPath, outputPath) {
|
||||||
|
try {
|
||||||
|
console.log(`🗜️ Minifying ${path.basename(inputPath)}...`);
|
||||||
|
|
||||||
|
const code = fs.readFileSync(inputPath, 'utf8');
|
||||||
|
const result = await minify(code, minifyOptions);
|
||||||
|
|
||||||
|
if (result.error) {
|
||||||
|
throw result.error;
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFileSync(outputPath, result.code);
|
||||||
|
|
||||||
|
// Show size reduction
|
||||||
|
const originalSize = fs.statSync(inputPath).size;
|
||||||
|
const minifiedSize = fs.statSync(outputPath).size;
|
||||||
|
const reduction = ((originalSize - minifiedSize) / originalSize * 100).toFixed(1);
|
||||||
|
|
||||||
|
console.log(` ✓ ${path.basename(inputPath)}: ${(originalSize/1024).toFixed(1)}KB → ${(minifiedSize/1024).toFixed(1)}KB (${reduction}% reduction)`);
|
||||||
|
|
||||||
|
return { original: originalSize, minified: minifiedSize };
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error(` ✗ Error minifying ${inputPath}:`, error.message);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function minifyAllJS() {
|
||||||
|
console.log('📦 Minifying JavaScript files...\n');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Check if source directory exists
|
||||||
|
if (!fs.existsSync(sourceDir)) {
|
||||||
|
console.log(`Creating source directory ${sourceDir}...`);
|
||||||
|
fs.mkdirSync(sourceDir, { recursive: true });
|
||||||
|
console.log('ℹ️ No JavaScript files found to minify');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get all JS files from source directory
|
||||||
|
const jsFiles = fs.readdirSync(sourceDir).filter(file => file.endsWith('.js'));
|
||||||
|
|
||||||
|
if (jsFiles.length === 0) {
|
||||||
|
console.log('ℹ️ No JavaScript files found to minify');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let totalOriginal = 0;
|
||||||
|
let totalMinified = 0;
|
||||||
|
let processedFiles = 0;
|
||||||
|
|
||||||
|
// Minify each file
|
||||||
|
for (const file of jsFiles) {
|
||||||
|
const inputPath = path.join(sourceDir, file);
|
||||||
|
const outputPath = path.join(buildDir, file);
|
||||||
|
const result = await minifyFile(inputPath, outputPath);
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
totalOriginal += result.original;
|
||||||
|
totalMinified += result.minified;
|
||||||
|
processedFiles++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (processedFiles > 0) {
|
||||||
|
const totalReduction = ((totalOriginal - totalMinified) / totalOriginal * 100).toFixed(1);
|
||||||
|
console.log(`\n✅ Successfully minified ${processedFiles}/${jsFiles.length} JavaScript file(s)`);
|
||||||
|
console.log(`📊 Total: ${(totalOriginal/1024).toFixed(1)}KB → ${(totalMinified/1024).toFixed(1)}KB (${totalReduction}% reduction)`);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('💥 Error during JavaScript minification:', error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
minifyAllJS();
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
module.exports = {
|
||||||
|
content: [
|
||||||
|
"./pkg/web/templates/**/*.html",
|
||||||
|
"./pkg/web/assets/**/*.js"
|
||||||
|
],
|
||||||
|
theme: {
|
||||||
|
extend: {},
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
require('daisyui'),
|
||||||
|
],
|
||||||
|
};
|
||||||