Come eseguire il fallback nel foglio di stile locale (non nello script) se la CDN fallisce


108

Sto collegando al foglio di stile jQuery Mobile su un CDN e vorrei tornare alla mia versione locale del foglio di stile se il CDN fallisce. Per gli script la soluzione è ben nota:

<!-- Load jQuery and jQuery mobile with fall back to local server -->
<script src="http://code.jquery.com/jquery-1.6.3.min.js"></script>
<script type="text/javascript">
  if (typeof jQuery == 'undefined') {
    document.write(unescape("%3Cscript src='jquery-1.6.3.min.js'%3E"));
  }
</script>

Vorrei fare qualcosa di simile per un foglio di stile:

<link rel="stylesheet" href="http://code.jquery.com/mobile/1.0b3/jquery.mobile-1.0b3.min.css" />

Non sono sicuro se un approccio simile possa essere ottenuto perché non sono sicuro che il browser si blocchi nello stesso modo quando collega uno script come fa quando carica uno script (forse è possibile caricare un foglio di stile in un tag di script e poi iniettarlo nella pagina)?

Quindi la mia domanda è: come posso assicurarmi che un foglio di stile venga caricato localmente se un CDN non funziona?


2
Vorrei sapere se anche questo è possibile ... Se davvero mi preoccupassi che il CDN non fosse disponibile, userei solo l'hosting locale.
Fosco

2
@Stefan Kendall, penso che l'affermazione giusta sia che il suo sito molto probabilmente andrà giù di un CDN
Shawn Mclean

Risposte:


63

Non testato su più browser, ma penso che funzionerà. Dovrà essere dopo aver caricato jquery, o dovrai riscriverlo in semplice Javascript.

<script type="text/javascript">
$.each(document.styleSheets, function(i,sheet){
  if(sheet.href=='http://code.jquery.com/mobile/1.0b3/jquery.mobile-1.0b3.min.css') {
    var rules = sheet.rules ? sheet.rules : sheet.cssRules;
    if (rules.length == 0) {
      $('<link rel="stylesheet" type="text/css" href="path/to/local/jquery.mobile-1.0b3.min.css" />').appendTo('head');
    }
 }
})
</script>

buona soluzione, un problema che non risolve è se il CDN è troppo lento da caricare ... forse una sorta di timeout?
GeorgeU

2
Per code.jquery.com/ui/1.10.2/themes/smoothness/jquery-ui.css , ottengo rules = null, anche se è stato caricato correttamente. Sto usando Chrome 26 e penso che sia perché lo script è interdominio?
simplfuzz

8
La soluzione non funziona davvero per tutti i CDN / fogli di stile, ad esempio gli CSSStyleSheetoggetti js che provengono da bootstrapcdn.com hanno tutti campi rulese vuoti cssRulesnel mio browser (Chrome 31). UPD : in realtà potrebbe essere un problema di crossdomain, anche il file css nella risposta non funziona per me.
Maksim Vi.

3
Questo è anche un buon sollievo usando l' onerrorevento. stackoverflow.com/questions/30546795/…
Chemical Programmer

2
Funziona per CSS caricato da un altro dominio? No, non puoi enumerare .rules/ .cssRulesper fogli di stile esterni. jsfiddle.net/E6yYN/13
Salman A

29

Immagino che la domanda sia rilevare se un foglio di stile è caricato o meno. Un possibile approccio è il seguente:

1) Aggiungi una regola speciale alla fine del tuo file CSS, come:

#foo { display: none !important; }

2) Aggiungi il div corrispondente nel tuo HTML:

<div id="foo"></div>

3) Quando il documento è pronto, controlla se #fooè visibile o meno. Se il foglio di stile è stato caricato, non sarà visibile.

Demo qui : carica il tema di levigatezza jquery-ui; nessuna regola viene aggiunta al foglio di stile.


42
+1 per una soluzione intelligente. L'unico problema è che normalmente non si può andare ad aggiungere una riga alla fine di un foglio di stile ospitato sul CDN di qualcuno
Jannie Theunissen

2
Sfortunatamente, non possiamo digitare le nostre classi nei file CDN. Forse possiamo provare a utilizzare quello che esiste già.
Ahamed

1
Questo mi piace MOLTO, grazie. È davvero molto potente. Ovviamente non posso manipolare il foglio di stile CDN ma so quali classi vengono utilizzate, quindi ho modificato il codice per mostrare il controllo se sono visibili - davvero molto intelligente :)
Nosnibor

3
NB: non è necessario aggiungere una nuova regola al CSS esterno. Usa semplicemente una regola esistente il cui comportamento è noto. Nella mia demo utilizzo la classe ui-helper-hidden che dovrebbe nascondere l'elemento, quindi controllo se l'elemento viene nascosto al caricamento della pagina.
Salman A

28

Supponendo che tu stia utilizzando lo stesso CDN per css e jQuery, perché non fare un solo test e catturarlo tutto ??

<link href="//ajax.googleapis.com/ajax/libs/jqueryui/1/themes/start/jquery-ui.css" rel="stylesheet" type="text/css" />
<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<script type="text/javascript" src="//ajax.googleapis.com/ajax/libs/jqueryui/1/jquery-ui.min.js"></script>
<script type="text/javascript">
    if (typeof jQuery == 'undefined') {
        document.write(unescape('%3Clink rel="stylesheet" type="text/css" href="../../Content/jquery-ui-1.8.16.custom.css" /%3E'));
        document.write(unescape('%3Cscript type="text/javascript" src="/jQuery/jquery-1.6.4.min.js" %3E%3C/script%3E'));
        document.write(unescape('%3Cscript type="text/javascript" src="/jQuery/jquery-ui-1.8.16.custom.min.js" %3E%3C/script%3E'));
    }
</script>

1
Posso chiedere quale sia il problema con l'utilizzo di stringhe senza caratteri di escape inizialmente, ad esempio document.write("<script type='text/javascript' src='path/to/file.js'>")?
Jack Tuck

2
@ JackTuck: il parser non può distinguere tra <script>all'interno di una stringa JS e una trovata all'esterno. Questo è comunemente il motivo per cui vedi anche <\/script>quando scrivi tag per i fallback CDN.
Brad Christie

6
-1 why not just do one test and catch it all?- Perché ci sono un milione di motivi per cui uno potrebbe fallire e gli altri hanno successo.
Flimzy

7

questo articolo suggerisce alcune soluzioni per il bootstrap css http://eddmann.com/posts/providing-local-js-and-css-resources-for-cdn-fallbacks/

in alternativa funziona per fontawesome

<link href="//maxcdn.bootstrapcdn.com/font-awesome/4.2.0/css/font-awesome.min.css" rel="stylesheet">
<script>
    (function($){
        var $span = $('<span class="fa" style="display:none"></span>').appendTo('body');
        if ($span.css('fontFamily') !== 'FontAwesome' ) {
            // Fallback Link
            $('head').append('<link href="https://stackoverflow.com/css/font-awesome.min.css" rel="stylesheet">');
        }
        $span.remove();
    })(jQuery);
</script>

Per coloro che cercano di usarlo con Font Awesome 5, ti consigliamo di cambiare "FontAwesome" (nella clausola if) in "Font Awesome 5 Free" (se stai usando i caratteri gratuiti). Altrimenti, dovrebbe funzionare bene.
Jason Clark

4

Si potrebbe essere in grado di verificare l'esistenza del foglio di stile in document.styleSheets.

var rules = [];
if (document.styleSheets[1].cssRules)
    rules = document.styleSheets[i].cssRules
else if (document.styleSheets[i].rules)
    rule= document.styleSheets[i].rules

Prova qualcosa di specifico per il file CSS che stai utilizzando.


4

Si potrebbe usare onerrorper quello:

<link rel="stylesheet" href="cdn.css" onerror="this.onerror=null;this.href='local.css';" />

Lo scopo this.onerror=null;è evitare cicli infiniti nel caso in cui il fallback stesso non sia disponibile. Ma potrebbe anche essere utilizzato per avere più fallback.

Tuttavia, attualmente funziona solo in Firefox e Chrome.


3

Ecco un'estensione alla risposta di Katy Lavallee. Ho avvolto tutto nella sintassi della funzione autoeseguibile per evitare collisioni tra variabili. Ho anche reso lo script non specifico per un singolo collegamento. IE, ora qualsiasi collegamento al foglio di stile con un attributo URL "data-fallback" verrà automaticamente analizzato. Non è necessario codificare gli URL in questo script come prima. Nota che questo dovrebbe essere eseguito alla fine <head>dell'elemento piuttosto che alla fine <body>dell'elemento, altrimenti potrebbe causare FOUC .

http://jsfiddle.net/skibulk/jnfgyrLt/

<link rel="stylesheet" type="text/css" href="broken-link.css" data-fallback="broken-link2.css">

.

(function($){
    var links = {};

    $( "link[data-fallback]" ).each( function( index, link ) {
        links[link.href] = link;
    });

    $.each( document.styleSheets, function(index, sheet) {
        if(links[sheet.href]) {
            var rules = sheet.rules ? sheet.rules : sheet.cssRules;
            if (rules.length == 0) {
                link = $(links[sheet.href]);
                link.attr( 'href', link.attr("data-fallback") );
            }
        }
    });
})(jQuery);

1
Mi piace l'incapsulamento, ma in generale non puoi ispezionare sheet.rules per un foglio di stile interdominio. È ancora possibile utilizzare questa idea generale, ma è necessario eseguire un controllo diverso.
John Vinopal

con document.styleSheets[i].ownerNode.datasetpuoi accedere agli <link data-* />attributi
Alwin Kesler

2

Vuoi davvero seguire questo percorso javascript per caricare CSS nel caso in cui un CDN fallisca?

Non ho pensato a tutte le implicazioni sulle prestazioni, ma perderai il controllo di quando il CSS viene caricato e in generale per le prestazioni di caricamento della pagina, CSS è la prima cosa che vuoi scaricare dopo l'HTML.

Perché non gestirlo a livello di infrastruttura: mappare il proprio nome di dominio sul CDN, dargli un breve TTL, monitorare i file sul CDN (ad esempio utilizzando Watchmouse o qualcos'altro), se CDN fallisce, cambiare il DNS al sito di backup.

Altre opzioni che potrebbero aiutare sono "cache per sempre" su contenuto statico, ma non c'è alcuna garanzia che il browser le manterrà ovviamente o che utilizzerà la cache dell'app.

In realtà, come qualcuno ha detto in alto, se il tuo CDN non è affidabile, procuratene uno nuovo

Andy


7
Il CDN può essere affidabile, ma non la connessione Internet dell'ambiente di sviluppo;)
icebreaker

1

Guarda queste funzioni:

$.ajax({
    url:'CSS URL HERE',
    type:'HEAD',
    error: function()
    {
        AddLocalCss();
    },
    success: function()
    {
        //file exists
    }
});

Ed ecco la versione JavaScript vanilla:

function UrlExists(url)
{
    var http = new XMLHttpRequest();
    http.open('HEAD', url, false);
    http.send();
    return http.status!=404;
}
if (!UrlExists('CSS URL HERE') {
AddLocalCss();
}

Ora la funzione effettiva:

function AddLocalCss(){
document.write('<link rel="stylesheet" type="text/css" href=" LOCAL CSS URL HERE">')
}

Assicurati solo che AddLocalCss sia chiamato nella testa.

Potresti anche considerare di utilizzare uno dei seguenti modi spiegati in questa risposta :

Carica usando AJAX

$.get(myStylesLocation, function(css)
{
   $('<style type="text/css"></style>')
      .html(css)
      .appendTo("head");
});

Carica utilizzando creato dinamicamente

$('<link rel="stylesheet" type="text/css" href="'+myStylesLocation+'" >')
   .appendTo("head");
Load using dynamically-created <style>

$('<style type="text/css"></style>')
    .html('@import url("' + myStylesLocation + '")')
    .appendTo("head");

o

$('<style type="text/css">@import url("' + myStylesLocation + '")</style>')
    .appendTo("head");

Sì, almeno nel browser moderno, non sono sicuro di IE6.

C'è un modo per controllare invece di scaricare l'intero file?
Shawn Mclean

L'unico motivo possibile per eseguire la richiesta degli OP è evitare il traffico di rete in eccesso. Questo crea un eccesso di traffico di rete.
Stefan Kendall

@stefan Kendall: no, non è nemmeno la possibile ragione per cui vuole assicurarsi che i file vengano caricati.

Se questa fosse l'unica preoccupazione, semplicemente non useresti un CDN. Testare solo l'intestazione è meglio, ma sono abbastanza sicuro che la maggior parte dei CDN e il tuo browser non consentiranno XSS.
Stefan Kendall

-1

Probabilmente userò qualcosa come yepnope.js

yepnope([{
  load: 'http:/­/ajax.googleapis.com/ajax/libs/jquery/1.5.1/jquery.min.js',
  complete: function () {
    if (!window.jQuery) {
      yepnope('local/jquery.min.js');
    }
  }
}]);

Tratto dal file readme.


10
@ BenSchwarz, ciò non significa che puoi incollare del codice irrilevante che non risponde in alcun modo alla domanda posta.
AlicanC

-8
//(load your cdn lib here first)

<script>window.jQuery || document.write("<script src='//me.com/path/jquery-1.x.min.js'>\x3C/script>")</script>
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.