2

我正在编写一个 RequireJS 加载器插件。该插件通过 EasyXDM 跨域获取 html 片段。它使用加载器语法调用,如下所示:

'html!someTemplate,#fragmentSelector'

由于可能会发生许多重复请求,例如从同一个 HTML 文档请求不同的片段,我想缓存整个 HTML 文档和片段。但到目前为止,我还不能做任何缓存,因为我对 RequireJS 加载器插件的理解显然存在很大的漏洞。我认为它不会再次被调用,直到它通过调用提供的onLoad()函数发出完成信号。但这种情况并非如此。使用控制台语句进行调试显示,在我调用onLoad()之前,快速连续进行了 62 次调用(在这个应用程序中总共有 62 个资产请求). 我尝试在传递到异步部分之前检查这些缓存,但缓存中从来没有任何内容,因为所有 62 个调用都已传递到异步部分。这 62 个异步调用确实返回了良好的数据,所以最终插件工作正常。但是我的缓存没有,而且我一生都无法弄清楚如何解决这个问题。任何帮助将不胜感激。

4

1 回答 1

0

好的,终于想出了如何做到这一点。口头上,您要做的是将所有对加载程序的调用排队,然后一次处理一个。在以下代码点,从队列中拉出一个调用来处理它:

  • 在第一次调用插件之后
  • 异步调用完成后
  • 每次通过返回缓存值来避免异步调用。

这是任何可能需要示例的人的代码

/**
 * RequireJS plugin for loading templates cross domain via easyXDM
 * Author: Larry Gerndt
 * Version: 0.0.1 (2013/5/1)
 * Usage: html!url,fragment-selector
 *      url: omit the .html extension
 *      fragment-selector: css selector of the fragment to extract
 */
define(['assetLoader','jquery'], function(AssetLoader, $) {
    /**
     * Caches documents and fragments of documents
     * The hash is derived from everything following the bang (!)
     * For example, given this: html!assets/templates/IntroductionTooltip/introduction-tooltip,#mint-itt, we just
     * strip out all illegal characters using the _hash() function and that's our hash for fragments.  But we also
     * want to cache the document from which the fragment came, in case a request is made for a different fragment from
     * the same document.  The hash for the document cache is made the same way as for fragments, except first omitting
     * everything from the comma to the end of the line.  In other words, omitting the fragment selector.
     */
    function Cache(name) {
        this.name = name;
        this.cache = {};
        this.size = 0;
    }
    Cache.prototype = {
        get: function(name) {
            return this.cache[name];
        },
        has: function(name) {
            return this.cache.hasOwnProperty(name);
        },
        add: function(name, html) {
            this.cache[name] = html;
            this.size += 1;
        }
    };


    //-----------------------------------------------------------------------------------------------------------
    // a FIFO queue that stores calls to this module
    //-----------------------------------------------------------------------------------------------------------

    function CallQueue() {
        this.store = [];
    }
    CallQueue.prototype = {
        push: function(name, req, onLoad, config) {
            this.store.push({
                name  : name,
                req   : req,
                onLoad: onLoad,
                config: config
            });
        },
        pop: function() {
            return this.store.length ? this.store.splice(this.store.length - 1, 1)[0] : null;
        },
        isEmpty: function() {
            return this.store.length === 0;
        }
    };

    var documentCache = new Cache('document'),
        fragmentCache = new Cache('fragment'),
        callQueue = new CallQueue(),
        processedFirstCall = false;

    //-----------------------------------------------------------------------------------------------------------
    // helpers
    //-----------------------------------------------------------------------------------------------------------

    function getFragment(html, fragmentSelector) {
        var $container, fragmentHtml;
        if (!document.getElementById('mint-html-container')) {
            $('body').append('<div id="mint-html-container" style="display:none;"></div>');
        }
        $('#mint-html-container').html(html);
        fragmentHtml = $(fragmentSelector).get(0).outerHTML;
        return fragmentHtml;
    }

    function hash(string) {
        return string.replace(/[\/,#\.\s\-]/g, '');
    }

    function loadRemoteAsset(url, fragmentSelector, onLoad) {
        var documentHash = hash(url),
            fragmentHash = hash(url+fragmentSelector);

        AssetLoader.loadHtmlFragment(url + '.html', fragmentSelector).then(function(fragmentHtml, allHtml) {
            documentCache.add(documentHash, allHtml);
            fragmentCache.add(fragmentHash, fragmentHtml);
            onLoad(fragmentHtml);
            processOneCall();
        }, function() {
            onLoad.error('AssetLoader: failed for unknown reason');
        });
    }

    function processOneCall() {

        if (!callQueue.isEmpty()) {
            var item = callQueue.pop(),
                split = item.name.split(','),
                url = split[0],
                fragmentSelector = split[1];

            if (url.indexOf('/') === 0) {
                url = item.config.baseUrl + url;
            }
            if (!url || !fragmentSelector) {
                item.onLoad.error('AssetLoader: invalid argument: ' + item.name + '\n Example Usage: html!assets/templates/IntroductionTooltip/introduction-tooltip,#mint-itt');
            }
            else {
                var documentHash = hash(url),
                    fragmentHash = hash(url+fragmentSelector);

                if (fragmentCache.has(fragmentHash)) {
                    item.onLoad(fragmentCache.get(fragmentHash));
                    //console.log('using cached fragment for url: ', url, ', fragment: ', fragmentSelector);
                    processOneCall();
                }
                else if (documentCache.has(documentHash)) {
                    var fragmentHtml = getFragment(documentCache.get(documentHash), fragmentSelector);
                    fragmentCache.add(fragmentHash, fragmentHtml);
                    item.onLoad(fragmentHtml);
                    //console.log('using cached document for url: ',url, ', fragment: ', fragmentSelector);
                    processOneCall();
                }
                else {
                    loadRemoteAsset(url, fragmentSelector, item.onLoad);
                }
            }
        }
    }

    //-----------------------------------------------------------------------------------------------------------
    // public API
    //-----------------------------------------------------------------------------------------------------------

    return {
        load : function(name, req, onload, config){
            callQueue.push(name, req, onload, config);
            if (!processedFirstCall) {
                processedFirstCall = true;
                processOneCall();
            }
        },
        pluginBuilder: 'html-builder' // uses this no-op module during Optimizer build to avoid error "window is not defined"
    };

});
于 2013-05-02T05:07:08.227 回答