Option 1
If you can calculate the hash of the script you're loading in advance (if it's not something generated dynamically), then a simple option to avoid
enabling script-src 'unsafe-eval' in Content-Security-Policy
would be to add only a hash of that specific script into the CSP header - this will still ensure that you're not executing any untrusted code, while allowing you to load and execute script manually.
MDN has some more examples of implementing such CSP policies here.
As for loading itself, you have two different paths from here:
Option 1.1
Combine the hash with a CSP3 policy unsafe-hashes which will allow you to keep using Function or eval like you currently, while still limiting code only to trusted.
For example, if you have a script like
alert('Hello, world.');
then your CSP header should contain
Content-Security-Policy: script-src 'unsafe-hashes' 'sha256-qznLcsROx4GACP2dm0UCKCzCG-HiZ1guq6ZZDob_Tng='
Unfortunately, CSP level 3, or, at least, this option is supported only in Chromium at the moment of writing.
Option 1.2
Instead of using Function or eval, you can dynamically create a script tag from JavaScript, populate its textContent with your response content and inject it into the DOM:
let resource = await fetch('heavy_file.js')
resource.clone().body.pipeTo(new WritableStream({
write(t) { on_receive(t.length) }
}));
resource.text().then(res => {
let s = document.createElement('script');
s.textContent = res;
document.head.appendChild(s);
});
In this case you only need to add the hash of the script to the CSP and it will work across all browsers:
Content-Security-Policy: script-src 'sha256-qznLcsROx4GACP2dm0UCKCzCG-HiZ1guq6ZZDob_Tng='
Option 2
If you do want to support dynamically generated scripts, then your only option might be to move your progress-tracking code to a Service Worker, and then use Client.postMessage to communicate progress to a script on the page.
This will work for any content served from your origin, but only once Service Worker is installed - usually on a subsequent page load, and so might not help you if the large script you're loading is part of the page user visits first on your website.