fetch动态加载HTML插入type='module'脚本仅首次生效后续不执行问题
问题原因
ES 模块规范默认对相同URL的模块只会执行一次顶层代码,后续无论你多少次插入相同src的type="module"脚本标签,浏览器都会复用已经缓存的模块实例,不会重新执行模块内的代码。普通脚本没有这个限制,所以你之前没加模块属性的时候运行正常。
你之前尝试的动态导入方案没有生效,是因为你把动态导入的逻辑还是写在了page_1.js的顶层,模块第一次执行后顶层代码不会重复运行,自然也就不会触发后续的执行逻辑。
解决方案
方案1:给脚本URL加随机参数强制不缓存
这是最简单的适配方案,不需要修改原有模块的代码,只需要在加载脚本时给URL拼接时间戳或者随机数,让浏览器认为每次都是不同的资源,就会重新执行:
function loadHTML(htmlPath) { fetch(`pages/${htmlPath}.html`) .then((res) => res.text()) .then((html) => { let page_content = document.querySelector("#page_content"); page_content.innerHTML = html; let script = document.createElement("script"); // 拼接时间戳,规避模块缓存 script.src = `pages/${htmlPath}.js?t=${Date.now()}`; if (htmlPath === "page_1") { script.type = "module"; } page_content.appendChild(script); }); }
这个方案的缺点是模块文件不会被浏览器缓存,每次加载都要重新请求,如果你的脚本体积不大的话完全可以忽略这个影响。
方案2:调整模块结构,导出初始化函数主动调用
如果想要保留模块缓存的优势,避免重复请求,可以把模块中原先写在顶层的执行逻辑封装成一个导出的函数,每次加载页面的时候主动调用这个函数即可:
首先修改page_1.js的代码:
import { displayConsoleMSG } from "../functions/someFunc.js"; // 把原来的顶层逻辑封装成init函数导出 export function init() { displayConsoleMSG("test"); // 其他原来需要每次加载页面都执行的逻辑都放到这里 }
然后修改main.js的加载逻辑,针对模块脚本使用动态导入并调用初始化函数:
function loadHTML(htmlPath) { fetch(`pages/${htmlPath}.html`) .then((res) => res.text()) .then(async (html) => { let page_content = document.querySelector("#page_content"); page_content.innerHTML = html; if (htmlPath === "page_1") { // 动态导入模块,调用初始化函数 const pageModule = await import(`./pages/${htmlPath}.js`); pageModule.init(); } else { // 普通脚本保持原有逻辑 let script = document.createElement("script"); script.src = `pages/${htmlPath}.js`; page_content.appendChild(script); } }); }
这个方案既保留了模块的缓存优势,也能保证每次切换页面都能执行对应逻辑,更符合ES模块的最佳实践。
内容的提问来源于stack exchange,提问作者Kleber Germano
相关产品推荐
相关产品推荐

