最近在作前端的發佈流程,發佈流程的主要實現如下幾個方面:css
構建:包括JavaScript、css、html等的壓縮,以及版本控制,利用md5生成版本號替換文件引用,實現長緩存策略。html
發佈:輸出新版本的代碼,切換系統到新版本前端
回滾:若是系統有問題,能夠切換到原有版本node
整個流程由gulp控制,webpack主要處理模塊化管理方面的處理,包括基於CommonJs模塊規範的包管理,基於SCSS的模塊化。react
利用Webpack實現JavaScript打包壓縮、SCSS編譯、CSS文件抽取。webpack
利用gulp-prefix實現cdn url替換git
版本替換(revision),利用gulp插件gulp-rev和gulp-rev-all實現文件引用分析和版本替換。web
發佈時,系統會生成新的版本號,將代碼輸出到對應版本的目錄下,徹底與其餘版本代碼隔離,系統重啓後切換到新的版本目錄。npm
系統能夠總體回滾到前一個版本,將系統啓動路徑切換到上個版本。json
完成構建後,代碼會發布到release目錄下。
gulp流程代碼以下:
var gulp = require('gulp'); var minimist = require('minimist'); var uglify = require('gulp-uglify'); var minifyHtml = require('gulp-minify-html'); var minifyCss = require('gulp-minify-css'); var rev = require('gulp-rev'); var revReplace = require('gulp-rev-replace'); var prefix = require('gulp-prefix'); var zip = require('gulp-zip'); var gulpSequence = require('gulp-sequence'); var RevAll = require('gulp-rev-all'); var syncy = require('syncy'); var dateFormat = require('dateformat'); var webpack = require("webpack"); var gutil = require("gulp-util"); var nodemon = require('gulp-nodemon'); var gls = require('gulp-live-server'); var webpackConfig = require("./webpack.config.js"); var path = require('path'); var revHash = require('rev-hash'); /** * 生產環境構建 */ var date = dateFormat(new Date(), 'yyyymmddhh'); var version = date; var options = minimist(process.argv.slice(2), { string: 'v', default: { v: date } }); if (options.v) version = options.v; var option = { src: '.', dest: '../release/' + version + '/', cdn: 'http://localhost:8082/',///poster/ static: '../release/' + version + '/public/' } //統一加MD5以後替換引用 gulp.task('rev', function () { var revAll = new RevAll({ dontRenameFile: [/^\/.*.html/] });// ,/^\/.*.jpg|png/ gulp.src([option.src + '/public/**']) .pipe(revAll.revision()) .pipe(gulp.dest(option.static)) .pipe(revAll.versionFile()) .pipe(gulp.dest(option.static)) .pipe(revAll.manifestFile()) .pipe(gulp.dest(option.static)); }); gulp.task("rep", function () { var manifest = gulp.src(option.static + "/rev-manifest.json"); return gulp.src([option.src + '/app/views/**/*.ejs']) .pipe(revReplace({ manifest: manifest, replaceInExtensions: ['.ejs'] })) .pipe(gulp.dest((option.dest + '/app/views/'))) }); gulp.task('syncfile', (done) => { syncy([ option.src + '/**', '!' + option.src + '/.*', '!' + option.src + '/public/**', '!' + option.src + '/app/views/**'], option.dest) .then(() => { done(); }) .catch((err) => { done(err); }); }); gulp.task('cdn_ejs', function () { console.log('EJS加CDN前綴...'); return gulp.src(option.dest + '/app/views/**/*.ejs') .pipe(prefix(option.cdn, null)) .pipe(gulp.dest(option.dest + '/app/views/')); }) gulp.task('cdn_html', function () { console.log('HTML加CDN前綴...'); return gulp.src([option.static + '/**/*.html']) .pipe(prefix(option.cdn, null)) .pipe(gulp.dest(option.static)); }) gulp.task('htmlmin', function () { return gulp.src([option.static + '/**/*.html']) .pipe(minifyHtml()) .pipe(gulp.dest(option.static)); }) //構建js和css gulp.task("webpack:build", function (callback) { // modify some webpack config options var myConfig = Object.create(webpackConfig); myConfig.plugins = myConfig.plugins.concat( new webpack.DefinePlugin({ "process.env": { // This has effect on the react lib size "NODE_ENV": JSON.stringify("production") } }), new webpack.optimize.DedupePlugin(), new webpack.optimize.UglifyJsPlugin() ); // run webpack webpack(myConfig, function (err, stats) { if (err) throw new gutil.PluginError("webpack:build", err); gutil.log("[webpack:build]", stats.toString({ colors: true })); callback(); }); }); //構建任務 gulp.task('build', ['webpack:build'], function (cb) { gulpSequence('rep', ['cdn_ejs', 'cdn_html'], ['htmlmin'], cb) })
webpack配置以下:
var webpack = require('webpack'); var path = require('path'); var fs = require('fs'); //讀取文件夾內的文件列表 var files = fs.readdirSync('./public/module/'); var ExtractTextPlugin = require("extract-text-webpack-plugin"); //以文件名做爲屬性組裝配置文件 var config = {}; files.forEach(function (file) { var cfgs = require('./public/module/' + file)['entry']; var entrys = []; cfgs.forEach(function (cfg) { entrys.push(path.resolve(__dirname, './public/', cfg)); }) config[path.parse(file).name] = entrys; }) console.log(config); module.exports = { entry: config, output: { path: path.join(__dirname, "./public/build/"), filename: "[name]/[name].entry.js", chunkFilename: "[id].js", publicPath: "assets/" }, module: { loaders: [ // Extract css files { test: /\.css$/, loader: ExtractTextPlugin.extract("style-loader", "css-loader") }, // Optionally extract less files // or any other compile-to-css language { test: /\.scss$/, loader: ExtractTextPlugin.extract("style-loader", "css-loader!sass-loader") } // You could also use other loaders the same way. I. e. the autoprefixer-loader ] }, // Use the plugin to specify the resulting filename (and add needed behavior to the compiler) plugins: [ new ExtractTextPlugin("[name]/[name].css") ] };
執行bash腳本,首先刪除current軟連接,刪除軟連接並不影響當前系統,由於當前系統連接到的是Release目錄的某個版本的目錄,刪除後從新創建軟連接,連接到新的版本目錄下,而後利用pm2重啓。
echo 【delete current link】 rm -rf current echo 【create current link】 ln -s release/$version current echo 【restarting application......】 cd current pm2 startOrRestart ./ecosystem.json --env production
在代碼路徑下,添加了一個build.sh的腳步,每一個構建執行,完整腳步以下:
version=$(date +%Y%m%d%H%m) echo 【start build $version】 echo 【updating project .....】 git pull echo 【finish update!】 echo 【updating environment......】 npm install echo 【finish update!】 echo 【building......】 gulp syncfile -v $version gulp rev -v $version gulp build -v $version echo 【finish build】 cd ../ echo 【delete current link】 rm -rf current echo 【create current link】 ln -s release/$version current echo 【restarting application......】 cd current pm2 startOrRestart ./ecosystem.json --env production
腳本使用日期做爲新的版本號。