I am using gulp to concatenate and minify a number of standalone *.js scripts used on my web site. Basically this is just a catchall folder where I place little utility scripts that run on page load. For example, one of them starts a carousel slider, another adds a class to the header that shrinks it on scroll, etc. Each of these "features" has its own standalone *.js file.
Now, I would like to use the popular js-cookie library in one of those scripts. Unfortunately, since my project is not set up as an ES6 module, I am not able to able to import the js-cookie library the way it's specified in the docs, like this:
import Cookies from 'js-cookie'
When I do this, I get the error message Uncaught SyntaxError: Cannot use import statement outside a module.
I tried changing it to this:
window.Cookies = require('js-cookie')
but that gave me this error:
Uncaught ReferenceError: require is not defined
Here is my gulpfile, followed by the feature.js script in which I'm trying to use the js-cookie library:
gulpfile.js
// Initialize modules
const { src, dest, watch, series, parallel } = require('gulp');
const sourcemaps = require('gulp-sourcemaps');
const sass = require('gulp-sass');
const concat = require('gulp-concat');
const uglify = require('gulp-uglify');
const postcss = require('gulp-postcss');
const autoprefixer = require('autoprefixer');
const cssnano = require('cssnano');
var replace = require('gulp-replace');
var merge = require('merge-stream');
// File paths (note that src paths are arrays)
const files = {
scssSrcPath: [
'scss/*.scss',
'scss/_pageContentModules/*.scss'
],
jsSrcPath: [
'js/*.js',
'node_modules/slick-carousel/slick/slick.js'
],
scssDstPath: '../web/css',
jsDstPath: '../web/js'
}
// Sass task: compiles SCSS files into style.css
function scssTask(){
return merge(files.scssSrcPath.map(function (file) {
return src(file)
}))
.pipe(sourcemaps.init()) // initialize sourcemaps first
.pipe(sass()) // compile SCSS to CSS
.pipe(postcss([ autoprefixer(), cssnano() ])) // PostCSS plugins
.pipe(sourcemaps.write('.'))
.pipe(dest(files.scssDstPath));
}
// JS task: concatenates and uglifies JS files to script.js
function jsTask(){
return merge(files.jsSrcPath.map(function (file) {
return src(file)
}))
.pipe(concat('app.js'))
.pipe(uglify())
.pipe(dest(files.jsDstPath));
}
// Watch task: watch SCSS and JS files for changes
// If any change, run scss and js tasks simultaneously
function watchTask(){
watch(files.scssSrcPath, scssTask);
watch(files.jsSrcPath, jsTask);
}
// Export the default Gulp task so it can be run
// Runs the scss and js tasks simultaneously
// then watch task
exports.default = series(
parallel(scssTask, jsTask),
watchTask
);
js/feature.js
import Cookies from 'js-cookie';
const rs = cookies.get('referral_source');
if (typeof rs !== 'undefined') {
console.log('referral_source = ' + rs);
}
How can I get this working? Is there a way to do it using my simple Gulp setup, or do I need to go beyond and set up a full-on Webpack setup (with all the complexity that adds)?
Unfortunately, as far as I know, Gulp does not support the ability to use ES6 modules. If you want to use them, you will need to use Webpack.
But js-cookie does have a jsDelivr CDN: <script src="https://cdn.jsdelivr.net/npm/js-cookie#3.0.1/dist/js.cookie.min.js"></script>. By including this before your JS script, like this:
<script src="https://cdn.jsdelivr.net/npm/jscookie#3.0.1/dist/js.cookie.min.js"></script>
<script src="./js/feature.js"></script>
I have a number of gulp tasks each residing in its own file (using the require-dir module) rather than a monolithic file.
I am using modules for configuration settings instead of json files (which I prefer for comments and for derived values).
To keep things simple here is an example setup with a single key I need to share/set between the gulp tasks.
/config/index.js
var config = {}
config.buildType = ''; // set this to either 'dev' or 'dist'
module.exports = config;
here is default task for which I want to set config.buildType to 'dev'
default.js
var gulp = require('gulp');
var config = require('../config/');
gulp.task('default', ['build'], function(cb) {
});
here is a deploy task for which I want to set buildType to 'dist'
deploy.js
var gulp = require('gulp');
var config = require('../config/');
gulp.task('deploy-s3', ['build'], function() {
});
here is a build task that I want to change based on buildType
build.js
var gulp = require('gulp');
var runSequence = require('run-sequence');
var config = require('../config/');
gulp.task('build', function(cb) {
console.log('in build',config.buildType);
if (config.buildType == 'dev') runSequence('clean',['sass',config.htmlGenerator], 'watch', cb);
if (config.buildType == 'dist') runSequence('clean',['sass',config.htmlGenerator], cb);
});
So here is the issue If I set config.buildType in default.js or deploy.js outside gulp.task then since they are all lumped into essentially one file by require-dir the value is simply whichever file was loaded last. If I set it inside the gulp.task function I am confused about about the timing/scope of that setting.
Update: Found this related issue https://github.com/gulpjs/gulp/issues/193. It was pointed out in this issue the task function starts after all the queued tasks so that means I can't set something inside the task function and expect it to be executed before the listed tasks (in my case 'build')
one poster made a task to set a parameter like this
gulp.task('set-dist', function () {
config.buildType = 'dist';
});
gulp.task('deploy', ['set-dist', 'build']);
So some advice..... do I go the way of this "hack" or is there some better way to do this??
(fyi, I am just a couple months into learning node/javascript on my own so my experience is limited)
You can use process.env to hold config attributes for your project. Check this.
In your case you can do:
var gulp = require('gulp');
var runSequence = require('run-sequence');
gulp.task('build', function(cb) {
if (process.env.NODE_ENV === 'development') {
runSequence('clean',['sass',config.htmlGenerator], 'watch', cb);
} else {
runSequence('clean',['sass',config.htmlGenerator], cb);
}
});
gulp build NODE_ENV=production for production
or gulp build NODE_ENV=development.
This will play nicely with existing CI tools like Travis.
Decided to go with this dropping the 'build' task altogether. It will hopefully work in some similar form after gulp 4.0 is released. It allows me to modify any setting before calling other tasks. Can use it with gulpif in tasks like my 'sass' task which is a pipe only task. Still wondering if there is a "better" way.
var gulp = require('gulp');
var config = require('../config/');
var runSequence = require('run-sequence');
gulp.task('default', function(cb) {
config.buildType='dev'
config.url = 'http://localhost:' + config.localport;
runSequence('clean',['sass',config.htmlGenerator],'watch', cb);
});
I am enumerating the subdirectories in a directory. For each sub directory I would like to apply a number of gulp activities like less compilation, and then create an output file specific to that subdirectory.
I would like the gulp process to continue, as further transformation steps need to be performed later.
Can someone help me understand how I can create these files half way through the "gulp pipeline"?
This seems quite interesting to achieve and gulp has no limitations at all.
I will give you detailed example how I have managed to accomplish such a task a while ago.
Let assume that you have directoryA. Subdirectories childA, childB and childC are contained into directoryA. So basically your tree structure looks like:
directoryA
--childA
--childB
--childC
I am always looking for a flexible solutions so I would suggest to include a JSON file in each subdirectory naming the tasks you would like to running. Using fs you can access these files. You can also use run-sequence to execute gulp tasks synchronously.
For demo purposes place a file named manifest.json inside childA subdirectory.
Manifest.json contains the following declarations:
{
"filesToProccess" : ["./childA/*.js", "./childB/*.js"],
"tasksToRun" :["taskA", "taskB"]
}
Finally gulpfile would like this:
'use strict';
//dependencies declared into package.json
//install them using npm
var gulp = require('gulp'),
fs = require('fs'),
runSequence = require('run-sequence'),
path = require('path');
//these two array will keep the actions you have included into manifest file.
var filesHolder = [], tasksHolder = [];
gulp.task('taskA', function () {
return gulp.src(filesHolder)
.pipe(whatever)
.pipe(gulp.dest('whatever')); //chailed actions
});
gulp.task('taskB', function () {
return gulp.src(filesHolder)
.pipe(whatever)
.pipe(gulp.dest('whatever'));
});
//a simple utility function to read all subdirectories of directoryA
function getDirectories(srcpath) {
return fs.readdirSync(srcpath).filter(function(file) {
return fs.statSync(path.join(srcpath, file)).isDirectory();
});
}
//finally insert the default gulp task
gulp.task('default', function(){
var manifest;
//map directory's A subdirectories
var availableDirs = getDirectories("./directoryA");
//finally loop the available subdirectories, load each manifest file and
availableDirs.forEach(function(subdir) {
manifest = require("./directoryA/"+subdir+"manifest.json");
filesHolder = manifest.filesToProccess;
tasksHolder = manifest.tasksToRun;
runSequence( tasksHolder , function () {
console.log( " Task ended :" + tasksHolder + " for subdirectory : " + subdir);
});
});
});
In make it's possible to define custom targets that have no relevance to the actual code that they act upon, in the sense that they are language agnostic.
release_sortof:
#echo packaging release...
tar czf release.tar.gz file1 file2 file3
ls /dev/null
ls /dev/stderr
ls /dev/stdout
I know the example above is horrible, but the point I'm trying to illustrate is that the code in the release_sortof target doesn't depend on the fact that my project uses code written in C, for example; nor does it depend on me using Make built-ins such as foreach.
Is there a way to work with javascript/<INSERT-NAME>script files without using the ever insufficient plugins available for gulp? As in, could I lint my coffeescript with coffeelint by directly calling the coffeelint module:
var gulp = require('gulp')
, coffeelint = require('coffeelint')
;
gulp.task('lint', function() {
/* run coffeelint on source files */
});
Or can this only be done using plugins?
Another example would be to run arbitrary code like so:
var spawn = require('child_process').spawn;
gulp.task('blue', function() {
var child = spawn('ls');
/* do stuff with spawned child process */
});
I do this kind of thing for browserify using vinyl-source-stream - basically allowing you to use the library as it is, and not using gulp-* plugins.
var browserify = require('browserify'),
gulp = require('gulp'),
source = require('vinyl-source-stream'),
stringify = require('stringify'),
plumber = require('gulp-plumber'),
config = require('../config').scripts;
gulp.task('browserify', function () {
return browserify(config.app)
.transform(stringify(['.html']))
.bundle()
.pipe(plumber())
.pipe(source('bundle.js'))
.pipe(gulp.dest(config.dest));
});
Heres the npm - https://www.npmjs.com/package/vinyl-source-stream
Use conventional text streams at the start of your gulp or vinyl
pipelines, making for nicer interoperability with the existing npm
stream ecosystem.
Maybe that will help you?
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.
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.
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.
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);
});
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
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/'));
});
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.
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.
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'))
});
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');
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!
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}))
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/'));
}
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:
Move all the scripts to an src folder
Just gulp-rename your core directory to _core
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.
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.
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.
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')
])
Apparently you can pass in the "nosort" option to gulp.src gulp.src.