├── .babelrc ├── .editorconfig ├── .eslintrc ├── .gitignore ├── .travis.yml ├── README.md ├── index.js ├── lib ├── image-path-transform.js ├── rev.js ├── sass-npm-importer.js └── testSetup.js ├── package.json ├── tasks ├── browserify.js ├── clean.js ├── copy.js ├── css-quality.js ├── extract.js ├── js-quality.js ├── optimise-svgs.js ├── responsive-images.js ├── retina-images.js ├── scss.js ├── small-sprite.js └── test-locales.js └── test ├── browserify.js ├── copy-and-clean.js ├── extract.js ├── fixtures ├── imgs │ ├── a-z-gallery-01-2560x800.jpg │ ├── a-z-gallery-02-2560x800.jpg │ ├── a-z-gallery-03-2560x800.jpg │ ├── sprite-out.css │ └── sprite-out.jpg ├── js │ ├── bad-es6.js │ ├── bad-es7.js │ ├── bad-import.js │ ├── bad-jscs.js │ ├── bad-jshint.js │ ├── env.js │ ├── factor-bundle │ │ ├── bundle-checkout.js │ │ ├── bundle-random.js │ │ ├── bundle.js │ │ └── lib.js │ ├── image-path.js │ ├── import.js │ ├── react.js │ ├── require.js │ ├── simple.babel.js │ ├── simple.js │ └── view.jsx ├── sass │ ├── bad-import.scss │ ├── fingerprint.scss │ ├── image-path.scss │ ├── import-npm.scss │ ├── import.scss │ ├── import2.scss │ ├── issue-14.scss │ ├── prefix.scss │ ├── rev-manifest.json │ ├── test-out.css │ ├── test-out.min-with-src.css │ ├── test-out.min.css │ ├── test.sass │ └── test.scss └── svg │ ├── logo-148x35-out.png │ ├── logo-148x35-out.svg │ └── logo-148x35.svg ├── js-quality.js ├── mocha.opts ├── optimise-svgs.js ├── rev.js ├── scss.js └── small-sprite.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["lmn"] 3 | } 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | indent_style = space 7 | indent_size = 2 -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": ["lmn"], 3 | "extends": "plugin:lmn/default" 4 | } 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | *.log 3 | test/fixtures/out 4 | /rev-manifest.json 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - '4' 5 | - '5' 6 | - '6' 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gulp Tasks [![Build Status](https://travis-ci.org/Lostmyname/lmn-gulp-tasks.svg?branch=master)](https://travis-ci.org/Lostmyname/lmn-gulp-tasks) 2 | 3 | A collection of generic configurable Gulp tasks used in various projects, 4 | themes and components at Lost My Name. They'll work in your projects, too! 5 | 6 | ## Install 7 | 8 | ``` 9 | $ npm install --save-dev lmn-gulp-tasks 10 | ``` 11 | 12 | ## Usage 13 | 14 | To use: 15 | 16 | ```js 17 | var gulp = require('gulp'); 18 | var getLmnTask = require('lmn-gulp-tasks'); 19 | 20 | gulp.task('js', getLmnTask('browserify', { 21 | src: './src/js/monkey.js', 22 | dest: './demo/build/bundle.js' 23 | })); 24 | ``` 25 | 26 | You can read why we're taking this approach of splitting Gulp into multiple 27 | files instead of the one detailed in the Gulp recipes in my article [here] 28 | (http://macr.ae/article/splitting-gulpfile-multiple-files.html). 29 | 30 | ## Handling errors 31 | 32 | Most of the tasks in this package have error handling built in so that Gulp 33 | doesn't explode if you miss a semi-colon from your JS. By default, the errors 34 | will be logged to the console and then ignored, but you can set your own 35 | handlers, either by changing the default handler, or by setting a handler per 36 | task. 37 | 38 | ### Changing the default handler 39 | 40 | ```js 41 | var getLmnTask = require('lmn-gulp-tasks'); 42 | 43 | getLmnTask.setErrorHandler(function (err) { 44 | console.log('OH NO CRAZY BAD THINGS ARE HAPPENING'); 45 | }); 46 | ``` 47 | 48 | ### Changing the handler per task 49 | 50 | ```js 51 | var gulp = require('gulp'); 52 | var getLmnTask = require('lmn-gulp-tasks'); 53 | 54 | gulp.task('js', getLmnTask('browserify', { 55 | src: './src/js/monkey.js', 56 | dest: './demo/build/bundle.js', 57 | onError: function (err) { 58 | console.log('The browserify task died!'); 59 | } 60 | })); 61 | ``` 62 | 63 | ## Revisioning assets 64 | 65 | A load of these tasks support revisioning of assets. There's an options called 66 | `rev` which should be boolean: 67 | 68 | ```js 69 | gulp.task('js', getLmnTask('browserify', { 70 | src: './src/js/monkey.js', 71 | dest: './demo/build/bundle.js', 72 | rev: true 73 | })); 74 | ``` 75 | 76 | That'll create a fingerprinted version of the file, and write the path to a 77 | manifest file. The location of the manifest file is by default the root 78 | directory, but can be specified using the `manifest` options, which in turn 79 | defaults to `process.env.MANIFEST_DEST`. 80 | 81 | `rev` defaults to `process.env.NODE_ENV === 'production'`. 82 | 83 | **Tasks that support revisioning are marked with asterisks.** 84 | 85 | 86 | ## Tasks 87 | 88 | ### browserify* 89 | 90 | We use browserify on nearly all of our JavaScript at Lost My Name. This very 91 | simple task just runs browserify on your input file: 92 | 93 | 94 | ```js 95 | gulp.task('js', getLmnTask('browserify', { 96 | src: './src/js/monkey.js', 97 | dest: './demo/build/bundle.js', 98 | minify: false 99 | })); 100 | ``` 101 | 102 | `minify` defaults to true: omitting the option will result in the resulting JS 103 | being minified. In addition to minifying, the `minify` option will call 104 | strip-debug, which strips `console`, `alert` and `debugger` statements. 105 | 106 | This will remove multiple versions of jQuery and include the version from the 107 | package in question. If you don't want jQuery, specify `jquery: false`. If you 108 | want multiple versions of jQuery: you can't do that, weirdo. 109 | 110 | There's also some react stuff built in: set `react` to true to enable the JSX 111 | parser, and set `hotModuleReloading` to enable hot module reloading (note that 112 | `watch` also needs to be true). 113 | 114 | #### factor-bundle 115 | 116 | Finally, we've added support for factor-bundle so that you can make other files 117 | with the dependencies in the main bundle still. This is good for performance! 118 | Configure it using the extras option. 119 | 120 | ```js 121 | gulp.task('js', getLmnTask('browserify', { 122 | src: './src/js/monkey.js', 123 | dest: './demo/build/bundle.js', 124 | minify: false, 125 | extras: [ 126 | { src: './src/js/monkey-extras.js', dest: './demo/build/bundle-extras.js' } 127 | ] 128 | })); 129 | ``` 130 | 131 | You need to `require('./monkey')` from monkey-extras.js, but then when you load 132 | bundle.js before bundle-extras.js and it will act as if they're in one file. 133 | 134 | ### clean 135 | 136 | This task deletes stuff. `src` can be either a string or an array. 137 | 138 | ```js 139 | gulp.task('clean', getLmnTask('clean', { 140 | src: 'deletethis.json' 141 | })); 142 | ``` 143 | 144 | ### copy* 145 | 146 | Literally just copies stuff from one place to another, and can fingerprint it 147 | if necessary. 148 | 149 | Have the option to flatten the directory structure if needed. 150 | 151 | ``` 152 | gulp.task('move-favicon', loadLmnTask('copy', { 153 | src: './assets/favicon.ico', 154 | dest: './demo/build', 155 | rev: false, 156 | flatten: false 157 | })); 158 | ``` 159 | 160 | ### extract* 161 | 162 | This task is used to extract assets (or anything else, for that matter) from 163 | modules stored in a node_modules directory somewhere. 164 | 165 | ```js 166 | gulp.task('getMarkdown', loadLmnTask('extract', { 167 | module: 'my-module', 168 | src: '/src/markdown/**/*.md', 169 | dest: 'markdown' 170 | })); 171 | ``` 172 | 173 | That will extract everything matching that path inside the first module 174 | matching `my-module`. 175 | 176 | ### html 177 | 178 | Useful in LMN components only, probably. Takes faux-erb files and turns them 179 | into HTML. 180 | 181 | ```js 182 | gulp.task('html', getLmnTask('html', { 183 | langBase: 'component.monkey', 184 | imagePath: '../imgs', 185 | context: { 186 | foo: 'bar' // Will be accessible in the template files 187 | } 188 | })); 189 | ``` 190 | 191 | ### js-quality 192 | 193 | This task runs ESLint on your JavaScript, and optionally stops Gulp if an 194 | error is found. 195 | 196 | ```js 197 | gulp.task('js-quality', getLmnTask('js-quality', { 198 | src: './src/js/**/*.js', 199 | dieOnError: true 200 | })); 201 | ``` 202 | 203 | `dieOnError` defaults to false, so if you miss that option out, Gulp will not 204 | die. 205 | 206 | Prior to 2.0.0, lmn-gulp-task used JSHint, JSCS, and Buddy.js. 207 | 208 | ### css-quality 209 | 210 | This task runs stylelint on your CSS, and stops Gulp if an 211 | error is found. 212 | 213 | ```js 214 | gulp.task('css-quality', getLmnTask('css-quality', { 215 | src: './src/**/*.scss' 216 | })); 217 | ``` 218 | 219 | ### optimise-svgs* 220 | 221 | This task gets svgs, optionally flattens the directory structure, optimises 222 | the svgs and makes optimised png fallbacks for browsers that don't support svg. 223 | 224 | ```js 225 | gulp.task('optimise-svgs', loadLmnTask('optimise-svgs', { 226 | src: './src/images/**/*.svg', 227 | dest: buildPath + 'images', 228 | flatten: true // Defaults to false 229 | })); 230 | ``` 231 | 232 | ### responsive-images* 233 | 234 | The responsive-images task is pretty big. It handles turning images like 235 | some-image-500x50@2x.png into some-image-xlarge.png, some-image-large.png, 236 | etc. 237 | 238 | ```js 239 | gulp.task('responsive-images', loadLmnTask('responsive-images', { 240 | src: './src/images/**/*.{png,jpg,gif}', 241 | dest: buildPath + 'images', 242 | lossless: function (file) { 243 | return _.contains(file.path, 'hero'); 244 | }, 245 | flatten: true // defaults to false 246 | })); 247 | ``` 248 | 249 | This is an option called `retinaSrc`, but you might not need to specify it: 250 | the task will attempt to calculate it by replacing `*.{` with `*@2x*.{`: for 251 | example, if your `src` is `./img/**/*.{png,jpg}`, your generated retinaSrc will 252 | be `./img/**/*@2x*.{png,jpg}`. 253 | 254 | The `lossless` option should be either a function or a boolean value, and is 255 | used to tell whether to compress the image losslessly or not. It defaults to 256 | false, and imagemin will use `jpegoptim({ max: 80 })`. It's a big reduction in 257 | file size, and it's not noticeable unless you look hard. You can see in the 258 | example code above that we compress everything but the header images using a 259 | lossy compression. 260 | 261 | There are also two options called `skipResize` and `skipOptimize` that you want 262 | to set true only on the certain situation. For example, they would be useful when 263 | you run integration tests on CI. It skips resizing but copies images with the 264 | publishing names. Both are false in default. 265 | 266 | ### scss* 267 | 268 | The scss tasks runs compass on one or more specified SCSS files, then runs 269 | autoprefixer on the resulting CSS, then optionally minifies it. 270 | 271 | ```js 272 | gulp.task('scss', getLmnTask('scss', { 273 | src: './src/scss/*.{sass,scss}', 274 | dest: './demo/build', 275 | includePaths: [], 276 | minify: false 277 | })); 278 | ``` 279 | 280 | `minify` defaults to true: omitting the option will result in the resulting CSS 281 | being minified. 282 | 283 | The task, in addition to passing through any include paths you give it, will 284 | pass in the output of [find-node-modules], making it easier to include SCSS 285 | from modules from npm. Using ../node_modules is an anti-pattern, as explained 286 | below! To disable this behaviour, set `includePaths` to `false`. 287 | 288 | #### Checking for ../node_modules 289 | 290 | The problem with writing "../node_modules" is that there is no guarantee that 291 | the module will actually be installed there. It could have been installed a 292 | directory up, in which case it won't be installed again below. 293 | 294 | There should never be any need to write "../node_modules", as the import paths 295 | are set for you. Therefore, it should be considered an antipattern, and this 296 | task will throw an error if you write "../node_modules" anywhere in your Sass. 297 | 298 | If you really want to use this anti-pattern, you can set the 299 | `ignoreSuckyAntipattern` option to true and the task won't check your code. 300 | 301 | ### small-sprite 302 | 303 | This task generates a sprite sheet from large images, shrinking them in the 304 | process. Good for book previews and New Zealand tourist websites. 305 | 306 | ```js 307 | gulp.task('a-z-images', getTask('small-sprite', { 308 | src: './src/components/garousel/images/*.jpg', 309 | imgDest: './src/components/garousel/azbook.jpg', 310 | cssDest: './src/components/garousel/_generated.scss' 311 | })); 312 | ``` 313 | 314 | I'd recommend committing the generated image and scss, and not committing the 315 | source images unless they're already in the repo—this task doesn't need 316 | running unless the images change, and it takes a couple seconds to run, which 317 | then needs combining with the scss task. You get the picture. 318 | 319 | ### test-locales 320 | 321 | This searches for localised files and ensures that if a file is localised, it 322 | is localised in every language: 323 | 324 | ```js 325 | gulp.task('test-locales', getLmnTask('test-locales', { 326 | src: './src/images/**/*.{png,jpg,gif}', 327 | locales: ['en-US', 'de', 'en-GB'] 328 | })); 329 | ``` 330 | 331 | 332 | ## License 333 | 334 | This project is released under the MIT license. 335 | 336 | [find-node-modules]: https://github.com/callumacrae/find-node-modules 337 | [buddy.js]: https://github.com/danielstjules/buddy.js 338 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | // Set up environmental vars from .env 4 | require('dotenv').load({ silent: true }); 5 | 6 | var browserSync = require('browser-sync'); 7 | var vinyl = require('vinyl-fs'); 8 | var plugins = require('gulp-load-plugins')(); 9 | 10 | // Default error handler. Sends to browser-sync, and logs to console. 11 | var errorHandler = function (err) { 12 | browserSync.notify(err.message, 3000); 13 | plugins.util.log(err.toString()); 14 | 15 | if (process.argv.indexOf('--fail') !== -1) { 16 | throw new Error('Failed'); 17 | } 18 | }; 19 | 20 | /** 21 | * Get a task. This function just gets a task from the tasks directory, and 22 | * sets some sane default options used on all tasks such as the error handler 23 | * and the `rev` option. 24 | * 25 | * @param {string} name The name of the task. 26 | * @param {object} [options] Options to pass to the task. 27 | * @returns {function} The task! 28 | */ 29 | module.exports = function getTask(name, options) { 30 | if (typeof options !== 'object') { 31 | options = {}; 32 | } 33 | 34 | if (typeof options.onError !== 'function') { 35 | options.onError = errorHandler; 36 | } 37 | 38 | if (typeof options.rev !== 'boolean') { 39 | options.rev = (process.env.NODE_ENV === 'production'); 40 | } 41 | 42 | if (typeof options.manifest !== 'string') { 43 | options.manifest = process.env.MANIFEST_DEST; 44 | } 45 | 46 | // This means that you don't have to call this.emit('end') yourself 47 | var actualErrorHandler = options.onError; 48 | options.onError = function () { 49 | actualErrorHandler.apply(this, arguments); 50 | this.emit('end'); 51 | }; 52 | 53 | return require('./tasks/' + name)(vinyl, plugins, options); 54 | }; 55 | 56 | /** 57 | * Set default error handler. 58 | * 59 | * @param newHandler 60 | */ 61 | module.exports.setErrorHandler = function (newHandler) { 62 | errorHandler = newHandler; 63 | }; 64 | -------------------------------------------------------------------------------- /lib/image-path-transform.js: -------------------------------------------------------------------------------- 1 | var transformTools = require('browserify-transform-tools'); 2 | 3 | module.exports = function (options) { 4 | return transformTools.makeFalafelTransform('image-path', { 5 | excludeExtensions: ['json'] 6 | }, transform); 7 | 8 | function transform(node, transformOptions, done) { 9 | if (node.type === 'CallExpression' && node.callee.name === 'imagePath') { 10 | var filename = node.arguments[0].value; 11 | var newFilename = options.resolvePath(filename, options.assetManifest); 12 | node.update("'" + newFilename + "'"); 13 | } 14 | 15 | done(); 16 | } 17 | }; 18 | -------------------------------------------------------------------------------- /lib/rev.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var multipipe = require('multipipe'); 5 | var revDel = require('rev-del'); 6 | var through = require('through2'); 7 | 8 | /** 9 | * This is used in all the tasks that support revisioning: they just pipe to 10 | * the stream returned by this function, and that handles the revisioning, 11 | * the creation of a manifest file, and the deletion of old assets. 12 | * 13 | * To use: 14 | * 15 | * .pipe(rev(vinyl, plugins, options)) 16 | */ 17 | module.exports = function (vinyl, plugins, options) { 18 | var manifestSpecified = !!options.manifest; 19 | if (!options.manifest) { 20 | options.manifest = process.cwd(); 21 | } 22 | 23 | var manifest = path.join(options.manifest, 'rev-manifest.json'); 24 | 25 | return options.rev ? multipipe( 26 | plugins.clone(), 27 | plugins.rev(), 28 | vinyl.dest(options.dest), 29 | plugins.rev.manifest(manifest, { 30 | base: manifestSpecified ? options.manifest : undefined, 31 | merge: true 32 | }), 33 | revDel({ force: true, dest: options.manifest }), 34 | vinyl.dest(options.manifest) 35 | ) : through.obj(); 36 | }; 37 | -------------------------------------------------------------------------------- /lib/sass-npm-importer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var findup = require('findup-sync'); 5 | 6 | var cache = {}; 7 | 8 | function sassNpmImporter(url, prev, done) { 9 | url = url.replace('~chameleon', 'chameleon-sass/assets/stylesheets'); 10 | 11 | // Fall back to old URL 12 | var newUrl = url; 13 | 14 | try { 15 | if (cache[url]) { 16 | newUrl = cache[url]; 17 | return; 18 | } 19 | 20 | if (!/^(?:@[^/]+\/)?[^/]+$/.test(url)) { 21 | return; 22 | } 23 | 24 | var modulePath = findup(path.join('node_modules', url), { 25 | cwd: path.dirname(prev), 26 | nocase: true 27 | }); 28 | 29 | var moduleJson = require(path.join(modulePath, 'package.json')); 30 | newUrl = path.join(modulePath, moduleJson.mainSass); 31 | } catch (e) { 32 | // Ignore error 33 | } finally { 34 | cache[url] = newUrl; 35 | done({ file: newUrl }); 36 | } 37 | } 38 | 39 | module.exports = sassNpmImporter; 40 | -------------------------------------------------------------------------------- /lib/testSetup.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | var del = require('del'); 5 | var vinyl = require('vinyl-fs'); 6 | var getLmnTask = require('../'); 7 | var vfsFake = require('vinyl-fs-fake'); 8 | 9 | vinyl.src = vfsFake.src; 10 | vinyl.dest = vfsFake.dest; 11 | 12 | // Throw them for should.js 13 | getLmnTask.setErrorHandler(function (err) { 14 | throw err; 15 | }); 16 | 17 | global.getFile = function getFile(name, slice) { 18 | var buffer = fs.readFileSync(name); 19 | 20 | if (slice !== false) { 21 | buffer = buffer.slice(0, -1); 22 | } 23 | 24 | return buffer; 25 | }; 26 | 27 | global.clean = function clean() { 28 | return del(['test/fixtures/out', 'rev-manifest.json']); 29 | }; 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lmn-gulp-tasks", 3 | "version": "13.0.0", 4 | "description": "Generic Gulp tasks used in Lost My Name components and projects", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha" 8 | }, 9 | "engines": { 10 | "node": ">=4" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/Lostmyname/lmn-gulp-tasks" 15 | }, 16 | "keywords": [ 17 | "gulp" 18 | ], 19 | "author": "Callum Macrae ", 20 | "license": "MIT", 21 | "bugs": { 22 | "url": "https://github.com/Lostmyname/lmn-gulp-tasks/issues" 23 | }, 24 | "homepage": "https://github.com/Lostmyname/lmn-gulp-tasks", 25 | "dependencies": { 26 | "@lostmyname/css": "^0.2.2", 27 | "babel-preset-react": "^6.3.13", 28 | "babelify": "^7.3.0", 29 | "browser-sync": "^2.0.1", 30 | "browserify": "^13.0.1", 31 | "browserify-transform-tools": "^1.6.0", 32 | "del": "^2.2.0", 33 | "dotenv": "^2.0.0", 34 | "envify": "^3.4.1", 35 | "factor-bundle": "^2.5.0", 36 | "find-node-modules": "^1.0.1", 37 | "findup-sync": "^0.4.1", 38 | "gm": "^1.22.0", 39 | "gulp-autoprefixer": "^3.1.0", 40 | "gulp-changed": "^1.1.1", 41 | "gulp-clean-css": "^2.0.10", 42 | "gulp-clone": "^1.0.0", 43 | "gulp-contains": "^1.1.0", 44 | "gulp-eslint": "^3.0.1", 45 | "gulp-fingerprint": "^0.3.2", 46 | "gulp-if": "^2.0.1", 47 | "gulp-imagemin": "^2.1.0", 48 | "gulp-load-plugins": "^1.2.4", 49 | "gulp-plumber": "^1.1.0", 50 | "gulp-rename": "^1.2.2", 51 | "gulp-rev": "^7.1.0", 52 | "gulp-sass": "^2.3.2", 53 | "gulp-sourcemaps": "^1.6.0", 54 | "gulp-strip-debug": "^1.1.0", 55 | "gulp-stylelint": "^3.4.0", 56 | "gulp-uglify": "^1.5.3", 57 | "gulp-util": "^3.0.2", 58 | "gulp.spritesmith": "^4.3.0", 59 | "imagemin-jpegoptim": "^4.0.0", 60 | "livereactload": "^3.0.0", 61 | "lodash": "^4.13.1", 62 | "merge-stream": "^1.0.0", 63 | "multipipe": "^0.3.1", 64 | "resolve": "^1.1.7", 65 | "rev-del": "^1.0.5", 66 | "through2": "^2.0.1", 67 | "vinyl-buffer": "^1.0.0", 68 | "vinyl-fs": "^2.4.3", 69 | "vinyl-paths": "^2.1.0", 70 | "vinyl-source-stream": "^1.1.0", 71 | "watchify": "^3.7.0" 72 | }, 73 | "devDependencies": { 74 | "babel-plugin-react-transform": "^2.0.0-beta1", 75 | "babel-preset-es2015": "^6.5.0", 76 | "babel-preset-lmn": "^1.0.0", 77 | "babel-preset-react": "^6.11.0", 78 | "babel-register": "^6.4.3", 79 | "eslint": "^3.7.1", 80 | "eslint-plugin-lmn": "^1.0.4", 81 | "find-node-modules": "^1.0.3", 82 | "gulp-debug": "^2.1.2", 83 | "hook-stdio": "^1.0.0", 84 | "jquery": "^2.1.4", 85 | "mocha": "^2.5.3", 86 | "react": "^0.14.6", 87 | "react-proxy": "^1.1.2", 88 | "should": "^9.0.2", 89 | "vinyl-fs-fake": "^1.0.0" 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /tasks/browserify.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var os = require('os'); 4 | var path = require('path'); 5 | var fs = require('fs'); 6 | var babelify = require('babelify'); 7 | var browserify = require('browserify'); 8 | var envify = require('envify'); 9 | var source = require('vinyl-source-stream'); 10 | var buffer = require('vinyl-buffer'); 11 | var through = require('through2'); 12 | var resolve = require('resolve'); 13 | var _ = require('lodash'); 14 | var watchify = require('watchify'); 15 | var rev = require('../lib/rev'); 16 | var imagePathify = require('../lib/image-path-transform'); 17 | 18 | module.exports = function (vinyl, plugins, options) { 19 | options = _.clone(options); 20 | 21 | var basename = path.basename(options.dest); 22 | var dirname = options.dest = path.dirname(options.dest); 23 | 24 | return function browserifyTask() { 25 | if (typeof options.minify !== 'boolean') { 26 | options.minify = process.env.MINIFY_ASSETS || false; 27 | } 28 | 29 | if (typeof options.sourcemaps !== 'boolean') { 30 | options.sourcemaps = process.env.SOURCEMAPS || true; 31 | } 32 | 33 | var ignore = options.ignoreSuckyAntipattern; 34 | 35 | var opts = { 36 | debug: options.sourcemaps, 37 | ignore: ['jquery'] 38 | }; 39 | 40 | if (options.watch) { 41 | opts = _.assign({}, watchify.args, opts); 42 | } 43 | 44 | var bundler = browserify(opts); 45 | 46 | if (options.watch) { 47 | bundler = watchify(bundler); 48 | bundler.on('update', bundle); 49 | bundler.on('log', function (msg) { 50 | console.log('Browserify:', msg); 51 | }); 52 | } 53 | 54 | var babelPlugins = []; 55 | 56 | if (options.react && options.watch && options.hotModuleReloading) { 57 | babelPlugins.push(['react-transform', { 58 | transforms: [{ 59 | transform: 'livereactload/babel-transform', 60 | imports: ['react'] 61 | }] 62 | }]); 63 | 64 | bundler.plugin(require('livereactload')) 65 | } 66 | 67 | bundler.transform(babelify.configure({ 68 | presets: ['lmn'].concat(options.react ? ['react'] : []), 69 | plugins: babelPlugins, 70 | ignore: /jquery\-browserify\.js/ 71 | })); 72 | 73 | bundler.transform(envify); 74 | 75 | if (options.disableImagePath !== true) { 76 | if (!options.assetManifest) { 77 | try { 78 | var manifestPath = path.join(options.manifest || '', 'rev-manifest.json'); 79 | var manifest = fs.readFileSync(manifestPath); 80 | options.assetManifest = JSON.parse(manifest.toString()); 81 | } catch (e) { 82 | if (e.message.indexOf('ENOENT') === -1) { 83 | throw e; 84 | } 85 | 86 | options.assetManifest = {}; 87 | } 88 | } 89 | 90 | if (!options.resolvePath) { 91 | options.resolvePath = function (filename, manifest) { 92 | return manifest[filename] || filename; 93 | }; 94 | } 95 | 96 | bundler.transform(imagePathify(options)); 97 | } 98 | 99 | // Add local jQuery only, if it exists 100 | if (options.jquery !== false) { 101 | try { 102 | var res = resolve.sync('jquery', { basedir: process.cwd() }); 103 | var stream = fs.createReadStream(res); 104 | stream.file = 'jquery-browserify.js'; 105 | bundler.require(stream, { expose: 'jquery' }); 106 | } catch (e) { 107 | if (e.message.indexOf('Cannot find module') !== -1) { 108 | console.log('jQuery couldn\'t be loaded, but that\'s okay'); 109 | } else { 110 | throw e; 111 | } 112 | } 113 | } 114 | 115 | bundler.add(options.src); 116 | 117 | if (options.extras && options.extras.length) { 118 | // factor-bundle isn't designed to do this, so the first file is junk 119 | var outputs = [path.join(os.tmpdir(), 'junk.js')]; 120 | 121 | options.extras.forEach(function (extra) { 122 | bundler.add(extra.src); 123 | outputs.push(extra.dest); 124 | 125 | // factor-bundle errors if the directory doesn't exist 126 | var dir = path.dirname(extra.dest); 127 | if (!fs.existsSync(dir)){ 128 | fs.mkdirSync(dir); 129 | } 130 | }); 131 | 132 | bundler.plugin('factor-bundle', { outputs: outputs }); 133 | } 134 | 135 | function bundle() { 136 | console.log('Browserify: Bundling'); 137 | 138 | return bundler.bundle() 139 | .on('error', options.onError) 140 | .pipe(source(basename)) 141 | .pipe(plugins.plumber({ errorHandler: options.onError })) 142 | .pipe(buffer()) 143 | .pipe(ignore ? through.obj() : plugins.contains('../node_modules')) 144 | .pipe(options.sourcemaps ? plugins.sourcemaps.init({ loadMaps: true }) : through.obj()) 145 | 146 | // Sourcemaps start 147 | .pipe(options.minify ? plugins.uglify() : through.obj()) 148 | .pipe(options.minify ? plugins.stripDebug() : through.obj()) 149 | // Sourcemaps end 150 | 151 | .pipe(options.sourcemaps ? plugins.sourcemaps.write('./') : through.obj()) 152 | .pipe(vinyl.dest(dirname)) 153 | .pipe(rev(vinyl, plugins, options)); 154 | } 155 | 156 | return bundle(); 157 | }; 158 | }; 159 | -------------------------------------------------------------------------------- /tasks/clean.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var del = require('del'); 4 | 5 | module.exports = function (vinyl, plugins, options) { 6 | return function cleanTask(cb) { 7 | del([].concat(options.src)) 8 | .then(function (files) { 9 | cb(null, files); 10 | }) 11 | .catch(cb); 12 | }; 13 | }; 14 | -------------------------------------------------------------------------------- /tasks/copy.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var rev = require('../lib/rev'); 4 | var through = require('through2'); 5 | 6 | module.exports = function (vinyl, plugins, options) { 7 | return function copyTask() { 8 | return vinyl.src(options.src) 9 | .pipe(plugins.plumber({ errorHandler: options.onError })) 10 | .pipe(plugins.changed(options.dest, { 11 | hasChanged: plugins.changed.compareSha1Digest 12 | })) 13 | .pipe(options.flatten ? plugins.rename({ dirname: '' }) : through.obj()) 14 | .pipe(vinyl.dest(options.dest)) 15 | .pipe(rev(vinyl, plugins, options)); 16 | }; 17 | }; 18 | -------------------------------------------------------------------------------- /tasks/css-quality.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function (vinyl, plugins, options) { 4 | return function cssQualityTask() { 5 | // Default to false 6 | if (options.dieOnError !== true) { 7 | options.dieOnError = false; 8 | } 9 | 10 | return vinyl.src(options.src) 11 | .pipe(plugins.stylelint({ 12 | failAfterError: options.dieOnError, 13 | reporters: [ 14 | { formatter: 'string', console: true } 15 | ] 16 | })); 17 | }; 18 | }; 19 | -------------------------------------------------------------------------------- /tasks/extract.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var findup = require('findup-sync'); 5 | var rev = require('../lib/rev'); 6 | 7 | module.exports = function (vinyl, plugins, options) { 8 | return function () { 9 | var module = findup('node_modules/' + options.module); 10 | if (!module) { 11 | throw new Error('Module ' + options.module + ' not found'); 12 | } 13 | 14 | return vinyl.src(path.join(module, options.src)) 15 | .pipe(plugins.plumber({ errorHandler: options.onError })) 16 | .pipe(plugins.rename({ dirname: '' })) 17 | .pipe(vinyl.dest(options.dest)) 18 | .pipe(rev(vinyl, plugins, options)); 19 | }; 20 | }; 21 | -------------------------------------------------------------------------------- /tasks/js-quality.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var through = require('through2'); 4 | 5 | module.exports = function (vinyl, plugins, options) { 6 | return function jsQualityTask() { 7 | // Default to false 8 | if (options.dieOnError !== true) { 9 | options.dieOnError = process.argv.indexOf('--fail') !== -1; 10 | } 11 | 12 | return vinyl.src(options.src) 13 | .pipe(plugins.eslint()) 14 | .pipe(plugins.eslint.format()) 15 | .pipe(options.dieOnError ? plugins.eslint.failOnError() : through.obj()); 16 | }; 17 | }; 18 | -------------------------------------------------------------------------------- /tasks/optimise-svgs.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var through = require('through2'); 4 | var rev = require('../lib/rev'); 5 | 6 | module.exports = function (vinyl, plugins, options) { 7 | return function () { 8 | // Optimise SVGs 9 | var svgStream = vinyl 10 | .src(options.src) 11 | .pipe(plugins.plumber({ errorHandler: options.onError })) 12 | .pipe(options.flatten ? plugins.rename({ dirname: '' }) : through.obj()) 13 | .pipe(plugins.changed(options.dest)) 14 | .pipe( 15 | plugins.imagemin({ 16 | svgoPlugins: [{ removeViewBox: false }, { removeDoctype: true }, { removeXMLProcInst: true }] 17 | }) 18 | ) 19 | .pipe(vinyl.dest(options.dest)) 20 | .pipe(rev(vinyl, plugins, options)); 21 | return svgStream; 22 | }; 23 | }; 24 | -------------------------------------------------------------------------------- /tasks/responsive-images.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('lodash'); 4 | var jpegoptim = require('imagemin-jpegoptim'); 5 | var mergeStream = require('merge-stream'); 6 | var rev = require('../lib/rev'); 7 | var through = require('through2'); 8 | var gm = require('gm').subClass({ imageMagick: true }); 9 | var path = require('path'); 10 | 11 | 12 | module.exports = function (vinyl, plugins, options) { 13 | return function () { 14 | var images = vinyl.src(options.src) 15 | .pipe(plugins.plumber({ errorHandler: options.onError })); 16 | var imageTasks = []; 17 | 18 | // Special case for retina images 19 | var stream = vinyl.src(options.retinaSrc || getRetinaSrc(options.src)) 20 | .pipe(plugins.plumber({ errorHandler: options.onError })) 21 | .pipe(handleRename('-xlarge')) 22 | .pipe(handleChanged()) 23 | .pipe(plugins.if(!options.skipOptimize, handleOptimize())) 24 | .pipe(vinyl.dest(options.dest)); 25 | 26 | imageTasks.push(stream); 27 | 28 | var sizes = options.sizes || { large: 100, medium: 66.7, small: 50 }; 29 | 30 | // Deal with each size separately 31 | _.each(sizes, function (factor, suffix) { 32 | var stream = images.pipe(plugins.clone()) 33 | .pipe(through.obj(function (file, enc, cb) { 34 | file.originalPath = file.path; 35 | cb(null, file); 36 | })) 37 | .pipe(handleRename('-' + suffix)) 38 | .pipe(handleChanged()) 39 | .pipe(plugins.if(!options.skipResize, handleResize(factor))) 40 | .pipe(plugins.if(!options.skipOptimize, handleOptimize())) 41 | .pipe(vinyl.dest(options.dest)); 42 | 43 | imageTasks.push(stream); 44 | }); 45 | 46 | return mergeStream.apply(this, imageTasks) 47 | .pipe(rev(vinyl, plugins, options)); 48 | }; 49 | 50 | // Only handle images that need handling. Should be ran *after* handleRename 51 | function handleChanged() { 52 | return plugins.changed(options.dest, { 53 | hasChanged: function (stream, cb, file, destPath) { 54 | var manifestPath = options.manifest || process.cwd(); 55 | var manifest = require(path.join(manifestPath, 'rev-manifest.json')); 56 | 57 | destPath = destPath.replace(/\d+[x-]\d+(?:@2x)?(?=(?:-[a-z]{2}(?:-[A-Z]{2})?)?\.[a-z]+$)/, 'small'); 58 | 59 | if (!manifest[path.basename(destPath)]) { 60 | stream.push(file); 61 | cb(); 62 | } else { 63 | plugins.changed.compareLastModifiedTime(stream, cb, file, destPath); 64 | } 65 | } 66 | }); 67 | } 68 | 69 | // Rename the image from something like file-10x10.jpg to file-small.jpg 70 | function handleRename(suffix) { 71 | return plugins.rename(function (path) { 72 | var matches = /^(.+)\-\d+[x-]\d+(?:@2x)?(?:(-[a-z]{2}(?:-[A-Z]{2})?))?$/.exec(path.basename); 73 | 74 | if (!matches) { 75 | var error = 'Failed to parse file name: ' + path.basename; 76 | throw new plugins.util.PluginError('lmn-gulp-tasks', error); 77 | } 78 | 79 | path.basename = matches[1] + suffix + (matches[2] || ''); 80 | if (options.flatten) { 81 | path.dirname = ''; 82 | } 83 | }); 84 | } 85 | 86 | // Use gulp-gm to resize the image 87 | function handleResize(factor) { 88 | return through.obj(function (file, enc, cb) { 89 | var newFactor = _.includes(file.originalPath, '@2x') ? factor / 2 : factor; 90 | 91 | if (newFactor === 100) { 92 | return cb(null, file); 93 | } 94 | 95 | gm(file.contents, file.path) 96 | .resize(newFactor, newFactor, '%') 97 | .toBuffer(function (err, buffer) { 98 | if (err) { 99 | return cb(err); 100 | } 101 | 102 | file.contents = buffer; 103 | cb(null, file); 104 | }); 105 | }); 106 | } 107 | 108 | // Compress images 109 | function handleOptimize() { 110 | function losslessTest(file) { 111 | if (_.isFunction(options.lossless)) { 112 | return options.lossless(file); 113 | } 114 | 115 | return options.lossless; 116 | } 117 | 118 | var lossyStream = plugins.imagemin({ use: [jpegoptim({ max: 80 })] }); 119 | var losslessStream = plugins.imagemin({ progressive: true }); 120 | 121 | return plugins.if(losslessTest, losslessStream, lossyStream); 122 | } 123 | 124 | // Work out src for retina images 125 | function getRetinaSrc(src) { 126 | if (_.isArray(src)) { 127 | return _.map(src, getRetinaSrc); 128 | } 129 | 130 | return src.replace('*.{', '*@2x*.{'); 131 | } 132 | }; 133 | -------------------------------------------------------------------------------- /tasks/retina-images.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _ = require('lodash'); 4 | var jpegoptim = require('imagemin-jpegoptim'); 5 | var mergeStream = require('merge-stream'); 6 | var rev = require('../lib/rev'); 7 | var through = require('through2'); 8 | var gm = require('gm').subClass({ imageMagick: true }); 9 | var path = require('path'); 10 | 11 | 12 | module.exports = function (vinyl, plugins, options) { 13 | return function () { 14 | var images = vinyl.src(options.src) 15 | .pipe(plugins.plumber({ errorHandler: options.onError })); 16 | var imageTasks = []; 17 | 18 | // Special case for retina images 19 | var stream = vinyl.src(options.retinaSrc || getRetinaSrc(options.src)) 20 | .pipe(plugins.plumber({ errorHandler: options.onError })) 21 | .pipe(handleRename('@2x')) 22 | .pipe(handleChanged()) 23 | .pipe(plugins.if(!options.skipOptimize, handleOptimize())) 24 | .pipe(vinyl.dest(options.dest)); 25 | 26 | imageTasks.push(stream); 27 | 28 | var sizes = options.sizes || { large: 100 }; 29 | 30 | // Deal with each size separately 31 | _.each(sizes, function (factor) { 32 | var stream = images.pipe(plugins.clone()) 33 | .pipe(through.obj(function (file, enc, cb) { 34 | file.originalPath = file.path; 35 | cb(null, file); 36 | })) 37 | .pipe(handleRename('')) 38 | .pipe(handleChanged()) 39 | .pipe(plugins.if(!options.skipResize, handleResize(factor))) 40 | .pipe(plugins.if(!options.skipOptimize, handleOptimize())) 41 | .pipe(vinyl.dest(options.dest)); 42 | 43 | imageTasks.push(stream); 44 | }); 45 | 46 | return mergeStream.apply(this, imageTasks) 47 | .pipe(rev(vinyl, plugins, options)); 48 | }; 49 | 50 | // Only handle images that need handling. Should be ran *after* handleRename 51 | function handleChanged() { 52 | return plugins.changed(options.dest, { 53 | hasChanged: function (stream, cb, file, destPath) { 54 | var manifestPath = options.manifest || process.cwd(); 55 | var manifest = require(path.join(manifestPath, 'rev-manifest.json')); 56 | 57 | destPath = destPath.replace(/\d+[x-]\d+(?:@2x)?(?=(?:-[a-z]{2}(?:-[A-Z]{2})?)?\.[a-z]+$)/, 'small'); 58 | 59 | if (!manifest[path.basename(destPath)]) { 60 | stream.push(file); 61 | cb(); 62 | } else { 63 | plugins.changed.compareLastModifiedTime(stream, cb, file, destPath); 64 | } 65 | } 66 | }); 67 | } 68 | 69 | // Rename the image from something like file-10x10.jpg to file-small.jpg 70 | function handleRename(suffix) { 71 | return plugins.rename(function (path) { 72 | var matches = /^(.+)\-\d+[x-]\d+(?:@2x)?(?:(-[a-z]{2}(?:-[A-Z]{2})?))?$/.exec(path.basename); 73 | 74 | if (!matches) { 75 | var error = 'Failed to parse file name: ' + path.basename; 76 | throw new plugins.util.PluginError('lmn-gulp-tasks', error); 77 | } 78 | 79 | path.basename = matches[1] + suffix + (matches[2] || ''); 80 | if (options.flatten) { 81 | path.dirname = ''; 82 | } 83 | }); 84 | } 85 | 86 | // Use gulp-gm to resize the image 87 | function handleResize(factor) { 88 | return through.obj(function (file, enc, cb) { 89 | var newFactor = _.includes(file.originalPath, '@2x') ? factor / 2 : factor; 90 | 91 | if (newFactor === 100) { 92 | return cb(null, file); 93 | } 94 | 95 | gm(file.contents, file.path) 96 | .resize(newFactor, newFactor, '%') 97 | .toBuffer(function (err, buffer) { 98 | if (err) { 99 | return cb(err); 100 | } 101 | 102 | file.contents = buffer; 103 | cb(null, file); 104 | }); 105 | }); 106 | } 107 | 108 | // Compress images 109 | function handleOptimize() { 110 | function losslessTest(file) { 111 | if (_.isFunction(options.lossless)) { 112 | return options.lossless(file); 113 | } 114 | 115 | return options.lossless; 116 | } 117 | 118 | var lossyStream = plugins.imagemin({ use: [jpegoptim({ max: 80 })] }); 119 | var losslessStream = plugins.imagemin({ progressive: true }); 120 | 121 | return plugins.if(losslessTest, losslessStream, lossyStream); 122 | } 123 | 124 | // Work out src for retina images 125 | function getRetinaSrc(src) { 126 | if (_.isArray(src)) { 127 | return _.map(src, getRetinaSrc); 128 | } 129 | 130 | return src.replace('*.{', '*@2x*.{'); 131 | } 132 | }; 133 | -------------------------------------------------------------------------------- /tasks/scss.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var path = require('path'); 4 | var findNodeModules = require('find-node-modules'); 5 | var rev = require('../lib/rev'); 6 | var sassNpmImporter = require('../lib/sass-npm-importer'); 7 | var through = require('through2'); 8 | 9 | module.exports = function (vinyl, plugins, options) { 10 | return function scssTask() { 11 | if (typeof options.minify !== 'boolean') { 12 | options.minify = process.env.MINIFY_ASSETS || false; 13 | } 14 | 15 | if (typeof options.sourcemaps !== 'boolean') { 16 | options.sourcemaps = process.env.SOURCEMAPS || true; 17 | } 18 | 19 | var manifest = 'rev-manifest.json'; 20 | if (options.manifest) { 21 | manifest = path.join(options.manifest, manifest); 22 | } 23 | 24 | var includePaths; 25 | if (options.includePaths === false) { 26 | includePaths = []; 27 | } else { 28 | includePaths = findNodeModules({ relative: false }); 29 | 30 | if (Array.isArray(options.includePaths)) { 31 | includePaths = includePaths.concat(options.includePaths); 32 | } 33 | } 34 | 35 | function imageUrl(imagePath) { 36 | var returnPath = (options.imagePath || '') + path.join('/', imagePath.getValue()); 37 | return new plugins.sass.compiler.types.String('url("' + returnPath + '")'); 38 | } 39 | 40 | var ignore = options.ignoreSuckyAntipattern; 41 | 42 | return vinyl.src(options.src) 43 | .pipe(plugins.plumber({ errorHandler: options.onError })) 44 | .pipe(ignore ? through.obj() : plugins.contains('../node_modules')) 45 | .pipe(options.sourcemaps ? plugins.sourcemaps.init() : through.obj()) 46 | 47 | // Sourcemap start 48 | .pipe(plugins.sass({ 49 | functions: { 'image-url($imagePath)': imageUrl }, 50 | includePaths: includePaths, // @todo: Deprecate includePaths? 51 | importer: sassNpmImporter 52 | })) 53 | .on('error', options.onError) // For some reason gulp-plumber doesn't like -compass 54 | .pipe(plugins.autoprefixer()) 55 | .pipe(options.minify ? plugins.cleanCss() : through.obj()) 56 | .pipe(options.rev ? plugins.fingerprint(manifest, { 57 | prefix: '/' 58 | }) : through.obj()) 59 | // Sourcemap end 60 | 61 | .pipe(options.sourcemaps ? plugins.sourcemaps.write('./') : through.obj()) 62 | .pipe(vinyl.dest(options.dest)) 63 | .pipe(rev(vinyl, plugins, options)); 64 | }; 65 | }; 66 | -------------------------------------------------------------------------------- /tasks/small-sprite.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var os = require('os'); 4 | var path = require('path'); 5 | var through = require('through2'); 6 | var gm = require('gm').subClass({ imageMagick: true }); 7 | var jpegoptim = require('imagemin-jpegoptim'); 8 | var merge = require('merge-stream'); 9 | 10 | module.exports = function (vinyl, plugins, options) { 11 | var cssBasename = path.basename(options.cssDest); 12 | var cssDirname = path.dirname(options.cssDest); 13 | var imgBasename = path.basename(options.imgDest); 14 | var imgDirname = path.dirname(options.imgDest); 15 | 16 | var percent = options.percent || 20; 17 | 18 | return function smallSpriteTask(done) { 19 | var stream = vinyl.src(options.src) 20 | .pipe(through.obj(function (file, enc, cb) { 21 | gm(file.contents, file.path) 22 | .resize(percent, percent, '%') 23 | .toBuffer(function (err, buffer) { 24 | if (err) { 25 | return cb(err); 26 | } 27 | 28 | file.contents = buffer; 29 | cb(null, file); 30 | }); 31 | })) 32 | .pipe(vinyl.dest(path.join(os.tmpdir(), 'small-sprite'))) 33 | .on('end', function () { 34 | // gulp.spritesmith doesn't get file contents from the stream 35 | var spriteStream = vinyl.src(path.join(os.tmpdir(), 'small-sprite/*.jpg')) 36 | .pipe(plugins.spritesmith({ 37 | imgName: imgBasename, 38 | cssName: cssBasename, 39 | algorithm: 'top-down', 40 | cssTemplate: options.cssTemplate 41 | })); 42 | 43 | var imgStream = spriteStream.img 44 | .pipe(jpegoptim({ progressive: true })()) 45 | .pipe(vinyl.dest(imgDirname)); 46 | 47 | var cssStream = spriteStream.css 48 | .pipe(vinyl.dest(cssDirname)); 49 | 50 | merge(imgStream, cssStream) 51 | .on('end', function () { 52 | done(); 53 | }) 54 | .on('error', function (err) { 55 | throw err; 56 | }) 57 | .resume(); 58 | }); 59 | 60 | stream.resume(); 61 | return stream; 62 | }; 63 | }; 64 | -------------------------------------------------------------------------------- /tasks/test-locales.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | var _ = require('lodash'); 5 | var through = require('through2'); 6 | 7 | var locales = ['en-US', 'de', 'en-GB']; // en-GB is blank in paths 8 | 9 | module.exports = function (vinyl, plugins, options) { 10 | function wrap(locale) { 11 | return (locale === 'en-GB' ? '' : '-' + locale) + '.'; 12 | } 13 | 14 | return function () { 15 | if (!options.locales) { 16 | options.locales = locales; 17 | } 18 | 19 | var localesGlob = _.map(options.locales, function (locale) { 20 | return options.src + '/**/*-' + locale + '.{png,jpg}'; 21 | }); 22 | 23 | var errors = false; 24 | 25 | return vinyl.src(localesGlob, { read: false }) 26 | .pipe(plugins.plumber({ 27 | errorHandler: function (err) { 28 | plugins.util.log(plugins.util.colors.red(err.message)); 29 | } 30 | })) 31 | .pipe(through.obj(function (file, enc, cb) { 32 | var path = file.relative; 33 | var failWithError = null; 34 | 35 | var fileLocale = _.filter(options.locales, function (locale) { 36 | var posFromEnd = path.length - path.lastIndexOf(wrap(locale)); 37 | return (posFromEnd === 5 + locale.length); 38 | })[0]; 39 | 40 | var fails = _.filter(options.locales, function (testLocale) { 41 | if (fileLocale === testLocale) { 42 | return false; // Obviously exists, no point testing 43 | } 44 | 45 | var pathShouldExist = path.replace(wrap(fileLocale), wrap(testLocale)); 46 | return !fs.existsSync(options.src + '/' + pathShouldExist); 47 | }); 48 | 49 | fails.forEach(function (failLocale) { 50 | errors = true; 51 | 52 | var error = 'No "' + failLocale + '" file found for ' + path; 53 | failWithError = new plugins.util.PluginError('LocaleChecker', error); 54 | }); 55 | 56 | this.push(file); 57 | 58 | return cb(failWithError); 59 | })) 60 | .on('end', function () { 61 | if (!errors) { 62 | var success = 'Success: no missing locale files detected'; 63 | plugins.util.log(plugins.util.colors.green(success)); 64 | } 65 | }); 66 | }; 67 | }; 68 | -------------------------------------------------------------------------------- /test/browserify.js: -------------------------------------------------------------------------------- 1 | /* global clean, getFile */ 2 | 3 | 'use strict'; 4 | 5 | import loadLmnTask from '../index'; 6 | 7 | import fs from 'fs'; 8 | import path from 'path'; 9 | import should from 'should'; 10 | 11 | var fixtures = path.join(__dirname, 'fixtures/js'); 12 | var fixturesOut = path.join(__dirname, 'fixtures/out'); 13 | 14 | describe('browserify', function () { 15 | beforeEach(clean); 16 | afterEach(clean); 17 | 18 | this.timeout(10000); 19 | 20 | it('should parse simple js', function (done) { 21 | var out = path.join(fixturesOut, 'simple.js'); 22 | var stream = loadLmnTask('browserify', { 23 | src: path.join(fixtures, 'simple.js'), 24 | sourcemaps: false, 25 | jquery: false, 26 | dest: out 27 | })(); 28 | 29 | stream.resume(); 30 | stream.on('end', function () { 31 | var file = getFile(out); 32 | 33 | file.length.should.be.within(450, 550); 34 | file.toString().should.containEql('test'); 35 | 36 | done(); 37 | }); 38 | }); 39 | 40 | it('should handle requires', function (done) { 41 | var out = path.join(fixturesOut, 'require.js'); 42 | var stream = loadLmnTask('browserify', { 43 | src: path.join(fixtures, 'require.js'), 44 | sourcemaps: false, 45 | jquery: false, 46 | dest: out 47 | })(); 48 | 49 | stream.resume(); 50 | stream.on('end', function () { 51 | var file = getFile(out); 52 | 53 | file.length.should.be.within(580, 680); 54 | file.toString().should.containEql('test'); 55 | 56 | done(); 57 | }); 58 | }); 59 | 60 | it('should handle es6 imports', function (done) { 61 | var out = path.join(fixturesOut, 'import.js'); 62 | var stream = loadLmnTask('browserify', { 63 | src: path.join(fixtures, 'import.js'), 64 | sourcemaps: false, 65 | jquery: false, 66 | dest: out 67 | })(); 68 | 69 | stream.resume(); 70 | stream.on('end', function () { 71 | var file = getFile(out); 72 | 73 | file.length.should.be.within(770, 870); 74 | file.toString().should.containEql('test'); 75 | 76 | done(); 77 | }); 78 | }); 79 | 80 | it('should object to ../node_modules', function (done) { 81 | loadLmnTask('browserify', { 82 | src: path.join(fixtures, 'bad-import.js'), 83 | dest: path.join(fixturesOut, 'bad-import.js'), 84 | minify: false, 85 | sourcemaps: false, 86 | jquery: false, 87 | onError: function (err) { 88 | err.toString().should.containEql('contains "../node_modules"'); 89 | done(); 90 | } 91 | })(); 92 | }); 93 | 94 | it('should minify and strip console.log', function (done) { 95 | var out = path.join(fixturesOut, 'require.js'); 96 | var stream = loadLmnTask('browserify', { 97 | src: path.join(fixtures, 'require.js'), 98 | sourcemaps: false, 99 | jquery: false, 100 | dest: out, 101 | minify: true 102 | })(); 103 | 104 | stream.resume(); 105 | stream.on('end', function () { 106 | var file = getFile(out); 107 | 108 | file.length.should.be.within(530, 600); 109 | file.toString().should.not.containEql('\n'); 110 | file.toString().should.not.containEql('console.log'); 111 | 112 | done(); 113 | }); 114 | }); 115 | 116 | it('should not minify when false', function (done) { 117 | var out = path.join(fixturesOut, 'require.js'); 118 | var stream = loadLmnTask('browserify', { 119 | src: path.join(fixtures, 'require.js'), 120 | sourcemaps: false, 121 | jquery: false, 122 | dest: out 123 | })(); 124 | 125 | stream.resume(); 126 | stream.on('end', function () { 127 | var file = getFile(out); 128 | 129 | file.length.should.be.within(580, 680); 130 | file.toString().should.containEql('\n'); 131 | file.toString().should.containEql('console.log'); 132 | 133 | done(); 134 | }); 135 | }); 136 | 137 | it('should do source maps', function (done) { 138 | var out = path.join(fixturesOut, 'simple.js'); 139 | var mapOut = path.join(fixturesOut, 'simple.js.map'); 140 | var stream = loadLmnTask('browserify', { 141 | src: path.join(fixtures, 'simple.js'), 142 | jquery: false, 143 | dest: out 144 | })(); 145 | 146 | stream.resume(); 147 | stream.on('end', function () { 148 | var file = getFile(out, false); 149 | 150 | file.length.should.be.within(500, 600); 151 | file.toString().should.containEql('//# sourceMappingURL=simple.js.map'); 152 | 153 | var map = getFile(mapOut, false); 154 | 155 | map.length.should.be.within(650, 800); 156 | map.toString().should.match(/"sources":.+"test\/fixtures\/js\/simple.js"/); 157 | 158 | done(); 159 | }); 160 | }); 161 | 162 | it('should magically add jquery', function (done) { 163 | var out = path.join(fixturesOut, 'simple.js'); 164 | var stream = loadLmnTask('browserify', { 165 | src: path.join(fixtures, 'simple.js'), 166 | sourcemaps: false, 167 | dest: out 168 | })(); 169 | 170 | stream.resume(); 171 | stream.on('end', function () { 172 | var file = getFile(out); 173 | 174 | file.length.should.be.above(200000); 175 | file.toString().should.containEql('test'); 176 | file.toString().should.containEql('noConflict = '); 177 | 178 | done(); 179 | }); 180 | }); 181 | 182 | it('should stay quiet when no jquery available', function (done) { 183 | var jqueryPath = path.join(process.cwd(), 'node_modules/jquery'); 184 | 185 | fs.rename(jqueryPath, jqueryPath + '1', function (err) { 186 | if (err) { 187 | done(err); 188 | } 189 | 190 | var out = path.join(fixturesOut, 'simple.js'); 191 | var stream = loadLmnTask('browserify', { 192 | src: path.join(fixtures, 'simple.js'), 193 | sourcemaps: false, 194 | dest: out 195 | })(); 196 | 197 | stream.resume(); 198 | stream.on('end', function () { 199 | var file = getFile(out); 200 | 201 | file.length.should.be.within(450, 550); 202 | file.toString().should.containEql('test'); 203 | file.toString().should.not.containEql('noConflict = '); 204 | 205 | fs.rename(jqueryPath + '1', jqueryPath, function (err) { 206 | if (err) { 207 | done(err); 208 | } 209 | 210 | done(); 211 | }); 212 | }); 213 | }); 214 | }); 215 | 216 | it('should handle ~~ES2015~~', function (done) { 217 | var out = path.join(fixturesOut, 'simple.babel.js'); 218 | var stream = loadLmnTask('browserify', { 219 | src: path.join(fixtures, 'simple.babel.js'), 220 | sourcemaps: false, 221 | jquery: false, 222 | dest: out 223 | })(); 224 | 225 | stream.resume(); 226 | stream.on('end', function () { 227 | var file = getFile(out); 228 | 229 | file.length.should.be.within(550, 650); 230 | file.toString().should.containEql('\'click\', function () {'); 231 | file.toString().should.not.containEql('=>'); 232 | 233 | done(); 234 | }); 235 | }); 236 | 237 | it('should not parse classes', function (done) { 238 | var out = path.join(fixturesOut, 'bad-es6.js'); 239 | var stream = loadLmnTask('browserify', { 240 | src: path.join(fixtures, 'bad-es6.js'), 241 | sourcemaps: false, 242 | jquery: false, 243 | disableImagePath: true, 244 | dest: out 245 | })(); 246 | 247 | stream.resume(); 248 | stream.on('end', function () { 249 | var file = getFile(out); 250 | 251 | var contents = file.toString(); 252 | 253 | contents.should.containEql('class Test {'); 254 | contents.should.not.containEql('function Test()'); 255 | 256 | done(); 257 | }); 258 | }); 259 | 260 | it('should get environmental variables', function (done) { 261 | process.env.TESTING_STRING = 'blablabla1234'; 262 | 263 | var out = path.join(fixturesOut, 'env.js'); 264 | var stream = loadLmnTask('browserify', { 265 | src: path.join(fixtures, 'env.js'), 266 | sourcemaps: false, 267 | jquery: false, 268 | dest: out 269 | })(); 270 | 271 | stream.resume(); 272 | stream.on('end', function () { 273 | var file = getFile(out); 274 | 275 | file.length.should.be.within(450, 550); 276 | file.toString().should.containEql('console.log("blablabla1234");'); 277 | 278 | done(); 279 | }); 280 | }); 281 | 282 | it('should support react and jsx', function (done) { 283 | this.timeout(8000); 284 | 285 | var out = path.join(fixturesOut, 'react.js'); 286 | var stream = loadLmnTask('browserify', { 287 | src: path.join(fixtures, 'react.js'), 288 | sourcemaps: false, 289 | jquery: false, 290 | react: true, 291 | dest: out 292 | })(); 293 | 294 | stream.resume(); 295 | stream.on('end', function () { 296 | var file = getFile(out); 297 | 298 | var contents = file.toString(); 299 | 300 | contents.should.match(/'div',\s+(?:null|\{[^}]+}),\s+'Teststring123'/); 301 | contents.should.not.containEql('
Teststring123
'); 302 | 303 | contents.length.should.be.within(600000, 700000); 304 | 305 | done(); 306 | }); 307 | }); 308 | 309 | it('should only allow stage 4 proposals', function (done) { 310 | loadLmnTask('browserify', { 311 | src: path.join(fixtures, 'bad-es7.js'), 312 | dest: path.join(fixturesOut, 'bad-es7.js'), 313 | sourcemaps: false, 314 | jquery: false, 315 | onError: function (err) { 316 | err.message.should.containEql('Unexpected token'); 317 | done(); 318 | } 319 | })(); 320 | }); 321 | 322 | it('should split stuff out into other files if required', function (done) { 323 | var out = path.join(fixturesOut, 'main-bundle.js'); 324 | var outOther = path.join(fixturesOut, 'checkout-bundle.js'); 325 | var outRandom = path.join(fixturesOut, 'random-bundle.js'); 326 | 327 | var stream = loadLmnTask('browserify', { 328 | src: path.join(fixtures, 'factor-bundle/bundle.js'), 329 | sourcemaps: false, 330 | jquery: false, 331 | dest: out, 332 | extras: [ 333 | { 334 | src: path.join(fixtures, 'factor-bundle/bundle-checkout.js'), 335 | dest: outOther 336 | }, 337 | { 338 | src: path.join(fixtures, 'factor-bundle/bundle-random.js'), 339 | dest: outRandom 340 | } 341 | ] 342 | })(); 343 | 344 | stream.resume(); 345 | stream.on('end', function () { 346 | var mainFile = getFile(out).toString(); 347 | var checkoutFile = getFile(outOther).toString(); 348 | var randomFile = getFile(outRandom).toString(); 349 | 350 | mainFile.should.containEql('lib on all pages!'); 351 | mainFile.should.containEql('console.log(lib);'); 352 | mainFile.should.not.containEql('checkout'); 353 | mainFile.should.not.containEql('random'); 354 | 355 | checkoutFile.should.containEql('checkout'); 356 | checkoutFile.should.not.containEql('lib on all pages!'); 357 | checkoutFile.should.not.containEql('random'); 358 | 359 | randomFile.should.containEql('random'); 360 | randomFile.should.not.containEql('lib on all pages!'); 361 | randomFile.should.not.containEql('checkout'); 362 | 363 | done(); 364 | }); 365 | }); 366 | 367 | describe('imagePath()', function () { 368 | // This horrible hack is to stop is being deleted straight away! 369 | beforeEach(function (done) { 370 | fs.writeFile('rev-manifest.json', '{"cat.jpg":"cat-123.jpg"}', function (err) { 371 | done(err); 372 | }); 373 | }); 374 | 375 | it('should revision paths wrapped by imagePath()', function (done) { 376 | var out = path.join(fixturesOut, 'image-path.js'); 377 | var stream = loadLmnTask('browserify', { 378 | src: path.join(fixtures, 'image-path.js'), 379 | sourcemaps: false, 380 | jquery: false, 381 | dest: out 382 | })(); 383 | 384 | stream.resume(); 385 | stream.on('end', function () { 386 | var file = getFile(out); 387 | 388 | file.toString().should.containEql('var path = \'cat-123.jpg\';'); 389 | file.toString().should.containEql('var badPath = \'404.jpg\';'); 390 | 391 | done(); 392 | }); 393 | }); 394 | 395 | it('should revision paths wrapped by imagePath() with custom manifest', function (done) { 396 | var out = path.join(fixturesOut, 'image-path.js'); 397 | var stream = loadLmnTask('browserify', { 398 | src: path.join(fixtures, 'image-path.js'), 399 | sourcemaps: false, 400 | jquery: false, 401 | assetManifest: { 'cat.jpg': 'dog.jpg' }, 402 | dest: out 403 | })(); 404 | 405 | stream.resume(); 406 | stream.on('end', function () { 407 | var file = getFile(out); 408 | 409 | file.toString().should.containEql('var path = \'dog.jpg\';'); 410 | file.toString().should.containEql('var badPath = \'404.jpg\';'); 411 | 412 | done(); 413 | }); 414 | }); 415 | 416 | it('should support custom functions', function (done) { 417 | var out = path.join(fixturesOut, 'image-path.js'); 418 | var stream = loadLmnTask('browserify', { 419 | src: path.join(fixtures, 'image-path.js'), 420 | sourcemaps: false, 421 | jquery: false, 422 | resolvePath: function (filename, manifest) { 423 | return '/a/b/c/' + (manifest[filename] || filename); 424 | }, 425 | dest: out 426 | })(); 427 | 428 | stream.resume(); 429 | stream.on('end', function () { 430 | var file = getFile(out); 431 | 432 | file.toString().should.containEql('var path = \'/a/b/c/cat-123.jpg\';'); 433 | file.toString().should.containEql('var badPath = \'/a/b/c/404.jpg\';'); 434 | 435 | done(); 436 | }); 437 | }); 438 | }); 439 | }); 440 | -------------------------------------------------------------------------------- /test/copy-and-clean.js: -------------------------------------------------------------------------------- 1 | /* global getFile */ 2 | 3 | 'use strict'; 4 | 5 | import loadLmnTask from '../index'; 6 | 7 | import path from 'path'; 8 | import should from 'should'; 9 | import _ from 'lodash'; 10 | 11 | var fixtures = path.join(__dirname, 'fixtures/js/**'); 12 | var fixturesOut = path.join(__dirname, 'fixtures/out'); 13 | 14 | var f = _.partial(path.join, fixturesOut); 15 | 16 | // For convenience, we're testing both the copy and clean tasks in this file 17 | 18 | describe('copy and clean', function () { 19 | it('should copy files', function (done) { 20 | var stream = loadLmnTask('copy', { 21 | src: fixtures, 22 | dest: fixturesOut 23 | })(); 24 | 25 | stream.resume(); 26 | stream.on('finish', function () { 27 | var file = getFile(f('simple.js')); 28 | 29 | file.length.should.equal(24); 30 | file.toString().should.containEql('test'); 31 | 32 | done(); 33 | }); 34 | }); 35 | 36 | it('should clean files', function (done) { 37 | loadLmnTask('clean', { 38 | src: f('simple.js') 39 | })(function (err, files) { 40 | should(err).equal(null); 41 | 42 | files.length.should.equal(1); 43 | 44 | should.throws(function () { 45 | getFile(f('simple.js')); 46 | }, /ENOENT/); 47 | 48 | done(); 49 | }); 50 | }); 51 | 52 | it('should clean arrays of files', function (done) { 53 | loadLmnTask('clean', { 54 | src: [f('bad-jscs.js'), f('bad-jshint.js'), f('require.js')] 55 | })(function (err, files) { 56 | should(err).equal(null); 57 | 58 | files.length.should.equal(3); 59 | 60 | should.throws(function () { 61 | getFile(f('bad-jscs.js')); 62 | }, /ENOENT/); 63 | 64 | should.throws(function () { 65 | getFile(f('bad-jshint.js')); 66 | }, /ENOENT/); 67 | 68 | done(); 69 | }); 70 | }); 71 | }); 72 | -------------------------------------------------------------------------------- /test/extract.js: -------------------------------------------------------------------------------- 1 | /* global getFile */ 2 | 3 | 'use strict'; 4 | 5 | import loadLmnTask from '../index'; 6 | import should from 'should'; 7 | 8 | describe('extract', function () { 9 | it('should extract stuff. duh.', function (done) { 10 | loadLmnTask('extract', { 11 | module: 'babel-register', 12 | src: 'package.json', 13 | dest: function (files) { 14 | files.length.should.equal(1); 15 | 16 | var filePath = 'node_modules/babel-register/package.json'; 17 | files[0].contents.should.eql(getFile(filePath, false)); 18 | 19 | done(); 20 | } 21 | })(); 22 | }); 23 | 24 | it('should have a proper error when module not found', function () { 25 | loadLmnTask('extract', { 26 | module: 'gulp', 27 | src: 'bin/gulp.js', 28 | dest: function () { 29 | should.fail('This should have errored'); 30 | } 31 | }).should.throw(/Module gulp not found/); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /test/fixtures/imgs/a-z-gallery-01-2560x800.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lostmyname/lmn-gulp-tasks/3b4c826e1a38bb5bc78e9026aca04427a531f489/test/fixtures/imgs/a-z-gallery-01-2560x800.jpg -------------------------------------------------------------------------------- /test/fixtures/imgs/a-z-gallery-02-2560x800.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lostmyname/lmn-gulp-tasks/3b4c826e1a38bb5bc78e9026aca04427a531f489/test/fixtures/imgs/a-z-gallery-02-2560x800.jpg -------------------------------------------------------------------------------- /test/fixtures/imgs/a-z-gallery-03-2560x800.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lostmyname/lmn-gulp-tasks/3b4c826e1a38bb5bc78e9026aca04427a531f489/test/fixtures/imgs/a-z-gallery-03-2560x800.jpg -------------------------------------------------------------------------------- /test/fixtures/imgs/sprite-out.css: -------------------------------------------------------------------------------- 1 | /* 2 | Icon classes can be used entirely standalone. They are named after their original file names. 3 | 4 | ```html 5 | 6 |
7 | 8 | 9 | 10 | ``` 11 | */ 12 | .icon-a-z-gallery-01-2560x800 { 13 | background-image: url(sprite.jpg); 14 | background-position: 0px 0px; 15 | width: 512px; 16 | height: 160px; 17 | } 18 | .icon-a-z-gallery-02-2560x800 { 19 | background-image: url(sprite.jpg); 20 | background-position: 0px -160px; 21 | width: 512px; 22 | height: 160px; 23 | } 24 | .icon-a-z-gallery-03-2560x800 { 25 | background-image: url(sprite.jpg); 26 | background-position: 0px -320px; 27 | width: 512px; 28 | height: 160px; 29 | } 30 | -------------------------------------------------------------------------------- /test/fixtures/imgs/sprite-out.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lostmyname/lmn-gulp-tasks/3b4c826e1a38bb5bc78e9026aca04427a531f489/test/fixtures/imgs/sprite-out.jpg -------------------------------------------------------------------------------- /test/fixtures/js/bad-es6.js: -------------------------------------------------------------------------------- 1 | class Test { 2 | constructor() { 3 | let hello = 'world'; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /test/fixtures/js/bad-es7.js: -------------------------------------------------------------------------------- 1 | data.combinedLetters.map((letter, index) => ({ ...letter, index })); 2 | -------------------------------------------------------------------------------- /test/fixtures/js/bad-import.js: -------------------------------------------------------------------------------- 1 | require('../../../node_modules/del'); 2 | -------------------------------------------------------------------------------- /test/fixtures/js/bad-jscs.js: -------------------------------------------------------------------------------- 1 | (function(){ 2 | //lol 3 | })( ); 4 | -------------------------------------------------------------------------------- /test/fixtures/js/bad-jshint.js: -------------------------------------------------------------------------------- 1 | var unused = 'test'; 2 | -------------------------------------------------------------------------------- /test/fixtures/js/env.js: -------------------------------------------------------------------------------- 1 | console.log(process.env.TESTING_STRING); 2 | -------------------------------------------------------------------------------- /test/fixtures/js/factor-bundle/bundle-checkout.js: -------------------------------------------------------------------------------- 1 | require('./bundle'); 2 | 3 | var lib = require('./lib'); 4 | 5 | console.log('checkout ' + lib); 6 | -------------------------------------------------------------------------------- /test/fixtures/js/factor-bundle/bundle-random.js: -------------------------------------------------------------------------------- 1 | require('./bundle'); 2 | 3 | var lib = require('./lib'); 4 | 5 | console.log('random ' + lib); 6 | -------------------------------------------------------------------------------- /test/fixtures/js/factor-bundle/bundle.js: -------------------------------------------------------------------------------- 1 | var lib = require('./lib'); 2 | 3 | console.log(lib); 4 | -------------------------------------------------------------------------------- /test/fixtures/js/factor-bundle/lib.js: -------------------------------------------------------------------------------- 1 | module.exports = 'lib on all pages!'; 2 | -------------------------------------------------------------------------------- /test/fixtures/js/image-path.js: -------------------------------------------------------------------------------- 1 | var path = imagePath('cat.jpg'); 2 | 3 | var badPath = imagePath('404.jpg'); 4 | -------------------------------------------------------------------------------- /test/fixtures/js/import.js: -------------------------------------------------------------------------------- 1 | import simple from './simple'; 2 | 3 | console.log(simple); 4 | -------------------------------------------------------------------------------- /test/fixtures/js/react.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import view from './view.jsx'; 3 | 4 | React.renderComponent(view(), document.getElementById('content')); 5 | -------------------------------------------------------------------------------- /test/fixtures/js/require.js: -------------------------------------------------------------------------------- 1 | var simple = require('./simple'); 2 | 3 | console.log(simple); 4 | -------------------------------------------------------------------------------- /test/fixtures/js/simple.babel.js: -------------------------------------------------------------------------------- 1 | let test = document.addEventListener('click', () => console.log(this)); 2 | -------------------------------------------------------------------------------- /test/fixtures/js/simple.js: -------------------------------------------------------------------------------- 1 | module.exports = 'test'; 2 | -------------------------------------------------------------------------------- /test/fixtures/js/view.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | export var MyView = React.createClass({ 4 | render: function () { 5 | return ( 6 |
Teststring123
7 | ); 8 | } 9 | }); 10 | -------------------------------------------------------------------------------- /test/fixtures/sass/bad-import.scss: -------------------------------------------------------------------------------- 1 | // Stop writing this pls 2 | @import "../../../node_modules/normalize.css"; 3 | -------------------------------------------------------------------------------- /test/fixtures/sass/fingerprint.scss: -------------------------------------------------------------------------------- 1 | body { 2 | background-image: url("image.jpg"); 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/sass/image-path.scss: -------------------------------------------------------------------------------- 1 | body { 2 | background-image: image-url('man.jpg'); 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/sass/import-npm.scss: -------------------------------------------------------------------------------- 1 | @import "normalize.css"; 2 | -------------------------------------------------------------------------------- /test/fixtures/sass/import.scss: -------------------------------------------------------------------------------- 1 | @import "normalize.css"; 2 | -------------------------------------------------------------------------------- /test/fixtures/sass/import2.scss: -------------------------------------------------------------------------------- 1 | @import "sass/prefix"; 2 | -------------------------------------------------------------------------------- /test/fixtures/sass/issue-14.scss: -------------------------------------------------------------------------------- 1 | a { 2 | background: image-url('test.png'); 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/sass/prefix.scss: -------------------------------------------------------------------------------- 1 | a { 2 | display: flex; 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/sass/rev-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "image.jpg": "image-blabla.jpg" 3 | } 4 | -------------------------------------------------------------------------------- /test/fixtures/sass/test-out.css: -------------------------------------------------------------------------------- 1 | body .div { 2 | color: blue; } 3 | -------------------------------------------------------------------------------- /test/fixtures/sass/test-out.min-with-src.css: -------------------------------------------------------------------------------- 1 | body .div{color:#00f} 2 | /*# sourceMappingURL=test.css.map */ 3 | -------------------------------------------------------------------------------- /test/fixtures/sass/test-out.min.css: -------------------------------------------------------------------------------- 1 | body .div{color:#00f} 2 | -------------------------------------------------------------------------------- /test/fixtures/sass/test.sass: -------------------------------------------------------------------------------- 1 | $color: blue 2 | 3 | body .div 4 | color: $color 5 | -------------------------------------------------------------------------------- /test/fixtures/sass/test.scss: -------------------------------------------------------------------------------- 1 | $color: blue; 2 | 3 | body { 4 | .div { 5 | color: $color; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /test/fixtures/svg/logo-148x35-out.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Lostmyname/lmn-gulp-tasks/3b4c826e1a38bb5bc78e9026aca04427a531f489/test/fixtures/svg/logo-148x35-out.png -------------------------------------------------------------------------------- /test/fixtures/svg/logo-148x35-out.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test/fixtures/svg/logo-148x35.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 8 | 10 | 11 | 12 | 13 | 14 | 15 | 17 | 18 | 19 | 20 | 21 | 22 | 24 | 25 | 26 | 28 | 29 | 30 | 31 | 34 | 35 | 36 | 38 | 39 | 40 | 43 | 44 | 45 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 56 | 57 | 58 | 62 | 63 | 64 | 67 | 68 | 69 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /test/js-quality.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import loadLmnTask from '../index'; 4 | 5 | import path from 'path'; 6 | import hook from 'hook-stdio'; 7 | 8 | var fixtures = path.join(__dirname, 'fixtures/js'); 9 | 10 | describe('js-quality', function () { 11 | it('should not complain when there is nothing wrong', function (done) { 12 | var stream = loadLmnTask('js-quality', { 13 | src: path.join(fixtures, 'simple.js'), 14 | onError: function () { 15 | throw new Error('This should not have errored!'); 16 | } 17 | })(); 18 | 19 | stream.resume(); 20 | stream.on('end', function () { 21 | done(); 22 | }); 23 | }); 24 | 25 | it('should die when ESLint syntax error (previously JSCS)', function (done) { 26 | var output = ''; 27 | var unhook = hook.stdout(function (string) { 28 | output += string; 29 | }); 30 | 31 | var stream = loadLmnTask('js-quality', { 32 | src: path.join(fixtures, 'bad-jscs.js') 33 | })(); 34 | 35 | stream.resume(); 36 | stream.on('end', function () { 37 | unhook(); 38 | 39 | output.should.containEql('space-in-parens'); 40 | 41 | // Basically a way of counting the errors 42 | output.split('error').length.should.equal(5); 43 | 44 | done(); 45 | }); 46 | }); 47 | 48 | it('should die when ESLint error', function (done) { 49 | var output = ''; 50 | var unhook = hook.stdout(function (string) { 51 | output += string; 52 | }); 53 | 54 | var stream = loadLmnTask('js-quality', { 55 | src: path.join(fixtures, 'bad-jshint.js') 56 | })(); 57 | 58 | stream.resume(); 59 | stream.on('end', function () { 60 | unhook(); 61 | 62 | output.should.containEql('\'unused\' is defined but never used'); 63 | 64 | done(); 65 | }); 66 | }); 67 | }); 68 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --require should 2 | --require ./lib/testSetup 3 | --compilers js:babel-register 4 | -------------------------------------------------------------------------------- /test/optimise-svgs.js: -------------------------------------------------------------------------------- 1 | /* global clean, getFile */ 2 | 3 | 'use strict'; 4 | 5 | import loadLmnTask from '../index'; 6 | import fs from 'fs'; 7 | import path from 'path'; 8 | import should from 'should'; 9 | import _ from 'lodash'; 10 | 11 | var p = _.partial(path.join, './test/fixtures'); 12 | 13 | describe('optimise-svgs', function () { 14 | beforeEach(clean); 15 | after(clean); 16 | 17 | this.timeout(4000); 18 | 19 | it('shouldnt optimise unchanged svgs', function (done) { 20 | var stream = loadLmnTask('optimise-svgs', { 21 | src: p('svg/logo-148x35.svg'), 22 | dest: p('svg') 23 | })(); 24 | 25 | stream.resume(); 26 | 27 | stream.on('end', function () { 28 | should.throws(function () { 29 | getFile(p('svg/logo-148x35.png')); 30 | }, /ENOENT/); 31 | done(); 32 | }); 33 | }); 34 | 35 | it('should optimise and rasterise changed svgs', function (done) { 36 | var stream = loadLmnTask('optimise-svgs', { 37 | src: p('svg/logo-148x35.svg'), 38 | dest: p('out') 39 | })(); 40 | 41 | stream.resume(); 42 | 43 | stream.on('end', function () { 44 | var expectedSvg = getFile(p('svg/logo-148x35-out.svg')); 45 | var svgOut = getFile(p('out/logo-148x35.svg'), false); 46 | 47 | svgOut.should.eql(expectedSvg); 48 | 49 | var expectedPng = getFile(p('svg/logo-148x35-out.png')); 50 | var pngOut = getFile(p('out/logo-148x35.png')); 51 | 52 | pngOut.should.eql(expectedPng); 53 | done(); 54 | }); 55 | }); 56 | 57 | it('should rev svgs and png correctly', function (done) { 58 | var stream = loadLmnTask('optimise-svgs', { 59 | src: p('svg/logo-148x35.svg'), 60 | dest: p('out'), 61 | rev: true 62 | })(); 63 | 64 | stream.resume(); 65 | 66 | stream.on('end', function () { 67 | var expectedSvg = getFile(p('svg/logo-148x35-out.svg')); 68 | var svgOut = getFile(p('out/logo-148x35.svg'), false); 69 | 70 | svgOut.should.eql(expectedSvg); 71 | 72 | var expectedPng = getFile(p('svg/logo-148x35-out.png')); 73 | var pngOut = getFile(p('out/logo-148x35.png')); 74 | 75 | pngOut.should.eql(expectedPng); 76 | 77 | fs.readdir(p('out'), function (err, files) { 78 | should(err).equal(null); 79 | files.length.should.equal(4); 80 | done(); 81 | }); 82 | }); 83 | }); 84 | }); 85 | -------------------------------------------------------------------------------- /test/rev.js: -------------------------------------------------------------------------------- 1 | /* global clean */ 2 | 3 | 'use strict'; 4 | 5 | import loadLmnTask from '../index'; 6 | 7 | import fs from 'fs'; 8 | import path from 'path'; 9 | import _ from 'lodash'; 10 | 11 | var fixtures = path.join(__dirname, 'fixtures/js'); 12 | var fixturesOut = path.join(__dirname, 'fixtures/out'); 13 | 14 | describe('rev', function () { 15 | beforeEach(clean); 16 | afterEach(clean); 17 | 18 | it('should support revisioning', function (done) { 19 | var out = path.join(fixturesOut, 'simple.js'); 20 | var stream = loadLmnTask('browserify', { 21 | src: path.join(fixtures, 'simple.js'), 22 | sourcemaps: false, 23 | jquery: false, 24 | rev: true, 25 | dest: out 26 | })(); 27 | 28 | stream.resume(); 29 | stream.on('end', function () { 30 | fs.readdir(fixturesOut, function (err, files) { 31 | if (err) { 32 | done(err); 33 | } 34 | 35 | files.length.should.equal(2); 36 | 37 | var manifest = require('../rev-manifest.json'); 38 | 39 | // Should contain both versioned and unversioned files 40 | files.should.containEql(_.keys(manifest)[0]); 41 | files.should.containEql(_.values(manifest)[0]); 42 | 43 | done(); 44 | }); 45 | }); 46 | }); 47 | 48 | it('should support outputting the manifest elsewhere', function (done) { 49 | var out = path.join(fixturesOut, 'simple.js'); 50 | var stream = loadLmnTask('browserify', { 51 | src: path.join(fixtures, 'simple.js'), 52 | sourcemaps: false, 53 | jquery: false, 54 | rev: true, 55 | manifest: path.join(fixturesOut, 'manifest'), 56 | dest: out 57 | })(); 58 | 59 | stream.resume(); 60 | stream.on('end', function () { 61 | fs.readdir(fixturesOut, function (err, files) { 62 | if (err) { 63 | done(err); 64 | } 65 | 66 | files.length.should.equal(3); 67 | 68 | var manifest = require(path.join(fixturesOut, 'manifest/rev-manifest.json')); 69 | 70 | // Should contain both versioned and unversioned files 71 | files.should.containEql(_.keys(manifest)[0]); 72 | files.should.containEql(_.values(manifest)[0]); 73 | 74 | done(); 75 | }); 76 | }); 77 | }); 78 | }); 79 | -------------------------------------------------------------------------------- /test/scss.js: -------------------------------------------------------------------------------- 1 | /* global getFile */ 2 | 3 | 'use strict'; 4 | 5 | import loadLmnTask from '../index'; 6 | 7 | import path from 'path'; 8 | import _ from 'lodash'; 9 | 10 | var fixtures = path.join(__dirname, 'fixtures/sass'); 11 | var p = _.partial(path.join, fixtures); 12 | 13 | describe('scss', function () { 14 | it('should parse simple scss', function (done) { 15 | loadLmnTask('scss', { 16 | src: path.join(fixtures, 'test.scss'), 17 | minify: true, 18 | sourcemaps: false, 19 | dest: function (files) { 20 | files.length.should.equal(1); 21 | 22 | files[0].contents.should.eql(getFile(p('test-out.min.css'))); 23 | 24 | done(); 25 | } 26 | })(); 27 | }); 28 | 29 | it('should parse simple sass'); // gulp-sass is broken 30 | 31 | //it('should parse simple sass', function (done) { 32 | // loadLmnTask('scss', { 33 | // src: path.join(fixtures, 'test.sass'), 34 | // dest: function (files) { 35 | // files.length.should.equal(1); 36 | // 37 | // files[0].contents.should.eql(getFile(p('test-out.min.css'))); 38 | // 39 | // done(); 40 | // } 41 | // })(); 42 | //}); 43 | 44 | it('should parse simple scss with minify false', function (done) { 45 | loadLmnTask('scss', { 46 | src: path.join(fixtures, 'test.scss'), 47 | minify: false, 48 | sourcemaps: false, 49 | dest: function (files) { 50 | files.length.should.equal(1); 51 | 52 | files[0].contents.should.eql(getFile(p('test-out.css'), false)); 53 | 54 | done(); 55 | } 56 | })(); 57 | }); 58 | 59 | it('should handle node_modules imports', function (done) { 60 | loadLmnTask('scss', { 61 | src: path.join(fixtures, 'import.scss'), 62 | sourcemaps: false, 63 | dest: function (files) { 64 | files.length.should.equal(1); 65 | 66 | files[0].contents.length.should.be.above(10); 67 | 68 | done(); 69 | } 70 | })(); 71 | }); 72 | 73 | it('should handle other import paths', function (done) { 74 | loadLmnTask('scss', { 75 | src: path.join(fixtures, 'import2.scss'), 76 | includePaths: ['test/fixtures'], 77 | sourcemaps: false, 78 | dest: function (files) { 79 | files.length.should.equal(1); 80 | 81 | done(); 82 | } 83 | })(); 84 | }); 85 | 86 | it('should handle node_modules imports even when other import paths', function (done) { 87 | loadLmnTask('scss', { 88 | src: path.join(fixtures, 'import.scss'), 89 | includePaths: ['something'], 90 | sourcemaps: false, 91 | dest: function (files) { 92 | files.length.should.equal(1); 93 | 94 | files[0].contents.length.should.be.above(10); 95 | 96 | done(); 97 | } 98 | })(); 99 | }); 100 | 101 | it('should not handle node_modules imports when told not to', function (done) { 102 | var doneOnce = _.once(done); 103 | loadLmnTask('scss', { 104 | src: path.join(fixtures, 'import.scss'), 105 | includePaths: false, 106 | sourcemaps: false, 107 | dest: function (files) { 108 | files.length.should.equal(1); 109 | done(); 110 | }, 111 | onError: function (err) { 112 | console.log(err) 113 | err.message.should.containEql('File to import not found or unreadable'); 114 | doneOnce(); 115 | } 116 | })(); 117 | }); 118 | 119 | it('should handle image-path', function (done) { 120 | loadLmnTask('scss', { 121 | src: path.join(fixtures, 'image-path.scss'), 122 | imagePath: '/what/ever', 123 | sourcemaps: false, 124 | dest: function (files) { 125 | files.length.should.equal(1); 126 | 127 | var contents = files[0].contents.toString('utf8'); 128 | 129 | contents.should.not.containEql('image-url'); 130 | contents.should.containEql('/what/ever/man.jpg'); 131 | 132 | done(); 133 | } 134 | })(); 135 | }); 136 | 137 | it('should be autoprefixed', function (done) { 138 | loadLmnTask('scss', { 139 | src: path.join(fixtures, 'prefix.scss'), 140 | sourcemaps: false, 141 | dest: function (files) { 142 | files.length.should.equal(1); 143 | 144 | files[0].contents.toString('utf8').should.containEql('-ms'); 145 | 146 | done(); 147 | } 148 | })(); 149 | }); 150 | 151 | it('should check for "../node_modules"', function (done) { 152 | loadLmnTask('scss', { 153 | src: path.join(fixtures, 'bad-import.scss'), 154 | sourcemaps: false, 155 | dest: function (files) { 156 | files.length.should.equal(0); 157 | }, 158 | onError: function () { 159 | done(); 160 | } 161 | })(); 162 | }); 163 | 164 | it('…unless you skip it', function (done) { 165 | loadLmnTask('scss', { 166 | src: path.join(fixtures, 'bad-import.scss'), 167 | ignoreSuckyAntipattern: true, 168 | sourcemaps: false, 169 | dest: function (files) { 170 | files.length.should.equal(1); 171 | done(); 172 | } 173 | })(); 174 | }); 175 | 176 | it('should do source maps', function (done) { 177 | loadLmnTask('scss', { 178 | src: path.join(fixtures, 'test.scss'), 179 | minify: true, 180 | dest: function (files) { 181 | files.length.should.equal(2); 182 | 183 | files[0].contents.toString().should.containEql('"sources":['); 184 | files[1].contents.should.eql(getFile(p('test-out.min-with-src.css'), false)); 185 | 186 | done(); 187 | } 188 | })(); 189 | }); 190 | 191 | it('should find paths in package.jsons', function (done) { 192 | loadLmnTask('scss', { 193 | src: path.join(fixtures, 'import-npm.scss'), 194 | sourcemaps: false, 195 | dest: function (files) { 196 | files.length.should.equal(1); 197 | 198 | files[0].contents.length.should.be.above(10); 199 | 200 | done(); 201 | } 202 | })(); 203 | }); 204 | 205 | it('should fingerprint images', function (done) { 206 | loadLmnTask('scss', { 207 | src: path.join(fixtures, 'fingerprint.scss'), 208 | rev: true, 209 | manifest: fixtures, 210 | dest: function (files) { 211 | files.length.should.equal(2); 212 | 213 | files[1].contents.toString().should.containEql('image-blabla.jpg'); 214 | 215 | done(); 216 | } 217 | })(); 218 | }); 219 | 220 | it('should not destroy remote images', function (done) { 221 | loadLmnTask('scss', { 222 | src: path.join(fixtures, 'issue-14.scss'), 223 | minify: false, 224 | sourcemaps: false, 225 | imagePath: 'http://localhost', 226 | dest: function (files) { 227 | files.length.should.equal(1); 228 | 229 | var contents = files[0].contents.toString(); 230 | contents.should.containEql('url("http://localhost/test.png");'); 231 | 232 | done(); 233 | } 234 | })(); 235 | }); 236 | }); 237 | -------------------------------------------------------------------------------- /test/small-sprite.js: -------------------------------------------------------------------------------- 1 | /* global getFile */ 2 | 3 | 'use strict'; 4 | 5 | import loadLmnTask from '../index'; 6 | 7 | import path from 'path'; 8 | import _ from 'lodash'; 9 | 10 | var fixtures = path.join(__dirname, 'fixtures/imgs'); 11 | var f = _.partial(path.join, __dirname, 'fixtures/out'); 12 | 13 | describe('small sprite', function () { 14 | this.timeout(5000); 15 | 16 | it('should generate spritesheet', function (done) { 17 | loadLmnTask('small-sprite', { 18 | src: path.join(fixtures, 'a-z-*.jpg'), 19 | imgDest: f('sprite.jpg'), 20 | cssDest: f('sprite.css') 21 | })(function (err) { 22 | if (err) { 23 | throw err; 24 | } 25 | 26 | var imgOut = getFile(f('sprite.jpg')); 27 | imgOut.length.should.be.within(23000, 23500); 28 | 29 | var cssOut = getFile(f('sprite.css')); 30 | var expectedCssOut = getFile(path.join(fixtures, 'sprite-out.css')); 31 | cssOut.should.deepEqual(expectedCssOut); 32 | 33 | done(); 34 | }); 35 | }); 36 | }); 37 | --------------------------------------------------------------------------------