Using Azure DevOps for NodeJS application optimization using Gulp

Posted by: Subodh Sohoni , on 3/28/2019, in Category DevOps
Views: 5269
Abstract: Using Azure DevOps to perform the build of a NodeJS app and deploy it to Azure App Service as a web app.

In a previous article in this series, we walked through the build and deployment of a NodeJS application on Azure App Service.

In this article, we will cover the following topics:

1. Use of task engine Gulp, in the build workflow. We will use Gulp to optimize the code of a NodeJS application as part of build

2. Archive the output created by Gulp in a deployable package

3. Deploy that archived package on Azure App Service.

Azure DevOps using Nodejs and Gulp

Overview and Use of Gulp

Let us first understand why to use Gulp.

Any standard application will probably have some JavaScript files, HTML files, images, stylesheets etc. When we create these files or edit them in an editor, they are not optimized. These files may have white spaces, formatting etc. which is good for readability, but not so good for the performance of the application. Once a web application has been written and is ready for deployment, it is time to check how optimized it is for performance.

We need to remove all extra spaces and compress the image files. This will reduce the overall size of our files, which allows the browser to download them faster. After all, network latency is one of the most common factors that impacts performance issues in web development. We may also want to obfuscate JavaScript code so that it is not easily readable.

There are different JavaScript libraries that can help with these optimization measures. They are downloadable as Node Modules.

Gulp is one of them.

Gulp allows us to create a set of tasks which are based upon the node modules that I mentioned earlier. It then allows us to execute those tasks as part of an automation script in a specific order or in parallel.

Grunt is another popular tool that also runs a task engine. The benefit of Gulp over Grunt is that Gulp does not store output of a module execution to a physical file, but rather stores it in a memory stream. If there are multiple modules to be executed on a set of input files, Grunt will be slower than Gulp, because it has to write a lot more on file.

Another aspect to consider is Why should I use Gulp with Azure DevOps?.

Gulp is sufficient to build a NodeJS project on its own, but the build which we execute on the command prompt assumes that we have the latest code with us. This assumption is valid when we are developing a trivial project, but when we are developing a non-trivial product in a team, this may not always hold true.

We may also want to set automation of trigger for everyone in the team. Setting up such an automation on each team members machine may be tedious and time consuming. Instead of all this, we can create a build pipeline on Azure DevOps and set the Continuous Integration trigger. We may also extend it to deploy the output of the build to Azure App Service by using Release Management service of Azure DevOps. In this walkthrough, we will use Gulp for optimization of code but use Azure DevOps for orchestrating Gulp and other tasks.

Configuration of Gulp is provided in a JavaScript file called gulpfile.js. Gulp uses npm to install various required node modules and then uses those modules while running the Gulp script.

This article is not a tutorial on Gulp but let me mention some of the libraries that I have used in this walkthrough:

gulp – This itself is a node module which provides a basic functionality of a task engine. Using this node module, we can create tasks that are required to be executed. In the project, this node module is installed using the npm command “npm install gulp --save". It is referenced using a line of code in gulpfile.js “var gulp = require(‘gulp’);”.

gulp-csso – This node module (CSS Optimizer) performs three sorts of transformations: cleaning (removing redundant), compression (replacement for shorter form) and restructuring (merge of declarations, rulesets and so on). As a result, your CSS becomes much smaller.

gulp-image – This node module compresses images to reduce their size.

gulp-uglify – Minimizes JavaScripts

gulp-htmlmin – Minimizes HTML files

gulp-changed – This node module detects if files in a stream have changed. If a file has changed, only then there is a need to run another module on it. Unchanged files are ignored. This reduces the time taken to run the modules.

fancy-log – This node module enables logging of the events on the command prompt.

A sample task created in gulpfile.js may look like this:

var img = require('gulp-image'); 
var changed = require('gulp-changed');
gulp.task('CompressImg', function() {
    return gulp.src("./source/images/*.+(png|jpg|gif)")
        .pipe(changed("./destination/images"))
        .pipe(img())
        .pipe(gulp.dest("./destination/images"));
})

The gulp.src function takes all the files matching a pattern in the project for running a module on it. pipe is a construct in Gulp which allows the use of memory stream between two module executions in the same task. gulp.dest is a function which finally writes the output to some destination location in the file form.

In the gulpfile.js file, we can create a default task. This contains an array of other tasks defined that we want to run. Remember, Gulp can run either in serial or in parallel, or in a combination of both!

gulp.task('default', function(done) {
    var runTasks = gulp.series('CompressImg','CSSOpt','HtmlMin','CopyIndex');
    runTasks();
    done();
})

When we open the command prompt from the directory which has gulpfile.js, we can run the command “Gulp” which will execute the “default” task which in turn will execute all the other configured tasks.

Reviewer Note: After I did npm install gulp –save and then gulp, my console displayed: 'gulp' is not recognized as an internal or external command. After installing gulp globally (npm install -g gulp), it worked.

A full gulpfile.js may look like following:

var gulp = require('gulp');
var csso = require('gulp-csso');
var img = require('gulp-image');
var htmlmin = require('gulp-htmlmin');
var changed = require('gulp-changed');
var autoprefixer = require('gulp-autoprefixer');
const AUTOPREFIXER_BROWSERS = [
    'ie >= 10',
    'ie_mob >= 10',
    'ff >= 30',
    'chrome >= 34',
    'safari >= 7',
    'opera >= 23',
    'ios >= 7',
    'android >= 4.4',
    'bb >= 10'
  ];
gulp.task('CompressImg', function() {
    return gulp.src("./source/images/*.+(png|jpg|gif)")
        .pipe(changed("./destination/images"))
        .pipe(img())
        .pipe(gulp.dest("./destination/images"));
})
gulp.task('CSSOpt', function() {
    return gulp.src("./source/css/style.css")
        .pipe(autoprefixer({browsers: AUTOPREFIXER_BROWSERS}))
        .pipe(csso())
        .pipe(gulp.dest("./destination/css"))
})
gulp.task('HtmlMin', function() {
    return gulp.src("./source/html/*.html")
    .pipe(htmlmin({
        collapseWhitespace: true,
        removeComments: true
        }))
        .pipe(gulp.dest("./destination/html"));
})
gulp.task('CopyIndex', function() {
    return gulp.src("./index.js")
    .pipe(gulp.dest("./destination"));
})
gulp.task('default', function(done) {
    var runTasks = gulp.series('CompressImg','CSSOpt','HtmlMin','CopyIndex');
    runTasks();
    done();
})

What each task in this Gulp script does is get some files from “Source” folder or its subfolders, process that with the help of some module and then write the output into a “Destination” folder.

In case you haven’t read the first article of this series, you can download the readymade app from https://github.com/Azure-Samples/nodejs-docs-hello-world to follow along.

To begin this walkthrough, create a folder structure as shown below.

structure-of-nodejs-app-with-gulp-file

Under the source folder, the html, js, images and css folders contain the respective resources as indicated by the name of the folder. Folder named destination may be retained empty or may have subfolders with the same name. Copy your package.json in the root folder. Just a side note, this folder structure is suggested for this example only and you may have a totally different structure for your app. Create an index.js file with the following code:

var http = require('http'),
    fs = require('fs');
    var port = process.env.PORT;
    function serveStaticFile(res, path, contentType, responseCode) {
        if(!responseCode) responseCode = 200;
        var enc = "utf8";
        var content = fs.readFileSync(__dirname + path, enc, function(err,  data) {
            if(err) {
                res.writeHead(500, { 'Content-Type' : 'text/plain' });
                res.end('500 - Internal Error');
                console.log(err);
            } 
            else {
                res.writeHead( responseCode, { 'Content-Type' : contentType });
                content = data;
                
            }
        });        
        return content;
    }
    http.createServer( function (req, res) {
    var path = req.url.replace(/\/?(?:\?.*)?$/, '').toLowerCase();
    switch(path) {
        case '':
            res.write(serveStaticFile(res, '/html/header.html', 'text/html'));
            res.write(serveStaticFile(res, '/html/home.html', 'text/html'));
            res.write(serveStaticFile(res, '/html/footer.html', 'text/html'));
            
            res.end();
            break;
        case '/home':
            serveStaticFile(res, '/html/home.html', 'text/html');
            break;
        case '/images/ssgslogo.jpg':
            serveStaticFile(res, '/images/ssgslogo.jpg', 'image/jpeg');
            break;
        case '/css/style.css':
            serveStaticFile(res, '/css/style.css', 'text/plain');
            break;
        default:
            serveStaticFile(res, '/html/404.html', 'text/html', 404);
            break;
    }
    
}).listen(port);

console.log("Server running at http://localhost:" + port);

Note: So far, we have set up a node server. In case you are new to all of this, it is advised to read more on gulp and node.

Observe that we have provided an environment variable named process.env.PORT to the port. This is because we are going to deploy it to Azure App Service as a NodeJS web app and that does not allow any port other than 80 and 443.

After creating the folder structure and the index.js file, we can now install all the necessary node modules. Those node modules are listed earlier.

Reviewer’s Note: After running npm install, the gulp modules are not installed automatically. Only after we install them manually (to the package.json) with the save-dev parameter, everything works as expected.

You will observe that once these node modules are installed, your package.json file gets updated to reflect them. Now it will look like this:

{
    "name": "app-service-hello-world",
    "description": "Simple Hello World Node.js sample for Azure App Service",
    "version": "0.0.1",
    "private": true,
    "license": "MIT",
    "author": "Microsoft",
    "scripts": {
        "start": "node ./index.js",
        "test": "mocha --timeout 10000 --reporter mocha-junit-reporter --reporter-options mochaFile=./test/test-result.xml"
    },
    "dependencies": {
        "chai": "^4.2.0",
        "fancy-log": "^1.3.3",
        "fs": "0.0.1-security",
        "gulp": "^4.0.0",
        "gulp-autoprefixer": "^6.0.0",
        "gulp-changed": "^3.2.0",
        "gulp-cli": "^2.0.1",
        "gulp-csso": "^3.0.1",
        "gulp-htmlmin": "^5.0.1",
        "gulp-image": "^4.4.1",
        "gulp-uglify": "^3.0.1",
        "mocha": "^5.2.0",
        "mocha-junit-reporter": "^1.18.0",
        "request": "^2.88.0"
    }
}

Reviewer’s Note: Depending on how experienced you are with these technologies, sometimes it is a good idea to execute the gulp script locally, before running it online. This makes it a lot easier to debug and see what’s going on.

Datadog, a monitoring and analytics platform that integrates with more than 250 technologies, unites metrics, traces, and logs in one platform so you can get full visibility into your infrastructure and applications. With powerful dashboards, anomaly and outlier detection, and distributed tracing, Datadog helps you get a handle on the performance of your .NET applications, as well as their underlying infrastructure.

Click here to try Datadog’s full-stack monitoring for free! (No Credit Card required)

Add code to git and Azure DevOps repo

We are now ready with the code and can commit/push it to the remote repository on your team project, which we created in the walkthrough during the first article of this series.

Build Pipeline creation

We will now create a build pipeline which will execute this Gulp script, archive the output and create an artifact that can be handed over to the release management for deployment.

Login to your Azure DevOps account and browse to the team project that we had created during the walkthrough of the first article in this series. In the Repos section, you will be able to see the pushed code. Now click the Pipelines – Build tab and then click the New Pipeline button.

For creation of this build pipeline, you can use the template of NodeJS with gulp.

select-build-pipeline-template-for-nodejs-with-gulp

This template adds following tasks to the workflow of the pipeline:

1. npm install – For this task, select the folder that contains your package.json file. You can leave the other parameters to their default:

tasks-under-build-pipeline

2. Run gulp – Select the gulpfile.js which will provide the gulp script to the build engine. This script will create a “destination” folder on the agent and then will create subfolders and files according to the script that we have written.

details-of-run-gulp-task

3. Archive file – This task will create a .ZIP package containing contents of the “destination” folder.

archive-task-under-build-pipeline

4. Publish Artifact – This will create a “drop” artifact that will contain the contents of the “destination” folder.

You will observe that there are a couple of files (.config and .json) that are at the root of the application. We will need to include these files too under the artifact. To do so, add a task of Copy Files before the Archive Files task. Set the root folder of the application as the “source folder” and “destination” as the “target folder”. Set *.config and *.json as the contents to copy.

copy-files-task

As a last setting for the build, open the Triggers tab of the build pipeline designer and enable the “Continuous Integration” trigger.

Now we can queue the build which will create the artifact that contains the .ZIP package which can be deployed to Azure App Service.

When the build is over, you can observe the “drop” artifact created by the build, which will contain the NodeApp.zip file as was configured.

Create Web application with NodeJS on Azure App Service

You will now create a web application that has support of NodeJS on Azure App Service. This part of the walkthrough is exactly similar to the part in the first article of this series. Steps to do will be:

1. Open the Azure Portal

2. Create a new Web Application in Azure App Service

3. Set the WEBSITE_NODE_DEFAULT_VERSION setting value to 8.9.4

The next step in this walkthrough is to create a Release Pipeline that will deploy the NodeJS app to the Azure web app created in an earlier step. This step is the same as that of creating a release pipeline in the walkthrough of the previous artifact. In that walkthrough, you had created a release pipeline that consisted of the following tasks:

1. Deploy NodeJS application to Azure web app

2. npm Install task to install all dependencies

3. npm install tasks to install mocha and its dependencies

4. npm test task to run the mocha test

5. Publish test results

In this walkthrough, we will not be adding any tasks related to mocha tests. They will remain exactly the same.

If you want to add a mocha test in the release pipeline, you will need to add a test folder to the “destination” folder so that it will be added to the “drop” artifact and will be available to release management to run the test, similar to what we did in the previous article. Another option is to add the test task which uses “gulp-mocha” node module and run the test as part of the Gulp tasks.

I leave that part of the walkthrough at your discretion.

Walk through these steps to create the new Release Pipeline

1. Select the template of “Deploy a Node.JS app to Azure App Service”.

2. Create the Dev Environment, just like in the previous article.

3. As the artifact provider, select the build pipeline that we created earlier in the walkthrough.

4. Set Latest version of build as the artifact to deploy

5. Select your Azure account to connect to for deployment

6. In task of “Deploy Azure App Service” provide the following values to the parameter

7. Package or folder: $(System.DefaultWorkingDirectory)/_SSGSNodeDemoApp-CI/drop/nodeapp.zip. Replace the path of .ZIP file as appropriate in your case.

8. On the rectangle surrounding the Artifact, select the icon that looks like a lightning bolt, for setting the Continuous Deployment trigger. Enable the Continuous Deployment trigger.

create-release-pipeline

9. Save the release pipeline and create a new release.

Summary:

In this article, we walked through using Azure DevOps to perform the build of a NodeJS app and deploy it to Azure App Service as a web app.

In this build, we used Gulp for the optimization of code and resources. We also took an overview of Gulp as a task manager. This tutorial demonstrated how to set the Continuous Integration and Continuous Deployment triggers for an automated build and release of an application, using Azure DevOps.

Thanks to Tim Sommer for his suggestions and for technically reviewing this article.

This article has been editorially reviewed by Suprotim Agarwal.

Absolutely Awesome Book on C# and .NET

C# and .NET have been around for a very long time, but their constant growth means there’s always more to learn.

We at DotNetCurry are very excited to announce the The Absolutely Awesome Book on C# and .NET. This is a 500 pages concise technical eBook available in PDF, ePub (iPad), and Mobi (Kindle).

Organized around concepts, this eBook aims to provide a concise, yet solid foundation in C# and .NET, covering C# 6.0, C# 7.0 and .NET Core, with chapters on .NET Standard and the upcoming C# 8.0 too. Use these concepts to deepen your existing knowledge of C# and .NET, to have a solid grasp of the latest in C# and .NET OR to crack your next .NET Interview.

Click here to Explore the Table of Contents or Download Sample Chapters!

What Others Are Reading!
Was this article worth reading? Share it with fellow developers too. Thanks!
Share on LinkedIn
Share on Google+

Author
Subodh is a consultant and corporate trainer. He has overall 28+ years of experience. His specialization is Application Lifecycle Management and Team Foundation Server. He is Microsoft MVP – VS ALM, MCSD – ALM and MCT. He has conducted more than 300 corporate trainings and consulting assignments. He is also a Professional SCRUM Master. He guides teams to become Agile and implement SCRUM. Subodh is authorized by Microsoft to do ALM Assessments on behalf of Microsoft. Follow him on twitter @subodhsohoni


Page copy protected against web site content infringement 	by Copyscape




Feedback - Leave us some adulation, criticism and everything in between!

Categories

JOIN OUR COMMUNITY

POPULAR ARTICLES

C# .NET BOOK

C# Book for Building Concepts and Interviews

Tags

JQUERY COOKBOOK

jQuery CookBook