自动切换 cdn.jsdelivr.net 域名的脚本
本文最后更新于 116 天前,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

jsdelivr-auto-fallback

前言

cdn.jsdelivr.net 有时候会出现国内无法访问的情况,以至于造成网站 js, css, image字体等文件无法正常显示。
因此 BestTools 大神开发出自动检查 cdn.jsdelivr.net 是否可用的脚本, 如果不可用时,会自动把所有资源地址切换到其他可用的域名。比如:gcore.jsdelivr.netfastly.jsdelivr.net等其他 CDN

项目地址

Github 地址:https://github.com/PipecraftNet/jsdelivr-auto-fallback

使用方法

  1. 直接复制 index.jsindex.min.js 里的内容,加到网站里。强烈建议添加到 head 标签最上面。
  2. 所有 script 标签加上 defer 属性。如果原来有 async 属性,可以跳过。这个可以避免 pending 状态带来的等待时间,大大提升性能。
  3. 如果是 hexo 生成的网站,可以安装 hexo-filter-jsdelivr-auto-fallback 插件,自动添加。
  • 示例:

    # 可以把 js 文件放到其他目录下进行引用
    <script defer src="index.js"></script>
    <script defer src="index.min.js"></script>
  • index.js 代码:

    ((document) => {
    'use strict';
    let fastNode;
    let failed;
    let isRunning;
    const DEST_LIST = [
    'cdn.jsdelivr.net',
    'fastly.jsdelivr.net',
    'gcore.jsdelivr.net',
    'cdn.zenless.top',
    'testingcf.jsdelivr.net',
    'test1.jsdelivr.net'
    ];
    const PREFIX = '//';
    const SOURCE = DEST_LIST[0];
    const starTime = Date.now();
    const TIMEOUT = 2000;
    const STORE_KEY = 'jsdelivr-auto-fallback';
    const TEST_PATH = '/gh/PipecraftNet/jsdelivr-auto-fallback@main/empty.css?';
    const shouldReplace = (text) => text && text.includes(PREFIX + SOURCE);
    const replace = (text) => text.replace(PREFIX + SOURCE, PREFIX + fastNode);
    const setTimeout = window.setTimeout;
    const $ = document.querySelectorAll.bind(document);
    
    const replaceElementSrc = () => {
    let element;
    let value;
    for (element of $('link[rel="stylesheet"]')) {
      value = element.href;
      if (shouldReplace(value) && !value.includes(TEST_PATH)) {
        element.href = replace(value);
      }
    }
    
    for (element of $('script')) {
      value = element.src;
      if (shouldReplace(value)) {
        const newNode = document.createElement('script');
        newNode.src = replace(value);
        element.defer = true;
        element.src = '';
        element.before(newNode);
        element.remove();
      }
    }
    
    for (element of $('img')) {
      value = element.src;
      if (shouldReplace(value)) {
        // Used to cancel loading. Without this line it will remain pending status.
        element.src = '';
        element.src = replace(value);
      }
    }
    
    // All elements that have a style attribute
    for (element of $('*[style]')) {
      value = element.getAttribute('style');
      if (shouldReplace(value)) {
        element.setAttribute('style', replace(value));
      }
    }
    
    for (element of $('style')) {
      value = element.innerHTML;
      if (shouldReplace(value)) {
        element.innerHTML = replace(value);
      }
    }
    };
    
    const tryReplace = () => {
    if (!isRunning && failed && fastNode) {
      console.warn(SOURCE + ' is not available. Use ' + fastNode);
      isRunning = true;
      setTimeout(replaceElementSrc, 0);
      // Some need to wait for a while
      setTimeout(replaceElementSrc, 20);
      // Replace dynamically added elements
      setInterval(replaceElementSrc, 500);
    }
    };
    
    const checkAvailable = (url, callback) => {
    let timeoutId;
    const newNode = document.createElement('link');
    const handleResult = (isSuccess) => {
      if (!timeoutId) {
        return;
      }
    
      clearTimeout(timeoutId);
      timeoutId = 0;
      // Used to cancel loading. Without this line it will remain pending status.
      if (!isSuccess) newNode.href = 'data:text/plain;base64,';
      newNode.remove();
      callback(isSuccess);
    };
    
    timeoutId = setTimeout(handleResult, TIMEOUT);
    
    newNode.addEventListener('error', () => handleResult(false));
    newNode.addEventListener('load', () => handleResult(true));
    newNode.rel = 'stylesheet';
    newNode.text = 'text/css';
    newNode.href = url + TEST_PATH + starTime;
    document.head.insertAdjacentElement('afterbegin', newNode);
    };
    
    const cached = (() => {
    try {
      return Object.assign(
        {},
        JSON.parse(localStorage.getItem(STORE_KEY) || '{}')
      );
    } catch {
      return {};
    }
    })();
    
    const main = () => {
    cached.time = starTime;
    cached.failed = false;
    cached.fastNode = null;
    
    for (const url of DEST_LIST) {
      checkAvailable('https://' + url, (isAvailable) => {
        // console.log(url, Date.now() - starTime, Boolean(isAvailable));
        if (!isAvailable && url === SOURCE) {
          failed = true;
          cached.failed = true;
        }
    
        if (isAvailable && !fastNode) {
          fastNode = url;
        }
    
        if (isAvailable && !cached.fastNode) {
          cached.fastNode = url;
        }
    
        tryReplace();
      });
    }
    
    setTimeout(() => {
      // If all domains are timeout
      if (failed && !fastNode) {
        fastNode = DEST_LIST[1];
        tryReplace();
      }
    
      localStorage.setItem(STORE_KEY, JSON.stringify(cached));
    }, TIMEOUT + 100);
    };
    
    if (
    cached.time &&
    starTime - cached.time < 60 * 60 * 1000 &&
    cached.failed &&
    cached.fastNode
    ) {
    failed = true;
    fastNode = cached.fastNode;
    tryReplace();
    setTimeout(main, 1000);
    } else {
    main();
    }
    })(document);
  • index.min.js 代码:

    (n=>{"use strict";let r,s,e;const l=["cdn.jsdelivr.net","fastly.jsdelivr.net","gcore.jsdelivr.net","cdn.zenless.top","testingcf.jsdelivr.net","test1.jsdelivr.net"],t="//",a=l[0],i=Date.now(),o=2e3,c="jsdelivr-auto-fallback",f="/gh/PipecraftNet/jsdelivr-auto-fallback@main/empty.css?",d=e=>e&&e.includes(t+a),m=e=>e.replace(t+a,t+r),u=window.setTimeout,v=n.querySelectorAll.bind(n),g=()=>{let e,t;for(e of v('link[rel="stylesheet"]'))t=e.href,d(t)&&!t.includes(f)&&(e.href=m(t));for(e of v("script"))if(t=e.src,d(t)){const r=n.createElement("script");r.src=m(t),e.defer=!0,e.src="",e.before(r),e.remove()}for(e of v("img"))t=e.src,d(t)&&(e.src="",e.src=m(t));for(e of v("*[style]"))t=e.getAttribute("style"),d(t)&&e.setAttribute("style",m(t));for(e of v("style"))t=e.innerHTML,d(t)&&(e.innerHTML=m(t))},y=()=>{!e&&s&&r&&(console.warn(a+" is not available. Use "+r),e=!0,u(g,0),u(g,20),setInterval(g,500))},b=(()=>{try{return Object.assign({},JSON.parse(localStorage.getItem(c)||"{}"))}catch{return{}}})();var h=()=>{b.time=i,b.failed=!1,b.fastNode=null;for(const t of l)((e,t)=>{let r;const s=n.createElement("link"),l=e=>{r&&(clearTimeout(r),r=0,e||(s.href="data:text/plain;base64,"),s.remove(),t(e))};r=u(l,o),s.addEventListener("error",()=>l(!1)),s.addEventListener("load",()=>l(!0)),s.rel="stylesheet",s.text="text/css",s.href=e+f+i,n.head.insertAdjacentElement("afterbegin",s)})("https://"+t,e=>{e||t!==a||(s=!0,b.failed=!0),e&&!r&&(r=t),e&&!b.fastNode&&(b.fastNode=t),y()});u(()=>{s&&!r&&(r=l[1],y()),localStorage.setItem(c,JSON.stringify(b))},o+100)};b.time&&i-b.time<36e5&&b.failed&&b.fastNode?(s=!0,r=b.fastNode,y(),u(h,1e3)):h()})(document);

用户脚本

作为用户,你也可以使用油猴脚本将网站中的 cdn.jsdelivr.net 替换为可以访问的域名。

  1. 浏览器安装 Tampermonkey
  2. 安装脚本: https://greasyfork.org/zh-CN/scripts/445701-jsdelivr-auto-fallback

jsdelivr 可用节点比较

gcore.jsdelivr.net Gcore 节点 可用性高
testingcf.jsdelivr.net Cloudflare 节点 可用性高
quantil.jsdelivr.net Quantil 节点 可用性尚可
fastly.jsdelivr.net Fastly 节点 可用性尚可
originfastly.jsdelivr.net Fastly 节点 可用性低
cdn.jsdelivr.net 通用节点 可用性低

参考文章

版权归属: E家之长
本文链接: https://www.5iehome.cc/archives/jsdelivr-auto-fallback.html
许可协议: 本文使用《署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)》协议授权
暂无评论

发送评论 编辑评论


上一篇
下一篇