Migrate from Bower to Gulp for Client Libraries Management in ASP.NET Core

access_time 2 years ago visibility359 comment 0


If you have been working on ASP.NET projects in the past years, you probably have heard or used quite a few client library management frameworks/tools. For example, Bower, npm, Gulp, Grunt, Webpack, Yarn, Parcel, Libman, etc. Before SPA became popular, the default ASP.NET (or ASP.NET Core) project templates use Bower as default client management tool and you need to bundle or minimize your client scripts or resources using other tools like Bundler & Minifier. This approach worked quite well for me except that:

  • I still use custom scripts to copy client resources, for example font files, to publish folder (wwwroot).
  • Bower doesn’t support bundle or minimisation. The project also is currently being maintained and it recommends using other frameworks/tools to manage client libs.

In this post, I’m going to show you how to migrate your projects from Bower to Gulp (with npm).


Gulp run on top of nodejs. Please make sure you have nodejs installed in your system.

Existing project

Client packages/libs

The following packages are used by my project (this website) as configured in bower.json file:

  "name": "kontext-v3.0.0-alpha.6",
  "private": true,
  "dependencies": {
    "bootstrap": "v4.3.1",
    "jquery": "3.4.0",
    "jquery-validation": "jquery-validate#1.19.0",
    "jquery-validation-unobtrusive": "v3.2.9",
    "popper.js": "v1.15.0",
    "font-awesome": "4.7.0",
    "Stickyfill": "2.1.0",
    "summernote": "v0.8.9"


The following bundling configurations are created:

// Configure bundling and minification for the project.
// More info at https://go.microsoft.com/fwlink/?LinkId=808241
    "outputFileName": "wwwroot/css/site.min.css",
    // An array of relative input file paths. Globbing patterns supported
    "inputFiles": [
    "outputFileName": "wwwroot/js/site.min.js",
    "inputFiles": [
    // Optionally specify minification options
    "minify": {
      "enabled": true,
      "renameLocals": true
    // Optionally generate .map file
    "sourceMap": false

Now let’s begin to migrate from Bower to Gulp.

Add npm configuration file package.json

First, add a npm Configuration File package.json.


And then add the dependencies for all the current used packages.

Please note some package name is different in npm compared with Bower. For example, Stickyfill is named stickyfilljs in npm instead of Stickyfill. There might be small changes to other packages too. You can go to each package’s official website to find out the package names.

  "version": "3.0.0",
  "name": "kontext",
  "private": true,
  "devDependencies": {
    "gulp": "^4.0.0",
    "gulp-concat": "2.6.1",
    "gulp-cssmin": "0.2.0",
    "gulp-uglify": "3.0.0",
    "rimraf": "2.6.1"
  "dependencies": {
    "bootstrap": "4.3.1",
    "jquery": "3.4.0",
    "jquery-validation": "1.19.0",
    "jquery-validation-unobtrusive": "3.2.9",
    "popper.js": "1.15.0",
    "font-awesome": "4.7.0",
    "stickyfilljs": "2.1.0",
    "summernote": "0.8.9"

I also added devDependencies for Gulp related packages which I’m going to use in the following sections.

After creating this package file, you can then run the following command to install the packages if it is not automatically done:

npm install

Create Gulp script file

Now we can create a JavaScript file named Gulpfile.js in your website project folder.

I’ve created the following JavaScript content to include the tasks I need to bundle and minimize client scripts.

"use strict";

const gulp = require("gulp"),
    rimraf = require("rimraf"),
    concat = require("gulp-concat"),
    cssmin = require("gulp-cssmin"),
    uglify = require("gulp-uglify");

const paths = {
    webroot: "./wwwroot/",
    node_modules: "./node_modules/"

/*CSS files */
paths.cssFiles = [
    paths.webroot + "css/_imports.css",
    paths.node_modules + "bootstrap/dist/css/bootstrap.css",
    paths.node_modules + "font-awesome/css/font-awesome.css",
    paths.node_modules + "summernote/dist/summernote-bs4.css",
    paths.webroot + "css/site.css"
paths.concatCssDest = paths.webroot + "css/site.min.css";

/*Javascript files */
paths.jsFiles = [
    paths.node_modules + "jquery/dist/jquery.js",
    paths.node_modules + "popper.js/dist/umd/popper.js",
    paths.node_modules + "stickyfilljs/dist/stickyfill.js",
    paths.node_modules + "bootstrap/dist/js/bootstrap.js",
    paths.node_modules + "jquery-validation/dist/jquery.validate.js",
    paths.node_modules + "jquery-validation-unobtrusive/dist/jquery.validate.unobtrusive.js",
    paths.webroot + "js/site.js"];
paths.concatJsDest = paths.webroot + "js/site.min.js";

/*Editor js files */
paths.jsFileEditor = [paths.node_modules + "summernote/dist/summernote-bs4.js"];
paths.concatJsDestEditor = paths.webroot + "js/site.editor.min.js";

/*Clean tasks*/
gulp.task("clean:js", done => rimraf(paths.concatJsDest, done));
gulp.task("clean:js:editor", done => rimraf(paths.concatJsDestEditor, done));
gulp.task("clean:css", done => rimraf(paths.concatCssDest, done));
gulp.task("clean:fonts:fontawesome", done => rimraf(paths.webroot + "fonts/", done));
gulp.task("clean:fonts:summernote", done => rimraf(paths.webroot + "css/font/", done));
gulp.task("clean", gulp.series(["clean:js", "clean:js:editor", "clean:css", "clean:fonts:fontawesome", "clean:fonts:summernote"]));

gulp.task("min:css", () => {
    return gulp.src(paths.cssFiles, { base: "." })

gulp.task("min:js", () => {
    return gulp.src(paths.jsFiles, { base: "." })

gulp.task("min:js:editor", () => {
    return gulp.src(paths.jsFileEditor, { base: "." })

gulp.task("copy:font:fontawesome", () => {
    return gulp.src(paths.node_modules + "font-awesome/fonts/*")
        .pipe(gulp.dest(paths.webroot + "fonts/"));

gulp.task("copy:font:summernote", () => {
    return gulp.src(paths.node_modules + "summernote/dist/font/*")
        .pipe(gulp.dest(paths.webroot + "css/font/"));

gulp.task("min", gulp.series(["min:js", "min:js:editor", "min:css"]));

gulp.task("build", gulp.series(["min", "copy:font:fontawesome", "copy:font:summernote"]));

// A 'default' task is required by Gulp v4
gulp.task("default", gulp.series(["min"]));

In this post, I am not going to cover the details about Gulp. If you want to learn more, please visit Gulp official website.

Basically the following Gulp tasks are created:

  • Tasks to clean existing bundled files (CSS and JavaScript) and fonts
  • Tasks to minimize CSS files into one single CSS file.
  • Tasks to bundle and ugnify JavaScript files.
  • Tasks to run multiple serial tasks.

Once these tasks are created, you can view all the tasks in Task Runner Explorer:


During development time, you can run each task by right-click the task in Task Runner Explorer or through nodejs command:

node node_modules\\gulp\\bin\\gulp.js {task name}

Gulp glob

In the above Gulp script file, I’m using script file names to make it easier for you to understand. However, usually you can use Gulp globs to match different files in one go.

For more details, refer to Explaining Globs.

Add nodejs commands into project file

<Target Name="PrepublishScript" BeforeTargets="BeforePublish">
     <Exec Command="npm install" />
     <Exec Command="node node_modules\\gulp\\bin\\gulp.js clean" />
     <Exec Command="node node_modules\\gulp\\bin\\gulp.js build" />

You can now add the above code to your website project file so that npm and Gulp commands are called before publishing the website.

What about development environments?

In your project, you might be directly using these CSS or script files like the following:

<environment include="Development">
     <link rel="stylesheet" href="~/css/_imports.css" />
     <link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
     <link rel="stylesheet" href="~/css/site.css" />
     <link rel="stylesheet" href="~/lib/font-awesome/css/font-awesome.css" />
     <link rel="stylesheet" href="~/lib/summernote/dist/summernote-bs4.css" />
<environment exclude="Development">
     <link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />

Now because these libraries are not existing in your wwwroot folder (instead in node_modules filder), the code won’t work anymore.

To make sure it still works, we can just add middleware to route those requests to node_modules folder:

if (env.IsDevelopment())
                     app.UseStaticFiles(new StaticFileOptions()
                         FileProvider = new PhysicalFileProvider(
                           Path.Combine(Directory.GetCurrentDirectory(), @"node_modules")),
                         RequestPath = new PathString("/lib")


Now you will have a very clean wwwroot folder in your ASP.NET core websites. Gulp doesn’t only support bundle and minimisation but also can copy other resources like fonts into your publish folder.

You can also use Webpack (also based on nodejs) to implement similar tasks. In fact, ASP.NET SPA project template by default uses Webpack as client package management tool.

info Last modified by Raymond at 2 years ago copyright This page is subject to Site terms.
Like this article?
Share on

Please log in or register to comment.

account_circle Log in person_add Register

Log in with external accounts

Want to publish your article on Kontext?

Learn more

Kontext Column

Created for everyone to publish data, programming and cloud related articles.
Follow three steps to create your columns.

Learn more arrow_forward

More from Kontext

local_offer asp.net core 2 local_offer asp.net core local_offer dotnetcore local_offer open-banking

visibility 266
thumb_up 0
access_time 2 years ago

I’ve just started an asp.net core 2.2 based implementation for Australia Consumer Data Standards (published by Data 61). Opening Banking initiative will follow these standards. The purpose is to help you to get familiar with these standards, especially the APIs that need to be implemented. The ...

local_offer asp.net core local_offer asp.net core 3

visibility 95
thumb_up 0
access_time 5 months ago

Sign-in with social accounts like Google, Microsoft, Twitter and Facebook accounts are very commonly used in websites to allow website users to logon easily without registering an separate account. During the implementation of Kontext Google sign-in function, I encountered an error: Exception ...

local_offer asp.net core 2 local_offer asp.net core

visibility 1156
thumb_up 0
access_time 4 years ago

Other related issues are found during my migration. https://stackoverflow.com/questions/46118930/unable-to-change-asp-identity-table-names-asp-net-core-2 I faced the same issue as the above post. To fix it, I need to derive my database context with all the parameters specified: public ...

About column

Articles about ASP.NET Core 1.x, 2.x and 3.x.

rss_feed Subscribe RSS