更新线上项目中的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万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章