控制动态加载脚本的加载顺序

Controlling load order on dynamically loaded scripts

我有一个基本的 SPA 可以按需加载一些资产(主要是样式表和脚本)。

加载程序看起来像这样(这是一个简化版本):

class ModuleXLoader
    constructor: ->
        @scripts = [
            'https://www.example.com/assets/js/script1.js',
            'https://www.example.net/assets/js/script2.js',
            'https://www.example.org/assets/js/script3.js'
        ]
        @scriptsLoaded = 0

    load: (@callback) ->
        document.head.appendChild @.scriptTag url for url in @scripts

    scriptTag: (url) ->
        domElement = document.createElement 'script'
        domElement.type = 'text/javascript'
        domElement.onload = (event) =>
            console.log event.currentTarget.src # This logs the script's URL
            @.callback() if ++@scriptsLoaded is @scripts.length and typeof @callback is 'function'
        domElement.src = url
        return domElement

所以,当我需要加载时 ModuleX 我会这样做:

loader = new ModuleXLoader()
loader.load () =>
   console.log 'All scripts have been loaded, let\'s do stuff!' 

这会将所需的脚本附加到我的 <head> 并且一切正常。


当所需脚本之间存在一些依赖关系时,会出现问题。根据每个 CDN 的响应时间(假设 example.comexample.net...)脚本以 随机 顺序加载,因此,有时我得到了经典:

Uncaught ReferenceError: ModuleXDependency is not defined

当然,我在我的 @scripts 数组上尝试了不同的排序,但它并不重要。

我在考虑某种信号量实现:

@scripts =
    script1:
        url: 'https://www.example.com/assets/js/script1.js'
        requires: 'script3'
        loaded: false
    script2: # etc.


domElement.onload = (event) =>
    # This is not a real implementation but kind of pseudocode idea...
    @wait() while not @scripts[@scripts['script1'].requires].loaded

不过老实说走这条路感觉太脏了,所以我想知道是否有人有更好的主意...

一个解决方案是创建包,只需将您将同时加载的所有依赖项连接到一个缩小的 js 文件中,"bundle"。加载一个文件而不是 3 个文件。它将解决您的依赖顺序问题并提供其他改进。

我的建议是使用像 AMD 这样的模块定义。也许您应该考虑使用现有的模块加载器,例如 requirejs. Here will you also find documentation about AMD. Maybe read the whyAMD text.

如果你想要一个绝对最小的加载器并自己加载脚本,你可以查看 loader.js

关于 AMD 的想法基本上是这样定义你的模块:

define('dep', [], function() {
  // here goes your code
  return {};
});

define('mymodule', ['dep'], function(dep) {
  // here goes your code.
});

loader.jsrequire.js这样的加载器基本上会构建这些模块的图表,然后当你做像require('mymodule')这样的事情时,它会知道它必须调用dep,然后将结果作为第一个参数注入mymodule

loader.js 基本上只会这样做,而 require.js 可以通过网络加载模块。

然而,对于这两种方式,您都可以手动加载脚本标签,等待所有脚本标签加载完毕,然后调用您的入口点。

重要的是调用 define 的执行顺序并不重要。