• 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(', '
    ).replace('', '
    '
    ); 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 里代码。

    
    

    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,用来判断哪些比是需要新增的哪些是需要删除的。

    结尾#

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

  • 相关阅读:
    【Windbg】记一次线程卡主的问题
    1386. 安排电影院座位
    C/C++算术表达式求值
    【ESD专题】静电防护物品、静电测试工具及防静电符号
    JWT整合Gateway实现鉴权(RSA与公私密钥工具类)
    内核IPv4路由选择子系统(简述)
    毕业答辩PPT怎么做?制作PPT必备的模板网站和AI工具来了!
    haproxy+keepalived实战
    ceph-ansible安装指南-centos8安装ceph- pacific
    SpringBoot 自定义注解 + SpringBoot Aop 实现切面日志处理
  • 原文地址:https://www.cnblogs.com/nextl/p/16738558.html