Comment revenir à la feuille de style locale (pas au script) en cas d'échec du CDN

108

Je crée un lien vers la feuille de style jQuery Mobile sur un CDN et j'aimerais revenir à ma version locale de la feuille de style si le CDN échoue. Pour les scripts, la solution est bien connue:

<!-- 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>

Je voudrais faire quelque chose de similaire pour une feuille de style:

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

Je ne suis pas sûr qu'une approche similaire puisse être réalisée car je ne suis pas sûr que le navigateur se bloque de la même manière lors de la liaison d'un script que lors du chargement d'un script (il est peut-être possible de charger une feuille de style dans une balise de script, puis l'injecter dans la page)?

Ma question est donc la suivante: comment m'assurer qu'une feuille de style est chargée localement en cas d'échec d'un CDN?

ssn
la source
2
J'aimerais savoir si cela est possible aussi ... Si je m'inquiétais vraiment de la panne du CDN, j'utiliserais simplement l'hébergement local.
Fosco le
2
@Stefan Kendall, je pense que la bonne déclaration est que son site sera plus que susceptible de tomber en panne qu'un CDN
Shawn Mclean

Réponses:

63

Pas testé sur plusieurs navigateurs, mais je pense que cela fonctionnera. Cela devra être après le chargement de jquery, ou vous devrez le réécrire en 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>
katy lavallee
la source
bonne solution, un problème qu'il ne résout pas est si le CDN est trop lent à charger ... peut-être une sorte de délai d'attente?
GeorgeU
2
Pour code.jquery.com/ui/1.10.2/themes/smoothness/jquery-ui.css , j'obtiens rules = null, même s'il a été chargé correctement. J'utilise Chrome 26 et je pense que c'est parce que le script est interdomaine?
simplfuzz
8
La solution ne fonctionne pas vraiment pour tous les CDN / feuilles de style, par exemple les CSSStyleSheetobjets js qui proviennent de bootstrapcdn.com ont tous des champs vides ruleset cssRulesdans mon navigateur (Chrome 31). UPD : cela pourrait en fait être un problème de multi-domaines, le fichier css dans la réponse ne fonctionne pas non plus pour moi.
Maksim Vi.
3
C'est également une bonne solution en utilisant l' onerrorévénement. stackoverflow.com/questions/30546795/…
Programmeur chimique
2
Cela fonctionne-t-il pour le CSS chargé à partir d'un autre domaine? Non, vous ne pouvez pas énumérer .rules/ .cssRulespour les feuilles de style externes. jsfiddle.net/E6yYN/13
Salman A
29

Je suppose que la question est de détecter si une feuille de style est chargée ou non. Une approche possible est la suivante:

1) Ajoutez une règle spéciale à la fin de votre fichier CSS, comme:

#foo { display: none !important; }

2) Ajoutez le div correspondant dans votre HTML:

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

3) Sur le document prêt, vérifiez s'il #fooest visible ou non. Si la feuille de style a été chargée, elle ne sera pas visible.

Démo ici - charge le thème de douceur jquery-ui; aucune règle n'est ajoutée à la feuille de style.

Salman A
la source
42
+1 pour une solution intelligente. Le seul problème est que, normalement, on ne peut pas ajouter une ligne à la fin d'une feuille de style hébergée sur le CDN de quelqu'un
Jannie Theunissen
2
Malheureusement, nous ne pouvons pas saisir nos propres classes dans des fichiers CDN. Peut-être pouvons-nous essayer d'utiliser celui qui existe déjà.
Ahamed
1
J'aime beaucoup celui-ci, merci. C'est vraiment très puissant. Évidemment, je ne peux pas manipuler la feuille de style CDN mais je sais quelles classes sont utilisées alors j'ai modifié le code pour montrer si elles sont visibles - très intelligentes en effet :)
Nosnibor
3
NB: vous n'avez pas vraiment besoin d'ajouter une nouvelle règle au CSS externe. Utilisez simplement une règle existante dont le comportement est connu. Dans ma démo, j'utilise la classe ui-helper-hidden qui est censée masquer l'élément, je vérifie ensuite si l'élément est masqué lors du chargement de la page.
Salman A
28

En supposant que vous utilisez le même CDN pour css et jQuery, pourquoi ne pas faire un seul test et tout attraper?

<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>
Mike Wills
la source
1
Puis-je demander quel est le problème avec l'utilisation initiale de chaînes sans échappement, par exemple document.write("<script type='text/javascript' src='path/to/file.js'>")?
Jack Tuck
2
@JackTuck: L'analyseur ne peut pas faire la différence entre <script>une chaîne JS à l'intérieur et une chaîne trouvée à l'extérieur. C'est généralement la raison pour laquelle vous voyez également <\/script>lors de l'écriture de balises pour les solutions de secours CDN.
Brad Christie
6
-1 why not just do one test and catch it all?- Parce qu'il y a un million de raisons pour lesquelles l'une peut échouer, et les autres réussissent.
Flimzy
7

cet article suggère quelques solutions pour le bootstrap css http://eddmann.com/posts/providing-local-js-and-css-resources-for-cdn-fallbacks/

sinon cela fonctionne pour 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>
dc2009
la source
Pour ceux qui cherchent à l'utiliser avec Font Awesome 5, vous voudrez changer 'FontAwesome' (dans la clause if) en 'Font Awesome 5 Free' (si vous utilisez les polices gratuites). Sinon, cela devrait fonctionner correctement.
Jason Clark
4

Vous pourrez peut- être tester l’existence de la feuille de style dans 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

Testez quelque chose de spécifique au fichier CSS que vous utilisez.

Stefan Kendall
la source
4

On pourrait utiliser onerrorpour cela:

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

Le but this.onerror=null;est d'éviter les boucles sans fin au cas où le repli lui-même ne serait pas disponible. Mais il pourrait également être utilisé pour avoir plusieurs solutions de secours.

Cependant, cela ne fonctionne actuellement que dans Firefox et Chrome.

Jan Martin Keil
la source
3

Voici une extension de la réponse de Katy Lavallee. J'ai tout enveloppé dans une syntaxe de fonction auto-exécutable pour éviter les collisions de variables. J'ai également rendu le script non spécifique à un seul lien. IE, maintenant tout lien de feuille de style avec un attribut URL "data-fallback" sera automatiquement analysé. Vous n'avez pas à coder en dur les URL dans ce script comme auparavant. Notez que cela devrait être exécuté à la fin de l' <head>élément plutôt qu'à la fin de l' <body>élément, sinon cela pourrait provoquer un 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);
skibulk
la source
1
J'aime l'encapsulation, mais en général, vous ne pouvez pas inspecter sheet.rules pour une feuille de style inter-domaines. Vous pouvez toujours utiliser cette idée générale, mais vous devez effectuer une vérification différente.
John Vinopal
avec document.styleSheets[i].ownerNode.datasetvous pouvez accéder aux <link data-* />attributs
Alwin Kesler
2

Voulez-vous vraiment emprunter cette route javascript pour charger du CSS en cas d'échec d'un CDN?

Je n'ai pas pensé à toutes les implications sur les performances, mais vous allez perdre le contrôle du chargement du CSS et en général pour les performances de chargement de la page, CSS est la première chose que vous voulez télécharger après le HTML.

Pourquoi ne pas gérer cela au niveau de l'infrastructure - mappez votre propre nom de domaine sur le CDN, donnez-lui un court TTL, surveillez les fichiers sur le CDN (par exemple en utilisant Watchmouse ou autre chose), si le CDN échoue, changez le DNS en site de sauvegarde.

D'autres options qui pourraient aider sont le "cache pour toujours" sur le contenu statique, mais il n'y a aucune garantie que le navigateur les conservera bien sûr ou en utilisant le cache d'application.

En réalité, comme quelqu'un l'a dit en haut, si votre CDN n'est pas fiable, procurez-vous un nouveau

Andy

Andy Davies
la source
7
CDN peut être fiable, mais pas la connexion Internet de l'environnement de développement;)
icebreaker
1

Regardez ces fonctions:

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

Et voici la version JavaScript vanille:

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

Maintenant la fonction réelle:

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

Assurez-vous simplement que AddLocalCss est appelé dans la tête.

Vous pouvez également envisager d'utiliser l'une des méthodes suivantes expliquées dans cette réponse :

Charger avec AJAX

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

Charger à l'aide de

$('<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");

ou

$('<style type="text/css">@import url("' + myStylesLocation + '")</style>')
    .appendTo("head");
Communauté
la source
Oui, au moins dans un navigateur moderne, je ne suis pas sûr d'IE6.
Existe-t-il un moyen de vérifier au lieu de télécharger le tout?
Shawn Mclean
La seule raison possible de faire la demande OP est d'éviter un trafic réseau excessif. Cela crée un trafic réseau excessif.
Stefan Kendall
@stefan Kendall: non, ce n'est même pas la raison pour laquelle il veut s'assurer que les fichiers sont chargés.
Si c'était le seul problème, vous n'utiliseriez tout simplement pas de CDN. Tester uniquement l'en-tête est préférable, mais je suis presque sûr que la plupart des CDN et votre navigateur n'autoriseront pas XSS.
Stefan Kendall
-1

J'utiliserais probablement quelque chose comme 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');
    }
  }
}]);

Tiré du fichier readme.

Ben Schwarz
la source
10
@BenSchwarz, cela ne veut pas dire que vous pouvez coller du code non pertinent qui ne répond en aucun cas à la question posée.
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>
crazy4groovy
la source