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

event 2019-04-14 visibility 998 comment 0 insights
more_vert
insights Stats
Raymond Raymond ASP.NET Core

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

Background

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).

Prerequisites

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"
  }
}

bundleconfig.json

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": [
      "wwwroot/lib/bootstrap/dist/css/bootstrap.css",
      "wwwroot/lib/font-awesome/css/font-awesome.min.css",
      "wwwroot/lib/summernote/dist/summernote-bs4.css",
      "wwwroot/css/site.css"
    ]
  },
  {
    "outputFileName": "wwwroot/js/site.min.js",
    "inputFiles": [
      "wwwroot/lib/jquery/dist/jquery.js",
      "wwwroot/lib/popper.js/dist/umd/popper.js",
      "wwwroot/lib/Stickyfill/dist/stickyfill.js",
      "wwwroot/lib/bootstrap/dist/js/bootstrap.js",
      "wwwroot/lib/jquery.toc/jquery.toc.js",
      "wwwroot/lib/jquery-validation/dist/jquery.validate.js",
      "wwwroot/lib/jquery-validation-unobtrusive/dist/jquery.validate.unobtrusive.js",
      "wwwroot/js/site.js"
    ],
    // 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.

image

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: "." })
        .pipe(concat(paths.concatCssDest))
        .pipe(cssmin())
        .pipe(gulp.dest('.'));
});

gulp.task("min:js", () => {
    return gulp.src(paths.jsFiles, { base: "." })
        .pipe(concat(paths.concatJsDest))
        .pipe(uglify())
        .pipe(gulp.dest("."));
});

gulp.task("min:js:editor", () => {
    return gulp.src(paths.jsFileEditor, { base: "." })
        .pipe(concat(paths.concatJsDestEditor))
        .pipe(uglify())
        .pipe(gulp.dest("."));
});


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:

image

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" />
   </Target>

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>
<environment exclude="Development">
     <link rel="stylesheet" href="~/css/site.min.css" asp-append-version="true" />
</environment>

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")
                     });
                 }

Summary

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.

More from Kontext
comment Comments
No comments yet.

Please log in or register to comment.

account_circle Log in person_add Register

Log in with external accounts