关键渲染路径

关键路径渲染

避免阻塞型脚本

总的来说我们希望所有的资源能够尽可能快地加载完毕,不过往往为了保证首页加载的速度,我们会考虑将部分非首屏需要的 JS/CSS 文件进行延迟加载,或者对于重复的视图使用浏览器本地缓存。JavaScript 是个单线程语言,渲染引擎和 JS 引擎不是同时进行的。所以提前引入 JS 文件可能会造成堵塞,从而影响页面加载,出现闪屏等情况。于是为了解决这种情况,就出现了 JS 在 body 最后引入、script 标签中 defer 和 async 异步加载 JS 的方式。

异步加载脚本

  • defer:如果 script 标签设置了该属性,则浏览器会异步的下载该文件并且不会影响到后续 DOM 的渲染。如果有多个设置了 defer 的 script 标签存在,则会按照顺序执行所有的 script。defer 脚本会在文档渲染完毕后,DOMContentLoaded 事件调用前执行。

  • async:async 浏览器立即异步下载文件,不同于 defer 得是,下载完成会立即执行,此时会阻塞 dom 渲染,所以 async 的 script 最好不要操作 dom。因为是下载完立即执行,不能保证多个加载时的先后顺序。

defer vs async

Lazy Load JS

目前来说,我们的网站都是偏向于静态,并不需要太多的 JavaScript 介入,不过考虑到日后的扩展空间,我们还是构建了一套完整的 JS 的工作流。众所周知,如果将 JS 直接放置到 head 标签中,其会阻塞整个页面的渲染。对于该点,最简单的方式就是将会阻塞渲染的 JS 脚本移动到页面的尾部,在整个首屏渲染完毕之后再进行加载。另一个常用的手段就是依然保持 JS 文件位于 head 标签中,不过为其添加一个defer的属性,这保证了浏览器只会先将该脚本下载下来,然后等到整个页面加载完毕再执行该脚本。另一个需要注意的是,因为我们并不使用类似于 jQuery 这样的第三方依赖库,而更多的依赖于浏览器原生的特性,因此我们希望在合适的浏览器内加载合适版本的 JS 代码,其效果大概如下:

<script>
// Mustard Cutting
if ('querySelector' in document && 'addEventListener' in window) {
  document.write('<script src="index.js" defer><\/script>');
}
</script>

Lazy Load CSS

正如上文所述,我们的网站偏向于静态展示,因此首屏的最大问题就是 CSS 文件的加载问题。浏览器会在 head 标签中声明的所有 CSS 文件下载完毕之前一直处于阻塞状态,这种机制很是明智的,不然的话浏览器在加载多个 CSS 文件的时候会进行重复的布局与渲染,这更是对于性能的浪费。为了避免非首屏的 CSS 文件阻塞页面渲染,我们使用loadCSS这个小的工具库来进行异步的 CSS 文件加载,它会在 CSS 文件加载完毕后执行回调。不过,异步加载 CSS 也会带来一个新的问题,如果我们将所有的 CSS 全部设置为了异步加载,那么用户会首先看到单纯的 HTML 页面,这也会给用户不好的体验。那么我们就需要在异步加载与首屏渲染之间找到一个平衡点,即首先加载那些必要的 CSS 文件。我们一般将首屏渲染中必要的 CSS 文件成为 Critical CSS,即关键的 CSS 文件,代指在保证页面的可读性的前提下需要加载的最少的 CSS 文件数目。Critical CSS 的选定会是一个非常耗时的过程,特别是我们网站本身的 CSS 样式设置也在不停变更,我们不可能完全依赖于人工去提取出关键的 CSS 文件,这里推荐Critical这个辅助工具能够帮你自动提取压缩 Critical CSS。下图的一个对比即是仅加载 Critical CSS 与加载全部 CSS 的区别:

上图中红色的线,即是所谓的折叠分割点。

Critical CSS

预加载

preload 和 prefecth 是 link 标签上的属性

  • preload:让浏览器提前加载指定资源(加载后并不执行),需要执行时再执行

  • prefetch:它告诉浏览器,这段资源将会在未来某个导航或者功能要用到,但是本资源的下载顺序权重比较低。也就是说 prefetch 通常用于加速下一次导航,而不是本次的。被标记为 prefetch 的资源,将会被浏览器在空闲时间加载。# 懒加载

代码分割

Loading Monitor(加载监控)

PACE:自动为页面添加加载指示

Installation

<head>
  <script src="/pace/pace.js"></script>
  <link href="/pace/themes/pace-theme-barber-shop.css" rel="stylesheet" />
</head>

LazyLoading(懒加载)

JS-Loader

const promises = {};

function loadScript(src) {
  if (promises[src]) {
    return promises[src];
  }
  let promise = (promises[src] = new Promise((resolve) => {
    var el = document.createElement("script");
    var loaded = false;
    el.onload = el.onreadystatechange = () => {
      if (
        (el.readyState &&
          el.readyState !== "complete" &&
          el.readyState !== "loaded") ||
        loaded
      ) {
        return false;
      }
      el.onload = el.onreadystatechange = null;
      loaded = true;
      resolve();
    };

    el.async = true;
    el.src = src;
    let head = document.getElementsByTagName("head")[0];
    head.insertBefore(el, head.firstChild);
  }));

  return promise;
}

TinyLoader

loadCSS:A function for loading CSS asynchronously

/*! loadCSS: load a CSS file asynchronously. [c]2016 @scottjehl, Filament Group, Inc. Licensed MIT */
(function (w) {
  "use strict";
  /* exported loadCSS */
  var loadCSS = function (href, before, media) {
    // Arguments explained:
    // `href` [REQUIRED] is the URL for your CSS file.
    // `before` [OPTIONAL] is the element the script should use as a reference for injecting our stylesheet <link> before
    // By default, loadCSS attempts to inject the link after the last stylesheet or script in the DOM. However, you might desire a more specific location in your document.
    // `media` [OPTIONAL] is the media type or query of the stylesheet. By default it will be 'all'
    var doc = w.document;
    var ss = doc.createElement("link");
    var ref;
    if (before) {
      ref = before;
    } else {
      var refs = (doc.body || doc.getElementsByTagName("head")[0]).childNodes;
      ref = refs[refs.length - 1];
    }
    var sheets = doc.styleSheets;
    ss.rel = "stylesheet";
    ss.href = href;
    // temporarily set media to something inapplicable to ensure it'll fetch without blocking render
    ss.media = "only x";

    // wait until body is defined before injecting link. This ensures a non-blocking load in IE11.
    function ready(cb) {
      if (doc.body) {
        return cb();
      }
      setTimeout(function () {
        ready(cb);
      });
    }
    // Inject link
    // Note: the ternary preserves the existing behavior of "before" argument, but we could choose to change the argument to "after" in a later release and standardize on ref.nextSibling for all refs
    // Note: `insertBefore` is used instead of `appendChild`, for safety re: http://www.paulirish.com/2011/surefire-dom-element-insertion/
    ready(function () {
      ref.parentNode.insertBefore(ss, before ? ref : ref.nextSibling);
    });
    // A method (exposed on return object for external use) that mimics onload by polling until document.styleSheets until it includes the new sheet.
    var onloadcssdefined = function (cb) {
      var resolvedHref = ss.href;
      var i = sheets.length;
      while (i--) {
        if (sheets[i].href === resolvedHref) {
          return cb();
        }
      }
      setTimeout(function () {
        onloadcssdefined(cb);
      });
    };

    function loadCB() {
      if (ss.addEventListener) {
        ss.removeEventListener("load", loadCB);
      }
      ss.media = media || "all";
    }

    // once loaded, set link's media back to `all` so that the stylesheet applies once it loads
    if (ss.addEventListener) {
      ss.addEventListener("load", loadCB);
    }
    ss.onloadcssdefined = onloadcssdefined;
    onloadcssdefined(loadCB);
    return ss;
  };
  // commonjs
  if (typeof exports !== "undefined") {
    exports.loadCSS = loadCSS;
  } else {
    w.loadCSS = loadCSS;
  }
})(typeof global !== "undefined" ? global : this);

最基本的用法:

loadCSS( "path/to/mystylesheet.css" );

react-loadscript

npm install react react-loadscript --save

script-loading as a component

(I’m so sorry)

import {Script} from 'react-loadscript';

class App {
  render(){
    return <Script src='https://code.jquery.com/jquery-2.1.4.min.js'>{
      ({done}) => !done?
        <div>loading...</div> :
        <div>{$.map([1, 2, 3], i => <div>{i*5}</div>)}</div>
    }</Script>
  }
}

uses promises + a simple cache to prevent duplicate script loads. enjoy!

PreLoading(预加载)

预加载网页内容为浏览者提供一个平滑的浏览体验。这个 api 我们在业务偶尔也会使用到。

Link prefetching 是利用浏览器最佳的时间去下载或者预加载一些用户可能将会在不久将来浏览的文档的一种浏览器机制。

<!-- full page -->
<link rel="prefetch" href="http://davidwalsh.name/css-enhancements-user-experience" />
<!-- just an image -->
<link rel="prefetch" href="http://davidwalsh.name/wp-content/themes/walshbook3/images/sprite.png" />

什么时候使用 link 预加载是否在自己的网站使用预加载,可以参考一下几点:

  • 当你做的是一种类似 slideshow 的网页,需要提前加载近 1-3 张页面(假设这些页面并不大)
  • 预先加载在网站中许多网页都会用到的图片
  • 预先加载网站搜索的结果的页面

Browsers are beginning to support a standard markup pattern for loading CSS (and other file types) asynchronously: ``(W3C Spec). We recommend using this markup pattern to reference any CSS files that should be loaded asynchronously, and usingloadCSS to polyfill support browsers that don’t yet support this new feature.

The markup for referencing your CSS file looks like this:

<link rel="preload" href="path/to/mystylesheet.css" as="style" onload="this.rel='stylesheet'">
<noscript><link rel="stylesheet" href="path/to/mystylesheet.css"></noscript>

Since rel=preload does not apply the CSS on its own (it merely fetches it), there is an onload handler on the link that will do that for us as soon as the CSS finishes loading. Since this step requires JavaScript, you may want to include an ordinary reference to your CSS file as well, using a noscript element to ensure it only applies in non-JavaScript settings.

With that markup in the head of your page, include the loadCSS script, as well as the loadCSS rel=preload polyfill script in your page (inline to run right away, or in an external file if the CSS is low-priority).

No further configuration is needed, as these scripts will automatically detect if the browsers supports rel=preload, and if it does not, they will find CSS files referenced in the DOM and preload them using loadCSS. In browsers that natively support rel=preload, these scripts will do nothing, allowing the browser to load and apply the asynchronous CSS (note the onload attribute above, which is there to set the link’s rel attribute to stylesheet once it finishes loading in browsers that support rel=preload).

Note: regardless of whether the browser supports rel=preload or not, your CSS file will be referenced from the same spot in the source order as the original link element. Keep this in mind, as you may want to place the link in a particular location in your head element so that the CSS loads with an expected cascade order.

You can view a demo of this rel=preload pattern here: http://filamentgroup.github.io/loadCSS/test/preload.html

[pageAccelerator](

https://github.com/Easyfood/pageAccelerator):预加载页面&pushState 替换 url

Tags

script

script标签可以用于指示JavaScript源代码,也可以用于引入远端的js脚本文件。

野生动态加载脚本文件

function loadJs(sid, jsurl, callback) {
  var nodeHead = document.getElementsByTagName("head")[0];
  var nodeScript = null;
  if (document.getElementById(sid) == null) {
    nodeScript = document.createElement("script");
    nodeScript.setAttribute("type", "text/javascript");
    nodeScript.setAttribute("src", jsurl);
    nodeScript.setAttribute("id", sid);
    if (callback != null) {
      nodeScript.onload = nodeScript.onreadystatechange = function () {
        if (nodeScript.ready) {
          return false;
        }
        if (
          !nodeScript.readyState ||
          nodeScript.readyState == "loaded" ||
          nodeScript.readyState == "complete"
        ) {
          nodeScript.ready = true;
          callback();
        }
      };
    }
    nodeHead.appendChild(nodeScript);
  } else {
    if (callback != null) {
      callback();
    }
  }
}

Github-LoadJs

// load a single file
loadjs("foo.js", function () {
  // foo.js loaded
});

// load multiple files (in parallel)
loadjs(["foo.js", "bar.js"], function () {
  // foo.js & bar.js loaded
});

// load multiple files (in series)
loadjs("foo.js", function () {
  loadjs("bar.js", function () {
    // foo.js loaded then bar.js loaded
  });
});

// add a bundle id
loadjs(["foo.js", "bar.js"], "foobar", function () {
  // foo.js & bar.js loaded
});

// add a failure callback
loadjs(
  ["foo.js", "bar.js"],
  "foobar",
  function () {
    /* foo.js & bar.js loaded */
  },
  function (pathsNotFound) {
    /* at least one path didn't load */
  }
);

// execute a callback after bundle finishes loading
loadjs(["foo.js", "bar.js"], "foobar");

loadjs.ready("foobar", function () {
  // foo.js & bar.js loaded
});

// .ready() can be chained together
loadjs("foo.js", "foo");
loadjs("bar.js", "bar");

loadjs
  .ready("foo", function () {
    // foo.js loaded
  })
  .ready("bar", function () {
    // bar.js loaded
  });

// compose more complex dependency lists
loadjs("foo.js", "foo");
loadjs("bar.js", "bar");
loadjs(["thunkor.js", "thunky.js"], "thunk");

// wait for multiple depdendencies
loadjs.ready(
  ["foo", "bar", "thunk"],
  function () {
    // foo.js & bar.js & thunkor.js & thunky.js loaded
  },
  function (depsNotFound) {
    if (depsNotFound.indexOf("foo") > -1) {
    } // foo failed
    if (depsNotFound.indexOf("bar") > -1) {
    } // bar failed
    if (depsNotFound.indexOf("thunk") > -1) {
    } // thunk failed
  }
);

// use .done() for more control
loadjs.ready("my-awesome-plugin", function () {
  myAwesomePlugin();
});

loadjs.ready("jquery", function () {
  // plugin requires jquery
  window.myAwesomePlugin = function () {
    if (!window.jQuery) throw "jQuery is missing!";
  };

  // plugin is done loading
  loadjs.done("my-awesome-plugin");
});
下一页