How to write RequireJS loader plugin with cache?

走远了吗. 提交于 2020-01-04 10:39:52

问题


I'm writing a RequireJS loader plugin. The plugin fetches html fragments cross domain via EasyXDM. It's invoked using the loader syntax, like this:

'html!someTemplate,#fragmentSelector'

Since many repeat requests may occur, for example requesting different fragments from the same HTML document, I want to cache both the whole HTML document and the fragments. But I can't do any caching so far because I apparently have a gaping hole in my understanding of RequireJS loader plugins. I thought it would not get called again until it signaled completion by calling the supplied onLoad() function. But this is not the case. Debugging with console statements revealed that 62 calls (there are 62 asset requests, total, in this app) are made in rapid succession before I ever call onLoad(). I try to check the cache on these before passing through to the asynchronous part, but there is never anything in the cache because all 62 calls have passed through to the asynchronous part. Those 62 async calls do return good data, so ultimately, the plugin works fine. But my caching doesn't, and I can't for the life of me figure our how to solve this. Any help would be greatly appreciated.


回答1:


Ok, finally figured out how to do this. Verbally, what you have to do is queue up all calls to the loader and then process them one at a time. At the following code points, pull one call from the queue process it:

  • after the first call to the plugin
  • after the asynchronous call completes
  • every time you avoid the asynchronous call by returning a cached value.

Here's the code for anyone who might need an example

/**
 * 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"
    };

});


来源:https://stackoverflow.com/questions/16314077/how-to-write-requirejs-loader-plugin-with-cache

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!