这是一个使用 vue3-sfc-loader 动态加载 Vue 单文件组件的示例项目,无需构建步骤即可运行 Vue3 应用。
- 使用 Vue3 + Vue Router + Pinia 构建
- 无需构建工具,直接在浏览器中运行
- 动态加载 .vue 文件
- 支持 ES6 模块导入语法
- 包含登录和主页示例
.
├── api/ # API 模块
│ └── login.js # 登录相关 API
├── router/ # 路由配置
│ └── index.js # 路由定义和守卫
├── stores/ # Pinia 状态存储
│ └── auth.js # 认证状态管理
├── utils/ # 工具函数
│ └── helpers.js # 辅助函数
├── views/ # Vue 视图组件
│ ├── login/index.vue # 登录页面
│ └── home/index.vue # 主页
├── styles/ # 样式文件
│ └── main.css # 全局样式
├── static/ # 静态资源
│ └── vue3-sfc-loader/ # vue3-sfc-loader 源码
├── index.html # 主页面
├── main.js # 应用入口
└── README.md # 项目说明
直接在浏览器中打开 index.html 即可运行项目。
默认登录账号密码:
- 账号:admin
- 密码:password
vue3-sfc-loader 是本项目的核心组件,它允许我们在浏览器中直接加载和运行 .vue 单文件组件。以下是对配置选项的详细说明:
- 类型:
babel_ParserPlugin[] - 说明: 额外的 Babel 解析器插件,用于支持特殊的 JavaScript 语法特性
- 类型:
Record<string, any> - 说明: 额外的 Babel 插件,用于代码转换和处理
- 类型:
Cache - 说明: 编译缓存配置,用于缓存已编译的组件以提高性能
- 类型:
[string, string] - 说明: 自定义文本插值分隔符,用于避免与服务端模板引擎冲突
- 类型:
boolean - 说明: 开发模式开关,启用后会提供更详细的错误信息和警告
- 类型:
(path: string) => string - 说明: 路径处理函数,用于从路径中提取文件名
- 类型:
ModuleHandler - 说明: 自定义模块处理器,用于处理特殊类型的模块(如 .css、.json 等)
- 类型:
(tag: string) => boolean | undefined - 说明: 自定义元素识别函数,用于识别原生自定义元素
- 类型:
Record<ModuleCacheId, LoadingType<ModuleExport> | ModuleExport> - 说明: 模块缓存,用于存储已加载的模块,避免重复加载。这是实现模块导入的关键配置
- 类型:
PathResolve - 说明: 路径解析函数,用于解析模块的绝对路径
- 类型:
"preserve" | "condense" - 说明: 空白字符处理策略,控制模板中空白字符的处理方式
- 类型:
(style: string, scopeId: string) => void - 说明: 样式添加函数,用于将编译后的 CSS 样式添加到页面中
- 类型:
(refPath: AbstractPath, source: string, options: Options) => Module - 说明: CommonJS 模块创建函数,用于创建 CommonJS 格式的模块
- 类型:
(block: CustomBlock, filename: AbstractPath, options: Options) => Promise<CustomBlockCallback> - 说明: 自定义块处理器,用于处理 .vue 文件中的自定义块
- 类型:
(path: AbstractPath) => Promise<File | ContentData> - 说明: 文件获取函数,用于获取指定路径的文件内容
- 类型:
(pathCx: PathContext, options: Options) => Resource - 说明: 资源获取函数,用于获取指定路径的资源
- 类型:
(path: AbstractPath, options: Options) => Promise<{}> - 说明: 模块加载函数,用于加载指定路径的模块
- 类型:
(type: string, ...data: any[]) => void - 说明: 日志记录函数,用于输出编译过程中的日志信息
- 类型:
(srcRaw: string, lang: string, filename: AbstractPath, options: Options) => Promise<string> - 说明: 样式处理函数,用于处理不同语言的样式代码
本项目中的工具函数、API 模块和 Pinia store 都采用 CJS (CommonJS) 格式的 IIFE (Immediately Invoked Function Expression) 定义,主要原因如下:
- 浏览器原生支持:IIFE 是立即执行的函数表达式,可以直接在浏览器中运行,无需额外的模块加载器
- vue3-sfc-loader 集成:vue3-sfc-loader 可以很好地与这种格式集成,通过 moduleCache 映射模块
- CJS 格式:CommonJS 是 Node.js 使用的模块系统,具有良好的生态系统和工具支持
- UMD 兼容性:通过 IIFE 包装,我们的模块既可以在浏览器中作为全局变量使用,也可以在 Node.js 环境中作为 CommonJS 模块使用
- 避免全局污染:IIFE 创建了一个独立的作用域,防止变量泄漏到全局命名空间
- 命名冲突避免:内部变量和函数不会与页面其他脚本产生冲突
- 无需构建步骤:直接在浏览器中运行,无需 Webpack、Vite 等构建工具
- 快速原型开发:适合快速开发和测试,降低项目初始化复杂度
示例格式:
// 使用 IIFE 包装确保作用域隔离
(function() {
// 严格模式确保代码质量
'use strict';
// 模块实现
var moduleName = {
// 模块方法
method: function() {
// 方法实现
}
};
// 多环境支持导出
if (typeof module !== 'undefined' && module.exports) {
// CommonJS/Node.js 环境
module.exports = moduleName;
} else {
// 浏览器环境
window.moduleName = moduleName;
}
})();在编写 JavaScript 模块时,必须注意避免重复定义 window 对象属性,以防止模块冲突:
- 唯一性原则:每个模块应使用唯一的名称挂载到
window对象上 - 语义化命名:使用具有明确含义的名称,如
window.helpersUtil、window.authStore - 避免冲突:确保新模块的名称不与已存在的模块或浏览器内置对象冲突
- 命名规范:推荐使用驼峰命名法,保持命名风格一致
错误示例:
// 错误:重复定义同一个window属性
window.utils = moduleA;
window.utils = moduleB; // 覆盖了moduleA正确示例:
// 正确:使用不同的属性名
window.helpersUtil = moduleA;
window.authStore = moduleB;index.html 中 JavaScript 文件的加载顺序非常重要,必须遵循严格的依赖关系:
<!-- 1. 加载核心依赖库 -->
<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
<script src="https://unpkg.com/vue-router@4/dist/vue-router.global.js"></script>
<script src="https://unpkg.com/pinia@2/dist/pinia.iife.js"></script>
<!-- 2. 加载vue3-sfc-loader -->
<script src="https://unpkg.com/vue3-sfc-loader/dist/vue3-sfc-loader.js"></script>
<!-- 3. 加载自定义模块 -->
<script src="./utils/helpers.js"></script>
<script src="./stores/auth.js"></script>
<script src="./router/index.js"></script>
<!-- 4. 启动应用 -->
<script src="./main.js"></script>- Vue 核心库优先:Vue 是整个应用的基础,必须首先加载
- Vue Router 和 Pinia:这些是 Vue 的插件,依赖于 Vue,需要在 Vue 之后加载
- vue3-sfc-loader:这是动态加载 .vue 组件的核心工具,依赖于 Vue
- 自定义模块:工具函数、状态管理和路由配置等模块,它们可能依赖于前面加载的库
- 应用入口:main.js 是应用的启动文件,需要在所有依赖加载完成后执行
这种加载顺序确保了:
- 每个模块在使用时其依赖已经存在
- 避免因依赖缺失导致的运行时错误
- 正确初始化全局对象和模块引用
moduleCache 是 vue3-sfc-loader 的核心配置之一,用于映射模块标识符到实际模块对象:
// router/index.js
const options = {
moduleCache: {
vue: Vue,
'vue-router': VueRouter,
pinia: Pinia,
'utils/helpers': window.helpersUtil,
'stores/auth': window.authStore,
'api/login': window.loginApi,
}
};这样在 .vue 组件中就可以使用标准的 ES6 导入语法:
<script setup>
import { getToken } from 'utils/helpers';
import { login } from 'api/login';
</script>如果不使用 moduleCache,也可以直接使用相对路径导入:
<script setup>
import { login } from '../../api/login.js';
import { setToken, getToken } from '../../utils/helpers.js';
</script>两种方式的差别:
- 路径处理:使用 moduleCache 可以使用简短的模块标识符,而相对路径需要根据文件位置调整
- 模块映射:moduleCache 提供了更清晰的模块映射关系,便于维护
- 可读性:moduleCache 方式更接近传统构建工具的使用体验
<script setup>
import { useRouter, useRoute } from 'vue-router'
const router = useRouter()
const route = useRoute()
// 导航
router.push('/home')
// 访问路由参数
console.log(route.params)
</script><script setup>
import { useAuthStore } from 'stores/auth'
const authStore = useAuthStore()
// 访问状态
console.log(authStore.user)
// 调用 actions
authStore.setUser(userInfo)
// 使用 getters
console.log(authStore.displayName)
</script><script setup>
import { getToken, setToken } from 'utils/helpers'
// 使用工具函数
const token = getToken()
setToken(newToken)
</script>在组件外部(如路由守卫、工具函数等),我们需要通过 window 对象来访问 router 和 pinia,原因如下:
- 组件外的代码(如路由守卫)在 Vue 应用实例创建之前就已经执行
- 此时通过 ES6 导入的模块还无法访问到 Vue 应用上下文中的 router 和 pinia 实例
- 通过
window对象可以确保在应用初始化后访问到这些实例
- 在 index.html 中,通过
<script>标签加载的模块会自动挂载到window对象上 - 每个模块在 IIFE 中都会自动将自身导出到
window对象,例如window.helpersUtil、window.authStore等 - 这样在任何地方都可以通过
window对象访问这些全局模块
- Vue 组件中的 ES6 导入在组件加载时解析
- 组件外的代码需要确保在相关模块加载完成后再访问
- 通过
window对象可以避免模块加载顺序问题
- index.html - 主页面,通过
<script>标签加载所有依赖并自动挂载到window对象 - router/index.js - 路由配置,通过 moduleCache 映射模块
- stores/auth.js - 状态管理,导出 Pinia store
- utils/helpers.js - 工具函数集合
- api/login.js - API 接口封装
- views//*.vue** - Vue 组件,通过 ES6 导入使用其他模块
这种架构实现了模块间的解耦,同时通过 vue3-sfc-loader 和 moduleCache 提供了良好的开发体验。