Cómo recurrir a la hoja de estilo local (no a la secuencia de comandos) si falla la CDN


108

Estoy enlazando a la hoja de estilo de jQuery Mobile en un CDN y me gustaría volver a mi versión local de la hoja de estilo si el CDN falla. Para los scripts, la solución es bien conocida:

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

Me gustaría hacer algo similar para una hoja de estilo:

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

No estoy seguro de si se puede lograr un enfoque similar porque no estoy seguro de si el navegador se bloquea de la misma manera cuando se vincula un script que cuando se carga un script (tal vez sea posible cargar una hoja de estilo en una etiqueta de script y luego inyectarlo en la página)?

Entonces, mi pregunta es: ¿Cómo me aseguro de que una hoja de estilo se cargue localmente si falla una CDN?


2
Me gustaría saber si esto también es posible ... Si realmente me preocupara que la CDN no funcione, simplemente usaría el alojamiento local.
Fosco

2
@Stefan Kendall, creo que la declaración correcta es que es más probable que su sitio se caiga que un CDN
Shawn Mclean

Respuestas:


63

No probado en varios navegadores, pero creo que esto funcionará. Sin embargo, tendrá que ser después de cargar jquery, o tendrá que reescribirlo en JavaScript simple.

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

buena solución, un problema que no aborda es si el CDN es demasiado lento para cargar ... ¿tal vez algún tipo de tiempo de espera?
GeorgeU

2
Para code.jquery.com/ui/1.10.2/themes/smoothness/jquery-ui.css , obtengo rules = null, aunque se haya cargado correctamente. Estoy usando Chrome 26 y creo que es porque el script es de dominio cruzado.
simplfuzz

8
La solución no funciona realmente para todas las CDN / hojas de estilo, por ejemplo, los CSSStyleSheetobjetos js que provienen de bootstrapcdn.com tienen campos vacíos rulesy cssRulesen mi navegador (Chrome 31). UPD : en realidad, podría ser un problema de dominio cruzado, el archivo css en la respuesta tampoco funciona para mí.
Maksim Vi.

3
Esta también es una buena solución mediante el onerrorevento. stackoverflow.com/questions/30546795/…
Programador químico

2
¿Funciona con CSS cargado desde otro dominio? No, no puede enumerar .rules/ .cssRulespara hojas de estilo externas. jsfiddle.net/E6yYN/13
Salman A

29

Supongo que la pregunta es detectar si una hoja de estilo está cargada o no. Un posible enfoque es el siguiente:

1) Agregue una regla especial al final de su archivo CSS, como:

#foo { display: none !important; }

2) Agregue el div correspondiente en su HTML:

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

3) Cuando el documento esté listo, compruebe si #fooestá visible o no. Si se cargó la hoja de estilo, no será visible.

Demostración aquí : carga el tema de suavidad jquery-ui; no se agrega ninguna regla a la hoja de estilo.


42
+1 para una solución inteligente. El único problema es que normalmente uno no puede ir y agregar una línea al final de una hoja de estilo que está alojada en el CDN de alguien
Jannie Theunissen

2
Desafortunadamente, no podemos escribir nuestras propias clases en archivos CDN. Quizás podamos intentar utilizar el que ya existe.
Ahamed

1
Me gusta MUCHO, gracias. Realmente es bastante poderoso. Obviamente, no puedo manipular la hoja de estilo CDN, pero sé qué clases se están utilizando, así que modifiqué el código para mostrar si están visibles, muy inteligente de hecho :)
Nosnibor

3
NB: realmente no tiene que agregar una nueva regla al CSS externo. Simplemente use una regla existente cuyo comportamiento sea conocido. En mi demostración, uso la clase ui-helper-hidden que se supone que oculta el elemento, luego verifico si el elemento se oculta al cargar la página.
Salman A

28

Suponiendo que está utilizando el mismo CDN para css y jQuery, ¿por qué no hacer una prueba y capturarlo todo?

<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
¿Puedo preguntar cuál es el problema con el uso de cadenas sin escape inicialmente, por ejemplo document.write("<script type='text/javascript' src='path/to/file.js'>")?
Jack Tuck

2
@JackTuck: el analizador no puede diferenciar entre el <script>interior de una cadena JS y uno que se encuentra fuera. Por lo general, esta es la razón por la que también se ve <\/script>al escribir etiquetas para alternativas de CDN.
Brad Christie

6
-1 why not just do one test and catch it all?- Porque hay un millón de razones por las que una puede fallar y las otras triunfan.
Flimzy

7

este artículo sugiere algunas soluciones para bootstrap css http://eddmann.com/posts/pro provide-local-js-and-css-resources-for-cdn-fallbacks/

alternativamente, esto funciona para 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>

Para aquellos que buscan usar esto con Font Awesome 5, querrán cambiar 'FontAwesome' (en la cláusula if) a 'Font Awesome 5 Free' (si está usando las fuentes gratuitas). De lo contrario, debería funcionar bien.
Jason Clark

4

Es posible que pueda probar la existencia de la hoja de estilo en 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

Prueba algo específico del archivo CSS que estás usando.


4

Uno podría usar onerrorpara eso:

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

El this.onerror=null;objetivo es evitar bucles sin fin en caso de que el respaldo en sí no esté disponible. Pero también podría usarse para tener múltiples alternativas.

Sin embargo, esto actualmente solo funciona en Firefox y Chrome.


3

Aquí hay una extensión de la respuesta de katy lavallee. Envolví todo en la sintaxis de funciones autoejecutables para evitar colisiones de variables. También hice que el script no sea específico para un solo enlace. Es decir, ahora se analizará automáticamente cualquier vínculo de hoja de estilo con un atributo de URL "data-fallback". No es necesario que codifique las URL en este script como antes. Tenga en cuenta que esto debe ejecutarse al final del <head>elemento en lugar de al final del <body>elemento, de lo contrario, podría causar 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
Me gusta la encapsulación, pero en general no se puede inspeccionar sheet.rules para una hoja de estilo de dominio cruzado. Aún puede usar esta idea general, pero necesita hacer una verificación diferente.
John Vinopal

con document.styleSheets[i].ownerNode.datasetpuede acceder a los <link data-* />atributos
Alwin Kesler

2

¿Realmente desea seguir esta ruta de JavaScript para cargar CSS en caso de que falle una CDN?

No he pensado en todas las implicaciones de rendimiento, pero perderá el control de cuándo se carga el CSS y, en general, para el rendimiento de carga de la página, CSS es lo primero que desea descargar después del HTML.

¿Por qué no manejar esto a nivel de infraestructura? Asigne su propio nombre de dominio a la CDN, dele un TTL corto, monitoree los archivos en la CDN (por ejemplo, usando Watchmouse u otra cosa), si la CDN falla, cambie el DNS al sitio de respaldo.

Otras opciones que pueden ayudar son "almacenar en caché para siempre" en contenido estático, pero no hay garantía de que el navegador las mantenga, por supuesto, o que use la caché de la aplicación.

En realidad, como dijo alguien en la parte superior, si su CDN no es confiable, obtenga uno nuevo

Andy


7
CDN puede ser confiable, pero no la conexión a Internet del entorno de desarrollo;)
rompehielos

1

Mira estas funciones:

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

Y aquí está la versión vainilla de JavaScript:

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

Ahora la función real:

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

Solo asegúrese de que se llame a AddLocalCss en el encabezado.

También podría considerar usar una de las siguientes formas explicadas en esta respuesta :

Cargar usando AJAX

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

Cargar usando creado dinámicamente

$('<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í, al menos en el navegador moderno, no estoy seguro de IE6.

¿Hay alguna forma de comprobarlo en lugar de descargarlo todo?
Shawn Mclean

La única razón posible para realizar la solicitud de OP es evitar el exceso de tráfico de red. Esto crea un exceso de tráfico en la red.
Stefan Kendall

@stefan Kendall: no, esa ni siquiera es la posible razón por la que quiere asegurarse de que los archivos se carguen.

Si esa fuera la única preocupación, simplemente no usaría una CDN. Probar solo el encabezado es mejor, pero estoy bastante seguro de que la mayoría de las CDN y su navegador no permitirán XSS.
Stefan Kendall

-1

Probablemente usaría algo como 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');
    }
  }
}]);

Tomado del archivo Léame.


10
@BenSchwarz, eso no significa que pueda pegar un código irrelevante que de ninguna manera responde a la pregunta formulada.
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>
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.