diff --git a/README.md b/README.md index e542e87..4454dc4 100644 --- a/README.md +++ b/README.md @@ -1,117 +1,51 @@ -# @apigo.cc/loader 模块调度中心 +# @apigo.cc/loader API 手册 -`@apigo.cc/loader` 是整个 `@apigo.cc` 体系的入口调度中心。它负责根据当前环境自动生成 `importmap`,并管理各模块的版本。 +全自动依赖调度器。通过动态注入脚本标签,实现对 Apigo 全线组件的一键加载、版本管理及环境自动适配。 --- -## 一、 集成方式 +## 1. 引入方式 (UMD 优先) -### 1. 引入 Loader (任选一个 CDN 镜像复制即用) -直接引用打包好的 `loader.min.js`,它会自动根据当前的加载源适配 CDN 镜像: +您可以通过 URL 参数或 **Hash (#)** 直接定义要加载的模块。 +### A. 极简带版本模式 (防缓存) ```html - - - - - - - - - - - - - - + + ``` -### 2. 自动注入 Importmap (支持做减法) -在 HTML 中引入任意一个镜像的 loader,并声明加载所有已有项目模块(在实际生产中可按需做减法删除不需要的项): - +### B. 简写 Hash 模式 ```html - - - - - -``` - -### 3. 原生朴素写法 (手动定义) -如果您不想使用 `Loader.load` 的自动注入,也可以手动定义 `importmap`(同样可以通过直接删除无用项来做减法): - -```html - - - + + ``` --- -## 二、 本地开发与调试重定向 +## 2. 核心特性 -在本地开发时,如果需要将某些模块重定向到您本地的开发服务器(例如 Vite 本地服务),**只需在 `Loader.load` 对应的模块中传入本地 URL 或相对路径**。`Loader` 内部会自动识别并将其作为自定义地址使用: +### 🚀 智能定位与版本传播 (Smart Propagation) +- **同域跟随**: 如果 `loader.js` 加载自 `/static/js/`,则所有子包默认也从 `/static/js/` 获取。 +- **版本锁步**: 只要 Loader URL 带有 `?v=X.Y.Z`,加载的所有依赖包(如 `base.js`)都会自动变为 `base.js?v=X.Y.Z`。 +- **压缩同步**: 只要引入的是 `loader.min.js`,其加载的所有依赖也会自动请求 `.min.js` 版本。 -```javascript -Loader.load('state:1.0.12', - 'bootstrap:http://localhost:5173/src/index.js', // 调试重定向:指向本地 Vite 源码服务 - 'base:1.0.7','datatable:1.0.7','kanban:1.0.1','mindmap:1.0.1','chart:1.0.1','editor:1.0.1'); -``` +### 🔗 自动依赖分析 +Loader 内部维护了完整的依赖链。例如: +- 请求 `datatable` -> 自动按序注入 `state` -> `bootstrap` -> `base` -> `datatable`。 --- -## 三、 核心特性 +## 3. API 接口:`__apigo_load()` -1. **多镜像自适应**:自动识别 `loader.min.js` 的来源 URL,并自动将其他模块指向相同的 CDN 镜像。 -2. **默认版本管理**:内置了各组件的稳定版本号,无需手动记忆。 -3. **零配置**:只需一行代码即可完成整个生态链的模块映射。 +脚本引入后,全局会挂载该函数。**注意:该函数在运行一次后会自动自毁(从全局删除)以保持环境纯净。** + +### `__apigo_load(...pkgs)` +- **`__apigo_load('base')`**: 加载默认稳定版。 +- **`__apigo_load('state:1.0.11')`**: 指定特定包的版本。 --- -## 四、 支持模块清单 -所有模块均发布在 `@apigo.cc` 组织下: -* `state` (原子状态机) -* `bootstrap` (UI 引擎) -* `base` (常用控件库) -* `datatable` (高性能表格) -* `kanban` (看板) -* `mindmap` (思维导图) -* `chart` (图表) -* `editor` (代码编辑器) +## 开发者提示 (AI 必读) +1. **参数优先级**: URL 查询参数 `?load=` 优先级高于 Hash 参数 `#`。 +2. **同步执行**: Loader 利用浏览器对动态插入脚本的顺序解析机制,保障了依赖项在业务代码运行前就绪。 +3. **自清理**: `__apigo_load` 仅在 ` + + + +
loading
+ + + + diff --git a/test/loader.spec.js b/test/loader.spec.js new file mode 100644 index 0000000..0f91629 --- /dev/null +++ b/test/loader.spec.js @@ -0,0 +1,33 @@ +import { test, expect } from '@playwright/test'; + +test('loader dynamic importmap generation and custom path redirect', async ({ page }) => { + page.on('console', msg => console.log('BROWSER LOG:', msg.text())); + + // 打开测试页面 + await page.goto('http://127.0.0.1:8084/test/index.html'); + + // 1. 验证生成的 importmap 标签结构 + const importMapJson = await page.evaluate(() => { + const script = document.querySelector('script[type="importmap"]'); + return script ? JSON.parse(script.textContent) : null; + }); + + expect(importMapJson).not.toBeNull(); + expect(importMapJson.imports).toBeDefined(); + + // 2. 检查 state (常规 CDN 映射) 是否生成正确 + const stateUrl = importMapJson.imports['@apigo.cc/state']; + expect(stateUrl).toContain('cdn.jsdelivr.net/npm/@apigo.cc/state'); + + // 3. 检查 bootstrap (本地相对路径重定向) 是否保留 + const bootstrapUrl = importMapJson.imports['@apigo.cc/bootstrap']; + expect(bootstrapUrl).toBe('./mock-bootstrap.js'); + + // 4. 检查 base (本地绝对 HTTP 重定向) 是否保留 + const baseUrl = importMapJson.imports['@apigo.cc/base']; + expect(baseUrl).toBe('http://127.0.0.1:8084/test/mock-base.js'); + + // 5. 验证重定向的 mock 模块确实可以被 ESM 成功导入并运行 (通过页面 status 的变化) + const status = page.locator('#status'); + await expect(status).toHaveText('success'); +}); diff --git a/test/mock-base.js b/test/mock-base.js new file mode 100644 index 0000000..98c6063 --- /dev/null +++ b/test/mock-base.js @@ -0,0 +1,3 @@ +export const HTTP = { isMock: true, name: 'base-mock' }; +export const UI = { toast: () => {} }; +export default { HTTP, UI }; diff --git a/test/mock-bootstrap.js b/test/mock-bootstrap.js new file mode 100644 index 0000000..b2c993e --- /dev/null +++ b/test/mock-bootstrap.js @@ -0,0 +1,2 @@ +export const Bootstrap = { isMock: true, name: 'bootstrap-mock' }; +export default Bootstrap; diff --git a/vite.config.js b/vite.config.js index 8db3480..17166ad 100644 --- a/vite.config.js +++ b/vite.config.js @@ -6,22 +6,29 @@ export default defineConfig({ build: { lib: { entry: resolve(__dirname, 'src/index.js'), - name: 'Loader', - formats: ['iife'] // 使用 IIFE 格式,方便直接在 script 标签中使用 + name: 'ApigoLoader', + formats: ['umd', 'es'] }, rollupOptions: { output: [ { - format: 'iife', - name: 'Loader', - entryFileNames: 'loader.js', - extend: true + format: 'umd', + name: 'ApigoLoader', + entryFileNames: 'loader.js' }, { - format: 'iife', - name: 'Loader', + format: 'umd', + name: 'ApigoLoader', entryFileNames: 'loader.min.js', - extend: true, + plugins: [terser()] + }, + { + format: 'es', + entryFileNames: 'loader.mjs' + }, + { + format: 'es', + entryFileNames: 'loader.min.mjs', plugins: [terser()] } ]