diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2ccbe46 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/node_modules/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c2f110c --- /dev/null +++ b/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2014 Elao + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md index 0bc2c97..bb40d26 100644 --- a/README.md +++ b/README.md @@ -1 +1,3 @@ # Elao - Assets - Gulp + +Handle your project's assets with style ! (and gulp) \ No newline at end of file diff --git a/assets.js b/assets.js new file mode 100644 index 0000000..b64541c --- /dev/null +++ b/assets.js @@ -0,0 +1,244 @@ +// Modules +var + util = require('gulp-util'); + +// Assets +var + Assets = function() { + + // Options + this.options = { + groups: [ + // Assets + { + name: 'assets', + pattern: 'assets' + }, + // Symfony - App + { + name: 'app', + pattern: 'app/Resources/assets' + }, + // Symfony - Bundles + { + name: function(path) { + return path + .replace(/^src\//, '') + .replace(/\/Resources\/assets(.*)$/, '') + .replace(/Bundle/g, '') + .replace(/\//g, '') + 'Bundle'; + }, + pattern: 'src/**/*Bundle/Resources/assets' + } + ], + includes: [ + 'bower_components', + 'node_modules' + ], + dest: 'web/assets', + header: false, + assets: { + js: { + glob: '/**/*.js', + src: '/js', + dest: '/js', + vendor: {} + }, + sass: { + glob: '/**/*.scss', + src: '/sass', + dest: '/css', + vendor: {} + }, + images: { + glob: '/**', + src: '/images', + dest: '/images', + vendor: {} + }, + fonts: { + glob: '/**', + src: '/fonts', + dest: '/fonts', + vendor: {} + } + } + }; + + // Assets + this.assets = {}; + + // Header Meta + this.headerMeta = null; + + // Current asset group + this.group = util.env.group || null; + }; + +// Merges two (or more) objects, +// giving the last one precedence +function merge(target, source) { + if (typeof target !== 'object') { + target = {}; + } + for (var property in source) { + if (source.hasOwnProperty(property)) { + var + sourceProperty = source[property]; + + if (typeof sourceProperty === 'object') { + target[property] = merge(target[property], sourceProperty); + continue; + } + + target[property] = sourceProperty; + } + } + + for (var a = 2, l = arguments.length; a < l; a++) { + merge(target, arguments[a]); + } + + return target; +}; + +Assets.prototype = { + + // Configuration + config: function(options) { + this.options = merge(this.options, options); + }, + + // Get assets + get: function(assetType) { + var + glob = require('glob'), + path = require('path'); + + if (this.assets[assetType] === undefined) { + + // Initialize assets object + this.assets[assetType] = {}; + + // Search assets groups + this.options.groups.forEach(function(group) { + + glob.sync(group.pattern + this.options.assets[assetType].src).forEach(function(assetGroupPath) { + var + assetGroup = (typeof(group.name) == 'function') ? group.name(assetGroupPath) : group.name; + + this.assets[assetType][assetGroup] = { + src: path.resolve(assetGroupPath + this.options.assets[assetType].glob), + dest: this.getDest(assetType) + }; + + if (util.env.verbose) { + util.log( + 'Found', "'" + util.colors.cyan(assetGroup) + "'", + 'group', "'" + util.colors.grey(assetType) + "'", + 'at', util.colors.magenta(assetGroupPath + this.options.assets[assetType].glob) + ); + } + }.bind(this)); + }.bind(this)); + + // Add vendors + Object.keys(this.options.assets[assetType].vendor).forEach(function(assetGroup) { + + var + assetGroupSrc = [this.options.assets[assetType].vendor[assetGroup].src]; + + this.options.includes.forEach(function(include) { + assetGroupSrc.push(include + '/' + assetGroupSrc[0]); + }); + + assetGroupSrc.forEach(function(src) { + if (glob.sync(src).length) { + + this.assets[assetType][assetGroup] = { + src: path.resolve(src), + dest: this.getDest(assetType) + (this.options.assets[assetType].vendor[assetGroup].dest ? this.options.assets[assetType].vendor[assetGroup].dest : '') + }; + + if (util.env.verbose) { + util.log( + 'Vendor', "'" + util.colors.cyan(assetGroup) + "'", + 'group', "'" + util.colors.grey(assetType) + "'", + 'at', util.colors.magenta(src) + ); + } + } + }.bind(this)); + }.bind(this)); + } + + // Filter on current asset group + if (this.group) { + var + assets = {}; + + Object.keys(this.assets[assetType]).forEach(function(assetGroup) { + if (assetGroup == this.group) { + assets[assetGroup] = this.assets[assetType][assetGroup]; + } + }.bind(this)); + + return assets; + } + + return this.assets[assetType]; + }, + + // Set current asset group + setGroup: function(group) { + this.group = group; + }, + + // Find asset group by asset type and path + findGroup: function(assetType, path) { + var + minimatch = require('minimatch'), + group = null; + + Object.keys(this.assets[assetType]).forEach(function(assetGroup) { + if (minimatch(path, this.assets[assetType][assetGroup].src)) { + group = assetGroup; + } + }.bind(this)); + + return group; + }, + + // Get sources + getSrc: function(assetType) { + var + src = []; + + Object.keys(this.get(assetType)).forEach(function(assetGroup) { + src.push(this.get(assetType)[assetGroup].src); + }.bind(this)); + + return src; + }, + + // Get destination + getDest: function(assetType) { + return this.options.dest + this.options.assets[assetType].dest; + }, + + // Get header + getHeader: function() { + return this.options.header; + }, + + getHeaderMeta: function() { + if (this.headerMeta === null) { + this.headerMeta = require('../../package.json'); + this.headerMeta.date = new Date(); + } + + return this.headerMeta; + } +}; + +module.exports = new Assets(); diff --git a/index.js b/index.js new file mode 100644 index 0000000..9848ea3 --- /dev/null +++ b/index.js @@ -0,0 +1,8 @@ +// Assets +var + assets = require('./assets'); + +module.exports = assets; + +// Tasks +require('require-dir')('./tasks'); diff --git a/package.json b/package.json new file mode 100644 index 0000000..cbd10ef --- /dev/null +++ b/package.json @@ -0,0 +1,66 @@ +{ + "name": "elao-assets-gulp", + "description": "Handle your project's assets with style ! (and gulp)", + "version": "0.1.0", + "homepage": "https://github.com/Elao/node-module-assets-gulp", + "repository": { + "type": "git", + "url": "https://github.com/Elao/node-module-assets-gulp" + }, + "bugs": { + "url": "https://github.com/Elao/node-module-assets-gulp/issues" + }, + "author": { + "name": "Elao", + "email": "contact@elao.com", + "url": "http://www.elao.com/" + }, + "tags": [ + "tool", + "asset", + "gulp", + "symfony" + ], + "files": [ + "index.js", + "assets.js", + "tasks" + ], + "dependencies": { + "gulp-util": "3.0.*", + "gulp-load-plugins": "0.6.*", + "gulp-plumber": "0.6.*", + "gulp-streamify": "0.0.*", + "gulp-if": "1.2.*", + "gulp-changed": "1.0.*", + "gulp-size": "1.0.*", + "gulp-notify": "1.5.*", + "gulp-header": "1.0.*", + "require-dir": "0.1.*", + "event-stream": "3.1.*", + "through2": "0.6.*", + "glob": "4.0.*", + "glob2base": "0.0.*", + "vinyl-source-stream": "0.1.*", + "minimatch": "1.0.*", + "rimraf": "2.2.*", + + "gulp-sass": "0.7.*", + "gulp-scss-lint": "0.1.*", + + "gulp-imagemin": "1.0.*", + + "browserify": "5.10.*", + "watchify": "1.0.*", + "gulp-uglify": "0.3.*", + "gulp-jshint": "1.8.*", + "jshint-stylish": "0.4.*", + "gulp-jscs": "1.1.*" + }, + "licenses": [ + { + "type": "MIT", + "url": "https://raw.githubusercontent.com/Elao/node-module-assets-gulp/master/LICENSE" + } + ] +} diff --git a/tasks/fonts.js b/tasks/fonts.js new file mode 100644 index 0000000..e31af14 --- /dev/null +++ b/tasks/fonts.js @@ -0,0 +1,64 @@ +var + gulp = require('gulp'), + assets = require('..'); + +gulp.task('fonts', function() { + var + plugins = require('gulp-load-plugins')(), + eventStream = require('event-stream'), + tasks = []; + + Object.keys(assets.get('fonts')).forEach(function(assetGroup) { + tasks.push( + gulp.src(assets.get('fonts')[assetGroup].src) + .pipe(plugins.plumber()) + .pipe(plugins.if( + plugins.util.env.dev || false, + plugins.changed(assets.get('fonts')[assetGroup].dest) + )) + .pipe(plugins.if( + plugins.util.env.verbose || false, + plugins.size({showFiles: true}) + )) + .pipe(gulp.dest(assets.get('fonts')[assetGroup].dest)) + ); + }); + + return !tasks.length ? null : eventStream.merge.apply(this, tasks) + .pipe(plugins.if( + plugins.util.env.notify || false, + plugins.notify({ + title : 'Gulp - Success', + message : "\n" + 'fonts', + onLast : true + }) + )); +}); + +// Watch +gulp.task('watch:fonts', function() { + var + plugins = require('gulp-load-plugins')(); + + return gulp.watch(assets.getSrc('fonts'), ['fonts']) + .on('change', function(event) { + // Set current asset group + assets.setGroup(assets.findGroup('fonts', event.path)); + + // Log + if (plugins.util.env.verbose || false) { + plugins.util.log( + 'Watched', "'" + plugins.util.colors.cyan(event.path) + "'", + 'has', plugins.util.colors.magenta(event.type) + ); + } + }); +}); + +// Clean +gulp.task('clean:fonts', function(callback) { + var + rimraf = require('rimraf'); + + rimraf(assets.getDest('fonts'), callback); +}); diff --git a/tasks/images.js b/tasks/images.js new file mode 100644 index 0000000..19e82d7 --- /dev/null +++ b/tasks/images.js @@ -0,0 +1,68 @@ +var + gulp = require('gulp'), + assets = require('..'); + +gulp.task('images', function() { + var + plugins = require('gulp-load-plugins')(), + eventStream = require('event-stream'), + tasks = []; + + Object.keys(assets.get('images')).forEach(function(assetGroup) { + tasks.push( + gulp.src(assets.get('images')[assetGroup].src) + .pipe(plugins.plumber()) + .pipe(plugins.if( + plugins.util.env.dev || false, + plugins.changed(assets.get('images')[assetGroup].dest) + )) + .pipe(plugins.if( + !plugins.util.env.dev || false, + plugins.imagemin() + )) + .pipe(plugins.if( + plugins.util.env.verbose || false, + plugins.size({showFiles: true}) + )) + .pipe(gulp.dest(assets.get('images')[assetGroup].dest)) + ); + }); + + return !tasks.length ? null : eventStream.merge.apply(this, tasks) + .pipe(plugins.if( + plugins.util.env.notify || false, + plugins.notify({ + title : 'Gulp - Success', + message : "\n" + 'images', + onLast : true + }) + )); +}); + +// Watch +gulp.task('watch:images', function() { + var + plugins = require('gulp-load-plugins')(); + + return gulp.watch(assets.getSrc('images'), ['images']) + .on('change', function(event) { + // Set current asset group + assets.setGroup(assets.findGroup('images', event.path)); + + // Log + if (plugins.util.env.verbose || false) { + plugins.util.log( + 'Watched', "'" + plugins.util.colors.cyan(event.path) + "'", + 'has', plugins.util.colors.magenta(event.type) + ); + } + }); +}); + +// Clean +gulp.task('clean:images', function(callback) { + var + rimraf = require('rimraf'); + + rimraf(assets.getDest('images'), callback); +}); diff --git a/tasks/js.js b/tasks/js.js new file mode 100644 index 0000000..b5712df --- /dev/null +++ b/tasks/js.js @@ -0,0 +1,130 @@ +var + gulp = require('gulp'), + assets = require('..'); + +function bundle(asset, base, dest, watch) { + var + plugins = require('gulp-load-plugins')(), + browserify = require('browserify'), + watchify = require('watchify'), + bundler = browserify( + asset, { + debug: plugins.util.env.dev || false, + // Watchify + cache: {}, + packageCache: {}, + fullPaths: true + } + ), + transform = function() { + var + path = require('path'), + source = require('vinyl-source-stream'); + + return bundler + .bundle() + .pipe(plugins.plumber()) + .pipe(source(path.relative(base, asset))) + .pipe(plugins.if( + !plugins.util.env.dev || false, + plugins.streamify(plugins.uglify()) + )) + .pipe(plugins.streamify(plugins.header(assets.getHeader(), assets.getHeaderMeta()))) + .pipe(plugins.if( + plugins.util.env.verbose || false, + plugins.streamify(plugins.size({showFiles: true})) + )) + .pipe(gulp.dest(dest)); + }; + + if (watch) { + bundler = watchify(bundler); + // Rebundle with watchify on changes. + bundler.on('update', transform); + } + + return transform(); +} + +gulp.task('js', function() { + var + plugins = require('gulp-load-plugins')(), + glob = require('glob'), + glob2base = require('glob2base'), + eventStream = require('event-stream'), + tasks = []; + + Object.keys(assets.get('js')).forEach(function(assetGroup) { + var + globber = glob.Glob(assets.get('js')[assetGroup].src, {sync: true}), + base = glob2base(globber); + + globber.found.forEach(function(asset) { + tasks.push( + bundle( + asset, + base, + assets.get('js')[assetGroup].dest, + false + ) + ); + }); + }); + + return !tasks.length ? null : eventStream.merge.apply(this, tasks) + .pipe(plugins.if( + plugins.util.env.notify || false, + plugins.notify({ + title : 'Gulp - Success', + message : "\n" + 'js', + onLast : true + }) + )); +}); + +gulp.task('watch:js', function() { + var + plugins = require('gulp-load-plugins')(), + glob = require('glob'), + glob2base = require('glob2base'), + eventStream = require('event-stream'), + tasks = []; + + Object.keys(assets.get('js')).forEach(function(assetGroup) { + var + globber = glob.Glob(assets.get('js')[assetGroup].src, {sync: true}), + base = glob2base(globber); + + globber.found.forEach(function(asset) { + tasks.push( + bundle( + asset, + base, + assets.get('js')[assetGroup].dest, + true + ) + ); + }); + }); + + return !tasks.length ? null : eventStream.merge.apply(this, tasks); +}); + +// Lint +gulp.task('lint:js', function() { + var + plugins = require('gulp-load-plugins')(); + + return gulp.src(assets.getSrc('js')) + .pipe(plugins.jshint('app/Resources/jshint.json')) + .pipe(plugins.jshint.reporter('jshint-stylish')) + .pipe(plugins.jscs('app/Resources/jscs.json')); +}); + +// Clean +gulp.task('clean:js', function(callback) { + var + rimraf = require('rimraf'); + + rimraf(assets.getDest('js'), callback); +}); diff --git a/tasks/sass.js b/tasks/sass.js new file mode 100644 index 0000000..6403dd6 --- /dev/null +++ b/tasks/sass.js @@ -0,0 +1,79 @@ +var + gulp = require('gulp'), + assets = require('..'); + +gulp.task('sass', function() { + var + plugins = require('gulp-load-plugins')(), + eventStream = require('event-stream'), + tasks = []; + + Object.keys(assets.get('sass')).forEach(function(assetGroup) { + tasks.push( + gulp.src(assets.get('sass')[assetGroup].src) + .pipe(plugins.plumber()) + .pipe(plugins.sass({ + errLogToConsole: true, + includePaths: assets.options.includes, + outputStyle: (plugins.util.env.dev || false) ? 'nested' : 'compressed', + precision: 10, + sourceComments: (plugins.util.env.dev || false) ? 'map' : 'none' + })) + .pipe(plugins.header(assets.getHeader(), assets.getHeaderMeta())) + .pipe(plugins.if( + plugins.util.env.verbose || false, + plugins.size({showFiles: true}) + )) + .pipe(gulp.dest(assets.get('sass')[assetGroup].dest)) + ); + }); + + return !tasks.length ? null : eventStream.merge.apply(this, tasks) + .pipe(plugins.if( + plugins.util.env.notify || false, + plugins.notify({ + title : 'Gulp - Success', + message : "\n" + 'sass', + onLast : true + }) + )); +}); + +// Watch +gulp.task('watch:sass', function() { + var + plugins = require('gulp-load-plugins')(); + + return gulp.watch(assets.getSrc('sass'), ['sass']) + .on('change', function(event) { + // Set current asset group + assets.setGroup(assets.findGroup('sass', event.path)); + + // Log + if (plugins.util.env.verbose || false) { + plugins.util.log( + 'Watched', "'" + plugins.util.colors.cyan(event.path) + "'", + 'has', plugins.util.colors.magenta(event.type) + ); + } + }); +}); + +// Lint +gulp.task('lint:sass', function() { + var + plugins = require('gulp-load-plugins')(); + + return gulp.src(assets.getSrc('sass')) + .pipe(plugins.scssLint({ + config: 'app/Resources/scss-lint.yml' + })); +}); + +// Clean +gulp.task('clean:sass', function(callback) { + var + rimraf = require('rimraf'); + + rimraf(assets.getDest('sass'), callback); +});