File size: 2,604 Bytes
c211499
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
import { Options } from './types'
import { resolveUrl } from './util'
import { getMimeType } from './mimes'
import { isDataUrl, makeDataUrl, resourceToDataURL } from './dataurl'

const URL_REGEX = /url\((['"]?)([^'"]+?)\1\)/g
const URL_WITH_FORMAT_REGEX = /url\([^)]+\)\s*format\((["']?)([^"']+)\1\)/g
const FONT_SRC_REGEX = /src:\s*(?:url\([^)]+\)\s*format\([^)]+\)[,;]\s*)+/g

function toRegex(url: string): RegExp {
  // eslint-disable-next-line no-useless-escape
  const escaped = url.replace(/([.*+?^${}()|\[\]\/\\])/g, '\\$1')
  return new RegExp(`(url\\(['"]?)(${escaped})(['"]?\\))`, 'g')
}

export function parseURLs(cssText: string): string[] {
  const urls: string[] = []

  cssText.replace(URL_REGEX, (raw, quotation, url) => {
    urls.push(url)
    return raw
  })

  return urls.filter((url) => !isDataUrl(url))
}

export async function embed(
  cssText: string,
  resourceURL: string,
  baseURL: string | null,
  options: Options,
  getContentFromUrl?: (url: string) => Promise<string>,
): Promise<string> {
  try {
    const resolvedURL = baseURL ? resolveUrl(resourceURL, baseURL) : resourceURL
    const contentType = getMimeType(resourceURL)
    let dataURL: string
    if (getContentFromUrl) {
      const content = await getContentFromUrl(resolvedURL)
      dataURL = makeDataUrl(content, contentType)
    } else {
      dataURL = await resourceToDataURL(resolvedURL, contentType, options)
    }
    return cssText.replace(toRegex(resourceURL), `$1${dataURL}$3`)
  } catch (error) {
    // pass
  }
  return cssText
}

function filterPreferredFontFormat(
  str: string,
  { preferredFontFormat }: Options,
): string {
  return !preferredFontFormat
    ? str
    : str.replace(FONT_SRC_REGEX, (match: string) => {
        // eslint-disable-next-line no-constant-condition
        while (true) {
          const [src, , format] = URL_WITH_FORMAT_REGEX.exec(match) || []
          if (!format) {
            return ''
          }

          if (format === preferredFontFormat) {
            return `src: ${src};`
          }
        }
      })
}

export function shouldEmbed(url: string): boolean {
  return url.search(URL_REGEX) !== -1
}

export async function embedResources(
  cssText: string,
  baseUrl: string | null,
  options: Options,
): Promise<string> {
  if (!shouldEmbed(cssText)) {
    return cssText
  }

  const filteredCSSText = filterPreferredFontFormat(cssText, options)
  const urls = parseURLs(filteredCSSText)
  return urls.reduce(
    (deferred, url) =>
      deferred.then((css) => embed(css, url, baseUrl, options)),
    Promise.resolve(filteredCSSText),
  )
}