Minification and compression
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/push/deploy Pipeline was successful

This commit is contained in:
2026-02-07 00:30:08 -03:00
parent 93e2618dcf
commit 840c6cdeba
9 changed files with 501 additions and 39 deletions

View File

@@ -1,6 +1,7 @@
/**
* Post-build: extract SvelteKit's inline bootstrap script to an external file
* and replace it with <script src="..."> so CSP can use script-src 'self' without unsafe-inline.
* Minifies the extracted JS.
*
* Usage: node scripts/externalize-inline-script.mjs [buildDir]
* buildDir: path to build output (default: "build"). Use from repo root.
@@ -9,6 +10,7 @@ import { readFileSync, writeFileSync, mkdirSync } from 'node:fs';
import { createHash } from 'node:crypto';
import { join } from 'node:path';
import { cwd } from 'node:process';
import * as esbuild from 'esbuild';
const buildDir = join(cwd(), process.argv[2] || 'build');
const htmlPath = join(buildDir, 'index.html');
@@ -66,33 +68,40 @@ function extractScriptContent(html, scriptStart) {
return null;
}
try {
let html = readFileSync(htmlPath, 'utf-8');
const found = findSvelteKitInlineScript(html);
if (!found) {
console.log('No SvelteKit inline bootstrap script found in', htmlPath);
process.exit(0);
async function main() {
try {
let html = readFileSync(htmlPath, 'utf-8');
const found = findSvelteKitInlineScript(html);
if (!found) {
console.log('No SvelteKit inline bootstrap script found in', htmlPath);
process.exit(0);
}
let content = found.content;
// Bootstrap runs from /_app/immutable/bootstrap.xxx.js; imports like "./_app/immutable/entry/..."
// would resolve to /_app/immutable/_app/immutable/entry/... (duplicate). Use directory-relative paths.
content = content.replace(/\.\/_app\/immutable\//g, './');
// Minify
const minified = await esbuild.transform(content, { minify: true, loader: 'js' });
content = minified.code;
const hash = createHash('sha256').update(content).digest('hex').slice(0, 8);
const filename = `bootstrap.${hash}.js`;
const immutableDir = join(buildDir, '_app', 'immutable');
mkdirSync(immutableDir, { recursive: true });
const scriptPath = join(immutableDir, filename);
writeFileSync(scriptPath, content, 'utf-8');
const scriptTag = `<script src="/_app/immutable/${filename}"></script>`;
html = html.slice(0, found.start) + scriptTag + html.slice(found.end);
// Use absolute paths for _app assets so they resolve correctly (host-based routing, redirects)
html = html.replace(/\.\/_app\//g, '/_app/');
writeFileSync(htmlPath, html, 'utf-8');
console.log('Externalized SvelteKit bootstrap to', scriptPath);
} catch (err) {
console.error(
'externalize-inline-script failed:',
err instanceof Error ? err.message : String(err),
);
process.exit(1);
}
let content = found.content;
// Bootstrap runs from /_app/immutable/bootstrap.xxx.js; imports like "./_app/immutable/entry/..."
// would resolve to /_app/immutable/_app/immutable/entry/... (duplicate). Use directory-relative paths.
content = content.replace(/\.\/_app\/immutable\//g, './');
const hash = createHash('sha256').update(content).digest('hex').slice(0, 8);
const filename = `bootstrap.${hash}.js`;
const immutableDir = join(buildDir, '_app', 'immutable');
mkdirSync(immutableDir, { recursive: true });
const scriptPath = join(immutableDir, filename);
writeFileSync(scriptPath, content.trimStart(), 'utf-8');
const scriptTag = `<script src="/_app/immutable/${filename}"></script>`;
html = html.slice(0, found.start) + scriptTag + html.slice(found.end);
// Use absolute paths for _app assets so they resolve correctly (host-based routing, redirects)
html = html.replace(/\.\/_app\//g, '/_app/');
writeFileSync(htmlPath, html, 'utf-8');
console.log('Externalized SvelteKit bootstrap to', scriptPath);
} catch (err) {
console.error(
'externalize-inline-script failed:',
err instanceof Error ? err.message : String(err),
);
process.exit(1);
}
main();

View File

@@ -0,0 +1,41 @@
/**
* Post-build: minify JS and CSS under build/assets (files copied from static/).
* Usage: node scripts/minify-static-assets.mjs [buildDir]
*/
import { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
import { join } from 'node:path';
import { cwd } from 'node:process';
import * as esbuild from 'esbuild';
const buildDir = join(cwd(), process.argv[2] || 'build');
const assetsDir = join(buildDir, 'assets');
function* walk(dir, base = '') {
for (const name of readdirSync(dir, { withFileTypes: true })) {
const rel = join(base, name.name);
if (name.isDirectory()) yield* walk(join(dir, name.name), rel);
else yield rel;
}
}
async function minifyAll() {
if (!existsSync(assetsDir)) return;
for (const rel of walk(assetsDir, '')) {
const path = join(assetsDir, rel);
const ext = rel.slice(rel.lastIndexOf('.'));
if (ext === '.js') {
const code = readFileSync(path, 'utf-8');
const out = await esbuild.transform(code, { minify: true, loader: 'js' });
writeFileSync(path, out.code);
} else if (ext === '.css') {
const code = readFileSync(path, 'utf-8');
const out = await esbuild.transform(code, { minify: true, loader: 'css' });
writeFileSync(path, out.code);
}
}
}
minifyAll().catch((e) => {
console.error(e);
process.exit(1);
});