Skip to content
Open
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
17 changes: 17 additions & 0 deletions config/minify.php
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,23 @@

'assets_storage' => env('MINIFY_ASSETS_STORAGE', 'resources'),

/*
|--------------------------------------------------------------------------
| Minify Assets Build Storage
|--------------------------------------------------------------------------
|
| This option specifies the storage path to save the minified assets.
| Minify checks the timestamp of the assets to determine whether to
| re-minify the assets. If the assets are not modified, Minify will serve
| the minified assets from this directory, making it faster.
| This directory will be created on the public path.
|
| Default: assets/_minify
|
*/

'assets_build_storage' => env('MINIFY_ASSETS_BUILD_STORAGE', 'assets/_minify'),

/*
|--------------------------------------------------------------------------
| Automatic Insert Semicolon
Expand Down
149 changes: 125 additions & 24 deletions src/Controllers/HttpConnectionHandler.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,50 +4,151 @@

use Fahlisaputra\Minify\Helpers\CSS;
use Fahlisaputra\Minify\Helpers\Javascript;
use Illuminate\Support\Str;

class HttpConnectionHandler
{
public function __invoke($file)
{
$js_insert_semicolon = (bool) config('minify.insert_semicolon.js', true);
$css_insert_semicolon = (bool) config('minify.insert_semicolon.css', true);
$obfuscate = (bool) config('minify.obfuscate', false);
$enabled = (bool) config('minify.assets_enabled', true);
// Load configuration
$config = [
'js_insert_semicolon' => (bool) config('minify.insert_semicolon.js', true),
'css_insert_semicolon' => (bool) config('minify.insert_semicolon.css', true),
'obfuscate' => (bool) config('minify.obfuscate', false),
'enabled' => (bool) config('minify.assets_enabled', true),
'storage' => config('minify.assets_storage', 'resources'),
'buildStorage' => config('minify.assets_build_storage', '/public/assets/_minify'),
];

$css = new CSS();
$js = new Javascript();

$storage = config('minify.assets_storage', 'resources');

// remove slash or backslash from the beginning of the file path
$file = ltrim($file, '/\\');
$cacheFile = storage_path('/framework/cache/minify.php');
$this->ensureCacheFileExists($cacheFile);

$cache = require $cacheFile;
$cachedFile = $cache[$file] ?? null;

// make sure the storage has trailing slash
$file = base_path(rtrim($storage, '/').'/'.$file);
$realFilePath = base_path(rtrim($config['storage'], '/').'/'.$file);
$buildFilePath = $cachedFile;

if (!file_exists($file)) {
if (!file_exists($realFilePath)) {
return abort(404);
}

$content = file_get_contents($file);
$mime = 'text/plain';
$this->createNestedDirectories($config['buildStorage']);

// due to support only for css and js (issue #9)
if ($enabled) {
if (preg_match("/\.css$/", $file)) {
$content = $css->replace($content, $css_insert_semicolon);
if ($this->shouldCache($realFilePath, $buildFilePath, $cachedFile)) {
$content = file_get_contents($realFilePath);
$mime = 'text/plain';
if ($config['enabled']) {
[$content, $mime] = $this->minifyContent($file, $content, $css, $js, $config);
}

if ($mime === 'text/css') {
$ext = '.min.css';
} elseif ($mime === 'application/javascript') {
$ext = '.min.js';
} else {
$ext = '';
}

$cachedFile = Str::random('24').$ext;
file_put_contents($config['buildStorage'].'/'.$cachedFile, $content);
$cache[$file] = $config['buildStorage'].'/'.$cachedFile;
file_put_contents($cacheFile, $this->exportCache($cache));
} elseif ($this->shouldReMinify($realFilePath, $buildFilePath, $cachedFile)) {
$content = file_get_contents($realFilePath);
$mime = 'text/plain';

if ($config['enabled']) {
[$content, $mime] = $this->minifyContent($file, $content, $css, $js, $config);
}

if ($mime === 'text/css') {
$ext = '.min.css';
} elseif ($mime === 'application/javascript') {
$ext = '.min.js';
} else {
$ext = '';
}

$newFileName = Str::random('24').$ext;

// remove the old file
unlink($buildFilePath);

$cachedFile = $config['buildStorage'].'/'.$newFileName;
file_put_contents($cachedFile, $content);
$cache[$file] = $cachedFile;
file_put_contents($cacheFile, $this->exportCache($cache));
} else {
// get the extension of the file
$ext = pathinfo($file, PATHINFO_EXTENSION);
$mime = 'text/plain';
if ($ext === 'css') {
$mime = 'text/css';
} elseif (preg_match("/\.js$/", $file)) {
$content = $js->replace($content, $js_insert_semicolon);
if ($obfuscate) {
$content = $js->obfuscate($content);
}
} elseif ($ext === 'js') {
$mime = 'application/javascript';
}
$content = file_get_contents($buildFilePath);
}

return response($content, 200, ['Content-Type' => $mime.'; charset=UTF-8']);
}

private function ensureCacheFileExists($cacheFile)
{
if (!file_exists($cacheFile)) {
file_put_contents($cacheFile, "<?php\nreturn ".var_export([], true).";\n");
}
}

return response($content, 200, [
'Content-Type' => $mime.'; charset=UTF-8',
]);
private function createNestedDirectories($path)
{
$dirs = explode('/', $path);
$currentPath = '';

foreach ($dirs as $dir) {
$currentPath .= $dir.'/';
if (!is_dir(base_path($currentPath))) {
mkdir(base_path($currentPath), 0755, true);
}
}
}

private function shouldCache($realFilePath, $buildFilePath, $cachedFile)
{
return file_exists($realFilePath) && (!file_exists($buildFilePath) || !$cachedFile);
}

private function shouldReMinify($realFilePath, $buildFilePath, $cachedFile)
{
return file_exists($realFilePath) && file_exists($buildFilePath) && $cachedFile
&& filemtime($realFilePath) > filemtime($buildFilePath);
}

private function minifyContent($file, $content, $css, $js, $config)
{
$mime = 'text/plain';

if (preg_match("/\.css$/", $file)) {
$content = $css->replace($content, $config['css_insert_semicolon']);
$mime = 'text/css';
} elseif (preg_match("/\.js$/", $file)) {
$content = $js->replace($content, $config['js_insert_semicolon']);
if ($config['obfuscate']) {
$content = $js->obfuscate($content);
}
$mime = 'application/javascript';
}

return [$content, $mime];
}

private function exportCache($cache)
{
return "<?php\nreturn ".var_export($cache, true).";\n";
}
}
29 changes: 23 additions & 6 deletions src/Helpers.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,36 @@
*/
function minify(string $file): string
{
if (config('minify.assets_enabled') === false) {
if (!config('minify.assets_enabled')) {
throw new \Exception('Minify assets is disabled');
}

$storage = config('minify.assets_storage', 'resources');
$cacheFile = storage_path('/framework/cache/minify.php');

// remove slash or backslash from the beginning of the file path
if (!file_exists($cacheFile)) {
file_put_contents($cacheFile, "<?php\nreturn ".var_export([], true).";\n");
}

// Normalize file path
$file = ltrim($file, '/\\');

// make sure the storage has trailing slash
$realFilePath = rtrim($storage, '/').'/'.$file;
if (!file_exists(base_path($realFilePath))) {
throw new \Exception('Cannot create minified route. File '.$realFilePath.' not found');
$cache = require $cacheFile;
$cachedFile = $cache[$file] ?? null;

$realFilePath = base_path(rtrim($storage, '/').'/'.$file);
if (!file_exists($realFilePath)) {
throw new \Exception("Cannot create minified route. File {$realFilePath} not found");
}

if ($cachedFile && file_exists($cachedFile)) {
if (filemtime($realFilePath) > filemtime($cachedFile)) {
$cachedFile = null;
}
}

if ($cachedFile) {
return asset(str_replace(public_path(), '', $cachedFile));
}

return route('minify.assets', ['file' => $file]);
Expand Down