Keširanje JS-a u local storage

Palo mi je na pamet keširanje JS-a u local storage. Gledam malo po netu i vidim da je local storage dosta brži od standardnog keša, pa se mislim da si složim neki loader.

Da li neko koristi ovu tehniku, kakva su iskustva?

P.S.
Local storage je podržan u svim preglednicima osim u opera mini

A sta bi tacno kesirao posto je localStorage ogranicen na 5MB, ako se ne varam ?

Da oko 5mb, zavisi od preglednika. Samo mi nemoj reći da imaš 5mb JS koda na sajtu ?
Znači, keširao bi eksterne JS datoteke (jquery, štagod).
Cilj keširanja u local storage bi bio izbjeći blokiranje parsera, i samim time dobiti na brzini učitavanja.

Koliko brži od standarnog keša? Vjerovatno minorno s obzirom na trajanje loada ostalih asseta.

Ionako je najveci problem prvi load kada nista nije kesirano, a kasnije vjerovatno dodje svejedno jel to localstorage ili keš namjenjen toj svrsi.

A ako odes tim smjerom da radis neke svoje varijante keša,radi minorne dobiti na loadu, navuci ces si samo druge probleme kojih trenutno nisi svjestan…koji trenutno mozda i nisu vidljivi zbog nepredvidljivosti daljnjeg razvoja…

Pa i nije baš minorno https://jsperf.com/localstorage-versus-browser-cache/12

Iako logično, i nije baš tako. Složio sam si loader koji kešira JS u localstorage i nakon testiranja mogu reći da je local storage u nekim situacijama i do 4 puta brži od normalnog keša.

Da, slažem se, ali… baš sam danas saznao da cloudfare koristi ovu tehniku u svom rocket loaderu :slight_smile:

Ovaj test baš ne odgovara na moje pitanje. Rekoh da je ušteda vjerovatno minorna s obzirom na trajanje loada ostalih asseta. …što znači da možeš imati i 100% brže učitavanje iz localStoragea,a da je to opet zanemariva ušteda spram onoga koliko će u realnosti load trajati zbog slikica i svega ostaloga.

+ uzmeš u obzir da ovakva optimizacija ne utječe nikako na prvi load, koji je ionako najteži
+ uzmeš u obzir da dodatno kompliciraš i možeš narušiti kompatibilnost s razno raznim alatima koji niču svaki dan.

Tako da možda sve skupa nema smisla, možda ima…na tebi je da odvažeš, jer sam najbolje znaš što želiš postići.

Prvi load je uvijek sranje kod svega, pa tako i kod ovoga.

Kad makneš JS iz DOM-a, nema više ništa što bi blokiralo generiranje CSSOM-a. Dodajmo još i asinkrono učitavanje CSS-a i onda to sve skupa se itekako osjeti.

Evo upravo završavam primjer kojeg ću postati ovdje, pa slobodno testirajte.

Pa ajmo reći da je otprilike to to, iako ni sam više nisam siguran da li sam dobio šta s ovim osim problema :slight_smile:

localscript.js

(function(w){

    'use strict';

    var d = w.document,
        self = d.currentScript || d.querySelector('script[data-expire]');

    d.addEventListener('DOMContentLoaded', function(){

        var ls = w.localStorage,
            scripts = d.getElementsByTagName('script');

        // check if local storage is supported
        if (ls) {
            var time = Math.floor(new Date().getTime() / 1000),
                expire = ls.getItem('ls-expire'),
                expire_in = self.getAttribute('data-expire') || 3600  // expire in seconds
            // clear if data is expired
            if (null === expire || time > expire) {
                ls.clear();
                ls.setItem('ls-expire', parseInt(time) + parseInt(expire_in));
            }
        }

        // loop through each script tag
        [].forEach.call(scripts, function(script){
            var code;
            // check type
            if (script.type != 'text/localscript') return true;
            // cache only external javascript
            if (script.hasAttribute('data-src')) {
                var src = script.getAttribute('data-src');
                code = ls ? ls.getItem(src) : null; 
                // src is not in storage or local storage is not supported
                if (null === code) { 
                    var x = new XMLHttpRequest();
                    // get the code
                    x.open('GET', src, script.hasAttribute('async'));
                    // to-do: timeout
                    x.send();
                    // check status
                    if (x.status === 200) {
                        code = x.responseText;
                        // is ls supported
                        if (ls) {
                            try {
                                ls.setItem(src, code);  
                            } catch (e) {
                                // storage is probably full, flush it out
                                ls.clear();
                            }
                        }                        
                    }

                }

            } else {
                code = script.innerHTML; 
            }

            try {
                new Function(code)();
            }
            catch (e) {
                console.log(e.name, e.message);
            }

        });

    }, false);

})(this);

Ubacite u footer stranice ovako
<script src="localscript.js" data-expire="3600"></script>
data-expire atribut označava koliko će skripta biti keširana u sekundama, zadana vrijednost je 3600 ili jedan sat

Promjenite sve JS tagove da izgledaju ovako

<script type="text/localscript" data-src="https://code.jquery.com/jquery-3.2.1.min.js"></script>
<script type="text/localscript" data-src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"></script>

I ovako

<script type="text/localscript">
    console.log('hello from localscript');
</script>

Znači ako je eksterna datoteka umjesto src treba biti data-src i type treba biti text/localscript

Imaš neki relevantan test brzine/performansi?

U FF-u se vidi razlika, Chrome ako keš ide iz memorije, a ne sa diska i nema neke razlike.

Evo malo sam se igrao i uz pomoć promise objekta napravio sam da se svi vanjski resursi učitavaju asinkrono, a izvršavaju sinkrono :smile: i sad ovo leti. Inicijalni load je puno brži

Evo mali update

    /* localscript.js */
    (function(w){ 
        
        'use strict';

        var d = w.document,
            ls = w.localStorage,
            self = d.currentScript || d.querySelector('script[data-expire]');

        var get = function(scripts) {

            var promises = [];

            scripts.forEach(function(script){

                var promise = new Promise(function(resolve, reject){
                    // cache only external javascript
                    if (script.url) {
                        // get code
                        var code = ls ? ls.getItem(script.url) : null; 
                        // url is not in storage or local storage is not supported
                        if (code === null) { 

                            var xhr = new XMLHttpRequest();

                            xhr.open('get', script.url);
                            xhr.onload = function(){
                                // check status
                                if (xhr.status == 200) {
                                    // if local storage
                                    if (ls) {
                                        try {
                                            ls.setItem(script.url, xhr.response);  
                                        } catch(e) {
                                            ls.clear(); // storage is probably full, flush it
                                        }
                                    }

                                    resolve(xhr.response);

                                } else {
                                    reject(new Error(xhr.responseURL + ' ' + xhr.statusText));
                                }

                            };

                            xhr.send();
         
                        } else {
                            resolve(code);
                        }
                        
                    } else {
                        resolve(script.code);
                    }
              
                });
                // add promise
                promises.push(promise);

            });
            // return promises
            return Promise.all(promises);
        };

        // hook up
        d.addEventListener('DOMContentLoaded', function(){

            var data = [],
                scripts = d.getElementsByTagName('script');

            // check if local storage is supported
            if (ls) {
                var time = Math.floor(new Date().getTime() / 1000),
                    expire = ls.getItem('ls-expire'),
                    expire_in = self.getAttribute('data-expire') || 3600;  // expire in seconds
                // clear if data is expired
                if (null === expire || time > expire) {
                    ls.clear();
                    ls.setItem('ls-expire', parseInt(time) + parseInt(expire_in));
                }
            }

            // process all script tags
            for(var i in scripts) {
                if (scripts[i].type == 'text/localscript') {
                    data.push({
                        url : scripts[i].getAttribute('data-src') || null,
                        code : scripts[i].innerHTML || null
                    });
                }
            }

            // run
            get(data).then(function(codes){
                for(var i in codes) {
                    try {
                        (0, eval)(codes[i]); // execute scripts in global scope
                    } catch(e) {
                        console.log(e.name, e.message);
                    }
                }
            }).catch(function(error){
                console.log(error);
            });

        }, false);

    })(this);
2 Likeova

Cool.

Bilo bi dobro da kreiras npm modul.

Hm… ne znam baš kako bi to išlo zbog samog načina rada skripte :slight_smile:

Ne znam kakav API skripta pruza, nisam gledao detaljno stvari, ali ne vidim nigdje problem.

Kreiras npm modul, i krajnji korsnik ga importa i koristi API kako zeli :slight_smile:

1 Like