This repository has been archived by the owner on Jan 13, 2019. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathbundlemanager.go
195 lines (159 loc) · 5.88 KB
/
bundlemanager.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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
/*
Digivance MVC Application Framework
Content Bundle Manager
Dan Mayor (dmayor@digivance.com)
This file defines the content bundle manager object for the Digivance mvcapp package
*/
package mvcapp
import (
"bufio"
"bytes"
"errors"
"fmt"
"io/ioutil"
"os"
"strings"
"time"
"github.com/tdewolff/minify"
"github.com/tdewolff/minify/css"
"github.com/tdewolff/minify/js"
)
// BundleManager is an object that we register our content bundles with and is used
// to compile said content bundles.
type BundleManager struct {
// Bundles are a collection of filenames that are compiled into a single deliverable
Bundles map[string]*BundleMap
Minifier *minify.M
}
// NewBundleManager returns a new instance of the bundle manager object
func NewBundleManager() *BundleManager {
rtn := &BundleManager{
Bundles: make(map[string]*BundleMap, 0),
Minifier: minify.New(),
}
rtn.Minifier.AddFunc("text/css", css.Minify)
rtn.Minifier.AddFunc("text/javascript", js.Minify)
return rtn
}
// CreateBundle is used to register a bundle by name, mime type and slice of filenames
// Once registered, bundles can be compiled using the BuildBundle method referencing
// the provided bundleName
func (bundleManager *BundleManager) CreateBundle(bundleName string, mimeType string, bundledFiles []string) error {
if bundleManager.Bundles[bundleName] != nil {
return errors.New("Failed to create new content bundle, there is already a bundle created using this name")
}
bundleManager.Bundles[bundleName] = NewBundleMap(mimeType, bundledFiles)
return nil
}
// RemoveBundle is used to remove a bundle from the manager by name.
func (bundleManager *BundleManager) RemoveBundle(bundleName string) error {
if bundleManager.Bundles[bundleName] == nil {
return fmt.Errorf("Failed to remove bundle, no bundles found for %s", bundleName)
}
delete(bundleManager.Bundles, bundleName)
return nil
}
// doBuild is really just a micro-optimization, it can be used so that "ALL" methods
// of this object only have to iterate the bundles map once
func (bundleManager *BundleManager) doBuild(bundleMap *BundleMap, bundleName string) error {
if bundleMap == nil {
return errors.New("Failed to build bundle, none found for provided name")
}
if len(bundleMap.Files) <= 0 {
return errors.New("Failed to build bundle, no files registered")
}
bundlePath := fmt.Sprintf("%s/bundle", GetApplicationPath())
os.Mkdir(bundlePath, 0644)
data := []byte{}
for _, filename := range bundleMap.Files {
if !strings.HasPrefix(filename, GetApplicationPath()) {
// Is tested successfully, hard to demonstrate because of scoping when testing
// (e.g. the GetApplicationPath is different between the unit test and the lib)
filename = fmt.Sprintf("%s/%s", GetApplicationPath(), filename)
}
contentData, err := ioutil.ReadFile(filename)
if err != nil {
return fmt.Errorf("Failed to bundle %s : %s", filename, err)
}
data = append(data, contentData...)
}
bundleFilename := fmt.Sprintf("%s/bundle/%s", GetApplicationPath(), bundleName)
os.RemoveAll(bundleFilename)
bundleFile, err := os.Create(bundleFilename)
if err != nil {
// this would be a permissions error, can't test
return fmt.Errorf("Failed to create new bundle file: %s", err)
}
reader := bytes.NewReader(data)
writer := bufio.NewWriter(bundleFile)
defer func() {
writer.Flush()
bundleFile.Close()
}()
if err := bundleManager.Minifier.Minify(bundleMap.MimeType, writer, reader); err != nil {
// Syntax error maybe?
return fmt.Errorf("Failed to minify and write the bundle file: %s", err)
}
bundleMap.BuildDate = time.Now()
return nil
}
// BuildBundle is used to compile the registered bundle defined by bundleName.
// This method will delete the existing bundle file if it exists and replacing
// with a newly built copy
func (bundleManager *BundleManager) BuildBundle(bundleName string) error {
bundleMap := bundleManager.Bundles[bundleName]
return bundleManager.doBuild(bundleMap, bundleName)
}
// BuildAllBundles is used to build all of the currently registered content bundles
func (bundleManager *BundleManager) BuildAllBundles() error {
for buildName, buildMap := range bundleManager.Bundles {
if err := bundleManager.doBuild(buildMap, buildName); err != nil {
return err
}
}
return nil
}
// RebuildBundle is used to compare the last modified times of the files in a content
// bundle to the creation time of this content bundle, if files have been modified
// since this bundle was built it will be built a new. Returns nil if no need to build
func (bundleManager *BundleManager) RebuildBundle(bundleName string) error {
bundleMap := bundleManager.Bundles[bundleName]
if bundleMap.BuildDate.IsZero() {
return bundleManager.doBuild(bundleMap, bundleName)
}
for _, filename := range bundleMap.Files {
si, err := os.Stat(filename)
if err != nil {
return err
}
if si.ModTime().After(bundleMap.BuildDate) {
return bundleManager.doBuild(bundleMap, bundleName)
}
}
return nil
}
// RebuildAllBundles is used to loop through all of the registered content bundles
// and compare file modification dates to the build time of the bundle. If files
// have been modified since this bundle was built, it will be built a new
func (bundleManager *BundleManager) RebuildAllBundles() error {
for bundleName, bundleMap := range bundleManager.Bundles {
if bundleMap.BuildDate.IsZero() {
if err := bundleManager.doBuild(bundleMap, bundleName); err != nil {
return err
}
}
for _, filename := range bundleMap.Files {
si, err := os.Stat(filename)
if err != nil {
return err
}
if si.ModTime().After(bundleMap.BuildDate) {
if err := bundleManager.doBuild(bundleMap, bundleName); err != nil {
return err
}
break
}
}
}
return nil
}