原生ES模块是浏览器内置的模块化规范,不需要依赖Webpack、Vite等打包工具,就能实现代码的模块拆分和按需加载,非常适合构建轻量化的现代化单页应用。这种方式减少了工具链配置成本,让开发流程更贴近代码本身,同时也能充分利用浏览器的原生能力提升加载效率。

项目基础结构搭建
首先我们需要规划项目的目录结构,原生ES模块单页应用不需要复杂的配置文件,核心目录只需要区分模块、页面、静态资源即可:
spa-demo/
├── index.html # 入口HTML文件
├── main.js # 应用入口脚本
├── router.js # 路由模块
├── store.js # 状态管理模块
├── modules/ # 公共工具模块
│ ├── api.js # 接口请求模块
│ └── utils.js # 通用工具函数
└── pages/ # 页面模块
├── home.js # 首页模块
├── about.js # 关于页模块
└── notFound.js # 404页模块入口HTML文件需要声明type="module"来启用原生ES模块支持,同时设置基础的页面容器:
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>原生ES模块SPA</title>
<style>
.page-container { padding: 20px; }
.nav-link { margin-right: 15px; cursor: pointer; color: #1890ff; }
</style>
</head>
<body>
<nav>
<span class="nav-link" data-path="/">首页</span>
<span class="nav-link" data-path="/about">关于</span>
</nav>
<div id="app" class="page-container"></div>
<script type="module" src="./main.js"></script>
</body>
</html>路由模块实现
原生ES模块下实现前端路由,主要依赖history API监听URL变化,匹配对应的页面模块并渲染到容器中。路由模块需要支持路径匹配、页面切换、导航拦截等基础能力:
// router.js
import home from './pages/home.js';
import about from './pages/about.js';
import notFound from './pages/notFound.js';
// 路由规则配置
const routes = [
{ path: '/', component: home },
{ path: '/about', component: about },
{ path: '*', component: notFound }
];
// 路由实例
class Router {
constructor(routes, containerId) {
this.routes = routes;
this.container = document.getElementById(containerId);
this.init();
}
// 初始化路由监听
init() {
// 监听浏览器前进后退
window.addEventListener('popstate', () => this.render());
// 监听导航点击
document.addEventListener('click', (e) => {
if (e.target.classList.contains('nav-link')) {
const path = e.target.dataset.path;
this.push(path);
}
});
// 初始渲染
this.render();
}
// 匹配当前路径对应的组件
matchComponent() {
const currentPath = window.location.pathname;
const route = this.routes.find(r => r.path === currentPath) || this.routes.find(r => r.path === '*');
return route.component;
}
// 渲染页面
async render() {
const component = this.matchComponent();
// 执行组件逻辑,返回要渲染的内容
const content = await component();
this.container.innerHTML = content;
}
// 跳转路由
push(path) {
window.history.pushState({}, '', path);
this.render();
}
}
export default Router;页面模块与状态管理
每个页面模块作为独立的ES模块导出,返回对应页面的HTML结构和逻辑,同时可以共享全局的状态管理模块。下面是首页模块和简单状态管理的实现示例:
// store.js 简单状态管理
class Store {
constructor() {
this.state = { count: 0 };
this.listeners = [];
}
// 获取状态
getState() {
return this.state;
}
// 更新状态并通知监听者
setState(newState) {
this.state = { ...this.state, ...newState };
this.listeners.forEach(listener => listener(this.state));
}
// 订阅状态变化
subscribe(listener) {
this.listeners.push(listener);
// 返回取消订阅的方法
return () => {
this.listeners = this.listeners.filter(l => l !== listener);
};
}
}
export default new Store();
// pages/home.js 首页模块
import store from '../store.js';
export default async function home() {
// 订阅状态变化,更新页面
store.subscribe((state) => {
const countEl = document.getElementById('count');
if (countEl) countEl.textContent = state.count;
});
return `
<h1>首页</h1>
<p>当前计数: <span id="count">${store.getState().count}</span></p>
<button id="addBtn">增加计数</button>
<script type="module">
import store from '../store.js';
document.getElementById('addBtn').addEventListener('click', () => {
store.setState({ count: store.getState().count + 1 });
});
</script>
`;
}入口文件整合
最后在main.js中导入路由模块,初始化整个应用即可:
// main.js
import Router from './router.js';
// 初始化路由,指定容器为id为app的元素
new Router(
[
{ path: '/', component: async () => (await import('./pages/home.js')).default },
{ path: '/about', component: async () => (await import('./pages/about.js')).default },
{ path: '*', component: async () => (await import('./pages/notFound.js')).default }
],
'app'
);注意事项与适用场景
这种原生ES模块的SPA方案虽然简化了配置,但也有一些需要注意的点:
- 浏览器兼容性:需要确认目标浏览器支持ES模块和
import/export语法,现代浏览器基本都支持,老旧浏览器需要额外做兼容处理。 - 模块路径:原生ES模块必须使用完整的相对路径或者绝对路径,不能像打包工具那样省略后缀名,路径错误会导致模块加载失败。
- 生产环境优化:如果需要做代码压缩、CDN部署,可以配合简单的构建工具做后置处理,不需要在开发阶段引入复杂的打包流程。
这种方案非常适合轻量化项目、内部工具、原型开发等场景,能大幅减少工具链带来的负担,让开发者更专注于业务逻辑本身。如果项目规模较大、依赖复杂,也可以结合轻量化的构建工具做补充,灵活选择适合自己项目的方案。
ES_moduleSPA原生JavaScript前端工程化模块化开发修改时间:2026-06-03 01:56:29