chore: sync
This commit is contained in:
1 parent
8b1ec67609
commit
ac65b75c73
6 files changed
+179
-10
No files matched your search
@@ -0,0 +1,113 @@
|
|||||||
|
# CODEBUDDY.md
|
||||||
|
|
||||||
|
This file provides guidance to CodeBuddy Code when working with code in this repository.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
G3Soft frontend infrastructure — a pnpm monorepo holding a self-built **Vue 3 component library** plus a **Docus documentation site**.
|
||||||
|
|
||||||
|
- `packages/ui` (`@g3soft/ui`) — component library, published to a private npm registry. One directory per component under `src/<name>/`.
|
||||||
|
- `packages/tokens` (`@g3soft/tokens`) — design tokens as plain CSS (`--g3-*` variables), shared by the library and the docs site.
|
||||||
|
- `docs` (`@g3soft/docs`) — Docus/Nuxt docs site. `private: true`, never published.
|
||||||
|
- `scripts/gen-api.mjs` — generates component API data from source.
|
||||||
|
|
||||||
|
Requires Node >= 20.19 and pnpm 11 (see `packageManager` in root `package.json`).
|
||||||
|
|
||||||
|
## Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm install
|
||||||
|
|
||||||
|
pnpm dev # start docs site (http://localhost:3000); live-reloads packages/ui via alias
|
||||||
|
pnpm build # build all packages under packages/** (component lib -> dist)
|
||||||
|
pnpm build:docs # build docs static site -> docs/.output/public
|
||||||
|
pnpm preview:docs # preview the built docs site
|
||||||
|
|
||||||
|
pnpm gen:api # regenerate docs/data/api/*.json from component source (REQUIRED after changing props/events/slots)
|
||||||
|
```
|
||||||
|
|
||||||
|
Deploying docs to a subpath:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
NUXT_APP_BASE_URL=/docs/ pnpm build:docs
|
||||||
|
```
|
||||||
|
|
||||||
|
Releasing the component library (changesets):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm changeset # record the change
|
||||||
|
pnpm version # bump versions + generate CHANGELOG
|
||||||
|
pnpm release # build + publish to the private registry
|
||||||
|
```
|
||||||
|
|
||||||
|
`packages/ui` runs `publint` via `prepublishOnly` to validate package structure (`exports`/`files` consistency).
|
||||||
|
|
||||||
|
**Testing:** there is no test runner configured in this repo (no vitest/jest, no test files). Do not assume `pnpm test` exists.
|
||||||
|
|
||||||
|
### Running a single component's checks
|
||||||
|
|
||||||
|
There is no per-component test/lint script. The closest equivalent is `pnpm --filter @g3soft/ui build` (runs `vite build`, which type-checks declarations via `vite-plugin-dts`), and `pnpm gen:api` to verify the docgen extraction still parses a component.
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Two independent pipelines
|
||||||
|
|
||||||
|
The docs site and the component library are deliberately decoupled:
|
||||||
|
|
||||||
|
| | Docs site | Component package |
|
||||||
|
|---|---|---|
|
||||||
|
| Published | No (`private: true`) | Yes (private npm registry) |
|
||||||
|
| Versioning | None | changesets (`ignore: ["@g3soft/docs"]`) |
|
||||||
|
| Build | `pnpm build:docs` | `pnpm build` |
|
||||||
|
| Output | `docs/.output/public` (static) | `packages/ui/dist` (ESM + d.ts) |
|
||||||
|
| Dep boundary | Docus/Nuxt/Tailwind live only in the `docs` package | only `vue` (peer) + `@g3soft/tokens` |
|
||||||
|
|
||||||
|
Nuxt-related deps appear **only** in `docs/package.json`; the library's dependency tree has none of it. changesets' `ignore` keeps the docs site out of version tags.
|
||||||
|
|
||||||
|
### Component library build (`packages/ui`)
|
||||||
|
|
||||||
|
`vite.config.ts` builds ESM + type declarations. Critical points:
|
||||||
|
- `vue` is marked `external` — never bundle Vue, or consumers get duplicate instances.
|
||||||
|
- `preserveModules` keeps the `src` structure so consumers can tree-shake.
|
||||||
|
- `vite-plugin-dts` with `rollupTypes` emits a single `dist/index.d.ts`, matched by `exports.types`.
|
||||||
|
- `src/index.ts` is the public entry: it re-exports every component plus its types, and exports the optional `G3UI` install plugin for whole-library registration.
|
||||||
|
|
||||||
|
### Docs site (`docs`)
|
||||||
|
|
||||||
|
- `extends: ['docus']` — all navigation/search/theme comes from the Docus Nuxt Layer.
|
||||||
|
- `vite.resolve.alias` points `@g3soft/ui` and `@g3soft/tokens` **directly at `packages/*/src`**, so the docs site can develop against the library without building it. Editing `packages/ui` hot-reloads the docs immediately.
|
||||||
|
- `site.url` is required (sitemap, llms.txt, OG images) — missing it breaks prerender with a 500.
|
||||||
|
- Deploy base path comes from `NUXT_APP_BASE_URL` (Nuxt's env override for `app.baseURL`), never hardcoded.
|
||||||
|
- `docs/app/components/content/` holds components usable directly inside Markdown: `Demo.vue` (renders a runnable example + source) and `ApiTable.vue` (renders the generated API tables).
|
||||||
|
|
||||||
|
### API generation pipeline
|
||||||
|
|
||||||
|
`scripts/gen-api.mjs` uses `vue-docgen-api` to parse every `.vue` under `packages/ui/src`, extracts props/events/slots and their JSDoc comments, and writes `docs/data/api/<dirName>.json`. `ApiTable.vue` reads those JSON files by component name.
|
||||||
|
|
||||||
|
Consequence: **API tables are generated from source and must never be hand-written.** The component directory name is the doc key (`src/button/Button.vue` → `button`). After any change to a component's props/events/slots, run `pnpm gen:api`.
|
||||||
|
|
||||||
|
### Design tokens (`packages/tokens`)
|
||||||
|
|
||||||
|
- `src/index.css` imports `tokens.css` (light/default) and `dark.css`. Consumed as `import '@g3soft/tokens'`.
|
||||||
|
- Dark theme activates via `html[data-g3-theme='dark']` (`document.documentElement.dataset.g3Theme = 'dark'`).
|
||||||
|
- Re-theming/customer customization = overriding same-named `--g3-*` variables, no component changes.
|
||||||
|
|
||||||
|
### Windows: dev-server port selection
|
||||||
|
|
||||||
|
`docs/package.json` runs `node scripts/dev.mjs` for `dev` (not `nuxt dev` directly). On Windows, Node sets `SO_REUSEADDR`, so **two processes can bind the same `host:port`**. Nuxt's built-in "is the port free?" check works by *trying to bind* a probe socket — which therefore succeeds even when another service already listens on 3000, so `pnpm dev` silently lands on 3000 (no 3000→3001 fallback). `scripts/dev.mjs` sidesteps this by testing the port with a real TCP *connection* (`connect` to `127.0.0.1`/`::1`) and walking upward from `DOCS_PORT`/`PORT`/3000 until it finds a free one, then passing `--port`. `devServer.host: ''` (empty string — deliberately not `'0.0.0.0'`) makes listhen listen on all interfaces *and* print a usable `Local: http://localhost:<port>/` plus the LAN `Network:` URL; with `'0.0.0.0'` the Local line would be an unopenable `http://0.0.0.0:<port>/`.
|
||||||
|
|
||||||
|
## Conventions (must follow)
|
||||||
|
|
||||||
|
- **No `scoped` in component `<style>`.** Isolate with the `g3-` prefix + BEM naming so consumers can override.
|
||||||
|
- **Never hardcode colors or sizes** — always reference `--g3-*` variables, and give every variable a fallback value (e.g. `var(--g3-color-primary, #1677ff)`) so components render correctly even when `@g3soft/tokens` is not loaded.
|
||||||
|
- **Component API docs are generated, not authored** — changes to props/events/slots require `pnpm gen:api`.
|
||||||
|
- **Examples** live at `docs/examples/<component>/<example>.vue` and are referenced in Markdown as `::demo{name="<component>/<example>"}`. API tables use `<ApiTable name="<component>" />`.
|
||||||
|
- New components get a `src/<name>/` directory (component `.vue`, `types.ts`, `index.ts`) and must be re-exported from `src/index.ts`.
|
||||||
|
|
||||||
|
## Version constraints (do not relax)
|
||||||
|
|
||||||
|
`pnpm-workspace.yaml` documents these deliberately — read it before touching dependency versions:
|
||||||
|
|
||||||
|
- **`nuxt` is pinned to exactly `4.4.8` in `docs/package.json`.** Do not widen to `^4.4.8` or upgrade to 4.5+. Nuxt 4.5+ pulls Vite 8 (Rolldown), which cannot resolve Nitro's `file:///D:/...` virtual modules on Windows, breaking `#nitro-internal-virtual/*` and causing every page to 500 (`Either manifest or precomputed data must be provided`). 4.4.8 → Vite 7 (Rollup) and matches Docus's official starter.
|
||||||
|
- **`pnpm 11` uses the `allowBuilds` map form** in `pnpm-workspace.yaml` — the v10 `onlyBuiltDependencies` array form is inert. `better-sqlite3` (Docus FTS5 search native module), `vue-demi`, and `esbuild` must stay allowed.
|
||||||
|
- `vue` and `typescript` versions are centralized in the workspace `catalog:`; each package references `"vue": "catalog:"` to avoid version drift.
|
||||||
@@ -1,11 +1,11 @@
|
|||||||
# g3soft-frontend
|
# g3soft-libs
|
||||||
|
|
||||||
G3Soft 前端技术基础设施:**自研 Vue 3 组件库** + **Docus 文档站**,pnpm monorepo 管理。
|
G3Soft 技术基础设施:**自研 Vue 3 组件库** + **Docus 技术文档站**(面向整体技术体系,不限于前端),pnpm monorepo 管理。
|
||||||
|
|
||||||
## 目录结构
|
## 目录结构
|
||||||
|
|
||||||
```
|
```
|
||||||
g3soft-frontend/
|
g3soft-libs/
|
||||||
├── pnpm-workspace.yaml # workspace 声明 + catalog 统一版本 + 构建脚本白名单
|
├── pnpm-workspace.yaml # workspace 声明 + catalog 统一版本 + 构建脚本白名单
|
||||||
├── packages/
|
├── packages/
|
||||||
│ ├── ui/ # 组件库(发布到 npm 私库)
|
│ ├── ui/ # 组件库(发布到 npm 私库)
|
||||||
@@ -38,7 +38,7 @@ pnpm install
|
|||||||
pnpm dev
|
pnpm dev
|
||||||
```
|
```
|
||||||
|
|
||||||
启动文档站(默认 http://localhost:3000)。组件源码通过 alias 直连,改 `packages/ui` 会即时热更。
|
启动技术文档站:端口默认从 3000 起,被占用时自动向后选空闲端口(见 `docs/scripts/dev.mjs`);控制台会打印本机(`http://localhost:<port>`)与局域网访问地址。组件源码通过 alias 直连,改 `packages/ui` 会即时热更。
|
||||||
|
|
||||||
## 构建
|
## 构建
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ pnpm build:docs # 构建文档站静态文件
|
|||||||
文档站支持挂载到子路径,构建时通过环境变量指定:
|
文档站支持挂载到子路径,构建时通过环境变量指定:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
DOCS_BASE_URL=/docs/ pnpm build:docs
|
NUXT_APP_BASE_URL=/docs/ pnpm build:docs
|
||||||
```
|
```
|
||||||
|
|
||||||
## 发布组件包
|
## 发布组件包
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
---
|
---
|
||||||
seo:
|
seo:
|
||||||
title: G3Soft 前端技术文档
|
title: G3Soft 技术文档
|
||||||
description: G3Soft 自研 Vue 3 组件库、设计变量与前端工程规范。
|
description: G3Soft 技术文档:组件库、设计变量、工程规范与设计决策。
|
||||||
---
|
---
|
||||||
|
|
||||||
:::u-page-hero
|
:::u-page-hero
|
||||||
@@ -9,7 +9,7 @@ seo:
|
|||||||
自研组件,一套规范
|
自研组件,一套规范
|
||||||
|
|
||||||
#description
|
#description
|
||||||
G3Soft 前端技术文档站:组件库 API、设计变量、工程规范与设计决策,全部集中在这里。
|
G3Soft 技术文档站:组件库 API、设计变量、工程规范与设计决策,全部集中在这里。
|
||||||
|
|
||||||
#links
|
#links
|
||||||
:::u-button
|
:::u-button
|
||||||
|
|||||||
@@ -15,7 +15,14 @@ export default defineNuxtConfig({
|
|||||||
// sitemap.xml、llms.txt、OG image 都依赖站点 URL,缺了会导致预渲染 500
|
// sitemap.xml、llms.txt、OG image 都依赖站点 URL,缺了会导致预渲染 500
|
||||||
site: {
|
site: {
|
||||||
url: process.env.DOCS_SITE_URL || 'https://ui.g3soft.dev',
|
url: process.env.DOCS_SITE_URL || 'https://ui.g3soft.dev',
|
||||||
name: 'G3Soft UI',
|
name: 'G3Soft 技术文档',
|
||||||
|
},
|
||||||
|
|
||||||
|
// 监听所有网卡:控制台会打印可用的 Local(http://localhost:PORT) 与 Network(局域网地址)。
|
||||||
|
// 这里必须用空串而不是 '0.0.0.0':listhen 把空串当“全部网卡”,且生成 URL 时回退成 localhost;
|
||||||
|
// 而 '0.0.0.0' 会把 Local 那行显示成 http://0.0.0.0:PORT(浏览器打不开)。
|
||||||
|
devServer: {
|
||||||
|
host: '',
|
||||||
},
|
},
|
||||||
|
|
||||||
vite: {
|
vite: {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "nuxt dev",
|
"dev": "node scripts/dev.mjs",
|
||||||
"build": "nuxt build",
|
"build": "nuxt build",
|
||||||
"generate": "nuxt generate",
|
"generate": "nuxt generate",
|
||||||
"preview": "nuxt preview"
|
"preview": "nuxt preview"
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
/**
|
||||||
|
* 启动文档站 dev server,启动前挑一个真正空闲的端口。
|
||||||
|
*
|
||||||
|
* 为什么不用 Nuxt 自带的端口回退:Nuxt 靠“试绑 host”判断端口是否空闲,而 Windows 上 Node
|
||||||
|
* 默认开启 SO_REUSEADDR,**允许多个进程绑定同一个 addr:port**,于是“能不能绑上”根本判断不出
|
||||||
|
* 冲突 —— 3000 已被别的服务占用时,探测仍然成功,Nuxt 就继续用 3000。
|
||||||
|
*
|
||||||
|
* 因此这里改用“实际建立 TCP 连接”来判断:只要 127.0.0.1 或 ::1 任一能连上,就说明该端口
|
||||||
|
* 正在被监听(可覆盖占用方绑在 0.0.0.0 / 127.0.0.1 / ::1 的所有常见情况),然后依次向上找。
|
||||||
|
*
|
||||||
|
* 起始端口:DOCS_PORT > PORT > 3000。
|
||||||
|
*/
|
||||||
|
import { spawn } from 'node:child_process'
|
||||||
|
import { connect } from 'node:net'
|
||||||
|
|
||||||
|
const start = Number(process.env.DOCS_PORT || process.env.PORT || 3000)
|
||||||
|
const MAX_TRIES = 100
|
||||||
|
|
||||||
|
/** 能否连接到 host:port(能连上即为“正在被监听”) */
|
||||||
|
function reachable(port, host) {
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const socket = connect({ port, host })
|
||||||
|
const done = (busy) => {
|
||||||
|
socket.destroy()
|
||||||
|
resolve(busy)
|
||||||
|
}
|
||||||
|
socket.setTimeout(400)
|
||||||
|
socket.once('connect', () => done(true))
|
||||||
|
socket.once('timeout', () => done(false))
|
||||||
|
socket.once('error', () => done(false))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function isBusy(port) {
|
||||||
|
const [v4, v6] = await Promise.all([reachable(port, '127.0.0.1'), reachable(port, '::1')])
|
||||||
|
return v4 || v6
|
||||||
|
}
|
||||||
|
|
||||||
|
let port = start
|
||||||
|
while (port < start + MAX_TRIES && (await isBusy(port))) port++
|
||||||
|
|
||||||
|
if (port !== start) console.log(`端口 ${start} 已被占用,改用 ${port}`)
|
||||||
|
|
||||||
|
const child = spawn('nuxt', ['dev', '--port', String(port)], {
|
||||||
|
stdio: 'inherit',
|
||||||
|
shell: true,
|
||||||
|
})
|
||||||
|
child.on('exit', (code) => process.exit(code ?? 0))
|
||||||
Reference in new issue
Block a user