html原生不支持模块化,必须依赖构建预处理、web components或js动态加载;import './header.html'会报错,和非标准方案且问题多,需配合document.importnode()和shadow dom才能实现真正隔离与复用。

纯 HTML 本身不支持组件化部署,所谓“模块化”必须靠额外机制介入——要么构建时预处理,要么运行时用 Web Components 或 JS 加载。指望直接写 <include src="header.html"></include> 就生效,只会得到空白页面或控制台报错。
为什么原生 HTML 无法直接 import 或 include 组件
浏览器对 HTML 的解析是线性的、声明式的,import 只认 JS 模块,<link rel="import"> 已被废弃,<iframe></iframe> 和 document.write() 都不是组件复用方案。
-
import './header.html'会直接抛出TypeError: Failed to resolve module specifier -
<iframe src="header.html"></iframe>导致样式隔离、JS 上下文断裂、SEO 不可见、滚动错位 -
document.write()破坏 DOM 构建流,触发强制重排,且极易引入 XSS 漏洞
构建时预处理:最稳妥的静态站点方案
适合无后端、纯静态部署(如 GitHub Pages、Vercel 静态托管),核心是把分散的 header.html、footer.html 在打包阶段合并进主页面。
- 用 Vite:配置
import headerHtml from './components/header.html?raw',再在 JS 中插入到document.body对应位置 - 用 Webpack:加
html-loader或posthtml-include插件,支持<include src="./header.html"></include>语法 - 路径必须写绝对或确保
<base href="/">已设置,否则<img src="logo.png">会 404 - HTML 片段里的
<script></script>标签不会自动执行,需手动创建并 append 到 DOM
运行时 Web Components:零构建、真封装的唯一原生路径
如果你坚持不走构建流程、又想获得真正封装(样式隔离 + 结构复用),customElements.define() 是当前唯一被所有现代浏览器稳定支持的方案。
- 组件名必须含短横线,如
"nav-bar";写成"navbar"浏览器直接忽略 -
<template></template>内不能放<style></style>或<script></script>,得用 JS 动态注入或adoptedStyleSheets - Shadow DOM 默认
mode="closed",外部 CSS 进不去;要继承全局样式,得显式设为mode="open" - 事件监听必须绑定在
this.shadowRoot上,document.addEventListener()捕获不到内部点击
data-属性 + 语义化标签:低成本可维护性的底线
即使不用任何构建或自定义元素,也能大幅提升模块可识别性与长期可维护性——关键不是“能不能复用”,而是“别人能不能一眼看懂你在复用什么”。
- 用
<header></header>、<nav></nav>、<main></main>替代一堆<div class="header">,它们是标准 DOM 接口,<code>document.querySelector('main')比查 class 稳定得多 - 每个模块外层加
data-module="header"和data-version="2.1",CI 流程可校验 HTML 片段是否匹配文档 - 避免用
id做锚点,同一页面多次渲染同个组件时,id重复会让document.getElementById()行为不可控 - 结构优先用
<section></section>(必须带<h2></h2>)、<article></article>(独立内容单元),而不是当布局容器滥用
真正容易被忽略的点是:模块化不是功能问题,而是契约问题。一个 header 组件是否预留了 slot 插槽?它的 data-theme 属性是否被 JS 实际读取?CSS 类名是否遵循 BEM 规范(如 cmp-header__logo)?这些细节不落地,再漂亮的“组件”也只是一次性代码。











