jkphl/defr

一个简单的使用模式,轻量级的JavaScript库,用于延迟加载外部CSS和JavaScript资源,并可选地支持localStorage缓存

维护者

详细信息

github.com/jkphl/defr

主页

源代码

问题

安装: 11

依赖关系: 0

建议者: 0

安全: 0

星标: 3

关注者: 2

分支: 1

开放问题: 1

语言:JavaScript

v0.1.0 2014-01-08 09:04 UTC

This package is auto-updated.

Last update: 2024-09-23 00:33:59 UTC


README

is an — at present: proof-of-concept — simple usage pattern and lightweight JavaScript library for deferred loading and localStorage caching of external CSS and JavaScript resources.

该库基本上有两个版本,一个适用于 现代浏览器,另一个是针对 旧版浏览器的填充版本(更大)。此外,每个库都有一个支持CSS和JavaScript资源localStorage缓存的版本(同样分别针对现代旧版浏览器)。

Please be aware that I hacked all this together in very little time, so don't expect it to be rocket science at the moment. It should, however, illustrate my thoughts. I'm happily awaiting your feedback and your suggestions!

基础知识

当向您的访问者提供内容时,首先应关注“卷面上”的部分,并尽一切可能避免“阻塞内容”(即需要先下载才能渲染文档的外部资源)。外部CSS和JavaScript文件始终被视为阻塞资源,仅将它们移动到HTML文档的底部并不能改变这一点。此外,添加asyncdefer属性在所有情况下都不会有所帮助——尽管它们仅存在于<script>元素中。要真正延迟加载样式表和脚本,您必须使用JavaScript并动态地将这些资源添加到DOM中。这正是defr发挥作用的地方,为您提供了一种非常简单的使用模式来实现这一目的。

示例

看看以下示例

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8" />
        <title>defr usage pattern example</title>
        
        <!-- defr asset bundle -->
        <noscript class="defr" itemtype="http://defr.jkphl.is/assets" itemscope="itemscope">
            <link rel="stylesheet" type="text/css" href="/path/to/stylesheet.css"/>
            <link itemprop="script" type="text/javascript" href="/path/to/script.js"/>
        </noscript>
        
    </head>
    <body>
        <p>Lorem ipsum dolor sit amet, ...</p>
        <script src="/path/to/defr.simple.min.js"></script>
    </body>
</html>

如您所见,文档的头部有一个特殊的<noscript>部分。实际上,可能会有多个,如果您不关心验证,也可以将其放在文档的任何地方,而不仅仅是<head>部分。此外,defr库本身也包含在文档的末尾(在实践中,我会建议内联库,但在这个示例中为了清晰起见,我将它保持为外部)。

<noscript>部分包含两个<link>元素,第一个仅是 对外部CSS资源的常规引用——没有任何特殊之处,除了它是<noscript>内容的一部分。由于延迟加载始终涉及JavaScript,这是一个完美的后备方案:如果没有JavaScript可用,<noscript>元素将被解析,样式表将正常(非延迟)加载。否则,<noscript>元素的内容被视为文本,默认情况下不会以任何方式进行处理。1

第二个 <link> 元素(引用 JavaScript 资源)缺少一个 rel 属性,根据 HTML5 词汇表,这是完全正常的。然而,当浏览器处于 noscript 模式时,它将完全忽略该元素,因为它不知道如何处理它——这再次是完全可以接受的,因为我们无论如何都不支持 JavaScript。

文档末尾包含的 defr 库将负责处理那个 <noscript> 部分,提取样式表和脚本资源,并通过动态将其注入 DOM 来加载它们。很简单,对吧?

那个 itemtypeitemprop 东西怎么办?

根据 HTML5 词汇表,一个 <noscript> 元素只能包含 <meta><link><style> 元素——但不幸的是,不能包含 <script> 元素。为了遵守 HTML5 验证规则,我们必须“滥用”<link> 元素来引用外部 JavaScript 资源。就我的品味而言,这绝对是完全可以接受的,因为它 nothing more than the reference established by e.g. <link rel="stylesheet"/> for a CSS resource. 然而,没有合适的 rel 值可以使用 JavaScript,因此我们根本不需要它(好吧,我们可以使用例如 rel="prefetch",但这肯定是一个滥用案例 ...)。

W3C HTML5 验证器要求每个 <link> 元素必须有一个 rel 或一个 itemprop 属性(后者是 Microdata 规范的一部分)。此外,具有 itemprop 属性的元素也必须是具有 itemscope 属性的元素的子元素(例如示例中的 <noscript>)。由于我无论如何都是 微观信息 的超级粉丝,我最终添加了 itemtype="http://defr.jkphl.is/asset" 属性,以清楚地表明这是一个 defr <noscript> 元素。 defr 所使用的完整微数据词汇表将很快可用

同时,这种使用模式本身就是有效的 HTML5。

使用 defr

总的来说,我建议内联 defr 库,因为它非常小,更重要的是——外部引用会引入库本身作为“阻塞内容”——这正是你想要避免的。无论如何,你应该将库(即 defr <noscript> 元素)包含在你的文档中,例如在关闭 </body> 元素之前。

	...
	<script>
		!function(a,b,c,d){ /* Here's the library code ... */ }(window,document,!1);
	</script>
</body

正如你所看到的,defr 库是一个 IIFE,它使用三个或四个参数调用

  1. 对窗口对象的引用(不要更改此设置)。
  2. 对文档对象的引用(不要更改此设置)。
  3. 一个布尔值,指示文档中的 defr 资产包是否应在页面加载时立即自动处理。如果你想在例如 演示页面 中手动开始加载外部资源,你可以将其设置为 false
  4. 用于查找资产包的 CSS 类名(默认为 "defr")。默认情况下,所有 <noscript class="defr"><noscript itemtype="http://defr.jkphl.is/assets"> 元素都会匹配(请参阅示例)。你通常不需要更改此设置,但如果需要,请将其作为第四个参数提供。

该库注册了一个全局的window.defr()函数,您可以通过它手动启动资源加载,例如:

...
<button type="button" onclick="window.defr()">Load CSS and scripts</button>
...

处理步骤和属性遍历

以下是defr库所采取的步骤:

  1. 首先,它匹配所有相关的资源包(即<noscript>元素),解析它们的文本内容,并逐个处理包含的<link>元素。
  2. CSS样式表引用(即<link rel="stylesheet" href="..."/>元素)将被附加到您的文档的<head>部分,而不会发生任何显著变化。所有属性都将被保留。
  3. 所有JavaScript资源(匹配<link type="text/javascript" href="..."/>)将被转换为<script>元素并注入,遵循以下属性和属性遍历规则:
    • 如果存在,全局属性accesskeyclasscontenteditabledirdraggabledropzonehiddenidlangspellcheckstyletabindextitletranslate将被保留,并从原始的<link>元素复制到创建的<script>元素。
    • 如果存在,事件处理器onloadonerror也将被保留并复制到创建的<script>元素。此外,onload处理器将被扩展为与Internet Explorer < 9兼容的版本(因为IE < 9中<script>元素对onload没有原生支持)。
    • 如果存在,属性data-crossorigindata-charset也将被保留并复制到创建的<script>元素,但分别重命名为crossorigincharset
    • 所有data-*属性都将被保留并复制到创建的<script>元素,而所有其他剩余的属性都将被丢弃。
    • <script>元素的href属性设置为原始<link>元素的src值。
    • <script>元素的type属性设置为"text/javascript"
    • <script>元素的defer属性设置为true

localStorage缓存的兼容性

除了延迟加载CSS和JavaScript资源之外,defr库的"localstorage"版本还使用您的HTML5浏览器的localStorage来缓存已从远程服务器检索的外部资源。这避免了重复请求(见[fn 2]),可能带来性能上的好处——尤其是在移动设备上。您不需要做任何特别的事情,缓存会自动发生。

在"localstorage"模式下,一些资源<link>的属性具有特殊含义(显然我受到了basket.js的启发)

  • 默认情况下,<link>元素的href属性被用作将资源存储到localStorage的唯一键。
  • 然而,如果<link>元素具有id属性,则其值将被用作唯一键,从而使其独立于资源URL。
  • 如果<link>元素具有data-expire属性,则其值将作为资产应该缓存的秒数。默认值为18000000秒(约7个月)。
  • 如果<link>元素具有data-revision属性,则其值将与其一起保存到localStorage中。如果遇到具有另一个data-revision值的相同资产(如上所述,根据唯一键识别),则缓存的版本将被相应地刷新或替换为新的版本。实际上,您可以使用此功能进行缓存清除。

请注意,localStorage 存储有浏览器依赖的内存限制(通常为 5 MB,但请检查您特定浏览器/平台的情况)。如果将资产存储到 localStorage 中没有足够的空闲空间,defr 将会清除它存储的较老资产(从最老的开始)直到可以缓存新的资产。

要清除特定(子)域的 localStorage,只需从 JavaScript 中发出 localStorage.clear() 调用即可(例如,使用开发工具的控制台输入)。

已知问题

支持 IE 7 & 8

由于 Internet Explorer 7 和 8 不将 <noscript> 元素的内容暴露给 JavaScript,defr 必须使用特殊技术才能在这些浏览器中工作:它们支持 propriety 的 <comment> 元素,该元素注释掉封装的数据,但仍然将其暴露给 JavaScript。结合我发现的少许条件注释魔法,我找到了这个——虽然有些 hack,但确实可行——解决方案

...
<!--[if (gte IE 7)&(lte IE 8)]><script>document.write('<comment class="defr">')</script><![endif]-->
<noscript class="defr" itemtype="http://defr.jkphl.is/assets" itemscope="itemscope">
    <link rel="stylesheet" type="text/css" href="/path/to/stylesheet.css"/>
    <link itemprop="script" type="text/javascript" href="/path/to/script.js"/>
</noscript>
<style><![CDATA[/*</comment><!--*/]]>--></style>
...

当 JavaScript 可用时,一个 <comment class="defr"> 元素会动态写入到文档中,紧位于 <noscript> 元素之前,从而使其成为纯注释文本。关闭的 </comment> 元素不能以任何方式损坏,也不能被包含在另一个 <!-- ... --> 注释或由 JavaScript 动态写入。所有这些方法都失败了,所以只能通过将其包裹在一个(其他方面为空的)<style> 元素中来实现。当 JavaScript 被禁用时,根本不会写入起始的 <comment>,并且 <noscript> 元素将正常工作。

我知道这听起来不太对劲。但老实说:真正的問題不是这个 hack,对吧?

示例/实时演示

每个库版本都有一个示例/演示页面,您可以用于测试您的平台和浏览器。

支持的/测试过的浏览器

以下是我在显式测试的浏览器/平台组合。如果您有机会测试一些缺失的浏览器,请告诉我您的结果。您可以使用 examples 目录中的演示页面来测试特定客户端的支持。您将必须查看相应的开发工具的网络和资源选项卡或您的 Web 服务器日志文件,以确定您的客户端请求哪些文件从您的服务器。

桌面客户端

❶ Polyfilled 库 ❷ 现代库 ✱ 仅使用特殊标记(包装 <comment> 元素)

移动客户端

我很快将对移动设备进行一些测试。

当前问题/注意事项

  • 必须测试禁用 JavaScript 时的回退(所有客户端/平台)

资源

法律

版权 © 2014 Joschi Kuphal joschi@kuphal.net / @jkphl

defr 在 MIT 许可证的条款下授权。