Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Gulp - How do I control processing order with gulp-concat

Tags:

angularjs

gulp

I'm trying to generate combined JavaScript and CSS resources into a single file using gulp-concat using something like this:

var concatjs = gulp
    .src(['app/js/app.js','app/js/*Controller.js', 'app/js/*Service.js'])
    .pipe(concat('app.js'))
    .pipe(gulp.dest('build'));

I get a concatted file with this, but the order of the javascript files embedded in the combined output file is random - in this case the controllers are showing up before the initial app.js file, which causes problems when trying to load the Angular app that expects app.js before any of the related resources are loaded. Likewise for CSS resources that get combined end up in random order, and again the order is somewhat important - ie. bootstrap needs to load before the theme and any custom style sheets.

How can I set up the concatenation process so that the order remains intact?

Update So it turns out the ordering above DOES actually work by explicitly specifying the file order in the array of file specs. So in this case the crucial thing is to list app/js/app.js first, then let the rest of the scripts where order doesn't matter in in any order.

The reason I failed to see this behavior (Duh!) is that Gulp Watch was running and the gulpfile.js update wasn't actually reflected in the output. Restarting gulp did update the script. Neophyte error...

Other Thoughts: Still wondering though - is this the right place to specify build order? It seems you're now stuffing application logic (load order) into the build script, which doesn't feel right. Are there other approaches to address this?

like image 441
Rick Strahl Avatar asked Sep 16 '14 19:09

Rick Strahl


2 Answers

For an angular application like the one in your example (and it's dependency management), I normally use this kind of syntax: gulp.src(['app\js\app.js', 'app\js\**\*.js']).

You can also use just gulp.src('app\js\**\*.js') if your app.js file is the first one in alphabetic order.

I see your point about moving the load file order into the build script: I had the same feeling till I started using gulp-inject for injecting the unminified files references in my index.html at development time and injecting the bundled, minified and versioned ones in the production index file. Using that glob ordering solution across all my development cycle made so sense to me that i don't think to it anymore.

Finally, a possible solution for this 'ordering smell' can be using browserify but to me it is just complicating the architecture for an angular application: in the end, as you said, you just need that one specific file is called before all the other ones.

like image 195
Ghidello Avatar answered Oct 24 '22 16:10

Ghidello


For my js i use a particular structure/naming convention which helps. I split it up into directories by feature, where each 'feature' is then treated as a separate encapsulated module.

So for my projects i have,

app/js/

    - app.js
    - app.routes.js
    - app.config.js

    /core/

        - core.js
        - core.controllers.js
        - core.services.js

        /test/

           - .spec.js test files for module here

    /feature1/

        - feature1.js
        - feature1.controllers.js

    /feature2/

        - feature2.js
        - feature2.controllers.js

    ...

So each directory has a file of the same name that simply has the initial module definition in it, which is all that app.js has in it for the whole app. So for feature1.js

angular.module('feature1', [])

and then subsequent files in the module retrieve the module and add things (controllers/services/factories etc) to it.

angular.module('feature1')
       .controller(....)


Anyway, i'll get to the point...

As i have a predefined structure and know that a specific file has to go first for each module, i'm able to use the function below to sort everything into order before it gets processed by gulp.

This function depends on npm install file and npm install path

function getModules(src, app, ignore) {

    var modules = [];

    file.walkSync(src, function(dirPath, dirs, files) {

        if(files.length < 1)
            return;

        var dir = path.basename(dirPath)
            module;

        if(ignore.indexOf(dir) === -1) {
            module = dirPath === src ? app : dir;

            files = files.sort(function(a, b) {
                return path.basename(a, '.js') === module ? -1 : 1;
            })
            .filter(function(value) {
                return value.indexOf('.') !== 0;
            })
            .map(function(value) {
                return path.join(dirPath, value);
            })

            modules = modules.concat(files);
        }
    })

    return modules;
}

It walks the directory structure passed to it, takes the files from each directory (or module) and sorts them into the correct order, ensuring that the module definition file is always first. It also ignores any directories that appear in the 'ignore' array and removes any hidden files that begin with '.'

Usage would be,

getModules(src, appName, ignoreDirs);
  • src is the dir you want to recurse from
  • appName is the name of your app.js file - so 'app'
  • ignoreDirs is an array of directory names you'd like to ignore

so

getModules('app/js', 'app', ['test']);

And it returns an array of all the files in your app in the correct order, which you could then use like:

gulp.task('scripts', function() {
    var modules = getModules('app/js', 'app', ['test']);

    return gulp.src(modules)
               .pipe(concat('app.js'))
               .pipe(gulp.dest('build'));
});
like image 26
james Avatar answered Oct 24 '22 15:10

james