Migrate Front-end Dependencies to NPM And Gulp Instead Of Bower

In this post we will demonstrate how we migrated a legacy bower based project to NPM and Gulp instead. read more in an updated article about how we Manage JavaTMP Front-end dependencies Using Node.js And Gulp. Bower is a package manager for Javascript libraries and other front-end dependencies that we were using for managing JavaTMP templates front-end dependencies. 

Move dependencies from bower.json to package.json

We were using the following bower.json file:

{
  "name": "javatmp-static-ajax",
  "version": "0.0.1",
  "main": "path/to/main.css",
  "ignore": [
    ".jshintrc",
    "**/*.txt"
  ],
  "dependencies": {
    "jquery": "3.2.1",
    "bootstrap": "^3.3.7",
    "select2": "^4.0.3",
    "select2-bootstrap-theme": "^0.1.0-beta.10",
    "bootstrap-tagsinput": "^0.8.0",
    "autosize": "^4.0.0",
    "summernote": "^0.8.7",
    "iCheck": "^1.0.2",
    "bootstrap-maxlength": "^1.7.0",
    "jquery-idletimer": "^1.0.2",
    "jQuery-contextMenu": "^2.5.0",
    "ion.rangeSlider": "^2.2.0",
    "toastr": "^2.1.3",
    "bootstrap-switch": "^3.3.4",
    "inputmask": "^3.3.7",
    "blockUI": "*",
    "bootstrap-datepicker": "^1.7.1",
    "bootstrap-daterangepicker": "^2.1.25",
    "datatables.net": "^1.10.15",
    "datatables.net-bs": "^2.1.1",
    "datatables.net-responsive": "^2.1.1",
    "datatables.net-responsive-bs": "^2.1.1",
    "datatables.net-fixedheader": "^3.1.2",
    "datatables.net-fixedheader-bs": "^3.1.2",
    "datatables.net-select": "^1.2.2",
    "datatables.net-select-bs": "^1.2.2",
    "fullcalendar": "^3.4.0",
    "jqueryui-touch-punch": "*",
    "jquery-validation": "^1.17.0",
    "font-awesome": "^4.7.0",
    "font-awesome-animation": "^0.0.10",
    "jquery-knob": "^1.2.13",
    "chart.js": "^2.6.0",
    "echarts": "^3.6.2",
    "bootstrap-colorselector": "*",
    "seiyria-bootstrap-slider": "^9.8.1",
    "cropper": "^2.3.4",
    "jquery.fancytree": "fancytree#^2.23.0",
    "jquery-form": "^4.2.2",
    "metisMenu": "^2.7.0",
    "moment": "^2.18.1",
    "typeahead.js": "^0.11.1",
    "jquery-ui": "^1.12.1",
    "scrollup": "^2.4.1",
    "jquery.counterup": "^2.1.0",
    "waypoints": "^4.0.1",
    "multiselect": "^0.9.12",
    "dropzone": "^5.1.1",
    "jquery-timeago": "^1.6.1",
    "jquery.steps": "^1.1.0",
    "ekko-lightbox": "v4.0.2",
    "magnific-popup": "^1.1.0",
    "slick-carousel": "^1.8.1"
  }
}

 And the following our ./.bowerrc file:

{
    "directory": "web/components"
}

After we ran the bower command all the front-end libraries mentioned above stored in folder ./public/components .

Most of our above front-end packages are already available on npm, So we started migrating by first creating package.json file manually or through npm init command.

Using npm init command to create the package.json will prompt you for values for the package.json fields. The two required fields are name and version. You'll also want to have a value for main. You can use the default, index.js.

And the following is a simple package.json file, you could start with:

{
  "name": "javatmp-static-ajax",
  "version": "0.0.1",
  "description": "your application description",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
  },
  "author": "You",
  "license": "",
  "dependencies": {},
  "devDependencies": {}
}

Then, we proceed to move our current bower dependencies from bower.json to our package.json. We could move the dependencies using two methods:

We could add jQuery as npm dependency by invoking:

npm install --save jquery

Or by updating the package.json file and add the following entry to dependencies key:

"jquery": "^3.2.1"

And Then, invoking the following command:

npm install

NPM loads jquery into your local node_modules folder, and stores relevant metadata to package.json. We Repeated this for all of the dependencies that are available on NPM and we ended up with something like:

...
"dependencies": {
    "autosize": "^4.0.0",
    "bootstrap": "^4.0.0",
    "bootstrap-colorselector": "^0.1.0",
    "bootstrap-datepicker": "^1.7.1",
    "bootstrap-daterangepicker": "^2.1.25",
    "bootstrap-maxlength": "^1.6.0",
    "bootstrap-slider": "^10.0.0",
    "bootstrap-switch": "^3.3.4",
    "bootstrap-tagsinput": "^0.7.1",
    "chart.js": "^2.7.1",
    "cropper": "^3.1.3",
    "datatables.net": "^1.10.16",
    "datatables.net-bs4": "^1.10.16",
    "datatables.net-fixedheader": "^3.1.3",
    "datatables.net-fixedheader-bs4": "^3.1.3",
    "datatables.net-responsive": "^2.2.0",
    "datatables.net-responsive-bs4": "^2.2.0",
    "datatables.net-select": "^1.2.3",
    "datatables.net-select-bs4": "^1.2.3",
    "dropzone": "^5.2.0",
    "echarts": "^3.8.5",
    "ekko-lightbox": "^5.3.0",
    "font-awesome": "^4.7.0",
    "font-awesome-animation": "^0.1.0",
    "fullcalendar": "^3.7.0",
    "inputmask": "^3.3.11",
    "ion-rangeslider": "^2.2.0",
    "jquery": "^3.2.1",
    "jquery-contextmenu": "^2.6.3",
    "jquery-form": "^4.2.2",
    "jquery-knob": "^1.2.11",
    "jquery-ui-dist": "^1.12.1",
    "jquery-validation": "^1.17.0",
    "jquery.counterup": "^2.1.0",
    "magnific-popup": "^1.1.0",
    "metismenu": "^2.7.1",
    "moment": "^2.19.3",
    "multiselect": "^0.9.12",
    "popper.js": "^1.13.0",
    "select2": "^4.0.6-rc.1",
    "slick-carousel": "^1.8.1",
    "summernote": "^0.8.8",
    "timeago": "^1.6.1",
    "toastr": "^2.1.2",
    "typeahead.js": "^0.11.1",
    "waypoints": "^4.0.1"
  },
  ...

Fix Issues for non node.js NPM package modules

There were mainly three issues raised when we moved dependencies from bower.json to package.json above:

  1. Not all our front-end libraries were node packages. Some of our front-end libraries exist in github repositories but were not registered in NPM repository.
  2. We need complete github repositories for some libraries, not just what their authors published in NPM repository.
  3. Not all front-end libraries resources are production ready. Some of them are still raw text and need a minification and cleaning.

For those first two issues we used a npm's package called napa which is a helper for installing repositories without a package.json with npm

napa NPM's package link and napa npm's github.com repository

We installed napa package using command:

npm install napa --save-dev

This command will install napa package and update main ./package.json file by adding an entry for napa package in devDependencies key:

...
"devDependencies": {
    ...
    "napa": "^3.0.0",
    ...
},
...

We configured napa package by updating our package.json file like:

1. Using new napa entry in package.json file to hold repositories:

{
  ...	
  "napa": {
    "blockui": "git+https://github.com/malsup/blockui.git",
    "scrollup": "git+https://github.com/markgoodyear/scrollup.git",
    "jquery.fancytree": "git+https://github.com/mar10/fancytree.git",
    "jquery-idletimer": "git+https://github.com/thorst/jquery-idletimer.git",
    "select2-bootstrap-theme": "git+https://github.com/angel-vladov/select2-bootstrap-theme.git",
    "quicksearch": "git+https://github.com/riklomas/quicksearch.git",
    "jquery-ui-touch-punch": "git+https://github.com/furf/jquery-ui-touch-punch.git"
  }
}

2. Adding scripts entries in package.json file to let napa run when we invoke npm install,npm update, and npm run githuh commands: 

{
  ...	
  "scripts": {
    "install": "napa",
    "update": "napa",
    "github": "napa"
  },
  ...
}

3. Adding custom napa's napa-config configuration options entry, mainly to disable repositories caching:

{
  ...	
  "napa-config": {
    "cache": false,
    "log-level": "debug"
  },
  ...
}

Now, whenever we invoke npm install command, all our front-end dependencies will be downloaded and installed in local ./node_modules folder by node.js and napa tools. The following is our complete package.json file updated with all dependencies, devDependencies, and napa entries:

{
  "name": "JavaTMP-Static-Ajax",
  "version": "0.0.1",
  "description": "Java Bootstrap Dashboard And Admin Template",
  "keywords": [
    "jquery",
    "bootstrap",
    "html",
    "template",
    "browser"
  ],
  "author": "javatmp",
  "licenses": "Commercial License",
  "contributors": [],
  "dependencies": {
    "autosize": "^4.0.0",
    "bootstrap": "^4.0.0",
    "bootstrap-colorselector": "^0.1.0",
    "bootstrap-datepicker": "^1.7.1",
    "bootstrap-daterangepicker": "^2.1.25",
    "bootstrap-maxlength": "^1.6.0",
    "bootstrap-slider": "^10.0.0",
    "bootstrap-switch": "^3.3.4",
    "bootstrap-tagsinput": "^0.7.1",
    "chart.js": "^2.7.1",
    "cropper": "^3.1.3",
    "datatables.net": "^1.10.16",
    "datatables.net-bs4": "^1.10.16",
    "datatables.net-fixedheader": "^3.1.3",
    "datatables.net-fixedheader-bs4": "^3.1.3",
    "datatables.net-responsive": "^2.2.0",
    "datatables.net-responsive-bs4": "^2.2.0",
    "datatables.net-select": "^1.2.3",
    "datatables.net-select-bs4": "^1.2.3",
    "dropzone": "^5.2.0",
    "echarts": "^3.8.5",
    "ekko-lightbox": "^5.3.0",
    "font-awesome": "^4.7.0",
    "font-awesome-animation": "^0.1.0",
    "fullcalendar": "^3.7.0",
    "inputmask": "^3.3.11",
    "ion-rangeslider": "^2.2.0",
    "jquery": "^3.2.1",
    "jquery-contextmenu": "^2.6.3",
    "jquery-form": "^4.2.2",
    "jquery-knob": "^1.2.11",
    "jquery-ui-dist": "^1.12.1",
    "jquery-validation": "^1.17.0",
    "jquery.counterup": "^2.1.0",
    "magnific-popup": "^1.1.0",
    "metismenu": "^2.7.1",
    "moment": "^2.19.3",
    "multiselect": "^0.9.12",
    "popper.js": "^1.13.0",
    "select2": "^4.0.6-rc.1",
    "slick-carousel": "^1.8.1",
    "summernote": "^0.8.8",
    "timeago": "^1.6.1",
    "toastr": "^2.1.2",
    "typeahead.js": "^0.11.1",
    "waypoints": "^4.0.1"
  },
  "devDependencies": {
    "del": "^3.0.0",
    "gulp": "^3.9.1",
    "gulp-autoprefixer": "^4.0.0",
    "gulp-clean-css": "^3.7.0",
    "gulp-concat": "^2.6.1",
    "gulp-connect": "^5.0.0",
    "gulp-eslint": "^4.0.0",
"gulp-if": "^2.0.2", "gulp-rename": "^1.2.2", "gulp-sass": "^3.1.0", "gulp-uglify": "^3.0.0", "napa": "^3.0.0", "pump": "^2.0.0" }, "scripts": { "install": "napa", "update": "napa", "github": "napa" }, "napa-config": { "cache": false, "log-level": "debug" }, "napa": { "blockui": "git+https://github.com/malsup/blockui.git", "scrollup": "git+https://github.com/markgoodyear/scrollup.git", "jquery.fancytree": "git+https://github.com/mar10/fancytree.git", "jquery-idletimer": "git+https://github.com/thorst/jquery-idletimer.git", "select2-bootstrap-theme": "git+https://github.com/angel-vladov/select2-bootstrap-theme.git", "quicksearch": "git+https://github.com/riklomas/quicksearch.git", "jquery-ui-touch-punch": "git+https://github.com/furf/jquery-ui-touch-punch.git" } }

 

Moving Required libraries Files to our web application folders

We can now reference the dependencies straight from ./node_modules folder just like we did on Bower components from ./web/components folder. but our node_modules folder is outside context root folder ./web and normally it is not a good idea to make node_modules folder inside your context root folder, indeed not all files and directories are needed in node_modules folder. The following is a high level folder structures of our JavaTMP-Static-Ajax web application:

JAVATMP-STATIC-AJAX
|   .gitignore
|   gulpfile.js
|   package.json
|---node_modules (Source Front-end Folders)
+---nbproject (Netbeans IDE config folders and files)
+---web (Web Application Context Root Folder)
    |   index.html (Main HTML Page references Front-end Libraries from components folder)
    +---assets (Specific Template JS,CSS,fonts, and images folders and files)
    +---components (Destination Front-end Folders)
    \---pages (HTML Pages reference Front-end Libraries from components folder)

So we need a simple process that chooses and moves required and needed plugins and frameworks from ./node_modules to ./web/components folders and processes them if they need any minification or cleaning before move them.

We used Gulp to automate our building process. So, we created a simple gulp task that implements our requirements above by first creating an object that hold need libraries. the following object hold our required libraries:

var config = {
    "sourceNodeLib": "./node_modules",
    "destComponentsLib": "./web/components",
    "plugins": {
        "jquery": [
            {"from": "${sourceNodeLib}/jquery/dist/jquery.min.js", "to": "${destComponentsLib}/jquery/dist"}
        ],
        "popper.js": [
            {"from": "${sourceNodeLib}/popper.js/dist/umd/popper.min.js", "to": "${destComponentsLib}/popper.js/dist/umd"}
        ],
        "bootstrap": [
            {"from": "${sourceNodeLib}/bootstrap/dist/css/bootstrap.min.css", "to": "${destComponentsLib}/bootstrap/dist/css"},
            {"from": "${sourceNodeLib}/bootstrap/dist/js/bootstrap.min.js", "to": "${destComponentsLib}/bootstrap/dist/js"}
        ],
        "font-awesome": [
            {"from": "${sourceNodeLib}/font-awesome/css/font-awesome.min.css", "to": "${destComponentsLib}/font-awesome/css"},
            {"from": "${sourceNodeLib}/font-awesome/fonts/*", "to": "${destComponentsLib}/font-awesome/fonts"}
        ],
        "blockui": [
            {"from": "${sourceNodeLib}/blockui/jquery.blockUI.js", "to": "${destComponentsLib}/blockui", "processCSS": false, "processJS": true}
        ],
        "metismenu": [
            {"from": "${sourceNodeLib}/metismenu/dist/metisMenu.min.js", "to": "${destComponentsLib}/metismenu/dist"},
            {"from": "${sourceNodeLib}/metismenu/dist/metisMenu.min.css", "to": "${destComponentsLib}/metismenu/dist"}
        ],
        "scrollup": [
            {"from": "${sourceNodeLib}/scrollup/dist/jquery.scrollUp.min.js", "to": "${destComponentsLib}/scrollup/dist"}
        ],
        "font-awesome-animation": [
            {"from": "${sourceNodeLib}/font-awesome-animation/dist/font-awesome-animation.min.css", "to": "${destComponentsLib}/font-awesome-animation/dist"}
        ],
        "jquery-ui": [
            {"from": "${sourceNodeLib}/jquery-ui-dist/jquery-ui.min.css", "to": "${destComponentsLib}/jquery-ui"},
            {"from": "${sourceNodeLib}/jquery-ui-dist/jquery-ui.min.js", "to": "${destComponentsLib}/jquery-ui"}
 
        ],
        "jquery.fancytree": [
            {"from": "${sourceNodeLib}/jquery.fancytree/dist/skin-bootstrap/**/*", "to": "${destComponentsLib}/jquery.fancytree/dist/skin-bootstrap"},
            {"from": "${sourceNodeLib}/jquery.fancytree/dist/jquery.fancytree-all.min.js", "to": "${destComponentsLib}/jquery.fancytree/dist", processJS: true},
            {"from": "${sourceNodeLib}/jquery.fancytree/3rd-party/extensions/contextmenu/js/jquery.fancytree.contextMenu.js", "to": "${destComponentsLib}/jquery.fancytree/3rd-party/extensions/contextmenu/js", processJS: true}
        ],
        "jquery-contextmenu": [
            {"from": "${sourceNodeLib}/jquery-contextmenu/dist/font/**/*", "to": "${destComponentsLib}/jquery-contextmenu/dist/font"},
            {"from": "${sourceNodeLib}/jquery-contextmenu/dist/jquery.contextMenu.min.css", "to": "${destComponentsLib}/jquery-contextmenu/dist"},
            {"from": "${sourceNodeLib}/jquery-contextmenu/dist/jquery.contextMenu.min.js", "to": "${destComponentsLib}/jquery-contextmenu/dist"}
        ],
        "toastr": [
            {"from": "${sourceNodeLib}/toastr/build/toastr.min.css", "to": "${destComponentsLib}/toastr/build"},
            {"from": "${sourceNodeLib}/toastr/build/toastr.min.js", "to": "${destComponentsLib}/toastr/build"}
        ],
        "jquery-idletimer": [
            {"from": "${sourceNodeLib}/jquery-idletimer/dist/idle-timer.min.js", "to": "${destComponentsLib}/jquery-idletimer/dist"}
        ],
        "bootstrap-datepickerr": [
            {"from": "${sourceNodeLib}/bootstrap-datepicker/dist/css/bootstrap-datepicker3.min.css", "to": "${destComponentsLib}/bootstrap-datepicker/dist/css"},
            {"from": "${sourceNodeLib}/bootstrap-datepicker/dist/js/bootstrap-datepicker.min.js", "to": "${destComponentsLib}/bootstrap-datepicker/dist/js"},
            {"from": "${sourceNodeLib}/bootstrap-datepicker/dist/locales/**/*", "to": "${destComponentsLib}/bootstrap-datepicker/dist/locales"}
        ],
        "moment": [
            {"from": "${sourceNodeLib}/moment/min/moment-with-locales.min.js", "to": "${destComponentsLib}/moment/min"}
        ],
        "bootstrap-daterangepicker": [
            {"from": "${sourceNodeLib}/bootstrap-daterangepicker/daterangepicker.css", "to": "${destComponentsLib}/bootstrap-daterangepicker", processCSS: true},
            {"from": "${sourceNodeLib}/bootstrap-daterangepicker/daterangepicker.js", "to": "${destComponentsLib}/bootstrap-daterangepicker", processJS: true}
        ],
        "bootstrap-colorselector": [
            {"from": "${sourceNodeLib}/bootstrap-colorselector/dist/bootstrap-colorselector.min.css", "to": "${destComponentsLib}/bootstrap-colorselector/dist"},
            {"from": "${sourceNodeLib}/bootstrap-colorselector/dist/bootstrap-colorselector.min.js", "to": "${destComponentsLib}/bootstrap-colorselector/dist"}
        ],
        "select2": [
            {"from": "${sourceNodeLib}/select2/dist/css/select2.min.css", "to": "${destComponentsLib}/select2/dist/css"},
            {"from": "${sourceNodeLib}/select2/dist/js/select2.full.min.js", "to": "${destComponentsLib}/select2/dist/js"}
        ],
        "select2-bootstrap-theme": [
            {"from": "${sourceNodeLib}/select2-bootstrap-theme/dist/select2-bootstrap.min.css", "to": "${destComponentsLib}/select2-bootstrap-theme/dist"}
        ],
        "typeahead.js": [
            {"from": "${sourceNodeLib}/typeahead.js/dist/typeahead.bundle.min.js", "to": "${destComponentsLib}/typeahead.js/dist"}
        ],
        "bootstrap-tagsinput": [
            {"from": "${sourceNodeLib}/bootstrap-tagsinput/dist/bootstrap-tagsinput.css", "to": "${destComponentsLib}/bootstrap-tagsinput/dist", processCSS: true},
            {"from": "${sourceNodeLib}/bootstrap-tagsinput/dist/bootstrap-tagsinput.min.js", "to": "${destComponentsLib}/bootstrap-tagsinput/dist"}
        ],
        "bootstrap-switch": [
            {"from": "${sourceNodeLib}/bootstrap-switch/dist/css/bootstrap3/bootstrap-switch.min.css", "to": "${destComponentsLib}/bootstrap-switch/dist/css/bootstrap3"},
            {"from": "${sourceNodeLib}/bootstrap-switch/dist/js/bootstrap-switch.min.js", "to": "${destComponentsLib}/bootstrap-switch/dist/js"}
 
        ],
        "bootstrap-maxlength": [
            {"from": "${sourceNodeLib}/bootstrap-maxlength/bootstrap-maxlength.min.js", "to": "${destComponentsLib}/bootstrap-maxlength"}
        ],
        "autosize": [
            {"from": "${sourceNodeLib}/autosize/dist/autosize.min.js", "to": "${destComponentsLib}/autosize/dist"}
        ],
        "summernote": [
            {"from": "${sourceNodeLib}/summernote/dist/font/**/*", "to": "${destComponentsLib}/summernote/dist/font"},
//            {"from": "${sourceNodeLib}/summernote/dist/lang/**/*", "to": "${destComponentsLib}/summernote/dist/lang"},
            {"from": "${sourceNodeLib}/summernote/dist/summernote-bs4.min.js", "to": "${destComponentsLib}/summernote/dist"},
            {"from": "${sourceNodeLib}/summernote/dist/summernote-bs4.css", "to": "${destComponentsLib}/summernote/dist"}
        ],
        "ion-rangeslider": [
            {"from": "${sourceNodeLib}/ion-rangeslider/css/ion.rangeSlider.css", "to": "${destComponentsLib}/ion-rangeslider/css", processCSS: true},
            {"from": "${sourceNodeLib}/ion-rangeslider/css/ion.rangeSlider.skinHTML5.css", "to": "${destComponentsLib}/ion-rangeslider/css", processCSS: true},
            {"from": "${sourceNodeLib}/ion-rangeslider/js/ion.rangeSlider.min.js", "to": "${destComponentsLib}/ion-rangeslider/js"}
        ],
        "bootstrap-slider": [
            {"from": "${sourceNodeLib}/bootstrap-slider/dist/css/bootstrap-slider.min.css", "to": "${destComponentsLib}/bootstrap-slider/dist/css"},
            {"from": "${sourceNodeLib}/bootstrap-slider/dist/bootstrap-slider.min.js", "to": "${destComponentsLib}/bootstrap-slider/dist"}
        ],
        "jquery-knob": [
            {"from": "${sourceNodeLib}/jquery-knob/dist/jquery.knob.min.js", "to": "${destComponentsLib}/jquery-knob/dist"}
        ],
        "fullcalendar": [
            {"from": "${sourceNodeLib}/fullcalendar/dist/fullcalendar.min.css", "to": "${destComponentsLib}/fullcalendar/dist"},
            {"from": "${sourceNodeLib}/fullcalendar/dist/fullcalendar.print.min.css", "to": "${destComponentsLib}/fullcalendar/dist"},
            {"from": "${sourceNodeLib}/fullcalendar/dist/fullcalendar.min.js", "to": "${destComponentsLib}/fullcalendar/dist"}
        ],
        "cropper": [
            {"from": "${sourceNodeLib}/cropper/dist/cropper.min.css", "to": "${destComponentsLib}/cropper/dist"},
            {"from": "${sourceNodeLib}/cropper/dist/cropper.min.js", "to": "${destComponentsLib}/cropper/dist"}
        ],
        "waypoints": [
            {"from": "${sourceNodeLib}/waypoints/lib/jquery.waypoints.min.js", "to": "${destComponentsLib}/waypoints/lib"}
        ],
        "jquery.counterup": [
            {"from": "${sourceNodeLib}/jquery.counterup/jquery.counterup.min.js", "to": "${destComponentsLib}/jquery.counterup"}
        ],
        "multiselect": [
            {"from": "${sourceNodeLib}/multiselect/css/multi-select.css", "to": "${destComponentsLib}/multiselect/css", processCSS: true},
            {"from": "${sourceNodeLib}/multiselect/img/switch.png", "to": "${destComponentsLib}/multiselect/img"},
            {"from": "${sourceNodeLib}/multiselect/js/jquery.multi-select.js", "to": "${destComponentsLib}/multiselect/js", processJS: true}
        ],
        "timeago": [
            {"from": "${sourceNodeLib}/timeago/jquery.timeago.js", "to": "${destComponentsLib}/timeago", processJS: true}
        ],
        "quicksearch": [
            {"from": "${sourceNodeLib}/quicksearch/jquery.quicksearch.js", "to": "${destComponentsLib}/quicksearch", processJS: true}
        ],
        "jquery-validation": [
            {"from": "${sourceNodeLib}/jquery-validation/dist/jquery.validate.min.js", "to": "${destComponentsLib}/jquery-validation/dist"},
            {"from": "${sourceNodeLib}/jquery-validation/dist/additional-methods.js", "to": "${destComponentsLib}/jquery-validation/dist", processJS: true}
        ],
        "inputmask": [
            {"from": "${sourceNodeLib}/inputmask/dist/**/*", "to": "${destComponentsLib}/inputmask/dist"}
        ],
        "jquery-form": [
            {"from": "${sourceNodeLib}/jquery-form/dist/jquery.form.min.js", "to": "${destComponentsLib}/jquery-form/dist"}
        ],
        "dropzone": [
            {"from": "${sourceNodeLib}/dropzone/dist/min/dropzone.min.css", "to": "${destComponentsLib}/dropzone/dist/min"},
            {"from": "${sourceNodeLib}/dropzone/dist/min/dropzone.min.js", "to": "${destComponentsLib}/dropzone/dist/min"}
        ],
        "datatables.net": [
            {"from": "${sourceNodeLib}/datatables.net/js/jquery.dataTables.js", "to": "${destComponentsLib}/datatables.net/js", processJS: true}
        ],
        "datatables.net-bs4": [
            {"from": "${sourceNodeLib}/datatables.net-bs4/css/dataTables.bootstrap4.css", "to": "${destComponentsLib}/datatables.net-bs4/css", processCSS: true},
            {"from": "${sourceNodeLib}/datatables.net-bs4/js/dataTables.bootstrap4.js", "to": "${destComponentsLib}/datatables.net-bs4/js", processJS: true}
        ],
        "datatables.net-fixedheader": [
            {"from": "${sourceNodeLib}/datatables.net-fixedheader/js/dataTables.fixedHeader.min.js", "to": "${destComponentsLib}/datatables.net-fixedheader/js"}
        ],
        "datatables.net-fixedheader-bs4": [
            {"from": "${sourceNodeLib}/datatables.net-fixedheader-bs4/css/fixedHeader.bootstrap4.min.css", "to": "${destComponentsLib}/datatables.net-fixedheader-bs4/css"}
        ],
        "datatables.net-responsive": [
            {"from": "${sourceNodeLib}/datatables.net-responsive/js/dataTables.responsive.min.js", "to": "${destComponentsLib}/datatables.net-responsive/js"}
        ],
        "datatables.net-responsive-bs4": [
            {"from": "${sourceNodeLib}/datatables.net-responsive-bs4/css/responsive.bootstrap4.min.css", "to": "${destComponentsLib}/datatables.net-responsive-bs4/css"},
            {"from": "${sourceNodeLib}/datatables.net-responsive-bs4/js/responsive.bootstrap4.min.js", "to": "${destComponentsLib}/datatables.net-responsive-bs4/js"}
        ],
        "datatables.net-select": [
            {"from": "${sourceNodeLib}/datatables.net-select/js/dataTables.select.min.js", "to": "${destComponentsLib}/datatables.net-select/js"}
        ],
        "datatables.net-select-bs4": [
            {"from": "${sourceNodeLib}/datatables.net-select-bs4/css/select.bootstrap4.min.css", "to": "${destComponentsLib}/datatables.net-select-bs4/css"}
        ],
        "chart.js": [
            {"from": "${sourceNodeLib}/chart.js/dist/Chart.bundle.min.js", "to": "${destComponentsLib}/chart.js/dist"}
        ],
        "echarts": [
            {"from": "${sourceNodeLib}/echarts/dist/echarts.min.js", "to": "${destComponentsLib}/echarts/dist"}
        ],
        "ekko-lightbox": [
            {"from": "${sourceNodeLib}/ekko-lightbox/dist/ekko-lightbox.css", "to": "${destComponentsLib}/ekko-lightbox/dist"},
            {"from": "${sourceNodeLib}/ekko-lightbox/dist/ekko-lightbox.min.js", "to": "${destComponentsLib}/ekko-lightbox/dist"}
        ],
        "magnific-popup": [
            {"from": "${sourceNodeLib}/magnific-popup/dist/magnific-popup.css", "to": "${destComponentsLib}/magnific-popup/dist", processCSS: true},
            {"from": "${sourceNodeLib}/magnific-popup/dist/jquery.magnific-popup.min.js", "to": "${destComponentsLib}/magnific-popup/dist"}
        ],
        "slick-carousel": [
            {"from": "${sourceNodeLib}/slick-carousel/slick/ajax-loader.gif", "to": "${destComponentsLib}/slick-carousel/slick"},
            {"from": "${sourceNodeLib}/slick-carousel/slick/fonts/**/*", "to": "${destComponentsLib}/slick-carousel/slick/fonts"},
            {"from": "${sourceNodeLib}/slick-carousel/slick/slick.css", "to": "${destComponentsLib}/slick-carousel/slick", processCSS: true},
            {"from": "${sourceNodeLib}/slick-carousel/slick/slick-theme.css", "to": "${destComponentsLib}/slick-carousel/slick", processCSS: true},
            {"from": "${sourceNodeLib}/slick-carousel/slick/slick.min.js", "to": "${destComponentsLib}/slick-carousel/slick"}
        ],
        "jquery-ui-touch-punch": [
            {"from": "${sourceNodeLib}/jquery-ui-touch-punch/jquery.ui.touch-punch.min.js", "to": "${destComponentsLib}/jquery-ui-touch-punch"}
        ]
    }
};

Actually it is not a standard nor optimal, but give us need functionality and flexibility to select only wanted file and decide what we could do with them before moving or merging them . The following is the gulp's task called copy-components that use the above configuration to copy folders and files from ./node_modules to ./web/components :

function getClass(object) {
    return Object.prototype.toString.call(object).slice(8, -1);
}

applyParameters = function (jsonObj, p) {
    if (getClass(jsonObj) !== 'String') {
        for (var key in jsonObj) {
            if (jsonObj.hasOwnProperty(key)) {
                applyParameters(jsonObj[key], (p === "" ? "" : p + ".") + key);
            }
        }
    } else {
        solveParameters(jsonObj);
    }
};
solveParameters = function (path) {
    var regex = new RegExp(/\$\{(.*?)\}/);
    var dot = function (obj, i) {
        return obj[i];
    };
    var matched;
    if (getClass(path) === 'String') {
        while (matched = path.match(regex)) {
            var realpath = matched[1].split('.').reduce(dot, config);
            path = path.replace(matched[0], realpath);
        }
    }
    return path;
};
gulp.task('delete-components', function (cb) {
    return del([config.destComponentsLib], cb);
});
gulp.task('copy-components', ["delete-components"], function () {
    for (var key in config.plugins) {
        if (config.plugins.hasOwnProperty(key)) {
            var componentConfig = config.plugins[key];
            for (var i = 0; i < componentConfig.length; i++) {
                var componentResource = componentConfig[i];
                var to = solveParameters(componentResource.to);
                var from = solveParameters(componentResource.from);
                console.log("copy resource from [" + from + "] to [" + to + "] processCSS [" + componentResource.processCSS + "], processJS [" + componentResource.processJS + "]");
                gulp.src(from)
                        .pipe(gulpif(componentResource.processJS === true, uglify()))
                        .pipe(gulpif(componentResource.processCSS === true, cleanCSS()))
                        .pipe(gulp.dest(to));
            }
        }
    }
});

We used gulp-if plugins to check if the resource need any preprocessing. So if it is a Javascript file we apply gulp-uglify plugin on the resource and if it is CSS file we apply gulp-clean-css plugin on the resource before move them.

We can simply update or add another gulp's task that improve above technique to accomplish your requirements.

At the end, We removed Bower and migrate our front-end dependencies from Bower to NPM and Gulp only. 

You could now create a gulp task that combine and generate big JS/CSS files of all front-end resources as we did in the JavaTMP-Static-Ajax-Starter project gulp's task generate-dist that take the output of copy-components above and generate a concatenated Javascript and CSS files.