如何搭建包含Vite+React前端、Express 5后端及共享TypeScript npm包的项目
嘿,我来帮你一步步搞定这个多包项目的搭建!按照你给出的目录结构,咱们分环节配置,确保前端、后端和共享包能顺畅协作~
第一步:初始化根项目管理
首先在根目录(app文件夹)创建一个package.json,用npm workspaces来统一管理所有子包,这样安装依赖和运行脚本会更方便:
{ "name": "my-app-monorepo", "private": true, "workspaces": [ "./client", "./server", "./shared" ], "scripts": { "dev:client": "npm run dev -w client", "dev:server": "npm run dev -w server", "dev": "concurrently \"npm run dev:client\" \"npm run dev:server\"", "build:shared": "npm run build -w shared", "build:client": "npm run build -w client", "build:server": "npm run build -w server", "build": "npm run build:shared && npm run build:client && npm run build:server" }, "devDependencies": { "concurrently": "^8.2.2" } }
这里concurrently用来同时启动前后端的开发服务,不用开多个终端;workspaces字段告诉npm哪些子目录是独立的包。
第二步:配置共享shared包
进入shared目录,先调整package.json,让它能被TypeScript编译并被其他包正确引用:
{ "name": "@my-app/shared", "version": "1.0.0", "type": "module", "main": "./dist/index.js", "types": "./dist/index.d.ts", "scripts": { "build": "tsc", "dev": "tsc --watch" }, "devDependencies": { "typescript": "^5.3.3" } }
- 用
@my-app/shared这种带scope的命名,避免和公共npm包重名 type: module启用ES模块规范main和types分别指定编译后的JS入口和TypeScript类型文件
然后修改tsconfig.json,确保编译后生成类型文件并输出到dist目录:
{ "compilerOptions": { "target": "ES2020", "module": "ESNext", "moduleResolution": "bundler", "declaration": true, "outDir": "./dist", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true }, "include": ["src/**/*"], "exclude": ["node_modules", "dist"] }
最后在shared/src/index.ts里导出你要共享的工具函数,比如:
export { default as helper } from './utils/helper';
第三步:配置Express 5后端(server)
进入server目录,先设置package.json:
{ "name": "@my-app/server", "version": "1.0.0", "type": "module", "main": "./dist/index.js", "scripts": { "dev": "tsx watch src/index.ts", "build": "tsc", "start": "node dist/index.js" }, "dependencies": { "@my-app/shared": "^1.0.0", "express": "^5.0.0-beta.1" }, "devDependencies": { "@types/express": "^4.17.21", "@types/node": "^20.10.6", "tsx": "^4.7.0", "typescript": "^5.3.3" } }
tsx用来直接运行TypeScript代码,不用手动编译- 依赖里直接引用咱们的共享包
@my-app/shared
接着修改tsconfig.json,添加路径别名方便开发时直接引用共享包源码:
{ "compilerOptions": { "target": "ES2020", "module": "ESNext", "moduleResolution": "bundler", "outDir": "./dist", "strict": true, "esModuleInterop": true, "skipLibCheck": true, "forceConsistentCasingInFileNames": true, "paths": { "@my-app/shared": ["../shared/src"] } }, "include": ["src/**/*"], "exclude": ["node_modules", "dist"] }
最后在server/src/index.ts里测试使用共享包:
import express from 'express'; import { helper } from '@my-app/shared'; const app = express(); const port = 3001; app.get('/', (req, res) => { res.send(`Express 5 server running! Shared helper says: ${helper()}`); }); app.listen(port, () => { console.log(`Server ready at http://localhost:${port}`); });
第四步:配置Vite+React前端(client)
如果还没初始化Vite+React项目,先在client目录执行npm create vite@latest . -- --template react-ts生成基础结构,然后修改package.json添加共享包依赖:
{ "name": "@my-app/client", "private": true, "version": "1.0.0", "type": "module", "scripts": { "dev": "vite", "build": "tsc && vite build", "preview": "vite preview" }, "dependencies": { "@my-app/shared": "^1.0.0", "react": "^18.2.0", "react-dom": "^18.2.0" }, "devDependencies": { "@types/react": "^18.2.45", "@types/react-dom": "^18.2.18", "@vitejs/plugin-react": "^4.2.1", "typescript": "^5.3.3", "vite": "^5.0.8" } }
接下来修改vite.config.ts,添加共享包的路径别名,同时配置代理让前端能访问后端接口:
import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; export default defineConfig({ plugins: [react()], resolve: { alias: { '@my-app/shared': '../shared/src', }, }, server: { port: 3000, proxy: { '/api': { target: 'http://localhost:3001', changeOrigin: true, }, }, }, });
还要更新tsconfig.json和tsconfig.app.json,添加路径别名让TypeScript能识别:tsconfig.json:
{ "files": [], "references": [{ "path": "./tsconfig.app.json" }, { "path": "./tsconfig.node.json" }], "compilerOptions": { "paths": { "@my-app/shared": ["../shared/src"] } } }
tsconfig.app.json:
{ "compilerOptions": { "composite": true, "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", "target": "ES2020", "useDefineForClassFields": true, "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext", "skipLibCheck": true, "moduleResolution": "bundler", "allowImportingTsExtensions": true, "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "react-jsx", "strict": true, "esModuleInterop": true, "forceConsistentCasingInFileNames": true, "paths": { "@my-app/shared": ["../shared/src"] } }, "include": ["src"], "exclude": ["node_modules"] }
最后在client/src/App.tsx里测试使用共享包:
import { helper } from '@my-app/shared'; import './App.css'; function App() { return ( <div className="App"> <h1>Vite + React Frontend</h1> <p>Shared helper output: {helper()}</p> </div> ); } export default App;
第五步:本地开发流程
- 在根目录执行
npm install,npm会自动安装所有子包的依赖,并关联workspaces中的内部包 - 启动共享包的监听编译:
npm run dev -w shared,修改共享代码会自动编译到dist目录 - 在根目录执行
npm run dev,同时启动前端和后端的开发服务 - 访问
http://localhost:3000看前端,http://localhost:3001看后端,修改任意代码都会自动热更新
第六步:构建与部署
- 先编译共享包:
npm run build:shared - 执行根目录的
npm run build,依次编译前端和后端 - 部署时:
- 前端
client/dist目录可以部署到静态服务器(如Nginx、Vercel) - 后端
server/dist目录部署到Node.js服务器(如ECS、Heroku) - 确保后端的
node_modules包含编译后的@my-app/shared包
- 前端
备注:内容来源于stack exchange,提问作者YarGnawh

