All files / utils loadResource.ts

100% Statements 42/42
100% Branches 19/19
100% Functions 8/8
100% Lines 41/41

Press n or j to go to the next uncovered block, b, p or k for the previous block.

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 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175                                                                                                                  22x   22x     10x 10x 10x 10x   10x     1x 1x 1x 1x   1x     5x 5x 5x 5x   5x     1x 1x 1x 1x   1x     5x 5x 5x   5x         22x 21x   22x 22x 3x 1x         2x     22x 17x                                                               18x 21x                     18x   21x     21x 21x            
export type LoadResourceElement =
  | HTMLScriptElement
  | HTMLLinkElement
  | HTMLStyleElement
  | HTMLImageElement
 
/**
 * 资源类型。
 *
 * @public
 */
export enum LoadResourceUrlType {
  /** 样式资源 */
  css = 'css',
 
  /** 样式文本 */
  cssText = 'cssText',
 
  /** 代码资源 */
  js = 'js',
 
  /** 代码文本 */
  jsText = 'jsText',
 
  /** 图片资源 */
  img = 'img',
}
 
export type LoadResourceHook = (el: LoadResourceElement) => any
 
/**
 * 资源地址。
 *
 * @public
 */
export interface LoadResourceUrl {
  /** 资源类型 */
  type: LoadResourceUrlType
 
  /** 资源路径 */
  path: string
 
  /** 备用资源路径 */
  alternatePath?: string
 
  /** 钩子 */
  hook?: LoadResourceHook
}
 
export interface LoadResourceOptions {
  /** 钩子 */
  hook?: LoadResourceHook
}
 
function loadSpecificResource(
  url: LoadResourceUrl,
): Promise<LoadResourceElement> {
  return new Promise((resolve, reject) => {
    let el!: LoadResourceElement
    switch (url.type) {
      case LoadResourceUrlType.js:
        {
          const _el = document.createElement('script')
          _el.src = url.path
          _el.async = true
          el = _el
        }
        break
      case LoadResourceUrlType.jsText:
        {
          const _el = document.createElement('script')
          _el.setAttribute('type', 'text/javascript')
          _el.textContent = url.path
          el = _el
        }
        break
      case LoadResourceUrlType.css:
        {
          const _el = document.createElement('link')
          _el.rel = 'stylesheet'
          _el.href = url.path
          el = _el
        }
        break
      case LoadResourceUrlType.cssText:
        {
          const _el = document.createElement('style')
          _el.setAttribute('type', 'text/css')
          _el.textContent = url.path
          el = _el
        }
        break
      case LoadResourceUrlType.img:
        {
          const _el = document.createElement('img')
          _el.src = url.path
          el = _el
        }
        break
      /* istanbul ignore next */
      default:
        break
    }
    if (url.hook) {
      url.hook(el)
    }
    el.onload = () => resolve(el)
    el.onerror = () => {
      if (url.alternatePath) {
        loadSpecificResource({
          type: url.type,
          path: url.alternatePath,
        }).then(resolve, reject)
      } else {
        reject(el)
      }
    }
    if (url.type !== LoadResourceUrlType.img) {
      document.head.appendChild(el)
    }
  })
}
 
/**
 * 加载图片、代码、样式等资源。
 *
 * @public
 * @param url 要加载的资源地址
 * @param options 选项
 * @returns 返回各资源的 HTML 元素组成的数组
 * @example
 * ```typescript
 * loadResource([
 *   'https://foo.bar/all.js',
 *   'https://foo.bar/all.css',
 *   'https://foo.bar/logo.png',
 *   {
 *     type: LoadResourceUrlType.js,
 *     path: 'https://s1.foo.bar/js/full',
 *     alternatePath: 'https://s2.foo.bar/js/full',
 *   },
 * ]).then(() => {
 *   // 资源加载完成后的操作
 * })
 * ```
 */
export function loadResource(
  url: string | LoadResourceUrl | Array<string | LoadResourceUrl>,
  options?: LoadResourceOptions,
): Promise<Array<LoadResourceElement>> {
  const urls = (Array.isArray(url) ? url : [url]).map<LoadResourceUrl>(item => {
    return !(typeof item === 'string')
      ? item
      : {
          type: /\.css$/i.test(item)
            ? LoadResourceUrlType.css
            : /\.(png|jpg|jpeg|gif|svg)$/i.test(item)
            ? LoadResourceUrlType.img
            : LoadResourceUrlType.js,
          path: item,
        }
  })
  return Promise.all(
    urls.map(url =>
      loadSpecificResource({
        ...url,
        hook: el => {
          options?.hook?.(el)
          url.hook?.(el)
        },
      }),
    ),
  )
}