由于@KevinGhadyani 的回答(或 blob 技术)需要减少您的 CSP(例如,通过添加一个worker-src data:orblob:指令),有一个小例子说明您可以如何利用importScripts工作人员内部来加载另一个域上托管的另一个工作人员脚本,不会减少您的 CSP。
它可以帮助您从您的 CSP 允许的任何 CDN 加载工作人员。
据我所知,它适用于 Opera、Firefox、Chrome、Edge 和所有支持 worker 的浏览器。
/**
* This worker allow us to import a script from our CDN as a worker
* avoiding to have to reduce security policy.
*/
/**
* Send a formated response to the main thread. Can handle regular errors.
* @param {('imported'|'error')} resp
* @param {*} data
*/
function respond(resp, data = undefined){
const msg = { resp };
if(data !== undefined){
if(data && typeof data === 'object'){
msg.data = {};
if(data instanceof Error){
msg.error = true;
msg.data.code = data.code;
msg.data.name = data.name;
msg.data.stack = data.stack.toString();
msg.data.message = data.message;
} else {
Object.assign(msg.data, data);
}
} else msg.data = data;
}
self.postMessage(msg);
}
function handleMessage(event){
if(typeof event.data === 'string' && event.data.match(/^@worker-importer/)){
const [
action = null,
data = null
] = event.data.replace('@worker-importer.','').split('|');
switch(action){
case 'import' :
if(data){
try{
importScripts(data);
respond('imported', { url : data });
//The work is done, we can just unregister the handler
//and let the imported worker do it's work without us.
self.removeEventListener('message', handleMessage);
}catch(e){
respond('error', e);
}
} else respond('error', new Error(`No url specified.`));
break;
default : respond('error', new Error(`Unknown action ${action}`));
}
}
}
self.addEventListener('message', handleMessage);
如何使用它 ?
显然,您的 CSP 必须允许 CDN 域,但您不需要更多的 CSP 规则。
假设您的域是my-domain.com,而您的 CDN 是statics.your-cdn.com.
我们要导入的工作人员托管在https://statics.your-cdn.com/super-worker.js并将包含:
self.addEventListener('message', event => {
if(event.data === 'who are you ?') {
self.postMessage("It's me ! I'm useless, but I'm alive !");
} else self.postMessage("I don't understand.");
});
假设您在 path 下的域(不是您的 CDN)上托管了一个包含工作程序导入程序代码的文件https://my-domain.com/worker-importer.js,并且您尝试在 脚本标签内启动您的工作程序https://my-domain.com/,这就是它的工作原理:
<script>
window.addEventListener('load', async () => {
function importWorker(url){
return new Promise((resolve, reject) => {
//The worker importer
const workerImporter = new Worker('/worker-importer.js');
//Will only be used to import our worker
function handleImporterMessage(event){
const { resp = null, data = null } = event.data;
if(resp === 'imported') {
console.log(`Worker at ${data.url} successfully imported !`);
workerImporter.removeEventListener('message', handleImporterMessage);
// Now, we can work with our worker. It's ready !
resolve(workerImporter);
} else if(resp === 'error'){
reject(data);
}
}
workerImporter.addEventListener('message', handleImporterMessage);
workerImporter.postMessage(`@worker-importer.import|${url}`);
});
}
const worker = await importWorker("https://statics.your-cdn.com/super-worker.js");
worker.addEventListener('message', event => {
console.log('worker message : ', event.data);
});
worker.postMessage('who are you ?');
});
</script>
这将打印:
Worker at https://statics.your-cdn.com/super-worker.js successfully imported !
worker message : It's me ! I'm useless, but I'm alive !
请注意,如果上面的代码也编写在 CDN 上托管的文件中,它甚至可以工作。
当您的 CDN 上有多个工作程序脚本时,或者如果您构建一个必须托管在 CDN 上的库并且您希望您的用户能够调用您的工作人员而无需在其域上托管所有工作人员,这将特别有用。