更新線上項目中的CSS,JS文件的實現

更新已經上線的項目中的某些CSS,JS文件的時候,我們需要考慮到緩存問題導致的更新的文件無法立即生效。特別是某些項目使用到了CDN緩存項目,這樣更新項目的文件的時候,必須保證原來CDN緩存的文件失效。

如何實現這樣的功能,保證每次類似於CSS,JS文件更新的時候立即生效?

思路是這樣的,我們修改了項目中某個CSS文件的內容,則同時修改CSS的文件名,並且,頁面引用這個CSS文件的時候,改變引用路徑。

 <link rel="stylesheet" type="text/css" href="new_name.css" />

上面的做法是需要我們修改文件名,下面我將介紹一個新的做法。

核心思想是這樣,比如,我們有一個js文件爲item.js,我們給這個文件一個版本號$version = md5_file(item.js), 同時我們生成一個新的文件來保存item.js的內容。 文件名爲item.$version.js,這樣,頁面中不在引用item.js 而是引用 item.$version.js. 同時有一個文件保存了item=>$version對應關係。

這樣,文件內容發生變化,我們不需要改變item.js的命名,只需要修改$version的值。 不管$version如何變化,我們都可以通過item找到它對應的版本號,即可以找到對應的js文件了。

完成這個事情,需要解決一下幾個問題:

可以獲取到需要修改的文件,即知道這個文件名和所在的目錄

需要一個文件保存這個文件的版本號

可以檢測到這個文件內容是否更新

css,js的引入不能寫死在頁面,需要使用程序導入。

額外的一個小建議:

儘量不要再HTML頁面中,寫大量的JS,不利於頁面的加載和JS的管理,可以寫到一個專用的JS文件中,一般項目中會有很多專用JS文件,如果能把多個合併成一個文件,這樣更利於JS的緩存。

核心思路:

第一步:使用一個版本文件保存需要檢測內容是否更新的文件.

例如 這個文件名爲 $fileName = css.version1.php  內容爲  array('a.css' => 'a.'xxxxxx'.css, 'b.css' => b.'yyyyyyy'.css);

其中‘xxxxxxxxxx’,'yyyyyyyyy'是a.css,b.css的版本號,使用md5_file(a.css)這樣的函數獲得。


第二步:正式檢測的文件內容是否更新,比如需要檢測a.css,b.css的內容使用更新,首先讀取a.css,b.css的版本號。即爲讀取css.version1.php文件的內容。

                使用md5_file(a.xxxxxxxxx.css)獲得現在的版本號zzzzzzzzz,通過現在的版本號和原來數組裏面保存的版本號對比,查看文件是否被更新過。


第三步:如果兩次的版本號不一致,則表示內容已經更新了,則更新a.css的版本號,即爲:array(a.css => a.zzzzzzz.csss),同時把a.xxxxxxx.css該名爲a.zzzzzz.css


第四步:頁面導入a.css文件,通過讀取a.css的版本號,找到最新的文件a.zzzzzzz.css,把這個文件引入到頁面中。

流程圖如下:



有一個文件,以數組的形式保存了需要掃描的文件的版本號,通過INCLUDE這個文件,既可以獲取到文件的版本號。核心代碼如下:

    // 記錄的文件類型版本號,文件類型有JS和CSS兩種
    private static $_versions = array();

    public static function getVersions($type)
    {
        // 判斷isset(),是爲了多次調用這個函數的時候,保證數據只有一份,類似於單例
        if (! isset(self::$_versions[$type])) {

            // 文件保存需要掃描的文件的版本號,文件內容是一個數組,形式爲 array('文件名' => '版本號')
            $filePath = DATA_PATH . $type . '_versions.php';
            if (is_file($filePath)) {
                self::$_versions[$type] = include $filePath;
            }

            // 第一次訪問的時候,這個文件還沒有生成數據,需要返回一個空數組
            else {
                self::$_versions[$type] = array();
            }
        }

        return self::$_versions[$type];
    }


需要掃描的JS類型文件的數組:

    /**
     * 需要壓縮合並的JS文件
     */
    public static $jsPacks = array(
// php.js , client.js, i-tips.js 這文件會合併到all.js中
        'all' => array(
            'php',
            'client',
            'i-tips',
        ),

        'friend',
    );

掃描的文件的核心代碼如下:

        // 讀取JS版本文件
        $jsVersions = MyHelper_Loader::getVersions('js');

        foreach (MyHelper_Loader::$jsPacks as $key => $script) {
            if (is_array($script)) {
                $changed = 0;
                foreach ($script as $_script) {
                    if ($this->_compareAndCopy('js', $_script, 'js', $jsVersions)) {
                        $changed++;
                    }
                }
                // 如果子文件有修改,則重新合併文件
                if ($changed > 0) {
                    $this->_merge('js', $key, $script, 'js', $jsVersions);
                }
            }
            // 對比文件是否改變
            else {
                $this->_compareAndCopy('js', $script, 'js', $jsVersions);
            }
        }

        // 更新JS版本文件
        MyHelper_Loader::updateVersionFile('js', $jsVersions);


_compareAndCopy函數作用是判斷文件內容是否改變,若有變化,則需要生成一個新文件,並設置文件的版本號:

    private function _compareAndCopy($dir, $script, $ext, &$versions)
    {
        $orgFile = APP_PATH . 'web/' . $dir .'/' . $script . '.' . $ext;
        $newMd5  = md5_file($orgFile);

        // 文件沒有發生變更
        if (isset($versions[$script]) && $versions[$script] == $newMd5) {
            return false;
        }

        // 拷貝新文件
        $newFile = APP_PATH . 'web/' . $dir . '_product/' . $script . '.' . $newMd5 . '.' . $ext;
        copy($orgFile, $newFile);

        // 刪除舊文件
        if (isset($versions[$script]) && $versions[$script]) {
            unlink(APP_PATH . 'web/' . $dir . '_product/' . $script . '.' . $versions[$script] . '.' . $ext);
        }

        // 重新設置文件的版本號
        return $versions[$script] = $newMd5;
    }

合併文件的核心代碼:

    private function _merge($dir, $mergeName, $scriptArr, $ext, &$versions)
    {
        $all = '';
        foreach ($scriptArr as $script) {
            $sourceFile = APP_PATH . 'web/' . $dir . '_product/' . $script . '.' . $versions[$script] . '.' . $ext;
            $all .= file_get_contents($sourceFile);
        }

        // 刪除舊的合併後文件
        if (isset($versions[$mergeName]) && $versions[$mergeName]) {
            unlink(APP_PATH . 'web/' . $dir . '_product/'. $mergeName . '.' . $versions[$mergeName] . '.' . $ext);
        }

        // 新的隨機版本號
        $versions[$mergeName] = uniqid();

        file_put_contents(APP_PATH . 'web/' . $dir . '_product/'. $mergeName . '.' . $versions[$mergeName] . '.' . $ext, Third_JSMin::minify($all));
    }


更新記錄文件版本號的那個文件的內容:

    public static function updateVersionFile($type, $versions)
    {
        file_put_contents(DATA_PATH . $type . '_versions.php', '<?php return ' . var_export($versions, true) . ';');
    }


在頁面中,調用導入JS文件的函數:

    public static function importJs($scripts)
    {
        // 讀取版本文件
        $versions = self::getVersions('js');

        if (! is_array($scripts)) {
            $scripts = array($scripts);
        }

        $html = '';

        foreach ($scripts as $script) {
            $version = isset($versions[$script]) ? '.' . $versions[$script] : '';
            $html .= '<script type="text/javascript" src="' . JS_DIR  . '_product/' . $script . $version . '.js"></script>';
        }

        return $html;
    }

這樣,既可防止各種緩存問題。







發佈了41 篇原創文章 · 獲贊 14 · 訪問量 14萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章