利用Pjax下载动态加载插件方案分享

作者:游侠好梦 时间:2024-04-29 14:06:38 

在纯静态网站里,有时候会动态更新某个区域往会选择 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,用来判断哪些比是需要新增的哪些是需要删除的。

结尾

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

来源:https://www.cnblogs.com/nextl/p/16738558.html

标签:Pjax,下载,插件
0
投稿

猜你喜欢

  • nodejs前端自动化构建环境的搭建

    2024-05-08 09:36:48
  • sql server 复制表从一个数据库到另一个数据库

    2024-01-16 17:53:57
  • 在MySQL中删除表的操作教程

    2024-01-15 09:37:46
  • 使用VSCode如何从github拉取项目的实现

    2023-02-02 10:32:35
  • Websocket通信协议在数字孪生中的应用

    2024-04-30 08:55:46
  • Python文件操作方法详解

    2023-01-08 14:40:29
  • 解密Python中的描述符(descriptor)

    2023-12-05 08:28:48
  • Python利用fastapi实现上传文件

    2023-12-09 05:54:40
  • JavaScript修改作用域外变量的方法

    2024-04-10 16:12:01
  • 关于微信小程序获取小程序码并接受buffer流保存为图片的方法

    2023-07-20 09:54:01
  • python中requests爬去网页内容出现乱码问题解决方法介绍

    2023-09-14 01:00:11
  • Oracle三种上载文件技术

    2010-07-16 13:34:00
  • mysql mysqldump只导出表结构或只导出数据的实现方法

    2024-01-23 19:46:55
  • PHP数字前补0的自带函数sprintf 和number_format的用法(详解)

    2024-06-05 09:50:01
  • 一文带你上手Vue新的状态管理Pinia

    2024-05-09 15:11:33
  • Apache POI操作批量导入MySQL数据库

    2024-01-27 16:52:58
  • Django自定义全局403、404、500错误页面的示例代码

    2021-07-27 20:03:33
  • javascript contains和compareDocumentPosition 方法来确定是否HTML节点间的关系

    2024-04-17 10:03:11
  • Python中map,reduce,filter和sorted函数的使用方法

    2023-04-04 14:01:48
  • CentOS 6.4安装配置LAMP服务器(Apache+PHP5+MySQL)

    2023-11-21 21:42:33
  • asp之家 网络编程 m.aspxhome.com