feat(worker): 添加微信小游戏 Worker 支持和 Worker Generator CLI (#297)
* feat(worker): 添加微信小游戏 Worker 支持和 Worker Generator CLI - 新增 @esengine/worker-generator 包,用于从 WorkerEntitySystem 生成 Worker 文件 - WorkerEntitySystem 添加 workerScriptPath 配置项,支持预编译 Worker 脚本 - CLI 工具支持 --wechat 模式,自动转换 ES6+ 为 ES5 语法 - 修复微信小游戏 Worker 消息格式差异(res 直接是数据,无需 .data) - 更新中英文文档,添加微信小游戏支持章节 * docs: 更新 changelog,添加 v2.3.1 说明并标注 v2.3.0 为废弃 * fix: 修复 CI 检查问题 - 移除 cli.ts 中未使用的 toKebabCase 函数 - 修复 generator.ts 中正则表达式的 ReDoS 风险(使用 [ \t] 替代 \s*) - 更新 changelog 版本号(2.3.1 -> 2.3.2) * docs: 移除未发布版本的 changelog 条目 * fix(worker-generator): 使用 TypeScript 编译器替代手写正则进行 ES5 转换 - 修复 CodeQL 检测的 ReDoS 安全问题 - 使用 ts.transpileModule 进行安全可靠的代码转换 - 移除所有可能导致回溯的正则表达式
This commit is contained in:
273
packages/worker-generator/src/parser.ts
Normal file
273
packages/worker-generator/src/parser.ts
Normal file
@@ -0,0 +1,273 @@
|
||||
/**
|
||||
* AST 解析器 - 提取 WorkerEntitySystem 子类信息
|
||||
* AST Parser - Extract WorkerEntitySystem subclass information
|
||||
*/
|
||||
|
||||
import { Project, SyntaxKind, ClassDeclaration, MethodDeclaration, Node } from 'ts-morph';
|
||||
import * as path from 'path';
|
||||
import type { WorkerSystemInfo, GeneratorConfig } from './types';
|
||||
|
||||
/**
|
||||
* 解析项目中的 WorkerEntitySystem 子类
|
||||
* Parse WorkerEntitySystem subclasses in the project
|
||||
*/
|
||||
export function parseWorkerSystems(config: GeneratorConfig): WorkerSystemInfo[] {
|
||||
const project = new Project({
|
||||
tsConfigFilePath: config.tsConfigPath,
|
||||
skipAddingFilesFromTsConfig: true,
|
||||
});
|
||||
|
||||
// 添加源文件
|
||||
// Add source files
|
||||
const globPattern = path.join(config.srcDir, '**/*.ts').replace(/\\/g, '/');
|
||||
project.addSourceFilesAtPaths(globPattern);
|
||||
|
||||
const results: WorkerSystemInfo[] = [];
|
||||
|
||||
for (const sourceFile of project.getSourceFiles()) {
|
||||
const filePath = sourceFile.getFilePath();
|
||||
|
||||
// 跳过 node_modules 和 .d.ts 文件
|
||||
// Skip node_modules and .d.ts files
|
||||
if (filePath.includes('node_modules') || filePath.endsWith('.d.ts')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const classDecl of sourceFile.getClasses()) {
|
||||
const info = extractWorkerSystemInfo(classDecl, filePath, config.verbose);
|
||||
if (info) {
|
||||
results.push(info);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查类是否继承自 WorkerEntitySystem
|
||||
* Check if class extends WorkerEntitySystem
|
||||
*/
|
||||
function isWorkerEntitySystemSubclass(classDecl: ClassDeclaration): boolean {
|
||||
const extendsClause = classDecl.getExtends();
|
||||
if (!extendsClause) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const extendsText = extendsClause.getText();
|
||||
|
||||
// 直接检查是否继承 WorkerEntitySystem
|
||||
// Directly check if extends WorkerEntitySystem
|
||||
if (extendsText.startsWith('WorkerEntitySystem')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 递归检查基类(如果需要)
|
||||
// Recursively check base class (if needed)
|
||||
// 这里简化处理,只检查直接继承
|
||||
// Simplified: only check direct inheritance
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取 WorkerEntitySystem 子类信息
|
||||
* Extract WorkerEntitySystem subclass information
|
||||
*/
|
||||
function extractWorkerSystemInfo(
|
||||
classDecl: ClassDeclaration,
|
||||
filePath: string,
|
||||
verbose?: boolean
|
||||
): WorkerSystemInfo | null {
|
||||
if (!isWorkerEntitySystemSubclass(classDecl)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const className = classDecl.getName();
|
||||
if (!className) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (verbose) {
|
||||
console.log(` Found WorkerEntitySystem: ${className} in ${filePath}`);
|
||||
}
|
||||
|
||||
// 查找 workerProcess 方法
|
||||
// Find workerProcess method
|
||||
const workerProcessMethod = classDecl.getMethod('workerProcess');
|
||||
if (!workerProcessMethod) {
|
||||
if (verbose) {
|
||||
console.log(` Warning: No workerProcess method found in ${className}`);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 提取方法体
|
||||
// Extract method body
|
||||
const workerProcessBody = extractMethodBody(workerProcessMethod);
|
||||
if (!workerProcessBody) {
|
||||
if (verbose) {
|
||||
console.log(` Warning: Could not extract workerProcess body from ${className}`);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 提取参数名
|
||||
// Extract parameter names
|
||||
const params = workerProcessMethod.getParameters();
|
||||
const workerProcessParams = {
|
||||
entities: params[0]?.getName() || 'entities',
|
||||
deltaTime: params[1]?.getName() || 'deltaTime',
|
||||
config: params[2]?.getName() || 'config',
|
||||
};
|
||||
|
||||
// 尝试提取 getSharedArrayBufferProcessFunction
|
||||
// Try to extract getSharedArrayBufferProcessFunction
|
||||
let sharedBufferProcessBody: string | undefined;
|
||||
const sharedBufferMethod = classDecl.getMethod('getSharedArrayBufferProcessFunction');
|
||||
if (sharedBufferMethod) {
|
||||
sharedBufferProcessBody = extractSharedBufferFunctionBody(sharedBufferMethod);
|
||||
}
|
||||
|
||||
// 尝试提取 entityDataSize
|
||||
// Try to extract entityDataSize
|
||||
let entityDataSize: number | undefined;
|
||||
const getDefaultEntityDataSizeMethod = classDecl.getMethod('getDefaultEntityDataSize');
|
||||
if (getDefaultEntityDataSizeMethod) {
|
||||
entityDataSize = extractEntityDataSize(getDefaultEntityDataSizeMethod);
|
||||
}
|
||||
|
||||
// 尝试从构造函数中提取 workerScriptPath 配置
|
||||
// Try to extract workerScriptPath from constructor
|
||||
const workerScriptPath = extractWorkerScriptPath(classDecl, verbose);
|
||||
|
||||
return {
|
||||
className,
|
||||
filePath,
|
||||
workerProcessBody,
|
||||
workerProcessParams,
|
||||
sharedBufferProcessBody,
|
||||
entityDataSize,
|
||||
workerScriptPath,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取方法体(去掉方法签名,保留函数体内容)
|
||||
* Extract method body (remove method signature, keep function body content)
|
||||
*/
|
||||
function extractMethodBody(method: MethodDeclaration): string | null {
|
||||
const body = method.getBody();
|
||||
if (!body) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 获取方法体的文本
|
||||
// Get method body text
|
||||
let bodyText = body.getText();
|
||||
|
||||
// 去掉外层的花括号
|
||||
// Remove outer braces
|
||||
if (bodyText.startsWith('{') && bodyText.endsWith('}')) {
|
||||
bodyText = bodyText.slice(1, -1).trim();
|
||||
}
|
||||
|
||||
return bodyText;
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取 getSharedArrayBufferProcessFunction 返回的函数体
|
||||
* Extract function body returned by getSharedArrayBufferProcessFunction
|
||||
*/
|
||||
function extractSharedBufferFunctionBody(method: MethodDeclaration): string | undefined {
|
||||
const body = method.getBody();
|
||||
if (!body) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// 查找 return 语句中的函数表达式
|
||||
// Find function expression in return statement
|
||||
const returnStatements = body.getDescendantsOfKind(SyntaxKind.ReturnStatement);
|
||||
for (const returnStmt of returnStatements) {
|
||||
const expression = returnStmt.getExpression();
|
||||
if (expression) {
|
||||
// 检查是否是函数表达式或箭头函数
|
||||
// Check if it's a function expression or arrow function
|
||||
if (Node.isFunctionExpression(expression) || Node.isArrowFunction(expression)) {
|
||||
const funcBody = expression.getBody();
|
||||
if (funcBody) {
|
||||
let bodyText = funcBody.getText();
|
||||
if (bodyText.startsWith('{') && bodyText.endsWith('}')) {
|
||||
bodyText = bodyText.slice(1, -1).trim();
|
||||
}
|
||||
return bodyText;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* 提取 entityDataSize 值
|
||||
* Extract entityDataSize value
|
||||
*/
|
||||
function extractEntityDataSize(method: MethodDeclaration): number | undefined {
|
||||
const body = method.getBody();
|
||||
if (!body) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// 查找 return 语句
|
||||
// Find return statement
|
||||
const returnStatements = body.getDescendantsOfKind(SyntaxKind.ReturnStatement);
|
||||
for (const returnStmt of returnStatements) {
|
||||
const expression = returnStmt.getExpression();
|
||||
if (expression && Node.isNumericLiteral(expression)) {
|
||||
return parseInt(expression.getText(), 10);
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
/**
|
||||
* 从构造函数中提取 workerScriptPath 配置
|
||||
* Extract workerScriptPath from constructor
|
||||
*/
|
||||
function extractWorkerScriptPath(classDecl: ClassDeclaration, verbose?: boolean): string | undefined {
|
||||
// 查找构造函数
|
||||
// Find constructor
|
||||
const constructors = classDecl.getConstructors();
|
||||
if (constructors.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const constructor = constructors[0]!;
|
||||
const body = constructor.getBody();
|
||||
if (!body) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const bodyText = body.getText();
|
||||
|
||||
// 使用正则表达式查找 workerScriptPath: 'xxx' 或 workerScriptPath: "xxx"
|
||||
// Use regex to find workerScriptPath: 'xxx' or workerScriptPath: "xxx"
|
||||
const patterns = [
|
||||
/workerScriptPath\s*:\s*['"]([^'"]+)['"]/,
|
||||
/workerScriptPath\s*:\s*`([^`]+)`/,
|
||||
];
|
||||
|
||||
for (const pattern of patterns) {
|
||||
const match = bodyText.match(pattern);
|
||||
if (match && match[1]) {
|
||||
if (verbose) {
|
||||
console.log(` Found workerScriptPath: ${match[1]}`);
|
||||
}
|
||||
return match[1];
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
Reference in New Issue
Block a user