-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
4 changed files
with
239 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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))) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -15,5 +15,6 @@ | |
"newLine": "LF", | ||
"jsx": "react", | ||
"lib": ["esnext", "dom"] | ||
} | ||
}, | ||
"exclude": ["node_modules", "lib", "dist"] | ||
} |