package unpack import ( "bytes" "crypto/md5" "encoding/json" "fmt" "log" "os" "path/filepath" "regexp" "strings" "github.com/dop251/goja" ) // PageConfig 存储页面配置 type PageConfig struct { Window map[string]interface{} `json:"window,omitempty"` } // AppConfig 存储应用配置 type AppConfig struct { Pages []string `json:"pages"` Window map[string]interface{} `json:"window,omitempty"` TabBar map[string]interface{} `json:"tabBar,omitempty"` NetworkTimeout map[string]interface{} `json:"networkTimeout,omitempty"` SubPackages []SubPackage `json:"subPackages,omitempty"` NavigateToMiniProgramAppIdList []string `json:"navigateToMiniProgramAppIdList,omitempty"` Workers string `json:"workers,omitempty"` Debug bool `json:"debug,omitempty"` } // SubPackage 存储子包配置 type SubPackage struct { Root string `json:"root"` Pages []string `json:"pages"` } // getWorkerPath 解析 workers.js 并返回公共路径 func getWorkerPath(name string) string { // 读取 workers.js 文件内容 code, err := os.ReadFile(name) if err != nil { panic(err) } // 使用 goja 创建 JavaScript VM vm := goja.New() _, err = vm.RunString(string(code)) if err != nil { return "" } // 初始化公共路径 commPath := "" err = vm.Set("define", func(call goja.FunctionCall) goja.Value { name := call.Argument(0).String() name = filepath.Dir(name) + "/" if commPath == "" { commPath = name } else { commPath = commonDir(commPath, name) } return goja.Undefined() }) if err != nil { return "" } _, err = vm.RunString(string(code)) if err != nil { return "" } // 去掉最后一个字符 if len(commPath) > 0 { commPath = commPath[:len(commPath)-1] } log.Printf("Worker path: \"" + commPath + "\"") return commPath } // commonDir 找到两个路径的公共目录 func commonDir(path1, path2 string) string { i := 0 for i < len(path1) && i < len(path2) && path1[i] == path2[i] { i++ } return path1[:i] } // changeExt 更改文件扩展名 func changeExt(filename, newExt string) string { ext := filepath.Ext(filename) return filename[:len(filename)-len(ext)] + newExt } // save 保存内容到文件 func save(filename string, content []byte) error { // 判断目录是否存在 dir := filepath.Dir(filename) if _, err := os.Stat(dir); os.IsNotExist(err) { err := os.MkdirAll(dir, 0755) if err != nil { return fmt.Errorf("unable to create directory %s: %v", dir, err) } } err := os.WriteFile(filename, content, 0755) if err != nil { return fmt.Errorf("unable to save file %s: %v", filename, err) } return nil } // ProcessConfigFiles 解析和处理配置文件 func ProcessConfigFiles(configFile string) error { dir := filepath.Dir(configFile) content, err := os.ReadFile(configFile) if err != nil { return err } // 定义结构体以解析 JSON 内容 var e struct { Pages []string `json:"pages"` EntryPagePath string `json:"entryPagePath"` Global map[string]interface{} `json:"global"` TabBar map[string]interface{} `json:"tabBar"` NetworkTimeout map[string]interface{} `json:"networkTimeout"` SubPackages []SubPackage `json:"subPackages"` NavigateToMiniProgramAppIdList []string `json:"navigateToMiniProgramAppIdList"` ExtAppid string `json:"extAppid"` Ext map[string]interface{} `json:"ext"` Debug bool `json:"debug"` Page map[string]PageConfig `json:"page"` } err = json.Unmarshal(content, &e) if err != nil { return err } // 处理页面路径,将 entryPagePath 放在首位 k := e.Pages entryIndex := indexOf(k, changeExt(e.EntryPagePath, "")) k = append(k[:entryIndex], k[entryIndex+1:]...) k = append([]string{changeExt(e.EntryPagePath, "")}, k...) // 构建应用配置 app := AppConfig{ Pages: k, Window: e.Global["window"].(map[string]interface{}), TabBar: e.TabBar, NetworkTimeout: e.NetworkTimeout, } // 处理子包 if len(e.SubPackages) > 0 { var subPackages []SubPackage for _, subPackage := range e.SubPackages { root := subPackage.Root if !strings.HasSuffix(root, "/") { root += "/" } root = strings.TrimPrefix(root, "/") var newPages []string for i := 0; i < len(app.Pages); { page := app.Pages[i] if strings.HasPrefix(page, root) { newPage := strings.TrimPrefix(page, root) newPages = append(newPages, newPage) app.Pages = append(app.Pages[:i], app.Pages[i+1:]...) } else { i++ } } subPackage.Root = root subPackage.Pages = newPages subPackages = append(subPackages, subPackage) } app.SubPackages = subPackages fmt.Printf("=======================================================\n这个小程序采用了分包\n子包个数为: %d\n=======================================================\n", len(app.SubPackages)) } // 处理 navigateToMiniProgramAppIdList if len(e.NavigateToMiniProgramAppIdList) > 0 { app.NavigateToMiniProgramAppIdList = e.NavigateToMiniProgramAppIdList } // 处理 workers.js if fileExists(filepath.Join(dir, "workers.js")) { app.Workers = getWorkerPath(filepath.Join(dir, "workers.js")) } // 处理 extAppid if len(e.ExtAppid) > 0 { extContent, _ := json.MarshalIndent(map[string]interface{}{ "extEnable": true, "extAppid": e.ExtAppid, "ext": e.Ext, }, "", " ") err := save(filepath.Join(dir, "ext.json"), extContent) if err != nil { return err } } // 处理调试模式 if e.Debug { app.Debug = e.Debug } // 处理页面中的组件路径 cur := "./file" for a, page := range e.Page { if page.Window != nil && page.Window["usingComponents"] != nil { for _, componentPath := range page.Window["usingComponents"].(map[string]interface{}) { componentPath := componentPath.(string) + ".html" file := componentPath if filepath.IsAbs(componentPath) { file = componentPath[1:] } else { file = toDir(filepath.Join(filepath.Dir(a), componentPath), cur) } if _, ok := e.Page[file]; !ok { e.Page[file] = PageConfig{} } if e.Page[file].Window == nil { e.Page[file] = PageConfig{Window: map[string]interface{}{"component": true}} } } } } // 处理 app-service.js 文件, 主包及子包 if fileExists(filepath.Join(dir, "app-service.js")) { serviceContent, _ := os.ReadFile(filepath.Join(dir, "app-service.js")) matches := findMatches(`__wxAppCode__\['[^']+\.json'\]\s*=\s*({[^;]*});`, string(serviceContent)) if len(matches) > 0 { attachInfo := make(map[string]interface{}) vm := goja.New() err = vm.Set("__wxAppCode__", attachInfo) if err != nil { return err } _, err = vm.RunString(strings.Join(matches, "")) if err != nil { return err } for name, info := range attachInfo { e.Page[changeExt(name, ".html")] = PageConfig{Window: info.(map[string]interface{})} } } // 子包配置 app-service.js for _, subPackage := range app.SubPackages { root := subPackage.Root subServiceFile := filepath.Join(dir, root, "app-service.js") if !fileExists(subServiceFile) { continue } serviceContent, _ = os.ReadFile(subServiceFile) matches = findMatches(`__wxAppCode__\['[^']+\.json'\]\s*=\s*({[^;]*});`, string(serviceContent)) if len(matches) > 0 { attachInfo := make(map[string]interface{}) vm := goja.New() err := vm.Set("__wxAppCode__", attachInfo) if err != nil { return err } _, err = vm.RunString(strings.Join(matches, "")) if err != nil { return err } for name, info := range attachInfo { e.Page[changeExt(name, ".html")] = PageConfig{Window: info.(map[string]interface{})} } } } } // 保存页面 JSON 文件 for a := range e.Page { aFile := changeExt(a, ".json") fileName := filepath.Join(dir, aFile) if aFile != "app.json" { windowContent, _ := json.MarshalIndent(e.Page[a].Window, "", " ") err = save(fileName, windowContent) if err != nil { return err } } } // 处理子包中的文件 if len(app.SubPackages) > 0 { for _, subPackage := range app.SubPackages { for _, item := range subPackage.Pages { a := subPackage.Root + item + ".xx" err := save(filepath.Join(dir, changeExt(a, ".js")), []byte("// "+changeExt(a, ".js")+"\nPage({data: {}})")) if err != nil { return err } err = save(filepath.Join(dir, changeExt(a, ".wxml")), []byte(""+changeExt(a, ".wxml")+"")) if err != nil { return err } err = save(filepath.Join(dir, changeExt(a, ".wxss")), []byte("/* "+changeExt(a, ".wxss")+" */")) if err != nil { return err } } } } // 处理 TabBar 图标路径 if app.TabBar != nil && app.TabBar["list"] != nil { var digests [][2]interface{} for _, file := range scanDirByExt(dir, "") { data, _ := os.ReadFile(file) digests = append(digests, [2]interface{}{md5.Sum(data), file}) } for _, e := range app.TabBar["list"].([]interface{}) { pagePath := e.(map[string]interface{})["pagePath"].(string) e.(map[string]interface{})["pagePath"] = changeExt(pagePath, "") if iconData, ok := e.(map[string]interface{})["iconData"].(string); ok { hash := md5.Sum([]byte(iconData)) for _, digest := range digests { digestByte, _ := digest[0].([16]byte) if bytes.Equal(hash[:], digestByte[:]) { delete(e.(map[string]interface{}), "iconData") e.(map[string]interface{})["iconPath"] = fixDir(digest[1].(string), dir) break } } } if selectedIconData, ok := e.(map[string]interface{})["selectedIconData"].(string); ok { hash := md5.Sum([]byte(selectedIconData)) for _, digest := range digests { digestByte, _ := digest[0].([16]byte) if bytes.Equal(hash[:], digestByte[:]) { delete(e.(map[string]interface{}), "selectedIconData") e.(map[string]interface{})["selectedIconPath"] = fixDir(digest[1].(string), dir) break } } } } } // 保存应用配置到 app.json appContent, _ := json.MarshalIndent(app, "", " ") err = save(filepath.Join(dir, "app.json"), appContent) if err != nil { return err } log.Printf("Config file processed: %s\n", configFile) return nil } // indexOf 返回字符串切片中项的索引 func indexOf(slice []string, item string) int { for i, v := range slice { if v == item { return i } } return -1 } // fileExists 检查文件是否存在 func fileExists(filename string) bool { info, err := os.Stat(filename) if os.IsNotExist(err) { return false } return !info.IsDir() } // toDir 将文件路径转换为相对路径 func toDir(file, base string) string { relative, err := filepath.Rel(base, file) if err != nil { return file } return relative } // findMatches 查找所有匹配模式的字符串 func findMatches(pattern, text string) []string { re := regexp.MustCompile(pattern) return re.FindAllString(text, -1) } // scanDirByExt 扫描目录中的文件并返回指定扩展名的文件列表 func scanDirByExt(dir, ext string) []string { var files []string err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error { if !info.IsDir() && strings.HasSuffix(info.Name(), ext) { files = append(files, path) } return nil }) if err != nil { return nil } return files } // fixDir 修复文件路径为相对路径 func fixDir(file, base string) string { rel, err := filepath.Rel(base, file) if err != nil { return file } return filepath.ToSlash(rel) }