更新线上项目中的CSS,JS文件的实现

来源:互联网 发布:天启软件华为 编辑:程序博客网 时间:2024/06/10 08:29

更新已经上线的项目中的某些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;    }

这样,既可防止各种缓存问题。







原创粉丝点击