plotly / dash

Data Apps & Dashboards for Python. No JavaScript Required.
https://plotly.com/dash
MIT License
21.6k stars 2.08k forks source link

switch to the spare cdn address when the cdn resource of the unpkg fails #2537

Open CNFeffery opened 1 year ago

CNFeffery commented 1 year ago

My current solution is as below:


class CustomDash(dash.Dash):

    def interpolate_index(self, **kwargs):
        scripts = kwargs.pop('scripts')

        external_scripts = re.findall(
            '(<script src="http.*?"></script>)', scripts)

        for external_script in external_scripts:
            scripts = scripts.replace(
                external_script,
                '''<script src="{}" onerror='this.remove(); let fallbackScript = document.createElement("script"); fallbackScript.src = "{}"; document.querySelector("footer").appendChild(fallbackScript);'></script>'''.format(
                    re.findall('"(.*?)"', external_script)[0]
                    .replace('https://unpkg.com/', 'https://unpk.com/'), # Manually simulated unpkg resource loading failure error
                    re.findall('"(.*?)"', external_script)[0]
                    .replace('https://unpkg.com/', 'https://npm.elemecdn.com/') # Switch to standby address
                )
            )

        return super(CustomDash, self).interpolate_index(scripts=scripts, **kwargs)

but it meets the Uncaught ReferenceError: DashRenderer is not defined error: image

is there a stable way to solve this issue🤔?

CNFeffery commented 1 year ago

solved.

class CustomDash(dash.Dash):

    def interpolate_index(self, **kwargs):

        scripts = kwargs.pop('scripts')

        # 提取scripts部分符合条件的外部js资源
        external_scripts = re.findall(
            '(<script src="http.*?"></script>)',
            scripts
        )

        # 将原有的script标签内容替换为带备用地址错误切换的版本
        for external_script in external_scripts:
            scripts = scripts.replace(
                external_script,
                '''<script src="{}" onerror='this.remove(); let fallbackScript = document.createElement("script"); fallbackScript.src = "{}"; document.querySelector("head").prepend(fallbackScript);'></script>'''.format(
                    re.findall('"(.*?)"', external_script)[0],
                    re.findall('"(.*?)"', external_script)[0]
                    .replace('https://unpkg.com/', 'https://npm.elemecdn.com/')
                )
            )

        scripts = '''<script>
window.onerror = async function(message, source, lineno, colno, error) {
    if (message.includes('is not defined') !== -1) {
        await waitForModules();
    }
}

async function waitForModules() {
    const requiredModules = [
        'DashRenderer',
        'dash_html_components',
        'dash_core_components',
        'feffery_antd_components',
        'feffery_utils_components',
        'feffery_markdown_components'
    ];

    while (!areModulesDefined(requiredModules)) {
        await delay(100); // 延迟100毫秒
    }

    // 变量已定义,触发事件
    var renderer = new DashRenderer();
}

function areModulesDefined(modules) {
    return modules.every(module => window[module]);
}

function delay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}
</script>
''' + scripts

        return super(CustomDash, self).interpolate_index(scripts=scripts, **kwargs)
alexcjohnson commented 1 year ago

Thanks @CNFeffery - I'm glad to see you found a solution. I haven't seen CDN fallbacks implemented before, is this common? If it is, we would gladly accept a feature to build this in natively, something like app = Dash(cdn_fallbacks={"unpkg.com": "npm.elemecdn.com"})

CNFeffery commented 1 year ago

@alexcjohnson Yes, it is common for origin CDN loads to fail due to unexpected network issues, in my solution I defined the requiredModules manually, it will be much more convenient if there are some parameters like cdn_fallbacks you mentioned😀.

alexcjohnson commented 1 year ago

OK great. I'll reopen this issue so it's more visible - I don't know if anyone here at Plotly will work on it in the near future, but if you or anyone else wants to make a PR to add cdn_fallbacks they'll have your code as a fantastic starting point!