html-to-image
Version:
Generates an image from a DOM node using HTML5 canvas and SVG.
112 lines (94 loc) • 2.65 kB
text/typescript
import { Options } from './types'
function getContentFromDataUrl(dataURL: string) {
return dataURL.split(/,/)[1]
}
export function isDataUrl(url: string) {
return url.search(/^(data:)/) !== -1
}
export function makeDataUrl(content: string, mimeType: string) {
return `data:${mimeType};base64,${content}`
}
export async function fetchAsDataURL<T>(
url: string,
init: RequestInit | undefined,
process: (data: { result: string; res: Response }) => T,
): Promise<T> {
const res = await fetch(url, init)
if (res.status === 404) {
throw new Error(`Resource "${res.url}" not found`)
}
const blob = await res.blob()
return new Promise<T>((resolve, reject) => {
const reader = new FileReader()
reader.onerror = reject
reader.onloadend = () => {
try {
resolve(process({ res, result: reader.result as string }))
} catch (error) {
reject(error)
}
}
reader.readAsDataURL(blob)
})
}
const cache: { [url: string]: string } = {}
function getCacheKey(
url: string,
contentType: string | undefined,
includeQueryParams: boolean | undefined,
) {
let key = url.replace(/\?.*/, '')
if (includeQueryParams) {
key = url
}
// font resource
if (/ttf|otf|eot|woff2?/i.test(key)) {
key = key.replace(/.*\//, '')
}
return contentType ? `[${contentType}]${key}` : key
}
export async function resourceToDataURL(
resourceUrl: string,
contentType: string | undefined,
options: Options,
) {
const cacheKey = getCacheKey(
resourceUrl,
contentType,
options.includeQueryParams,
)
if (cache[cacheKey] != null) {
return cache[cacheKey]
}
// ref: https://developer.mozilla.org/en/docs/Web/API/XMLHttpRequest/Using_XMLHttpRequest#Bypassing_the_cache
if (options.cacheBust) {
// eslint-disable-next-line no-param-reassign
resourceUrl += (/\?/.test(resourceUrl) ? '&' : '?') + new Date().getTime()
}
let dataURL: string
try {
const content = await fetchAsDataURL(
resourceUrl,
options.fetchRequestInit,
({ res, result }) => {
if (!contentType) {
// eslint-disable-next-line no-param-reassign
contentType = res.headers.get('Content-Type') || ''
}
return getContentFromDataUrl(result)
},
)
dataURL = makeDataUrl(content, contentType!)
} catch (error) {
dataURL = options.imagePlaceholder || ''
let msg = `Failed to fetch resource: ${resourceUrl}`
if (error) {
msg = typeof error === 'string' ? error : error.message
}
if (msg) {
console.warn(msg)
}
}
cache[cacheKey] = dataURL
return dataURL
}