VSTS - VSTS Build - Node.js 後篇

02 December 2015 — Written by Sky Chang
#Azure#VSTS#CI/CD#JavaScript#Release Management#Node.js

前言

上一篇我們進行了基本的設定,設定了 npm 流程,接著 gulp 然後再佈署到 Azure Web Site。

但,如果是常寫 Node.js 的神人們,一定會知道,光是這樣是不夠的...

那我們還欠缺甚麼呢??..

  1. gulp 的設定檔案
  2. 給 Azure Web Site 用的 Web.config
  3. Azure Web Site 設定使用 Node.js 5.0.0 版本

所以這篇,我們就來解決這些事情。

gulp 的設定

畢竟我們使用到 gulp 來 run 流程,所以當然要先設定 gulp ,而對於 gulp 不熟悉的朋友可以參考這篇,礙於篇幅,小弟就不針對 gulp 做詳細的介紹了,請多見諒。

如果大家仔細看底下的 Code ,大致上可以發現,require 的東西都還滿常見的,只有兩個是平常比較稍微沒用到的 gulp-zipminimist

所以,在 run gulp 之前,我們可以先用

npm i gulp-zip minimist -D 

來進行安裝。

gulp-zip ,其實大家應該不難猜到,他就是負責幫我們進行打包的 package ,我們會用 gulp-zip 將所有檔案打包成 package.zip。

那 minimist 是甚麼呢?

他其實可以讓我們把參數變成物件寫在設定檔裡面,有興趣的可以參考這篇

接下來,我們稍微看一下 Code ,首先,定義了一個 knownOptions 物件,這個物件裡面定義了 packageName 和 packagePath 路徑;接下來,我們透過 minimist 將此物件轉成參數物件;到時候會將參數提供給 gulp-zip 使用,簡單的說,就是告訴 gulp-zip ,壓縮後的檔案名稱是甚麼,要放在哪邊。

接下來的 Code 就比較好玩一點,首先有一個 packagePaths 的陣列,裡面紀錄了哪些要壓縮,那些不要壓縮,** 代表全部要壓縮,但排除任何目錄底下的 _package ,底下的所有檔案 ( 同理可證 !/typings/ )。

那接下來,他又做了哪些事情哩...

接著,他使用 fs 來把 package.json 讀進來,並且取出 package.json 裡面的 devDependencies,並且把這些全部都排除掉!!

簡單的說,打包的 Package.zip 完全不包含 devDependencies 底下的 Package ,所以如果有誤放位置的,請移到 Dependencies 底下,不然 package 就不會傳上去了。

最後,就透過 gulp 進行打包的動作....

var gulp = require('gulp');
var path = require('path');
var zip = require('gulp-zip');
var minimist = require('minimist');
var fs = require('fs');

var knownOptions = {
	string: 'packageName',
	string: 'packagePath',
	default: {packageName: "Package.zip", packagePath: path.join(__dirname, '_package')}
}

var options = minimist(process.argv.slice(2), knownOptions);

gulp.task('default', function () {

	var packagePaths = ['**', 
					'!**/_package/**', 
					'!**/typings/**',
					'!typings', 
					'!_package', 
					'!gulpfile.js']
	
	//add exclusion patterns for all dev dependencies
	var packageJSON = JSON.parse(fs.readFileSync(path.join(__dirname, 'package.json'), 'utf8'));
	var devDeps = packageJSON.devDependencies;

	for(var propName in devDeps)
	{
		var excludePattern1 = "!**/node_modules/" + propName + "/**";
		var excludePattern2 = "!**/node_modules/" + propName;
		packagePaths.push(excludePattern1);
		packagePaths.push(excludePattern2);
	}
	
    return gulp.src(packagePaths)
        .pipe(zip(options.packageName))
        .pipe(gulp.dest(options.packagePath));
});

另外,怎麼可以只寫 gulp 忘記 webpack 勒!? 底下是小弟改的 webpack 搭配 gulp 的版本,這邊就不解釋了,基本上邏輯和上面一樣。

var gulp = require("gulp");
var gutil = require("gulp-util");
var webpack = require("webpack");
var WebpackDevServer = require("webpack-dev-server");
var webpackConfig = require('./webpack-dev.config');
var path = require('path');
var zip = require('gulp-zip');
var minimist = require('minimist');
var fs = require('fs');

var knownOptions = {
	string: 'packageName',
	string: 'packagePath',
	default: {packageName: "Package.zip", packagePath: path.join(__dirname, '_package')}
}

var options = minimist(process.argv.slice(2), knownOptions);

gulp.task("webpack", function(callback) {
    // run webpack
    webpack(webpackConfig, function(err, stats) {
        if(err) throw new gutil.PluginError("webpack", err);
        gutil.log("[webpack]", stats.toString({
            // output options
        }));
        callback();
    });
});

gulp.task('zip',['webpack'], function () {

	var packagePaths = ['**', 
					'!**/_package/**', 
					'!**/typings/**',
					'!typings', 
					'!_package', 
					'!gulpfile.js']
	
	//add exclusion patterns for all dev dependencies
	var packageJSON = JSON.parse(fs.readFileSync(path.join(__dirname, 'package.json'), 'utf8'));
	var devDeps = packageJSON.devDependencies;

	for(var propName in devDeps)
	{
		var excludePattern1 = "!**/node_modules/" + propName + "/**";
		var excludePattern2 = "!**/node_modules/" + propName;
		packagePaths.push(excludePattern1);
		packagePaths.push(excludePattern2);
	}
	
    return gulp.src(packagePaths)
        .pipe(zip(options.packageName))
        .pipe(gulp.dest(options.packagePath));
});


gulp.task('default', ['zip']);

當設定完成後,可以先在 local run gulp 看看,如果順利,就可以看到壓縮檔,但別忘記砍掉,不然不小心簽入進去,到時候會被一起包起來送到 Azure 阿!!!

Web.config 設定

相對於 gulp ,web.config 就相對簡單; web.config 是提供 Azure 上 web site ( iis ) 的 Node.js 設定環境,基本上只要照 copy 就可以,若有需求,可以自行再去調整。

<?xml version="1.0" encoding="utf-8"?>
<!--
     This configuration file is required if iisnode is used to run node processes behind
     IIS or IIS Express.  For more information, visit:

     https://github.com/tjanczuk/iisnode/blob/master/src/samples/configuration/web.config
-->

<configuration>
  <system.webServer>
    <!-- Visit http://blogs.msdn.com/b/windowsazure/archive/2013/11/14/introduction-to-websockets-on-windows-azure-web-sites.aspx for more information on WebSocket support -->
    <webSocket enabled="false" />
    <handlers>
      <!-- Indicates that the server.js file is a node.js site to be handled by the iisnode module -->
      <add name="iisnode" path="Server.js" verb="*" modules="iisnode"/>
    </handlers>
    <rewrite>
      <rules>
        <!-- Do not interfere with requests for node-inspector debugging -->
        <rule name="NodeInspector" patternSyntax="ECMAScript" stopProcessing="true">
          <match url="^Server.js\/debug[\/]?" />
        </rule>

        <!-- First we consider whether the incoming URL matches a physical file in the /public folder -->
        <rule name="StaticContent">
          <action type="Rewrite" url="public{REQUEST_URI}"/>
        </rule>

        <!-- All other URLs are mapped to the node.js site entry point -->
        <rule name="DynamicContent">
          <conditions>
            <add input="{REQUEST_FILENAME}" matchType="IsFile" negate="True"/>
          </conditions>
          <action type="Rewrite" url="Server.js"/>
        </rule>
      </rules>
    </rewrite>
    
    <!-- 'bin' directory has no special meaning in node.js and apps can be placed in it -->
    <security>
      <requestFiltering>
        <hiddenSegments>
          <remove segment="bin"/>
        </hiddenSegments>
      </requestFiltering>
    </security>

    <!-- Make sure error responses are left untouched -->
    <httpErrors existingResponse="PassThrough" />

    <!--
      You can control how Node is hosted within IIS using the following options:
        * watchedFiles: semi-colon separated list of files that will be watched for changes to restart the server
        * node_env: will be propagated to node as NODE_ENV environment variable
        * debuggingEnabled - controls whether the built-in debugger is enabled

      See https://github.com/tjanczuk/iisnode/blob/master/src/samples/configuration/web.config for a full list of options
    -->
    <!--<iisnode watchedFiles="web.config;*.js"/>-->
  </system.webServer>
</configuration>

但調整完後,要特別注意,別忘記要把 web.config 也簽入到版控,這樣才會被一起打包送到 Azure 喔!!

Azure Node.js 版本設定

請直接參考這篇

開始執行 Build

經過長時間的設定,我們終於可以來測試看看了。

要測試非常簡單,我們只要在我們建立好的 Build 上面,按下滑鼠右鍵。 ( 是的,雖然他是網頁,但請勇敢地按下滑鼠右鍵 ) ,並且選擇 Queue Build ,他就會將我們的 Build 放到 Queue 裡面,並且馬上執行。

接著,我們就可以看到 Build 已經執行在跑了,如果有任何的錯誤,也會顯示於 log 上,大家可以看錯誤訊息後,再來調整看哪邊出錯,如果成功後,就會顯示綠燈了。

後記

為了寫新版的 Build 整整寫了四篇,( 包含一篇超短的 Azure Node.js 版本設定 ) ,小弟就在最後談談用了 Build 的感想吧...

老實說,剛建完後的感覺,並沒有甚麼特別的...心中的 OS 大概就是 : "阿,弄好了..." 類似這樣的感覺。但建完後的幾天,寫 Code ,簽入,寫 Code ,簽入,這種感覺才真正的出來;那時,會有一種," 阿!!! 好順暢喔 !!! ",尤其是在弄 Node.js 和前端的時候...

以前往往弄完,還要到 Azure 上去 npm install ,這是非常非常久的一件事情,我同事也深受其害 XDDD,但又不可能把 package 全部簽入進去... ,所以佈署變成一種奢侈,甚至是到 Demo 之前,才會花一整天的時間去串...

但現在串起來後,改 bug ,新功能,簽入後,就不用管他了,後面整個串起來,自己處理,我們只需要喝口可樂,上個廁所,回來一切都搞定了!!! ( 可惜還是只有我自 High ,公司大頭還是無感,畢竟現在還沒正式發布產品... )

而這種速度,也代表著 DevOps 時代的來臨,雖然這不能代表整個 DevOps,但當使用者反饋問題,或是監控到 bug,我們可以透過高速的佈署速度 ,很快的解決和發佈新功能,更快速的反應給客戶,加速流程,這也是重要的一環阿!!!

最後,還是要提一下,真實環境並非那樣的單純,可能佈署完後,還要搭配測試,也可能在 gulp 裡面包測試,其次,這邊在真實環境裡面,可能只是佈署到 Team 的內部測試環境,而真實環境,可能還有預備環境和正式環境等等,所以我們可能還會搭配 Release Management 來整合其他的過程,但不管怎樣,當完成這段的時候,你已經進了一大步了。

大家一起加油!! 愛上敏捷,愛上 DevOps ~

參考資料

Sky & Study4.TW