@zhiwu215/code-template 1.0.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 (44) hide show
  1. package/LICENSE +21 -0
  2. package/index.js +59 -0
  3. package/package.json +38 -0
  4. package/src/frameworks/electron-vite.js +107 -0
  5. package/src/utils/renderTemplate.js +58 -0
  6. package/templates/electron-vite/plugins/shadcn/components.json +21 -0
  7. package/templates/electron-vite/plugins/shadcn/package.json +13 -0
  8. package/templates/electron-vite/plugins/shadcn/src/renderer/src/assets/globals.css +125 -0
  9. package/templates/electron-vite/plugins/shadcn/src/renderer/src/lib/utils.ts +6 -0
  10. package/templates/electron-vite/plugins/tailwind/electron.vite.config.ts +21 -0
  11. package/templates/electron-vite/plugins/tailwind/package.json +6 -0
  12. package/templates/electron-vite/plugins/tailwind/src/renderer/src/assets/globals.css +1 -0
  13. package/templates/electron-vite/plugins/tailwind/src/renderer/src/main.tsx +11 -0
  14. package/templates/electron-vite/plugins/zustand/package.json +5 -0
  15. package/templates/electron-vite/plugins/zustand/src/renderer/src/store/index.ts +1 -0
  16. package/templates/electron-vite/plugins/zustand/src/renderer/src/store/useAppStore.ts +18 -0
  17. package/templates/electron-vite/react/.editorconfig +9 -0
  18. package/templates/electron-vite/react/.prettierignore +6 -0
  19. package/templates/electron-vite/react/.prettierrc.yaml +4 -0
  20. package/templates/electron-vite/react/.vscode/extensions.json +3 -0
  21. package/templates/electron-vite/react/.vscode/launch.json +39 -0
  22. package/templates/electron-vite/react/.vscode/settings.json +11 -0
  23. package/templates/electron-vite/react/README.md +34 -0
  24. package/templates/electron-vite/react/build/entitlements.mac.plist +12 -0
  25. package/templates/electron-vite/react/build/icon.icns +0 -0
  26. package/templates/electron-vite/react/build/icon.ico +0 -0
  27. package/templates/electron-vite/react/build/icon.png +0 -0
  28. package/templates/electron-vite/react/dev-app-update.yml +3 -0
  29. package/templates/electron-vite/react/electron-builder.yml +43 -0
  30. package/templates/electron-vite/react/electron.vite.config.ts +16 -0
  31. package/templates/electron-vite/react/eslint.config.mjs +32 -0
  32. package/templates/electron-vite/react/package.json +56 -0
  33. package/templates/electron-vite/react/pnpm-workspace.yaml +4 -0
  34. package/templates/electron-vite/react/resources/icon.png +0 -0
  35. package/templates/electron-vite/react/src/main/index.ts +72 -0
  36. package/templates/electron-vite/react/src/preload/index.d.ts +8 -0
  37. package/templates/electron-vite/react/src/preload/index.ts +22 -0
  38. package/templates/electron-vite/react/src/renderer/index.html +17 -0
  39. package/templates/electron-vite/react/src/renderer/src/App.tsx +5 -0
  40. package/templates/electron-vite/react/src/renderer/src/env.d.ts +1 -0
  41. package/templates/electron-vite/react/src/renderer/src/main.tsx +9 -0
  42. package/templates/electron-vite/react/tsconfig.json +4 -0
  43. package/templates/electron-vite/react/tsconfig.node.json +8 -0
  44. package/templates/electron-vite/react/tsconfig.web.json +19 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 知兀
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/index.js ADDED
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env node
2
+ import * as p from '@clack/prompts'
3
+ import color from 'picocolors'
4
+ import fs from 'fs'
5
+ import path from 'path'
6
+ import { createElectronVite } from './src/frameworks/electron-vite.js'
7
+
8
+ async function main() {
9
+ console.clear()
10
+ p.intro(color.bgCyan(color.black(' 专属脚手架开发工具 ')))
11
+
12
+ // 1. 收集全局通用参数:项目名称与项目类型
13
+ const globalConfig = await p.group(
14
+ {
15
+ projectName: () =>
16
+ p.text({
17
+ message: '请输入项目名称:',
18
+ placeholder: 'my-app',
19
+ defaultValue: 'my-app',
20
+ validate: (val) => {
21
+ const name = val.trim()
22
+ if (!name) return '项目名称不能为空'
23
+ if (fs.existsSync(path.resolve(process.cwd(), name))) {
24
+ return `目录 "${name}" 已存在,请换一个名称`
25
+ }
26
+ }
27
+ }),
28
+
29
+ toolchain: () =>
30
+ p.select({
31
+ message: '请选择项目生态体系:',
32
+ options: [
33
+ { value: 'electron-vite', label: 'Electron 桌面应用 (基于 electron-vite)' }
34
+ ]
35
+ })
36
+ },
37
+ {
38
+ onCancel: () => {
39
+ p.cancel('已取消创建。')
40
+ process.exit(0)
41
+ }
42
+ }
43
+ )
44
+
45
+ const projectName = globalConfig.projectName.trim()
46
+ const targetDir = path.resolve(process.cwd(), projectName)
47
+
48
+ // 2. 路由分发给对应的具体框架模块处理
49
+ switch (globalConfig.toolchain) {
50
+ case 'electron-vite':
51
+ await createElectronVite(projectName, targetDir)
52
+ break
53
+ default:
54
+ p.cancel('未知的项目类型')
55
+ process.exit(1)
56
+ }
57
+ }
58
+
59
+ main()
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@zhiwu215/code-template",
3
+ "version": "1.0.0",
4
+ "description": "专属脚手架与项目模板开发工具",
5
+ "type": "module",
6
+ "main": "index.js",
7
+ "bin": {
8
+ "code-template": "./index.js"
9
+ },
10
+ "files": [
11
+ "index.js",
12
+ "src",
13
+ "templates"
14
+ ],
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "git+https://github.com/zhiwu215/code-template.git"
21
+ },
22
+ "keywords": [
23
+ "scaffold",
24
+ "template",
25
+ "electron-vite",
26
+ "react",
27
+ "tailwind",
28
+ "shadcn",
29
+ "zustand"
30
+ ],
31
+ "author": "zhiwu215",
32
+ "license": "MIT",
33
+ "dependencies": {
34
+ "@clack/prompts": "^0.9.0",
35
+ "picocolors": "^1.1.1"
36
+ }
37
+ }
38
+
@@ -0,0 +1,107 @@
1
+ import * as p from '@clack/prompts'
2
+ import color from 'picocolors'
3
+ import fs from 'fs'
4
+ import path from 'path'
5
+ import { fileURLToPath } from 'url'
6
+ import { renderTemplate } from '../utils/renderTemplate.js'
7
+
8
+ const __dirname = path.dirname(fileURLToPath(import.meta.url))
9
+ const ROOT_DIR = path.resolve(__dirname, '..', '..')
10
+ const TEMPLATES_DIR = path.resolve(ROOT_DIR, 'templates', 'electron-vite')
11
+ const REACT_BASE_DIR = path.resolve(TEMPLATES_DIR, 'react')
12
+ const PLUGINS_DIR = path.resolve(TEMPLATES_DIR, 'plugins')
13
+
14
+ /**
15
+ * 负责 electron-vite 项目的创建与组装
16
+ * @param {string} projectName 项目名称
17
+ * @param {string} targetDir 目标创建路径
18
+ */
19
+ export async function createElectronVite(projectName, targetDir) {
20
+ const options = await p.group(
21
+ {
22
+ framework: () =>
23
+ p.select({
24
+ message: '请选择前端框架:',
25
+ options: [
26
+ { value: 'react', label: 'React + TypeScript' }
27
+ ]
28
+ }),
29
+
30
+ css: () =>
31
+ p.select({
32
+ message: '请选择 CSS 样式方案:',
33
+ options: [
34
+ { value: 'tailwind', label: 'Tailwind CSS (v4)' },
35
+ { value: 'none', label: '无 (原生 CSS)' }
36
+ ]
37
+ }),
38
+
39
+ ui: () =>
40
+ p.select({
41
+ message: '请选择 UI 组件库:',
42
+ options: [
43
+ { value: 'shadcn', label: 'shadcn/ui' },
44
+ { value: 'none', label: '无 (不添加 UI 库)' }
45
+ ]
46
+ }),
47
+
48
+ stateManagement: () =>
49
+ p.select({
50
+ message: '请选择状态管理方案:',
51
+ options: [
52
+ { value: 'zustand', label: 'Zustand (轻量响应式状态管理)' },
53
+ { value: 'none', label: '无 (不添加状态管理库)' }
54
+ ]
55
+ })
56
+ },
57
+ {
58
+ onCancel: () => {
59
+ p.cancel('已取消创建。')
60
+ process.exit(0)
61
+ }
62
+ }
63
+ )
64
+
65
+ const s = p.spinner()
66
+
67
+ // 1. 渲染基础底座
68
+ s.start(`正在初始化项目底座: ${color.cyan(projectName)}...`)
69
+ renderTemplate(REACT_BASE_DIR, targetDir)
70
+
71
+ // 更新目标 package.json 项目名称
72
+ const pkgPath = path.join(targetDir, 'package.json')
73
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'))
74
+ pkg.name = projectName
75
+ fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf-8')
76
+ s.stop('项目底座初始化完毕')
77
+
78
+ // 2. 智能依赖计算:如果选了 shadcn/ui,自动补齐前置依赖 Tailwind CSS
79
+ const needsTailwind = options.css === 'tailwind' || options.ui === 'shadcn'
80
+
81
+ if (needsTailwind) {
82
+ s.start('正在配置 Tailwind CSS...')
83
+ renderTemplate(path.join(PLUGINS_DIR, 'tailwind'), targetDir)
84
+ s.stop('Tailwind CSS 配置完成')
85
+ }
86
+
87
+ // 3. 叠加 shadcn/ui
88
+ if (options.ui === 'shadcn') {
89
+ s.start('正在配置 shadcn/ui...')
90
+ renderTemplate(path.join(PLUGINS_DIR, 'shadcn'), targetDir)
91
+ s.stop('shadcn/ui 配置完成')
92
+ }
93
+
94
+ // 4. 叠加 Zustand 状态管理
95
+ if (options.stateManagement === 'zustand') {
96
+ s.start('正在配置 Zustand 状态管理...')
97
+ renderTemplate(path.join(PLUGINS_DIR, 'zustand'), targetDir)
98
+ s.stop('Zustand 配置完成')
99
+ }
100
+
101
+ // 5. 交付提示(秒级完成,由用户自行按需安装依赖)
102
+ p.outro(color.green('项目创建成功!'))
103
+ console.log('\n接下来请运行以下命令启动项目:')
104
+ console.log(color.cyan(` cd ${projectName}`))
105
+ console.log(color.cyan(' pnpm install (或 npm install / yarn install)'))
106
+ console.log(color.cyan(' pnpm dev (或 npm run dev / yarn dev)\n'))
107
+ }
@@ -0,0 +1,58 @@
1
+ import fs from 'fs'
2
+ import path from 'path'
3
+
4
+ /**
5
+ * 递归模板渲染与覆盖引擎
6
+ * @param {string} src 源目录
7
+ * @param {string} dest 目标目录
8
+ */
9
+ export function renderTemplate(src, dest) {
10
+ const stats = fs.statSync(src)
11
+
12
+ if (stats.isDirectory()) {
13
+ if (['node_modules', 'pnpm-lock.yaml', 'package-lock.json', 'yarn.lock'].includes(path.basename(src))) return
14
+ fs.mkdirSync(dest, { recursive: true })
15
+ for (const file of fs.readdirSync(src)) {
16
+ renderTemplate(path.resolve(src, file), path.resolve(dest, file))
17
+ }
18
+ return
19
+ }
20
+
21
+ const filename = path.basename(src)
22
+
23
+ // 遇到 package.json 执行智能深度合并
24
+ if (filename === 'package.json' && fs.existsSync(dest)) {
25
+ const existingPkg = JSON.parse(fs.readFileSync(dest, 'utf-8'))
26
+ const newPkg = JSON.parse(fs.readFileSync(src, 'utf-8'))
27
+ const merged = deepMerge(existingPkg, newPkg)
28
+ fs.writeFileSync(dest, JSON.stringify(merged, null, 2) + '\n', 'utf-8')
29
+ return
30
+ }
31
+
32
+ // 普通文件直接覆盖
33
+ fs.copyFileSync(src, dest)
34
+ }
35
+
36
+ /**
37
+ * 递归合并两个 JavaScript 对象(专用于 package.json)
38
+ */
39
+ export function deepMerge(target, source) {
40
+ const result = { ...target }
41
+ for (const key of Object.keys(source)) {
42
+ const oldVal = result[key]
43
+ const newVal = source[key]
44
+
45
+ if (Array.isArray(oldVal) && Array.isArray(newVal)) {
46
+ result[key] = Array.from(new Set([...oldVal, ...newVal]))
47
+ } else if (
48
+ oldVal && typeof oldVal === 'object' &&
49
+ newVal && typeof newVal === 'object' &&
50
+ !Array.isArray(oldVal) && !Array.isArray(newVal)
51
+ ) {
52
+ result[key] = deepMerge(oldVal, newVal)
53
+ } else {
54
+ result[key] = newVal
55
+ }
56
+ }
57
+ return result
58
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema.json",
3
+ "style": "base-nova",
4
+ "rsc": false,
5
+ "tsx": true,
6
+ "tailwind": {
7
+ "config": "",
8
+ "css": "@renderer/assets/globals.css",
9
+ "baseColor": "neutral",
10
+ "cssVariables": true,
11
+ "prefix": ""
12
+ },
13
+ "aliases": {
14
+ "components": "@renderer/components",
15
+ "utils": "@renderer/lib/utils",
16
+ "ui": "@renderer/components/ui",
17
+ "lib": "@renderer/lib",
18
+ "hooks": "@renderer/hooks"
19
+ },
20
+ "iconLibrary": "lucide"
21
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "dependencies": {
3
+ "@tailwindcss/vite": "^4.3.3",
4
+ "tailwindcss": "^4.3.3",
5
+ "class-variance-authority": "^0.7.1",
6
+ "clsx": "^2.1.1",
7
+ "tailwind-merge": "^3.6.0",
8
+ "cn": "^0.2.6",
9
+ "lucide-react": "^1.45.0",
10
+ "shadcn": "^4.21.0",
11
+ "tw-animate-css": "^1.4.0"
12
+ }
13
+ }
@@ -0,0 +1,125 @@
1
+ @import "tailwindcss";
2
+ @import "tw-animate-css";
3
+ @import "shadcn/tailwind.css";
4
+
5
+ @custom-variant dark (&:is(.dark *));
6
+
7
+ @theme inline {
8
+ --color-background: var(--background);
9
+ --color-foreground: var(--foreground);
10
+ --color-card: var(--card);
11
+ --color-card-foreground: var(--card-foreground);
12
+ --color-popover: var(--popover);
13
+ --color-popover-foreground: var(--popover-foreground);
14
+ --color-primary: var(--primary);
15
+ --color-primary-foreground: var(--primary-foreground);
16
+ --color-secondary: var(--secondary);
17
+ --color-secondary-foreground: var(--secondary-foreground);
18
+ --color-muted: var(--muted);
19
+ --color-muted-foreground: var(--muted-foreground);
20
+ --color-accent: var(--accent);
21
+ --color-accent-foreground: var(--accent-foreground);
22
+ --color-destructive: var(--destructive);
23
+ --color-destructive-foreground: var(--destructive-foreground);
24
+ --color-border: var(--border);
25
+ --color-input: var(--input);
26
+ --color-ring: var(--ring);
27
+ --color-chart-1: var(--chart-1);
28
+ --color-chart-2: var(--chart-2);
29
+ --color-chart-3: var(--chart-3);
30
+ --color-chart-4: var(--chart-4);
31
+ --color-chart-5: var(--chart-5);
32
+ --radius-sm: calc(var(--radius) * 0.6);
33
+ --radius-md: calc(var(--radius) * 0.8);
34
+ --radius-lg: var(--radius);
35
+ --radius-xl: calc(var(--radius) * 1.4);
36
+ --radius-2xl: calc(var(--radius) * 1.8);
37
+ --radius-3xl: calc(var(--radius) * 2.2);
38
+ --radius-4xl: calc(var(--radius) * 2.6);
39
+ --color-sidebar: var(--sidebar);
40
+ --color-sidebar-foreground: var(--sidebar-foreground);
41
+ --color-sidebar-primary: var(--sidebar-primary);
42
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
43
+ --color-sidebar-accent: var(--sidebar-accent);
44
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
45
+ --color-sidebar-border: var(--sidebar-border);
46
+ --color-sidebar-ring: var(--sidebar-ring);
47
+ }
48
+
49
+ :root {
50
+ --radius: 0.625rem;
51
+ --background: oklch(1 0 0);
52
+ --foreground: oklch(0.145 0 0);
53
+ --card: oklch(1 0 0);
54
+ --card-foreground: oklch(0.145 0 0);
55
+ --popover: oklch(1 0 0);
56
+ --popover-foreground: oklch(0.145 0 0);
57
+ --primary: oklch(0.205 0 0);
58
+ --primary-foreground: oklch(0.985 0 0);
59
+ --secondary: oklch(0.97 0 0);
60
+ --secondary-foreground: oklch(0.205 0 0);
61
+ --muted: oklch(0.97 0 0);
62
+ --muted-foreground: oklch(0.556 0 0);
63
+ --accent: oklch(0.97 0 0);
64
+ --accent-foreground: oklch(0.205 0 0);
65
+ --destructive: oklch(0.577 0.245 27.325);
66
+ --border: oklch(0.922 0 0);
67
+ --input: oklch(0.922 0 0);
68
+ --ring: oklch(0.708 0 0);
69
+ --chart-1: oklch(0.646 0.222 41.116);
70
+ --chart-2: oklch(0.6 0.118 184.704);
71
+ --chart-3: oklch(0.398 0.07 227.392);
72
+ --chart-4: oklch(0.828 0.189 84.429);
73
+ --chart-5: oklch(0.769 0.188 70.08);
74
+ --sidebar: oklch(0.985 0 0);
75
+ --sidebar-foreground: oklch(0.145 0 0);
76
+ --sidebar-primary: oklch(0.205 0 0);
77
+ --sidebar-primary-foreground: oklch(0.985 0 0);
78
+ --sidebar-accent: oklch(0.97 0 0);
79
+ --sidebar-accent-foreground: oklch(0.205 0 0);
80
+ --sidebar-border: oklch(0.922 0 0);
81
+ --sidebar-ring: oklch(0.708 0 0);
82
+ }
83
+
84
+ .dark {
85
+ --background: oklch(0.145 0 0);
86
+ --foreground: oklch(0.985 0 0);
87
+ --card: oklch(0.205 0 0);
88
+ --card-foreground: oklch(0.985 0 0);
89
+ --popover: oklch(0.205 0 0);
90
+ --popover-foreground: oklch(0.985 0 0);
91
+ --primary: oklch(0.922 0 0);
92
+ --primary-foreground: oklch(0.205 0 0);
93
+ --secondary: oklch(0.269 0 0);
94
+ --secondary-foreground: oklch(0.985 0 0);
95
+ --muted: oklch(0.269 0 0);
96
+ --muted-foreground: oklch(0.708 0 0);
97
+ --accent: oklch(0.269 0 0);
98
+ --accent-foreground: oklch(0.985 0 0);
99
+ --destructive: oklch(0.704 0.191 22.216);
100
+ --border: oklch(1 0 0 / 10%);
101
+ --input: oklch(1 0 0 / 15%);
102
+ --ring: oklch(0.556 0 0);
103
+ --chart-1: oklch(0.488 0.243 264.376);
104
+ --chart-2: oklch(0.696 0.17 162.48);
105
+ --chart-3: oklch(0.769 0.188 70.08);
106
+ --chart-4: oklch(0.627 0.265 303.9);
107
+ --chart-5: oklch(0.645 0.246 16.439);
108
+ --sidebar: oklch(0.205 0 0);
109
+ --sidebar-foreground: oklch(0.985 0 0);
110
+ --sidebar-primary: oklch(0.488 0.243 264.376);
111
+ --sidebar-primary-foreground: oklch(0.985 0 0);
112
+ --sidebar-accent: oklch(0.269 0 0);
113
+ --sidebar-accent-foreground: oklch(0.985 0 0);
114
+ --sidebar-border: oklch(1 0 0 / 10%);
115
+ --sidebar-ring: oklch(0.556 0 0);
116
+ }
117
+
118
+ @layer base {
119
+ * {
120
+ @apply border-border outline-ring/50;
121
+ }
122
+ body {
123
+ @apply bg-background text-foreground;
124
+ }
125
+ }
@@ -0,0 +1,6 @@
1
+ import { clsx, type ClassValue } from 'clsx'
2
+ import { twMerge } from 'tailwind-merge'
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs))
6
+ }
@@ -0,0 +1,21 @@
1
+ import { resolve } from 'path'
2
+ import { defineConfig } from 'electron-vite'
3
+ import react from '@vitejs/plugin-react'
4
+ import tailwindcss from '@tailwindcss/vite'
5
+
6
+ export default defineConfig({
7
+ main: {},
8
+ preload: {},
9
+ renderer: {
10
+ resolve: {
11
+ alias: {
12
+ '@renderer': resolve('src/renderer/src')
13
+ }
14
+ },
15
+ plugins: [
16
+ react(),
17
+ // @ts-expect-error
18
+ tailwindcss()
19
+ ]
20
+ }
21
+ })
@@ -0,0 +1,6 @@
1
+ {
2
+ "dependencies": {
3
+ "@tailwindcss/vite": "^4.3.3",
4
+ "tailwindcss": "^4.3.3"
5
+ }
6
+ }
@@ -0,0 +1,11 @@
1
+ import './assets/globals.css'
2
+
3
+ import { StrictMode } from 'react'
4
+ import { createRoot } from 'react-dom/client'
5
+ import App from './App'
6
+
7
+ createRoot(document.getElementById('root')!).render(
8
+ <StrictMode>
9
+ <App />
10
+ </StrictMode>
11
+ )
@@ -0,0 +1,5 @@
1
+ {
2
+ "dependencies": {
3
+ "zustand": "^5.0.15"
4
+ }
5
+ }
@@ -0,0 +1 @@
1
+ export * from './useAppStore'
@@ -0,0 +1,18 @@
1
+ import { create } from 'zustand'
2
+
3
+ interface AppState {
4
+ count: number
5
+ increment: () => void
6
+ decrement: () => void
7
+ reset: () => void
8
+ }
9
+
10
+ /**
11
+ * 应用全局状态 Store 示例
12
+ */
13
+ export const useAppStore = create<AppState>((set) => ({
14
+ count: 0,
15
+ increment: () => set((state) => ({ count: state.count + 1 })),
16
+ decrement: () => set((state) => ({ count: state.count - 1 })),
17
+ reset: () => set({ count: 0 })
18
+ }))
@@ -0,0 +1,9 @@
1
+ root = true
2
+
3
+ [*]
4
+ charset = utf-8
5
+ indent_style = space
6
+ indent_size = 2
7
+ end_of_line = lf
8
+ insert_final_newline = true
9
+ trim_trailing_whitespace = true
@@ -0,0 +1,6 @@
1
+ out
2
+ dist
3
+ pnpm-lock.yaml
4
+ LICENSE.md
5
+ tsconfig.json
6
+ tsconfig.*.json
@@ -0,0 +1,4 @@
1
+ singleQuote: true
2
+ semi: false
3
+ printWidth: 100
4
+ trailingComma: none
@@ -0,0 +1,3 @@
1
+ {
2
+ "recommendations": ["dbaeumer.vscode-eslint"]
3
+ }
@@ -0,0 +1,39 @@
1
+ {
2
+ "version": "0.2.0",
3
+ "configurations": [
4
+ {
5
+ "name": "Debug Main Process",
6
+ "type": "node",
7
+ "request": "launch",
8
+ "cwd": "${workspaceRoot}",
9
+ "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron-vite",
10
+ "windows": {
11
+ "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/electron-vite.cmd"
12
+ },
13
+ "runtimeArgs": ["--sourcemap"],
14
+ "env": {
15
+ "REMOTE_DEBUGGING_PORT": "9222"
16
+ }
17
+ },
18
+ {
19
+ "name": "Debug Renderer Process",
20
+ "port": 9222,
21
+ "request": "attach",
22
+ "type": "chrome",
23
+ "webRoot": "${workspaceFolder}/src/renderer",
24
+ "timeout": 60000,
25
+ "presentation": {
26
+ "hidden": true
27
+ }
28
+ }
29
+ ],
30
+ "compounds": [
31
+ {
32
+ "name": "Debug All",
33
+ "configurations": ["Debug Main Process", "Debug Renderer Process"],
34
+ "presentation": {
35
+ "order": 1
36
+ }
37
+ }
38
+ ]
39
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "[typescript]": {
3
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
4
+ },
5
+ "[javascript]": {
6
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
7
+ },
8
+ "[json]": {
9
+ "editor.defaultFormatter": "esbenp.prettier-vscode"
10
+ }
11
+ }
@@ -0,0 +1,34 @@
1
+ # electron-vite-react
2
+
3
+ An Electron application with React and TypeScript
4
+
5
+ ## Recommended IDE Setup
6
+
7
+ - [VSCode](https://code.visualstudio.com/) + [ESLint](https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint) + [Prettier](https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode)
8
+
9
+ ## Project Setup
10
+
11
+ ### Install
12
+
13
+ ```bash
14
+ $ pnpm install
15
+ ```
16
+
17
+ ### Development
18
+
19
+ ```bash
20
+ $ pnpm dev
21
+ ```
22
+
23
+ ### Build
24
+
25
+ ```bash
26
+ # For windows
27
+ $ pnpm build:win
28
+
29
+ # For macOS
30
+ $ pnpm build:mac
31
+
32
+ # For Linux
33
+ $ pnpm build:linux
34
+ ```
@@ -0,0 +1,12 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3
+ <plist version="1.0">
4
+ <dict>
5
+ <key>com.apple.security.cs.allow-jit</key>
6
+ <true/>
7
+ <key>com.apple.security.cs.allow-unsigned-executable-memory</key>
8
+ <true/>
9
+ <key>com.apple.security.cs.allow-dyld-environment-variables</key>
10
+ <true/>
11
+ </dict>
12
+ </plist>
@@ -0,0 +1,3 @@
1
+ provider: generic
2
+ url: https://example.com/auto-updates
3
+ updaterCacheDirName: electron-vite-react-updater
@@ -0,0 +1,43 @@
1
+ appId: com.electron.app
2
+ productName: electron-vite-react
3
+ directories:
4
+ buildResources: build
5
+ files:
6
+ - '!**/.vscode/*'
7
+ - '!src/*'
8
+ - '!electron.vite.config.{js,ts,mjs,cjs}'
9
+ - '!{.eslintcache,eslint.config.mjs,.prettierignore,.prettierrc.yaml,dev-app-update.yml,CHANGELOG.md,README.md}'
10
+ - '!{.env,.env.*,.npmrc,pnpm-lock.yaml}'
11
+ - '!{tsconfig.json,tsconfig.node.json,tsconfig.web.json}'
12
+ asarUnpack:
13
+ - resources/**
14
+ win:
15
+ executableName: electron-vite-react
16
+ nsis:
17
+ artifactName: ${name}-${version}-setup.${ext}
18
+ shortcutName: ${productName}
19
+ uninstallDisplayName: ${productName}
20
+ createDesktopShortcut: always
21
+ mac:
22
+ entitlementsInherit: build/entitlements.mac.plist
23
+ extendInfo:
24
+ - NSCameraUsageDescription: Application requests access to the device's camera.
25
+ - NSMicrophoneUsageDescription: Application requests access to the device's microphone.
26
+ - NSDocumentsFolderUsageDescription: Application requests access to the user's Documents folder.
27
+ - NSDownloadsFolderUsageDescription: Application requests access to the user's Downloads folder.
28
+ notarize: false
29
+ dmg:
30
+ artifactName: ${name}-${version}.${ext}
31
+ linux:
32
+ target:
33
+ - AppImage
34
+ - snap
35
+ - deb
36
+ maintainer: electronjs.org
37
+ category: Utility
38
+ appImage:
39
+ artifactName: ${name}-${version}.${ext}
40
+ npmRebuild: false
41
+ publish:
42
+ provider: generic
43
+ url: https://example.com/auto-updates
@@ -0,0 +1,16 @@
1
+ import { resolve } from 'path'
2
+ import { defineConfig } from 'electron-vite'
3
+ import react from '@vitejs/plugin-react'
4
+
5
+ export default defineConfig({
6
+ main: {},
7
+ preload: {},
8
+ renderer: {
9
+ resolve: {
10
+ alias: {
11
+ '@renderer': resolve('src/renderer/src')
12
+ }
13
+ },
14
+ plugins: [react()]
15
+ }
16
+ })
@@ -0,0 +1,32 @@
1
+ import { defineConfig } from 'eslint/config'
2
+ import tseslint from '@electron-toolkit/eslint-config-ts'
3
+ import eslintConfigPrettier from '@electron-toolkit/eslint-config-prettier'
4
+ import eslintPluginReact from 'eslint-plugin-react'
5
+ import eslintPluginReactHooks from 'eslint-plugin-react-hooks'
6
+ import eslintPluginReactRefresh from 'eslint-plugin-react-refresh'
7
+
8
+ export default defineConfig(
9
+ { ignores: ['**/node_modules', '**/dist', '**/out'] },
10
+ tseslint.configs.recommended,
11
+ eslintPluginReact.configs.flat.recommended,
12
+ eslintPluginReact.configs.flat['jsx-runtime'],
13
+ {
14
+ settings: {
15
+ react: {
16
+ version: 'detect'
17
+ }
18
+ }
19
+ },
20
+ {
21
+ files: ['**/*.{ts,tsx}'],
22
+ plugins: {
23
+ 'react-hooks': eslintPluginReactHooks,
24
+ 'react-refresh': eslintPluginReactRefresh
25
+ },
26
+ rules: {
27
+ ...eslintPluginReactHooks.configs.recommended.rules,
28
+ ...eslintPluginReactRefresh.configs.vite.rules
29
+ }
30
+ },
31
+ eslintConfigPrettier
32
+ )
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "electron-vite-react",
3
+ "version": "1.0.0",
4
+ "description": "An Electron application with React and TypeScript",
5
+ "main": "./out/main/index.js",
6
+ "author": "example.com",
7
+ "homepage": "https://electron-vite.org",
8
+ "scripts": {
9
+ "format": "prettier --write .",
10
+ "lint": "eslint --cache .",
11
+ "typecheck:node": "tsc --noEmit -p tsconfig.node.json --composite false",
12
+ "typecheck:web": "tsc --noEmit -p tsconfig.web.json --composite false",
13
+ "typecheck": "npm run typecheck:node && npm run typecheck:web",
14
+ "start": "electron-vite preview",
15
+ "dev": "electron-vite dev",
16
+ "build": "npm run typecheck && electron-vite build",
17
+ "postinstall": "electron-builder install-app-deps",
18
+ "build:unpack": "npm run build && electron-builder --dir",
19
+ "build:win": "npm run build && electron-builder --win",
20
+ "build:mac": "electron-vite build && electron-builder --mac",
21
+ "build:linux": "electron-vite build && electron-builder --linux"
22
+ },
23
+ "dependencies": {
24
+ "@electron-toolkit/preload": "^3.0.2",
25
+ "@electron-toolkit/utils": "^4.0.0",
26
+ "electron-updater": "^6.3.9"
27
+ },
28
+ "devDependencies": {
29
+ "@electron-toolkit/eslint-config-prettier": "^3.0.0",
30
+ "@electron-toolkit/eslint-config-ts": "^3.1.0",
31
+ "@electron-toolkit/tsconfig": "^2.0.0",
32
+ "@types/node": "^22.19.1",
33
+ "@types/react": "^19.2.7",
34
+ "@types/react-dom": "^19.2.3",
35
+ "@vitejs/plugin-react": "^5.1.1",
36
+ "electron": "^39.2.6",
37
+ "electron-builder": "^26.0.12",
38
+ "electron-vite": "^5.0.0",
39
+ "eslint": "^9.39.1",
40
+ "eslint-plugin-react": "^7.37.5",
41
+ "eslint-plugin-react-hooks": "^7.0.1",
42
+ "eslint-plugin-react-refresh": "^0.4.24",
43
+ "prettier": "^3.7.4",
44
+ "react": "^19.2.1",
45
+ "react-dom": "^19.2.1",
46
+ "typescript": "^5.9.3",
47
+ "vite": "^7.2.6"
48
+ },
49
+ "pnpm": {
50
+ "onlyBuiltDependencies": [
51
+ "electron",
52
+ "esbuild",
53
+ "electron-winstaller"
54
+ ]
55
+ }
56
+ }
@@ -0,0 +1,4 @@
1
+ allowBuilds:
2
+ electron: true
3
+ electron-winstaller: true
4
+ esbuild: true
@@ -0,0 +1,72 @@
1
+ import { app, shell, BrowserWindow, ipcMain } from 'electron'
2
+ import { join } from 'path'
3
+ import { electronApp, optimizer, is } from '@electron-toolkit/utils'
4
+ import icon from '../../resources/icon.png?asset'
5
+
6
+ function createWindow(): void {
7
+ // Create the browser window.
8
+ const mainWindow = new BrowserWindow({
9
+ width: 900,
10
+ height: 670,
11
+ show: false,
12
+ autoHideMenuBar: true,
13
+ ...(process.platform === 'linux' ? { icon } : {}),
14
+ webPreferences: {
15
+ preload: join(__dirname, '../preload/index.js'),
16
+ sandbox: false
17
+ }
18
+ })
19
+
20
+ mainWindow.on('ready-to-show', () => {
21
+ mainWindow.show()
22
+ })
23
+
24
+ mainWindow.webContents.setWindowOpenHandler((details) => {
25
+ shell.openExternal(details.url)
26
+ return { action: 'deny' }
27
+ })
28
+
29
+ // HMR for renderer base on electron-vite cli.
30
+ // Load the remote URL for development or the local html file for production.
31
+ if (is.dev && process.env['ELECTRON_RENDERER_URL']) {
32
+ mainWindow.loadURL(process.env['ELECTRON_RENDERER_URL'])
33
+ } else {
34
+ mainWindow.loadFile(join(__dirname, '../renderer/index.html'))
35
+ }
36
+ }
37
+
38
+ // This method will be called when Electron has finished
39
+ // initialization and is ready to create browser windows.
40
+ // Some APIs can only be used after this event occurs.
41
+ app.whenReady().then(() => {
42
+ // Set app user model id for windows
43
+ electronApp.setAppUserModelId('com.electron')
44
+
45
+ // Default open or close DevTools by F12 in development
46
+ // and ignore CommandOrControl + R in production.
47
+ // see https://github.com/alex8088/electron-toolkit/tree/master/packages/utils
48
+ app.on('browser-window-created', (_, window) => {
49
+ optimizer.watchWindowShortcuts(window)
50
+ })
51
+
52
+ createWindow()
53
+
54
+ app.on('activate', function () {
55
+ // On macOS it's common to re-create a window in the app when the
56
+ // dock icon is clicked and there are no other windows open.
57
+ if (BrowserWindow.getAllWindows().length === 0) createWindow()
58
+ })
59
+ })
60
+
61
+ // Quit when all windows are closed, except on macOS. There, it's common
62
+ // for applications and their menu bar to stay active until the user quits
63
+ // explicitly with Cmd + Q.
64
+ app.on('window-all-closed', () => {
65
+ if (process.platform !== 'darwin') {
66
+ app.quit()
67
+ }
68
+ })
69
+
70
+ // In this file you can include the rest of your app's specific main process
71
+ // code. You can also put them in separate files and require them here.
72
+
@@ -0,0 +1,8 @@
1
+ import { ElectronAPI } from '@electron-toolkit/preload'
2
+
3
+ declare global {
4
+ interface Window {
5
+ electron: ElectronAPI
6
+ api: unknown
7
+ }
8
+ }
@@ -0,0 +1,22 @@
1
+ import { contextBridge } from 'electron'
2
+ import { electronAPI } from '@electron-toolkit/preload'
3
+
4
+ // Custom APIs for renderer
5
+ const api = {}
6
+
7
+ // Use `contextBridge` APIs to expose Electron APIs to
8
+ // renderer only if context isolation is enabled, otherwise
9
+ // just add to the DOM global.
10
+ if (process.contextIsolated) {
11
+ try {
12
+ contextBridge.exposeInMainWorld('electron', electronAPI)
13
+ contextBridge.exposeInMainWorld('api', api)
14
+ } catch (error) {
15
+ console.error(error)
16
+ }
17
+ } else {
18
+ // @ts-ignore (define in dts)
19
+ window.electron = electronAPI
20
+ // @ts-ignore (define in dts)
21
+ window.api = api
22
+ }
@@ -0,0 +1,17 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <title>Electron</title>
6
+ <!-- https://developer.mozilla.org/en-US/docs/Web/HTTP/CSP -->
7
+ <meta
8
+ http-equiv="Content-Security-Policy"
9
+ content="default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data:"
10
+ />
11
+ </head>
12
+
13
+ <body>
14
+ <div id="root"></div>
15
+ <script type="module" src="/src/main.tsx"></script>
16
+ </body>
17
+ </html>
@@ -0,0 +1,5 @@
1
+ function App(): React.JSX.Element {
2
+ return <div></div>
3
+ }
4
+
5
+ export default App
@@ -0,0 +1 @@
1
+ /// <reference types="vite/client" />
@@ -0,0 +1,9 @@
1
+ import { StrictMode } from 'react'
2
+ import { createRoot } from 'react-dom/client'
3
+ import App from './App'
4
+
5
+ createRoot(document.getElementById('root')!).render(
6
+ <StrictMode>
7
+ <App />
8
+ </StrictMode>
9
+ )
@@ -0,0 +1,4 @@
1
+ {
2
+ "files": [],
3
+ "references": [{ "path": "./tsconfig.node.json" }, { "path": "./tsconfig.web.json" }]
4
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "extends": "@electron-toolkit/tsconfig/tsconfig.node.json",
3
+ "include": ["electron.vite.config.*", "src/main/**/*", "src/preload/**/*"],
4
+ "compilerOptions": {
5
+ "composite": true,
6
+ "types": ["electron-vite/node"]
7
+ }
8
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "extends": "@electron-toolkit/tsconfig/tsconfig.web.json",
3
+ "include": [
4
+ "src/renderer/src/env.d.ts",
5
+ "src/renderer/src/**/*",
6
+ "src/renderer/src/**/*.tsx",
7
+ "src/preload/*.d.ts"
8
+ ],
9
+ "compilerOptions": {
10
+ "composite": true,
11
+ "jsx": "react-jsx",
12
+ "baseUrl": ".",
13
+ "paths": {
14
+ "@renderer/*": [
15
+ "src/renderer/src/*"
16
+ ]
17
+ }
18
+ }
19
+ }