Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Odd behavior from grunt-contrib-concat when using globbing pattern

I'm using grunt, and I would like to concat all js files in a certain directory, in a specific order (It's an angular js app, so I want to do my module definitions first, followed by everything else). My grunt concat target looks like:

concat: {

    mobile: {
        expand: true,
        cwd: "static/javascript/mobile/app/",
        src: ["main-module.js", "**/*-module.js", "**/*.js", "!static/javascript/mobile/dist/*"],
        dest: "static/javascript/mobile/app/dist/ngmobile.concat.js"
    }
}

The above configuration seems like it should concat main-module.js, followed by all other module.js files, followed by everything else, omitting everything in the dist folder. However, the result, when running grunt with --verbose is something like:

Running "concat:mobile" (concat) task
Verifying property concat.mobile exists in config...OK
Files: static/javascript/mobile/app/main-module.js -> static/javascript/mobile/app/dist/ngmobile.concat.js/main-module.js
Files: static/javascript/mobile/app/clients/clients-module.js -> static/javascript/mobile/app/dist/ngmobile.concat.js/clients/clients-module.js
Files: static/javascript/mobile/app/reports/reports-module.js -> static/javascript/mobile/app/dist/ngmobile.concat.js/reports/reports-module.js
Files: static/javascript/mobile/app/schedules/schedules-module.js -> static/javascript/mobile/app/dist/ngmobile.concat.js/schedules/schedules-module.js
Files: static/javascript/mobile/app/services/services-module.js -> static/javascript/mobile/app/dist/ngmobile.concat.js/services/services-module.js
Files: static/javascript/mobile/app/clients/addclient-ctrl.js -> static/javascript/mobile/app/dist/ngmobile.concat.js/clients/addclient-ctrl.js
Files: static/javascript/mobile/app/clients/clientprofile-ctrl.js -> static/javascript/mobile/app/dist/ngmobile.concat.js/clients/clientprofile-ctrl.js
Files: static/javascript/mobile/app/clients/clients-ctrl.js -> static/javascript/mobile/app/dist/ngmobile.concat.js/clients/clients-ctrl.js
Files: static/javascript/mobile/app/clients/clients-service.js -> static/javascript/mobile/app/dist/ngmobile.concat.js/clients/clients-service.js
Files: static/javascript/mobile/app/common/directives.js -> static/javascript/mobile/app/dist/ngmobile.concat.js/common/directives.js
Files: static/javascript/mobile/app/common/filters.js -> static/javascript/mobile/app/dist/ngmobile.concat.js/common/filters.js
Files: static/javascript/mobile/app/common/header-ctrl.js -> static/javascript/mobile/app/dist/ngmobile.concat.js/common/header-ctrl.js
Files: static/javascript/mobile/app/common/navbarcollapse-directive.js -> static/javascript/mobile/app/dist/ngmobile.concat.js/common/navbarcollapse-directive.js
Files: static/javascript/mobile/app/dist/ngmobile.concat.js -> static/javascript/mobile/app/dist/ngmobile.concat.js/dist/ngmobile.concat.js
Files: static/javascript/mobile/app/main-ctrl.js -> static/javascript/mobile/app/dist/ngmobile.concat.js/main-ctrl.js
Files: static/javascript/mobile/app/main-service.js -> static/javascript/mobile/app/dist/ngmobile.concat.js/main-service.js
Files: static/javascript/mobile/app/reports/reports-ctrl.js -> static/javascript/mobile/app/dist/ngmobile.concat.js/reports/reports-ctrl.js
Files: static/javascript/mobile/app/schedules/schedules-ctrl.js -> static/javascript/mobile/app/dist/ngmobile.concat.js/schedules/schedules-ctrl.js
Files: static/javascript/mobile/app/schedules/schedules-service.js -> static/javascript/mobile/app/dist/ngmobile.concat.js/schedules/schedules-service.js
Files: static/javascript/mobile/app/services/addservice-ctrl.js -> static/javascript/mobile/app/dist/ngmobile.concat.js/services/addservice-ctrl.js
Files: static/javascript/mobile/app/services/serviceprofile-ctrl.js -> static/javascript/mobile/app/dist/ngmobile.concat.js/services/serviceprofile-ctrl.js
Files: static/javascript/mobile/app/services/services-ctrl.js -> static/javascript/mobile/app/dist/ngmobile.concat.js/services/services-ctrl.js
Files: static/javascript/mobile/app/services/services-service.js -> static/javascript/mobile/app/dist/ngmobile.concat.js/services/services-service.js
Options: separator="\n", banner="", footer="", stripBanners=false, process=false
Reading static/javascript/mobile/app/main-module.js...OK
Writing static/javascript/mobile/app/dist/ngmobile.concat.js/main-module.js...ERROR
Warning: Unable to write "static/javascript/mobile/app/dist/ngmobile.concat.js/main-module.js" file (Error code: ENOTDIR). Use --force to continue.

Aborted due to warnings.

This tells me that it's finding the files I want it to find, and then attempting to write (copy?) them to the file path specified in dest. How did I get this so horribly wrong? :) If anyone cares enough to save the last few hairs on my head I would really appreciate some input as to what I'm doing wrong here. I want to concat all the files in src into the dest file.

Edit

If I remove the expand property, the output looks like:

Running "concat:mobile" (concat) task
Verifying property concat.mobile exists in config...OK
Files: main-module.js, clients/clients-module.js, reports/reports-module.js, schedules/schedules-module.js, services/services-module.js, clients/addclient-ctrl.js, clients/clientprofile-ctrl.js, clients/clients-ctrl.js, clients/clients-service.js, common/directives.js, common/filters.js, common/header-ctrl.js, common/navbarcollapse-directive.js, dist/ngmobile.concat.js, main-ctrl.js, main-service.js, reports/reports-ctrl.js, schedules/schedules-ctrl.js, schedules/schedules-service.js, services/addservice-ctrl.js, services/serviceprofile-ctrl.js, services/services-ctrl.js, services/services-service.js -> static/javascript/mobile/app/dist/ngmobile.concat.js
Options: separator="\n", banner="", footer="", stripBanners=false, process=false
>> Source file "main-module.js" not found.
>> Source file "clients/clients-module.js" not found.
>> Source file "reports/reports-module.js" not found.
>> Source file "schedules/schedules-module.js" not found.
>> Source file "services/services-module.js" not found.
>> Source file "clients/addclient-ctrl.js" not found.
>> Source file "clients/clientprofile-ctrl.js" not found.
>> Source file "clients/clients-ctrl.js" not found.
>> Source file "clients/clients-service.js" not found.
>> Source file "common/directives.js" not found.
>> Source file "common/filters.js" not found.
>> Source file "common/header-ctrl.js" not found.
>> Source file "common/navbarcollapse-directive.js" not found.
>> Source file "dist/ngmobile.concat.js" not found.
>> Source file "main-ctrl.js" not found.
>> Source file "main-service.js" not found.
>> Source file "reports/reports-ctrl.js" not found.
>> Source file "schedules/schedules-ctrl.js" not found.
>> Source file "schedules/schedules-service.js" not found.
>> Source file "services/addservice-ctrl.js" not found.
>> Source file "services/serviceprofile-ctrl.js" not found.
>> Source file "services/services-ctrl.js" not found.
>> Source file "services/services-service.js" not found.
Writing static/javascript/mobile/app/dist/ngmobile.concat.js...OK
File "static/javascript/mobile/app/dist/ngmobile.concat.js" created.

Notice, the third line in the snipped above "Files: ..." lists all the files I wanted it to find, but then says the source file isn't found.

EDIT 2

Matt's solution below takes care of the issue, below is my updated code:

concat: {
    mobile: {
        dest: "static/javascript/mobile/app/dist/ngmobile-concat.js",
        src: (function () {
            var cwd = "static/javascript/mobile/app/";
            var files = ["*-module.js", "**/*-module.js", "**/*.js"];

            files = files.map(function (file) {
                return cwd + file;
            });

            files.push("! static/javascript/mobile/app/dist");

            return files;
        }())
    }
}
like image 564
Greg Avatar asked Oct 01 '22 17:10

Greg


1 Answers

Having run into this myself with an angular app, I considered three options:

1) Follow the suggestion on the project github thread

You are not alone. There's a long thread at grunt-contrib-concat github project about this issue. It has to do with the way cwd works with dest.

A suggested workaround posted there moves the cwd property into a function that then produces the anticipated results rather than then a ENOTDIR error.

src: (function() {
  var cwd = 'src/js/';
  var arr = [];
  // determine file order here and concat to arr
  return arr.map(function(file) { return cwd + file; });
}())

I think the downside is the addition of a function into the gruntfile, which is normally very simple. If you aren't the only one maintaining the app, this might be confusing.

2) Ditch cwd and use a static approach with <%= %> variable syntax

If your src and dest directories are consistent through your entire gruntfile, you could define the src and dest directories doing something like this:

myapp: {
  // configurable paths
  app: "static/javascript/mobile/app/",
  dist: "static/javascript/mobile/app/dist/"
}

...then you can remove the cwd property and configure things to look more 'static':

concat: {

    mobile: {
        expand: true,
        src: ["<%= myapp.app %>/main-module.js", "<%= myapp.app %>/**/*-module.js", "<%= myapp.app %>/**/*.js", "!<%= myapp.dist %>/*"],
        dest: "<%= myapp.dist %>/ngmobile.concat.js"
    }
}

For most of my projects, this is sufficient. It's simple, DRY, and easy to understand when I come back later and need to make changes.

Granted: The configuration properties get a little harder to read at first with all the <%=%> clutter, but with a little formatting in my editor that can be improved. Also, it's very easy for another programmer to read this and know what you intended.

3) Use a plugin like usemin that does this for you

If you are also doing minification, you might consider a plugin like grunt-usemin.

The downside of this option is obvious: usemin adds complexity because you now have another plugin to manage and configure and you will need to format your HTML to use the usemin functionality. Usemin configuration can be confusing at first and if you are working with a team, things can very easily be broken by a programmer or designer who modify the usemin configuration or the HTML comments usemin needs.

The upside is that usemin will generate grunt's copy, concat and minification configurations for you directly from the HTML. Thus, if you add/remove scripts or change the load order in the HTML, you won't have to do anything in the grunt configuration because usemin will pick it up.

like image 130
Matthew Bakaitis Avatar answered Oct 05 '22 11:10

Matthew Bakaitis