196

Say, for example, you are building a project on Backbone or whatever and you need to load scripts in a certain order, e.g. underscore.js needs to be loaded before backbone.js.

How do I get it to concat the scripts so that they’re in order?

// JS concat, strip debugging and minify
gulp.task('scripts', function() {
    gulp.src(['./source/js/*.js', './source/js/**/*.js'])
    .pipe(concat('script.js'))
    .pipe(stripDebug())
    .pipe(uglify())
    .pipe(gulp.dest('./build/js/'));
});

I have the right order of scripts in my source/index.html, but since files are organized by alphabetic order, gulp will concat underscore.js after backbone.js, and the order of the scripts in my source/index.html does not matter, it looks at the files in the directory.

So does anyone have an idea on this?

Best idea I have is to rename the vendor scripts with 1, 2, 3 to give them the proper order, but I am not sure if I like this.

As I learned more I found Browserify is a great solution, it can be a pain at first but it’s great.

Sebastian Simon
  • 18,263
  • 7
  • 55
  • 75
Michael Joseph Aubry
  • 12,282
  • 16
  • 70
  • 135
  • 5
    I might mention that now-a-days I am using browserify. It has it's own little learning curve IMO. I struggled at first but gulp browserify is a cool way to go! Allowing your code to be modular! You handle the order in a shim, so concatenating is not necessary when using browserify. – Michael Joseph Aubry Apr 01 '14 at 07:58
  • Care to give more details for your solution or a link? – Dmitri Zaitsev Jul 10 '14 at 05:12
  • http://kroltech.com/2013/12/boilerplate-web-app-using-backbone-js-expressjs-node-js-mongodb/ here is a link to a boilerplate project that really helped me get started with good project management. After suffering with learning all of this I can manage my projects much better. He has the project on github and you can see how he uses browserify. Youtube always helps and of course the source itself is always underestimated https://github.com/substack/node-browserify#usage – Michael Joseph Aubry Jul 15 '14 at 05:42
  • Basically the idea is being able to use npm like syntax with `require` on the front end because of course if you have used npm on your server side you see how you can require modules, but browserify allows you to do that on the client side code, keep in mind to get started it requires a bit of tinkering, but it's mainly inside the package.json and if you want to use with gulp.js or grunt.js. If you install the gulp/grunt browserify package you can run `gulp/grunt browserify` and turn your script into one main script, it's a slight learning curve but worth it IMO. – Michael Joseph Aubry Jul 15 '14 at 05:45
  • Thanks! Actually I came across great article https://medium.com/@dickeyxxx/best-practices-for-building-angular-js-apps-266c1a4a6917?source=email-7f7d7c98862f-1405262869336-note_published making a good point that you don't really need `browserify` for `Angular` modules, where simple concatenation works and order does not matter :) – Dmitri Zaitsev Jul 15 '14 at 08:46
  • Nice, I am going to give that a read out of curiosity. I am using backbone.js and not angular.js so in my case it looks like I need to stick to browserify. – Michael Joseph Aubry Jul 24 '14 at 10:08
  • I am using [bower](http://bower.io/) to manage my client-side dependencies and it works great with gulp. [main-bower-files](https://github.com/ck86/main-bower-files) can be used to get a list of all of the sources in order, which you can then concat and uglify. – Codebling Jul 13 '15 at 16:43
  • try use gulp-order package – Anton Putau Oct 16 '15 at 06:54

17 Answers17

205

I had a similar problem recently with Grunt when building my AngularJS app. Here's a question I posted.

What I ended up doing is to explicitly list the files in order in the grunt config. The config file will then look like this:

[
  '/path/to/app.js',
  '/path/to/mymodule/mymodule.js',
  '/path/to/mymodule/mymodule/*.js'
]

Grunt is able to figure out which files are duplicates and not include them. The same technique will work with Gulp as well.

Chad Johnson
  • 21,215
  • 34
  • 109
  • 207
  • 74
    This works fine under gulp as well, by the way. Gulp won't repeat files either. – OverZealous Feb 22 '14 at 22:07
  • 1
    Cool guys, these two masterpieces are awesome. I just finally set up my gulp.js file to work how I want, wrote in some html, saved the file and boom a site built with the best frameworks and good practices at the touch of a button. Plus updates will be easy, if you're not using either one you need to! – Michael Joseph Aubry Feb 22 '14 at 22:11
  • 1
    Yes! I started using Grunt recently, and it is awesome. No more embedding JavaScript applications inside backends frameworks. – Chad Johnson Feb 22 '14 at 22:17
  • 3
    Gulp was duplicating files in my attempt, but I realized I had the case different in gulp vs. the file system, so look out for that! With exact case, gulp will not duplicate files. – Christopher Nov 07 '15 at 18:57
  • How would you add these files in Gulp config? – Christopher Grigg Jan 28 '16 at 00:20
  • It would be exactly the same in a Gulp config. Just use an array. – Chad Johnson Jan 30 '16 at 07:43
  • 2
    Manual ordering is a nightmare in a serious project. There are special file sorters - for angularjs and others. – zhekaus Mar 12 '16 at 14:56
  • 1
    @zhekaus Mind providing links/examples to one or more of those? – Chad Johnson Mar 13 '16 at 00:42
  • 1
    I just had this error so thought I would share; note that if you are using gulp.src([paths.scripts]) before you manually changed the order of the JavaScript files, you will need to get rid of the array or it will throw an error: 'Missing positive glob". So it should look like this now: gulp.src(paths.scripts) as it is already in an array; – Ray Kim Jul 09 '16 at 15:23
138

Another thing that helps if you need some files to come after a blob of files, is to exclude specific files from your glob, like so:

[
  '/src/**/!(foobar)*.js', // all files that end in .js EXCEPT foobar*.js
  '/src/js/foobar.js',
]

You can combine this with specifying files that need to come first as explained in Chad Johnson's answer.

OverZealous
  • 39,252
  • 15
  • 98
  • 100
  • Ah I actually saw your post earlier and it helped me with injecting code to my `src` and to my `build` I saw you using that syntax, I ended up erasing that part because I wasn't sure exactly why I needed it, makes sense now. – Michael Joseph Aubry Feb 22 '14 at 22:14
  • Oh okay so your point here just hit me, wouldnt that make foobar.js last? Shouldnt it be the other way around. `['./source/js/*.js', './source/js/**/underscore.js', './source/js/**/!(underscore)*.js']` – Michael Joseph Aubry Feb 22 '14 at 22:21
  • Yeah, this was more just an additional bit of help. It's most useful when you need or want your core application code to come in after everything else is loaded. There's no reason to use it (`!(foobar)`) if you've included a specific file beforehand. – OverZealous Feb 22 '14 at 22:45
  • For an AngularJS application where my module definitions reside in files that have 'no dash' in the name, this Gulp glob pattern worked; `['src/app/**/!(*-)*.js', 'src/app/**/*.js']` – Sam T Mar 02 '16 at 16:34
18

I have used the gulp-order plugin but it is not always successful as you can see by my stack overflow post gulp-order node module with merged streams. When browsing through the Gulp docs I came across the streamque module which has worked quite well for specifying order of in my case concatenation. https://github.com/gulpjs/gulp/blob/master/docs/recipes/using-multiple-sources-in-one-task.md

Example of how I used it is below

var gulp         = require('gulp');
var concat       = require('gulp-concat');
var handleErrors = require('../util/handleErrors');
var streamqueue  = require('streamqueue');

gulp.task('scripts', function() {
    return streamqueue({ objectMode: true },
        gulp.src('./public/angular/config/*.js'),
        gulp.src('./public/angular/services/**/*.js'),
        gulp.src('./public/angular/modules/**/*.js'),
        gulp.src('./public/angular/primitives/**/*.js'),
        gulp.src('./public/js/**/*.js')
    )
        .pipe(concat('app.js'))
        .pipe(gulp.dest('./public/build/js'))
        .on('error', handleErrors);
});
Community
  • 1
  • 1
dtothefp
  • 1,364
  • 1
  • 16
  • 22
  • See also [stream-series](https://github.com/rschmukler/stream-series). It doesn't require you to specify the object mode, and works with gulp-inject. See my answer. – Codebling Jul 13 '15 at 16:38
  • it seems half the gulp plugins simply don't work consistently (like order as you pointed out), which is a crying shame because the architectural concept of gulp is spectacular, just so many people implementing and maintaining their plugins poorly I think... I find the underlying node modules are more useful so thankyou for this solution! Works great! – Jimmy Hoffa Sep 02 '15 at 16:26
  • 1
    streamqueue, event-stream didn't work for me, but merge2 worked as expected https://www.npmjs.com/package/merge2 – Alexander Shutau Jun 26 '17 at 14:33
14

With gulp-useref you can concatenate every script declared in your index file, in the order in which you declare it.

https://www.npmjs.com/package/gulp-useref

var $ = require('gulp-load-plugins')();
gulp.task('jsbuild', function () {
  var assets = $.useref.assets({searchPath: '{.tmp,app}'});
  return gulp.src('app/**/*.html')
    .pipe(assets)
    .pipe($.if('*.js', $.uglify({preserveComments: 'some'})))
    .pipe(gulp.dest('dist'))
    .pipe($.size({title: 'html'}));
});

And in the HTML you have to declare the name of the build file you want to generate, like this:

<!-- build:js js/main.min.js -->
    <script src="js/vendor/vendor.js"></script>
    <script src="js/modules/test.js"></script>
    <script src="js/main.js"></script>

In your build directory you will have the reference to main.min.js which will contain vendor.js, test.js, and main.js

SuperIRis
  • 423
  • 5
  • 9
  • 2
    This is perfect! I hated the answers where I needed to define order! You know what? The order is there: in the HTML file. Perfect solution. – Ali Ok Jan 15 '16 at 00:23
6

The sort-stream may also be used to ensure specific order of files with gulp.src. Sample code that puts the backbone.js always as the last file to process:

var gulp = require('gulp');
var sort = require('sort-stream');
gulp.task('scripts', function() {
gulp.src(['./source/js/*.js', './source/js/**/*.js'])
  .pipe(sort(function(a, b){
    aScore = a.path.match(/backbone.js$/) ? 1 : 0;
    bScore = b.path.match(/backbone.js$/) ? 1 : 0;
    return aScore - bScore;
  }))
  .pipe(concat('script.js'))
  .pipe(stripDebug())
  .pipe(uglify())
  .pipe(gulp.dest('./build/js/'));
});
fracz
  • 20,536
  • 18
  • 103
  • 149
  • I wish this module worked because it seems like the simplest answer for me, but in my case, where I have numbered filenames and a very simply comparison function, this doesn't work. – Jeremy John Jul 21 '18 at 15:13
  • @JeremyJohn You should be able to sort using `return a.path.localeCompare(b.path, undefined, { numeric: true })` – MiniGod Aug 12 '21 at 14:50
5

I just add numbers to the beginning of file name:

0_normalize.scss
1_tikitaka.scss
main.scss

It works in gulp without any problems.

user3360496
  • 357
  • 2
  • 7
  • 1
    Yeah I find this a bit easier, I mean if you're compiling all your files for production it makes no difference what you name your files in development. – Michael Joseph Aubry Mar 12 '14 at 15:28
  • 2
    I just found out this doesn't work correctly. try using 1_xx, 2_xx, 10_xx ,11_xx. Under Windows at least, it will be 1_xx,10_xx, 11_xx, 2_xx – dbinott Jan 20 '15 at 15:01
  • 18
    Personally I pretty much totally disagree with the statement that it doesn't matter what you name your files in development. All development should be human-focused first, not computer-focused. Changing your files to match your build tool defeats the purpose of a build tool. Change your build to match your project, not the other way around. – Jon Hieb Apr 20 '15 at 18:04
  • 2
    @JonHieb In a way, prefixing your files with a number will also help the next developer know the dependencies of each file, no? If i open a folder and see 1_foo.js , 2_bar.js, 3_baz.js, i will open those files in that order, and read start reading code at 1_foo.js – sqram Mar 15 '16 at 00:16
  • I have found that gulp.src runs async, which means that this does not work consistently in cases where there are more files to process in a dir. – Jeremy John Jul 21 '18 at 15:09
  • You should NOT hack the filenames to get your work done. This will make updates and upgrades a mess in the future. This seems an easy solution, but you are setting a trap to your future self. – Luciano Bargmann Oct 13 '18 at 18:56
5

I have my scripts organized in different folders for each package I pull in from bower, plus my own script for my app. Since you are going to list the order of these scripts somewhere, why not just list them in your gulp file? For new developers on your project, it's nice that all your script end-points are listed here. You can do this with gulp-add-src:

gulpfile.js

var gulp = require('gulp'),
    less = require('gulp-less'),
    minifyCSS = require('gulp-minify-css'),
    uglify = require('gulp-uglify'),
    concat = require('gulp-concat'),
    addsrc = require('gulp-add-src'),
    sourcemaps = require('gulp-sourcemaps');

// CSS & Less
gulp.task('css', function(){
    gulp.src('less/all.less')
        .pipe(sourcemaps.init())
        .pipe(less())
        .pipe(minifyCSS())
        .pipe(sourcemaps.write('source-maps'))
        .pipe(gulp.dest('public/css'));
});

// JS
gulp.task('js', function() {
    gulp.src('resources/assets/bower/jquery/dist/jquery.js')
    .pipe(addsrc.append('resources/assets/bower/bootstrap/dist/js/bootstrap.js'))
    .pipe(addsrc.append('resources/assets/bower/blahblah/dist/js/blah.js'))
    .pipe(addsrc.append('resources/assets/js/my-script.js'))
    .pipe(sourcemaps.init())
    .pipe(concat('all.js'))
    .pipe(uglify())
    .pipe(sourcemaps.write('source-maps'))
    .pipe(gulp.dest('public/js'));
});

gulp.task('default',['css','js']);

Note: jQuery and Bootstrap added for demonstration purposes of order. Probably better to use CDNs for those since they are so widely used and browsers could have them cached from other sites already.

prograhammer
  • 20,132
  • 13
  • 91
  • 118
3

Try stream-series. It works like merge-stream/event-stream.merge() except that instead of interleaving, it appends to the end. It doesn't require you to specify the object mode like streamqueue, so your code comes out cleaner.

var series = require('stream-series');

gulp.task('minifyInOrder', function() {
    return series(gulp.src('vendor/*'),gulp.src('extra'),gulp.src('house/*'))
        .pipe(concat('a.js'))
        .pipe(uglify())
        .pipe(gulp.dest('dest'))
});
Codebling
  • 10,764
  • 2
  • 38
  • 66
3

merge2 looks like the only working and maintained ordered stream merging tool at the moment.

Update 2020

The APIs are always changing, some libraries become unusable or contain vulnerabilities, or their dependencies contain vulnerabilities, that are not fixed for years. For text files manipulations you'd better use custom NodeJS scripts and popular libraries like globby and fs-extra along with other libraries without Gulp, Grunt, etc wrappers.

import globby from 'globby';
import fs from 'fs-extra';

async function bundleScripts() {
    const rootPaths = await globby('./source/js/*.js');
    const otherPaths = (await globby('./source/**/*.js'))
        .filter(f => !rootFiles.includes(f));
    const paths = rootPaths.concat(otherPaths);

    const files = Promise.all(
        paths.map(
            // Returns a Promise
            path => fs.readFile(path, {encoding: 'utf8'})
        )
    );

    let bundle = files.join('\n');
    bundle = uglify(bundle);
    bundle = whatever(bundle);
    bundle = bundle.replace(/\/\*.*?\*\//g, '');

    await fs.outputFile('./build/js/script.js', bundle, {encoding: 'utf8'});
}

bundleScripts.then(() => console.log('done');
Alexander Shutau
  • 2,660
  • 22
  • 32
1

An alternative method is to use a Gulp plugin created specifically for this problem. https://www.npmjs.com/package/gulp-ng-module-sort

It allows you to sort your scripts by adding in a .pipe(ngModuleSort()) as such:

var ngModuleSort = require('gulp-ng-module-sort');
var concat = require('gulp-concat');

gulp.task('angular-scripts', function() {
    return gulp.src('./src/app/**/*.js')
        .pipe(ngModuleSort())
        .pipe(concat('angularAppScripts.js))
        .pipe(gulp.dest('./dist/));
});

Assuming a directory convention of:

|——— src/
|   |——— app/
|       |——— module1/
|           |——— sub-module1/
|               |——— sub-module1.js
|           |——— module1.js
|       |——— module2/
|           |——— sub-module2/
|               |——— sub-module2.js
|           |——— sub-module3/
|               |——— sub-module3.js
|           |——— module2.js
|   |——— app.js

Hope this helps!

Oak
  • 151
  • 1
  • 9
1

For me I had natualSort() and angularFileSort() in pipe which was reordering the files. I removed it and now it works fine for me

$.inject( // app/**/*.js files
    gulp.src(paths.jsFiles)
      .pipe($.plumber()), // use plumber so watch can start despite js errors
      //.pipe($.naturalSort())
      //.pipe($.angularFilesort()),
    {relative: true}))
casper123
  • 1,736
  • 4
  • 21
  • 39
1

I just use gulp-angular-filesort

function concatOrder() {

    return gulp.src('./build/src/app/**/*.js')
        .pipe(sort())
        .pipe(plug.concat('concat.js'))
        .pipe(gulp.dest('./output/'));
}
Tavish Aggarwal
  • 1,020
  • 3
  • 22
  • 51
Maccurt
  • 12,655
  • 7
  • 32
  • 43
0

I'm in a module environnement where all are core-dependents using gulp. So, the core module needs to be appended before the others.

What I did:

  1. Move all the scripts to an src folder
  2. Just gulp-rename your core directory to _core
  3. gulp is keeping the order of your gulp.src, my concat src looks like this:

    concat: ['./client/src/js/*.js', './client/src/js/**/*.js', './client/src/js/**/**/*.js']
    

It'll obviously take the _ as the first directory from the list (natural sort?).

Note (angularjs): I then use gulp-angular-extender to dynamically add the modules to the core module. Compiled it looks like this:

angular.module('Core', ["ui.router","mm.foundation",(...),"Admin","Products"])

Where Admin and Products are two modules.

soyuka
  • 8,839
  • 3
  • 39
  • 54
0

if you would like to order third party libraries dependencies, try wiredep. This package basically checks each package dependency in bower.json then wire them up for you.

zak.http
  • 316
  • 4
  • 4
0

I tried several solutions from this page, but none worked. I had a series of numbered files which I simply wanted be ordered by alphabetical foldername so when piped to concat() they'd be in the same order. That is, preserve the order of the globbing input. Easy, right?

Here's my specific proof-of-concept code (print is just to see the order printed to the cli):

var order = require('gulp-order');
var gulp = require('gulp');
var print = require('gulp-print').default;

var options = {};

options.rootPath = {
  inputDir: process.env.INIT_CWD + '/Draft',
  inputGlob: '/**/*.md',
};

gulp.task('default', function(){
  gulp.src(options.rootPath.inputDir + options.rootPath.inputGlob, {base: '.'})
    .pipe(order([options.rootPath.inputDir + options.rootPath.inputGlob]))
    .pipe(print());
});

The reason for the madness of gulp.src? I determined that gulp.src was running async when I was able to use a sleep() function (using a .map with sleeptime incremented by index) to order the stream output properly.

The upshot of the async of src mean dirs with more files in it came after dirs with fewer files, because they took longer to process.

Jeremy John
  • 13,686
  • 2
  • 16
  • 16
0

In my gulp setup, I'm specifying the vendor files first and then specifying the (more general) everything, second. And it successfully puts the vendor js before the other custom stuff.

gulp.src([
  // vendor folder first
  path.join(folder, '/vendor/**/*.js'),
  // custom js after vendor
  path.join(folder, '/**/*.js')
])    
ProGrammar
  • 298
  • 4
  • 17
0

Apparently you can pass in the "nosort" option to gulp.src gulp.src.

Wildhammer
  • 2,017
  • 1
  • 27
  • 33