82. 资源解析器AssetResolver
在阅读本主题前,请先阅读本系的《资源库assets library详解》以详细了解资源库相关知识
用于解析页面附加的资源库和渲染数组中的js设置(drupalSettings),执行入口:
\Drupal\Core\Render\HtmlResponseAttachmentsProcessor::processAssetLibraries
附加资源集:
在系统中用附加资源集对象代表一个页面的资源,这也是资源解析器要操作的对象,类如下:
\Drupal\Core\Asset\AttachedAssets
该对象保存三大内容:
资源库:$render_array['#attached']['library']
js设置:$render_array['#attached']['drupalSettings']
已经被前端页面加载的库:比如在ajax时,已经加载的库不会再次加载
在整个请求流程中附加的资源都汇集到这里,在派发响应事件时,从响应对象中得到资源信息后保存到该对象,然后传递给资源解析器以得到相关占位符的内容
资源解析器:
服务名:asset.resolver
类:Drupal\Core\Asset\AssetResolver
该服务使用缓存后端cache.data来缓存相关数据;主要方法见下。
获取css集:
一个页面的所有css以如下cid被缓存:
$cid = 'css:活动主题名:' . Crypt::hashBase64(serialize($libraries_to_load)) . (int)是否优化;
缓存标签为:['library_info'],缓存内容是一个按照加载顺序排好序的数组,这就是我们需要的css集数组,如果配置关闭了优化,那么键名是css绝对路径(有三种类型:外部路径含协议相对URI、流路径、或相对于系统根目录的没有“/”前缀的路径),否则是数字索引,键值是一个选项数组,她代表需要加载的一个css资源文件,选项数组是一个css资源的内部表示,她来自库定义时的属性数组,会进行如下一系列处理:
首先进行默认附加,以补充缺省值,一个css资源会被附加以下默认选项及值:
$default_options = [
'type' => 'file',
'group' => CSS_AGGREGATE_DEFAULT,
'weight' => 0,
'media' => 'all',
'preprocess' => TRUE,
'browsers' => ['IE' => TRUE,'!IE' => TRUE,],
];
然后校正preprocess、weight的值,并新增data键以保存文件绝对路径(格式如上所述)
以资源的绝对路径(如上所述)作为键名,选项数组做键值,形成按照依赖关系排序的css集数组
主题或模块可以定义修改钩子'css'去修改这个css集数组,函数如下:
Hook_css_alter(&$css, $assets);
第一个参数$css应该以引用接收
第二个参数是附加资源集对象\Drupal\Core\Asset\AttachedAssets,不应该修改她,用于辅助判断,里面还有js及其设置等资源
调用该修改钩后再次执行排序(之前资源已经是依据依赖关系排过序,这可对没有依赖关系的资源排序,但这也可以更改依赖关系形成的排序),css资源的排序先以组group进行,其值是一个整数,再以权重weight进行(此时weight已是SMACSS分类权重和定义权重相加的结果),数值越高越后加载,在前端优先级越高,系统指定的默认组是主题定义时为100(常量CSS_AGGREGATE_THEME),模块为0(常量CSS_AGGREGATE_DEFAULT),我们可以在前文提到的“css”修改钩中修改她。
继续执行主题info文件中定义的表单移除,
如果启用了css聚合优化,那么将采用\Drupal::service('asset.css.collection_optimizer')->optimize($css);进行优化,见本系列《资源css与js优化处理》主题。
最后返回一个选项数组构成的css集数组,每个选项数组代表需要加载的一个css文件,她们已经按照页面加载顺序排序
注意:在没有启用聚合优化时,页面css集中选项数组有以下键名:
type、group、weight、media、preprocess、browsers、data
如果启用了那么键名weight将被去掉(后续不要依赖该键名,集已排好序,不再需要她),如果资源是被聚合过的,那么会增加键名:preprocessed,值为true.
从资源解析器中获得的css集在系统后续流程中将继续传递给css集渲染器。
获取js集:
和css一样js数据同样设置有缓存加速,缓存id为:
$cid = 'js:活动主题名:语言id:' . Crypt::hashBase64(serialize($libraries_to_load)) . (int) (count($assets->getSettings()) > 0) . (int) 是否优化;
css文件全部在html头部加载,和css不同的是js可以在头部和尾部加载,如果库设置的header选项为真,那么该库及其依赖的库中js全部在页头加载,js的选项会被附加以下默认值:
$default_options = [
'type' => 'file',
'group' => JS_DEFAULT,
'weight' => 0,
'cache' => TRUE,
'preprocess' => TRUE,
'attributes' => [],
'version' => NULL,
'browsers' => [],
];
与css一样,在内部用选项数组代表一个js资源,在该数组中用“scope”指示该资源在html头部还是尾部加载
如果js资源不能缓存或设置了标签属性,那么将不进行聚合
模块和主题可以通过“js”修改钩子去修改加载的js资源:
Hook_js_alter(&$js, $assets);
参数$js是以依赖顺序排序的js资源数组,键名是全路径(同css),键值是选项数组,参数$assets同css
执行完修改钩子后再按照weight的值去调整加载顺序,同css,该值优先级高于依赖关系
排序完后,和css一样进行js文件聚合
系统提供了一个很特殊的库“core/drupalSettings”用以处理js设置数据,该数据有两种方式指定:
1、在库定义中以“drupalSettings”选项指定
2、在渲染数组中以“$render_array['#attached']['drupalSettings']”指定
js设置数据处理流程如下:
库定义的设置数据按照依赖顺序依次合并,采用如下合并方法:
\Drupal\Component\Utility\NestedArray::mergeDeepArray
执行模块钩子修改合并结果,函数如下:
hook_js_settings_build(&$settings, $assets);
注意:这里的设置参数并不包括渲染数组中的设置值,返回后它将采用同样的合并方法与渲染数组的设置值合并,渲染数组设置的设置值优先级最高,可以覆写上述数据,合并后派发修改钩子“js_settings”:
hook_js_settings_alter(array &$settings, \Drupal\Core\Asset\AttachedAssetsInterface $assets)
如果库“core/drupalSettings”在头部加载那么设置数据将在任何js加载前加载,如果在尾部加载,那么设置数据在任何尾部js加载前加载。
库发现:
这是一个服务,用于得到一个扩展定义的所有资源库,可通过扩展名得到其下所有资源库定义,也可通过扩展名和库名得到具体的一个资源库定义,这些定义已经被应用了覆写和继承,同时提供清除缓存功能,库发现服务定义如下:
服务名:library.discovery
类:Drupal\Core\Asset\LibraryDiscovery
该服务本质上只提供了静态缓存功能,实际工作是在库发现收集器中进行的
库发现收集器library.discovery.collector:
服务:library.discovery.collector
类:Drupal\Core\Asset\LibraryDiscoveryCollector
该服务继承了缓存收集器CacheCollector(\Drupal\Core\Cache\CacheCollector),缓存收集器用于有巨量数据需要缓存的情形下,如果每次请求都全部加载,性能是很糟糕的,她相当于一个过滤器,只加载被真实使用过的数据,但并不会丢失任何数据,这在运行时主题注册表(\Drupal\Core\Utility\ThemeRegistry)中也被用到。
在库发现收集器中,库定义数据被缓存在cache.discovery中,cid是“library_info:”+当前活动主题名,缓存标签:['library_info'],数据是以扩展为单位储存的,也就是说该缓存数据是以扩展名为键名的一个数组,最初这个数组是空的,在系统运行过程中逐步加入扩展定义的库数据,只有在扩展定义的库被用到后,才会将该扩展加入此缓存以备再用,这就是上述缓存收集器的作用,要得到某个扩展定义的全部库可以这样:
\Drupal::service("library.discovery.collector")->get("bartik");
该代码一旦执行,对应扩展的库定义数据将被缓存且以后每次请求都会被加载,因此不要随意使用该代码,你也可以使用以下方法清理缓存:
\Drupal::service("library.discovery")->clearCachedDefinitions();
//持久缓存连同本次运行时的静态缓存一起清理
\Drupal::service("library.discovery.collector")->clear();
//和前一方法相比只清理持久缓存
在库发现收集器中处理了库继承和一部分库覆写,更多库操作在库发现解析器中进行。
库发现解析器library.discovery.parser:
用于得到单个扩展(核心、模块、主题)定义的全部库信息,这些库没有处理库继承,进行了部分库覆写
服务id:library.discovery.parser
类:Drupal\Core\Asset\LibraryDiscoveryParser
调用入口:
\Drupal::service("library.discovery.parser")->buildByExtension($extension);
该服务执行主要流程是:
读取扩展定义的库文件
如果扩展实现了动态定义(也就是实现了钩子:library_info_build,函数名:moduleName_library_info_build())那么将文件定义和动态定义合并
执行模块和主题的“library_info”修改钩子:hook_library_info_alter(&$libraries, $extension)去调整库定义
将以上步骤得到的库定义运用主题定义的覆写
补充各选项默认值,转化资源路径为绝对路径
注意该服务没有运用任何缓存机制,针对整个库的覆写尚没有完成替换或删除,也没有完成库继承,这些在库发现收集器中完成:"library.discovery.collector"
库依赖解析器library.dependency_resolver:
服务名:library.dependency_resolver
类:Drupal\Core\Asset\LibraryDependencyResolver
该服务的代码量不多,但非常精炼,这里解释一下采用的算法:
doGetDependencies(array $libraries_with_unresolved_dependencies, array $final_libraries = [])
该方法返回传入的所有库和她们的所有依赖,并已经排好序,可被直接使用。
首先我们需要明白被依赖的库应该先加载,在库数组中排在前面,库以“扩展名/库名”的格式出现;库的依赖结构可以视为一张有向无环图,(参考数学专业的《图论》或计算机科学与技术专业的《数据结构》),依赖不能出现循环,否则就是有环图了,该方法采用深度优先遍历算法,能够保证返回的数组中被依赖的库一定出现在依赖她的库的前面,且每一个库只出现一次,该方法第一个参数应该是以加载顺序排好序的库数组,先加载的排在前面,如果传入的数组各元素之间本身存在依赖关系,那么将忽略传入的顺序以保证被依赖的库在前面。
getMinimalRepresentativeSubset(array $libraries)
该方法用于返回一个库集的最小表示,换句话说就是在这个库聚合中,如果一个库被集合中的其他库依赖,那么被依赖的这个库将被去掉,举个例子,假设传入的库集合为:
['core/a', 'core/b', 'core/c']
其中core/c依赖core/a,那么经过该方法处理后,将返回:
['core/b', 'core/c']
由该方法得到的最小表示是最紧凑的,她们的依赖可以由getLibrariesWithDependencies(array $libraries)方法合并进来
资源解析相关设置:
是否聚合css或js文件,在系统后台该处设置:
/admin/config/development/performance
从配置系统得到该信息:
\Drupal::config('system.performance')->get('css.preprocess');
\Drupal::config('system.performance')->get('js.preprocess');
也可以在配置文件中设置以下值强行覆写:
$config['system.performance']['css']['preprocess'] = FALSE;
$config['system.performance']['js']['preprocess'] = FALSE;