在之前的文章里,我介绍了 MediaWiki 中导入 CSS 和 JavaScript 脚本的两种主要方式,分别是 Widget 和 Gadget。
网页中 JavaScript 的加载有一个很重要的问题,就是加载的时机。假如一段控制 DOM 的代码在 DOM 加载完成之前执行,或者一个函数调用在函数被定义之前执行,都会导致脚本执行出错。在 MediaWiki 系统中,由于代码的导入方式比较复杂,我们必须仔细研究不同方式导入代码的时机,以及如何控制它们之间的依赖。
Widget 导入脚本
Widget 的代码就是被 MediaWiki 系统内部处理好插入页面中的,它已经包含在了网页源代码里面。因此,Widget 导入的脚本会像一般的网页 DOM 一样被执行。运行时机分为三种:
<script>:加载到这一段代码之后马上执行,执行完毕后再继续加载 DOM。
<script defer>:页面 DOM 加载完毕之后执行。
<script async>:加载到这一段代码之后开始执行,同时加载 DOM。
Gadget 导入脚本
Gadget 其实是利用 ResourceLoader 加载的。
ResourceLoader 在什么时候开始加载内容,我并不清楚。
ResourceLoader 有一套依赖体系。这就是写在 Mediawiki:Gadgets-definition 里面的 dependencies。这套体系其实并不是在 Gadget 当中导入什么内容,而是规定了 Gadget 的加载顺序,一个 Gadget 在它的所有依赖被加载完成之后才会被加载。
Gadget 什么时候能够被加载完,这一点也并不是很确定。
mw.loader 导入脚本
mw 是 MediaWiki 挂载到 window 对象上的一个工具箱,其中 mw.loader 就是 ResourceLoader。它在 MediaWiki 和 WikiMedia 网站都有文档可查。分为以下三种导入方式:
mw.loader.load
可以导入模块、CSS 或 js。第一个参数是要导入的内容,第二个参数是内容类型。
它是异步加载的,所以你不能够确保其他代码在这一条代码完成之后运行。
如果要导入 CSS,我觉得用这个就够了,毕竟 CSS 是相对独立的。
mw.loader.using
它本身不会导入模块,只是等待模块被导入。第一个参数是模块,第二个参数是导入模块后的回调函数。这可以确保回调函数在模块被导入后执行。本身是一个 Promise。
mw.loader.getScript
MediaWiki 1.33 导入的新功能,只能导入外部脚本。它本身也是一个 Promise,导入完毕外部脚本之后会执行回调函数。
window.RLQ.push() 导入脚本
MediaWiki 并没有官方文档描述这一项功能。然而它是很多 Widget 都必须使用的一个功能。
因为 Widget 是包含在网页源代码之内的,所以它的加载也有可能非常早,甚至早于 mw 对象被初始化。如果没有 mw,mw.loader 就无法使用。这时候就必须使用 window.RLQ 来“提前”准备好这些脚本,等 mw 初始化之后再使用它。
RLQ 的全称是 ResouceLoader Queue。它的写法就类似于 Gadgets-definition 的写法,有以下两种,分别对应没有依赖和有依赖:
(window.RLQ=window.RLQ||[]).push(function(){ ... });
(window.RLQ=window.RLQ||[]).push([ ['module.a'], function(){ ... } ]);
开发建议
如果你的脚本需要被依赖,那么建议使用 Gadget。Gadget 依赖 Gadget 可以在 Gadget-definitions 里面写出来,Widget 依赖 Gadget 可以使用 window.RLQ.push 或者 mw.loader.using。一般不要用 Widget,除非你使用广播事件等特殊手法。
如果你的脚本是 Widget,但是需要使用 mw,那么你应该使用 window.RLQ.push。