前端白屏问题分析——CDN资源失效、JS代码不兼容、css代码不兼容

前端白屏问题分析——CDN资源失效、JS代码不兼容、css代码不兼容 1、CDN资源失效导致白屏用户网络环境复杂运营商劫持国外访问CDN无效等等。这种情况下会导致白屏。应得这种情况识别资源加载识别切换线路重新加载资源自然成了我们选择的方案。大家已经有了一些方案简单策略让前端资源实现高可用域名劫持资源重加载方案前端资源加载重试configuring webpack public path at runtime这些方案有通用的策略也有针对webpack打包的针对性方案。不过这些方案总结来说还是要实现2个大的功能识别资源加载失败重新加载失败的资源识别资源加载失败对于第一个问题上面头条的同学给一个简单通用的方案就是这个script var errors []; window.addEventListener(error, function (e) { if (!(e instanceof ErrorEvent)) { errors.push(e); } }, true); /script利用addEventListener在捕获阶段获得错误通过判断e不是ErrorEvent来判断是资源加载错误。但是这个错误不能判断资源是404还是网络问题都统一返回一个error。如果需要知道具体的问题还需要通过ajax请求一次这个出错的资源才能知道具体问题是404、域名解析不了、服务不可用、还是加载超时等捕获到错误以后应该如何加载资源呢重新加载哪些资源呢js、css、image?重新加载失败的资源这个问题可以利用策略来解决。这里有两种策略页面内重新加载在页面里获得当前页面失败到或者所有到资源重新切换线路进行加载。提示用户资源加载失败然后reload页面在url上给一个标示让这个重新reload的页面采用新的资源路径。第二种策略耗时会更久这里就不讨论了具体在业务中笔者也没有真的实现。我们来聊一聊页面内重新加载的思路。上面头条的方案中讨论了js重新加载的方案这里我们会覆盖讨论css和背景图片的问题。css资源和js资源css资源和js资源都是通过dom标签加载的所以实现方案上可以统一流程都是识别所有的dom然后把对应链接里的CDN域名提高为新的域名再把得到的新链接生成dom重新插入到页面中利用浏览器的并行加载顺序执行重新执行一遍这些资源。Tips: 如果出现部分js资源失败部分成功。重新加载全部资源可能会导致某些js执行出错如果这些js文件不支持多次执行的话。我们这里不对这种情况做讨论指考虑统一失败的情况加载所有资源都使用了相同的CDN且这些js都支持多次重复执行。看如下代码function cndReloadAll (nodeName, urlAttr) { var domNodeList document.getElementsByTagName(nodeName) for (var i 0; i domNodeList.length; i) { var onedom domNodeList[i]; if (!onedom[urlAttr] || (onedom[urlAttr].indexOf(a.cdn.com))) { continue; } var newNode document.createElement(nodeName); for (var key in onedom.attributes) { if (onedom.attributes.hasOwnProperty(key)) { var nodeAttr onedom.attributes[key] var name nodeAttr.name if (nodeAttr.name crossorigin) { newNode.crossOrigin crossorigin } else if ([onerror, onload, onabort].indexOf(name) -1) { newNode[nodeAttr.name] nodeAttr.value } } } newNode[urlAttr] onedom[urlAttr].replace(a.cdn.com, b.cdn.com) onedom.parentNode.insertBefore(newNode, onedom) onedom.parentNode.removeChild(onedom) } }代码里的函数cdnReloadAll有两个参数nodeName表示标签名字比如script或者linkurlAttr表示需要替换的属性script对应srclink对应href。a.cdn.com表示当前的CDN域名b.cdn.com表示切换后的域名。这段代码还贴心的复制了原来script标签里一些常用属性。背景图片把css文件上传到多个CDN以后css文件里的背景图URL可能还是同一个切换的时候需要同时进行切换。看如下代码function reloadBackgroundImage () { var isSupportStyleSheets document.styleSheets document.styleSheets[0] if (!isSupportStyleSheets) return false var styleSheetsLength document.styleSheets.length for (var i 0; i styleSheetsLength; i) { var styleSheets document.styleSheets[i] try { if (!(styleSheets.rules styleSheets.rules.length 0)) { continue } for (var j 0; j styleSheets.rules.length; j) { var item styleSheets.rules[j] // is back null var backStr item.selectorText item.style item.style.backgroundImage if (!backStr) { continue } // is has real url var matchRes backStr.match(/url\([|](.*)[|]\)/) var backgroundImage matchRes matchRes[1] if (!backgroundImage) { continue } // base64 not replace if (backgroundImage.indexOf(data:image/) ! -1) { continue; } var nodeName BACKGROUNDIMAGE if (backgroundImage.indexOf(a.cdn.com) -1) { continue; } var newUrl backgroundImage.replace(a.cdn.com, b.cdn.com) if (!newUrl) { continue } var cssText item.selectorText { background-image: url( newUrl )} var cssTextKey i cssText if (window.reloadCache[nodeName].indexOf(cssTextKey) -1) { window.reloadCache[nodeName].push(cssTextKey) styleSheets.insertRule(cssText, styleSheets.rules.length) } } } catch (err) { throw Error(CdnAssetsSwitch replaceBackGroundImage err) } } }上面这段代码遍历了页面里所有的样式表并且提取里面的background-image属性如果发现是a.cdn.com的就生成一个b.cdn.com的新样式插入到这个样式表中。里面还做了一个缓存稍微提高一下性能。这样我们就把重新加载资源搞定了。第一步和第二部要如何联合在一期呢在不侵入代码的情况下。我们可以在body后业务js代码前插入一段js代码如下window.addEventListener(error, function (e) { if (!(e instanceof ErrorEvent)) { if (!isCdnError) { isCdnError true var script window.document.createElement(script); script.type text/javascript; script.addEventListener(error, function () { // TODO 继续切换其他线路 }) script.src reload.js; window.document.body.appendChild(script); } } }, true)其中reload.js里写的是上面重载css、js、和背景图片的代码定义两个函数并且执行。未完待续