This commit is contained in:
79
scripts/externalize-bootstrap.js
Normal file
79
scripts/externalize-bootstrap.js
Normal file
@@ -0,0 +1,79 @@
|
||||
/**
|
||||
* 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);
|
||||
});
|
||||
Reference in New Issue
Block a user