Skip to content
Merged

#18 #49

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added example/assets/img/controllers.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 2 additions & 0 deletions example/warden.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ assets:
source: example/assets
directories:
- img
compress:
quality: 20

tasks:
frontend:
Expand Down
31 changes: 29 additions & 2 deletions lib/asset_mover.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import "package:ansi_styles/ansi_styles.dart";
import "package:path/path.dart" as p;
import "package:warden/assets.dart";
import "package:warden/destination.dart";
import "package:warden/file_compressor.dart";

/// The `AssetMover` class is responsible for copying third-party frontend assets
/// (like JavaScript or CSS files) from a `node_modules` source directory to a public
Expand All @@ -16,14 +17,20 @@ import "package:warden/destination.dart";
class AssetMover {
final Destination destination;
final Asset assets;
final FileCompressor fileCompressor;
late Directory nodeModules;
late Directory outputDir;
bool shouldCompress = false;

AssetMover({
required this.destination,
required this.assets,
required this.fileCompressor,
}) {
outputDir = Directory(destination.destination);
if (assets.compress["use"] != null) {
shouldCompress = assets.compress["use"];
}
}

/// Copies each file defined in `dependencies.files` from the `node_modules` directory
Expand All @@ -47,7 +54,7 @@ class AssetMover {
_move(nonJSFiles, dependencySrc);
}

void moveAssets() {
moveAssets() async {
for (final dirName in assets.directories) {
final sourceDir = Directory(p.join(assets.source, dirName));
final destDir = Directory(p.join(outputDir.path, dirName));
Expand All @@ -63,8 +70,17 @@ class AssetMover {
final targetFile = File(p.join(destDir.path, relative));
targetFile.createSync(recursive: true);
targetFile.writeAsBytesSync(entity.readAsBytesSync());
print(
if (shouldCompress) {
await _compress(targetFile.path);
print(
"${AnsiStyles.cyan("✔ copied asset:\n")}"
"${AnsiStyles.magenta("\t ◉ [${entity.path} ${AnsiStyles.cyanBright.bold("${_getSize(entity.path)}kb")}] ->\n")}"
"${AnsiStyles.magenta("\t ◉ [${targetFile.path} ${AnsiStyles.cyanBright.bold("${_getSize(targetFile.path)}kb")}]\n")}"
);
} else {
print(
"${AnsiStyles.cyan("✔ copied asset: ")}${AnsiStyles.magenta("[${entity.path} -> ${targetFile.path}]")}");
}
}
}
}
Expand All @@ -88,4 +104,15 @@ class AssetMover {
AnsiStyles.green("✔ moved [${source.path} -> ${destination.path}]"));
}
}

Future<void> _compress(String filename) async {
await fileCompressor.compress(filename);
}

/// Returns file size in mb
int _getSize(String filename) {
final file = File(filename);
double size = file.lengthSync() / 1024;
return double.parse(size.toStringAsFixed(0)).toInt();
}
}
9 changes: 8 additions & 1 deletion lib/assets.dart
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
class Asset {
String source;
List<String> directories;
/// { quality: 0-100, use: true/false }
Map<String, dynamic> compress;

Asset({
required this.source,
required this.directories,
required this.compress,
});

@override
String toString() {
return "Asset(source: $source, directories: $directories)";
return "Asset("
"source: $source, "
"directories: $directories, "
"compress: $compress"
")";
}
}
4 changes: 2 additions & 2 deletions lib/dependency.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ class Dependency {
assetMover.moveFilesExclSuffix(".js", files, source);
}

void moveAssets() {
assetMover.moveAssets();
moveAssets() async {
await assetMover.moveAssets();
}

void bundleFiles(StringBuffer buffer) {
Expand Down
82 changes: 82 additions & 0 deletions lib/file_compressor.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import 'dart:io';
import 'package:image/image.dart' as img;

class FileCompressor {

List<String> fileTypes = [
"jpg",
"png",
"jpeg",
"gif",
"bmp",
"tga",
"tiff",
];
/// quality can be set from 1-100 - default 80
int quality = 80;

FileCompressor({required this.quality}) {
quality = quality.clamp(1, 100).toInt();
}

Future<void> compress(String filename) async {
final file = File(filename);
final bytes = await file.readAsBytes();
final image = img.decodeImage(bytes)!;
final fileType = _getFileExt(filename);
if (!_validate(fileType)) {

return;
}
switch (fileType) {
case "jpg":
case "jpeg": {
final compressed = img.encodeJpg(image, quality: quality);
await File(filename).writeAsBytes(compressed);
break;
}
case "png": {
// level: min = 0, max = 9, default = 6
final compressed = img.encodePng(image, level: _round());
await File(filename).writeAsBytes(compressed);
break;
}
case "gif": {
final compressed = img.encodeGif(image);
await File(filename).writeAsBytes(compressed);
break;
}
case "bmp": {
final compressed = img.encodeBmp(image);
await File(filename).writeAsBytes(compressed);
break;
}
case "tga": {
final compressed = img.encodeTga(image);
await File(filename).writeAsBytes(compressed);
break;
}
case "tiff": {
final compressed = img.encodeTiff(image);
await File(filename).writeAsBytes(compressed);
break;
}
default: {
// No op...
return;
}
}
}

String _getFileExt(String fileName) {
return fileName.split(".").last.toLowerCase();
}

bool _validate(String ext) {
return fileTypes.contains(ext);
}

int _round() {
return ((100 - quality) / 100 * 9).round().clamp(0, 9).toInt();
}
}
54 changes: 47 additions & 7 deletions lib/warden.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import "package:path/path.dart" as p;
import "package:warden/environment.dart";
import "package:warden/exceptions.dart";
import "package:warden/excluder.dart";
import "package:warden/file_compressor.dart";
import "package:warden/logger.dart";
import "package:warden/main_file.dart";
import "package:warden/mode.dart";
Expand Down Expand Up @@ -127,7 +128,7 @@ class Warden {
await processor.run();
}
// pre bundle the initial run
_bundleAndMoveFiles(stopwatch);
await _bundleAndMoveFiles(stopwatch);
}

_runWatcher(Watcher watcher) {
Expand Down Expand Up @@ -155,7 +156,7 @@ class Warden {
}
// Wait for all processes to run & then re bundle file
await Future.wait(futures);
_bundleAndMoveFiles(stopwatch);
await _bundleAndMoveFiles(stopwatch);
} on ProcessingCompileException catch (e) {
if (debug) {
log.info(e.toString());
Expand Down Expand Up @@ -187,7 +188,7 @@ class Warden {
await processor.run();
}
// pre bundle the initial run
_bundleAndMoveFiles(stopwatch);
await _bundleAndMoveFiles(stopwatch);

watcher.events.listen((event) async {
final normalized = p.normalize(event.path);
Expand All @@ -202,11 +203,11 @@ class Warden {
}
// Wait for all processes to run & then re bundle file
await Future.wait(futures);
_bundleAndMoveFiles(stopwatch);
await _bundleAndMoveFiles(stopwatch);
});
}

void _bundleAndMoveFiles(Stopwatch stopwatch) {
_bundleAndMoveFiles(Stopwatch stopwatch) async {
bundler.destroyBundleFile();
// Initiate the String buffer
bundler.start();
Expand All @@ -220,7 +221,7 @@ class Warden {
dependency.moveAllFiles();
}
if (assets.source != "") {
dependency.moveAssets();
await dependency.moveAssets();
}
}
// Bundle the main file
Expand Down Expand Up @@ -287,11 +288,25 @@ class Warden {
if (dependency["main"] != null) {
mainFile = dependency["main"] as String;
}

// Setup file compression from asset yaml options
int quality = 100;
if (assets.compress["quality"] != null) {
quality = assets.compress["quality"];
}
FileCompressor fileCompressor = FileCompressor(
quality: quality,
);

dependencies.add(Dependency(
source: dependency["source"] as String,
bundle: bundle,
files: List<String>.from(dependency["files"]),
assetMover: AssetMover(destination: destination, assets: assets),
assetMover: AssetMover(
destination: destination,
assets: assets,
fileCompressor: fileCompressor,
),
bundler: Bundler(destination, dependencyMainFile: mainFile, debug: debug),
));
}
Expand All @@ -317,16 +332,41 @@ class Warden {
_setAssets(dynamic yamlMap) {
var assetResult = yamlMap["assets"];
String source = "";
Map<String, dynamic> compress = {};
List<String> directories = [];
if (assetResult != null && assetResult["source"] != null) {
source = assetResult["source"];
}
if (assetResult != null && assetResult["directories"] != null) {
directories = List<String>.from(assetResult["directories"]);
}
if (assetResult != null && assetResult["compress"] != null) {
if (assetResult["compress"]["quality"] != null) {
compress = {
"quality": assetResult["compress"]["quality"],
"use": true,
};
print(AnsiStyles.cyan("◆ setting file compression to [${AnsiStyles.cyanBright.bold("${compress['quality']}%")}]"));
} else {
compress = {
"quality": 80,
"use": true,
};
print(AnsiStyles.cyan("◆ setting file compression to default [${AnsiStyles.cyanBright.bold("${compress['quality']}%")}]"));
log.info(AnsiStyles.yellow("Setting image compression quality default: ${compress["quality"]}"));
}
/// The fileCompressor is still get created but passing `use: false` will equate to a no op.
if (assetResult["compress"] == null) {
compress = {
"quality": 100,
"use": false,
};
}
}
assets = Asset(
source: source,
directories: directories,
compress: compress,
);
}
}
Loading