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平台插件式开发和动态加载技术的原理和实 ...
随机推荐
- 解决Windows10、Windows11文件名无法大写的问题
问题描述: 同一目录下的不同文件有些可以用大写字母做文件名,有些输入大写字母完成后自动变成小写. 甚至同一文件的文件名中的相同字母也会有这种情况,例如:文件名为"bu人BU"的文件 ...
- range函数的使用
循环结构终于会出现了 这章讲完差不多读者可以实现大部分程序了 range()函数 用于生成一个整数序列 内置函数:前面不需要加任何前缀,可以直接使用的函数 创建range对象的三种方式 range(s ...
- linux nginx搭建与使用
安装nginx yum -y install nginx 测试是否安装正确: nginx -t 打印如下: nginx: the configuration file /etc/nginx/nginx ...
- 4G巴歇尔槽流量采集网关
首先向大家展示下拓扑图: 金鸽科技R10物联网网关,带有一个RS485口可以采集巴歇尔槽的液位状态,还提供一个网口用于给摄像头和现场其他的网络设备提供网络传输通道!R10A内置了巴歇尔槽液位换算成流量 ...
- Python Flask Blueprint 蓝图
Python Flask Blueprint 蓝图 本篇来了解一下 Flask 中 Blueprint 蓝图,什么蓝图 ..就是一个分模块的扩展而已,用来让不同的 业务模块api 分到不同的pytho ...
- 8月份的.NET Conf 活动 专注于 .NET MAUI
.NET Conf:Focus on MAUI 是一个为期一天的免费直播活动,将于太平洋时间 8 月 9 日上午 9 点开始,来自社区和 Microsoft 团队的演讲者们将分享使用MAUI .了解. ...
- .NET CORE 读书笔记之与.NET Framework对比
.NET Framework存在的问题 它是属于系统级别安装的程序 操作系统内的所有程序共享一个.NET Framework实例,如果其中某一个应用程序需要升级Framework,其他程序也会收到影响 ...
- LuoguP4165 [SCOI2007]组队
化式子,然后两个指针平\(A\)过去 #include <cstring> #include <cstdio> #include <algorithm> #incl ...
- Spring 01 概述
简介 Spring 是开源的轻量级 J2EE 框架 我们常说的 Spring 实际上是指 Spring Framework,它是 Spring 家族中的一个重要分支. 官方文档 https://doc ...
- 10种有用的Linux Bash_Completion 命令示例
摘要:我们可以对这个 bash 补全进行加速,并使用 complete 命令将其提升到一个新的水平. 本文分享自华为云社区<有用的 Linux Bash_Completion 命令示例(Ster ...