420 lines
12 KiB
Go
420 lines
12 KiB
Go
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")+"--><text>"+changeExt(a, ".wxml")+"</text>"))
|
|
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)
|
|
}
|