chore: version 1.0.3

This commit is contained in:
AI Engineer 2026-06-04 19:21:45 +08:00
parent 731fd0cfca
commit 70ebb40e4f
8 changed files with 193 additions and 95 deletions

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
node_modules/
dist/
test-results/
playwright-report/
.DS_Store
*.log

108
README.md
View File

@ -6,86 +6,102 @@
## 一、 集成方式
直接引用打包好的 `loader.min.js`,它会自动根据当前的加载源适配 CDN 镜像。
### 1. 自动注入 Importmap
在 HTML 中引入 loader 并声明需要加载的模块:
### 1. 引入 Loader (任选一个 CDN 镜像复制即用)
直接引用打包好的 `loader.min.js`,它会自动根据当前的加载源适配 CDN 镜像:
```html
<!-- 引入 loader -->
<script src="https://cdn.jsdelivr.net/npm/@apigo.cc/loader@1.0.2/dist/loader.min.js"></script>
<!-- jsDelivr (默认推荐) -->
<script src="https://cdn.jsdelivr.net/npm/@apigo.cc/loader@1.0.3/dist/loader.min.js"></script>
<!-- esm.sh -->
<script src="https://esm.sh/@apigo.cc/loader@1.0.3/dist/loader.min.js"></script>
<!-- unpkg.com -->
<script src="https://unpkg.com/@apigo.cc/loader@1.0.3/dist/loader.min.js"></script>
<!-- unpkg.zhimg.com (中国加速) -->
<script src="https://unpkg.zhimg.com/@apigo.cc/loader@1.0.3/dist/loader.min.js"></script>
<!-- npm.elemecdn.com (中国加速) -->
<script src="https://npm.elemecdn.com/@apigo.cc/loader@1.0.3/dist/loader.min.js"></script>
```
### 2. 自动注入 Importmap (支持做减法)
在 HTML 中引入任意一个镜像的 loader并声明加载所有已有项目模块在实际生产中可按需做减法删除不需要的项
```html
<script src="https://cdn.jsdelivr.net/npm/@apigo.cc/loader@1.0.3/dist/loader.min.js"></script>
<script>
// 定义需要使用的模块,自动生成 importmap
// 支持格式: 'name' 或 'name:version'
// 也可以使用标签模式: 'name:latest', 'name:beta' 等
Loader.load('state:1.0.11', 'bootstrap:1.0.3', 'base:1.0.7');
// 声明需要使用的模块,自动生成 importmap (版本号在 build 时由脚本自动同步至最新)
Loader.load('state:1.0.12','bootstrap:1.0.4','base:1.0.8','datatable:1.0.7','kanban:1.0.1','mindmap:1.0.1','chart:1.0.1','editor:1.0.1');
</script>
<!-- 随后即可使用原生 import -->
<script type="module">
import { $ } from '@apigo.cc/state';
// 核心开发范例导入核心状态、UI 引擎以及最核心的 UI/网络基建 (base)
import { NewState, LocalStorage } from '@apigo.cc/state';
import { Bootstrap } from '@apigo.cc/bootstrap';
import { HTTP, UI } from '@apigo.cc/base';
console.log('Ready');
// 1. 初始化配色与深色模式绑定
Bootstrap.config({ primary: '#6366f1' });
Bootstrap.bindDarkMode(LocalStorage, 'darkMode');
// 2. 使用 base 提供的全局 UI 能力
UI.toast('系统已成功加载');
// 3. 使用 base 提供的 HTTP 请求工具
HTTP.get('/api/status').then(res => {
console.log('API Response:', res);
});
</script>
```
### 2. 原生朴素写法 (手动定义)
如果您不想使用 `Loader.load` 的自动注入,也可以手动定义 `importmap`
### 3. 原生朴素写法 (手动定义)
如果您不想使用 `Loader.load` 的自动注入,也可以手动定义 `importmap`(同样可以通过直接删除无用项来做减法)
```html
<script type="importmap">
{
"imports": {
"@apigo.cc/state": "https://cdn.jsdelivr.net/npm/@apigo.cc/state@1.0.11/dist/state.min.js",
"@apigo.cc/bootstrap": "https://cdn.jsdelivr.net/npm/@apigo.cc/bootstrap@1.0.3/dist/bootstrap.min.js"
"@apigo.cc/state": "https://cdn.jsdelivr.net/npm/@apigo.cc/state@1.0.12/dist/state.min.js",
"@apigo.cc/bootstrap": "https://cdn.jsdelivr.net/npm/@apigo.cc/bootstrap@1.0.4/dist/bootstrap.min.js",
"@apigo.cc/base": "https://cdn.jsdelivr.net/npm/@apigo.cc/base@1.0.8/dist/base.min.js",
"@apigo.cc/datatable": "https://cdn.jsdelivr.net/npm/@apigo.cc/datatable@1.0.7/dist/datatable.min.js",
"@apigo.cc/kanban": "https://cdn.jsdelivr.net/npm/@apigo.cc/kanban@1.0.1/dist/kanban.min.js",
"@apigo.cc/mindmap": "https://cdn.jsdelivr.net/npm/@apigo.cc/mindmap@1.0.1/dist/mindmap.min.js",
"@apigo.cc/chart": "https://cdn.jsdelivr.net/npm/@apigo.cc/chart@1.0.1/dist/chart.min.js",
"@apigo.cc/editor": "https://cdn.jsdelivr.net/npm/@apigo.cc/editor@1.0.1/dist/editor.min.js"
}
}
</script>
<script type="module">
import { $ } from '@apigo.cc/state';
import { NewState } from '@apigo.cc/state';
import { HTTP, UI } from '@apigo.cc/base';
// ... 业务代码
</script>
```
---
## 二、 核心特性
## 二、 本地开发与调试重定向
1. **多镜像自适应**:自动识别 `loader.min.js` 的来源 URL并自动将其他模块指向相同的 CDN 镜像。支持以下镜像:
* `cdn.jsdelivr.net` (默认)
* `esm.sh`
* `unpkg.com`
* `unpkg.zhimg.com` (中国加速)
* `npm.elemecdn.com` (中国加速)
2. **默认版本管理**:内置了各组件的稳定版本号,无需手动记忆。
3. **零配置**:只需一行代码即可完成整个生态链的模块映射。
在本地开发时,如果需要将某些模块重定向到您本地的开发服务器(例如 Vite 本地服务),**只需在 `Loader.load` 对应的模块中传入本地 URL 或相对路径**。`Loader` 内部会自动识别并将其作为自定义地址使用:
```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');
```
---
## 三、 本地开发与调试重定向
## 三、 核心特性
在本地开发时,如果需要将某些模块重定向到本地开发服务器进行调试,可以通过在浏览器控制台设置 `localStorage` 来实现,**无需修改任何 HTML 业务代码**。
### 1. 启用本地调试重定向
比如将 `bootstrap``state` 模块重定向到您本地运行的 Vite 开发服务器:
```javascript
// 重定向 bootstrap 模块
localStorage.setItem('dev:@apigo.cc/bootstrap', 'http://localhost:5173/src/index.js');
// 重定向 state 模块
localStorage.setItem('dev:@apigo.cc/state', 'http://localhost:5174/src/index.js');
```
### 2. 取消本地调试重定向
调试完成后,清除 `localStorage` 对应的键即可恢复使用 CDN 版本:
```javascript
// 恢复为 CDN 版本
localStorage.removeItem('dev:@apigo.cc/bootstrap');
localStorage.removeItem('dev:@apigo.cc/state');
```
1. **多镜像自适应**:自动识别 `loader.min.js` 的来源 URL并自动将其他模块指向相同的 CDN 镜像。
2. **默认版本管理**:内置了各组件的稳定版本号,无需手动记忆。
3. **零配置**:只需一行代码即可完成整个生态链的模块映射。
---

32
dist/loader.js vendored
View File

@ -1,15 +1,15 @@
(function(exports) {
"use strict";
const DEFAULT_VERSIONS = {
"state": "1.0.11",
"bootstrap": "1.0.3",
"base": "1.0.7",
"datatable": "1.0.6",
"kanban": "1.0.0",
"mindmap": "1.0.0",
"chart": "1.0.0",
"editor": "1.0.0",
"loader": "1.0.2"
"state": "1.0.12",
"bootstrap": "1.0.4",
"base": "1.0.8",
"datatable": "1.0.7",
"kanban": "1.0.1",
"mindmap": "1.0.1",
"chart": "1.0.1",
"editor": "1.0.1",
"loader": "1.0.3"
};
const Loader = {
load: (...pkgs) => {
@ -32,17 +32,15 @@
}
const importMap = { imports: {} };
pkgs.forEach((pkg) => {
let [name, version] = pkg.split(":");
const colonIdx = pkg.indexOf(":");
const name = colonIdx === -1 ? pkg : pkg.slice(0, colonIdx);
let version = colonIdx === -1 ? "" : pkg.slice(colonIdx + 1);
const key = name.toLowerCase();
const fullKey = `@apigo.cc/${key}`;
let url = "";
try {
if (typeof localStorage !== "undefined") {
url = localStorage.getItem(`dev:${fullKey}`) || "";
}
} catch (e) {
}
if (!url) {
if (version && /^(https?:|\.|\/)/.test(version)) {
url = version;
} else {
version = version || DEFAULT_VERSIONS[key] || "latest";
url = tpl.replace(/{project}/g, key).replace(/{tag}/g, version);
}

2
dist/loader.min.js vendored
View File

@ -1 +1 @@
!function(t){"use strict";const e={state:"1.0.11",bootstrap:"1.0.3",base:"1.0.7",datatable:"1.0.6",kanban:"1.0.0",mindmap:"1.0.0",chart:"1.0.0",editor:"1.0.0",loader:"1.0.2"},c={load:(...t)=>{if("undefined"==typeof document)return;const c=document.currentScript,o=c?c.src:"";let n="https://cdn.jsdelivr.net/npm/@apigo.cc/{project}@{tag}/dist/{project}.min.js";o&&(o.includes("esm.sh")?n="https://esm.sh/@apigo.cc/{project}@{tag}/dist/{project}.min.js":o.includes("unpkg.com")?n="https://unpkg.com/@apigo.cc/{project}@{tag}/dist/{project}.min.js":o.includes("unpkg.zhimg.com")?n="https://unpkg.zhimg.com/@apigo.cc/{project}@{tag}/dist/{project}.min.js":o.includes("npm.elemecdn.com")?n="https://npm.elemecdn.com/@apigo.cc/{project}@{tag}/dist/{project}.min.js":o.includes("jsdelivr.net")&&(n="https://cdn.jsdelivr.net/npm/@apigo.cc/{project}@{tag}/dist/{project}.min.js"));const s={imports:{}};t.forEach(t=>{let[c,o]=t.split(":");const i=c.toLowerCase(),p=`@apigo.cc/${i}`;let a="";try{"undefined"!=typeof localStorage&&(a=localStorage.getItem(`dev:${p}`)||"")}catch(t){}a||(o=o||e[i]||"latest",a=n.replace(/{project}/g,i).replace(/{tag}/g,o)),s.imports[p]=a});const i=document.createElement("script");i.type="importmap",i.textContent=JSON.stringify(s),c?c.parentNode.insertBefore(i,c):document.head.appendChild(i)}};globalThis.Loader=c,t.Loader=c,t.default=c,Object.defineProperties(t,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})}(this.Loader=this.Loader||{});
!function(t){"use strict";const e={state:"1.0.12",bootstrap:"1.0.4",base:"1.0.8",datatable:"1.0.7",kanban:"1.0.1",mindmap:"1.0.1",chart:"1.0.1",editor:"1.0.1",loader:"1.0.3"},c={load:(...t)=>{if("undefined"==typeof document)return;const c=document.currentScript,o=c?c.src:"";let s="https://cdn.jsdelivr.net/npm/@apigo.cc/{project}@{tag}/dist/{project}.min.js";o&&(o.includes("esm.sh")?s="https://esm.sh/@apigo.cc/{project}@{tag}/dist/{project}.min.js":o.includes("unpkg.com")?s="https://unpkg.com/@apigo.cc/{project}@{tag}/dist/{project}.min.js":o.includes("unpkg.zhimg.com")?s="https://unpkg.zhimg.com/@apigo.cc/{project}@{tag}/dist/{project}.min.js":o.includes("npm.elemecdn.com")?s="https://npm.elemecdn.com/@apigo.cc/{project}@{tag}/dist/{project}.min.js":o.includes("jsdelivr.net")&&(s="https://cdn.jsdelivr.net/npm/@apigo.cc/{project}@{tag}/dist/{project}.min.js"));const n={imports:{}};t.forEach(t=>{const c=t.indexOf(":"),o=-1===c?t:t.slice(0,c);let i=-1===c?"":t.slice(c+1);const p=o.toLowerCase(),r=`@apigo.cc/${p}`;let a="";i&&/^(https?:|\.|\/)/.test(i)?a=i:(i=i||e[p]||"latest",a=s.replace(/{project}/g,p).replace(/{tag}/g,i)),n.imports[r]=a});const i=document.createElement("script");i.type="importmap",i.textContent=JSON.stringify(n),c?c.parentNode.insertBefore(i,c):document.head.appendChild(i)}};globalThis.Loader=c,t.Loader=c,t.default=c,Object.defineProperties(t,{__esModule:{value:!0},[Symbol.toStringTag]:{value:"Module"}})}(this.Loader=this.Loader||{});

64
package-lock.json generated
View File

@ -1,14 +1,15 @@
{
"name": "@apigo.cc/loader",
"version": "1.0.0",
"version": "1.0.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@apigo.cc/loader",
"version": "1.0.0",
"version": "1.0.2",
"license": "ISC",
"devDependencies": {
"@playwright/test": "^1.60.0",
"@rollup/plugin-terser": "^1.0.0",
"terser": "^5.48.0",
"vite": "^5.4.21"
@ -427,6 +428,21 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@playwright/test": {
"version": "1.60.0",
"resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.60.0.tgz",
"integrity": "sha512-O71yZIbAh/PxDMNGns37GHBIfrVkEVyn+AXyIa5dOTfb4/xNvRWV+Vv/NMbNCtODB/pO7vLlF2OTmMVLhmr7Ag==",
"dev": true,
"dependencies": {
"playwright": "1.60.0"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=18"
}
},
"node_modules/@rollup/plugin-terser": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-1.0.0.tgz",
@ -1153,6 +1169,50 @@
"integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
"dev": true
},
"node_modules/playwright": {
"version": "1.60.0",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.60.0.tgz",
"integrity": "sha512-hheHdokM8cdqCb0lcE3s+zT4t4W+vvjpGxsZlDnikarzx8tSzMebh3UiFtgqwFwnTnjYQcsyMF8ei2mCO/tpeA==",
"dev": true,
"dependencies": {
"playwright-core": "1.60.0"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"fsevents": "2.3.2"
}
},
"node_modules/playwright-core": {
"version": "1.60.0",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.60.0.tgz",
"integrity": "sha512-9bW6zvX/m0lEbgTKJ6YppOKx8H3VOPBMOCFh2irXFOT4BbHgrx5hPjwJYLT40Lu+4qtD36qKc/Hn56StUW57IA==",
"dev": true,
"bin": {
"playwright-core": "cli.js"
},
"engines": {
"node": ">=18"
}
},
"node_modules/playwright/node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"hasInstallScript": true,
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/postcss": {
"version": "8.5.15",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz",

View File

@ -1,6 +1,6 @@
{
"name": "@apigo.cc/loader",
"version": "1.0.2",
"version": "1.0.3",
"type": "module",
"main": "dist/loader.js",
"module": "dist/loader.js",
@ -11,6 +11,7 @@
"dev": "vite",
"prebuild": "node scripts/update-versions.js",
"build": "vite build",
"test": "playwright test",
"pub": "node scripts/publish.js"
},
"keywords": [],
@ -18,6 +19,7 @@
"license": "ISC",
"description": "",
"devDependencies": {
"@playwright/test": "^1.60.0",
"@rollup/plugin-terser": "^1.0.0",
"terser": "^5.48.0",
"vite": "^5.4.21"

View File

@ -78,24 +78,40 @@ if (fs.existsSync(readmePath)) {
readmeContent = readmeContent.replace(
/Loader\.load\(([^)]*)\)/g,
(match, p1) => {
// 提取被单引号/双引号包裹的各个 package 项
const items = p1.split(',').map(item => item.trim().replace(/['"]/g, ''));
// 按照逗号拆分,但保留各行原样(包含可能的换行和注释)
const items = p1.split(',');
const newItems = items.map(item => {
const [name] = item.split(':');
const trimmed = item.trim();
if (trimmed.startsWith('//') || !trimmed) {
return item;
}
// 提取清除引号后的内容进行路径和版本判断
const cleanItem = trimmed.replace(/['"]/g, '');
const colonIdx = cleanItem.indexOf(':');
const name = colonIdx === -1 ? cleanItem : cleanItem.slice(0, colonIdx);
const val = colonIdx === -1 ? '' : cleanItem.slice(colonIdx + 1);
// 如果值里包含 http://, https://, ./, ../, / 说明是调试重定向路径,原样保留
if (val && (/^(https?:|\.|\/)/.test(val))) {
return item;
}
const key = name.toLowerCase();
if (versions[key]) {
return `'${key}:${versions[key]}'`;
// 尽量保留原有的包裹引号格式
const quote = item.includes('"') ? '"' : "'";
return `${quote}${key}:${versions[key]}${quote}`;
}
return `'${item}'`;
return item;
});
return `Loader.load(${newItems.join(',')})`;
}
);
// 替换 cdn 链接里的 loader 版本
// 如 <script src="https://cdn.jsdelivr.net/npm/@apigo.cc/loader@1.0.0/dist/loader.min.js"></script>
// 替换 cdn 链接及所有地方的 loader 版本
readmeContent = readmeContent.replace(
/(\/\/cdn\.jsdelivr\.net\/npm\/@apigo\.cc\/loader@)[0-9.]+(\/dist\/loader\.min\.js)/g,
/(@apigo\.cc\/loader@)[0-9.]+(\/dist\/loader)/g,
`$1${loaderVersion}$2`
);

View File

@ -1,18 +1,18 @@
/**
* @web/loader
* @apigo.cc/loader
* 极简模块调度中心
*/
const DEFAULT_VERSIONS = {
'state': '1.0.11',
'bootstrap': '1.0.3',
'base': '1.0.7',
'datatable': '1.0.6',
'kanban': '1.0.0',
'mindmap': '1.0.0',
'chart': '1.0.0',
'editor': '1.0.0',
'loader': '1.0.2'
'state': '1.0.12',
'bootstrap': '1.0.4',
'base': '1.0.8',
'datatable': '1.0.7',
'kanban': '1.0.1',
'mindmap': '1.0.1',
'chart': '1.0.1',
'editor': '1.0.1',
'loader': '1.0.3'
};
const Loader = {
@ -43,19 +43,19 @@ const Loader = {
const importMap = { imports: {} };
pkgs.forEach(pkg => {
let [name, version] = pkg.split(':');
// 支持格式 'name' 或 'name:version' 或 'name:path' (如 'bootstrap:http://localhost:5173/src/index.js')
const colonIdx = pkg.indexOf(':');
const name = colonIdx === -1 ? pkg : pkg.slice(0, colonIdx);
let version = colonIdx === -1 ? '' : pkg.slice(colonIdx + 1);
const key = name.toLowerCase();
const fullKey = `@apigo.cc/${key}`;
let url = '';
// 优先检查本地 localStorage 重定向配置,方便开发调试
try {
if (typeof localStorage !== 'undefined') {
url = localStorage.getItem(`dev:${fullKey}`) || '';
}
} catch (e) {}
if (!url) {
// 如果 version 包含 http://, https://, ./, ../, / 说明是自定义路径,直接作为调试或重定向地址
if (version && (/^(https?:|\.|\/)/.test(version))) {
url = version;
} else {
version = version || DEFAULT_VERSIONS[key] || 'latest';
url = tpl.replace(/{project}/g, key).replace(/{tag}/g, version);
}