/
pack.go
134 lines (109 loc) · 2.27 KB
/
pack.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
package fs
import (
"bytes"
"encoding/gob"
"runtime"
"sort"
"github.com/andybalholm/brotli"
"github.com/pkg/errors"
)
// Pack compresses a set of files from disk for bundled use in the generated code.
//
// This function is only supposed to be called by broccoli the tool.
func Pack(files []*File, quality int) ([]byte, error) {
sort.Slice(files, func(i, j int) bool {
return files[i].Fpath < files[j].Fpath
})
n := runtime.NumCPU()
feed := make(chan *File)
errs := make(chan error, n)
defer close(errs)
for i := 0; i < n; i++ {
go func() {
for f := range feed {
data, err := f.compress(quality)
if err != nil {
errs <- err
return
}
f.Data = data
}
errs <- nil
}()
}
for _, file := range files {
if file.IsDir() {
continue
}
feed <- file
}
close(feed)
for err := range errs {
if err != nil {
return nil, err
}
n--
if n == 0 {
break
}
}
var b bytes.Buffer
w := brotli.NewWriterLevel(&b, quality)
if err := gob.NewEncoder(w).Encode(files); err != nil {
return nil, err
}
if err := w.Close(); err != nil {
return nil, err
}
return b.Bytes(), nil
}
// New decompresses the bundle byte-slice and creates a virtual file system.
// Depending on whether if optional decompression is enabled, it will or
// will not decompress the files while loading them.
//
// This function is only supposed to be called from the generated code.
func New(opt bool, bundle []byte) *Broccoli {
var files []*File
r := brotli.NewReader(bytes.NewBuffer(bundle))
if err := gob.NewDecoder(r).Decode(&files); err != nil {
panic(err)
}
br := &Broccoli{
filePaths: make([]string, 0, len(files)),
files: map[string]*File{},
}
for _, f := range files {
f.compressed = true
f.br = br
br.files[f.Fpath] = f
br.filePaths = append(br.filePaths, f.Fpath)
}
if opt {
return br
}
n := runtime.NumCPU()
feed := make(chan *File)
done := make(chan struct{}, n)
defer close(done)
for i := 0; i < n; i++ {
go func() {
for f := range feed {
if err := f.decompress(f.Data); err != nil {
panic(errors.Wrap(err, "could not decompress"))
}
}
done <- struct{}{}
}()
}
for _, file := range files {
feed <- file
}
close(feed)
for range done {
n--
if n == 0 {
break
}
}
return br
}