起因
由于项目中直接通过script引入了,大量资源导致了首次渲染特别慢,在进行发布没有办法利用本地缓存的情况下,超过半分钟。
1.png
异步资源加载
其实涉及到的第三方库,是没有必要一开始就加载的,至少是不能影响到dom渲染的。
直接能想到的script defer 但是因为script是在index.html文件下,因此采用的脚本化的方式处理更为合适。
关于异步加载资源直接想到的是AMD,CMD模块化加载的方式,其最为底层的基理还是动态添加script。
动态添加script
const script = document.createElement('script')
script.type = 'text/javascript'
script.src = url
script.async = 'async'
const head = document.getElementsByTagName('head')[0];
(head || document.body).appendChild(script)
利用装饰器保证脚本确实被加载完成
利用装饰器将会使代码的侵入变得更少,在webpack中需要添加上babel的transform-decorators-legacy来保证对装饰器语法的支持
// .babelrc
{
"presets": [
[
"env",
{
"modules": false,
"targets": {
"browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
}
}
],
"stage-0"
],
"plugins": [
"transform-vue-jsx",
"transform-runtime",
"transform-decorators-legacy"
],
"env": {
"development": {
"plugins": ["dynamic-import-node"]
}
}
}
代码
class LoadScripts {
/** 创建脚本元素
* @function createScript
* @param {*} id
* @param {*} url
*/
createScript({ id, url }) {
if (LoadScripts.loadInfo && LoadScripts.loadInfo[id] && LoadScripts.loadInfo[id]['created']) {
return LoadScripts.loadInfo[id]['script']
}
if (!url) {
throw new Error('必须要有url参数')
}
const script = document.createElement('script')
script.type = 'text/javascript'
script.src = url
script.async = 'async'
const head = document.getElementsByTagName('head')[0];
(head || document.body).appendChild(script)
LoadScripts.loadInfo = LoadScripts.loadInfo ? LoadScripts.loadInfo : {}
LoadScripts.loadInfo[id] = {
created: true,
script
} // 标识script已经创建
return script
}
/** 加载静态资源
* @function load
* @param {*} id
* @param {*} url
* @param {*} callBack
*/
load({ id, url, callBack }) {
if (!id) {
throw new Error('必须要有id参数')
}
// 当标识已经加载完成 直接执行回调
if (LoadScripts.loadInfo && LoadScripts.loadInfo[id] && LoadScripts.loadInfo[id]['loaded']) {
callBack && callBack({
loaded: true,
success: true
})
return
}
const baseUrl = process.env.NODE_ENV === 'development' ? '../static' : './static'
if (!url) {
url = `${baseUrl}/${id}`
}
const script = this.createScript({ id, url, callBack })
script.onload = () => {
LoadScripts.loadInfo[id] && (LoadScripts.loadInfo[id]['loaded'] = true)
callBack && callBack({
loaded: true,
success: true
})
}
script.onerror = () => {
LoadScripts.loadInfo[id] && (LoadScripts.loadInfo[id]['loaded'] = false)
callBack && callBack({
loaded: true,
success: false
})
}
}
/** 多静态资源加载
* @function loads
* @param {*} arr
*/
loads(arr) {
if (!Array.isArray(arr)) {
throw new Error('need a array!')
}
arr.forEach(item => this.load(item))
}
/** 加载有相互依赖的资源 比如 bimrun.config.js bimrun.js
* @function loadsAsync
* @param {*} id
* @param {*} ids
*/
async loadsAsync({ fullPath, id, ids, callBack }) {
if (!id) throw new Error('need a id!')
// 当标识已经加载完成 直接执行回调
if (LoadScripts.asyncloadInfo && LoadScripts.asyncloadInfo[id] && LoadScripts.asyncloadInfo[id]['asyncLoaded']) {
callBack && callBack({
loaded: true,
success: true
})
return
}
// 处理id与ids的映射
if (!LoadScripts.aysncIdMap) {
LoadScripts.aysncIdMap = {}
}
if (ids) {
LoadScripts.aysncIdMap[id] = ids
} else {
ids = LoadScripts.aysncIdMap[id]
}
if (!Array.isArray(ids)) {
throw new Error('need a array!')
}
const promiseRes = []
for (let index = 0; index < ids.length; index++) {
const tpId = ids[index]
const promise = new Promise((resolve, reject) => {
this.load({ id: tpId, url: (fullPath ? tpId : null), callBack({ loaded, success }) {
resolve({ loaded, success })
} })
})
promiseRes.push(await promise)
}
// Promise.all(promiseAll).then(res => {
const success = promiseRes.every(item => item.success)
const loaded = promiseRes.every(item => item.loaded)
if (!LoadScripts.asyncloadInfo) {
LoadScripts.asyncloadInfo = {}
}
if (!(LoadScripts.asyncloadInfo && LoadScripts.asyncloadInfo[id])) LoadScripts.asyncloadInfo[id] = {}
LoadScripts.asyncloadInfo[id] && (LoadScripts.asyncloadInfo[id]['asyncloaded'] = true)
callBack && callBack({
loaded,
success
})
}
/** 资源加载装饰器 如果资源正在加载 等待资源加载完成 如果加载完成 直接执行
* @function loadDecorator
* @param {*} id
* @param {*} isAsync
*/
loadDecorator(id, isAsync) {
return (target, name, descriptor) => {
const loadScript = new LoadScripts()
const oldValue = descriptor.value
descriptor.value = async function fn(...args) {
const promise = new Promise((resolve, reject) => {
if (isAsync) {
loadScript.loadsAsync({
id,
callBack({ load, success }) {
resolve({ load, success })
}
})
} else {
loadScript.load({
id,
callBack({ load, success }) {
resolve({ load, success })
}
})
}
})
await promise
return oldValue.call(this, ...args)
}
return descriptor
}
}
}
export default new LoadScripts()
使用姿势
提前加载资源
// loadResource.js
import loadScripts from '@/utils/loadScripts'
// 加载资源
try {
loadScripts.loads([
{ id: 'Cesium/Cesium.js' },
{ id: 'tinymce4.7.5/tinymce.min.js' },
{ id: 'BIMRUN/bimrun-engine.js' }
])
} catch (err) {
console.log(err)
}
// gantt图加载
try {
loadScripts.loadsAsync({
id: 'gantt',
ids: [
'dhtmlx/dhtmlxgantt.js',
'codebase/dhtmlxscheduler.js',
'dhtmlx/ext/dhtmlxgantt_keyboard_navigation.js'
]
})
} catch (err) {
console.log(err)
}
// main.js
import './loadResource'
保证资源加载完成后使用
// cesium.js的加载
@loadResource('Cesium/Cesium.js')
// gantt的加载
@loadResource('gantt', true)
网友评论