-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.dart
171 lines (131 loc) · 4.45 KB
/
main.dart
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
import 'dart:convert';
import 'dart:io';
import 'package:args/args.dart';
import 'package:crypto/crypto.dart';
import 'package:path/path.dart';
import 'package:sass/sass.dart';
import 'package:watcher/watcher.dart';
bool brotli, watch, webp;
void main(List<String> args) {
final parser = ArgParser()
..addSeparator("usage: cvl-asset [<flags>] <source> <destination>\n")
..addFlag('no-brotli', abbr: 'B', negatable: false)
..addFlag('help', abbr: 'h', negatable: false)
..addOption('mount', abbr: 'm', defaultsTo: '/')
..addFlag('no-webp', abbr: 'W', negatable: false)
..addFlag('watch', abbr: 'w', negatable: false);
String source, destination, mount;
try {
final result = parser.parse(args);
brotli = !result['no-brotli'];
mount = result['mount'];
watch = result['watch'] as bool;
webp = !result['no-webp'];
source = result.rest[0];
destination = result.rest[1];
} catch (e) {
print(parser.usage);
exit(1);
}
source = absolute(source);
destination = absolute(destination);
Directory.current = source;
List<File> files = [];
for (var entity in Directory('').listSync(recursive: true)) {
if (entity is File &&
(!entity.path.endsWith('.scss') ||
!basename(entity.path).startsWith('_'))) files.add(entity);
}
// Sort non-sass files first, then by name.
files.sort((a, b) {
final aIsSass = a.path.endsWith('.scss');
final bIsSass = b.path.endsWith('.scss');
if (aIsSass && !bIsSass)
return 1;
else if (!aIsSass && bIsSass)
return -1;
else
return a.path.compareTo(b.path);
});
run(source, destination, mount, files);
if (watch) {
DirectoryWatcher(source).events.listen((e) {
// Skip deleted files.
if (e.type == ChangeType.REMOVE)
return;
// FIXME We're re-running everything on file change
// and not actually detecting new files!
run(source, destination, mount, files);
});
}
}
void run(String source, String destination, String mount, List<File> files) {
Map<String, String> manifest = Map();
for (final file in files) {
final path = file.path.substring(2);
print("\x1B[0;34mProcessing $path\x1B[0m");
List<int> bytes;
var ext = extension(path);
if (ext == '.scss') {
ext = '.css';
// TODO Build a dependency graph for watch.
List<String> deps = [];
final css = compile(
path,
functions: [
Callable("asset-url", r"$url", (args) {
var url = args[0].assertString("url").text;
deps.add(url);
// TODO Probably need to URL encode this.
return SassString(
'url(' + SassString(manifest[url]).toString() + ')',
quotes: false,
);
}),
],
loadPaths: [source],
sourceMap: (map) => deps.addAll(map.urls),
style: OutputStyle.compressed,
);
deps.removeWhere((dep) => dep == path);
bytes = Utf8Encoder().convert(css);
final hack = File('/tmp/hack.css');
hack.createSync(recursive: true);
hack.writeAsBytesSync(bytes, flush: true);
ProcessResult res = Process.runSync('postcss', ['/tmp/hack.css', '--no-map', '-u', 'autoprefixer']);
bytes = Utf8Encoder().convert(res.stdout);
} else {
bytes = file.readAsBytesSync();
}
final assetPath =
withoutExtension(path) + '-' + md5.convert(bytes).toString() + ext;
print("\x1B[0;32mGenerating $assetPath\x1B[0m");
// Write the fingerprinted asset to disk.
final asset = File(join(destination, assetPath));
asset.createSync(recursive: true);
asset.writeAsBytesSync(bytes, flush: true);
switch (ext) {
case '.css':
case '.js':
case '.svg':
if (brotli) {
final brPath = assetPath + '.br';
print("\x1B[0;32mGenerating $brPath\x1B[0m");
// TODO Show stderr
Process.runSync('brotli', [join(destination, assetPath)]);
}
break;
case '.png':
if (webp) {
final webpPath = assetPath + '.webp';
print("\x1B[0;32mGenerating $webpPath\x1B[0m");
// TODO Show stderr
Process.runSync('cwebp', ['-z', '9', join(destination, assetPath), '-o', join(destination, webpPath)]);
}
break;
}
manifest[path] = mount + assetPath;
}
File(join(destination, 'manifest.json'))
.writeAsBytesSync(JsonUtf8Encoder().convert(manifest), flush: true);
}