前端Node项目发布流程
Posted coder_zyz
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了前端Node项目发布流程相关的知识,希望对你有一定的参考价值。
最近在做前端的发布流程,发布流程的主要实现以下几个方面:
构建:包括javascript、css、html等的压缩,以及版本控制,利用md5生成版本号替换文件引用,实现长缓存策略。
发布:输出新版本的代码,切换系统到新版本
回滚:如果系统有问题,可以切换到原有版本
构建
整个流程由gulp控制,webpack主要处理模块化管理方面的处理,包括基于CommonJs模块规范的包管理,基于SCSS的模块化。
利用Webpack实现JavaScript打包压缩、SCSS编译、CSS文件抽取。
利用gulp-prefix实现cdn url替换
版本替换(revision),利用gulp插件gulp-rev和gulp-rev-all实现文件引用分析和版本替换。
发布
发布时,系统会生成新的版本号,将代码输出到对应版本的目录下,完全与其他版本代码隔离,系统重启后切换到新的版本目录。
回滚
系统可以整体回滚到前一个版本,将系统启动路径切换到上个版本。
实现
目录结构
- source:存放系统源代码
- release:存放系统发布的每个版本的代码,子目录是每个版本的版本号
- logs:存放系统运行日志,pm2的日志存储在这个目录。
- 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
脚本使用日期作为新的版本号。
以上是关于前端Node项目发布流程的主要内容,如果未能解决你的问题,请参考以下文章