@lark-apaas/coding-templates 0.1.43-alpha.2 → 0.1.43-alpha.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/meta.json +8 -2
- package/package.json +1 -1
- package/{template-vite-react/.spark-project → template-html/.spark_project} +2 -3
- package/template-html/index.html +19 -20
- package/template-html/package.json +17 -2
- package/template-html/scripts/build.sh +28 -16
- package/{template-vite-react → template-html}/scripts/mock-health.cjs +1 -1
- package/template-html/src/main.js +38 -0
- package/template-html/src/styles.css +1 -0
- package/template-html/vite.config.ts +70 -0
- package/template-vite-react/.spark_project +12 -0
- package/template-vite-react/README.md +10 -10
- package/template-vite-react/components.json +1 -1
- package/template-vite-react/package.json +6 -12
- package/template-vite-react/scripts/build.sh +15 -3
- package/template-vite-react/scripts/dev.mjs +125 -0
- package/template-vite-react/{client/src → src}/index.tsx +9 -2
- package/template-vite-react/tsconfig.app.json +3 -3
- package/template-vite-react/tsconfig.json +1 -1
- package/template-vite-react/vite.config.ts +1 -54
- package/template-html/_gitignore +0 -2
- package/template-html/routes.json +0 -1
- /package/template-vite-react/.spark/{meta.json.example → meta.json} +0 -0
- /package/template-vite-react/{client/index.html → index.html} +0 -0
- /package/template-vite-react/{client/public → public}/favicon.svg +0 -0
- /package/template-vite-react/{client/public → public}/icons.svg +0 -0
- /package/template-vite-react/{client/src → src}/app.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/layout.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/accordion.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/alert-dialog.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/alert.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/aspect-ratio.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/avatar.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/badge.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/breadcrumb.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/button-group.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/button.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/calendar.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/card.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/carousel.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/chart.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/checkbox.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/collapsible.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/command.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/context-menu.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/dialog.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/drawer.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/dropdown-menu.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/empty.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/field.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/form.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/hover-card.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/image.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/input-group.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/input-otp.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/input.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/item.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/kbd.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/label.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/menubar.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/native-select.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/navigation-menu.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/pagination.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/popover.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/progress.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/radio-group.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/resizable.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/scroll-area.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/select.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/separator.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/sheet.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/sidebar.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/skeleton.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/slider.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/sonner.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/spinner.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/switch.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/table.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/tabs.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/textarea.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/toggle-group.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/toggle.tsx +0 -0
- /package/template-vite-react/{client/src → src}/components/ui/tooltip.tsx +0 -0
- /package/template-vite-react/{client/src → src}/hooks/use-mobile.ts +0 -0
- /package/template-vite-react/{client/src → src}/index.css +0 -0
- /package/template-vite-react/{client/src → src}/pages/NotFoundPage/NotFoundPage.tsx +0 -0
package/meta.json
CHANGED
|
@@ -1,10 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"templates": [
|
|
3
|
+
{
|
|
4
|
+
"name": "vite-react",
|
|
5
|
+
"dir": "template-vite-react",
|
|
6
|
+
"description": "Vite + React 19 + shadcn — jsPage 主推(HMR / Inspector / Capability / OpenTel)",
|
|
7
|
+
"features": ["typescript", "vite", "hmr", "tailwind", "shadcn-ui", "inspector", "capability", "observable"]
|
|
8
|
+
},
|
|
3
9
|
{
|
|
4
10
|
"name": "html",
|
|
5
11
|
"dir": "template-html",
|
|
6
|
-
"description": "纯 HTML —
|
|
7
|
-
"features": ["tailwind
|
|
12
|
+
"description": "纯 HTML — Vite dev + HMR + Tailwind v4 + Capability / OpenTel",
|
|
13
|
+
"features": ["vite", "hmr", "tailwind", "capability", "observable"]
|
|
8
14
|
},
|
|
9
15
|
{
|
|
10
16
|
"name": "nextjs-static",
|
package/package.json
CHANGED
|
@@ -7,12 +7,11 @@ build = ["npm", "run", "build"]
|
|
|
7
7
|
|
|
8
8
|
[files]
|
|
9
9
|
[files.restrict]
|
|
10
|
-
pathPatterns = ["package.json", ".
|
|
10
|
+
pathPatterns = ["package.json", ".spark_project", ".spark/meta.json", ".gitignore"]
|
|
11
11
|
[files.hidden]
|
|
12
12
|
pathPatterns = [".config", ".git", "scripts", "node_modules", "dist", ".spark", ".agent", ".agents", "tmp", ".spark_project", ".playwright-cli"]
|
|
13
13
|
|
|
14
14
|
[sandbox]
|
|
15
15
|
# 临时兼容老沙箱硬编码探测的 3000 / 8001 端口。
|
|
16
|
-
#
|
|
17
|
-
# 沙箱链路切到新协议后,可改回 [8080],并删掉 mock-health.cjs。
|
|
16
|
+
# 8001 由 vite dev server 真实占据;3000 由 scripts/mock-health.cjs 占位。
|
|
18
17
|
healthCheck = [8001, 3000]
|
package/template-html/index.html
CHANGED
|
@@ -1,23 +1,22 @@
|
|
|
1
1
|
<!doctype html>
|
|
2
2
|
<html lang="zh-CN">
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
</body>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>应用标题</title>
|
|
7
|
+
<link
|
|
8
|
+
href="https://lf3-static.bytednsdoc.com/obj/eden-cn/ylcylz_fsph_ryhs/ljhwZthlaukjlkulzlp/feisuda/feisuda.svg"
|
|
9
|
+
rel="shortcut icon"
|
|
10
|
+
/>
|
|
11
|
+
</head>
|
|
12
|
+
<body class="bg-gray-50">
|
|
13
|
+
<!-- 画布布局容器:业务页面内容放这里 -->
|
|
14
|
+
<main id="canvas" class="mx-auto max-w-7xl p-6">
|
|
15
|
+
<h1 class="text-2xl font-semibold">Hello HTML 模板</h1>
|
|
16
|
+
<p class="mt-2 text-gray-600">编辑 <code>index.html</code> 开始你的应用。</p>
|
|
17
|
+
</main>
|
|
18
|
+
|
|
19
|
+
<!-- 入口模块:装载平台 SDK(capability / observable / ...) + 样式 -->
|
|
20
|
+
<script type="module" src="/src/main.js"></script>
|
|
21
|
+
</body>
|
|
23
22
|
</html>
|
|
@@ -5,9 +5,24 @@
|
|
|
5
5
|
"mclaw": {
|
|
6
6
|
"stack": "html",
|
|
7
7
|
"stackVersion": "0.1.0",
|
|
8
|
-
"features": [
|
|
8
|
+
"features": [
|
|
9
|
+
"client"
|
|
10
|
+
]
|
|
9
11
|
},
|
|
10
12
|
"scripts": {
|
|
11
|
-
"
|
|
13
|
+
"dev": "concurrently -k -n vite,health 'vite --port 8001 --host 0.0.0.0' 'node scripts/mock-health.cjs'",
|
|
14
|
+
"build": "bash scripts/build.sh",
|
|
15
|
+
"lint": "echo 'no lint for plain HTML'"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@lark-apaas/client-toolkit-lite": "1.1.7-alpha.6",
|
|
19
|
+
"@lark-apaas/observable-web": "^1.0.6"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"@lark-apaas/coding-vite-preset": "1.0.7-alpha.2",
|
|
23
|
+
"@tailwindcss/vite": "^4.0.0",
|
|
24
|
+
"concurrently": "^9",
|
|
25
|
+
"tailwindcss": "^4.0.0",
|
|
26
|
+
"vite": "^8"
|
|
12
27
|
}
|
|
13
28
|
}
|
|
@@ -3,29 +3,41 @@ set -e
|
|
|
3
3
|
|
|
4
4
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
5
5
|
OUTPUT="$ROOT/dist/output"
|
|
6
|
+
OUTPUT_RESOURCE="$ROOT/dist/output_resource"
|
|
6
7
|
OUTPUT_STATIC="$ROOT/dist/output_static"
|
|
7
|
-
STATIC_PREFIX="${MCLAW_STATIC_PREFIX:-}"
|
|
8
8
|
|
|
9
|
-
#
|
|
9
|
+
# 映射平台环境变量到 preset 期望的变量名(同 template-vite-react)
|
|
10
|
+
export CLIENT_BASE_PATH="${MCLAW_APP_ID:+/app/$MCLAW_APP_ID}"
|
|
11
|
+
export ASSETS_CDN_PATH="${MCLAW_CDN_DOMAIN:-/}"
|
|
12
|
+
export NODE_ENV="${NODE_ENV:-production}"
|
|
13
|
+
|
|
10
14
|
rm -rf "$ROOT/dist"
|
|
15
|
+
|
|
16
|
+
# 1. Vite 构建 → dist/client/
|
|
17
|
+
npx vite build --outDir "$ROOT/dist/client" --emptyOutDir
|
|
18
|
+
|
|
19
|
+
# 2. HTML → dist/output/
|
|
11
20
|
mkdir -p "$OUTPUT"
|
|
21
|
+
find "$ROOT/dist/client" -maxdepth 1 -name '*.html' -exec cp {} "$OUTPUT/" \;
|
|
12
22
|
|
|
13
|
-
#
|
|
14
|
-
|
|
15
|
-
find . -name '*.html' -not -path './dist/*' -not -path './node_modules/*' | while read -r file; do
|
|
16
|
-
dir=$(dirname "$file")
|
|
17
|
-
mkdir -p "$OUTPUT/$dir"
|
|
18
|
-
sed "s|\\\$STATIC\\\$|${STATIC_PREFIX}|g" "$file" > "$OUTPUT/$file"
|
|
19
|
-
done
|
|
23
|
+
# 生成 routes.json(运行态 PageServer 按 type 区分渲染策略)
|
|
24
|
+
echo '{ "version": 1, "type": "html", "fallback": "index.html" }' > "$OUTPUT/routes.json"
|
|
20
25
|
|
|
21
|
-
#
|
|
22
|
-
|
|
26
|
+
# 3. assets/ → dist/output_resource/(JS/CSS/字体上传到 CDN)
|
|
27
|
+
if [ -d "$ROOT/dist/client/assets" ]; then
|
|
28
|
+
mkdir -p "$OUTPUT_RESOURCE"
|
|
29
|
+
cp -r "$ROOT/dist/client/assets" "$OUTPUT_RESOURCE/"
|
|
30
|
+
fi
|
|
23
31
|
|
|
24
|
-
#
|
|
25
|
-
if [ -d "$ROOT/static" ]; then
|
|
26
|
-
|
|
32
|
+
# 4. 私有静态资源 → dist/output_static/(排除代码文件)
|
|
33
|
+
if [ -d "$ROOT/shared/static" ]; then
|
|
34
|
+
mkdir -p "$OUTPUT_STATIC"
|
|
35
|
+
rsync -a --exclude='*.ts' --exclude='*.tsx' --exclude='*.js' --exclude='*.jsx' "$ROOT/shared/static/" "$OUTPUT_STATIC/"
|
|
27
36
|
fi
|
|
28
37
|
|
|
38
|
+
rm -rf "$ROOT/dist/client"
|
|
39
|
+
|
|
29
40
|
echo "Build complete"
|
|
30
|
-
echo " HTML
|
|
31
|
-
[ -d "$
|
|
41
|
+
echo " HTML → dist/output/"
|
|
42
|
+
[ -d "$OUTPUT_RESOURCE" ] && echo " Resource → dist/output_resource/" || true
|
|
43
|
+
[ -d "$OUTPUT_STATIC" ] && echo " Static → dist/output_static/" || true
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
// 极薄入口:装平台 SDK。
|
|
2
|
+
// 业务代码写在 index.html 的 <script> 里,或者再 import './your-app.js'。
|
|
3
|
+
|
|
4
|
+
import './styles.css';
|
|
5
|
+
|
|
6
|
+
// 1) capability — 调插件能力
|
|
7
|
+
import { capabilityClient } from '@lark-apaas/client-toolkit-lite';
|
|
8
|
+
// 暴露到 window 让 index.html 内联 <script> 也能用
|
|
9
|
+
window.capability = capabilityClient;
|
|
10
|
+
|
|
11
|
+
// 2) observable — OpenTelemetry traces/logs/metrics
|
|
12
|
+
// lite 里的 initObservable() 绑了 React useEffect,纯 HTML 用不了;inline 一份等效逻辑。
|
|
13
|
+
import { observable, AppEnv } from '@lark-apaas/observable-web';
|
|
14
|
+
try {
|
|
15
|
+
const appId = window.appId;
|
|
16
|
+
const isNewPath =
|
|
17
|
+
typeof window.location !== 'undefined' &&
|
|
18
|
+
/^\/app\/[^/]/.test(window.location.pathname);
|
|
19
|
+
observable.start({
|
|
20
|
+
serviceName: 'app',
|
|
21
|
+
env: process.env.NODE_ENV === 'development' ? AppEnv.Dev : AppEnv.Prod,
|
|
22
|
+
collectorUrl: isNewPath
|
|
23
|
+
? {
|
|
24
|
+
log: `/app/${appId}/__runtime__/api/v1/observability/logs/collect`,
|
|
25
|
+
trace: `/app/${appId}/__runtime__/api/v1/observability/traces/collect`,
|
|
26
|
+
metric: `/app/${appId}/__runtime__/api/v1/observability/metrics/collect`,
|
|
27
|
+
time: `/app/${appId}/__runtime__/api/v1/observability/current_server_timestamp`,
|
|
28
|
+
}
|
|
29
|
+
: {
|
|
30
|
+
log: `/spark/app/${appId}/runtime/api/v1/observability/logs/collect`,
|
|
31
|
+
trace: `/spark/app/${appId}/runtime/api/v1/observability/traces/collect`,
|
|
32
|
+
metric: `/spark/app/${appId}/runtime/api/v1/observability/metrics/collect`,
|
|
33
|
+
time: `/spark/api/v1/observability/app/${appId}/current_server_timestamp`,
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
} catch (error) {
|
|
37
|
+
console.error('Failed to start WebObservableSdk:', error);
|
|
38
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import path from 'path'
|
|
2
|
+
import type { IncomingMessage, ServerResponse } from 'http'
|
|
3
|
+
import { defineConfig } from '@lark-apaas/coding-vite-preset'
|
|
4
|
+
import type { Plugin, ViteDevServer } from 'vite'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* dev 模式 base-path normalize 中间件(同 template-vite-react)
|
|
8
|
+
*
|
|
9
|
+
* 沙箱预览网关把流量原样转给 vite(保留 /app/:appId 前缀)。但浏览器首次访问的
|
|
10
|
+
* URL 是 `/app/app_xxx?sso_token=...`(pathname 缺末尾 /)。
|
|
11
|
+
*
|
|
12
|
+
* vite 8 的 baseMiddleware 对"缺末尾 /"的请求不再像 vite 5 那样自动 301,
|
|
13
|
+
* 而是直接返回 404 警告页 "The server is configured with a public base URL..."。
|
|
14
|
+
*
|
|
15
|
+
* 这里做 internal rewrite(不发 301,避免跟沙箱网关形成 round-trip 死循环):
|
|
16
|
+
* pathname === base 去尾 / → 改写 req.url 加上尾 /,下游继续 strip → SPA fallback。
|
|
17
|
+
*
|
|
18
|
+
* 必须 stack.unshift 而非 server.middlewares.use,否则排到内置 baseMiddleware
|
|
19
|
+
* 之后失效。
|
|
20
|
+
*
|
|
21
|
+
* TODO: coding-vite-preset 新版本已含同等逻辑,待依赖普及后可删此 plugin。
|
|
22
|
+
*/
|
|
23
|
+
const baseNormalizePlugin: Plugin = {
|
|
24
|
+
name: 'base-path-normalize',
|
|
25
|
+
configureServer(server: ViteDevServer) {
|
|
26
|
+
const base = (process.env.CLIENT_BASE_PATH || '').replace(/\/+$/, '')
|
|
27
|
+
if (!base) return
|
|
28
|
+
const stack = (
|
|
29
|
+
server.middlewares as unknown as {
|
|
30
|
+
stack: Array<{ route: string; handle: unknown }>
|
|
31
|
+
}
|
|
32
|
+
).stack
|
|
33
|
+
if (!Array.isArray(stack)) return
|
|
34
|
+
stack.unshift({
|
|
35
|
+
route: '',
|
|
36
|
+
handle: (req: IncomingMessage, _res: ServerResponse, next: () => void) => {
|
|
37
|
+
const url = req.url || ''
|
|
38
|
+
const qIdx = url.indexOf('?')
|
|
39
|
+
const pathname = qIdx >= 0 ? url.slice(0, qIdx) : url
|
|
40
|
+
const search = qIdx >= 0 ? url.slice(qIdx) : ''
|
|
41
|
+
if (pathname === base) {
|
|
42
|
+
req.url = base + '/' + search
|
|
43
|
+
}
|
|
44
|
+
next()
|
|
45
|
+
},
|
|
46
|
+
})
|
|
47
|
+
},
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export default defineConfig({
|
|
51
|
+
// 模板根目录就是 index.html 所在位置(跟 vite-react 不同,没有 client/ 子目录)
|
|
52
|
+
root: path.resolve(__dirname),
|
|
53
|
+
resolve: {
|
|
54
|
+
alias: {
|
|
55
|
+
'@shared': path.resolve(__dirname, 'shared'),
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
build: {
|
|
59
|
+
// coding-vite-preset 默认把 entry 设为 client/index.html(vite-react 用的布局),
|
|
60
|
+
// HTML 模板没有 client/ 子目录,override 成根目录的 index.html。
|
|
61
|
+
rollupOptions: {
|
|
62
|
+
input: path.resolve(__dirname, 'index.html'),
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
// 同步告诉 Vite dev 优化器 entry 在哪,避免"failed to resolve rolldownOptions.input"警告。
|
|
66
|
+
optimizeDeps: {
|
|
67
|
+
entries: [path.resolve(__dirname, 'index.html')],
|
|
68
|
+
},
|
|
69
|
+
plugins: [baseNormalizePlugin],
|
|
70
|
+
})
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
run = ["npm", "run", "dev"]
|
|
2
|
+
lint = ["npm", "run", "lint"]
|
|
3
|
+
hidden = [".config", ".git", "scripts", "node_modules", "dist", ".spark", ".agent", ".agents", "tmp", ".spark_project", ".playwright-cli"]
|
|
4
|
+
|
|
5
|
+
[deployment]
|
|
6
|
+
build = ["npm", "run", "build"]
|
|
7
|
+
|
|
8
|
+
[files]
|
|
9
|
+
[files.restrict]
|
|
10
|
+
pathPatterns = ["package.json", ".spark_project", ".spark/meta.json", ".gitignore"]
|
|
11
|
+
[files.hidden]
|
|
12
|
+
pathPatterns = [".config", ".git", "scripts", "node_modules", "dist", ".spark", ".agent", ".agents", "tmp", ".spark_project", ".playwright-cli"]
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
## 目录结构
|
|
16
16
|
|
|
17
17
|
```
|
|
18
|
-
|
|
18
|
+
src/
|
|
19
19
|
├── index.tsx # 入口(勿修改)
|
|
20
20
|
├── app.tsx # 路由配置(仅在 <Routes> 内增删 <Route>)
|
|
21
21
|
├── index.css # 全局样式 + 主题变量
|
|
@@ -51,8 +51,8 @@ shared/
|
|
|
51
51
|
|
|
52
52
|
| 文件 | 原因 |
|
|
53
53
|
|------|------|
|
|
54
|
-
| `
|
|
55
|
-
| `
|
|
54
|
+
| `src/index.tsx` | Provider 层级 + 样式引入,由模板管理 |
|
|
55
|
+
| `src/components/ui/*` | shadcn/ui 内置组件,版本锁定 |
|
|
56
56
|
|
|
57
57
|
---
|
|
58
58
|
|
|
@@ -60,10 +60,10 @@ shared/
|
|
|
60
60
|
|
|
61
61
|
| 内容类型 | 放置位置 |
|
|
62
62
|
|---------|---------|
|
|
63
|
-
| 新页面 | `
|
|
64
|
-
| 页面专属组件 | `
|
|
65
|
-
| 自定义 Hooks | `
|
|
66
|
-
| 工具函数 | `
|
|
63
|
+
| 新页面 | `src/pages/<PageName>/PageName.tsx` |
|
|
64
|
+
| 页面专属组件 | `src/pages/<PageName>/components/` |
|
|
65
|
+
| 自定义 Hooks | `src/hooks/` |
|
|
66
|
+
| 工具函数 | `src/lib/` |
|
|
67
67
|
| 静态数据文件 | `shared/static/data/` |
|
|
68
68
|
| 静态图片 | `shared/static/images/` |
|
|
69
69
|
|
|
@@ -72,7 +72,7 @@ shared/
|
|
|
72
72
|
## 导入路径
|
|
73
73
|
|
|
74
74
|
```typescript
|
|
75
|
-
// @/ 别名 →
|
|
75
|
+
// @/ 别名 → src/
|
|
76
76
|
import { cn } from "@/lib/utils";
|
|
77
77
|
import { useIsMobile } from "@/hooks/use-mobile";
|
|
78
78
|
|
|
@@ -85,14 +85,14 @@ import configData from "@shared/static/config.json";
|
|
|
85
85
|
|
|
86
86
|
## 路由配置
|
|
87
87
|
|
|
88
|
-
- 新增页面需在 `
|
|
88
|
+
- 新增页面需在 `src/app.tsx` 的 `<Routes>` 内注册 `<Route>`
|
|
89
89
|
- `BrowserRouter` 已在 `index.tsx` 中配置,`app.tsx` 中**禁止**再包裹 Router
|
|
90
90
|
|
|
91
91
|
---
|
|
92
92
|
|
|
93
93
|
## 主题变量
|
|
94
94
|
|
|
95
|
-
主题色定义在 `
|
|
95
|
+
主题色定义在 `src/index.css`,通过 `:root` CSS 变量 + `@theme inline` 注册到 Tailwind。
|
|
96
96
|
|
|
97
97
|
| 用途 | Tailwind 类 | CSS 变量 |
|
|
98
98
|
|------|------------|----------|
|
|
@@ -1,20 +1,13 @@
|
|
|
1
1
|
{
|
|
2
|
-
"name": "
|
|
2
|
+
"name": "miaoda-vite-react-app",
|
|
3
3
|
"version": "0.1.0",
|
|
4
4
|
"private": true,
|
|
5
|
-
"mclaw": {
|
|
6
|
-
"stack": "vite-react",
|
|
7
|
-
"stackVersion": "0.1.0",
|
|
8
|
-
"features": [
|
|
9
|
-
"client"
|
|
10
|
-
]
|
|
11
|
-
},
|
|
12
5
|
"scripts": {
|
|
13
|
-
"dev": "
|
|
6
|
+
"dev": "node scripts/dev.mjs",
|
|
14
7
|
"build": "bash scripts/build.sh",
|
|
15
8
|
"typecheck": "tsc -p tsconfig.app.json",
|
|
16
9
|
"lint": "concurrently npm:typecheck npm:lint:eslint",
|
|
17
|
-
"lint:eslint": "eslint
|
|
10
|
+
"lint:eslint": "eslint src"
|
|
18
11
|
},
|
|
19
12
|
"dependencies": {
|
|
20
13
|
"@hookform/resolvers": "^5.2.2",
|
|
@@ -44,7 +37,7 @@
|
|
|
44
37
|
"@radix-ui/react-toggle": "^1.1.9",
|
|
45
38
|
"@radix-ui/react-toggle-group": "^1.1.10",
|
|
46
39
|
"@radix-ui/react-tooltip": "^1.1.18",
|
|
47
|
-
"@lark-apaas/client-toolkit-lite": "
|
|
40
|
+
"@lark-apaas/client-toolkit-lite": "1.1.7-alpha.23",
|
|
48
41
|
"@formkit/auto-animate": "^0.9.0",
|
|
49
42
|
"@gsap/react": "^2.1.2",
|
|
50
43
|
"framer-motion": "^12.38.0",
|
|
@@ -62,6 +55,7 @@
|
|
|
62
55
|
"react": "^19.2.4",
|
|
63
56
|
"react-day-picker": "^9.14.0",
|
|
64
57
|
"react-dom": "^19.2.4",
|
|
58
|
+
"react-error-boundary": "^6.0.0",
|
|
65
59
|
"react-hook-form": "^7.72.0",
|
|
66
60
|
"react-resizable-panels": "^4.7.6",
|
|
67
61
|
"react-router-dom": "^7.13.2",
|
|
@@ -79,7 +73,7 @@
|
|
|
79
73
|
"@types/react": "^19",
|
|
80
74
|
"@types/react-dom": "^19",
|
|
81
75
|
"@lark-apaas/coding-presets": "^1.0.0",
|
|
82
|
-
"@lark-apaas/coding-vite-preset": "
|
|
76
|
+
"@lark-apaas/coding-vite-preset": "1.0.7-alpha.21",
|
|
83
77
|
"concurrently": "^9",
|
|
84
78
|
"eslint": "^9",
|
|
85
79
|
"eslint-plugin-react-hooks": "^7",
|
|
@@ -36,10 +36,22 @@ if [ -d "$ROOT/shared/static" ]; then
|
|
|
36
36
|
rsync -a --exclude='*.ts' --exclude='*.tsx' --exclude='*.js' --exclude='*.jsx' "$ROOT/shared/static/" "$OUTPUT_STATIC/"
|
|
37
37
|
fi
|
|
38
38
|
|
|
39
|
+
# 5. capability 配置 → dist/output_capabilities/(老路径优先)
|
|
40
|
+
if [ -d "$ROOT/server/capabilities" ]; then
|
|
41
|
+
CAPS_SRC="$ROOT/server/capabilities"
|
|
42
|
+
elif [ -d "$ROOT/shared/capabilities" ]; then
|
|
43
|
+
CAPS_SRC="$ROOT/shared/capabilities"
|
|
44
|
+
fi
|
|
45
|
+
if [ -n "${CAPS_SRC:-}" ]; then
|
|
46
|
+
mkdir -p "$ROOT/dist/output_capabilities"
|
|
47
|
+
cp -r "$CAPS_SRC/." "$ROOT/dist/output_capabilities/"
|
|
48
|
+
fi
|
|
49
|
+
|
|
39
50
|
# 清理中间产物
|
|
40
51
|
rm -rf "$ROOT/dist/client"
|
|
41
52
|
|
|
42
53
|
echo "Build complete"
|
|
43
|
-
echo " HTML
|
|
44
|
-
[ -d "$OUTPUT_RESOURCE" ] && echo " Resource
|
|
45
|
-
[ -d "$OUTPUT_STATIC" ] && echo " Static
|
|
54
|
+
echo " HTML → dist/output/"
|
|
55
|
+
[ -d "$OUTPUT_RESOURCE" ] && echo " Resource → dist/output_resource/" || true
|
|
56
|
+
[ -d "$OUTPUT_STATIC" ] && echo " Static → dist/output_static/" || true
|
|
57
|
+
[ -d "$ROOT/dist/output_capabilities" ] && echo " Capabilities → dist/output_capabilities/" || true
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Dev launcher: 启动 vite dev server,把 stdout/stderr 加时间戳前缀
|
|
4
|
+
* 写到 logs/<name>.std.log + 当前 stdout;负责子进程组管理与端口孤儿清理。
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import fs from 'node:fs';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import readline from 'node:readline';
|
|
10
|
+
import { spawn, execSync } from 'node:child_process';
|
|
11
|
+
import { fileURLToPath } from 'node:url';
|
|
12
|
+
|
|
13
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
const ROOT = path.resolve(__dirname, '..');
|
|
15
|
+
process.chdir(ROOT);
|
|
16
|
+
|
|
17
|
+
const LOG_DIR = process.env.LOG_DIR || 'logs';
|
|
18
|
+
const CLIENT_DEV_PORT = process.env.CLIENT_DEV_PORT || '8001';
|
|
19
|
+
|
|
20
|
+
fs.mkdirSync(LOG_DIR, { recursive: true });
|
|
21
|
+
|
|
22
|
+
function timestamp() {
|
|
23
|
+
const now = new Date();
|
|
24
|
+
const pad = (n) => String(n).padStart(2, '0');
|
|
25
|
+
return (
|
|
26
|
+
`${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ` +
|
|
27
|
+
`${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function log(msg) {
|
|
32
|
+
const line = `[${timestamp()}] [dev] ${msg}\n`;
|
|
33
|
+
try { process.stdout.write(line); } catch {}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** 清理端口占用:lsof -ti:<port> | kill -9 */
|
|
37
|
+
function killOrphansByPort(port) {
|
|
38
|
+
try {
|
|
39
|
+
const out = execSync(`lsof -ti:${port}`, { stdio: ['ignore', 'pipe', 'ignore'] })
|
|
40
|
+
.toString()
|
|
41
|
+
.trim();
|
|
42
|
+
if (!out) return [];
|
|
43
|
+
const pids = out.split('\n').filter(Boolean);
|
|
44
|
+
for (const pid of pids) {
|
|
45
|
+
try {
|
|
46
|
+
process.kill(Number(pid), 'SIGKILL');
|
|
47
|
+
log(`killed orphan pid=${pid} on :${port}`);
|
|
48
|
+
} catch {}
|
|
49
|
+
}
|
|
50
|
+
return pids;
|
|
51
|
+
} catch {
|
|
52
|
+
return [];
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const managed = [];
|
|
57
|
+
|
|
58
|
+
function startProcess({ name, command, args, logFileName }) {
|
|
59
|
+
const logFd = logFileName
|
|
60
|
+
? fs.openSync(path.join(LOG_DIR, logFileName), 'a')
|
|
61
|
+
: null;
|
|
62
|
+
|
|
63
|
+
const child = spawn(command, args, {
|
|
64
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
65
|
+
shell: false,
|
|
66
|
+
cwd: ROOT,
|
|
67
|
+
env: process.env,
|
|
68
|
+
detached: true,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const pipeLines = (stream) => {
|
|
72
|
+
const rl = readline.createInterface({ input: stream, crlfDelay: Infinity });
|
|
73
|
+
rl.on('line', (line) => {
|
|
74
|
+
const msg = `[${timestamp()}] [${name}] ${line}\n`;
|
|
75
|
+
try { process.stdout.write(msg); } catch {}
|
|
76
|
+
if (logFd != null) {
|
|
77
|
+
try { fs.writeSync(logFd, msg); } catch {}
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
};
|
|
81
|
+
pipeLines(child.stdout);
|
|
82
|
+
pipeLines(child.stderr);
|
|
83
|
+
|
|
84
|
+
managed.push({ name, child });
|
|
85
|
+
return child;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
killOrphansByPort(CLIENT_DEV_PORT);
|
|
89
|
+
|
|
90
|
+
startProcess({
|
|
91
|
+
name: 'client',
|
|
92
|
+
command: 'npx',
|
|
93
|
+
args: ['vite', '--port', CLIENT_DEV_PORT, '--host', '0.0.0.0'],
|
|
94
|
+
logFileName: 'client.std.log',
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
let stopping = false;
|
|
98
|
+
function cleanup(signal) {
|
|
99
|
+
if (stopping) return;
|
|
100
|
+
stopping = true;
|
|
101
|
+
log(`cleanup triggered by ${signal}`);
|
|
102
|
+
|
|
103
|
+
for (const { child } of managed) {
|
|
104
|
+
if (!child.pid) continue;
|
|
105
|
+
try { process.kill(-child.pid, signal || 'SIGTERM'); } catch {}
|
|
106
|
+
}
|
|
107
|
+
setTimeout(() => {
|
|
108
|
+
for (const { child } of managed) {
|
|
109
|
+
if (!child.pid) continue;
|
|
110
|
+
try { process.kill(-child.pid, 'SIGKILL'); } catch {}
|
|
111
|
+
}
|
|
112
|
+
killOrphansByPort(CLIENT_DEV_PORT);
|
|
113
|
+
process.exit(0);
|
|
114
|
+
}, 2000);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
process.on('SIGINT', () => cleanup('SIGTERM'));
|
|
118
|
+
process.on('SIGTERM', () => cleanup('SIGTERM'));
|
|
119
|
+
// pkill 杀父 npm 后会收 SIGHUP(controlling tty 关闭);
|
|
120
|
+
// Node 默认直接退出不跑 handler,注册 handler 触发 cleanup
|
|
121
|
+
process.on('SIGHUP', () => cleanup('SIGTERM'));
|
|
122
|
+
|
|
123
|
+
Promise.race(
|
|
124
|
+
managed.map(({ child }) => new Promise((r) => child.on('exit', r))),
|
|
125
|
+
).then(() => cleanup('SIGTERM'));
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { StrictMode } from "react";
|
|
2
2
|
import { createRoot } from "react-dom/client";
|
|
3
3
|
import { BrowserRouter } from "react-router-dom";
|
|
4
|
-
import {
|
|
4
|
+
import { ErrorBoundary } from "react-error-boundary";
|
|
5
|
+
import { AppContainer, ErrorRender } from "@lark-apaas/client-toolkit-lite";
|
|
5
6
|
import App from "./app";
|
|
6
7
|
import "./index.css";
|
|
7
8
|
|
|
@@ -9,7 +10,13 @@ createRoot(document.getElementById("root")!).render(
|
|
|
9
10
|
<StrictMode>
|
|
10
11
|
<BrowserRouter basename={process.env.CLIENT_BASE_PATH || "/"}>
|
|
11
12
|
<AppContainer>
|
|
12
|
-
<
|
|
13
|
+
<ErrorBoundary
|
|
14
|
+
fallbackRender={({ error, resetErrorBoundary }) => (
|
|
15
|
+
<ErrorRender error={error} resetErrorBoundary={resetErrorBoundary} />
|
|
16
|
+
)}
|
|
17
|
+
>
|
|
18
|
+
<App />
|
|
19
|
+
</ErrorBoundary>
|
|
13
20
|
</AppContainer>
|
|
14
21
|
</BrowserRouter>
|
|
15
22
|
</StrictMode>,
|
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
|
5
5
|
"baseUrl": ".",
|
|
6
6
|
"paths": {
|
|
7
|
-
"@/*": ["./
|
|
7
|
+
"@/*": ["./src/*"],
|
|
8
8
|
"@shared/*": ["./shared/*"]
|
|
9
9
|
}
|
|
10
10
|
},
|
|
11
|
-
"include": ["
|
|
12
|
-
"exclude": ["
|
|
11
|
+
"include": ["src", "shared"],
|
|
12
|
+
"exclude": ["src/components/ui"]
|
|
13
13
|
}
|
|
@@ -1,64 +1,11 @@
|
|
|
1
1
|
import path from 'path'
|
|
2
|
-
import type { IncomingMessage, ServerResponse } from 'http'
|
|
3
2
|
import { defineConfig } from '@lark-apaas/coding-vite-preset'
|
|
4
|
-
import type { Plugin, ViteDevServer } from 'vite'
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* dev 模式 base-path normalize 中间件
|
|
8
|
-
*
|
|
9
|
-
* 沙箱预览网关把流量原样转给 vite(保留 /app/:appId 前缀)。浏览器首次访问的
|
|
10
|
-
* URL 形如 `/app/app_xxx?sso_token=...`,pathname 缺末尾 /。
|
|
11
|
-
*
|
|
12
|
-
* vite 8 的 baseMiddleware 对"缺末尾 /"的请求不再像 vite 5 那样自动 301,
|
|
13
|
-
* 而是直接返回 404 警告页 "The server is configured with a public base URL..."。
|
|
14
|
-
*
|
|
15
|
-
* 这里做 internal rewrite(不发 301,避免跟沙箱网关形成 round-trip 死循环):
|
|
16
|
-
* 在 vite 内置 baseMiddleware **之前** unshift 一个中间件,把 req.url 改写
|
|
17
|
-
* 成带尾 /,下游 baseMiddleware 继续 strip base → SPA fallback。
|
|
18
|
-
* 浏览器全程不感知 redirect,地址栏可保留缺尾 / 形态(BrowserRouter basename
|
|
19
|
-
* 处理路由)。
|
|
20
|
-
*
|
|
21
|
-
* 必须 stack.unshift 而非 server.middlewares.use(...),否则会排到内置
|
|
22
|
-
* baseMiddleware 之后失效。
|
|
23
|
-
*
|
|
24
|
-
* TODO: 这段已同步到 @lark-apaas/coding-vite-preset,待该包发布并被本模板
|
|
25
|
-
* 依赖升级拉到后,可以删除此 plugin。保留是为了让现存沙箱(用着老 preset
|
|
26
|
-
* 的应用)也能立即生效。
|
|
27
|
-
*/
|
|
28
|
-
const baseNormalizePlugin: Plugin = {
|
|
29
|
-
name: 'base-path-normalize',
|
|
30
|
-
configureServer(server: ViteDevServer) {
|
|
31
|
-
const base = (process.env.CLIENT_BASE_PATH || '').replace(/\/+$/, '')
|
|
32
|
-
if (!base) return
|
|
33
|
-
const stack = (
|
|
34
|
-
server.middlewares as unknown as {
|
|
35
|
-
stack: Array<{ route: string; handle: unknown }>
|
|
36
|
-
}
|
|
37
|
-
).stack
|
|
38
|
-
if (!Array.isArray(stack)) return
|
|
39
|
-
stack.unshift({
|
|
40
|
-
route: '',
|
|
41
|
-
handle: (req: IncomingMessage, _res: ServerResponse, next: () => void) => {
|
|
42
|
-
const url = req.url || ''
|
|
43
|
-
const qIdx = url.indexOf('?')
|
|
44
|
-
const pathname = qIdx >= 0 ? url.slice(0, qIdx) : url
|
|
45
|
-
const search = qIdx >= 0 ? url.slice(qIdx) : ''
|
|
46
|
-
if (pathname === base) {
|
|
47
|
-
req.url = base + '/' + search
|
|
48
|
-
}
|
|
49
|
-
next()
|
|
50
|
-
},
|
|
51
|
-
})
|
|
52
|
-
},
|
|
53
|
-
}
|
|
54
3
|
|
|
55
4
|
export default defineConfig({
|
|
56
|
-
root: path.resolve(__dirname, 'client'),
|
|
57
5
|
resolve: {
|
|
58
6
|
alias: {
|
|
59
|
-
'@': path.resolve(__dirname, '
|
|
7
|
+
'@': path.resolve(__dirname, 'src'),
|
|
60
8
|
'@shared': path.resolve(__dirname, 'shared'),
|
|
61
9
|
},
|
|
62
10
|
},
|
|
63
|
-
plugins: [baseNormalizePlugin],
|
|
64
11
|
})
|
package/template-html/_gitignore
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{ "version": 1, "type": "html", "fallback": "index.html" }
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|