<template>标签本身不发起网络请求,无法跨源加载HTML;必须用JavaScript通过fetch获取远程数据,并结合本地模板安全渲染。

template 标签本身不提供跨源能力,别指望它自动加载远程 HTML
HTML <template> 是纯客户端结构容器,不发起网络请求,也不解析外部 URL。写 <template src="https://api.example.com/card.html"></template> 无效——浏览器会忽略 src 属性,只当它是空模板。
所谓“跨源数据能力”,实际指组件能安全获取并渲染来自不同域名的数据(如 API JSON),而非加载跨域 HTML 片段。这和 <template> 无关,得靠 JS 主动 fetch + 正确处理 CORS。
- 必须用
fetch()或XMLHttpRequest显式请求,且服务端需返回Access-Control-Allow-Origin头 - 不能在
constructor或connectedCallback里直接innerHTML = await response.text()—— 模板字符串里的<slot>不生效,插槽逻辑全丢 - 若想复用远程 HTML 结构,得先
fetch回来,再用DOMParser().parseFromString(html, 'text/html')转成 DocumentFragment,最后cloneNode(true)
customElements.define 的组件如何安全消费跨源 JSON 数据
核心是把数据获取和模板渲染解耦:数据走 fetch,结构走本地 <template>,二者在 connectedCallback 中拼装。
例如一个 <weather-card> 组件,属性 data-url 指向跨域 API:
立即学习“前端免费学习笔记(深入)”;
class WeatherCard extends HTMLElement {
constructor() {
super();
this.attachShadow({ mode: 'open' });
const tpl = document.getElementById('weather-template');
this.shadowRoot.appendChild(tpl.content.cloneNode(true));
}
connectedCallback() {
const url = this.getAttribute('data-url');
if (!url) return;
// 确保只 fetch 一次,避免重复触发
if (this._fetched) return;
this._fetched = true;
fetch(url)
.then(r => {
if (!r.ok) throw new Error(`HTTP ${r.status}`);
return r.json();
})
.then(data => {
const h3 = this.shadowRoot.querySelector('h3');
const p = this.shadowRoot.querySelector('p');
if (h3 && p) {
h3.textContent = data.location;
p.textContent = `${data.temp}°C`;
}
})
.catch(err => {
this.shadowRoot.querySelector('p').textContent = '加载失败';
});
}
}
customElements.define('weather-card', WeatherCard);
- 不要在
attributeChangedCallback里反复 fetch——属性变一次就发一次请求,容易被限流或触发竞态 - 错误处理必须有,否则跨源请求失败时组件空白,用户无感知
- JSON 字段名硬编码在 JS 里(如
data.temp);若 API 结构不稳定,建议加字段存在性检查,避免Cannot read property 'temp' of undefined
为什么不能用 import() 动态加载跨域 HTML 模板
import() 只支持同源或 CORS 配置正确的 ES Module JS 文件,不支持 HTML。试图 import('https://cdn.example.com/template.html') 会报 TypeError: Failed to resolve module specifier。
- 即使服务端配置了
Access-Control-Allow-Origin: *,HTML 文件不是合法的 JS 模块,浏览器拒绝解析 - 改用
fetch().then(r => r.text())拿到字符串后,仍需手动解析为 DOM 节点——无法直接appendChild字符串,否则<slot>失效、事件丢失、样式不生效 - 远程 HTML 若含内联脚本(
<script>),DOMParser不执行它;若依赖运行时 JS,就得自己eval()或注入,极不安全,禁止这么做
真正可复用的跨源组件,关键在“数据契约”而非“模板搬运”
把跨源能力寄托在搬运 HTML 上,本质是把服务端模板强耦合到前端组件里,违背封装原则。稳定的做法是约定 JSON Schema,让组件只关心字段语义,不关心 HTML 结构来源。
- 组件内部模板固定,仅通过
textContent/setAttribute填充数据,不拼接 HTML 字符串——杜绝 XSS - 远程数据字段映射由组件自身控制(如
data.temp_max → <span class="high">),不依赖服务端返回带 class 的 HTML - 若需服务端渲染部分结构(如富文本),应由服务端返回 sanitized HTML,并用
this.shadowRoot.querySelector('.content').innerHTML = data.html,但必须确保data.html已经过 CSP 兼容的白名单过滤
跨源不是技术开关,而是信任边界。组件能跑通 fetch,不代表它该无条件信任返回的任意 HTML 或任意字段名——字段缺失、类型错乱、结构突变,才是线上最常出问题的地方。



















