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平台插件式开发和动态加载技术的原理和实 ...
随机推荐
- ELK 日志分析系统的部署
一.ELK简介 ElasticSearch介绍Elasticsearch是一个基于Lucene的搜索服务器. 它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口. Elasti ...
- Solution -「HDU」Professor Ben
Description 有 \(Q\) 个询问.每次给定一个正整数 \(n\),求它的所有因数的质因数个数的和. Solution 就讲中间的一个 Trick. 我们定义正整数 \(x\) 有 \(f ...
- 【每天学一点-02】创建Node.js的第一个应用
1.引入require模块,使用createServer()创建服务器 [server.js]文件 var http = require('http'); http.createServer(func ...
- 枚举子集为什么是 O(3^n) 的
这是更新日志 \(2021/2/9\) 代数推导 \(2021/2/10\) 组合意义,构建 TOC 目录 枚举子集 复杂度证明 代数推导 组合意义 Summary 枚举子集 枚举子集为什么是 \(O ...
- MySQL客户端工具的使用与MySQL SQL语句
MySQL客户端工具的使用 1.MySQL程序的组成 客户端 mysql:CLI交互式客户端程序 mycli:CLI交互式客户端程序;使用sql语句时会有提示信息 mysql_secure_insta ...
- Vue 引出声明周期 && 组件的基本使用
1 <!DOCTYPE html> 2 <html> 3 <head> 4 <meta charset="UTF-8" /> 5 & ...
- 两天时间学习的html的知识笔记
坚持努力背 特殊字符: 空格符 < 小于号 <> 大于号 >& 和号 &¥ 人民币 ¥ 版权 ©R 注册商标 ®. 摄氏度 ° 正负号 ±X 乘号 × 除号 ...
- linux常见命令(十二)
sed/egrep将order.txt文件按行号展示出来,并删除第2,4行nl order.txt |sed '2,4d'将order.txt文件按行号展示出来,并删除第3行nl order.txt ...
- 走进Redis:哨兵集群
为什么需要哨兵 在 Redis 的主从库模式中,如果从库发生了故障,用户的操作是可以继续进行的,因为写操作是只在主库中进行的.那么,如果主库发生了故障,用户的操作将会收到影响.这时候可能会需要选择一个 ...
- Git 08 IDEA撤销添加
参考源 https://www.bilibili.com/video/BV1FE411P7B3?spm_id_from=333.999.0.0 版本 本文章基于 Git 2.35.1.2 如果将不想添 ...