AssetsAutoCompressComponent.php 11.5 KB
<?php

namespace app\ht\exts;

use Yii;
use yii\helpers\FileHelper;
use yii\base\BootstrapInterface;
use yii\base\Component;
use yii\base\Event;
use yii\helpers\Html;
use yii\helpers\Url;
use yii\web\Application;
use yii\web\Response;
use yii\web\View;
use function serialize;
use function array_keys;
use function md5;
use function strlen;
use function substr;
use function ltrim;
use function trim;
use function implode;
use function basename;
use function is_dir;
use function fopen;
use function fwrite;
use function fclose;
use function file_exists;
use function filemtime;
use function is_readable;
use function file_get_contents;

/**
 * 压缩功能默认关闭
 * @todo 压缩目录的垃圾回收机制.
 * @todo JS压缩还不能有效去除换行,建议只用合并功能。(寻求更好的JS压缩算法组件替换)
 *
 * Class AssetsAutoCompressComponent
 * @package app\ht\exts
 * @author Semenov Alexander <semenov@skeeks.com>
 * @author Lee Li <349238652@qq.com>
 */
class AssetsAutoCompressComponent extends Component implements BootstrapInterface
{
    /**
     * @var bool
     */
    public $debug = false;

    /**
     * @var bool 是否启动自动压缩组件
     */
    public $enabled = true;

    /**
     * @var bool 是否启动CSS文件的合并
     */
    public $cssFileMerge = true;

    /**
     * @var bool 是否已启动css文件的压缩
     */
    public $cssFileCompress = false;
    public $srcDirs = [];

    /**
     * @var bool 是否启动JS文件的合并
     */
    public $jsFileMerge = true;
    public $jsDirs = [];

    /**
     * @var bool 是否已启动JS文件的压缩
     */
    public $jsFileCompress = false;

    /**
     * @var bool Disable YUI style comment preservation.
     */
    public $jsFileCompressFlaggedComments = false;

    /**
     * @param \yii\web\Application $app
     */
    public function bootstrap($app)
    {
        if ($app instanceof Application) {
            $app->view->on(
                View::EVENT_END_PAGE,
                function(Event $e) {
                    /**
                     * @var $view View
                     */
                    $view = $e->sender;
                    if ($this->enabled && $view instanceof View && Yii::$app->response->format == Response::FORMAT_HTML && !Yii::$app->request->isAjax) {
                        Yii::beginProfile('Compress assets');
                        $this->_processing($view);
                        Yii::endProfile('Compress assets');
                    }
                }
            );
        }
    }

    /**
     * @return string
     */
    public function getSettingsHash()
    {
        return serialize((array) $this);
    }

    /**
     * @param View $view
     */
    protected function _processing(View $view)
    {
        //
        if ($view->jsFiles && $this->jsFileMerge) {
            Yii::beginProfile('Compress js files');
            foreach ($view->jsFiles as $pos => $files) {
                if ($files) {
                    $view->jsFiles[$pos] = $this->_processingJsFiles($files);
                }
            }
            Yii::endProfile('Compress js files');

        }
        //
        if ($view->cssFiles && $this->cssFileMerge) {
            Yii::beginProfile('Compress css files');
            $view->cssFiles = $this->_processingCssFiles($view->cssFiles);
            Yii::endProfile('Compress css files');
        }
    }

    /**
     * @param array $files
     * @return array
     */
    protected function _processingJsFiles($files = [])
    {

        $baseAssetsUrl = Yii::$app->params['assetsUrl'];
        $webDir = Yii::getAlias("@web");

        $fileName   =  md5( implode(array_keys($files)) . $this->getSettingsHash()) . '.js';
        $publicUrl  = $baseAssetsUrl . '/assets/js-compress/' . $fileName;

        $rootDir    = Yii::getAlias('@webroot/assets/js-compress');
        $rootUrl    = $rootDir . '/' . $fileName;

        if (file_exists($rootUrl) && false == $this->debug) {
            $resultFiles        = [];
            foreach ($files as $fileCode => $fileTag) {
                $fileCode = $webDir . substr($fileCode, strlen($baseAssetsUrl));
                if (!Url::isRelative($fileCode)) {
                    $resultFiles[$fileCode] = $fileTag;
                }
            }
            $publicUrl                  = $publicUrl . "?v=" . filemtime($rootUrl);
            $resultFiles[$publicUrl]    = Html::jsFile($publicUrl);
            return $resultFiles;
        }

        $resultContent  = [];
        $resultFiles    = [];
        foreach ($files as $fileCode => $fileTag) {
            $fileCode = $webDir . substr($fileCode, strlen($baseAssetsUrl));
            if (Url::isRelative($fileCode)) {
                /**
                 * 先去掉 $file Yii::getAlias("@web")部分
                 */
                $file = substr($fileCode, strlen($webDir));
                $file = ltrim($file, DIRECTORY_SEPARATOR);
                $file = ltrim($file, '\/');
                $filePath = Yii::getAlias('@webroot' . '/' . $file);
                $resultContent[] = $this->fileGetContents( $filePath ) . "\r\n;";
            } else {
                $resultFiles[$fileCode] = $fileTag;
            }
        }
        /**
         * 把lib拷贝过去
         */
        foreach ($this->jsDirs as $jsDir) {
            $jsDir = Yii::getAlias($jsDir);
            if (is_dir($rootDir) && is_dir($jsDir)) {
                $dstDir = $rootDir . '/' . basename($jsDir);
                FileHelper::copyDirectory($jsDir, $dstDir, ['fileMode'=>0777]);
            }
        }

        if ($resultContent) {
            if (!is_dir($rootDir)) {
                if (!FileHelper::createDirectory($rootDir, 0777)) {
                    return $files;
                }
            }

            if ($this->jsFileCompress) {
                foreach ($resultContent as $index => $content) {
                    $resultContent[$index] = JsMin::minify($content);
                }
            }

            $content = implode(";\r\n", $resultContent);
            // D:/Program Files (x86)/EasyPHP-DevServer-14.1VC11/data/localweb/mkmart/app\ht/web/assets/js-compress/03fe340ca976037a19b30d7c4a939866.js
            // D:/Program Files (x86)/EasyPHP-DevServer-14.1VC11/data/localweb/mkmart/app\ht/web/assets/js-compress
            $file = fopen($rootUrl, "w");
            fwrite($file, $content);
            fclose($file);
        }

        if (file_exists($rootUrl)) {
            $publicUrl                  = $publicUrl . "?v=" . filemtime($rootUrl);
            $resultFiles[$publicUrl]    = Html::jsFile($publicUrl);
            return $resultFiles;
        } else {
            return $files;
        }
    }

    /*
     * 例子:
     * Array(
     *   [http://127.0.0.1//mkmart/app\ht/web/exts/base/1.0.0/unit/ui-base/ui-base-1.0.0.css] => <link href="http://127.0.0.1//mkmart/app\ht/web/exts/base/1.0.0/unit/ui-base/ui-base-1.0.0.css" rel="stylesheet">
     *   [http://127.0.0.1//mkmart/app\ht/web/exts/base/1.0.0/unit/ui-base/ui-base-theme-1.0.0.css] => <link href="http://127.0.0.1//mkmart/app\ht/web/exts/base/1.0.0/unit/ui-base/ui-base-theme-1.0.0.css" rel="stylesheet">
     *   [http://127.0.0.1//mkmart/app\ht/web/exts/base/1.0.0/unit/global-init/global-init-1.0.0.css] => <link href="http://127.0.0.1//mkmart/app\ht/web/exts/base/1.0.0/unit/global-init/global-init-1.0.0.css" rel="stylesheet">
     *   [http://127.0.0.1//mkmart/app\ht/web/exts/base/1.0.0/unit/ui-base/custom-1.0.0.css] => <link href="http://127.0.0.1//mkmart/app\ht/web/exts/base/1.0.0/unit/ui-base/custom-1.0.0.css" rel="stylesheet">
     *   [http://127.0.0.1//mkmart/app\ht/web/exts/base/1.0.0/ui/editable/css/editable-1.0.0.css] => <link href="http://127.0.0.1//mkmart/app\ht/web/exts/base/1.0.0/ui/editable/css/editable-1.0.0.css" rel="stylesheet" position="1">
     *   [/mkmart/app\ht/web/assets/630a894c/toolbar.css] => <link href="/mkmart/app\ht/web/assets/630a894c/toolbar.css" rel="stylesheet">
     *)
     * @param array $files
     * @return array
     */
    protected function _processingCssFiles($files = [])
    {
        $baseAssetsUrl = Yii::$app->params['assetsUrl'];
        $webDir = Yii::getAlias("@web");

        $fileName   =  md5( implode(array_keys($files)) . $this->getSettingsHash() ) . '.css';
        $publicUrl  = $baseAssetsUrl . '/assets/css-compress/' . $fileName;

        $rootDir    = Yii::getAlias('@webroot/assets/css-compress');
        $rootUrl    = $rootDir . '/' . $fileName;

        /**
         * 如果压缩文件存在
         * 返回 $resultFiles 结束程序
         */
        if (file_exists($rootUrl) && false == $this->debug) {
            $resultFiles        = [];
            foreach ($files as $fileCode => $fileTag) {
                $fileCode = $webDir . substr($fileCode, strlen($baseAssetsUrl));
                if (false == Url::isRelative($fileCode)) {
                    $resultFiles[$fileCode] = $fileTag;
                }
            }
            $publicUrl                  = $publicUrl . "?v=" . filemtime($rootUrl);
            $resultFiles[$publicUrl]    = Html::cssFile($publicUrl);
            return $resultFiles;
        }

        /**
         * 如果缓存文件不存在,在debug状态下,每次都重新生成文件。
         */
        $resultContent  = [];
        $resultFiles    = [];
        foreach ($files as $fileCode => $fileTag) {
            /**
             * @$baseAssetsUrl 把资源路径截取掉
             */
            $fileCode = $webDir . substr($fileCode, strlen($baseAssetsUrl));
            if (Url::isRelative($fileCode)) {
                /**
                 * 先去掉 $file Yii::getAlias("@web")部分
                 */
                $file = substr($fileCode, strlen($webDir));
                $file = ltrim($file, DIRECTORY_SEPARATOR);
                $file = ltrim($file, '\/');
                $filePath = Yii::getAlias('@webroot' . '/' . $file);
                $contentTmp         = trim($this->fileGetContents( $filePath ));
                $resultContent[] = $contentTmp;
            } else {
                $resultFiles[$fileCode] = $fileTag;
            }
        }

        if ($resultContent) {
            $content = implode($resultContent, "\n");
            if (!is_dir($rootDir)) {
                if (!FileHelper::createDirectory($rootDir, 0777)) {
                    return $files;
                }
            }

            /**
             * 把fonts 和 i 拷贝过去
             */
            foreach ($this->srcDirs as $srcDir) {
                $srcDir = Yii::getAlias($srcDir);
                if (is_dir($rootDir) && is_dir($srcDir)) {
                    $dstDir = $rootDir . '/' . basename($srcDir);
                    FileHelper::copyDirectory($srcDir, $dstDir, ['fileMode'=>0777]);
                }
            }

            if ($this->cssFileCompress) {
                $compressor = new CSSmin();
                $content = $compressor->run($content);
            }
            $file = fopen($rootUrl, "w");
            fwrite($file, $content);
            fclose($file);
        }

        if (file_exists($rootUrl)) {
            $resultFiles = [];
            $publicUrl                  = $publicUrl . "?v=" . filemtime($rootUrl);
            $resultFiles[$publicUrl]    = Html::cssFile($publicUrl);
            return $resultFiles;
        } else {
            return $files;
        }
    }

    /**
     * Read file contents
     * @param $file
     * @return string
     */
    public function fileGetContents($file)
    {
        if (is_readable($file)) {
            return file_get_contents($file);
        }

        return false;
    }
}