最近在做前端的发布流程,发布流程的主要实现以下几个方面:

构建:包括JavaScript、css、html等的压缩,以及版本控制,利用md5生成版本号替换文件引用,实现长缓存策略。

发布:输出新版本的代码,切换系统到新版本

回滚:如果系统有问题,可以切换到原有版本

构建

整个流程由gulp控制,webpack主要处理模块化管理方面的处理,包括基于CommonJs模块规范的包管理,基于SCSS的模块化。

利用Webpack实现JavaScript打包压缩、SCSS编译、CSS文件抽取。

利用gulp-prefix实现cdn url替换

版本替换(revision),利用gulp插件gulp-rev和gulp-rev-all实现文件引用分析和版本替换。

发布

发布时,系统会生成新的版本号,将代码输出到对应版本的目录下,完全与其他版本代码隔离,系统重启后切换到新的版本目录。

回滚

系统可以整体回滚到前一个版本,将系统启动路径切换到上个版本。

实现

目录结构

  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

脚本使用日期作为新的版本号。

前端Node项目发布流程的更多相关文章

  1. Linux项目发布流程

    Linux项目发布流程(一) 1.安装pyhton3.7 的依赖包 yum -y groupinstall "Development tools" yum -y install z ...

  2. node项目发布+域名及其二级域名配置+nginx反向代理+pm2

    学习node的时候也写了一些demo.但是只是限于本地测试,从来没有发布.今天尝试发布项目. 需要准备的东西 node 项目:为了突出重点,说明主要问题.我只是拿express 写了很简单的demo. ...

  3. node项目的基本构建流程或者打开一个node项目的流程

    1.  确立项目所需要的所有依赖.框架(比如bootstrap,vue,angular等) 2. 在项目的根目录下创建一个package.json文件,package.json文件是项目的最重要文件之 ...

  4. Jenkins构建 前端node项目

    1.新建一个自由风格的项目 2.配置git 3.构建-增加构建步骤-执行shell cd $WORKSPACE npm install --registry=http://ip:port --unsa ...

  5. linux单项目发布流程

    1.安装python #1.安装python3.7所需要的依赖包yum -y groupinstall "Development tools"yum -y install zlib ...

  6. linux 下安装 python ngix 项目发布流程

    1.安装python #1.安装python3.7所需要的依赖包yum -y groupinstall "Development tools"yum -y install zlib ...

  7. docker jenkins 前端node项目 自动化部署异常 env: ‘node’: No such file or directory

    出现问题是docker jenkins 里面没有自动安装node导致找不到这个Node命令 解决方案:手动安装nodejs # 进入jenkins对应容器中 # docker exec -it [对应 ...

  8. k8s:py项目发布完整流程

    k8s:py项目发布流程 1. 编写Dockerfile # cat Dockerfile FROM python:3.6-slim USER root RUN apt-get update & ...

  9. 基于bat脚本的前端发布流程的优化

    背景介绍 前面在基于bat脚本的前端发布流程设计与实现中,我已经介绍了设计与实现,这一篇主要是针对其的一个优化折腾(分两步走,第一步先搞出来,第二步再想着怎么去优化它),我主要做了以下几件事. &qu ...

随机推荐

  1. wex5 windowDialog组件

    windowDialog组件可以实现页面跳转 xid:windowDialog src:$UI/lohas/detailedknowledge.w 父页:myknowledge.w Model.pro ...

  2. PDF转图片,在线PDF转JPG/PNG

    [在线DEMO](https://oktools.net/pdf2img) 原理 使用pdf.js预览图片,pdf.js将pdf通过canvas将每一页渲染出来,然后我们通过canvas的toData ...

  3. 60. Permutation Sequence (JAVA)

    The set [1,2,3,...,n] contains a total of n! unique permutations. By listing and labeling all of the ...

  4. 关于ftp无法链接的情况

    首先查看ftp是否安装 systemctl status vsftpd 如果没有先安装 yum install vsftpd 然后启动 systemctl start vsftpd 如果有报错 Job ...

  5. tp5 apache 转 nginx 需要配置的伪静态

    location / { if (!-e $request_filename){ rewrite ^(.*)$ /index.php?s=$ last; break; } }

  6. IDeajCommunity 配置smart tomcat插件

    ntelliJ IDEA社区版没有自带tomcat. 装插件--smart tomcat. IntelliJ IDEA>>Preferences>>Plugins>> ...

  7. Linux日常之命令uniq

    命令uniq 作用是过滤文件内容重复部分 需要注意的是,该命令只是对相邻的行进行比较,若两个相同的行不相邻,不会被过滤掉 选项 -c,在每行行首显示出该行出现的次数 -d,只显示出重复的行 -u,只显 ...

  8. Web Api 接口返回值不困惑:返回值类型详解

    前言:已经有一个月没写点什么了,感觉心里空落落的.今天再来篇干货,想要学习Webapi的园友们速速动起来,跟着博主一起来学习吧.之前分享过一篇 WebApi 接口参数:传参详解,这篇博文内容本身很基础 ...

  9. QT中获取选中的radioButton的两种方法

    QT中要获取radioButton组中被选中的那个按钮,可以采用两种如下两种办法进行: 方法一:采用对象名称进行获取 代码: 1 QRadioButton* pbtn = qobject_cast&l ...

  10. python 面向对象_1

    self #self 是相当于c++的 this指针 class Ball: def setName(self,name): self.name = name def kick(self): prin ...