@liu_jimmy/create-fe-kit 0.1.0

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.
Files changed (45) hide show
  1. package/index.js +92 -0
  2. package/package.json +30 -0
  3. package/templates/react/.cursor/rules/fe-kit-style.mdc +23 -0
  4. package/templates/react/AGENTS.md +3 -0
  5. package/templates/react/eslint.config.js +3 -0
  6. package/templates/react/index.html +12 -0
  7. package/templates/react/package.json +32 -0
  8. package/templates/react/postcss.config.js +6 -0
  9. package/templates/react/src/App.tsx +69 -0
  10. package/templates/react/src/main.tsx +11 -0
  11. package/templates/react/src/style.css +3 -0
  12. package/templates/react/src/vite-env.d.ts +6 -0
  13. package/templates/react/tailwind.config.js +7 -0
  14. package/templates/react/tsconfig.json +21 -0
  15. package/templates/react/tsconfig.node.json +11 -0
  16. package/templates/react/vite.config.ts +6 -0
  17. package/templates/svelte/.cursor/rules/fe-kit-style.mdc +8 -0
  18. package/templates/svelte/AGENTS.md +3 -0
  19. package/templates/svelte/eslint.config.js +3 -0
  20. package/templates/svelte/index.html +12 -0
  21. package/templates/svelte/package.json +28 -0
  22. package/templates/svelte/postcss.config.js +6 -0
  23. package/templates/svelte/src/App.svelte +56 -0
  24. package/templates/svelte/src/main.ts +7 -0
  25. package/templates/svelte/src/style.css +3 -0
  26. package/templates/svelte/src/vite-env.d.ts +7 -0
  27. package/templates/svelte/svelte.config.js +5 -0
  28. package/templates/svelte/tailwind.config.js +7 -0
  29. package/templates/svelte/tsconfig.json +19 -0
  30. package/templates/svelte/tsconfig.node.json +11 -0
  31. package/templates/svelte/vite.config.ts +6 -0
  32. package/templates/vue/.cursor/rules/fe-kit-style.mdc +51 -0
  33. package/templates/vue/AGENTS.md +22 -0
  34. package/templates/vue/eslint.config.js +3 -0
  35. package/templates/vue/index.html +12 -0
  36. package/templates/vue/package.json +29 -0
  37. package/templates/vue/postcss.config.js +6 -0
  38. package/templates/vue/src/App.vue +59 -0
  39. package/templates/vue/src/main.ts +6 -0
  40. package/templates/vue/src/style.css +3 -0
  41. package/templates/vue/src/vite-env.d.ts +6 -0
  42. package/templates/vue/tailwind.config.js +7 -0
  43. package/templates/vue/tsconfig.json +22 -0
  44. package/templates/vue/tsconfig.node.json +11 -0
  45. package/templates/vue/vite.config.ts +6 -0
package/index.js ADDED
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * create-fe-kit
5
+ * Usage: pnpm create @liu_jimmy/fe-kit or npx @liu_jimmy/create-fe-kit
6
+ * Prompts for project name + framework (Vue / React / Svelte), then copies template.
7
+ */
8
+ import { createInterface } from "readline";
9
+ import { createWriteStream, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from "fs";
10
+ import { dirname, join } from "path";
11
+ import { fileURLToPath } from "url";
12
+
13
+ const __dirname = dirname(fileURLToPath(import.meta.url));
14
+ const TEMPLATES_DIR = join(__dirname, "templates");
15
+
16
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
17
+
18
+ function ask(question, defaultAnswer = "") {
19
+ return new Promise((resolve) => {
20
+ const prompt = defaultAnswer ? `${question} (${defaultAnswer}): ` : `${question}: `;
21
+ rl.question(prompt, (answer) => resolve(answer.trim() || defaultAnswer));
22
+ });
23
+ }
24
+
25
+ function copyRecursive(src, dest, vars = {}) {
26
+ mkdirSync(dest, { recursive: true });
27
+ for (const name of readdirSync(src)) {
28
+ if (name === "node_modules") continue;
29
+ const srcPath = join(src, name);
30
+ const destPath = join(dest, name);
31
+ const stat = statSync(srcPath);
32
+ if (stat.isDirectory()) {
33
+ copyRecursive(srcPath, destPath, vars);
34
+ } else {
35
+ let content = readFileSync(srcPath, "utf8");
36
+ for (const [key, value] of Object.entries(vars)) {
37
+ content = content.replace(new RegExp(`{{${key}}}`, "g"), value);
38
+ }
39
+ writeFileSync(destPath, content);
40
+ }
41
+ }
42
+ }
43
+
44
+ async function main() {
45
+ console.log("\n create-fe-kit — Vue / React / Svelte + Vite + fe-kit\n");
46
+
47
+ const projectName = await ask("Project name", "my-fe-kit-app");
48
+ const nameSafe = projectName.replace(/\s+/g, "-").toLowerCase();
49
+ console.log("\nFramework:");
50
+ console.log(" 1) Vue");
51
+ console.log(" 2) React");
52
+ console.log(" 3) Svelte");
53
+ const choice = await ask("Choose (1/2/3)", "1");
54
+ const framework = { "1": "vue", "2": "react", "3": "svelte" }[choice] || "vue";
55
+
56
+ const templatePath = join(TEMPLATES_DIR, framework);
57
+ try {
58
+ statSync(templatePath);
59
+ } catch {
60
+ console.error(`\nTemplate "${framework}" not found. Use Vue (1) for now.`);
61
+ rl.close();
62
+ process.exit(1);
63
+ }
64
+
65
+ const targetDir = join(process.cwd(), nameSafe);
66
+ try {
67
+ statSync(targetDir);
68
+ console.error(`\nDirectory already exists: ${targetDir}`);
69
+ rl.close();
70
+ process.exit(1);
71
+ } catch {}
72
+
73
+ const vars = {
74
+ PROJECT_NAME: nameSafe,
75
+ TITLE: projectName,
76
+ };
77
+
78
+ console.log(`\nCreating ${nameSafe} with ${framework}...`);
79
+ copyRecursive(templatePath, targetDir, vars);
80
+
81
+ console.log("\nDone. Next steps:\n");
82
+ console.log(` cd ${nameSafe}`);
83
+ console.log(" pnpm install");
84
+ console.log(" pnpm dev\n");
85
+ rl.close();
86
+ }
87
+
88
+ main().catch((err) => {
89
+ console.error(err);
90
+ rl.close();
91
+ process.exit(1);
92
+ });
package/package.json ADDED
@@ -0,0 +1,30 @@
1
+ {
2
+ "name": "@liu_jimmy/create-fe-kit",
3
+ "version": "0.1.0",
4
+ "description": "Create a new project with fe-kit (Vue / React / Svelte + Vite + Tailwind v3 + ESLint)",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-fe-kit": "./index.js"
8
+ },
9
+ "files": [
10
+ "index.js",
11
+ "templates"
12
+ ],
13
+ "keywords": [
14
+ "create",
15
+ "fe-kit",
16
+ "vue",
17
+ "react",
18
+ "svelte",
19
+ "vite",
20
+ "scaffold"
21
+ ],
22
+ "license": "MIT",
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "https://github.com/fe-kit/fe-kit.git"
26
+ },
27
+ "engines": {
28
+ "node": ">=18"
29
+ }
30
+ }
@@ -0,0 +1,23 @@
1
+ ---
2
+ description: 生成/修改前端代码时必须遵守 fe-kit 的样式与命名规范,禁止偏离
3
+ alwaysApply: true
4
+ ---
5
+
6
+ # fe-kit 风格约束(生成代码时必须遵守)
7
+
8
+ 本项目中**所有生成或修改的前端代码**必须符合以下规范。不得使用任意颜色/间距/圆角,必须使用 fe-kit 的语义 class 与命名约定。
9
+
10
+ ## 一、样式:只允许 fe-kit 语义 class
11
+
12
+ - **禁止**:内联 `style` 写颜色、间距、圆角、阴影;禁止任意 Tailwind 颜色如 `bg-blue-500`;禁止 Tailwind 任意值写颜色如 `bg-[#fff]`。
13
+ - **必须**:使用 fe-kit tailwind preset 的语义 class。
14
+
15
+ ### 背景与文字:`bg-bg`、`bg-bg-elevated`、`text-fg`、`text-fg-muted`
16
+ ### 主色与边框:`bg-primary`、`text-primary-fg`、`border-border`
17
+ ### 圆角/阴影:`rounded-xl`、`shadow-md` 等;主题 `theme-blue`/`theme-purple`/`theme-green`,深色 `dark`
18
+
19
+ ## 二、命名
20
+
21
+ - 目录 kebab-case;组件文件 PascalCase;非组件文件 kebab-case;变量 camelCase;常量 UPPER_SNAKE_CASE;类型 PascalCase;函数动词开头。
22
+
23
+ ## 三、生成后应通过 `pnpm lint`。
@@ -0,0 +1,3 @@
1
+ # AI 生成代码约束(fe-kit)
2
+
3
+ 生成或修改前端代码时必须遵守:只使用 fe-kit 语义 class(`bg-bg`、`text-fg`、`border-border`、`bg-primary`、`rounded-xl` 等),禁止内联颜色/任意 Tailwind 颜色或任意值;命名按 kebab-case 目录、PascalCase 组件、camelCase 变量、UPPER_SNAKE_CASE 常量;生成后应通过 `pnpm lint`。
@@ -0,0 +1,3 @@
1
+ import feKitPreset from "@liu_jimmy/fe-kit-eslint-preset";
2
+
3
+ export default feKitPreset;
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>{{TITLE}}</title>
7
+ </head>
8
+ <body>
9
+ <div id="root"></div>
10
+ <script type="module" src="/src/main.tsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "{{PROJECT_NAME}}",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "tsc -b && vite build",
9
+ "preview": "vite preview",
10
+ "lint": "eslint .",
11
+ "lint:fix": "eslint . --fix"
12
+ },
13
+ "dependencies": {
14
+ "@liu_jimmy/fe-kit-tailwind-preset": "^0.1.0",
15
+ "@liu_jimmy/fe-kit-tokens": "^0.1.0",
16
+ "react": "^19.0.0",
17
+ "react-dom": "^19.0.0"
18
+ },
19
+ "devDependencies": {
20
+ "@eslint/js": "^9.15.0",
21
+ "@liu_jimmy/fe-kit-eslint-preset": "^0.1.0",
22
+ "@types/react": "^19.0.0",
23
+ "@types/react-dom": "^19.0.0",
24
+ "@vitejs/plugin-react": "^4.3.4",
25
+ "autoprefixer": "^10.4.20",
26
+ "eslint": "^9.39.3",
27
+ "postcss": "^8.4.49",
28
+ "tailwindcss": "^3.4.15",
29
+ "typescript": "~5.6.2",
30
+ "vite": "^6.0.3"
31
+ }
32
+ }
@@ -0,0 +1,6 @@
1
+ export default {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ };
@@ -0,0 +1,69 @@
1
+ import { useState } from "react";
2
+
3
+ type ThemeId = "theme-blue" | "theme-purple" | "theme-green";
4
+
5
+ const themes: { id: ThemeId; label: string }[] = [
6
+ { id: "theme-blue", label: "蓝" },
7
+ { id: "theme-purple", label: "紫" },
8
+ { id: "theme-green", label: "绿" },
9
+ ];
10
+
11
+ function setTheme(id: ThemeId) {
12
+ document.documentElement.classList.remove("theme-blue", "theme-purple", "theme-green");
13
+ document.documentElement.classList.add(id);
14
+ }
15
+
16
+ function App() {
17
+ const [theme, setThemeState] = useState<ThemeId>("theme-blue");
18
+ const [isDark, setIsDark] = useState(false);
19
+
20
+ function handleSetTheme(id: ThemeId) {
21
+ setTheme(id);
22
+ setThemeState(id);
23
+ }
24
+
25
+ function toggleDark() {
26
+ setIsDark((v) => !v);
27
+ document.documentElement.classList.toggle("dark", !isDark);
28
+ }
29
+
30
+ return (
31
+ <div className="min-h-screen bg-bg text-fg font-sans">
32
+ <header className="border-b border-border bg-bg-elevated px-6 py-4 shadow-sm">
33
+ <h1 className="text-2xl font-semibold text-fg">{{TITLE}}</h1>
34
+ <p className="mt-1 text-sm text-fg-muted">fe-kit + React + Vite</p>
35
+ </header>
36
+
37
+ <main className="mx-auto max-w-2xl space-y-8 p-6">
38
+ <section className="rounded-xl border border-border bg-bg-elevated p-6 shadow-md">
39
+ <h2 className="text-lg font-medium text-fg">主题</h2>
40
+ <div className="mt-4 flex flex-wrap gap-2">
41
+ {themes.map((t) => (
42
+ <button
43
+ key={t.id}
44
+ type="button"
45
+ className={`rounded-lg px-4 py-2 text-sm font-medium transition-colors ${
46
+ theme === t.id ? "bg-primary text-primary-fg" : "bg-primary-muted text-fg hover:bg-primary hover:text-primary-fg"
47
+ }`}
48
+ onClick={() => handleSetTheme(t.id)}
49
+ >
50
+ {t.label}
51
+ </button>
52
+ ))}
53
+ <button
54
+ type="button"
55
+ className={`rounded-lg border px-4 py-2 text-sm font-medium ${
56
+ isDark ? "border-fg bg-fg text-bg" : "border-border bg-bg-muted text-fg"
57
+ }`}
58
+ onClick={toggleDark}
59
+ >
60
+ {isDark ? "浅色" : "深色"}
61
+ </button>
62
+ </div>
63
+ </section>
64
+ </main>
65
+ </div>
66
+ );
67
+ }
68
+
69
+ export default App;
@@ -0,0 +1,11 @@
1
+ import "@liu_jimmy/fe-kit-tokens";
2
+ import "./style.css";
3
+ import { StrictMode } from "react";
4
+ import { createRoot } from "react-dom/client";
5
+ import App from "./App.tsx";
6
+
7
+ createRoot(document.getElementById("root")!).render(
8
+ <StrictMode>
9
+ <App />
10
+ </StrictMode>,
11
+ );
@@ -0,0 +1,3 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
@@ -0,0 +1,6 @@
1
+ /// <reference types="vite/client" />
2
+
3
+ declare module "@liu_jimmy/fe-kit-tokens" {
4
+ const url: string;
5
+ export default url;
6
+ }
@@ -0,0 +1,7 @@
1
+ import preset from "@liu_jimmy/fe-kit-tailwind-preset";
2
+
3
+ /** @type {import('tailwindcss').Config} */
4
+ export default {
5
+ presets: [preset],
6
+ content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"],
7
+ };
@@ -0,0 +1,21 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "useDefineForClassFields": true,
5
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
6
+ "module": "ESNext",
7
+ "skipLibCheck": true,
8
+ "moduleResolution": "bundler",
9
+ "allowImportingTsExtensions": true,
10
+ "resolveJsonModule": true,
11
+ "isolatedModules": true,
12
+ "noEmit": true,
13
+ "jsx": "react-jsx",
14
+ "strict": true,
15
+ "noUnusedLocals": true,
16
+ "noUnusedParameters": true,
17
+ "noFallthroughCasesInSwitch": true
18
+ },
19
+ "include": ["src"],
20
+ "references": [{ "path": "./tsconfig.node.json" }]
21
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "compilerOptions": {
3
+ "composite": true,
4
+ "skipLibCheck": true,
5
+ "module": "ESNext",
6
+ "moduleResolution": "bundler",
7
+ "allowSyntheticDefaultImports": true,
8
+ "strict": true
9
+ },
10
+ "include": ["vite.config.ts"]
11
+ }
@@ -0,0 +1,6 @@
1
+ import { defineConfig } from "vite";
2
+ import react from "@vitejs/plugin-react";
3
+
4
+ export default defineConfig({
5
+ plugins: [react()],
6
+ });
@@ -0,0 +1,8 @@
1
+ ---
2
+ description: 生成/修改前端代码时必须遵守 fe-kit 的样式与命名规范
3
+ alwaysApply: true
4
+ ---
5
+
6
+ # fe-kit 风格约束
7
+
8
+ 只使用 fe-kit 语义 class(bg-bg、text-fg、border-border、bg-primary、rounded-xl 等);禁止内联颜色、任意 Tailwind 颜色或任意值;命名:目录 kebab-case、组件 PascalCase、变量 camelCase、常量 UPPER_SNAKE_CASE;生成后应通过 pnpm lint。
@@ -0,0 +1,3 @@
1
+ # AI 生成代码约束(fe-kit)
2
+
3
+ 只使用 fe-kit 语义 class(bg-bg、text-fg、border-border、bg-primary、rounded-xl 等);禁止内联颜色与任意 Tailwind 颜色;命名按 CONVENTIONS;生成后应通过 pnpm lint。
@@ -0,0 +1,3 @@
1
+ import feKitPreset from "@liu_jimmy/fe-kit-eslint-preset";
2
+
3
+ export default feKitPreset;
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>{{TITLE}}</title>
7
+ </head>
8
+ <body>
9
+ <div id="app"></div>
10
+ <script type="module" src="/src/main.ts"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "{{PROJECT_NAME}}",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "preview": "vite preview",
10
+ "lint": "eslint .",
11
+ "lint:fix": "eslint . --fix"
12
+ },
13
+ "dependencies": {
14
+ "@liu_jimmy/fe-kit-tailwind-preset": "^0.1.0",
15
+ "@liu_jimmy/fe-kit-tokens": "^0.1.0",
16
+ "svelte": "^4.2.0"
17
+ },
18
+ "devDependencies": {
19
+ "@liu_jimmy/fe-kit-eslint-preset": "^0.1.0",
20
+ "@sveltejs/vite-plugin-svelte": "^4.0.0",
21
+ "autoprefixer": "^10.4.20",
22
+ "eslint": "^9.39.3",
23
+ "postcss": "^8.4.49",
24
+ "tailwindcss": "^3.4.15",
25
+ "typescript": "~5.6.2",
26
+ "vite": "^6.0.3"
27
+ }
28
+ }
@@ -0,0 +1,6 @@
1
+ export default {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ };
@@ -0,0 +1,56 @@
1
+ <script lang="ts">
2
+ type ThemeId = "theme-blue" | "theme-purple" | "theme-green";
3
+
4
+ let theme: ThemeId = "theme-blue";
5
+ let isDark = false;
6
+
7
+ const themes: { id: ThemeId; label: string }[] = [
8
+ { id: "theme-blue", label: "蓝" },
9
+ { id: "theme-purple", label: "紫" },
10
+ { id: "theme-green", label: "绿" },
11
+ ];
12
+
13
+ function setTheme(id: ThemeId) {
14
+ document.documentElement.classList.remove("theme-blue", "theme-purple", "theme-green");
15
+ document.documentElement.classList.add(id);
16
+ theme = id;
17
+ }
18
+
19
+ function toggleDark() {
20
+ isDark = !isDark;
21
+ document.documentElement.classList.toggle("dark", isDark);
22
+ }
23
+ </script>
24
+
25
+ <main class="min-h-screen bg-bg text-fg font-sans">
26
+ <header class="border-b border-border bg-bg-elevated px-6 py-4 shadow-sm">
27
+ <h1 class="text-2xl font-semibold text-fg">{{TITLE}}</h1>
28
+ <p class="mt-1 text-sm text-fg-muted">fe-kit + Svelte + Vite</p>
29
+ </header>
30
+
31
+ <div class="mx-auto max-w-2xl space-y-8 p-6">
32
+ <section class="rounded-xl border border-border bg-bg-elevated p-6 shadow-md">
33
+ <h2 class="text-lg font-medium text-fg">主题</h2>
34
+ <div class="mt-4 flex flex-wrap gap-2">
35
+ {#each themes as t}
36
+ <button
37
+ type="button"
38
+ class="rounded-lg px-4 py-2 text-sm font-medium transition-colors {theme === t.id
39
+ ? 'bg-primary text-primary-fg'
40
+ : 'bg-primary-muted text-fg hover:bg-primary hover:text-primary-fg'}"
41
+ on:click={() => setTheme(t.id)}
42
+ >
43
+ {t.label}
44
+ </button>
45
+ {/each}
46
+ <button
47
+ type="button"
48
+ class="rounded-lg border px-4 py-2 text-sm font-medium {isDark ? 'border-fg bg-fg text-bg' : 'border-border bg-bg-muted text-fg'}"
49
+ on:click={toggleDark}
50
+ >
51
+ {isDark ? "浅色" : "深色"}
52
+ </button>
53
+ </div>
54
+ </section>
55
+ </div>
56
+ </main>
@@ -0,0 +1,7 @@
1
+ import "@liu_jimmy/fe-kit-tokens";
2
+ import "./style.css";
3
+ import App from "./App.svelte";
4
+
5
+ const app = new App({ target: document.getElementById("app")! });
6
+
7
+ export default app;
@@ -0,0 +1,3 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
@@ -0,0 +1,7 @@
1
+ /// <reference types="svelte" />
2
+ /// <reference types="vite/client" />
3
+
4
+ declare module "@liu_jimmy/fe-kit-tokens" {
5
+ const url: string;
6
+ export default url;
7
+ }
@@ -0,0 +1,5 @@
1
+ import { vitePreprocess } from "@sveltejs/vite-plugin-svelte";
2
+
3
+ export default {
4
+ preprocess: vitePreprocess(),
5
+ };
@@ -0,0 +1,7 @@
1
+ import preset from "@liu_jimmy/fe-kit-tailwind-preset";
2
+
3
+ /** @type {import('tailwindcss').Config} */
4
+ export default {
5
+ presets: [preset],
6
+ content: ["./index.html", "./src/**/*.{svelte,js,ts}"],
7
+ };
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "ESNext",
5
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
6
+ "skipLibCheck": true,
7
+ "moduleResolution": "bundler",
8
+ "allowImportingTsExtensions": true,
9
+ "resolveJsonModule": true,
10
+ "isolatedModules": true,
11
+ "noEmit": true,
12
+ "strict": true,
13
+ "noUnusedLocals": true,
14
+ "noUnusedParameters": true,
15
+ "noFallthroughCasesInSwitch": true
16
+ },
17
+ "include": ["src/**/*.ts", "src/**/*.svelte"],
18
+ "references": [{ "path": "./tsconfig.node.json" }]
19
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "compilerOptions": {
3
+ "composite": true,
4
+ "skipLibCheck": true,
5
+ "module": "ESNext",
6
+ "moduleResolution": "bundler",
7
+ "allowSyntheticDefaultImports": true,
8
+ "strict": true
9
+ },
10
+ "include": ["vite.config.ts"]
11
+ }
@@ -0,0 +1,6 @@
1
+ import { defineConfig } from "vite";
2
+ import { svelte } from "@sveltejs/vite-plugin-svelte";
3
+
4
+ export default defineConfig({
5
+ plugins: [svelte()],
6
+ });
@@ -0,0 +1,51 @@
1
+ ---
2
+ description: 生成/修改前端代码时必须遵守 fe-kit 的样式与命名规范,禁止偏离
3
+ alwaysApply: true
4
+ ---
5
+
6
+ # fe-kit 风格约束(生成代码时必须遵守)
7
+
8
+ 本项目中**所有生成或修改的前端代码**必须符合以下规范。不得使用任意颜色/间距/圆角,必须使用 fe-kit 的语义 class 与命名约定。
9
+
10
+ ## 一、样式:只允许 fe-kit 语义 class
11
+
12
+ - **禁止**:内联 `style` 写颜色、间距、圆角、阴影;禁止任意 Tailwind 颜色如 `bg-blue-500`、`text-gray-700`;禁止 Tailwind 任意值写颜色如 `bg-[#fff]`、`text-[rgb(0,0,0)]`。
13
+ - **必须**:使用 fe-kit tailwind preset 的语义 class。
14
+
15
+ ### 背景与文字
16
+ - 背景:`bg-bg`、`bg-bg-elevated`、`bg-bg-muted`
17
+ - 文字:`text-fg`、`text-fg-muted`、`text-fg-subtle`
18
+
19
+ ### 主色与边框
20
+ - 主色:`bg-primary`、`text-primary-fg`、`bg-primary-muted`、hover 用 `hover:bg-primary-hover`
21
+ - 边框:`border-border`、`border-border-strong`
22
+
23
+ ### 圆角、间距、阴影
24
+ - 圆角:`rounded-sm`、`rounded-md`、`rounded-lg`、`rounded-xl`、`rounded-2xl`、`rounded-full`
25
+ - 阴影:`shadow-sm`、`shadow-md`、`shadow-lg`、`shadow-xl`
26
+ - 间距:`p-4`、`px-6`、`space-y-4` 等
27
+
28
+ ### 主题
29
+ - 主题色:`theme-blue`、`theme-purple`、`theme-green`(加在 `document.documentElement`)
30
+ - 深色:根节点加 `dark` class
31
+
32
+ ## 二、文件与目录命名
33
+
34
+ - **目录**:kebab-case(`user-card/`、`data-table/`)
35
+ - **组件文件**(.vue / .tsx / .svelte):PascalCase(`UserCard.vue`)
36
+ - **非组件文件**(.ts / .js):kebab-case(`format-date.ts`)
37
+
38
+ ## 三、变量与函数命名
39
+
40
+ - **变量**:camelCase(`userName`、`isLoading`)
41
+ - **函数**:动词开头(`fetchUser()`、`formatDate()`)
42
+ - **常量**:UPPER_SNAKE_CASE(`API_BASE_URL`)
43
+ - **类型**:PascalCase(`type User`、`interface Pagination`)
44
+
45
+ ## 四、Vue 组件
46
+
47
+ - 模板中组件名 PascalCase;块顺序:script → template → style
48
+
49
+ ## 五、生成后自检
50
+
51
+ - 代码应能通过 `pnpm lint`;若有报错只做最小修复。
@@ -0,0 +1,22 @@
1
+ # AI 生成代码约束(fe-kit)
2
+
3
+ 生成或修改前端代码时必须遵守以下约束。
4
+
5
+ ## 核心原则
6
+
7
+ 1. **样式**:只使用 fe-kit 语义 class(`bg-bg`、`text-fg`、`border-border`、`bg-primary`、`rounded-xl` 等),禁止内联颜色/间距,禁止任意 Tailwind 颜色(如 `bg-blue-500`)或任意值(如 `bg-[#fff]`)。
8
+ 2. **命名**:目录 kebab-case、组件文件 PascalCase、变量 camelCase、常量 UPPER_SNAKE_CASE、类型 PascalCase、函数动词开头。
9
+ 3. **生成后**:应能通过 `pnpm lint`。
10
+
11
+ ## 语义 class 速查
12
+
13
+ | 用途 | 使用示例 |
14
+ |------|----------|
15
+ | 背景 | `bg-bg`、`bg-bg-elevated`、`bg-bg-muted` |
16
+ | 文字 | `text-fg`、`text-fg-muted`、`text-fg-subtle` |
17
+ | 主色 | `bg-primary`、`text-primary-fg`、`bg-primary-muted`、`hover:bg-primary-hover` |
18
+ | 边框 | `border-border`、`border-border-strong` |
19
+ | 圆角 | `rounded-sm`~`rounded-2xl`、`rounded-full` |
20
+ | 阴影 | `shadow-sm`~`shadow-xl` |
21
+
22
+ 主题:根节点 `theme-blue` / `theme-purple` / `theme-green`;深色加 `dark`。
@@ -0,0 +1,3 @@
1
+ import feKitPreset from "@liu_jimmy/fe-kit-eslint-preset";
2
+
3
+ export default feKitPreset;
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>{{TITLE}}</title>
7
+ </head>
8
+ <body>
9
+ <div id="app"></div>
10
+ <script type="module" src="/src/main.ts"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,29 @@
1
+ {
2
+ "name": "{{PROJECT_NAME}}",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vue-tsc -b && vite build",
9
+ "preview": "vite preview",
10
+ "lint": "eslint .",
11
+ "lint:fix": "eslint . --fix"
12
+ },
13
+ "dependencies": {
14
+ "@liu_jimmy/fe-kit-tailwind-preset": "^0.1.0",
15
+ "@liu_jimmy/fe-kit-tokens": "^0.1.0",
16
+ "vue": "^3.5.13"
17
+ },
18
+ "devDependencies": {
19
+ "@liu_jimmy/fe-kit-eslint-preset": "^0.1.0",
20
+ "@vitejs/plugin-vue": "^5.2.1",
21
+ "autoprefixer": "^10.4.20",
22
+ "eslint": "^9.39.3",
23
+ "postcss": "^8.4.49",
24
+ "tailwindcss": "^3.4.15",
25
+ "typescript": "~5.6.2",
26
+ "vite": "^6.0.3",
27
+ "vue-tsc": "^2.1.10"
28
+ }
29
+ }
@@ -0,0 +1,6 @@
1
+ export default {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ };
@@ -0,0 +1,59 @@
1
+ <script setup lang="ts">
2
+ import { ref } from "vue";
3
+
4
+ const theme = ref<"theme-blue" | "theme-purple" | "theme-green">("theme-blue");
5
+ const isDark = ref(false);
6
+
7
+ const themes = [
8
+ { id: "theme-blue" as const, label: "蓝" },
9
+ { id: "theme-purple" as const, label: "紫" },
10
+ { id: "theme-green" as const, label: "绿" },
11
+ ] as const;
12
+
13
+ function setTheme(id: (typeof themes)[number]["id"]) {
14
+ document.documentElement.classList.remove("theme-blue", "theme-purple", "theme-green");
15
+ document.documentElement.classList.add(id);
16
+ theme.value = id;
17
+ }
18
+
19
+ function toggleDark() {
20
+ isDark.value = !isDark.value;
21
+ document.documentElement.classList.toggle("dark", isDark.value);
22
+ }
23
+ </script>
24
+
25
+ <template>
26
+ <div class="min-h-screen bg-bg text-fg font-sans">
27
+ <header class="border-b border-border bg-bg-elevated px-6 py-4 shadow-sm">
28
+ <h1 class="text-2xl font-semibold text-fg">{{TITLE}}</h1>
29
+ <p class="mt-1 text-sm text-fg-muted">fe-kit + Vue + Vite</p>
30
+ </header>
31
+
32
+ <main class="mx-auto max-w-2xl space-y-8 p-6">
33
+ <section class="rounded-xl border border-border bg-bg-elevated p-6 shadow-md">
34
+ <h2 class="text-lg font-medium text-fg">主题</h2>
35
+ <div class="mt-4 flex flex-wrap gap-2">
36
+ <button
37
+ v-for="t in themes"
38
+ :key="t.id"
39
+ type="button"
40
+ :class="[
41
+ 'rounded-lg px-4 py-2 text-sm font-medium transition-colors',
42
+ theme === t.id ? 'bg-primary text-primary-fg' : 'bg-primary-muted text-fg hover:bg-primary hover:text-primary-fg',
43
+ ]"
44
+ @click="setTheme(t.id)"
45
+ >
46
+ {{ t.label }}
47
+ </button>
48
+ <button
49
+ type="button"
50
+ :class="['rounded-lg border px-4 py-2 text-sm font-medium', isDark ? 'border-fg bg-fg text-bg' : 'border-border bg-bg-muted text-fg']"
51
+ @click="toggleDark"
52
+ >
53
+ {{ isDark ? "浅色" : "深色" }}
54
+ </button>
55
+ </div>
56
+ </section>
57
+ </main>
58
+ </div>
59
+ </template>
@@ -0,0 +1,6 @@
1
+ import "@liu_jimmy/fe-kit-tokens";
2
+ import "./style.css";
3
+ import { createApp } from "vue";
4
+ import App from "./App.vue";
5
+
6
+ createApp(App).mount("#app");
@@ -0,0 +1,3 @@
1
+ @tailwind base;
2
+ @tailwind components;
3
+ @tailwind utilities;
@@ -0,0 +1,6 @@
1
+ /// <reference types="vite/client" />
2
+
3
+ declare module "@liu_jimmy/fe-kit-tokens" {
4
+ const url: string;
5
+ export default url;
6
+ }
@@ -0,0 +1,7 @@
1
+ import preset from "@liu_jimmy/fe-kit-tailwind-preset";
2
+
3
+ /** @type {import('tailwindcss').Config} */
4
+ export default {
5
+ presets: [preset],
6
+ content: ["./index.html", "./src/**/*.{vue,ts,tsx,js,jsx}"],
7
+ };
@@ -0,0 +1,22 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "useDefineForClassFields": true,
5
+ "module": "ESNext",
6
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
7
+ "skipLibCheck": true,
8
+ "moduleResolution": "bundler",
9
+ "allowImportingTsExtensions": true,
10
+ "resolveJsonModule": true,
11
+ "isolatedModules": true,
12
+ "noEmit": true,
13
+ "jsx": "preserve",
14
+ "strict": true,
15
+ "noUnusedLocals": true,
16
+ "noUnusedParameters": true,
17
+ "noFallthroughCasesInSwitch": true,
18
+ "paths": { "@/*": ["./src/*"] }
19
+ },
20
+ "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"],
21
+ "references": [{ "path": "./tsconfig.node.json" }]
22
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "compilerOptions": {
3
+ "composite": true,
4
+ "skipLibCheck": true,
5
+ "module": "ESNext",
6
+ "moduleResolution": "bundler",
7
+ "allowSyntheticDefaultImports": true,
8
+ "strict": true
9
+ },
10
+ "include": ["vite.config.ts"]
11
+ }
@@ -0,0 +1,6 @@
1
+ import { defineConfig } from "vite";
2
+ import vue from "@vitejs/plugin-vue";
3
+
4
+ export default defineConfig({
5
+ plugins: [vue()],
6
+ });