如何在外部 CDN 脚本失败时异步回退? (async/defer)

How to asynchronously fall back when external CDN script fails? (async/defer)

我有这些脚本,我想 download/execute 异步地防止页面呈现阻塞:


Bootstrap依赖于jQuery,所以必须按照这个顺序执行。这就是我使用 defer 的原因,但我的问题也适用于 async

如何异步回退到我自己的 jQuery 或 Bootstrap 托管版本,或任何 CDN 托管的脚本?

我已经看到 this related question on Whosebug,但这些解决方案不起作用,因为:

  1. 它依赖于document.write,不能用于async/defer脚本;或:
  2. 它不会立即执行回退脚本,当 jQuery 无法加载时会导致问题,并且依赖于 jQuery 的其他脚本会在 jQuery 回退执行之前加载。

我尝试了以下方法,但这也导致了上面描述的问题 #2:

<script src="failing external CDN script" defer onerror="
    var script = document.createElement('script');
    script.async = false;
    script.src = 'my own hosted version the script';
    document.body.appendChild(script);
"></script>

I have these scripts that I want to download/execute asynchronously to prevent page render blocking

最简单的方法就是将它们放在页面末尾,就在结束 </body> 标记之前。他们不 render-block 在那里,你可以简单地做回退:

<script src="jQuery CDN"></script>
<script>
if (typeof jQuery === "undefined") {
    // ...add your local version instead...
}
</script>

您在评论中说过,PageSpeed 会抱怨 </body> 之前的 script 标签,如果它们没有 defer。恕我直言,它没有(请注意 link 到 code.jquery.com 是故意破坏的):

<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Example</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="shortcut icon" href="favicon.png">
</head>
<body>
<div class="container">
    <div class="row">
        <div class="col-md-12">
            Content here, turns green on click
        </div>
    </div>
</div>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet" type="text/css" />
<script src="https://code.jquery.com/jquery-2.2.4.mn.js"></script>
<script>
if (typeof jQuery === "undefined") {
    document.write('<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.2.4/jquery.min.js"><\/script>');
} 
</script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/js/bootstrap.min.js"></script>
<script>
$(".col-md-12").on("click", function() {
    $(this).css("color", "green");
});
</script>
</body>
</html>

从 PageSpeed 获得 100/100(如果您正确托管它——压缩等)。

现在,如果我在底部有 CSS link,我也会在顶部有内联 CSS 来设置 above-the-fold 内容的样式。 (但我可能不会打扰,我只是将 link 放在顶部并接受命中。我只将它放在该示例的底部,因为 PageSpeed 将 JS 和 CSS 放在一起它关于该主​​题的警告,我想证明 </body> 之前的 script 不会触发该错误...)


但是如果你想使用 defer,你必须监听你问题中显示的 error 事件。当然,这意味着您必须推迟添加依赖于 jQuery 的脚本,直到您从原始脚本或其替换中获得 load,因为您不能 insert 到管道中。例如:

<script src="failing external CDN script" defer onerror="
    var script = document.createElement('script');
    script.async = false;
    script.onload = addDependencies;
    script.src = 'my own hosted version the script';
    document.body.appendChild(script);
" onload="addDependencies()"></script>

...其中 addDependencies 根据脚本为事物添加 script 标签。