From 5ed98efbddba10a38142f4c655e07c9574861c84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dani=C3=ABl=20Mostertman?= Date: Thu, 24 Aug 2017 18:06:00 +0200 Subject: [PATCH] Precompression for (pipelined) assets (nginx::gzip_static support). Compressing resources on-the-fly adds CPU-load and latency (wait for the compression to be done) every time a resource is served. nginx offers another way of doing things, and it's called gzip_static. http://nginx.org/en/docs/http/ngx_http_gzip_static_module.html If you enable gzip_static, nginx will look for $filename.gz and serve that directly, so no extra CPU-cost or latency is added to your requests, speeding up the serving of your website. This feature adds support to directly compress asset files when they are written to disk by the Grav\Common\Assets class. Using this feature you can also specify the compression level (default to the maximum of 9). Since you only have to compress every resource only once, using the maximum compression level is an ideal default. --- system/blueprints/config/system.yaml | 21 ++++++++ system/config/system.yaml | 2 + system/src/Grav/Common/Assets.php | 72 ++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+) diff --git a/system/blueprints/config/system.yaml b/system/blueprints/config/system.yaml index 9ed7a07533..6496646a3f 100644 --- a/system/blueprints/config/system.yaml +++ b/system/blueprints/config/system.yaml @@ -794,6 +794,27 @@ form: validate: type: bool + assets.precompress: + type: toggle + label: PLUGIN_ADMIN.PRECOMPRESS_ASSET + help: PLUGIN_ADMIN.PRECOMPRESS_ASSET_HELP + highlight: 0 + options: + 1: PLUGIN_ADMIN.YES + 0: PLUGIN_ADMIN.NO + validate: + type: bool + + assets.precompress_level: + type: text + size: x-small + label: PLUGIN_ADMIN.PRECOMPRESS_ASSET_LEVEL + help: PLUGIN_ADMIN.PRECOMPRESS_ASSET_LEVEL_HELP + validate: + type: number + min: 1 + max: 9 + assets.enable_asset_timestamp: type: toggle label: PLUGIN_ADMIN.ENABLED_TIMESTAMPS_ON_ASSETS diff --git a/system/config/system.yaml b/system/config/system.yaml index a6110c4a8e..b3f1a4c95d 100644 --- a/system/config/system.yaml +++ b/system/config/system.yaml @@ -102,6 +102,8 @@ assets: # Configuration for Assets Mana js_pipeline_include_externals: true # Include external URLs in the pipeline by default js_pipeline_before_excludes: true # Render the pipeline before any excluded files js_minify: true # Minify the JS during pipelining + precompress: false # Precompress assets with GZip + precompress_level: 9 # Compression level to use (1 = fastest, 9 = highest) enable_asset_timestamp: false # Enable asset timestamps collections: jquery: system://assets/jquery/jquery-2.x.min.js diff --git a/system/src/Grav/Common/Assets.php b/system/src/Grav/Common/Assets.php index a329cd618e..79d4bdbf48 100644 --- a/system/src/Grav/Common/Assets.php +++ b/system/src/Grav/Common/Assets.php @@ -90,6 +90,10 @@ class Assets protected $css_no_pipeline = []; protected $js_no_pipeline = []; + // Default values for asset precompression. + protected $precompress = false; + protected $precompress_level = 9; + /** * Assets constructor. * @@ -170,6 +174,15 @@ public function config(array $config) $this->js_minify = $config['js_minify']; } + // Asset precompression + if (isset($config['precompress'])) { + $this->precompress = $config['precompress']; + } + + if (isset($config['precompress_level'])) { + $this->precompress_level = $config['precompress_level']; + } + // Set collections if (isset($config['collections']) && is_array($config['collections'])) { $this->collections = $config['collections']; @@ -750,6 +763,10 @@ protected function pipelineCss($group = 'head', $returnURL = true) // Write non-pipeline files out if (!empty($this->css_no_pipeline)) { file_put_contents($this->assets_dir . $inline_file, json_encode($this->css_no_pipeline)); + + if ($this->precompress) { + $this->compressFile($this->assets_dir . $inline_file); + } } @@ -774,6 +791,10 @@ protected function pipelineCss($group = 'head', $returnURL = true) if (strlen(trim($buffer)) > 0) { file_put_contents($this->assets_dir . $file, $buffer); + if ($this->precompress) { + $this->compressFile($this->assets_dir . $file); + } + if ($returnURL) { return $relative_path . $this->getTimestamp(); } @@ -843,6 +864,10 @@ protected function pipelineJs($group = 'head', $returnURL = true) // Write non-pipeline files out if (!empty($this->js_no_pipeline)) { file_put_contents($this->assets_dir . $inline_file, json_encode($this->js_no_pipeline)); + + if ($this->precompress) { + $this->compressFile($this->assets_dir . $inline_file); + } } // Concatenate files @@ -857,6 +882,10 @@ protected function pipelineJs($group = 'head', $returnURL = true) if (strlen(trim($buffer)) > 0) { file_put_contents($this->assets_dir . $file, $buffer); + if ($this->precompress) { + $this->compressFile($this->assets_dir . $file); + } + if ($returnURL) { return $relative_path . $this->getTimestamp(); } @@ -868,6 +897,49 @@ protected function pipelineJs($group = 'head', $returnURL = true) } } + /** + * Compresses the given file. + * + * @param string $filename File to compress + * @return bool true if succesful, false if not. + */ + protected function compressFile($filename = null) + { + // Test if we have gzencode() + if (!function_exists('gzencode')) { + return false; + } + + // Test if $filename is set + if (!empty($filename)) { + + // Test if the source file exists and can be read + if (!is_readable($filename)) { + return false; + } + + // Test if we can write to the target file + $fp = fopen($filename . ".gz", "w"); + if (!$fp) { + return false; + } + + // Load source file, gzencode it, and write it out + $written = fwrite($fp, gzencode(file_get_contents($filename), $this->precompress_level)); + fclose($fp); + + if ($written === false) { + return false; + } + + // Make the file modification time identical to that of the source file + touch($filename . ".gz", filemtime($filename)); + + return true; + } + return false; + } + /** * Return the array of all the registered CSS assets * If a $key is provided, it will try to return only that asset