前端Node項目發佈流程

最近在作前端的發佈流程,發佈流程的主要實現如下幾個方面: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

實現

目錄結構

  1. source:存放系統源代碼
  2. release:存放系統發佈的每一個版本的代碼,子目錄是每一個版本的版本號
  3. logs:存放系統運行日誌,pm2的日誌存儲在這個目錄。
  4. current:軟連接,連接到release下面的某個版本的目錄

構建流程

完成構建後,代碼會發布到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

腳本使用日期做爲新的版本號。

相關文章
相關標籤/搜索