背景
想在 GitHub 做一个自动注入 npm badge 的链接,点击即可跳转到 npm。 而无需查看 package.json name 手动拼接对应的 npm 链接。 但是因为 GitHub 设置了 CSP 导致没法直接插入除 GitHub 等规定域名以外的图片。
执行:
$('.markdown-body h1').insertAdjacentHTML('afterend', `<a href="https://badge.fury.io/js/ts-brand"><img src="https://badge.fury.io/js/ts-brand.svg" alt="npm version" height="18"></a>`);
报错:
Refused to load the image 'badge.fury.io/js/ts-brand…' because it violates the following Content Security Policy directive: "img-src data: 'self' data: github.githubassets.com identicons.github.com collector.githubapp.com collector.github.com github-cloud.s3.amazonaws.com secured-user-images.githubusercontent.com/ *.githubusercontent.com".
解决方案
Inline image use createObjectURL and readAsDataURL
通过 createObjectURL 或 readAsDataURL 让图片内联到 DOM。
createObjectURL
async function fetchObjectURL(url) {
const { URL, fetch } = window;
const blob = await fetch(url).then((resp) => resp.blob());
const objectURL = URL.createObjectURL(blob);
return [objectURL, () => { return URL.revokeObjectURL(objectURL); }]
}
使用
const httpSrc = `https://img.shields.io/npm/v/verb-corpus.svg`
const [objectURL, revokeObjectURL] = await fetchObjectURL(httpSrc)
img.src = objectURL
// 虽然浏览器会在 DOM unload 自动销毁但建议使用完毕手动回收
img.onload = revokeObjectURL
readAsDataURL
async function fetchDataURL(url) {
const reader = new FileReader()
const blob = await fetch(url).then((resp) => resp.blob())
reader.readAsDataURL(blob)
return new Promise((resolve, reject) => {
reader.addEventListener('load', () => {
resolve(reader.result)
});
reader.addEventListener('error', event => {
reject(event)
});
})
}
使用
const dataURL = await fetchDataURL(httpSrc)
img.src = dataURL
效果图
技术总结
下面我们开始技术总结:
-
可设置图片的load事件处理器来释放对象URL,当图片加载完成之后对象URL就不再需要了。可通过调用window.URL.revokeObjectURL()来释放。
-
返回释放函数,是一个经典的模式,让调用者尽可能的少感知逻辑,这个叫做『最小知识原则』也叫做『Law of Demeter』。否则得这么写:
img.onload = () => { window.URL.revokeObjectURL(objectURL); // BAD => 抽象泄露 }
-
reader 不能重复使用,故每次都
new FileReader()
。