Compile JavaScripts with Gulp and Resolve Dependencies (separate files) - javascript

I want to compile JavaScript files with Gulp.
I have a src directory where all scripts are present with .js extension. I want all scripts to be compiled separately and placed into a destination directory (dist) with the same filename as the original.
Consider this example:
src/jquery.js:
/**
* #require ../../vendor/jquery/dist/jquery.js
*/
src/application.js:
/**
* #require ../../vendor/angular/angular.js
* #require ../../vendor/ngprogress-lite/ngprogress-lite.js
* #require ../../vendor/restangular/dist/restangular.js
* #require ../../vendor/lodash/dist/lodash.underscore.js
* #require ../../vendor/angular-input-locker/dist/angular-input-locker.js
* #require ../../vendor/angular-route/angular-route.js
*/
(function(document, angular) {
'use strict';
var moduleName = 'waApp';
angular.module(moduleName, [
// Some more code here.
;
// Bootstrapping application when DOM is ready.
angular.element(document).ready(function() {
angular.bootstrap(document, [moduleName]);
});
})(document, angular);
I'm using gulp-resolve-dependencies to resolve dependencies specified in the header of each source JavaScript file.
My gulpfile.js is looking like this:
//==============//
// Dependencies //
//==============//
var gulp = require('gulp');
var pathModule = require('path');
var resolveDependencies = require('gulp-resolve-dependencies');
var concat = require('gulp-concat');
var uglify = require('gulp-uglify');
//=======//
// TASKS //
//=======//
gulp.task('build:scripts', function(callback) {
return gulp.src('scripts/*.js')
.pipe(resolveDependencies({
pattern: /\* #require [\s-]*(.*?\.js)/g,
log: true
}))
.pipe(concat('all.js'))
.pipe(uglify())
.pipe(gulp.dest('js/'))
;
});
In order to merge scripts resolved by resolveDependencies I have to use concat, but concat requires a filename and merges not only original file and dependencies resolved for it, but all JavaScript files specified via glob pattern.
So, How do I get individual JavaScript files as the output? Like this:
dist/jquery.js:
src/jquery.js
vendor/jquery.js
dist/application.js:
src/application.js
vendor/angular.js
vendor/ngprogress-lite.js
...
I have this workaround for now:
gulp.task('build:scripts', function(callback) {
var compileScript = function(stream, filename) {
return stream
.pipe(resolveDependencies({
pattern: /\* #require [\s-]*(.*?\.js)/g,
log: true
}))
.pipe(concat(filename))
.pipe(uglify())
.pipe(gulp.dest('dist/'))
;
};
var scripts = getListOfFiles('src/', 'js');
for (key in scripts) {
var filename = scripts[key];
var stream = gulp.src(pathModule.join('src/', filename));
compileScript(stream, filename);
}
callback(null);
});
//===================//
// FUNCTIONS & UTILS //
//===================//
/**
* Returns list of files in the specified directory
* with the specified extension.
*
* #param {string} path
* #param {string} extension
* #returns {string[]}
*/
function getListOfFiles(path, extension) {
var list = [];
var files = fs.readdirSync(path);
var pattern = new RegExp('.' + extension + '$');
for (var key in files) {
var filename = files[key];
if (filename.match(pattern)) {
list.push(filename);
}
}
return list;
}
But it looks hackish and I can't find a good way to make it work with gulp-watch.
Is there a better and simpler way to solve this problem and achieve desired result?

How do I get individual JavaScript files as the output?
Check an answer I gave to a similar problem here: Pass random value to gulp pipe template
Using this gulp plugin: https://github.com/adam-lynch/glob-to-vinyl
You can have access to single files.
This is how (assuming the use of this plugin):
function compileScript(file) {
gulp
.src('file')
.pipe(resolveDependencies({
pattern: /\* #require [\s-]*(.*?\.js)/g,
log: true
}))
.pipe(concat())
.pipe(uglify())
.pipe(gulp.dest('dist/'))
;
};
gulp.task('build:scripts', function() {
globToVinyl('src/**/*.js', function(err, files){
for (var file in files) {
compileScript(files[file].path);
}
});
});

Here's the result of rewriting my task using solution specified by #avcajaraville. It's a complete, tested and working code.
var targetDir = 'web';
var sourceDir = 'assets';
var gulp = require('gulp');
var pathModule = require('path');
var resolveDependencies = require('gulp-resolve-dependencies');
var concat = require('gulp-concat');
var uglify = require('gulp-uglify');
var globToVinyl = require('glob-to-vinyl');
gulp.task('build:scripts', function(callback) {
var compileScript = function(filePath, destinationDirectory) {
// Extracting filename from absolute path (required by concat).
var filename = pathModule.basename(filePath);
return gulp
.src(filePath)
.pipe(resolveDependencies({
pattern: /\* #require [\s-]*(.*?\.js)/g,
log: true
}))
.pipe(concat(filename))
.pipe(uglify())
.pipe(gulp.dest(destinationDirectory))
;
};
var sourceGlob = pathModule.join(sourceDir, '/scripts/*.js');
var destinationDirectory = pathModule.join(targetDir, '/js/');
globToVinyl(sourceGlob, function(errors, files) {
for (var file in files) {
compileScript(files[file].path, destinationDirectory);
}
});
callback(null);
});

Related

Set a different destination folder structure than the source folder

I'm running a gulp task to minify and move JS files.
var js_modules = 'application/modules/**/assets/js/*.js';
var js_dist_modules = 'assets/js/modules/';
gulp.task('dev_scripts', function() {
return gulp.src(js_modules)
.pipe(plumber())
.pipe(uglify())
.pipe(gulp.dest(js_dist_modules));
});
With this task the output is:
Source:
application/modules/users/assets/js/users.js
application/modules/menu/assets/js/menu.js
Destination:
assets/js/modules/users/assets/js/users.js
assets/js/modules/menu/assets/js/menu.js
And I want the destinations to be:
assets/js/modules/users/users.js
assets/js/modules/menu/menu.js
How do I achiev that?
I used gulp-rename:
var rename = require('gulp-rename');
var js_modules = 'application/modules/**/assets/js/*.js';
var js_dist_modules = 'assets/js/modules/';
gulp.task('dev_scripts', function() {
return gulp.src(js_modules)
.pipe(plumber())
.pipe(uglify())
.pipe(rename(function(file) {
file.dirname = js_dist_modules + file.basename;
}))
.pipe(gulp.dest(.));
});
Using gulp-flatten it appears something like this may work (untested):
var flatten= require('gulp-flatten);
var js_modules = 'application/modules/**/assets/js/*.js';
var js_dist_modules = 'assets/js/modules/';
gulp.task('dev_scripts', function() {
return gulp.src(js_modules)
.pipe(plumber())
.pipe(uglify())
.pipe(flatten({ subPath: [2, 1] } ))
.pipe(gulp.dest(js_dist_modules));
});
You could play with the subPath numbers to get the third (2) or whichever subDirectory you want.
This answer is more general than the previous using rename in case your desired directories - in your case the glob ** - are not the same as files' basenames.

How to setup gulp for this type of scaffolding?

Folder Structure
Components
------component1
-partials
- js
- html
- scss
- component1.css
- component1.js
------component2
-partials
- js
- html
- scss
- component2.css
- component2.js
Functionality is all my js, html and scss file convert into one css and js fine but inside into the component folder.
If I create a new component I don't want every time to add them separately into gulp It will automatically add through gulp. How can I write my gulp to achieve this type of functionality ?
Help will be really appreaticed...!!!
You can use this code that I created for your case:
Project structure:
folder structure tested
gulpfile.js
/**
* Created by Anderson on 22/04/2016.
*/
'use strict';
var gulp = require('gulp');
var data = require('gulp-data');
var concat = require('gulp-concat');
var sass = require('gulp-sass');
var gcallback = require('gulp-callback');
var rename = require('gulp-rename');
var del = require('del');
var path = require('path');
var fs = require('fs');
gulp.task('main',function(){
//scan componens folder all files and folders
gulp.src("./components/**")
.pipe(data(function(file,cb){
var filepath = file.path;
var stats = fs.lstatSync(filepath);
var base = file.base;
var fpath = filepath.replace(file.base,"");
//console.log(fpath);
var array = fpath.split("\\");
var basename = path.basename(fpath);
var componentName = array[0];
//destiny
var dest = base + fpath.replace(basename,"");
dest = dest.replace("partials\\","");
//process the scss
if(stats.isDirectory() && basename == "scss"){
//console.log(array,componentName,basename);
//console.log(file.path);
var scssScan = base + fpath+"\\*.scss";
console.log("scan",scssScan );
console.log("dest",dest);
//scan dir for scss
gulp.src(scssScan)
//concat all scss in a temporary file
.pipe(concat(componentName+".scss"))
.pipe(gulp.dest(dest))
.pipe(gcallback(function(){
//scan to process the scss
var componentPath = base + componentName;
var componentScssScan = componentPath + "\\*.scss";
//console.log(componentPath,componentScssScan);
//console.log(componentPath + "\\" + componentName+".scss");
//scan the component dir for scss
gulp.src(componentScssScan)
//process the sass
.pipe(sass())
.pipe(gulp.dest(dest));
//delete the temporary scss
//.pipe(del(componentPath + "\\" + componentName+".scss"));
}));
}
//process the js
if(stats.isDirectory() && basename == "js"){
//console.log(array,componentName,basename);
//console.log(file.path);
var scssScan = base + fpath+"\\*.js";
console.log("scan",scssScan );
console.log("dest",dest);
//scan dir for js
gulp.src(scssScan)
//concat all js in a temporary file
.pipe(concat(componentName+".js"))
.pipe(gulp.dest(dest));
}
cb(undefined,undefined);
}));
});
I think what you need to do is add watcher for the css and js files and run specific task based on the changes, here's some example:
var gulp = require('gulp'),
less = require('gulp-less'),
cleancss = require('gulp-clean-css'),
server = require('gulp-server-livereload'),
rename = require('gulp-rename');
gulp.task('server', ['watch', 'webserver']);
gulp.task('styles', function() {
return gulp.src('public/assets/less/app.less')
.pipe(less())
.pipe(rename('app.css'))
.pipe(cleancss())
.pipe(rename({suffix: '.min'}))
.pipe(gulp.dest('public/dist/css'))
});
// Add watcher for the changes in files
gulp.task('watch', function() {
gulp.watch('public/assets/less/*.less', ['styles']);
})
gulp.task('webserver', function() {
gulp.src('public')
.pipe(server({
livereload: true,
port: 8080,
open: true,
}))
});
And you can use Livereload Chrome Extension to enable browser auto-refresh whenever there's a changes in your code as well.

Detect what file change with gulp

I have this gulpfile:
var gulp = require('gulp'),
concat = require('gulp-concat'),
uglify = require('gulp-uglify');
gulp.task('minifyJS', function() {
return gulp.src(['src/*.js'])
.pipe(uglify())
.pipe(gulp.dest('min'));
});
gulp.task('watch', function() {
gulp.watch(['src/*.js'], ['minifyJS']);
});
I want to know what file trigger the watcher and his absolute path.
For example: if my project is placed in /myCode and I change the file src/main.js, I want to see /myCode/src/main.js inside minifyJS task. Is there a way to do it?
Thank you for your time.
You can do it by using gulp-ng-annotate and gulp-changed:
var gulp = require('gulp');
var changed = require('gulp-changed');
var rename = require('gulp-rename');
var ngAnnotate = require('gulp-ng-annotate'); // just as an example
var SRC = 'src/*.js';
var DEST = 'src/';
//Function to get the path from the file name
function createPath(file) {
var stringArray = file.split('/');
var path = '';
var name = stringArray[1].split('.');
stringArray = name[0].split(/(?=[A-Z])/);
if (stringArray.length>1) {stringArray.pop()};
return {folder: stringArray[0], name: name[0]}
}
gulp.task('default', function () {
return gulp.src(SRC)
.pipe(changed(DEST))
// ngAnnotate will only get the files that
// changed since the last time it was run
.pipe(ngAnnotate())
.pipe(rename(function (path) {
var createdPath = createPath(path);
path.dirname = createdPath.folder;
path.basename: createdPath.name,
path.prefix: "",
path.suffix: "",
path.extname: ".min.js"
}))
.pipe(gulp.dest(DEST));
});
Result:
Use gulp-changed npm package.
$ npm install --save-dev gulp-changed
Try the below in gulp file, (I haven't tried)
var gulp = require('gulp'),
concat = require('gulp-concat'),
uglify = require('gulp-uglify'),
changed = require('gulp-changed');
gulp.task('minifyJS', function() {
return gulp.src(['src/*.js'])
.pipe(changed('min'))
.pipe(uglify())
.pipe(gulp.dest('min'));
});
gulp.task('watch', function() {
gulp.watch(['src/*.js'], ['minifyJS']);
});
see the documentation of this package https://www.npmjs.com/package/gulp-changed
Based on your comment to Julien's answer this should be fairly close to what you want, or at least get you going in the right direction:
var gulp = require('gulp'),
concat = require('gulp-concat'),
uglify = require('gulp-uglify'),
cache = require('gulp-cached'),
rename = require('gulp-rename'),
path = require('path');
function fileName(file) {
return file.dirname + path.sep + file.basename + file.extname;
}
gulp.task('minifyJS', function() {
return gulp.src(['src/*.js'])
.pipe(cache('minifyJS'))
.pipe(rename(function(file) {
var nameOfChangedFile = fileName(file);
if (nameOfChangedFile == './main.js') {
file.basename = 'main.min'
}
if (nameOfChangedFile == './userView.js') {
file.basename = 'user/userView.min'
}
console.log(nameOfChangedFile + ' -> ' + fileName(file));
}))
.pipe(uglify())
.pipe(gulp.dest('min'));
});
gulp.task('watch', function() {
gulp.watch(['src/*.js'], ['minifyJS']);
});
This uses gulp-cached to keep an in-memory cache of all the files in your src/ folder that have passed through the stream. Only files that have changed since the last invocation of minifyJS are passed down to the gulp-rename plugin.
The gulp-rename plugin itself is then used to alter the destination path of the changed files.
Note: the cache is empty on first run, since no files have passed through the gulp-cached plugin yet. This means that the first time you change a file all files in src/ will be written to the destination folder. On subsequent changes only the changed files will be written.

Browser Sync + Gulp + Jade, Why separate the Jade watch task?

I was looking at this browser sync recipe which is a gulpfile configuration that works with jade, sass and browser sync, I don't care about sass so to simplify I modified the code a little:
var gulp = require('gulp');
var browserSync = require('browser-sync');
var jade = require('gulp-jade');
var reload = browserSync.reload;
/**
* Compile jade files into HTML
*/
gulp.task('templates', function() {
return gulp.src('./app/*.jade')
.pipe(jade())
.pipe(gulp.dest('./dist/'));
});
/**
* Important!!
* Separate task for the reaction to `.jade` files
*/
gulp.task('jade-watch', ['templates'], reload);
/**
* Serve and watch the jade files for changes
*/
gulp.task('default', ['templates'], function () {
browserSync({server: './dist'});
gulp.watch('./app/*.jade', ['jade-watch']);
});
What I don't understand is this comment:
/**
* Important!!
* Separate task for the reaction to `.jade` files
*/
Why is this important? Why not just do this?
/**
* Compile jade files into HTML
*/
gulp.task('templates', function() {
return gulp.src('./app/*.jade')
.pipe(jade())
.pipe(gulp.dest('./dist/'))
.pipe(reload({stream: true}));
});
/**
* Serve and watch the jade files for changes
*/
gulp.task('default', ['templates'], function () {
browserSync({server: './dist'});
gulp.watch('./app/*.jade', ['templates']);
});
You might have figured this out by now; but in case anyone else comes along wondering the same thing (as I did): by setting the 'templates' task as a dependency of 'jade-watch' you ensure it has completed before triggering reload.

Gulp watch not called from within Nodemon

So I have my Gulp file as below.
The 'watch' block seems to work absolutely fine, and do what is expected. Nodemon works in the way that it detects file changes and refreshes the server, that works too.
But I can't for the life of me get Nodemon to call the 'watch' method when a file changes.
On line 81, the .on('start' is called succesfully from nodemon, but the code inside the 'watch' method is never executed.
var gulp = require('gulp');
var gutil = require('gulp-util'); // For logging stats and warnings
var jshint = require('gulp-jshint'); // For checking JavaScript for warnings
var concat = require('gulp-concat'); // For joining together multiple files
var uglify = require('gulp-uglify'); // For minimising files
var coffee = require('gulp-coffee'); // For compiling coffee script into js
var cofLint = require('gulp-coffeelint');// For checking coffee script for errors
var less = require('gulp-less'); // For compiling Less into CSS
var cssLint = require('gulp-csslint'); // For checking the awesomeness of css
var minCss = require('gulp-minify-css');// For minifying css
var uncss = require('gulp-uncss'); // For deleting unused CSS rules
var footer = require('gulp-footer'); // For adding footer text into files
var nodemon = require('gulp-nodemon'); // For the super cool instant refreshing server
var bSync = require('browser-sync'); // Syncs the place between multiple browsers for dev
var es = require('event-stream'); // For working with streams rather than temp dirs
var sourcePath = "sources";
var destPath = "public";
var jsFileName = "all.min.js";
var cssFileName = "all.min.css";
var footerText = "";
/* JavaScript Tasks */
gulp.task('scripts', function(){
var jsSrcPath = sourcePath + '/javascript/**/*';
var jsResPath = destPath + '/javascript';
var jsFromCs = gulp.src(jsSrcPath+'.coffee')// Get all coffee script
.pipe(cofLint()) // Check CS for errors or warnings
.pipe(cofLint.reporter()) // Output the error results
.pipe(coffee()); // Convert coffee to vanilla js
var jsFromPlain = gulp.src(jsSrcPath+'.js');// get all vanilla JavaScript
return es.merge(jsFromCs, jsFromPlain) // Both js from cs and vanilla js
.pipe(jshint()) // Check js errors or warnings
.pipe(jshint.reporter('jshint-stylish')) // Print js errors or warnings
.pipe(concat(jsFileName,{newLine: ';'})) // Concatenate all files together
.pipe(uglify()) // Minify JavaScript
.pipe(footer(footerText)) // Add footer to script
.pipe(gulp.dest(jsResPath)); // Save to destination
});
/* CSS Tasks */
gulp.task('styles', function(){
var cssSrcPath = sourcePath + '/styles/**/*';
var cssResPath = destPath + '/stylesheet';
var cssFromLess = gulp.src(cssSrcPath+'.less') // Get all Less code
.pipe(less()); // Convert Less to CSS
var cssFromVanilla = gulp.src(cssSrcPath+'.css');// Get all CSS
return es.merge(cssFromLess, cssFromVanilla) // Combine both CSS
.pipe(cssLint()) // Check CSS for errors or warnings
.pipe(cssLint.reporter()) // And output the results
.pipe(concat(cssFileName)) // Concatenate all files together
.pipe(minCss({compatibility: 'ie8'})) // Minify the CSS
.pipe(gulp.dest(cssResPath)); // Save to destination
});
/* Configure files to watch for changes */
gulp.task('watch', function() {
gulp.watch(sourcePath+'/**/*.{js,coffee}', ['scripts']);
gulp.watch(sourcePath+'/**/*.{css,less}', ['styles']);
});
/* Start Nodemon */
gulp.task('demon', function () {
nodemon({
script: './bin/www',
ext: 'js coffee css less html',
env: { 'NODE_ENV': 'development'}
})
.on('start', ['watch'])//TODO: ERROR: watch is never called, even though start is called
.on('change', ['watch'])
.on('restart', function () {
console.log('restarted!');
});
});
/* Default Task */
gulp.task('default', ['demon']);
Any suggestions why this may be happening?
Is there something wrong with my Gulp file?
(and yes, I know the code is a little over-commented, I work with apprentices and they like English better than code ;)
The problem is that Nodemon and browser-sync both need to be running. This code works:
gulp.task('nodemon', function (cb) {
var called = false;
return nodemon({
script: './bin/www',
watch: ['source/**/*']
})
.on('start', function onStart() {
if (!called) { cb(); }
called = true;
})
.on('restart', function onRestart() {
setTimeout(function reload() {
bSync.reload({
stream: false
});
}, 500);
});
});
gulp.task('browser-sync', ['nodemon', 'scripts', 'styles'], function () {
bSync.init({
files: ['sources/**/*.*'],
proxy: 'http://localhost:3000',
port: 4000,
browser: ['google chrome']
});
gulp.watch(sourcePath+'/**/*.{js,coffee}', ['scripts']);
gulp.watch(sourcePath+'/**/*.{css,less}', ['styles']);
gulp.watch("sources/**/*").on('change', bSync.reload);
gulp.watch("views/**/*.jade").on('change', bSync.reload);
});
/* Default Task */
gulp.task('default', ['clean', 'browser-sync']);

Categories