我正在使用 Node.js 和 TypeScript 实现 gRPC 服务器。我使用静态 gRPC 方法生成了 TypeScript 类型(
@grpc/grpc-js
、grpc-tools
、grpc_tools_node_protoc_ts
、@types/node
、@types/google-protobuf
)。但是,当尝试运行我的服务器时,我收到以下错误:
> tsx server/server.ts
>
> node:internal/modules/run_main:122
> triggerUncaughtException(
> ^
> Error [TransformError]: Transform failed with 1 error:
> C:\Dev\Nodejs-ts-gRPC\src\gen\users_grpc_pb.d.ts:24:13: ERROR: The constant
> "UserServiceService" must be initialized
>
> // rest of the error
我的依赖项可能存在问题,因为我正在尝试使用 ESModules 实现此实现:
tsconfig.json
:
{
"compilerOptions": {
"target": "ES2017",
"module": "ESNext",
"moduleResolution": "node",
"sourceMap": true,
"rootDir": "./src",
"outDir": "./dist",
"strict": true,
"allowSyntheticDefaultImports": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"noEmit": false
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
package.json
:
{
"name": "nodejs-ts-grpc",
"version": "1.0.0",
"main": "index.js",
"type": "module",
"scripts": {
"build": "tsc",
"start": "tsx src/index.ts",
"dev": "tsx watch src/index.ts",
"lint": "eslint \"src/**/*.{ts,js}\"",
"lint:fix": "eslint \"src/**/*.{ts,js}\" --fix",
"format": "prettier --write \"src/**/*.{ts,js}\"",
"format:check": "prettier --check \"src/**/*.{ts,js}\"",
"server": "tsx server/server.ts",
"client": "tsx server/client.ts",
"generate:proto": "bash proto/build.sh"
},
"keywords": [],
"author": "",
"license": "ISC",
"description": "",
"devDependencies": {
"@eslint/js": "^9.16.0",
"@types/google-protobuf": "^3.15.12",
"@types/node": "^22.10.1",
"@typescript-eslint/eslint-plugin": "^8.17.0",
"@typescript-eslint/parser": "^8.17.0",
"eslint": "^9.16.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.2.1",
"globals": "^15.13.0",
"grpc_tools_node_protoc_ts": "^5.3.3",
"prettier": "^3.4.2",
"tsx": "^4.19.2",
"typescript": "^5.7.2",
"typescript-eslint": "^8.17.0"
},
"dependencies": {
"@grpc/grpc-js": "^1.12.2",
"grpc-tools": "^1.12.4"
}
}
此外,这是我生成的
users_grpc_pb.d.ts
文件:
// package: user
// file: users.proto
/* tslint:disable */
/* eslint-disable */
import * as grpc from '@grpc/grpc-js';
import * as users_pb from './users_pb';
interface IUserServiceService extends grpc.ServiceDefinition<grpc.UntypedServiceImplementation> {
getUser: IUserServiceService_IGetUser;
}
interface IUserServiceService_IGetUser extends grpc.MethodDefinition<users_pb.GetUserRequest, users_pb.GetUserResponse> {
path: '/user.UserService/GetUser';
requestStream: false;
responseStream: false;
requestSerialize: grpc.serialize<users_pb.GetUserRequest>;
requestDeserialize: grpc.deserialize<users_pb.GetUserRequest>;
responseSerialize: grpc.serialize<users_pb.GetUserResponse>;
responseDeserialize: grpc.deserialize<users_pb.GetUserResponse>;
}
export const UserServiceService: IUserServiceService;
export interface IUserServiceServer extends grpc.UntypedServiceImplementation {
getUser: grpc.handleUnaryCall<users_pb.GetUserRequest, users_pb.GetUserResponse>;
}
export interface IUserServiceClient {
getUser(request: users_pb.GetUserRequest, callback: (error: grpc.ServiceError | null, response: users_pb.GetUserResponse) => void): grpc.ClientUnaryCall;
getUser(request: users_pb.GetUserRequest, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: users_pb.GetUserResponse) => void): grpc.ClientUnaryCall;
getUser(
request: users_pb.GetUserRequest,
metadata: grpc.Metadata,
options: Partial<grpc.CallOptions>,
callback: (error: grpc.ServiceError | null, response: users_pb.GetUserResponse) => void,
): grpc.ClientUnaryCall;
}
export class UserServiceClient extends grpc.Client implements IUserServiceClient {
constructor(address: string, credentials: grpc.ChannelCredentials, options?: Partial<grpc.ClientOptions>);
public getUser(request: users_pb.GetUserRequest, callback: (error: grpc.ServiceError | null, response: users_pb.GetUserResponse) => void): grpc.ClientUnaryCall;
public getUser(request: users_pb.GetUserRequest, metadata: grpc.Metadata, callback: (error: grpc.ServiceError | null, response: users_pb.GetUserResponse) => void): grpc.ClientUnaryCall;
public getUser(
request: users_pb.GetUserRequest,
metadata: grpc.Metadata,
options: Partial<grpc.CallOptions>,
callback: (error: grpc.ServiceError | null, response: users_pb.GetUserResponse) => void,
): grpc.ClientUnaryCall;
}
server.ts
:
import * as grpc from '@grpc/grpc-js';
import { UserService } from './services.ts';
import { UserServiceService } from '../src/gen/users_grpc_pb.d';
const server = new grpc.Server();
server.addService(UserServiceService, UserService);
const PORT = '50051';
server.bindAsync(`0.0.0.0:${PORT}`, grpc.ServerCredentials.createInsecure(), (err, port) => {
if (err) {
console.error(`Error al iniciar el servidor: ${err}`);
return;
}
console.log(`Servidor gRPC escuchando en puerto ${port}`);
server.start();
});
我用来从 .proto 文件生成 JS 和 TS 代码的脚本是:
build.sh
:
#!/bin/bash
OUT_DIR=./src/gen
mkdir -p ${OUT_DIR}
grpc_tools_node_protoc \
--js_out=import_style=commonjs,binary:${OUT_DIR} \
--grpc_out=grpc_js:${OUT_DIR} \
--plugin=protoc-gengrpc=./node_modules/.bin/grpc_tools_node_protoc_plugin \
-I ./proto \
proto/*.proto
grpc_tools_node_protoc \
--plugin=protoc-gents=./node_modules/.bin/protoc-gen-ts \
--ts_out=grpc_js:${OUT_DIR} \
-I ./proto \
proto/*.proto
知道发生了什么吗?
我认为错误出在你的
server.ts
模块中:
import { UserServiceService } from '../src/gen/users_grpc_pb.d';
您正在尝试从类型声明文件导入常量。然而,
.d.ts
模块是用来装饰JS文件的。您不应该直接引用它们。相反,将导入更改为:
import { UserServiceService } from '../src/gen/users_grpc_pb'; // drop the ".d"
这将允许 TypeScript 编译器使用
.d.ts
进行类型检查,同时确保(可能的)JS 文件在运行时使用。