如何从 CDN 异步加载多个文件(但同步执行)?

How to load multiple files from CDNs asynchronously (but execute them synchronously)?

当下载多个常用的 javascript/css 文件(例如 boostrap 和 jquery)时,许多主题如 this one 推荐使用 CDN,其主要论点之一是它然后可以用来异步加载它们。

这是如何运作的?据我所知,header 中的 <script> 标签是同步读取的,因此在第一个 CDN 文件完成之前它不会真正查看第二个 CDN 文件。

  <script src="//ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
  <script src="//cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js"></script>
  <script src="//maxcdn.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>

我怎样才能使页面异步下载脚本,但同步执行它们?或者这实际上是以某种方式默认发生的吗? CSS 个文件呢,我的

  <link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/4.5.2/css/bootstrap.min.css">

在这个意义上有什么不同吗?我想在将我自己的故障转移添加到本地代码之前正确理解加载过程(如果 CDN 已关闭),以防止卡在同步下载中。

(请注意,尽管标题为 near-identical,但这并不是 this question 的重复,后者是关于动态加载脚本的。)

另请注意,我不能使用 defer(至少以我所知道的普通方式),因为这会阻止我在 CDN 关闭时添加所述故障转移,例如

<script src="//netdna.bootstrapcdn.com/twitter-bootstrap/2.2.1/js/bootstrap.min.js"></script>
<script>    $.fn.modal || document.write('<script src="Script/bootstrap.min.js">\x3C/script>')</script>

只需添加 defer.

即可打破

我认为您仍然可以使用 defer,只需将您的后备代码放入事件处理程序中...

https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script

defer

This Boolean attribute is set to indicate to a browser that the script is meant to be executed after the document has been parsed, but before firing DOMContentLoaded.

Scripts with the defer attribute will prevent the DOMContentLoaded event from firing until the script has loaded and finished evaluating.

[...]

Scripts with the defer attribute will execute in the order in which they appear in the document.

...所以 DOMContentLoaded 可能是个不错的选择。

或者,你也可以将后备代码放在一个单独的.js文件中,然后用defer也可以加载,依赖于引用的底部,所以在-订单执行。

更多的是 并行性 而不是异步性。 (它们当然是相关的,但 CDN 关于限制同源多次下载的争论是关于并行性的。)

How can I make the page download the scripts asynchronously, but execute them synchronously?

任何体面的浏览器,当给定您显示的三个脚本标签时,将并行下载它们(达到其从同一站点并行的限制),然后按顺序执行它们。您无需执行任何操作即可实现。浏览器在 HTML 中提前读取以查找要获取的资源。

使用 document.write 添加回退脚本可能会使浏览器执行此操作的能力复杂化,甚至阻止它,但您可以使用 <link rel="preload" as="script" href="..."> (more on MDN) 以声明方式确保它。将其与失败的 CDN 资源的回退脚本相结合,它可能看起来像这样:

<head>
<!-- ... -->
<link rel="preload" as="script" href="//ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js">
<link rel="preload" as="script" href="//cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js">
<link rel="preload" as="script" href="//maxcdn.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js">
</head>
<body>
<!-- ... -->
<script src="//ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script>if (!/*loaded condition*/) document.write(/*fallback*/);</script>
<script src="//cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js"></script>
<script>if (!/*loaded condition*/) document.write(/*fallback*/);</script>
<script src="//maxcdn.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js"></script>
<script>if (!/*loaded condition*/) document.write(/*fallback*/);</script>
</body>
</html>

请注意,这不会预加载回退。你可以,但是即使 CDN 正在工作,你也会加载它们,这会浪费最终用户的带宽。回退将用于 CDN 不可用的可能是临时降级的情况,其中降级的用户体验可能是可以接受的。 (您甚至可以在安排回退时向用户显示问题指示器,例如 Gmail 的“某事花费的时间比平时长”指示器。)


如果您对重复 URL 感到困扰,并且您可以接受小剂量 document.write(就像您看起来的那样),您可以通过执行以下操作来避免重复 URL:

<head>
<!-- ... -->
<script>
var scripts = [
    {
        url: "//ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js",
        okay: function() { return /*check it loaded*/; }
    },
    {
        url: "//cdnjs.cloudflare.com/ajax/libs/popper.js/1.16.0/umd/popper.min.js",
        okay: function() { return /*check it loaded*/; }
    },
    {
        url: "//maxcdn.bootstrapcdn.com/bootstrap/4.5.2/js/bootstrap.min.js",
        okay: function() { return /*check it loaded*/; }
    },
];
scripts.forEach(function(script) {
    document.write('<link rel="preload" as="script" href="' + script.url + '">');
});
</script>
</head>
<body>
<!-- ... -->
<script>
scripts.forEach(function(script, index) {
    var fallback = script.url.substring(script.url.lastIndexOf('/') + 1);
    document.write('<script src="' + script.url + '"><\/script>');
    document.write('<script>if (!scripts[' + index + '].okay()) document.write(\'<script src="' + fallback + '"><\/script>\');<\/script>');
});
</script>
</body>
</html>

(因为你不太可能转译这些内联脚本,所以我将语法保持在 ES5 级别,以防你必须支持过时的环境。)