Best Practice: 如何在 WordPress 里优雅地替换 jQuery

jQuery 是一个很大的库,如果能从 CDN 加载,是最好不过的了,这就涉及到要替换 jQuery 的地址,而且我准备前台后台都要替换

WordPress 作为一个插件系统极其变态的存在,自然是有办法替换的了。现在网上的很多现成代码都是两三年前的了,也就意味着,没用,那就自己写吧。

下面是写出来始终没法正常工作的第一版:

// This works bad with concat
add_action( 'init', 'script_movesrc2lfs');
function script_movesrc2lfs(){
	$path = 'https://ajax.googleapis.com/ajax/libs/jquery/1.12.3/jquery.min.js';

	$e = $o->query('jquery', 'registered');
	if($e && $e->ver == '1.12.3'){
		$e = $o->query( 'jquery-core', 'registered');
		$e->src = $path;
	}
}

直接在 registered 层面就把地址替换了,原理看起来是很简单的。

然而,因为 WordPress 对后台所有的 scripts 都会做拼接处理(concat),每次 jQuery 应该是第一个拼接的。经过这段代码之后,jQuery 库的地址不在本地,WP 就把这个库移到拼接 script 的后面了。后面了。后面了。

后面啊!别的依赖它的程序还怎么运行啊!

另外,之前还想把 migrate 拿掉,结果天啊,万万没想到破 Akismet 的 Javascript 还依赖 migrate。

于是这是还算能用的第二版:

add_action( 'wp_enqueue_scripts', 'script_movesrc2lfs', 5);
add_action( 'admin_print_scripts', 'script_movesrc2lfs', 5);
function script_movesrc2lfs(){
	$o = wp_scripts();
	$path = 'https://ajax.googleapis.com/ajax/libs/jquery/1.12.3/jquery.min.js';
	$ver = null;

	$e = $o->query('jquery', 'registered');
	if($e && $e->ver == '1.12.3'){ // Remove $e->ver if you want to be aggressive
		$e->deps = array('jquery-migrate');
		// Removed jquery-core

		$e = $o->query( 'jquery-core', 'registered');
		$ver = $e->ver;
		$e->src = $path;
	}

	$e = $o->query('jquery', 'queue');
	if($e && $e->ver == '1.12.3'){ // Remove $e->ver if you want to be aggressive
		echo "<script type='text/javascript' src='".$path."?ver=".$ver."'></script>n";
	}
}

这块的逻辑是,自己实现 header 位置 jquery 依赖 jquery-core 的过程,因为在 print_head_scripts 前执行,只要再把 jquery-core 从 queue 列表中拿走,就不会影响到自动 concat 的过程了。

带来的可能存在的问题是,如果程序是在 footer 请求的 jquery,就只能请求到 jquery-migrate,core 加载不进来。如果直接请求 jquery-core 是不受影响的。因为我自己的 WordPress 不会有这种情况,就把逻辑省下来了。插入回去的话还得特意再插个 action 自己实现 footer 的依赖。

写完上面这段,出去吃了个饭,觉得有点不甘心。我们的思路已经很清楚了,就是要自己实现 deps,那怎么不直接 Hook 到处理 deps 的位置呢?吃完饭回来查了下,果然 WordPress 在这么细节的位置也有 Hook(print_scripts_array),那就出第三版咯。

add_action( 'wp_print_scripts', 'script_movesrc2lfs', 5);
add_filter( 'print_scripts_array', 'script_movesrc2lfs_handledeps', 5);
function script_movesrc2lfs(){
    $o = wp_scripts();

    $path = 'https://ajax.googleapis.com/ajax/libs/jquery/1.12.3/jquery.min.js';
    $ver = '1.12.3'; // To be more aggressive, use null

    $e = $o->query('jquery', 'registered');
    if($e && (!$ver || $e->ver == $ver)){
        // $e->deps = array('jquery-core');
        // Akismet 居然还依赖 Migrate,无法去除

        $e = $o->query( 'jquery-core', 'registered');
        $e->src = $path;
    }
}
function script_movesrc2lfs_handledeps($array){
    $o = wp_scripts();
    if($o->do_concat && in_array('jquery', $array)){
        // Remove it from the array
        $array = array_diff($array, array('jquery-core'));
        // Handle dependencies by ourselves
        $o->do_concat = false;
        $o->do_item('jquery-core');
        $o->do_concat = true;
    }
    return $array;
}

完美。如果要替换更多的库文件也是同理,就是要注意自己实现依赖的时候,前后的顺序咯。

还有,WordPress 自己用的 jQuery 文件后面有带 noConflict(),换了 CDN 一般就没这句了。因为我觉得让 jQuery 污染了 $ 之后好像没什么问题,就没在 inline 代码里补上。如果要彻底模拟 WP 的功能的话还是应该加上的。

后话:noConflict 是为了跟 Prototype 兼容的,然而 WP 早就不自带 Prototype 了(代码直接引用的 Google CDN)。

P.S. 截至发稿时,Google CDN 还没有同步 jQuery 1.12.3 版,所以上面代码直接运行是运行不动的。按照国情,我实际用的 CDN 地址自然是自己的 CDN 咯。

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

To respond on your own website, enter the URL of your response which should contain a link to this post's permalink URL. Your response will then appear (possibly after moderation) on this page. Want to update or remove your response? Update or delete your post and re-enter your post's URL again. (Find out more about Webmentions.)