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

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/[email protected]/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/[email protected]/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 节点 可用性低
test1.jsdelivr.net Cloudflare 节点 可用性低
cdn.jsdelivr.net 通用节点 可用性低

参考文章:

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

发送评论 编辑评论


上一篇
下一篇