在纯静态网站里,有时候会动态更新某个区域往会选择 Pjax(swup、barba.js)去处理,他们都是使用 ajax 和 pushState 通过真正的永久链接,页面标题和后退按钮提供快速浏览体验。

但是实际使用中可能会遇到不同页面可能会需要加载不同插件处理,有些人可能会全量选择加载,这样会导致加载很多无用的脚本,有可能在用户关闭页面时都不一定会访问到,会很浪费资源。

解决思路

首先想到的肯定是在请求到新的页面后,我们手动去比较当前 DOM 和 新 DOM 之间 script 标签的差异,手动给他插入到 body 里。

处理 Script

一般来说 JavaScript 脚本都是放在 body 后,避免阻塞页面渲染,假设我们页面脚本也都是在 body 后,并在 script 添加 [data-reload-script] 表明哪些是需要动态加载的。

首先我们直接获取到带有 [data-reload-script] 属性的 script 标签:

// NewHTML 为 新页面 HTML
const pageContent = NewHTML.replace('<body', '<div id="DynamicPluginBody"').replace('</body>', '</div>');
let element = document.createElement('div');
element.innerHTML = pageContent;
const children = element.querySelector('#DynamicPluginBody').querySelectorAll('script[data-reload-script]');

然后通过创建 script 标签插入到 body

children.forEach(item => {
const element = document.createElement('script');
for (const { name, value } of arrayify(item.attributes)) {
element.setAttribute(name, value);
}
element.textContent = item.textContent;
element.setAttribute('async', 'false');
document.body.insertBefore(element)
})

如果你的插件都是通过 script 引入,且不需要执行额外的 JavaScript 代码,只需要在 Pjax 钩子函数这样处理就可以了。

执行代码块

实际很多插件不仅仅需要你引入,还需要你手动去初始化做一些操作的。我们可以通过 src 去判断是引入的脚本,还是代码块。

let scripts = Array.from(document.scripts)
let scriptCDN = []
let scriptBlock = [] children.forEach(item => {
if (item.src)
scripts.findIndex(s => s.src === item.src) < 0 && scriptCDN.push(item);
else
scriptBlock.push(item.innerText)
})

scriptCDN 继续通过上面方式插入到 body 里,然后通过 eval 或者 new Function 去执行 scriptBlock 。因为 scriptBlock 里的代码可能是会依赖 scriptCDN 里的插件的,所以需要在 scriptCDN 加载完成后在执行 scriptBlock 。

const loadScript = (item) => {
return new Promise((resolve, reject) => {
const element = document.createElement('script');
for (const { name, value } of arrayify(item.attributes)) {
element.setAttribute(name, value);
}
element.textContent = item.textContent;
element.setAttribute('async', 'false');
element.onload = resolve
element.onerror = reject
document.body.insertBefore(element)
})
} const runScriptBlock = (code) => {
try {
const func = new Function(code);
func()
} catch (error) {
try {
window.eval(code)
} catch (error) {
}
}
} Promise.all(scriptCDN.map(item => loadScript(item))).then(_ => {
scriptBlock.forEach(code => {
runScriptBlock(code)
})
})

卸载插件

按照上面思去处理之后,会存在一个问题。 比如:我们添加了一个 全局的 'resize' 事件的监听,在跳转其他页面时候我们需要移除这个监听事件。

这个时候我们需要对代码块的格式进行一个约束,比如像下面这样,在初次加载时执行 mount 里代码,页面卸载时执行 unmount 里代码。

<script data-reload-script>
DynamicPlugin.add({
// 页面加载时执行
mount() {
this.timer = setInterval(() => {
document.getElementById('time').innerText = new Date().toString()
}, 1000)
},
// 页面卸载时执行
unmount() {
window.clearInterval(this.timer)
this.timer = null
}
})
</script>

DynamicPlugin 大致结构:

let cacheMount = []
let cacheUnMount = []
let context = {} class DynamicPlugin {
add(options) {
if (isFunction(options))
cacheMount.push(options) if (isPlainObject(options)) {
let { mount, unmount } = options
if (isFunction(mount))
cacheMount.push(mount)
if (isFunction(unmount))
cacheUnMount.push(unmount)
} // 执行当前页面加载钩子
this.runMount()
} runMount() {
while (cacheMount.length) {
let item = cacheMount.shift();
item.call(context);
}
} runUnMount() {
while (cacheUnMount.length) {
let item = cacheUnMount.shift();
item.call(context);
}
}
}

页面卸载时调用 DynamicPlugin.runUnMount()。

处理 Head

Head 部分处理来说相对比较简单,可以通过拿到新旧两个 Head,然后循环对比每个标签的 outerHTML,用来判断哪些比是需要新增的哪些是需要删除的。

结尾

本文示例代码完整版本可以 参考这里

Pjax 下动态加载插件方案的更多相关文章

  1. 反射 type 的基本用法,动态加载插件

    这里介绍反射的简单实用 MyClass类 public class MyClass { public int Age { get; set; } public string Name { get; s ...

  2. 原生JS下拉加载插件分享。

    无聊写了一个JS下拉加载插件,有需要的可以下载. // 使用 // new ManDownLoad("#ul","json/load.json",functio ...

  3. .Net动态加载插件-反射

    /// <summary> /// 动态加载插件 /// </summary> void LoadPlugin() { string[] ps = Directory.GetF ...

  4. Android热修复(动态加载)方案汇总

    整理了以下动态加载的方案,便于在项目中使用时查阅: Dexposed github (https://github.com/alibaba/dexposed) AndFix github (https ...

  5. 页面滚动图片等元素动态加载插件jquery.scrollLoading.js

    如果一个网页很长,那么该页面的加载时间也会相应的较长.而这里给大家介绍的这个jQuery插件scrollLoading的作用则是,对页面元素进行动态加载,通俗的说就是滚到哪就加载到哪,屏幕以下看不见的 ...

  6. Unity3d热更新全书-加载(二)如何在不用AssetBundle的前提下动态加载预设

    Unity3D的主要构成大家都知道,首先是场景图,场景图上的节点构成一颗树. 每个节点对应一个GameObject对象 然后每个GameObject有若干个组件 有一些组件会与资源产生关系,比如Mes ...

  7. chrome调试状态下动态加载的js

    在js文件中加入 //@ sourceURL=文件名.js

  8. Qt mac QMYSQL(动态加载插件QPluginLoader的用法)

    用此段代码查出问题所在 QPluginLoader loader; loader.setFileName("/Users/danny/Qt5.3.2/5.3/clang_64/plugins ...

  9. 携程Android App插件化和动态加载实践

    携程Android App的插件化和动态加载框架已上线半年,经历了初期的探索和持续的打磨优化,新框架和工程配置经受住了生产实践的考验.本文将详细介绍Android平台插件式开发和动态加载技术的原理和实 ...

随机推荐

  1. net core 3.1使用identityServer登录时signin-oidc报Correlation failed的解决方法

    此问题全网找了很久,也困扰了我很久,始终没有找到解决方法.今天结合网上其他问题的帖子,自己研究的半天,终于找到了这个解决方法,经亲自测试可行.欢迎大牛指导指正. 有时客户收藏的系统地址是认证端的,然后 ...

  2. 【破解】设置 Codesys for Raspberry 每118分钟自动重启Runtime

    Codesys for Raspberry 无授权时,试用2小时后会自动退出,重启Runtime后就又恢复2小时试用时长. 官网授权购买地址: [单核] https://store.codesys.c ...

  3. 注解_概念和注解_JDK内置注解

    注解: 概念:说明程序的,给计算机看的 注解:用文字描述程序的,给程序员看的 定义:注解(Annotation),也叫元数据.一种代码级别的说明.他是JDK1.5及以后的版本引入的一个特性,与类,接口 ...

  4. Class对象共嫩

    需求:写一个"框架",不能改变该类的任何代码的前提下,可以帮我们创建任意类的对象,并且执行其中任意方法 实现: 1.配置文件 2.反射 步骤: 1.将需要创建的对象的全类名和需要执 ...

  5. SpringBoot集成文件 - 如何使用POI导出Word文档?

    前文我们介绍了通过Apache POI导出excel,而Apache POI包含是操作Office Open XML(OOXML)标准和微软的OLE 2复合文档格式(OLE2)的Java API.所以 ...

  6. php date函数和首位带0问题

    一.带零 echo date('Y-m-d'); 2012-08-08 二.不带零 echo date('Y-n-j'); 2012-8-8 以下为参数详解(转载): a - "am&quo ...

  7. Docker Php + mysql + nginx

    1 # 一.数据库搭建(MySQL) 2 # 1.拉取mysql镜像 3 docker pull mysql:5.6.50 4 # 2.启动镜像:--name(容器名), MYSQL_ROOT_PAS ...

  8. SpringBoot定时任务 - 经典定时任务设计:时间轮(Timing Wheel)案例和原理

    Timer和ScheduledExecutorService是JDK内置的定时任务方案,而业内还有一个经典的定时任务的设计叫时间轮(Timing Wheel), Netty内部基于时间轮实现了一个Ha ...

  9. 3. 安装部署MGR集群 | 深入浅出MGR

    GreatSQL社区原创内容未经授权不得随意使用,转载请联系小编并注明来源. 目录 1. 安装准备 2. 初始化MySQL Server 3. 初始化MGR第一个节点 4. 继续设置另外两个节点 5. ...

  10. 技术分享 | 浅谈MySQL闪回的实现

    欢迎来到 GreatSQL社区分享的MySQL技术文章,如有疑问或想学习的内容,可以在下方评论区留言,看到后会进行解答 1.闪回实现原理 2.binlog文件格式初探 3.闪回实现过程 1.闪回实现原 ...