Skip to content

Commit

Permalink
feat: add loadResource
Browse files Browse the repository at this point in the history
  • Loading branch information
fjc0k committed Jun 1, 2020
1 parent 235ef52 commit 675d7a9
Show file tree
Hide file tree
Showing 4 changed files with 239 additions and 1 deletion.
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export * from './utils/indent'
export * from './utils/isChineseIDCardNumber'
export * from './utils/isPossibleChineseMobilePhoneNumber'
export * from './utils/isUrl'
export * from './utils/loadResource'
export * from './utils/readFile'
export * from './utils/wait'
// @endindex
128 changes: 128 additions & 0 deletions src/utils/loadResource.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
import { loadResource, LoadResourceUrlType } from './loadResource'

describe(loadResource.name, () => {
const createElement = document.createElement.bind(document)
beforeAll(() => {
jest.spyOn(document, 'createElement').mockImplementation((tag: string) => {
const el = createElement(tag)
setTimeout(() => {
if (
/returnError/.test(
(el as HTMLScriptElement).src || (el as HTMLLinkElement).href,
)
) {
el.onerror?.(new Event('error'))
} else {
el.onload?.(new Event('load'))
}
}, 0)
return el
})
})
afterAll(() => {
jest.clearAllMocks()
})

test('只传入字符串时将根据后缀判断当作何种资源加载', async () => {
// 默认 js
const [jsEl] = await loadResource('https://foo.bar')
expect(jsEl.tagName).toBe('SCRIPT')

// 图片后缀
const [imgEl] = await loadResource('https://foo.bar/img.png')
expect(imgEl.tagName).toBe('IMG')

// 样式后缀
const [cssEl] = await loadResource('https://foo.bar/style.css')
expect(cssEl.tagName).toBe('LINK')
})

test('可传入 LoadResourceUrl 指定加载的资源类型', async () => {
const [jsEl] = await loadResource({
type: LoadResourceUrlType.js,
path: 'https://foo.bar/js',
})
expect(jsEl.tagName).toBe('SCRIPT')

const [styleEl] = await loadResource({
type: LoadResourceUrlType.css,
path: 'https://foo.bar/css',
})
expect(styleEl.tagName).toBe('LINK')

const [imgEl] = await loadResource({
type: LoadResourceUrlType.img,
path: 'https://foo.bar/img',
})
expect(imgEl.tagName).toBe('IMG')
})

test('可传入一个数组,异步加载多个资源', async () => {
const [jsEl, styleEl, imgEl] = await loadResource([
'https://foo.bar/js',
{
type: LoadResourceUrlType.css,
path: 'https://foo.bar/css',
},
{
type: LoadResourceUrlType.img,
path: 'https://foo.bar/img',
},
])

expect(jsEl.tagName).toBe('SCRIPT')
expect(styleEl.tagName).toBe('LINK')
expect(imgEl.tagName).toBe('IMG')
})

test('资源加载出错时抛出错误', async () => {
const jsPath = 'https://foo.bar/returnError'

expect(
loadResource({
type: LoadResourceUrlType.js,
path: jsPath,
}).catch(el => Promise.reject(el.src)),
).rejects.toMatch(jsPath)

expect(
loadResource([
'https://foo.bar/x.jpg',
{
type: LoadResourceUrlType.js,
path: jsPath,
},
]).catch(el => Promise.reject(el.src)),
).rejects.toMatch(jsPath)
})

test('主资源加载出错时支持加载备用资源', async () => {
const alternatePath = 'https://foo.bar/alternate'
const [jsEl] = await loadResource({
type: LoadResourceUrlType.js,
path: 'https://foo.bar/returnError',
alternatePath: alternatePath,
})

expect(jsEl.tagName).toBe('SCRIPT')
expect((jsEl as any).src).toBe(alternatePath)
})

test('代码资源应插入 DOM', async () => {
const src = 'http://foo.bar/this-is-an-script.js'
await loadResource(src)
expect(document.documentElement.outerHTML).toInclude(src)
})

test('样式资源应插入 DOM', async () => {
const src = 'http://foo.bar/this-is-an-css.css'
await loadResource(src)
expect(document.documentElement.outerHTML).toInclude(src)
})

test('样式资源不插入 DOM', async () => {
const src = 'http://foo.bar/this-is-an-image.jpg'
await loadResource(src)
expect(document.documentElement.outerHTML).not.toInclude(src)
})
})
108 changes: 108 additions & 0 deletions src/utils/loadResource.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import { castArray } from 'lodash-es'

/**
* 资源类型。
*/
export enum LoadResourceUrlType {
/** 样式资源 */
css = 'css',

/** 代码资源 */
js = 'js',

/** 图片资源 */
img = 'img',
}

/**
* 资源地址。
*/
export interface LoadResourceUrl {
/** 资源类型 */
type: LoadResourceUrlType

/** 资源路径 */
path: string

/** 备用资源路径 */
alternatePath?: string
}

function loadSpecificResource(
url: LoadResourceUrl,
): Promise<HTMLScriptElement | HTMLLinkElement | HTMLImageElement> {
return new Promise((resolve, reject) => {
let el!: HTMLScriptElement | HTMLLinkElement | HTMLImageElement
switch (url.type) {
case LoadResourceUrlType.js:
el = document.createElement('script')
el.src = url.path
break
case LoadResourceUrlType.css:
el = document.createElement('link')
el.rel = 'stylesheet'
el.href = url.path
break
case LoadResourceUrlType.img:
el = document.createElement('img')
el.src = url.path
break
/* istanbul ignore next */
default:
break
}
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)
}
})
}

/**
* 加载图片、代码、样式等资源。
*
* ```
* 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(() => {
* // 资源加载完成后的操作
* })
* ```
*
* @param url 要加载的资源地址
* @returns 返回各资源的 HTML 元素组成的数组
*/
export function loadResource(
url: string | LoadResourceUrl | Array<string | LoadResourceUrl>,
): Promise<Array<HTMLScriptElement | HTMLLinkElement | HTMLImageElement>> {
const urls = castArray(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)))
}
3 changes: 2 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,6 @@
"newLine": "LF",
"jsx": "react",
"lib": ["esnext", "dom"]
}
},
"exclude": ["node_modules", "lib", "dist"]
}

0 comments on commit 675d7a9

Please sign in to comment.