news 2026/10/2 13:46:07

Nakama 内嵌 goja 引擎的 JavaScript AST 节点体系:从解析到源码级实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Nakama 内嵌 goja 引擎的 JavaScript AST 节点体系:从解析到源码级实现
  • 后端
  • 即时通讯
  • 社交
  • 游戏开发

【免费下载链接】nakama

Scalable open-source game backend server: multiplayer, matchmaking, leaderboards, chat, and social features for games.

项目地址:https://gitcode.com/GitHub_Trending/na/nakama
点击查看免费下载

导读

本文以 Nakama 仓库中vendor/github.com/dop251/goja/ast包的 API 文档(README.markdown)为核心骨架,系统梳理 goja 这一纯 Go 实现的 ECMAScript 引擎在 Nakama 游戏后端中的 JavaScript 抽象语法树(AST)节点体系:包括 Node/Expression/Statement/Declaration 四大接口、数十种节点结构体、基于file.Idx的源码位置追踪机制,以及它们如何被 goja 的 parser 与 compiler 消费,并最终在 Nakama 的 JS 运行时(server/runtime_javascript.go)中用于解析游戏服务器模块源码。读完本文,你将掌握 goja AST 的完整节点清单、字段语义与位置编码原理,并能读懂 Nakama 加载 JS 模块时的goja.Parse解析链路。

说明:本文中的接口与结构体定义均取自 Nakama 仓库内 goja 库的实际源码与文档,属于该引擎的通用 API;文中另结合 Nakama 服务端代码给出其在游戏后端中的具体应用场景。

一、背景:goja 与 AST 在 Nakama 中的位置

Nakama 是一款可扩展的开源游戏后端服务器,支持用 Lua、TypeScript/JavaScript 编写服务端模块。其中 JavaScript 运行时基于 goja 实现——一个纯 Go 编写、无 cgo 依赖的 ECMAScript 引擎。goja 的解析与编译流程为:

  1. 词法/语法解析:parser包将 JS 源码解析为 AST(*ast.Program);
  2. AST 编译:compiler包将 AST 编译为字节码;
  3. 虚拟机执行:vm包解释执行字节码。

在 Nakama 中,这一流程被封装在 server/runtime_javascript.go 里——加载 JS 模块时先调用goja.Parse(modName, string(content))生成 AST,再调用goja.Compile(modName, string(content), true)编译,随后将Program与Ast一并放入模块缓存:

modAst, _ := goja.Parse(modName, string(content)) prg, err := goja.Compile(modName, string(content), true) if err != nil { logger.Error("Could not compile JavaScript module", zap.String("module", modName), zap.Error(err)) return nil, err } moduleCache.Add(&RuntimeJSModule{ Name: modName, Path: absEntrypoint, Program: prg, Ast: modAst, })

也就是说,Nakama 每次启动加载 JS 模块时,都会先经由ast包定义的节点类型构建一棵完整的语法树,再进行编译与缓存。ast包正是这棵语法树的"骨架定义"。

ast包本身只负责声明节点类型,不含解析逻辑(解析在parser包)、不含求值逻辑(求值在vm包)。其包注释与文档均带有如下警告,读者在自行扩展或依赖这些接口时需要注意:

The parser and AST interfaces are still works-in-progress (particularly where node types are concerned) and may change in the future. (parser 与 AST 接口仍在演进中,尤其是节点类型部分,未来可能发生变化。)

二、AST 的三大核心接口与声明体系

ast包的所有节点都可以归入三棵类型树,全部定义在 vendor/github.com/dop251/goja/ast/node.go 中。

2.1 Node 接口:一切节点的根

type Node interface { Idx0() file.Idx // The index of the first character belonging to the node Idx1() file.Idx // The index of the first character immediately after the node }

Node是所有 AST 节点的根基,每个节点都必须实现两个方法:

  • Idx0():返回节点第一个字符在源文件中的位置索引;
  • Idx1():返回节点最后一个字符之后第一个位置(即开区间终点)的索引。

两者共同定义了节点在源码中占据的字符区间[Idx0, Idx1),这是 AST 节点与源码文本保持可追溯映射的关键。

2.2 Expression / Statement / Declaration 接口

type Expression interface { Node // contains filtered or unexported methods } type Statement interface { Node // contains filtered or unexported methods } type Declaration interface { Node // contains filtered or unexported methods }
  • Expression(表达式):所有表达式节点实现此接口,例如字面量、二元运算、函数调用、成员访问等;
  • Statement(语句):所有语句节点实现此接口,例如if、for、return等;
  • Declaration(声明):所有声明节点实现此接口,如var变量声明。

接口中"filtered or unexported methods"对应源码中用于类型约束的私有标记方法。在 node.go 中,Expression接口实际声明为:

type Expression interface { Node _expressionNode() }

私有方法_expressionNode()使接口只能被包内声明的类型实现,外部无法伪造节点类型,从而保证了类型系统的封闭性。Statement、Declaration、Property、Pattern、ClassElement、ConciseBody等接口均采用同样的"私有标记方法"约束模式(见 node.go 与 node.go)。

2.3 Program:AST 的根节点

type Program struct { Body []Statement DeclarationList []Declaration File *file.File }

parser.ParseFile的返回值就是*ast.Program,它是整棵语法树的根:

  • Body:顶层语句序列;
  • DeclarationList:顶层声明列表(如var/let/const与函数声明);
  • File:关联的源文件对象,用于把file.Idx换算成行/列位置。

Program.Idx0()返回Body[0].Idx0()(首个语句的起点),Program.Idx1()返回Body[len(Body)-1].Idx1()(末条语句的终点),见 node.go 与 node.go。

三、源码位置追踪:file.Idx 与 Idx0/Idx1 的实现

AST 节点的位置字段几乎全部使用file.Idx类型。在 vendor/github.com/dop251/goja/file/file.go 中:

// Idx is a compact encoding of a source position within a file set. // It can be converted into a Position for a more convenient, but much // larger, representation. type Idx int type Position struct { Filename string // The filename where the error occurred, if any Line int // The line number, starting at 1 Column int // The column number, starting at 1 (The character count) }

Idx是文件集内的紧凑偏移编码,FileSet.Position(idx)可将其换算为Position{Line, Column}。FileSet管理多个File,每个File持有src与base(起始偏移),新增文件时base递推为上一个文件base + len(src) + 1(file.go),从而保证所有文件的Idx全局唯一。文档中Node接口的两条注释也印证了这一设计:

  • Idx0() file.Idx:The index of the first character belonging to the node(节点首字符索引);
  • Idx1() file.Idx:The index of the first character immediately after the node(节点之后紧邻的字符索引)。

3.1 Idx0 的几种计算策略

从 node.go 的Idx0()实现可以归纳出四类策略:

策略代表节点实现
返回自己的关键字/定界符位置IfStatement、WhileStatement、FunctionLiteral、ArrayLiteral、ObjectLiteralreturn self.If/self.While/self.Function/self.LeftBracket/self.LeftBrace
委托给左操作数/被调用者AssignExpression、BinaryExpression、CallExpression、DotExpressionreturn self.Left.Idx0()/self.Callee.Idx0()
前缀/后缀差异UnaryExpression后缀时返回Operand.Idx0(),前缀时返回运算符Idx(node.go)
复合节点取首元素SequenceExpression、Programreturn self.Sequence[0].Idx0()

3.2 Idx1 的几个关键实现

Idx1()的实现同样值得推敲(node.go):

  • 字面量类:如BooleanLiteral、NumberLiteral、StringLiteral、RegExpLiteral用Idx + len(Literal)计算,NullLiteral固定Idx + 4("null"四个字符),ThisExpression固定Idx + 4、SuperExpression固定Idx + 5;
  • 括号类:ArrayLiteral为RightBracket + 1、CallExpression为RightParenthesis + 1、BlockStatement为RightBrace + 1;
  • 后置运算符:UnaryExpression在后缀时返回Operand.Idx1() + 2(++/--占两个字符,node.go);
  • 条件分支:IfStatement有Alternate时返回Alternate.Idx1(),否则返回Consequent.Idx1();TryStatement依次优先Finally、Catch、Body(node.go、node.go);
  • 变长声明列表:VariableStatement、LexicalDeclaration、ForLoopInitializerVarDeclList均取List[len(List)-1].Idx1();VariableDeclaration在列表为空时退化为Var + 3("var"三个字符,node.go)。

正是这套统一的区间计算规则,让 goja 能把每个 AST 节点精确映射回原始源码片段,支持FunctionLiteral.Source之类的源码还原能力。

四、字面量节点(Literal)全解析

字面量是 AST 中最基础的表达式节点。文档列出了五种核心字面量,源码中还存在模板字面量等补充类型。

4.1 BooleanLiteral(布尔字面量)

type BooleanLiteral struct { Idx file.Idx Literal string // 源码文本,如 "true" Value bool // 求值后的布尔值 }

同时保留原始文本与解析后的值,Idx1()用Idx + len(Literal)定位。

4.2 NullLiteral(空字面量)

type NullLiteral struct { Idx file.Idx Literal string // 固定为 "null" }

无Value字段(null即空值),Idx1()固定返回Idx + 4。

4.3 NumberLiteral(数字字面量)

type NumberLiteral struct { Idx file.Idx Literal string Value interface{} }

Value类型为interface{},因为 goja 内部数字可能以int64、float64等不同形态存储,解析阶段统一为接口类型,运行时再按需取值。

4.4 StringLiteral(字符串字面量)

type StringLiteral struct { Idx file.Idx Literal string Value string }

Literal是包含引号的原始源码文本,Value是转义处理后的实际字符串值。

4.5 RegExpLiteral(正则字面量)

type RegExpLiteral struct { Idx file.Idx Literal string // 完整的正则源码文本 Pattern string // 模式部分 Flags string // 标志位,如 "gim" Value string }

Pattern与Flags从Literal中拆分,Value字段在源码中保留(文档中列出的该类型含Value string字段;node.go 中该字段已移除,仅保留Pattern/Flags,佐证了包注释中"节点类型仍在演进"的警告)。

4.6 ArrayLiteral(数组字面量)

type ArrayLiteral struct { LeftBracket file.Idx RightBracket file.Idx Value []Expression // 元素列表,含 SpreadElement 展开节点 }

元素是表达式序列,支持[...spread]展开(SpreadElement内嵌Expression)。Idx0()返回LeftBracket,Idx1()返回RightBracket + 1。

4.7 ObjectLiteral 与 Property(对象字面量)

type ObjectLiteral struct { LeftBrace file.Idx RightBrace file.Idx Value []Property } type Property struct { Key string Kind string Value Expression }

对象成员由Property描述:Key为属性名,Kind为属性种类,Value为属性值表达式。文档中Kind是string,源码中已演进为强类型枚举(node.go):

type PropertyKind string const ( PropertyKindValue PropertyKind = "value" // 普通数据属性 PropertyKindGet PropertyKind = "get" // getter PropertyKindSet PropertyKind = "set" // setter PropertyKindMethod PropertyKind = "method" // 方法(ES6 简写方法) )

同时在源码中,Property已细分为两个实现类型(node.go):

type PropertyShort struct { Name Identifier Initializer Expression // 例如 { x = 1 } 中的默认值 } type PropertyKeyed struct { Key Expression Kind PropertyKind Value Expression Computed bool // 是否为 [expr] 计算属性名 }

4.8 TemplateLiteral 与 TemplateElement(模板字符串)

源码为 ES6 模板字符串补充了节点(node.go):

type TemplateElement struct { Idx file.Idx Literal string Parsed unistring.String // 转义处理后的文本 Valid bool } type TemplateLiteral struct { OpenQuote file.Idx CloseQuote file.Idx Tag Expression // 可选,带标签的模板 Elements []*TemplateElement Expressions []Expression // 插值表达式 ${...} }

TemplateElement负责静态文本段,Expressions负责动态插值段,两者按序交错构成完整模板。

五、表达式节点(Expression)分类详解

表达式节点数量最多,可按语义分为若干族。

5.1 标识符与成员访问

type Identifier struct { Name string Idx file.Idx } type DotExpression struct { Left Expression // 对象 Identifier Identifier // 属性名,如 obj.name } type BracketExpression struct { Left Expression // 对象 Member Expression // 下标表达式,如 obj[key] LeftBracket file.Idx RightBracket file.Idx }

DotExpression(点访问)与BracketExpression(方括号访问)是属性访问的两种形态。DotExpression.Idx1()委托给Identifier.Idx1(),即Idx + len(Name)。

源码中还包含私有属性访问(node.go)与可选链:

type PrivateDotExpression struct { Left Expression Identifier PrivateIdentifier // #field } type OptionalChain struct { Expression } type Optional struct { Expression }

5.2 运算类:Binary / Assign / Unary / Conditional / Sequence

type BinaryExpression struct { Operator token.Token // + - * / && || == < > ... Left Expression Right Expression Comparison bool // 是否为比较运算(==、!=、<、> 等) } type AssignExpression struct { Operator token.Token // = += -= *= ... Left Expression Right Expression } type UnaryExpression struct { Operator token.Token // ! ~ - + typeof void delete Idx file.Idx // If a prefix operation Operand Expression Postfix bool // 是否为后置运算(x++ / x--) } type ConditionalExpression struct { Test Expression // 条件 Consequent Expression // 真分支 Alternate Expression // 假分支 } type SequenceExpression struct { Sequence []Expression // 逗号表达式,如 (a, b, c) }

各运算符来自token.Token类型(goja 的 token 包)。BinaryExpression的Comparison标志在编译期可用于区分比较运算与算术运算的求值路径。

5.3 调用与构造:Call / New

type CallExpression struct { Callee Expression // 被调用者,可能是函数、方法链等 LeftParenthesis file.Idx ArgumentList []Expression RightParenthesis file.Idx } type NewExpression struct { New file.Idx Callee Expression LeftParenthesis file.Idx ArgumentList []Expression RightParenthesis file.Idx }

两者的Idx1()均为RightParenthesis + 1;NewExpression的Idx1()在无参数列表时回退到Callee.Idx1()(node.go),对应new Foo无括号写法。

5.4 This / Super / MetaProperty / Yield / Await

type ThisExpression struct { Idx file.Idx } type SuperExpression struct { Idx file.Idx } type MetaProperty struct { Meta, Property *Identifier // new.target 等 Idx file.Idx } type YieldExpression struct { Yield file.Idx Argument Expression Delegate bool // 是否为 yield* 委托 } type AwaitExpression struct { Await file.Idx Argument Expression }

SuperExpression支持类继承中的super调用,MetaProperty支持new.target,YieldExpression/AwaitExpression对应生成器与 async/await 语法(后两者是文档未列出、源码已支持的类型,再次印证 AST 持续演进)。

5.5 函数与类字面量

type FunctionLiteral struct { Function file.Idx Name *Identifier // 可为 nil(匿名函数) ParameterList *ParameterList Body Statement Source string // 函数体原始源码 DeclarationList []Declaration } type ParameterList struct { Opening file.Idx List []*Identifier Closing file.Idx }

FunctionLiteral保存了Source字段用于源码级还原;在 node.go 中,Body类型收紧为*BlockStatement,并新增Async, Generator bool标志位,ParameterList.List也升级为[]*Binding以支持解构与默认参数。

源码补充的箭头函数与类字面量(node.go、node.go):

type ArrowFunctionLiteral struct { Start file.Idx ParameterList *ParameterList Body ConciseBody // 表达式体或块体 Source string DeclarationList []*VariableDeclaration Async bool } type ClassLiteral struct { Class file.Idx RightBrace file.Idx Name *Identifier SuperClass Expression Body []ClassElement Source string }

ConciseBody有两个实现:ExpressionBody(x => expr)与BlockStatement(x => { ... })。ClassElement则有FieldDefinition(实例/静态字段)、MethodDefinition(方法/getter/setter)、ClassStaticBlock(静态初始化块)三类实现。

5.6 错误占位与解构模式

type BadExpression struct { From file.Idx To file.Idx }

BadExpression用于解析失败时占位,保留失败区间。解构相关(node.go):

type BindingTarget interface { Expression _bindingTarget() } type Pattern interface { BindingTarget _pattern() } type Binding struct { Target BindingTarget Initializer Expression // 默认值 } type ArrayPattern struct { LeftBracket file.Idx RightBracket file.Idx Elements []Expression Rest Expression } type ObjectPattern struct { LeftBrace file.Idx RightBrace file.Idx Properties []Property Rest Expression }

Identifier、ArrayPattern、ObjectPattern、BadExpression均可作为绑定目标,支持const {a, b} = obj、const [x, ...rest] = arr等 ES6 解构语法。

六、语句节点(Statement)分类详解

6.1 块与空语句

type BlockStatement struct { LeftBrace file.Idx List []Statement RightBrace file.Idx } type EmptyStatement struct { Semicolon file.Idx // 独立分号 ; }

BlockStatement是所有复合语句的"容器";EmptyStatement表示空操作。

6.2 变量声明语句与表达式语句

type VariableStatement struct { Var file.Idx List []Expression // var 声明的绑定列表 } type ExpressionStatement struct { Expression Expression // 如 foo(); a = 1; }

在源码中VariableStatement.List已升级为[]*Binding(node.go),并新增LexicalDeclaration(let/const,带Token token.Token区分二者,node.go):

type LexicalDeclaration struct { Idx file.Idx Token token.Token // let 或 const List []*Binding }

6.3 控制流:If / Switch / Branch / Labelled

type IfStatement struct { If file.Idx Test Expression // 条件 Consequent Statement // 真分支 Alternate Statement // 假分支,可为 nil } type SwitchStatement struct { Switch file.Idx Discriminant Expression // switch 的判别表达式 Default int // default 子句在 Body 中的下标,-1 表示无 Body []*CaseStatement } type CaseStatement struct { Case file.Idx Test Expression // case 表达式;default 时为 nil Consequent []Statement } type BranchStatement struct { Idx file.Idx Token token.Token // break / continue Label *Identifier // 带标签的 break/continue } type LabelledStatement struct { Label *Identifier Colon file.Idx Statement Statement }

SwitchStatement.Default记录default分支在Body中的索引(无则 -1),BranchStatement统一表达break/continue(含标签形式),LabelledStatement支持label: statement语法。

6.4 循环:For / ForIn / ForOf / While / DoWhile

type ForStatement struct { For file.Idx Initializer Expression // 初始化表达式 Update Expression // 步进表达式 Test Expression // 条件 Body Statement } type ForInStatement struct { For file.Idx Into Expression // 迭代目标 Source Expression // 被遍历对象 Body Statement } type WhileStatement struct { While file.Idx Test Expression Body Statement } type DoWhileStatement struct { Do file.Idx Test Expression Body Statement }

源码中ForInStatement之外还新增了ForOfStatement(node.go),两者共用ForInto抽象(ForIntoVar、ForDeclaration、ForIntoExpression三个实现,分别对应for (var x in ...)、for (const x of ...)、for (x of ...));ForStatement.Initializer也抽象为ForLoopInitializer接口(表达式 / var 声明列表 / let·const 声明三种实现)。

6.5 跳转与异常:Return / Throw / Try / Catch

type ReturnStatement struct { Return file.Idx Argument Expression // 可为 nil(裸 return) } type ThrowStatement struct { Throw file.Idx Argument Expression } type TryStatement struct { Try file.Idx Body Statement Catch *CatchStatement // 可为 nil Finally Statement // 可为 nil } type CatchStatement struct { Catch file.Idx Parameter *Identifier // 异常变量,如 catch (e) Body Statement }

TryStatement的Catch/Finally均为指针,允许try...catch、try...finally、完整try...catch...finally三种组合;ReturnStatement.Idx1()在无参数时固定Return + 6("return"六个字符)。

6.6 其他:With / Debugger / Bad

type WithStatement struct { With file.Idx Object Expression Body Statement } type DebuggerStatement struct { Debugger file.Idx // debugger; } type BadStatement struct { From file.Idx To file.Idx }

WithStatement对应非严格模式的with语句;BadStatement与BadExpression一样是解析错误占位。

七、声明节点(Declaration):函数与类

type FunctionDeclaration struct { Function *FunctionLiteral // function foo() {} } type VariableDeclaration struct { Var file.Idx List []*VariableExpression // 单个 var 声明中的多个变量 } type VariableExpression struct { Name string Idx file.Idx Initializer Expression // var x = 1 }

FunctionDeclaration把函数字面量包装为声明(区别于作为表达式的FunctionLiteral);VariableDeclaration是var a, b = 1, c;这类多变量声明的单元。源码中VariableDeclaration.List已升级为[]*Binding,并新增ClassDeclaration(node.go):

type ClassDeclaration struct { Class *ClassLiteral // class Foo {} }

八、parser 包:AST 的入口

ast包不负责解析,解析由同仓库的parser包完成。ast/README.markdown的姊妹文档 vendor/github.com/dop251/goja/parser/README.markdown 给出了使用范例:

import ( "github.com/dop251/goja/parser" ) filename := "" // A filename is optional src := ` // Sample xyzzy example (function(){ if (3.14159 > 0) { console.log("Hello, World."); return; } var xyzzy = NaN; console.log("Nothing happens."); return xyzzy; })(); ` // Parse some JavaScript, yielding a *ast.Program and/or an ErrorList program, err := parser.ParseFile(nil, filename, src, 0)

核心函数签名:

func ParseFile(fileSet *file.FileSet, filename string, src interface{}, mode Mode) (*ast.Program, error)
  • fileSet为nil时解析不带文件集;非nil时先把filename与src加入文件集(这也是Program.File与file.Idx位置换算的基础);
  • filename可选,仅用于错误标注;
  • src可以是字符串、字节切片、bytes.Buffer或io.Reader,必须是 UTF-8 编码;
  • 返回值*ast.Program即本文第二节介绍的根节点。

另有便捷函数ParseFunction(parameterList, body string) (*ast.FunctionLiteral, error),把逗号分隔的参数列表与函数体直接解析为ast.FunctionLiteral,适合在运行时动态构造小函数。

在 Nakama 中,goja.Parse(内部封装parser.ParseFile)被用于模块预检与 AST 缓存(见第一节的runtime_javascript.go代码),随后goja.Compile才真正编译执行。这种"先解析成 AST、再编译"的两段式设计,也让 Nakama 在模块加载阶段就能发现语法错误并记录日志("Could not compile JavaScript module")。

九、AST 在 Nakama 中的完整调用链与测试佐证

9.1 运行时调用链

从源码结构可以梳理出 Nakama 中 JS 模块的 AST 相关完整链路:

  1. Nakama 启动时扫描模块目录(data/modules/*.js),读取 JS 文件内容(runtime_javascript.go 使用os.ReadFile);
  2. 以模块文件名调用goja.Parse(modName, content)生成*ast.Program(runtime_javascript.go);
  3. 调用goja.Compile(modName, content, true)编译为可执行程序(runtime_javascript.go);
  4. 将Program与Ast一起缓存进RuntimeJSModule(runtime_javascript.go);
  5. 运行时通过goja.New()创建虚拟机实例执行(如 runtime_javascript.go、runtime_javascript_match_core.go)。

9.2 测试用例佐证

仓库内的测试也直接体现了"AST 先于执行"的编译流程,例如 server/runtime_javascript_test.go:

r := goja.New() p, _ := goja.Compile("test", ` // ... JS 测试代码 ... `) r.RunProgram(p)

同样的模式还出现在 runtime_javascript_logger_test.go、runtime_javascript_match_core.go 等处——先Compile(内部含解析阶段)再RunProgram,验证了编译产物(即 AST 的下游)是虚拟机执行的直接输入。

9.3 与其他模块的边界

  • ast↔parser:parser.ParseFile产出ast.Program;
  • ast↔token:表达式/语句中的运算符、break/continue、let/const等均由token.Token表示;
  • ast↔file:所有位置字段为file.Idx,可经FileSet.Position转为行/列;
  • ast↔compiler/vm:compiler遍历 AST 生成字节码,vm执行。

十、演进提示与实践建议

  1. 接口不稳定:官方文档与包注释均声明 parser/AST 接口(尤其是节点类型)仍在演进。本文中可明显观察到文档(godocdown 生成)与 node.go 的差异:Property被PropertyShort/PropertyKeyed取代、ParameterList.List由[]*Identifier变为[]*Binding、RegExpLiteral去掉了Value字段、新增了大量 ES6+ 节点(ClassLiteral、ArrowFunctionLiteral、TemplateLiteral、ForOfStatement、OptionalChain等)。任何基于ast包的二次开发都应锁定 goja 版本并保持更新跟进;
  2. AST 仅用于结构表示:若需对 Nakama 的 JS 模块做静态分析(如遍历函数声明、收集依赖),可在模块缓存命中Ast字段(server/runtime_javascript.go中RuntimeJSModule.Ast)后自行实现Node遍历器;
  3. 位置信息是排错利器:file.Idx与FileSet.Position配合,能把编译/运行错误准确定位到文件:行:列,Nakama 的"Could not compile JavaScript module"错误日志正是依赖这一机制。

相关文件索引

  • AST 节点类型定义:vendor/github.com/dop251/goja/ast/node.go
  • AST 包 API 文档:vendor/github.com/dop251/goja/ast/README.markdown
  • 解析器包与示例:vendor/github.com/dop251/goja/parser/README.markdown
  • 位置编码与文件集:vendor/github.com/dop251/goja/file/file.go
  • Nakama JS 运行时(Parse/Compile/缓存):server/runtime_javascript.go
  • Nakama JS 运行时测试:server/runtime_javascript_test.go、server/runtime_javascript_logger_test.go
  • 后端
  • 即时通讯
  • 社交
  • 游戏开发

【免费下载链接】nakama

Scalable open-source game backend server: multiplayer, matchmaking, leaderboards, chat, and social features for games.

项目地址:https://gitcode.com/GitHub_Trending/na/nakama
点击查看免费下载
上一篇:Wasp 社交登录默认行为与 override 机制:userSignupFields 与 configFn 深度指南
下一篇:pytest 6.0.0 发布全解读:弃用升级、pyproject.toml 原生配置与新命令行能力

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/2 13:46:06

gotalk 实战:用 Go 与 JavaScript 构建多房间 WebSocket 聊天室

示例工程教程 【免费下载链接】go-daily-lib Go 每日一库 项目地址&#xff1a; https://gitcode.com/GitHub_Trending/go/go-daily-lib 点击查看 免费下载 导读 gotalk 是一个同时提供 Go 与 JavaScript 端实现的通信库&#xff0c;可以让浏览器与 Go 后端通过 WebSocket 直…

作者头像 李华
网站建设 2026/10/2 13:45:56

Hoppscotch 自托管部署:10 分钟跑起你自己的完整 API 调试工具

Hoppscotch 自托管部署&#xff1a;10 分钟跑起你自己的完整 API 调试工具 【免费下载链接】hoppscotch Open-Source API Development Ecosystem • https://hoppscotch.io • Offline, On-Prem & Cloud • Web, Desktop & CLI • Open-Source Alternative to Postman,…

作者头像 李华
网站建设 2026/10/2 13:45:13

Axure 9.0 动态面板基本操作

&#xff08;1&#xff09; 进入状态编辑界面双击画布中的动态面板&#xff0c;即可进入编辑模式。此时页面背景会变成灰色遮罩&#xff0c;代表当前仅编辑面板内部内容&#xff0c;外部元件不可操作。顶部悬浮工具栏可管理所有状态。&#xff08;2&#xff09; 新增新增状态&a…

作者头像 李华
网站建设 2026/10/2 13:45:05

新一代AI程序开发利器Windsurf应用指南:把BYOK Base URL改到TaoToken

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/2 13:42:55

移动AI编程平台WebCode:架构设计与工程实践全解

几年前第一次跟同事说“我打算在手机上写代码”&#xff0c;对方回了我一句“你是嫌自己头发太多吗”。当时我也不太信&#xff0c;毕竟没物理键盘、屏幕就那么点大、后台随时可能被杀&#xff0c;怎么看都像自虐。但这个想法一直没散。后来移动设备的性能上来了&#xff0c;云…

作者头像 李华
网站建设 2026/10/2 13:42:04

融合需求侧虚拟储能的楼宇微网优化调度Matlab实现

1. 项目整体思路拆解&#xff1a;虚拟储能凭什么能“凭空”削峰填谷做楼宇微网优化调度的人&#xff0c;可能都遇到过同一个问题&#xff1a;微网里接了一堆分布式光伏&#xff0c;屋顶装了电池储能&#xff0c;但调度来调度去&#xff0c;总感觉经济性提升不明显。光伏大发的时…

作者头像 李华