VSTS - VSTS Build - 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 之前,我們可以先用

1
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 進行打包的動作….

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
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 的版本,這邊就不解釋了,基本上邏輯和上面一樣。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
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 就可以,若有需求,可以自行再去調整。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
<?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 ~

參考資料