80 lines
2.8 KiB
JavaScript
80 lines
2.8 KiB
JavaScript
/**
|
|
* Move SvelteKit's inline bootstrap script to an external file for CSP (no unsafe-inline).
|
|
* Run after vite build; reads/writes build/.
|
|
* Finds <script>...</script> containing __sveltekit_, minifies it, writes to _app/immutable/entry/bootstrap.js,
|
|
* and replaces the inline script with <script src="...">.
|
|
*/
|
|
import { readFileSync, writeFileSync, readdirSync, mkdirSync } from 'fs';
|
|
import { join, dirname } from 'path';
|
|
import { fileURLToPath } from 'url';
|
|
import { minify } from 'terser';
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
const buildDir = join(__dirname, '..', 'build');
|
|
const entryDir = join(buildDir, '_app', 'immutable', 'entry');
|
|
const bootstrapPath = join(entryDir, 'bootstrap.js');
|
|
const scriptSrc = './_app/immutable/entry/bootstrap.js';
|
|
|
|
function getFiles(dir, ext, files = []) {
|
|
for (const name of readdirSync(dir, { withFileTypes: true })) {
|
|
const full = join(dir, name.name);
|
|
if (name.isDirectory()) getFiles(full, ext, files);
|
|
else if (name.name.endsWith(ext)) files.push(full);
|
|
}
|
|
return files;
|
|
}
|
|
|
|
// Find first <script>...</script> that contains __sveltekit_
|
|
function findInlineBootstrap(html) {
|
|
const scriptOpen = html.indexOf('<script>');
|
|
if (scriptOpen === -1) return null;
|
|
const scriptClose = html.indexOf('</script>', scriptOpen);
|
|
if (scriptClose === -1) return null;
|
|
const content = html.slice(scriptOpen + '<script>'.length, scriptClose);
|
|
if (!content.includes('__sveltekit_')) return null;
|
|
return { content: content.trim(), start: scriptOpen, end: scriptClose + '</script>'.length };
|
|
}
|
|
|
|
const SCRIPT_TAG = `<script src="${scriptSrc}"></script>`;
|
|
|
|
async function main() {
|
|
const htmlFiles = getFiles(buildDir, '.html');
|
|
let bootstrapWritten = false;
|
|
let count = 0;
|
|
for (const htmlFile of htmlFiles) {
|
|
let html = readFileSync(htmlFile, 'utf8');
|
|
const found = findInlineBootstrap(html);
|
|
if (!found) continue;
|
|
|
|
if (!bootstrapWritten) {
|
|
// Imports relative to script location when in _app/immutable/entry/
|
|
let scriptContent = found.content.replace(
|
|
/import\("\.\/_app\/immutable\/entry\/([^"]+)"\)/g,
|
|
'import("./$1")'
|
|
);
|
|
const result = await minify(scriptContent, {
|
|
format: { comments: false },
|
|
compress: { passes: 1 }
|
|
});
|
|
if (result.code) scriptContent = result.code;
|
|
mkdirSync(entryDir, { recursive: true });
|
|
writeFileSync(bootstrapPath, scriptContent, 'utf8');
|
|
bootstrapWritten = true;
|
|
}
|
|
|
|
html = html.slice(0, found.start) + SCRIPT_TAG + html.slice(found.end);
|
|
writeFileSync(htmlFile, html, 'utf8');
|
|
count++;
|
|
}
|
|
if (count > 0) {
|
|
console.log('Bootstrap script externalized (minified):', scriptSrc, `(${count} HTML file(s))`);
|
|
} else if (htmlFiles.length > 0) {
|
|
console.log('No SvelteKit inline script found in HTML (bootstrap already external?)');
|
|
}
|
|
}
|
|
|
|
main().catch((err) => {
|
|
console.error(err);
|
|
process.exit(1);
|
|
});
|