Merge pull request #334 from LD-Reborn/331-bug-image-elements-do-not-have-explicit-width-and-height-lighthouse-warning

331 bug image elements do not have explicit width and height lighthouse warning
This commit is contained in:
LD50
2025-11-23 01:54:04 +01:00
committed by GitHub
17 changed files with 211 additions and 21 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -6,6 +6,7 @@ using Berufsschule_HAM.Services;
using Berufsschule_HAM.Models; using Berufsschule_HAM.Models;
using Berufsschule_HAM.HealthChecks; using Berufsschule_HAM.HealthChecks;
using System.Text.Json.Serialization; using System.Text.Json.Serialization;
using Microsoft.AspNetCore.ResponseCompression;
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
// Bind options // Bind options
@@ -50,11 +51,14 @@ builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationSc
builder.Services.AddResponseCompression(options => builder.Services.AddResponseCompression(options =>
{ {
options.EnableForHttps = true; options.EnableForHttps = true;
options.Providers.Add<GzipCompressionProvider>();
options.Providers.Add<BrotliCompressionProvider>();
options.MimeTypes = options.MimeTypes =
[ [
"text/plain", "text/plain",
"text/css", "text/css",
"application/javascript", "application/javascript",
"text/javascript",
"text/html", "text/html",
"application/xml", "application/xml",
"text/xml", "text/xml",
@@ -91,6 +95,7 @@ if (app.Environment.IsDevelopment())
app.UseSwaggerUI(); app.UseSwaggerUI();
} }
app.UseResponseCompression();
app.UseStaticFiles(new StaticFileOptions app.UseStaticFiles(new StaticFileOptions
{ {
OnPrepareResponse = ctx => OnPrepareResponse = ctx =>
@@ -118,7 +123,6 @@ app.UseStaticFiles(new StaticFileOptions
app.UseRouting(); app.UseRouting();
app.UseAuthorization(); app.UseAuthorization();
app.UseResponseCaching(); app.UseResponseCaching();
app.UseResponseCompression();
string[] supportedCultures = ["de", "en"]; string[] supportedCultures = ["de", "en"];
var localizationOptions = new RequestLocalizationOptions() var localizationOptions = new RequestLocalizationOptions()

View File

@@ -16,8 +16,8 @@
} }
<form id="updateSettings" style="margin-bottom: 4rem !important" method="post" asp-controller="Settings" asp-action="Admin"> <form id="updateSettings" style="margin-bottom: 4rem !important" method="post" asp-controller="Settings" asp-action="Admin">
<h4 class="fw-bold">@T["General settings"]</h4>
<div class="row g-3"> <div class="row g-3">
<h4 class="fw-bold">@T["General settings"]</h4>
<div class="col-md-3"> <div class="col-md-3">
<label class="form-label" for="updateHashAlgorithm">@T["Default hash algorithm"]</label> <label class="form-label" for="updateHashAlgorithm">@T["Default hash algorithm"]</label>
<select type="text" name="DefaultHashAlgorithm" id="updateHashAlgorithm" class="form-control"> <select type="text" name="DefaultHashAlgorithm" id="updateHashAlgorithm" class="form-control">

View File

@@ -12,14 +12,26 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - Berufsschule_HAM</title> <title>@ViewData["Title"] - Berufsschule_HAM</title>
<script type="importmap"></script> <script type="importmap"></script>
@* <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.min.css" asp-append-version="true"/> *@ <style>
<link rel="stylesheet" @if (Context.Request.Path.Value is not null)
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" {
crossorigin="anonymous"> string path = System.IO.Path.Combine("CriticalCSS", Context.Request.Path.Value.Trim('/').Replace("/", ".") + ".css");
if (File.Exists(path))
{
@Html.Raw(File.ReadAllText(path));
}
} else {
@Html.Raw(File.ReadAllText("CriticalCSS/_Layout.css"));
}
</style>
<link rel="preload" href="~/lib/bootstrap/dist/css/bootstrap.min.css" as="style"/>
<link rel="stylesheet" fetchpriority="high"
href="~/lib/bootstrap/dist/css/bootstrap.min.css"
media="print"
onload="this.media='all'">
@* <link rel="stylesheet" href="~/css/site.css" asp-append-version="true" /> *@ @* <link rel="stylesheet" href="~/css/site.css" asp-append-version="true" /> *@
<link rel="preload" href="~/css/site.css" as="style" onload="this.onload=null;this.rel='stylesheet'"> <link rel="preload" href="~/css/site.css" as="style" onload="this.onload=null;this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="~/css/site.css"></noscript> <noscript><link fetchpriority="high" rel="stylesheet" href="~/css/site.css"></noscript>
<link rel="stylesheet" href="~/Berufsschule_HAM.styles.css" asp-append-version="true" />
<script> <script>
window.appTranslations = { window.appTranslations = {
selectLocation: '@T["Select location"]', selectLocation: '@T["Select location"]',
@@ -38,9 +50,9 @@
</script> </script>
<header> <header>
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar bg border-bottom box-shadow mb-3"> <nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar bg border-bottom box-shadow mb-3">
<a href="#main-content" class="skip-link btn btn-primary">@T["Jump to content"]</a> <a href="#main-content" class="skip-link btn btn-primary" style="position: fixed; left: -10000px; z-index: 1000;">@T["Jump to content"]</a>
<div class="container-fluid"> <div class="container-fluid">
<a class="" asp-area="" asp-controller="Home" asp-action="Index"><img fetchpriority="high" src="/HAM_Banner_xs.png" alt="Logo" style="max-height: 40px; width: 123px;"></a> <a class="" asp-area="" asp-controller="Home" asp-action="Index"><img fetchpriority="high" src="/HAM_Banner_xs.png" alt="Logo" width="123" height="40" style="width: 123px; height: 40px;"></a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent" <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target=".navbar-collapse" aria-controls="navbarSupportedContent"
aria-expanded="false" aria-label="Toggle navigation"> aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span> <span class="navbar-toggler-icon"></span>

View File

@@ -46,14 +46,3 @@ button.accept-policy {
white-space: nowrap; white-space: nowrap;
line-height: 60px; line-height: 60px;
} }
.skip-link {
position: fixed;
left: -10000px;
z-index: 1000;
}
.skip-link:focus {
left: 10px;
top: 10px;
outline: none;
}

164
src/critical.js Normal file
View File

@@ -0,0 +1,164 @@
import { generate } from 'critical';
import fs from 'fs';
import path from 'path';
import puppeteer from 'puppeteer';
const browser = await puppeteer.launch();
const page = await browser.newPage();
// Login
await page.goto('http://localhost:5275/Home/Login');
await page.type('#username', 'admin');
await page.type('#password', 'Test1234.');
await page.click('button[type=submit]');
await page.waitForNavigation();
// Extract cookies
const cookies = await page.cookies();
await browser.close();
async function generateCriticalCSSForViews() {
const viewsDir = 'Views';
// Helper function to get all .cshtml files recursively
function getAllCshtmlFiles(dir) {
let results = [];
const list = fs.readdirSync(dir);
list.forEach(file => {
const filePath = path.join(dir, file);
const stat = fs.statSync(filePath);
if (stat && stat.isDirectory()) {
// Recursively get files from subdirectories
results = results.concat(getAllCshtmlFiles(filePath));
} else if (file.endsWith('.cshtml')) {
results.push(filePath);
}
});
return results;
}
// Helper function to convert file path to URL path
function filePathToUrlPath(filePath) {
// Remove 'Views/' prefix
let relativePath = filePath.replace(/^Views[\/\\]/, '');
// Remove .cshtml extension
relativePath = relativePath.replace(/\.cshtml$/, '');
// Convert to URL format (replace \ with / and capitalize first letter)
const urlPath = relativePath
.split(/[\/\\]/)
.map((segment, index) =>
index === 0 ? segment : segment.charAt(0).toUpperCase() + segment.slice(1)
)
.join('/');
// Handle the case where we have a single file (like Index.cshtml)
if (relativePath.includes('/')) {
// Convert to URL path format: Views/Home/Index.cshtml -> /Home/Index
return '/' + relativePath.replace(/\\/g, '/').replace(/\.cshtml$/, '');
} else {
// For files directly in Views folder (like Views/Index.cshtml)
return '/' + relativePath.replace(/\.cshtml$/, '');
}
}
// Get all .cshtml files
const cshtmlFiles = getAllCshtmlFiles(viewsDir);
// Create CriticalCSS directory if it doesn't exist
const criticalCssDir = 'CriticalCSS';
if (!fs.existsSync(criticalCssDir)) {
fs.mkdirSync(criticalCssDir, { recursive: true });
}
// Process each file
for (const file of cshtmlFiles) {
try {
const urlPath = filePathToUrlPath(file);
// Generate critical CSS
await generate({
src: `http://localhost:5275${urlPath}`,
inline: false,
width: 1920,
height: 1080,
penthouse: {
customHeaders: {
cookie: cookies.map(c => `${c.name}=${c.value}`).join('; ')
},
forceInclude: [
'[data-bs-theme="dark"]',
'.navbar',
'.dropdown-menu',
'.me-2',
'.align-items-center',
'.d-flex',
'.position-fixed', // print batch
'.bottom-0',
'.start-0',
'.m-4',
'.row', // elements
'.row>*',
'.g-3',
'.col-md-3',
'.text-center',
'.mb-3',
'.mb-4',
'.mt-3',
'.py-4',
'.text-center',
'h2',
'.form-control',
'.form-control-sm',
'.modal',
'.btn',
'.btn-sm',
'.btn-secondary',
'.btn-warning',
'.btn-danger',
'.rounded-circle', // user icon
'table', // table elements (users)
'.table>thead',
'.user-row > td',
'thead',
'tbody',
'th',
'tr',
'td',
'.table>:not(caption)>*>*',
'.table-striped>tbody>tr:nth-of-type(odd)>*',
'.gap-2',
'.table',
'.table-responsive',
'.align-middle'
]
},
target: {
css: path.join(criticalCssDir, urlPath.replace(/\//g, '.').replace(/^\./, '') + '.css')
}
});
console.log(`Critical CSS generated for: ${urlPath}`);
} catch (err) {
console.error(`Error processing ${file}:`, err);
}
}
console.log('All critical CSS files generated!');
}
// Run the function
generateCriticalCSSForViews().catch(console.error);
// How to run this:
// install dependencies:
// npm i -D critical
// npm install puppeteer
// run:
// node critical.js

View File

@@ -72,3 +72,13 @@ h3.modal-title {
h4.fw-bold, h4.card-title { h4.fw-bold, h4.card-title {
font-size: 1rem; font-size: 1rem;
} }
.table button {
word-break: normal;
}
.skip-link:focus {
left: 10px;
top: 10px;
outline: none;
}