From 8179d73ae068ccfcee1c8436cde9a88d092e643f Mon Sep 17 00:00:00 2001 From: Star <> Date: Fri, 15 Mar 2024 13:40:20 +0800 Subject: [PATCH] add export plugins code to typescript add RunFile --- bridge.go | 6 +- go.mod | 8 +- gojs.go | 113 ++++++++++++++-- gojs_test.go | 72 ++++++++--- ts.go | 360 +++++++++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 522 insertions(+), 37 deletions(-) create mode 100644 ts.go diff --git a/bridge.go b/bridge.go index a7f4575..01902cc 100644 --- a/bridge.go +++ b/bridge.go @@ -165,9 +165,9 @@ func _makeJsValue(ctx *plugin.Context, in interface{}, n int, key string, plugin // pluginConf := GetPluginConfig(pluginName) // realArgs[i] = reflect.ValueOf(pluginConf) // continue - } else if injectObject := ctx.GetInject(inTypeString); injectObject != nil { - realArgs[i] = reflect.ValueOf(injectObject) - continue + //} else if injectObject := ctx.GetInject(inTypeString); injectObject != nil { + // realArgs[i] = reflect.ValueOf(injectObject) + // continue } if !realArgs[i].IsValid() { diff --git a/go.mod b/go.mod index 1ac0399..4bcf944 100644 --- a/go.mod +++ b/go.mod @@ -5,12 +5,12 @@ go 1.17 require ( apigo.cloud/git/apigo/plugin v1.0.1 apigo.cloud/git/apigo/qjs v0.0.1 - github.com/ssgo/log v0.6.12 - github.com/ssgo/u v0.6.12 + github.com/ssgo/log v1.7.2 + github.com/ssgo/u v1.7.2 ) require ( - github.com/ssgo/config v0.6.12 // indirect - github.com/ssgo/standard v0.6.12 // indirect + github.com/ssgo/config v1.7.2 // indirect + github.com/ssgo/standard v1.7.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/gojs.go b/gojs.go index 63435e0..1378d77 100644 --- a/gojs.go +++ b/gojs.go @@ -7,13 +7,24 @@ import ( "fmt" "github.com/ssgo/log" "github.com/ssgo/u" + "path" "regexp" "strings" ) var pluginNameMatcher = regexp.MustCompile(`(\w+?)\.`) +var exportMatcher = regexp.MustCompile(`export\s+([\w{}, ]+)\s*;?`) +var importMatcher = regexp.MustCompile(`import\s+([\w{}, ]+)\s+from\s+['"]([\w./\- ]+)['"]`) + +type RuntimeOption struct { + Globals map[string]interface{} + Imports map[string]string + Logger *log.Logger +} type JSRuntime struct { + imports map[string]string + imported map[string]string freeJsValues []quickjs.Value rt quickjs.Runtime JsCtx *quickjs.Context @@ -31,7 +42,52 @@ func (rt *JSRuntime) Close() { rt.rt.Close() } -func (rt *JSRuntime) initCode(code string) { +func (rt *JSRuntime) run(code string) (out interface{}, err error, stack string) { + // support import + code = importMatcher.ReplaceAllStringFunc(code, func(importStr string) string { + m := importMatcher.FindStringSubmatch(importStr) + importVar := rt.imported[m[2]] + if importVar == "" { + baseName := path.Base(m[2]) + jsFile := m[2] + isTS := false + if strings.HasSuffix(baseName, ".ts") { + isTS = true + baseName = baseName[0 : len(baseName)-3] + } + if strings.HasSuffix(baseName, ".js") { + baseName = baseName[0 : len(baseName)-3] + } else { + jsFile += ".js" + } + if !isTS && (rt.imports[m[2]] != "" || u.FileExists(jsFile)) { + importCode := rt.imports[m[2]] + if importCode == "" { + importCode, _ = u.ReadFile(jsFile) + } + if importCode != "" { + importVar = "import_" + u.UniqueId() + rt.imported[m[2]] = importVar + importedCode := exportMatcher.ReplaceAllStringFunc(importCode, func(exportStr string) string { + return strings.Replace(exportStr, "export", "return", 1) + }) + err, stack := rt.Exec("let " + importVar + " = (function(){" + importedCode + "})()") + if err != nil { + rt.logger.Error(err.Error(), "stack", stack) + } + } else { + importVar = "{}" + } + return "let " + m[1] + " = " + importVar + } else { + // ignore ts and plugin + return "" + } + } else { + return "let " + m[1] + " = " + importVar + } + }) + tryPlugins := map[string]bool{} for _, m := range pluginNameMatcher.FindAllStringSubmatch(code, 1024) { tryPlugins[m[1]] = true @@ -51,9 +107,7 @@ func (rt *JSRuntime) initCode(code string) { } } } -} -func (rt *JSRuntime) run(code string) (out interface{}, err error, stack string) { if r, err := rt.JsCtx.Eval(code); err == nil { result := MakeFromJsValue(r) r.Free() @@ -67,16 +121,30 @@ func (rt *JSRuntime) run(code string) (out interface{}, err error, stack string) } func (rt *JSRuntime) Exec(code string) (err error, stack string) { - rt.initCode(code) _, err, stack = rt.run(code) return err, stack } +func (rt *JSRuntime) ExecFile(filename string) (err error, stack string) { + if code, err := u.ReadFile(filename); err == nil { + return rt.Exec(code) + } else { + return err, "" + } +} + func (rt *JSRuntime) Run(code string) (out interface{}, err error, stack string) { - rt.initCode(code) return rt.run("(function(){" + code + "})()") } +func (rt *JSRuntime) RunFile(filename string) (out interface{}, err error, stack string) { + if code, err := u.ReadFile(filename); err == nil { + return rt.Run(code) + } else { + return nil, err, "" + } +} + func SetPluginsConfig(conf map[string]plugin.Config) { for _, plg := range plugin.List() { if plg.Init != nil { @@ -85,33 +153,42 @@ func SetPluginsConfig(conf map[string]plugin.Config) { } } -func New(globals map[string]interface{}, logger *log.Logger) *JSRuntime { - if logger == nil { - logger = log.DefaultLogger +func New(option *RuntimeOption) *JSRuntime { + if option == nil { + option = &RuntimeOption{nil, map[string]string{}, log.DefaultLogger} + } + + if option.Imports == nil { + option.Imports = map[string]string{} + } + if option.Logger == nil { + option.Logger = log.DefaultLogger } // 初始化JS虚拟机 jsRt := quickjs.NewRuntime() jsCtx := jsRt.NewContext() goCtx := plugin.NewContext(map[string]interface{}{ - "*log.Logger": logger, + "*log.Logger": option.Logger, "*quickjs.Context": jsCtx, }) rt := &JSRuntime{ + imports: option.Imports, + imported: map[string]string{}, freeJsValues: make([]quickjs.Value, 0), rt: jsRt, JsCtx: jsCtx, GoCtx: goCtx, - logger: logger, + logger: option.Logger, plugins: map[string]*plugin.Plugin{}, } rt.GoCtx.SetData("_freeJsValues", &rt.freeJsValues) // 全局变量 - if globals != nil { - for k, obj := range globals { + if option.Globals != nil { + for k, obj := range option.Globals { rt.JsCtx.Globals().Set(k, MakeJsValue(rt.GoCtx, obj, false)) } } @@ -173,8 +250,8 @@ func makeStringArray(args []interface{}, color u.TextColor, bg u.BgColor) []inte return stringArgs } -func Run(code string, globals map[string]interface{}, logger *log.Logger) (out interface{}, err error, stack string) { - rt := New(globals, logger) +func Run(code string, option *RuntimeOption) (out interface{}, err error, stack string) { + rt := New(option) defer func() { if err := recover(); err != nil { rt.logger.Error(u.String(err)) @@ -184,6 +261,14 @@ func Run(code string, globals map[string]interface{}, logger *log.Logger) (out i return rt.Run(code) } +func RunFile(filename string, option *RuntimeOption) (out interface{}, err error, stack string) { + if code, err := u.ReadFile(filename); err == nil { + return Run(code, option) + } else { + return nil, err, "" + } +} + var jsErrorCodeMatcher = regexp.MustCompile(`code:(\d+)`) func GetJSError(err error, code string) string { diff --git a/gojs_test.go b/gojs_test.go index ccc6ad4..68435be 100644 --- a/gojs_test.go +++ b/gojs_test.go @@ -7,11 +7,11 @@ import ( "github.com/ssgo/log" "github.com/ssgo/u" "runtime" + "strings" "testing" "time" ) - type Object struct { id string } @@ -20,18 +20,48 @@ func (obj *Object) GetId() string { return obj.id } +type TestBirthday struct { + Year int + Month int + Day int +} + +type TestBaseUser struct { + Id int + Name string +} + +type TestUser struct { + TestBaseUser + Birthday *TestBirthday +} + +func (b *TestBirthday) String() string { + return fmt.Sprintln(b.Year, b.Month, b.Day) +} + +func (u *TestUser) GetBirthdayString() string { + return u.Birthday.String() +} + func init() { defaultObject := Object{id: "o-00"} - plugin.Register(plugin.Plugin{ + plg := plugin.Plugin{ Id: "obj", Name: "test obj plugin", Objects: map[string]interface{}{ + "name": "1233", + "list1": []interface{}{1, "2", map[string]interface{}{"aaa": 111, "bbb": "222"}, true}, + "log": log.DefaultLogger.Info, "getId": defaultObject.GetId, "new": func(id string) interface{} { return &Object{id: id} }, - "echo": func(text string, echoFunc func(text string) string) interface{} { - return echoFunc(text) + "test1": func(id int, id2 uint16, id3 float64) *TestUser { + return nil + }, + "echo": func(text string, echoFunc func(text string, t2 int) string, t3 []uint32, t4 *bool) interface{} { + return echoFunc(text, 0) }, "echoTimes": func(echoFunc func(text string)) { for i := 0; i < 5; i++ { @@ -40,6 +70,10 @@ func init() { } }, }, + } + plugin.Register(plg) + gojs.SetPluginsConfig(map[string]plugin.Config{ + "obj": plugin.Config{}, }) } @@ -52,6 +86,12 @@ func test(t *testing.T, name string, check bool, extArgs ...interface{}) { } } +func TestTS(t *testing.T) { + plg := plugin.Get("obj") + plgCode := gojs.MakePluginCode(plg) + test(t, "ts code", strings.Contains(plgCode, "(echoFunc: (text: string) => void)")) +} + func TestGlobal(t *testing.T) { code := ` log('test', 'name', 'log') @@ -63,18 +103,20 @@ return plus(number,2) "plus": func(i, j int) int { return i + j }, } - r, _, _ := gojs.Run(code, globals, log.DefaultLogger) + r, _, _ := gojs.Run(code, &gojs.RuntimeOption{ + Globals: globals, + }) test(t, "call", u.Int(r) == 11, r) } func TestPlugin(t *testing.T) { - r, _, _ := gojs.Run("return obj.getId()", nil, log.DefaultLogger) + r, _, _ := gojs.Run("return obj.getId()", nil) test(t, "obj.getId()", u.String(r) == "o-00", r) r, _, _ = gojs.Run(` o = obj.new('o-01') return o.getId() -`, nil, log.DefaultLogger) +`, nil) test(t, "new obj.getId()", u.String(r) == "o-01", r) t1 := time.Now() @@ -82,11 +124,11 @@ return o.getId() out = '' obj.echo('123', function(text){ out = text -}) +}, null) return out -`, nil, log.DefaultLogger) +`, nil) t2 := time.Now() - fmt.Println("time:", t2.UnixMicro() - t1.UnixMicro()) + fmt.Println("time:", t2.UnixMicro()-t1.UnixMicro()) test(t, "callback", u.String(r) == "123", r) t1 = time.Now() @@ -96,20 +138,19 @@ obj.echoTimes(function(text){ out += text }) return out -`, nil, log.DefaultLogger) +`, nil) t2 = time.Now() - fmt.Println("time:", t2.UnixMicro() - t1.UnixMicro()) + fmt.Println("time:", t2.UnixMicro()-t1.UnixMicro()) test(t, "callbacks", u.String(r) == "01234", r) } - func BenchmarkEcho(tb *testing.B) { tb.StopTimer() ms1 := runtime.MemStats{} runtime.ReadMemStats(&ms1) tb.StartTimer() for i := 0; i < tb.N; i++ { - gojs.Run(`return 1`, nil, log.DefaultLogger) + gojs.Run(`return 1`, nil) } tb.StopTimer() @@ -122,7 +163,6 @@ func BenchmarkEcho(tb *testing.B) { fmt.Println(">>", ms1.HeapInuse, ms2.HeapInuse, ms3.HeapInuse) } - func BenchmarkCallback(tb *testing.B) { tb.StopTimer() ms1 := runtime.MemStats{} @@ -135,7 +175,7 @@ obj.echoTimes(function(text){ out += text }) return out -`, nil, log.DefaultLogger) +`, nil) } tb.StopTimer() diff --git a/ts.go b/ts.go new file mode 100644 index 0000000..df63a1d --- /dev/null +++ b/ts.go @@ -0,0 +1,360 @@ +package gojs + +import ( + "apigo.cloud/git/apigo/plugin" + "github.com/ssgo/u" + "path" + "reflect" + "regexp" + "runtime" + "strings" +) + +type ArgInfo struct { + index int + isVariadic bool + isOutArg bool + isFunc bool + isSkip bool + funcInArgs []ArgInfo + funcOutArgs []ArgInfo + Name string + Type string +} + +var numberMatcher = regexp.MustCompile("[a-z0-9]*(int|float)[a-z0-9]*") +var mapMatcher = regexp.MustCompile("map\\[(\\w+)](\\w+)") + +func makeArgTypeString(argType string) string { + if strings.Contains(argType, "interface {}") { + argType = strings.ReplaceAll(argType, "interface {}", "any") + } + if strings.HasPrefix(argType, "*") { + argType = argType[1:] + } + if strings.HasPrefix(argType, "[]") { + argType = argType[2:] + "[]" + } + if strings.Contains(argType, "int") || strings.Contains(argType, "float") { + argType = numberMatcher.ReplaceAllString(argType, "number") + } + if strings.Contains(argType, "bool") { + argType = strings.ReplaceAll(argType, "bool", "boolean") + } + if strings.Contains(argType, "booleanean") { + argType = strings.ReplaceAll(argType, "booleanean", "boolean") + } + if strings.HasPrefix(argType, "map[") { + argType = mapMatcher.ReplaceAllString(argType, "Map<$1, $2>") + } + //if strings.ContainsRune(argType, '.') { + // argType = argType[strings.LastIndexByte(argType, '.')+1:] + //} + return argType +} + +func (argInfo *ArgInfo) String() string { + argType := argInfo.Type + if argInfo.isFunc { + argType = "(" + makeInArgsString(argInfo.funcInArgs) + ") => " + makeOutArgsString(argInfo.funcOutArgs) + } else { + argType = makeArgTypeString(argType) + } + if argInfo.isOutArg { + return argType + } else { + argName := argInfo.Name + if argName == "" { + argName = "arg" + u.String(argInfo.index+1) + } + if argInfo.isVariadic { + argName = "..." + argName + } + return argName + ": " + argType + } +} + +func makeInArgs(args []ArgInfo) []string { + arr := make([]string, 0) + for _, arg := range args { + if !arg.isSkip { + arr = append(arr, arg.String()) + } + } + return arr +} + +func makeInArgsString(args []ArgInfo) string { + return strings.Join(makeInArgs(args), ", ") +} + +func makeOutArgsString(args []ArgInfo) string { + arr := makeInArgs(args) + if len(arr) == 0 { + return "void" + } else if len(arr) == 1 { + return arr[0] + } else { + return "[" + strings.Join(arr, ", ") + "]" + } +} + +func makeFuncArgsNames(v reflect.Value, inArgs []ArgInfo, isMethod bool) { + fp := runtime.FuncForPC(v.Pointer()) + file, lineNo := fp.FileLine(fp.Entry()) + if file != "" && u.FileExists(file) { + lines, _ := u.ReadFileLines(file) + if len(lines) >= lineNo { + line := lines[lineNo-1] + if !strings.Contains(line, "func") && lineNo-2 >= 0 && strings.Contains(lines[lineNo-2], "func") { + line = lines[lineNo-2] + } + line = strings.ReplaceAll(line, "interface{}", "any") + pos := strings.Index(line, "func") + if pos != -1 { + line = strings.TrimSpace(line[pos+4:]) + if isMethod { + // skip method this arg + pos = strings.Index(line, "(") + if pos != -1 { + line = strings.TrimSpace(line[pos+1:]) + makeFuncArgsName(line, inArgs[1:]) + } + } else { + makeFuncArgsName(line, inArgs) + } + } + } + } +} + +func makeFuncArgsName(line string, inArgs []ArgInfo) { + pos := strings.Index(line, "(") + if pos != -1 { + line = strings.TrimSpace(line[pos+1:]) + for i := 0; i < len(inArgs); i++ { + // find param name + pos = strings.Index(line, " ") + // support combined args a, b string + pos1 := strings.Index(line, ",") + if pos1 != -1 && pos1 < pos { + inArgs[i].Name = line[0:pos1] + line = strings.TrimSpace(line[pos1+1:]) + continue + } + if pos != -1 { + inArgs[i].Name = line[0:pos] + line = strings.TrimSpace(line[pos+1:]) + } + // skip inline func + if strings.HasPrefix(line, "func") { + line = strings.TrimSpace(line[4:]) + leftQuotes := 0 + quoteStarted := false + for pos = 0; pos < len(line); pos++ { + if line[pos] == '(' { + leftQuotes++ + quoteStarted = true + continue + } + if quoteStarted && line[pos] == ')' { + leftQuotes-- + } + if quoteStarted && leftQuotes == 0 { + break + } + } + makeFuncArgsName(line, inArgs[i].funcInArgs) + line = strings.TrimSpace(line[pos+1:]) + } + // skip , + pos = strings.Index(line, ",") + if pos != -1 { + line = strings.TrimSpace(line[pos+1:]) + } + } + } +} + +func makeFieldElemType(t reflect.Type, existsClasses *map[string]bool, classes *[]string, isSkip bool) string { + originT := t + if t.Kind() == reflect.Pointer { + t = t.Elem() + } + if t.Kind() == reflect.Map { + if t.Elem().Kind() == reflect.Struct && !isSkip { + makeClass(t.Elem(), existsClasses, classes) + } + return "Map<" + makeArgTypeString(t.Key().String()) + ", " + makeFieldElemType(t.Elem(), existsClasses, classes, isSkip) + ">" + } else if t.Kind() == reflect.Slice && t.Elem().Kind() != reflect.Uint8 { + if t.Elem().Kind() == reflect.Struct && !isSkip { + makeClass(t.Elem(), existsClasses, classes) + } + return "Array<" + makeFieldElemType(t.Elem(), existsClasses, classes, isSkip) + ">" + } else if t.Kind() == reflect.Struct { + if !isSkip { + makeClass(originT, existsClasses, classes) + } + return makeArgTypeString(t.Name()) + } else { + return makeArgTypeString(t.String()) + } +} + +func makeClass(t reflect.Type, existsClasses *map[string]bool, classes *[]string) { + originT := t + if t.Kind() == reflect.Pointer { + t = t.Elem() + } + if !(*existsClasses)[t.Name()] { + (*existsClasses)[t.Name()] = true + classItems := make([]string, 0) + implements := make([]string, 0) + for i := 0; i < t.NumField(); i++ { + if !t.Field(i).IsExported() { + continue + } + ft := t.Field(i).Type + if ft.Kind() == reflect.Pointer { + ft = ft.Elem() + } + ftStr := makeFieldElemType(ft, existsClasses, classes, false) + if t.Field(i).Anonymous { + implements = append(implements, ftStr) + continue + } + classItems = append(classItems, " "+u.GetLowerName(t.Field(i).Name)+": "+ftStr) + } + for i := 0; i < originT.NumMethod(); i++ { + if !originT.Method(i).IsExported() { + continue + } + inArgs, outArgs := makeFuncInOutArgs(originT.Method(i).Type, existsClasses, classes) + if len(inArgs) > 0 { + inArgs[0].isSkip = true + } + makeFuncArgsNames(originT.Method(i).Func, inArgs, true) + classItems = append(classItems, " "+u.GetLowerName(originT.Method(i).Name)+"("+makeInArgsString(inArgs)+"): "+makeOutArgsString(outArgs)) + } + implementsStr := "" + if len(implements) > 0 { + implementsStr = " extends " + strings.Join(implements, ", ") + } + newClass := append([]string{}, "interface "+t.Name()+implementsStr+" {") + newClass = append(newClass, classItems...) + newClass = append(newClass, "}") + *classes = append(*classes, strings.Join(newClass, "\n")) + } +} + +func makeFuncInOutArgs(t reflect.Type, existsClasses *map[string]bool, classes *[]string) (inArgs []ArgInfo, outArgs []ArgInfo) { + inArgs = make([]ArgInfo, t.NumIn()) + outArgs = make([]ArgInfo, t.NumOut()) + // *plugin.Context + for i := t.NumIn() - 1; i >= 0; i-- { + arg := t.In(i) + argInfo := ArgInfo{ + index: i, + isOutArg: false, + isFunc: false, + isSkip: false, + isVariadic: t.IsVariadic() && i == t.NumIn()-1, + Name: "", + Type: makeFieldElemType(arg, existsClasses, classes, true), + } + if arg.String() == "*plugin.Context" { + argInfo.isSkip = true + } + originArg := arg + if arg.Kind() == reflect.Pointer { + arg = arg.Elem() + } + if arg.Kind() == reflect.Func { + argInfo.isFunc = true + argInfo.funcInArgs, argInfo.funcOutArgs = makeFuncInOutArgs(arg, existsClasses, classes) + } + if !argInfo.isSkip && arg.Kind() == reflect.Struct { + makeClass(originArg, existsClasses, classes) + } + inArgs[i] = argInfo + } + for i := t.NumOut() - 1; i >= 0; i-- { + arg := t.Out(i) + argInfo := ArgInfo{ + index: i, + isOutArg: true, + isFunc: false, + isSkip: false, + isVariadic: false, + Name: "", + Type: makeFieldElemType(arg, existsClasses, classes, true), + } + if arg.String() == "error" { + argInfo.isSkip = true + } + originArg := arg + if arg.Kind() == reflect.Pointer { + arg = arg.Elem() + } + if !argInfo.isSkip && arg.Kind() == reflect.Struct { + makeClass(originArg, existsClasses, classes) + } + outArgs[i] = argInfo + } + return inArgs, outArgs +} + +func findObject(v reflect.Value, level int, existsClasses *map[string]bool) (codes []string, classes []string) { + codes = make([]string, 0) + classes = make([]string, 0) + v = u.FinalValue(v) + indent := strings.Repeat(" ", level) + if v.Kind() == reflect.Map { + codes = append(codes, "{") + for _, k := range v.MapKeys() { + codes2, classes2 := findObject(v.MapIndex(k), level+1, existsClasses) + classes = append(classes, classes2...) + codes = append(codes, indent+" \""+k.String()+"\": "+strings.Join(codes2, "\n")+",") + } + codes = append(codes, indent+"}") + } else if v.Kind() == reflect.Struct { + //codes = append(codes, "{") + //for _, k := range v.MapKeys() { + // codes2, classes2 := findObject(v.MapIndex(k), level+1, existsClasses) + // classes = append(classes, classes2...) + // codes = append(codes, indent+" \""+k.String()+"\": "+strings.Join(codes2, "\n")+",") + //} + //codes = append(codes, indent+"}") + } else if v.Kind() == reflect.Slice && v.Type().Elem().Kind() != reflect.Uint8 { + codes = append(codes, "[") + for i := 0; i < v.Len(); i++ { + codes2, classes2 := findObject(v.Index(i), level+1, existsClasses) + classes = append(classes, classes2...) + codes = append(codes, indent+" "+strings.Join(codes2, "\n")+",") + } + codes = append(codes, indent+"]") + } else if v.Kind() == reflect.Func { + inArgs, outArgs := makeFuncInOutArgs(v.Type(), existsClasses, &classes) + makeFuncArgsNames(v, inArgs, false) + codes = append(codes, "function ("+makeInArgsString(inArgs)+"): "+makeOutArgsString(outArgs)+" {return}") + } else { + codes = append(codes, u.Json(v.Interface())) + } + return codes, classes +} + +func MakeAllPluginCode() { + for _, plg := range plugin.List() { + code := MakePluginCode(&plg) + _ = u.WriteFile(path.Join("plugins", plg.Id+".ts"), code) + } +} + +func MakePluginCode(plg *plugin.Plugin) string { + if plg == nil { + return "" + } + existsClasses := make(map[string]bool) + codes, classes := findObject(reflect.ValueOf(plg.Objects), 0, &existsClasses) + return strings.Join(classes, "\n\n") + "\n\nexport default " + strings.Join(codes, "\n") +}