篇前声明:为了不涉及业务细节,篇内信息统一以某游戏,某功能代替

前不久,某游戏准备内测客户端,开发人员测试过程中发现某功能突然不灵了,之前的测试一切ok,没有发现任何异常,第一反应是,游戏内浏览器都是自己包装的,是不是做了什么改造,触发了某个盲点。

游戏方表示浏览器还是以前包装的Chromium,不过还真有不同的,就是UA改了,而且不是在原UA后加的后缀标识,而是完全替换,使用了游戏名称做UA,问题应该就在这里了,从代码上来看,不会触发任何雷区,理论上不会有问题,如果有问题,极有可能出现在框架中,YUI3的底层逻辑。

顺便抱怨一下,游戏内测试本身就是一个蛋痛的事情,基本都是自己封装浏览器,即使使用同样的内核,也有可能包装方式不同引出不同的坑,下载了一个游戏,使用fiddler神器代理js测试,js又一直被缓存,游戏内也不知道如何清缓存,代理不到,只能转而代理html页面,js加随机数时间戳。

第一步:YUI组件加载完毕打log

  1. YUI({combine: true}).use('node', 'io', 'xml', function (Y) {
  2.     Y.log("init");
  3. });

测试结果:没有log,验证了我的想法,影响YUI组件加载,框架层级的

第二步:增减组件测试

测试结果:刚开始毫无规律,确实有部分组件去掉以后可以正常加载,貌似有共同点,但又看不出明确的共同点,跟其他同事沟通,貌似这些组件加载了皮肤,单独加载其它自带皮肤的组件,果然有问题,而不带皮肤就ok

第三步:YUI组件加载打log观察

测试结果:果然,竟然… 只加载了皮肤,没有加载js

这个时候才想起来看抓包… 发现组件真的没加载

第四步:YUI组件加载代码分析

先来看YUI加载过程

从这个抓包图来看,分为四步,yui种子文件—》loader组件配置文件—》组件皮肤—》组件js代码

1. 加载yui.js

可以理解为种子文件,它将负责后续yui的模块加载,当执行YUI.use引用模块时,开始加载loader文件(如果只引用了种子文字,但没有写js去use方法,实际上不会加载模块的)

YUI的_init方法对loader的配置,_init:

  1. config.base = YUI.config.base || Y.Env.getBase(/^(.*)yui\/yui([\.\-].*)js(\?.*)?$/,  /^(.*\?)(.*\&)(.*)yui\/yui[\.\-].*js(\?.*)?$/);
  2. config.loaderPath = YUI.config.loaderPath || 'loader/loader' + (filter || '-min.') + 'js';

use方法代码片段,开始加载loader

  1. handleBoot = function() {
  2.     Y._loading = false;
  3.     queue.running = false;
  4.     Env.bootstrapped = true;
  5.     if (Y._attach(['loader'])) {
  6.         Y.use.apply(Y, args);
  7.     }
  8. };
  9.  
  10. if (G_ENV._bootstrapping) {
  11.     queue.add(handleBoot);
  12. } else {
  13.     G_ENV._bootstrapping = true;
  14.     var url = config.base + config.loaderPath;
  15.     url += (url.indexOf("?") > -1 ? "&" : "?") + "v=" + Y.Env.CACHE_VERSION;
  16.     Y.Get.script(url, {
  17.         onEnd: handleBoot
  18.     });
  19. }

2. 加载loader.js

yui3为按需加载,Loader相当于所有组件的一个配置文件(yui最新版本已将loader直接放入yui.js,减少一个请求),配置内容包括所有的模块,模块的依赖项,模块的皮肤等,如果模块有皮肤,会优先加载,模块有依赖模块,也需要同时加载

我们来简单看一下loader的代码

  1. "console": {
  2.     "requires": [
  3.         "yui-log",
  4.         "widget",
  5.         "substitute"
  6.     ],
  7.     "skinnable": true
  8. }

requires代表它依赖的其它模块,skinnable则代表它需要皮肤

模块添加时有这么一句代码 ,如果有皮肤,则添加皮肤

  1. if (o.skinnable) {
  2.     this._addSkin(this.skin.defaultSkin, i, name);
  3. }
  4.  
  5. _addSkin: function(skin, mod, parent) {
  6.     var mdef, pkg, name,
  7.         info = this.moduleInfo,
  8.         sinf = this.skin,
  9.         ext  = info[mod] && info[mod].ext;
  10.  
  11.     // Add a module definition for the module-specific skin css
  12.     if (mod) {
  13.         name = this.formatSkin(skin, mod);
  14.         if (!info[name]) {
  15.             mdef = info[mod];
  16.             pkg = mdef.pkg || mod;
  17.             this.addModule({
  18.                 name:  name,
  19.                 group: mdef.group,
  20.                 type:  'css',
  21.                 after: sinf.after,
  22.                 after_map: YArray.hash(sinf.after),
  23.                 path:  (parent || pkg) + '/' + sinf.base + skin + '/' + mod + '.css',
  24.                 ext:   ext
  25.             });
  26.         }
  27.     }
  28.     return name;
  29. }

看一下addskin方法,会根据配置的基准路径寻找该皮肤文件,默认是该组件所在文件夹下面的assets/skins/sam下的同名css文件

3. Loader加载成功

重新回到yui.Js的user方法中来看,此时,该判断就会生效,便拿到了所有loader对象,获取到所有的组件加载项args

  1. if (boot && len && Y.Loader) {
  2.     Y._loading = true;
  3.     loader = getLoader(Y);
  4.     loader.onEnd = handleLoader;
  5.     loader.context = Y;
  6.     // loader.attaching = args;
  7.     loader.data = args;
  8.     loader.require((fetchCSS) ? missing : args);
  9.     loader.insert(null, (fetchCSS) ? null : 'js');
  10. }

一路追踪到loader.js中的loadNext,我们使用了合并的方式,该方法会将同类资源文件合并加载(js or css),然后调用Y.Get.js or Y.Get. css进行资源文件的加载,由此再追踪到yui.js中的_next方法

4. 组件加载

此时,开始进行组件加载,并跟踪加载流程

  1. if (q.type === "script") {
  2.     n = _scriptNode(url, w, q.attributes);
  3. } else {
  4.     n = _linkNode(url, w, q.attributes);
  5. }
  6. // track this node's load progress
  7. _track(q.type, n, id, url, w, q.url.length);

创建script or link节点

  1. // add it to the head or insert it before 'insertBefore'.  Work around IE
  2. // bug if there is a base tag.
  3. insertBefore = q.insertBefore || d.getElementsByTagName('base')[0];
  4.  
  5. if (insertBefore) {
  6.     s = _get(insertBefore, id);
  7.     if (s) {
  8.         s.parentNode.insertBefore(n, s);
  9.     }
  10. } else {
  11.     h.appendChild(n);
  12. }

添加到页面上,这里皮肤css与js并不是完全的并行加载,对于webkit与gecko内核,直接开始加载下一个资源文件,即js

  1. if ((ua.webkit || ua.gecko) && q.type === "css") {
  2.     _next(id, url);
  3. }

我们看一下_track方法

  1. if (ua.ie) {
  2.     n.onreadystatechange = function() {
  3.         var rs = this.readyState;
  4.         if ("loaded" === rs || "complete" === rs) {
  5.             n.onreadystatechange = null;
  6.             f(id, url);
  7.         }
  8.     };
  9. // webkit prior to 3.x is no longer supported
  10. } else if (ua.webkit) {
  11.     if (type === "script") {
  12.         // Safari 3.x supports the load event for script nodes (DOM2)
  13.         n.addEventListener("load", function() {
  14.             f(id, url);
  15.         });
  16.     }
  17.  
  18. // FireFox and Opera support onload (but not DOM2 in FF) handlers for
  19. // script nodes.  Opera, but not FF, supports the onload event for link
  20. // nodes.
  21. } else {
  22.     n.onload = function() {
  23.         f(id, url);
  24.     };
  25.     n.onerror = function(e) {
  26.         _fail(id, e + ": " + url);
  27.     };
  28. }

ie浏览器通过readystatechange进行判断并加载后续的资源文件,其它浏览器如webkit則监听onload(即使监听失败,_next方法中可直接执行js加载)

我们加载的模块是console,但是只加载了css,js不见了,如下图,我不是webkit,我不是gecko,我也不是IE,然后就进入了死胡同

由于UA变更,没有将其判断为webkit,_next方法没有执行,加载完css后无法直接加载js,故只能依靠track方法,track方法判断其不属于IE浏览器,直接进入了else分支,但由于浏览器本身是Chromium内核,link节点的onload方法无法触发。进入了死万劫不复之地。

解决方法也比较简单,对于非css请求,依赖_trace方法触发回调其它js加载(js加载需要严格时序保证),而css请求,不依赖_trace,直接调用_next触发其它资源加载

该方法改动较小,风险较小,即使后续有新的UA变更或浏览器更新等,仍可以保证正常使用,缺点在于,如果加载过程中发生网络波动等异常,有可能出现样式闪动的问题。

【原】从一个bug浅谈YUI3组件的资源加载的更多相关文章

  1. Vue.js 子组件的异步加载及其生命周期控制

    前端开发社区的繁荣,造就了很多优秀的基于 MVVM 设计模式的框架,而组件化开发思想也越来越深入人心.这其中不得不提到 Vue.js 这个专注于 VM 层的框架. 本文主要对 Vue.js 组件化开发 ...

  2. 细谈unity资源加载和卸载

    转载请标明出处:http://www.cnblogs.com/zblade/ 一.概要 在了解unity的资源管理方式之后,接下来细谈一下Unity的资源是如何从磁盘中加载到运行时的内存中,以及又是如 ...

  3. 再谈DOMContentLoaded与渲染阻塞—分析html页面事件与资源加载

    浏览器的多线程中,有的线程负责加载资源,有的线程负责执行脚本,有的线程负责渲染界面,有的线程负责轮询.监听用户事件. 这些线程,根据浏览器自身特点以及web标准等等,有的会被浏览器特意的阻塞.两个很明 ...

  4. Vue性能优化之组件按需加载(以及一些常见的性能优化方法)

    关于Vue中的按需加载我就简单介绍一下:大概就是我们所有的东西都会在app.js里面,但是我们并不需要把所有的组件都一次性加载进来,我们可以在需要它的时候再将它加载进来,话不多说,开车! 1.webp ...

  5. React Router 4.0 + webpack 实现组件按需加载

    网上关于React Router 4.0的按需加载文章有很多,大致的思路都一样,但是其实具体实现起来却要根据自己的实际情况来定,这里主要介绍一下我的实现方式. 主要方式是通过Route组件的rende ...

  6. vue2 自定义全局组件(Loading加载效果)

    vue2 自定义全局组件(Loading加载效果) github地址: https://github.com/ccyinghua/custom-global-component 一.构建项目 vue ...

  7. 浅谈PHP组件、框架以及Composer

    本篇文章主要介绍了PHP组件.框架以及Composer,具有一定的学习价值,感兴趣的朋友可以了解一下. 什么是组件 组件是一组打包的代码,是一系列相关的类.接口和Trait,用于帮助我们解决PHP应用 ...

  8. 【ASP.NET MVC系列】浅谈ASP.NET MVC资源过滤和授权

    最近比较忙,博客很久没更新了,很多博友问何时更新博文,因此,今天就花了点时间,写了本篇文章,但愿大家喜欢. 本篇文章不适合初学者,需要对ASP.NET MVC具有一定基础. 本篇文章主要从ASP.NE ...

  9. (BUG已修改,最优化)安卓ListView异步加载网络图片与缓存软引用图片,线程池,只加载当前屏之说明

    原文:http://blog.csdn.net/java_jh/article/details/20068915 迟点出更新的.这个还有BUG.因为软引应不给力了.2.3之后 前几天的原文有一个线程管 ...

随机推荐

  1. js压缩解压工具

    参看下面链接:http://js.clicki.cc/

  2. Mysql优化之创建高性能索引(二)

    1.索引的优点 索引可以让服务器快速地定位到表的指定位置.总结下来有三大优点: 索引大大减少了服务器需要扫描的数据量 索引可以帮助服务器避免排序和临时表 索引可以将随机I/O变为顺序I/O 2.高性能 ...

  3. css 兼容小三角

    <!DOCTYPE><html ><head><meta http-equiv="Content-Type" content=" ...

  4. Java、Tomcat 及 MySQL 环境配置

    Java开发环境的配置 首先我们要下载JDK. 到Oracle官网上去下载即可,目前最新版是Java SE 8u25. 开始我很混乱,Java SE 和 JDK是什么关系呢?最后查了一下 Java S ...

  5. python核心编程-第五章-习题

    1.长整型表示数的范围比整型更大.在python中,整型.长整型趋于统一,普通用户不用特别关注两者区别,仅当需引用C语言时需要特别注意. 2.操作符 (a) def product(x,y): ret ...

  6. Inno Setup 打包工具总结(转)

    最近打包用到了Inno setup,在这个过程中容易犯一些低级错误,特别写出来已提醒自己 1.打包文件夹 打包文件按照向导来一般没什么问题,但文件夹就不一样了.向导生成的打包文件夹的代码如下: Sou ...

  7. 让Maven正确处理javac警告

    [ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:2.3.1:compile (default ...

  8. Bayesian Formulation on Cooperative Tracking

    Suppose a joint state representing a set of \(N_{n}\) nodes moving in a field\[    \textbf{X}=    \b ...

  9. OSCHina技术导向:开源企业ERP系统Opentaps

    opentaps Open Source ERP + CRM 基于 Apache OFBiz (The Open For Business Project ) 构建, 是一款设计良好, 逐渐流行起来的 ...

  10. Google地图轨迹回放模拟

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...