Pjax 下动态加载插件方案
在纯静态网站里,有时候会动态更新某个区域往会选择 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 下动态加载插件方案的更多相关文章
- 反射 type 的基本用法,动态加载插件
这里介绍反射的简单实用 MyClass类 public class MyClass { public int Age { get; set; } public string Name { get; s ...
- 原生JS下拉加载插件分享。
无聊写了一个JS下拉加载插件,有需要的可以下载. // 使用 // new ManDownLoad("#ul","json/load.json",functio ...
- .Net动态加载插件-反射
/// <summary> /// 动态加载插件 /// </summary> void LoadPlugin() { string[] ps = Directory.GetF ...
- Android热修复(动态加载)方案汇总
整理了以下动态加载的方案,便于在项目中使用时查阅: Dexposed github (https://github.com/alibaba/dexposed) AndFix github (https ...
- 页面滚动图片等元素动态加载插件jquery.scrollLoading.js
如果一个网页很长,那么该页面的加载时间也会相应的较长.而这里给大家介绍的这个jQuery插件scrollLoading的作用则是,对页面元素进行动态加载,通俗的说就是滚到哪就加载到哪,屏幕以下看不见的 ...
- Unity3d热更新全书-加载(二)如何在不用AssetBundle的前提下动态加载预设
Unity3D的主要构成大家都知道,首先是场景图,场景图上的节点构成一颗树. 每个节点对应一个GameObject对象 然后每个GameObject有若干个组件 有一些组件会与资源产生关系,比如Mes ...
- chrome调试状态下动态加载的js
在js文件中加入 //@ sourceURL=文件名.js
- Qt mac QMYSQL(动态加载插件QPluginLoader的用法)
用此段代码查出问题所在 QPluginLoader loader; loader.setFileName("/Users/danny/Qt5.3.2/5.3/clang_64/plugins ...
- 携程Android App插件化和动态加载实践
携程Android App的插件化和动态加载框架已上线半年,经历了初期的探索和持续的打磨优化,新框架和工程配置经受住了生产实践的考验.本文将详细介绍Android平台插件式开发和动态加载技术的原理和实 ...
随机推荐
- Scala 练习题 学生分数案例
一.相关信息题目:1.统计班级人数2.统计学生的总分3.统计总分年级排名前十学生各科的分数4.统计总分大于年级平均分的学生5.统计每科都及格的学生6.统计偏科最严重的前100名学生数据样例(部分数据) ...
- 【AcWing】周赛
A.糖果 题目链接 链接 题目描述 给定三个正整数 a,b,c. 请计算 ⌊a+b+c2⌋,即 a,b,c 相加的和除以 2 再下取整的结果. 输入格式 第一行包含整数 T,表示共有 T 组测试数据. ...
- BI报表与数据开发
先贴个不好看的图让内容好看一点,也顺便说一下数据处理的流程:收集数据,数据清洗与数据加工,数据展示 报表制作一般就是前面的开发兄弟们完成然后把数据交到报表的兄弟们,然后在根据领导要求制作报表.大概就是 ...
- ADB命令用法大全
一.ADB简介 Android Debug Bridge,安卓调试桥,它借助adb.exe(Android SDK安装目录platform-tools下),用于电脑端与模拟器或者真实设备交互:使用 ...
- Taurus.MVC WebAPI 入门开发教程2:添加控制器输出Hello World。
系列目录 1.Taurus.MVC WebAPI 入门开发教程1:框架下载环境配置与运行. 2.Taurus.MVC WebAPI 入门开发教程2:添加控制器输出Hello World. 3.Tau ...
- #万答10:mysqldump 是如何实现一致性备份的
万答10:mysqldump 是如何实现一致性备份的 实验场景 MySQL 8.0.25 InnoDB 实验步骤: 先开启 general_log 观察导出执行过程的变化 set global gen ...
- Normal3类定义
法线的相关操作都在图中,实现部分还是大家自己练习,照着图写代码就行了. 类声明: class Normal3 { public: Normal3(); ~Normal3(); Normal3(ldou ...
- 使用SSH连接解决git报错:fatal: unable to access 'https://github.com/xxx/xxx.github.io.git/': Proxy CONNECT aborted
TL;DRs 这个错误的原因和HTTPS的代理配置有关,使用SSH方式连接可以避免这一问题 最近git pull和push的时候总是报错 fatal: unable to access 'https: ...
- 一文带你弄懂 JVM 三色标记算法!
大家好,我是树哥. 最近和一个朋友聊天,他问了我 JVM 的三色标记算法.我脑袋一愣发现竟然完全不知道!于是我带着疑问去网上看了几天的资料,终于搞清楚啥事三色标记算法,它是用来干嘛的,以及它和 CMS ...
- Get请求使用请求体传递参数会报400异常的问题
问题描述: 前端使用Get请求并且使用请求体传递参数,后端使用@RequestBody注解封装参数,这时会出现400的异常信息. 解决方法: 1.Get请求不要使用请求体,使用请求体的话用POST请求 ...