一個小型項目的前端構建方案

首先,給你們說明一下此次項目的背景,是針對於一個用smarty模板引擎寫的開放平臺的UI改版工做,需求比較急。原始架構是基於php yii框架,先後端代碼雜糅在一塊兒,邏輯也較爲混亂,如下是其基本目錄結構:javascript

基本的業務代碼是在websites文件目錄下,assets文件夾目錄下存儲了一些靜態文件資源。鑑於以上幾個緣由,最後定的方案是,不進行html代碼的重構,只進行css代碼的樣式覆蓋並採用less語言進行開發從而提升效率。php

方案肯定好了,接下來就是肯定前端的一個基本的構建方案,要解決的問題有這麼幾個:css

一、less文件的代碼組織方式。html

二、less文件的編譯打包,你天然不能把一堆less文件扔到線上讓less.min.js去進行編譯解析。前端

三、緩存問題,爲了最大程度的提高性能,咱們需把每次上線的新版本文件加戳,從而不讀取緩存獲取最新的代碼。關於緩存這方面的知識能夠參考https://my.oschina.net/jathon/blog/404968 一文。java

針對於第一個問題,最終採用的方案是根據less自己函數注入、導入、變量等特性而採起組件化開發的思想,對於整個項目進行公用部分的拆分,針對性的對每個組件單獨抽離出相應的less文件樣式。最終的目錄結構以下:(圖一所示)node

       

               圖一                                                  圖二                                                                              圖三web

在d_theme_main.less文件中是對各組件文件的引用,如圖二所示。本次UI改版的樣式文件引用在lib.tpl文件下,如圖三所示。正則表達式

針對於第2、三個問題咱們放在一塊兒進行解決。json

與其餘項目不一樣的是,本次項目開發沒有開發、生產環境之分,不存在輸出目錄的概念。咱們要作的就是:

一、實現對於less文件的編譯打包壓縮,對於各組件的less文件將最終編譯打包到一個css文件裏。

二、實現對lib.tpl文件中對css文件引用的加戳,從而解決緩存問題。

咱們用的是gulp前端自動化工具,將在項目根目錄下建立gulpfile.js文件,並安裝相應的項目依賴。

第一個問題相對來講好解決,gulpfile.js代碼以下:

//導入工具包 require('node_modules裏對應模塊')
var gulp = require('gulp'), //本地安裝gulp所用到的地方
    less = require('gulp-less'),
    rev = require('gulp-rev'),
    nodepath = require('path'),
    through = require('through2'),
    gutil = require('gulp-util'),
    inject = require('gulp-inject'),
    cleanCSS = require('gulp-clean-css'),
    assetRev = require('gulp-asset-rev'),
    revCollector = require('gulp-rev-collector'),
    revReplace = require("gulp-rev-replace");

var path = {
        app: process.cwd(), //當前app的目錄路徑
        basePath: './datawork/websites/visual', //基礎目錄
        lessPath: './datawork/websites/visual/assets/css/theme/', //需打包編譯的less路徑和產出css文件的目錄

    }
//定義一個testLess任務(自定義任務名稱)
gulp.task('testLess', function() {
    gulp.src(path.lessPath + 'd_theme_main.less') //該任務針對的文件
        .pipe(less()) //該任務調用的模塊
        .pipe(cleanCSS())
        .pipe(gulp.dest(path.lessPath)); //將會在src/css下生成相應的css文件
});

通過編譯測試發現結果如預期,對less文件進行編譯打包壓縮,並在當前目錄下生成相應的css文件。

接下來,編寫加戳功能和自動編譯的代碼,代碼以下所示:

//css文件生成hash編碼並生成rev-manifest.json文件名對照映射
gulp.task('revCss',['testLess'], function() {
    return gulp.src(path.lessPath + 'd_theme_main.css')
        .pipe(rev())  //針對相應的css文件生成md5戳
        .pipe(rev.manifest()) //生成rev-manifest.json文件
        .pipe(gulp.dest('./'))  //rev-manifest.json文件的生成位置
})
//參照rev-manifest.json文件,替換相應目錄下的lib.tpl文件對於css文件的引用
gulp.task('revhtml',['revCss'], function() {
    return gulp.src(['./rev-manifest.json', path.basePath + '/protected/views/layouts/lib.tpl'])
        .pipe(revCollector())
        .pipe(gulp.dest(path.basePath + '/protected/views/layouts/'));
})


gulp.task('watch', function() {
    gulp.watch(path.lessPath + '*.less', ['revhtml']);
})
gulp.task('default', ['revhtml', 'watch']); //定義默認任務 

執行gulp命令,獲得的結果以下:

根目錄下生成了manifest.json對應文件
{
  "d_theme_main.css": "d_theme_main-803a7fe4ae.css"
}

<link rel="stylesheet" href="/assets/css/theme/d_theme_main-803a7fe4ae.css">

這顯然不符合咱們的預期,咱們並不須要在相應的目錄下生成d_theme_main-803a7fe4ae.css文件同時改變tpl文件中的引用,咱們只須要在相應的引用後面加戳便可。

更改gulp-rev和gulp-rev-collector

打開node_modules\gulp-rev\index.js 第144行 manifest[originalFile] = revisionedFile; 更新爲: manifest[originalFile] = originalFile + '?v=' + file.revHash;
打開nodemodules\gulp-rev\nodemodules\rev-path\index.js 10行 return filename + '-' + hash + ext; 更新爲: return filename + ext;
打開node_modules\gulp-rev-collector\index.js 31行 if ( !_.isString(json[key]) || path.basename(json[key]).replace(new RegExp( opts.revSuffix ), '' ) !== path.basename(key) ) { 更新爲: if ( !_.isString(json[key]) || path.basename(json[key]).split('?')[0] !== path.basename(key) ) {
打開node_modules\gulp-assets-rev\index.js 78行 var verStr = (options.verConnecter || "-") + md5; 更新爲:var verStr = (options.verConnecter || "") + md5; 80行 src = src.replace(verStr, '').replace(/(\.[^\.]+)$/, verStr + "$1"); 更新爲:src=src+"?v="+verStr;

再執行gulp命令,獲得的結果以下(效果正確):

<link rel="stylesheet" href="/assets/css/theme/d_theme_main.css?v=afd12860ab">

可是假如咱們更改了css和js後,再執行gulp命令,獲得的結果會以下:

<link rel="stylesheet" href="/assets/css/theme/d_theme_main.css?v=afd12860ab?v=803a7fe4ae">

有沒有發現,會在版本號後面再添加一個版本號,由於gulp只替換了原來文件名,這樣又不符合預期效果了,因此咱們想到,還須要修改插件的替換正則表達式。

四、繼續更改gulp-rev-collector

打開node_modules\gulp-rev-collector\index.js 第107行 regexp: new RegExp( '([\/\\\\\'"])' + pattern, 'g' ), 更新爲: regexp: new RegExp( '([\/\\\\\'"])' + pattern+'(\\?v=\\w{10})?', 'g' ),

如今你無論執行多少遍gulp命令,獲得的html效果都是

<link rel="stylesheet" href="/assets/css/theme/d_theme_main.css?v=86f020d88a">

至此,一個較爲完善的一個方案就誕生了,可是還有優化的地方嗎,答案天然天然是有的,假如這時又有其餘人介入此項目的開發,你總不能讓他們根據你的文檔去修改相應插件的源碼吧,因而進行如下修改:

//導入工具包 require('node_modules裏對應模塊')
var gulp = require('gulp'), //本地安裝gulp所用到的地方
    less = require('gulp-less'),
    rev = require('gulp-rev'),
    nodepath = require('path'),
    through = require('through2'),
    gutil = require('gulp-util'),
    inject = require('gulp-inject'),
    cleanCSS = require('gulp-clean-css'),
    revReplace = require("gulp-rev-replace");

var path = {
        app: process.cwd(), //當前app的目錄路徑
        basePath: './datawork/websites/visual', //基礎目錄
        lessPath: './datawork/websites/visual/assets/css/theme/', //需打包編譯的less路徑和產出css文件的目錄

    }
    //定義一個testLess任務(自定義任務名稱)
gulp.task('testLess', function() {
    gulp.src(path.lessPath + 'd_theme_main.less') //該任務針對的文件
        .pipe(less()) //該任務調用的模塊
        .pipe(cleanCSS())
        .pipe(gulp.dest(path.lessPath)); //將會在src/css下生成index.css
});

gulp.task('rev', ['testLess'], function() {
    return gulp.src([path.lessPath + 'd_theme_main.css'])
        .pipe(rev())
        .pipe(function() {
            var hashes = {},
                oPath = '';
            var collect = function(file, enc, cb) {
                if (file.revHash) {
                    var filename = nodepath.basename(file.revOrigPath);
                    hashes[filename] = filename + '?v=' + file.revHash;
                    oPath = file.base;
                }
                return cb();
            }

            var emit = function(cb) {
                if (oPath) {
                    var file = new gutil.File({
                        base: oPath,
                        cwd: process.cwd(),
                        path: nodepath.resolve(oPath, 'rev-manifest.json')
                    });
                    file.contents = new Buffer(JSON.stringify(hashes, null, 4));
                    this.push(file);
                }
                return cb();
            }
            return through.obj(collect, emit);
        }())
        .pipe(gulp.dest(path.app))
        .pipe(rev.manifest())
})

gulp.task('revCollector', ['rev'], function() {
    var manifest = gulp.src([nodepath.resolve(path.app, 'rev-manifest.json')]);
    var cssPath = nodepath.resolve(path.lessPath, 'd_theme_main.css');

    return gulp.src([path.basePath + '/protected/views/layouts/lib.tpl'])
        .pipe(inject(gulp.src(cssPath, {read: false}), {
            transform: function (filepath, file, i, length) {
                var fpath = ['/assets', filepath.split('assets')[1]].join('');
                return '<link rel="stylesheet" href="'+fpath+'">';
            }
        }))
        .pipe(revReplace({ 
            manifest: manifest,
            replaceInExtensions: ['.tpl']
        }))
        .pipe(gulp.dest(path.basePath + '/protected/views/layouts/'));
})

gulp.task('watch', function() {
    gulp.watch(path.lessPath + '*.less', ['revCollector']);
})


gulp.task('default', ['revCollector', 'watch']); //定義默認任務


//gulp.task(name[, deps], fn) 定義任務  name:任務名稱 deps:依賴任務名稱 fn:回調函數
//gulp.src(globs[, options]) 執行任務處理的文件  globs:處理的文件路徑(字符串或者字符串數組) 
//gulp.dest(path[, options]) 處理完後文件生成路徑

至此,一個更爲完善的前端構建方案就誕生了。

相關文章
相關標籤/搜索