Como fazer fallback para a folha de estilo local (não script) se o CDN falhar

108

Estou vinculando a folha de estilo do jQuery Mobile em um CDN e gostaria de voltar para minha versão local da folha de estilo se o CDN falhar. Para scripts, a solução é bem conhecida:

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

Eu gostaria de fazer algo semelhante para uma folha de estilo:

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

Não tenho certeza se uma abordagem semelhante pode ser alcançada porque não tenho certeza se o navegador bloqueia da mesma maneira ao vincular um script e ao carregar um script (talvez seja possível carregar uma folha de estilo em uma tag de script e então injete-o na página)?

Portanto, minha pergunta é: Como posso garantir que uma folha de estilo seja carregada localmente se um CDN falhar?

ssn
fonte
2
Eu gostaria de saber se isso também é possível ... Se eu realmente me preocupasse com a queda do CDN, eu usaria apenas a hospedagem local.
Fosco
2
@Stefan Kendall, acho que a afirmação certa é que o site dele provavelmente irá cair do que um CDN
Shawn Mclean

Respostas:

63

Não foi testado em cross-browser, mas acho que funcionará. Terá que ser depois de carregar o jquery, ou você terá que reescrevê-lo em Javascript simples.

<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
fonte
boa solução, um problema que ela não resolve é se o CDN está muito lento para carregar ... talvez algum tipo de tempo limite?
GeorgeU
2
Para code.jquery.com/ui/1.10.2/themes/smoothness/jquery-ui.css , obtenho rules = null, embora tenha sido carregado corretamente. Estou usando o Chrome 26 e acho que é porque o script é de domínio cruzado.
simplfuzz
8
A solução realmente não funciona para todos os CDNs / folhas de estilo, por exemplo, CSSStyleSheetobjetos js que vêm de bootstrapcdn.com têm todos os campos rulese vazios cssRulesem meu navegador (Chrome 31). UPD : na verdade, pode ser um problema de crossdomain, o arquivo css na resposta também não funciona para mim.
Maksim Vi.
3
Esta também é uma boa solução usando onerrorevento. stackoverflow.com/questions/30546795/…
Programador químico
2
Isso funciona para CSS carregado de outro domínio? Não, você não pode enumerar .rules/ .cssRulespara folhas de estilo externas. jsfiddle.net/E6yYN/13
Salman A
29

Acho que a questão é detectar se uma folha de estilo está carregada ou não. Uma abordagem possível é a seguinte:

1) Adicione uma regra especial ao final do seu arquivo CSS, como:

#foo { display: none !important; }

2) Adicione o div correspondente em seu HTML:

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

3) No documento pronto, verifique se #fooestá visível ou não. Se a folha de estilo foi carregada, ela não ficará visível.

Demo here - carrega o tema de suavidade jquery-ui; nenhuma regra é adicionada à folha de estilo.

Salman A
fonte
42
1 para uma solução inteligente. O único problema é que normalmente não se pode adicionar uma linha ao final de uma folha de estilo hospedada no CDN de alguém
Jannie Theunissen
2
Infelizmente, não podemos digitar nossas próprias classes em arquivos CDN. Podemos tentar utilizar aquele que já existe.
Ahamed
1
Gosto MUITO deste, obrigado. É realmente muito poderoso. Obviamente, não posso manipular a folha de estilo do CDN, mas sei quais classes estão sendo usadas, então
alterei
3
NB: você realmente não precisa adicionar uma nova regra ao CSS externo. Basta usar uma regra existente cujo comportamento seja conhecido. Em minha demonstração, uso a classe ui-helper-hidden, que supostamente oculta o elemento, e verifico se o elemento fica oculto no carregamento da página.
Salman A
28

Supondo que você esteja usando o mesmo CDN para css e jQuery, por que não fazer apenas um teste e pegar tudo ??

<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
fonte
1
Posso perguntar qual é o problema de usar strings sem escape inicialmente, por exemplo document.write("<script type='text/javascript' src='path/to/file.js'>")?
Jack Tuck
2
@JackTuck: O analisador não consegue diferenciar entre <script>dentro de uma string JS e uma encontrada fora. É geralmente por isso que você também vê <\/script>ao gravar tags para substitutos de CDN.
Brad Christie
6
-1 why not just do one test and catch it all?- Porque há um milhão de razões para que uma falhe e as outras tenham sucesso.
Flimzy
7

este artigo sugere algumas soluções para o bootstrap css http://eddmann.com/posts/providing-local-js-and-css-resources-for-cdn-fallbacks/

alternativamente, isso 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>
DC2009
fonte
Para aqueles que procuram usar isso com Font Awesome 5, você vai querer mudar 'FontAwesome' (na cláusula if) para 'Font Awesome 5 Free' (se você estiver usando as fontes gratuitas). Caso contrário, deve funcionar bem.
Jason Clark
4

Você pode testar a existência da folha de estilo em 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

Teste algo específico para o arquivo CSS que você está usando.

Stefan Kendall
fonte
4

Pode-se usar onerrorpara isso:

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

O this.onerror=null;objetivo é evitar loops infinitos caso o próprio fallback não esteja disponível. Mas também pode ser usado para ter vários substitutos.

No entanto, isso atualmente funciona apenas no Firefox e Chrome.

Jan Martin Keil
fonte
3

Aqui está uma extensão da resposta de Katy Lavallee. Envolvi tudo em sintaxe de função autoexecutável para evitar colisões de variáveis. Também tornei o script não específico para um único link. Ou seja, agora qualquer link de folha de estilo com um atributo de url "fallback de dados" será automaticamente analisado. Você não precisa codificar os urls neste script como antes. Observe que isso deve ser executado no final do <head>elemento e não no final do <body>elemento, caso contrário, pode 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);
skibulk
fonte
1
Eu gosto do encapsulamento, mas em geral você não pode inspecionar sheet.rules para uma folha de estilo de domínio cruzado. Você ainda pode usar essa ideia geral, mas precisa fazer uma verificação diferente.
John Vinopal
com document.styleSheets[i].ownerNode.datasetvocê pode acessar <link data-* />atributos
Alwin Kesler,
2

Você realmente deseja seguir esta rota de javascript para carregar CSS no caso de um CDN falhar?

Não pensei em todas as implicações de desempenho, mas você vai perder o controle de quando o CSS é carregado e, em geral, para o desempenho de carregamento da página, CSS é a primeira coisa que você deseja baixar após o HTML.

Por que não lidar com isso no nível de infraestrutura - mapeie seu próprio nome de domínio para o CDN, dê a ele um TTL curto, monitore os arquivos no CDN (por exemplo, usando o Watchmouse ou outro), se o CDN falhar, mude o DNS para o site de backup.

Outras opções que podem ajudar são "armazenar em cache para sempre" em conteúdo estático, mas não há garantia de que o navegador os manterá no curso ou usando o cache do aplicativo.

Na realidade, como alguém disse no topo, se o seu CDN não for confiável, compre um novo

Andy

Andy Davies
fonte
7
O CDN pode ser confiável, mas não a conexão com a Internet do ambiente de desenvolvimento;)
quebra
1

Observe estas funções:

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

E aqui está a versão vanilla do 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();
}

Agora, a função real:

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

Apenas certifique-se de que AddLocalCss seja chamado na cabeça.

Você também pode usar uma das seguintes maneiras explicadas nesta resposta :

Carregar usando AJAX

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

Carregar usando criado 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");

ou

$('<style type="text/css">@import url("' + myStylesLocation + '")</style>')
    .appendTo("head");
Comunidade
fonte
Sim, pelo menos no navegador moderno, não tenho certeza sobre o IE6.
Existe uma maneira de apenas verificar em vez de fazer o download de tudo?
Shawn Mclean
A única razão possível para fazer a solicitação de OPs é evitar o excesso de tráfego de rede. Isso cria tráfego de rede em excesso.
Stefan Kendall
@stefan Kendall: não, essa não é a possível razão pela qual ele quer ter certeza de que os arquivos serão carregados.
Se essa fosse a única preocupação, você simplesmente não usaria um CDN. Testar apenas o cabeçalho é melhor, mas tenho certeza de que a maioria dos CDNs e seu navegador não permitirá XSS.
Stefan Kendall,
-1

Provavelmente usaria 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');
    }
  }
}]);

Retirado do readme.

Ben Schwarz
fonte
10
@BenSchwarz, isso não significa que você pode colar algum código irrelevante que de forma alguma responde à pergunta feita.
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
fonte