zoukankan      html  css  js  c++  java
  • 前端Node项目发布流程

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

    构建:包括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

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

  • 相关阅读:
    Don't set high speed for the 'DoubleClick'
    How to set UITextField to ReadOnly
    Parallel World 1 并行世界的两个基本问题
    Parallel World 3 – Parallel Loop (2)
    hdu 2680 Choose the best route Dijkstra 虚拟点
    hdu 2255 KM算法
    最小生成树 kruskal 和 pime 模版
    hdu 1863 畅通工程 最小生成树+并查集
    hdu 2603 过山车 最大匹配,匈牙利算法模板(易理解)
    KM 最优匹配 讲解
  • 原文地址:https://www.cnblogs.com/coder-zyz/p/6748983.html
Copyright © 2011-2022 走看看