├── .gitignore ├── .travis.yml ├── LICENSE ├── appveyor.yml ├── lib ├── block.js ├── common.js ├── index.js └── parser.js ├── package.json ├── readme.md └── test ├── buffer.js ├── expected.html ├── fixture.html ├── null.js └── stream.js /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | Thumbs.db 3 | .DS_Store 4 | node_modules 5 | coverage 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" 4 | - "0.11" 5 | - "0.12" 6 | - "iojs" 7 | after_script: 8 | - npm run coveralls 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Vladimir Kucherenko 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | environment: 2 | matrix: 3 | # node.js 4 | - nodejs_version: "0.10" 5 | - nodejs_version: "0.11" 6 | - nodejs_version: "0.12" 7 | # io.js 8 | - nodejs_version: "1.0" 9 | 10 | install: 11 | - ps: Install-Product node $env:nodejs_version 12 | - npm install 13 | 14 | test_script: 15 | - node --version 16 | - npm --version 17 | - npm test 18 | 19 | build: off 20 | -------------------------------------------------------------------------------- /lib/block.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('util'); 4 | var path = require('path'); 5 | var slash = require('slash'); 6 | 7 | var Block = function (config, file, match) { 8 | this.replacement = match[0]; 9 | this.linefeed = match[1]; 10 | this.indent = match[2]; 11 | this.beginTag = match[3]; 12 | this.taskName = match[4]; 13 | this.originalContent = match[5]; 14 | this.endTag = match[6]; 15 | 16 | this.replacements = []; 17 | this.config = config; 18 | this.template = null; 19 | this.file = file; 20 | }; 21 | 22 | Block.prototype.build = function () { 23 | if (!this.replacements.length) { 24 | return this.config.keepUnassigned ? [this.indent + this.originalContent.trim()] : []; 25 | } 26 | 27 | // get the replacement strings and do replacements for extensions 28 | if (this.uniqueExts) { 29 | var extname = path.extname(this.file.path); 30 | var basename = path.basename(this.file.path, extname); 31 | 32 | if (this.uniqueExts['%f']) { 33 | this.uniqueExts['%f'].value = basename; 34 | } 35 | if (this.uniqueExts['%e']) { 36 | this.uniqueExts['%e'].value = extname; 37 | } 38 | 39 | Object.keys(this.uniqueExts).forEach(function (key) { 40 | var unique = this.uniqueExts[key]; 41 | this.template = this.template.replace(unique.regex, unique.value); 42 | }.bind(this)); 43 | } 44 | 45 | if (this.srcIsNull) { 46 | return [this.indent + this.template]; 47 | } 48 | 49 | return this.replacements.map(function (replacement) { 50 | if (this.template) { 51 | if (Array.isArray(replacement)) { 52 | replacement.unshift(this.template); 53 | return this.indent + util.format.apply(util, replacement); 54 | } else { 55 | return this.indent + util.format(this.template, replacement); 56 | } 57 | } 58 | 59 | if (this.config.resolvePaths) { 60 | var replacementPath = path.resolve(this.file.cwd, replacement); 61 | replacement = path.relative(path.dirname(this.file.path), replacementPath); 62 | replacement = slash(replacement); 63 | } 64 | 65 | var ext = replacement.split('?')[0].toLowerCase().split('.').pop(); 66 | 67 | if (ext === 'js') { 68 | return util.format('%s', this.indent, replacement); 69 | } else if (ext === 'css') { 70 | return util.format('%s', this.indent, replacement); 71 | } 72 | return this.indent + replacement; 73 | }.bind(this)); 74 | }; 75 | 76 | Block.prototype.compile = function (tasks) { 77 | var task = tasks[this.taskName]; 78 | 79 | if (task) { 80 | this.replacements = task.src; 81 | this.template = task.tpl; 82 | this.uniqueExts = task.uni; 83 | this.srcIsNull = task.srcIsNull; 84 | } 85 | 86 | var buildResult = this.build(); 87 | 88 | if (this.config.keepBlockTags) { 89 | buildResult.unshift(this.indent + this.beginTag); 90 | buildResult.push(this.indent + this.endTag); 91 | } 92 | 93 | buildResult.unshift(null); 94 | buildResult.push(null); 95 | 96 | return buildResult.join(this.linefeed); 97 | }; 98 | 99 | module.exports = Block; 100 | -------------------------------------------------------------------------------- /lib/common.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var Promise = require('bluebird'); 4 | var buffer = require('vinyl-buffer'); 5 | 6 | function isStream(obj) { 7 | return obj && typeof obj.pipe === 'function' && typeof obj.on === 'function'; 8 | } 9 | 10 | /** 11 | * Takes the src property of the task configuration and deeply "resolves" any vinyl file stream in it by turning 12 | * it into a string. 13 | * 14 | * This function doesn't change the "arborescence" of the given value: all the forms with strings accepted 15 | * work with vinyl file streams. 16 | * 17 | * @returns {Promise} 18 | */ 19 | function resolveSrcString(srcProperty) { 20 | if (Array.isArray(srcProperty)) { 21 | // handle multiple tag replacement 22 | return Promise.all(srcProperty.map(function (item) { 23 | return resolveSrcString(item); 24 | })); 25 | } else if (isStream(srcProperty)) { 26 | return new Promise(function (resolve, reject) { 27 | var strings = []; 28 | 29 | srcProperty.pipe(buffer()) 30 | .on('data', function (file) { 31 | strings.push(file.contents.toString()); 32 | }) 33 | .on('error', function(error) { 34 | reject(error); 35 | this.end(); 36 | }) 37 | .once('end', function () { 38 | resolve(strings); 39 | }); 40 | }); 41 | } else { 42 | return Promise.resolve(srcProperty); 43 | } 44 | } 45 | 46 | module.exports = { 47 | /** 48 | * tasks = { 49 | * 'task-name': { 50 | * 'src': [file1, file2], 51 | * 'tpl': '' 52 | * }, 53 | * .... 54 | * } 55 | **/ 56 | parseTasks: function (options) { 57 | options = options || {}; 58 | 59 | var utilExtensions = /%f|%e/g; 60 | var tasksByNames = {}; 61 | 62 | var tasksPromises = Object.keys(options).map(function (name) { 63 | 64 | var task = { 65 | src: [], 66 | tpl: null, 67 | uni: {}, 68 | srcIsNull: false 69 | }; 70 | 71 | return Promise 72 | .resolve() 73 | .then(function () { 74 | var item = options[name]; 75 | var src = typeof item.src !== 'undefined' ? item.src : item; 76 | 77 | return resolveSrcString(src) 78 | .then(function(srcStrings) { 79 | task.srcIsNull = srcStrings === null; 80 | task.src = task.src.concat(srcStrings); 81 | task.tpl = item.tpl; 82 | }); 83 | }) 84 | .then(function () { 85 | var result; 86 | 87 | while (result = utilExtensions.exec(task.tpl)) { 88 | var type = result[0]; 89 | var unique = {}; 90 | 91 | if (task.uni[type]) { 92 | continue; 93 | } 94 | 95 | unique.regex = new RegExp(result[0], "g"); 96 | unique.value = null; 97 | task.uni[type] = unique; 98 | } 99 | }) 100 | .then(function () { 101 | tasksByNames[name] = task; 102 | }); 103 | }); 104 | 105 | return Promise.all(tasksPromises) 106 | .then(function() { 107 | return tasksByNames; 108 | }); 109 | }, 110 | 111 | regexMatchAll: function (string, regexp) { 112 | var matches = []; 113 | string.replace(regexp, function () { 114 | var arr = Array.prototype.slice.call(arguments); 115 | matches.push(arr); 116 | }); 117 | return matches; 118 | } 119 | }; 120 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var clone = require('clone'); 4 | var objectAssign = require('object-assign'); 5 | var Transform = require('readable-stream/transform'); 6 | var common = require('./common'); 7 | var Parser = require('./parser'); 8 | 9 | module.exports = function (options, userConfig) { 10 | var tasksPromise = common.parseTasks(options); 11 | 12 | var config = { 13 | keepUnassigned: false, 14 | keepBlockTags: false, 15 | resolvePaths: false 16 | }; 17 | 18 | if (typeof userConfig === 'boolean') { 19 | config.keepUnassigned = userConfig; 20 | } else if (typeof userConfig === 'object') { 21 | objectAssign(config, userConfig); 22 | } 23 | 24 | return new Transform({ 25 | objectMode: true, 26 | transform: function (file, enc, callback) { 27 | tasksPromise 28 | .then(function (tasks) { 29 | 30 | var parser = new Parser(clone(tasks), config, file); 31 | 32 | if (file.isBuffer()) { 33 | parser.write(file.contents); 34 | parser.end(); 35 | 36 | var contents = new Buffer(0); 37 | parser.on('data', function (data) { 38 | contents = Buffer.concat([contents, data]); 39 | }); 40 | parser.once('end', function () { 41 | file.contents = contents; 42 | callback(null, file); 43 | }); 44 | return; 45 | } 46 | 47 | if (file.isStream()) { 48 | file.contents = file.contents.pipe(parser); 49 | } 50 | 51 | callback(null, file); 52 | }) 53 | .catch(callback); 54 | } 55 | }); 56 | }; 57 | -------------------------------------------------------------------------------- /lib/parser.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var util = require('util'); 4 | var Transform = require('readable-stream/transform'); 5 | var Block = require('./block'); 6 | var common = require('./common'); 7 | 8 | /** 9 | * 1 - newline 10 | * 2 - indentation 11 | * 3 - begin tag 12 | * 4 - task name 13 | * 5 - original content 14 | * 6 - end tag 15 | * 16 | * @type {RegExp} 17 | */ 18 | var regex = /(\n?)([ \t]*)()\n?([\s\S]*?)\n?()\n?/ig; 19 | 20 | function Parser(tasks, config, file) { 21 | Transform.call(this); 22 | 23 | this.tasks = tasks; 24 | this.config = config; 25 | this.file = file; 26 | } 27 | util.inherits(Parser, Transform); 28 | 29 | Parser.prototype._transform = function (chunk, enc, done) { 30 | var content = chunk.toString('utf8'); 31 | 32 | var matches = common.regexMatchAll(content, regex); 33 | matches.forEach(function (match) { 34 | var block = new Block(this.config, this.file, match); 35 | content = content.replace(block.replacement, function () { 36 | return block.compile(this.tasks) 37 | }.bind(this)); 38 | }.bind(this)); 39 | 40 | done(null, content); 41 | }; 42 | 43 | module.exports = Parser; 44 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gulp-html-replace", 3 | "version": "1.6.2", 4 | "description": "Replace build blocks in HTML. Like useref but done right.", 5 | "keywords": [ 6 | "gulpplugin", 7 | "html", 8 | "replace" 9 | ], 10 | "files": [ 11 | "lib" 12 | ], 13 | "repository": { 14 | "type": "git", 15 | "url": "https://github.com/VFK/gulp-html-replace.git" 16 | }, 17 | "author": { 18 | "name": "Vladimir Kucherenko", 19 | "email": "kvsoftware@gmail.com" 20 | }, 21 | "contributors": [ 22 | { 23 | "name": "Bruce MacNaughton", 24 | "email": "bmacnaughton@gmail.com" 25 | } 26 | ], 27 | "main": "./lib/index.js", 28 | "scripts": { 29 | "test": "mocha", 30 | "coverage": "istanbul cover _mocha -- -R dot", 31 | "coveralls": "istanbul cover _mocha && istanbul-coveralls" 32 | }, 33 | "engines": { 34 | "node": ">= 0.9" 35 | }, 36 | "dependencies": { 37 | "bluebird": "^3.1.1", 38 | "clone": "^1.0.2", 39 | "object-assign": "^4.0.1", 40 | "readable-stream": "^2.0.4", 41 | "slash": "^1.0.0", 42 | "vinyl-buffer": "^1.0.0" 43 | }, 44 | "devDependencies": { 45 | "concat-stream": "^1.5.1", 46 | "from2-string": "^1.1.0", 47 | "gulp-util": "^3.0.7", 48 | "istanbul": "^0.4.0", 49 | "istanbul-coveralls": "^1.0.3", 50 | "mocha": "^2.3.4", 51 | "vinyl": "^1.1.0", 52 | "vinyl-source-stream": "^1.1.0" 53 | }, 54 | "license": "MIT" 55 | } 56 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # gulp-html-replace [![NPM version][npm-image]][npm-url] [![Travis][travis-image]][travis-url] [![AppVeyor][appveyor-image]][appveyor-url] [![Coverage Status][coveralls-image]][coveralls-url] 2 | 3 | > Replace build blocks in HTML. Like useref but done right. 4 |   5 | 6 | ### Table of Contents 7 | 8 | - [Usage](#usage) 9 | - [API](#api) 10 | - [Example](#example) 11 | - [Upgrade](#upgrade) 12 | 13 | 14 | ## Usage 15 | Install: 16 | ```shell 17 | npm install --save-dev gulp-html-replace 18 | ``` 19 | 20 | Put some blocks in your HTML file: 21 | ```html 22 | 23 | Everything here will be replaced 24 | 25 | ``` 26 | `name` is the name of the block. Could consist of letters, digits, underscore ( **_** ) and hyphen ( **-** ) symbols. 27 | 28 | ## API 29 | ### htmlreplace(tasks, options) 30 | 31 | #### tasks 32 | Type: `Object` `{task-name: replacement}` 33 | 34 | * **task-name** - The name of the block in your HTML. 35 | * **replacement** - `String|Array|stream.Readable|Object` The replacement. See examples below. 36 | 37 | ###### Simple example: 38 | ```javascript 39 | // Options is a single string 40 | htmlreplace({js: 'js/main.js'}) 41 | 42 | // Options is an array of strings 43 | htmlreplace({js: ['js/monster.js', 'js/hero.js']}) 44 | ``` 45 | >If your options strings ends with `.js` or `.css` they will be replaced by correct script/style tags, so you don't need to specify a template like in the example below. 46 | 47 | ###### Advanced example: 48 | ```javascript 49 | // Options is an object 50 | htmlreplace({ 51 | js: { 52 | src: 'img/avatar.png', 53 | tpl: '' 54 | } 55 | }) 56 | 57 | // Multiple tag replacement 58 | htmlreplace({ 59 | js: { 60 | src: [['data-main.js', 'require-src.js']], 61 | tpl: '' 62 | } 63 | }) 64 | ``` 65 | * **src** - `String|Array|stream.Readable` Same thing as in simple example. 66 | * **tpl** - `String` Template string. Uses [util.format()](http://nodejs.org/api/util.html#util_util_format_format) internally. 67 | 68 | > In the first example `%s` will be replaced with `img/avatar.png` producing `` as the result. 69 | 70 | > In the second example `data-main="%s"` and `src="%s"` will be replaced with `data-main.js` and `require-src.js` accordingly, producing `` as the result 71 | 72 | ###### Extended replacements: 73 | ```javascript 74 | // Replacement based on the file being processed 75 | htmlreplace({ 76 | js: { 77 | src: null, 78 | tpl: '' 79 | } 80 | }) 81 | // Extended replacement combined with standard replacement 82 | htmlreplace({ 83 | js: { 84 | src: 'dir', 85 | tpl: '' 86 | } 87 | }) 88 | 89 | ``` 90 | * **src** - `null|String|Array|stream.Readable` Same as examples above but null if there are no standard replacements in the template. 91 | * **tpl** - `String` Template string. Extended replacements do not use `util.format()` and are performed before standard replacements. 92 | 93 | > In the first example `src` is null because there are no standard replacements. `%f` is replaced with the name (without extension) of the file currently being processed. If the file being processed is `xyzzy.html` the result is ``. 94 | 95 | > In the second example `src` has been set to the string `'dir'`. Extended replacements are processed first, replacing `%f` with `xyzzy`, then `%s` will be replaced with `dir` resulting in ``. 96 | 97 | Valid extended replacements are: 98 | 99 | * **%f** - this will be replaced with the filename, without an extension. 100 | * **%e** - this will be replaced with the extension including the `.` character. 101 | 102 | ###### Stream replacements: 103 | Everywhere a string replacement can be given, a stream of vinyl is also accepted. The content of each file will be treated as UTF-8 text and used for replacement. If the stream produces more than a file the behavior is the same as when an array is given. 104 | ```javascript 105 | // Replacement is a stream 106 | htmlreplace({ 107 | cssInline: { 108 | src: gulp.src('style/main.scss').pipe(sass()), 109 | tpl: '' 110 | } 111 | }) 112 | 113 | ``` 114 | 115 | #### options 116 | Type: `object` 117 | 118 | All `false` by default. 119 | 120 | - {Boolean} **keepUnassigned** - Whether to keep blocks with unused names or remove them. 121 | - {Boolean} **keepBlockTags** - Whether to keep `` and `` comments or remove them. 122 | - {Boolean} **resolvePaths** - Try to resolve *relative* paths. For example if your `cwd` is ``/``, your html file is `/page/index.html` and you set replacement as `lib/file.js` the result path in that html will be `../lib/file.js` 123 | 124 | ###### Options example: 125 | ```javascript 126 | htmlreplace({ 127 | js: { 128 | src: null, 129 | tpl: '' 130 | } 131 | }, { 132 | keepUnassigned: false, 133 | keepBlockTags: false, 134 | resolvePaths: false 135 | }) 136 | ``` 137 | 138 | ## Example 139 | index.html: 140 | 141 | ```html 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | ``` 160 | 161 | gulpfile.js: 162 | 163 | ```javascript 164 | var gulp = require('gulp'); 165 | var htmlreplace = require('gulp-html-replace'); 166 | 167 | gulp.task('default', function() { 168 | gulp.src('index.html') 169 | .pipe(htmlreplace({ 170 | 'css': 'styles.min.css', 171 | 'js': 'js/bundle.min.js' 172 | })) 173 | .pipe(gulp.dest('build/')); 174 | }); 175 | ``` 176 | 177 | Result: 178 | 179 | ```html 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | ``` 191 | 192 | ## Upgrade 193 | 194 | ### From 0.x to 1.x 195 | >This version introduces streaming support, less confusing API, new option *keepUnused* and full code overhaul. 196 | * If you used single task like this: `htmlreplace('js', 'script.js')` just change it to `htmlreplace({js: 'script.js'})` 197 | * If you used single task with template: `htmlreplace('js', 'script.js', '')` change it to `htmlreplace({js: {src: 'script.js', tpl: ''})` 198 | * `files` renamed to `src`, see previous example. Rename if needed. 199 | 200 | ### From 1.1.x to 1.2.x 201 | >This version switches to the new way of specifying options which is more future-proof. Before it was `htmlreplace(tasks, keepUnassigned = false)`, now it's `htmlreplace(tasks, {keepUnassigned: false})`. 202 | No action required, old syntax will still work, but it is advisable to switch to the new syntax. 203 | 204 | [npm-url]: https://npmjs.org/package/gulp-html-replace 205 | [npm-image]: http://img.shields.io/npm/v/gulp-html-replace.svg 206 | [travis-url]: https://travis-ci.org/VFK/gulp-html-replace 207 | [travis-image]: https://travis-ci.org/VFK/gulp-html-replace.svg 208 | [appveyor-url]: https://ci.appveyor.com/project/VFK/gulp-html-replace 209 | [appveyor-image]: https://ci.appveyor.com/api/projects/status/66kwbnis5a1gwp6d?svg=true 210 | [coveralls-url]: https://coveralls.io/github/VFK/gulp-html-replace?branch=master 211 | [coveralls-image]: https://coveralls.io/repos/VFK/gulp-html-replace/badge.svg?branch=master&service=github 212 | -------------------------------------------------------------------------------- /test/buffer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var plugin = require('..'); 4 | var fs = require('fs'); 5 | var path = require('path'); 6 | var File = require('vinyl'); 7 | var assert = require('assert'); 8 | var stringToStream = require('from2-string'); 9 | var source = require('vinyl-source-stream'); 10 | 11 | function compare(fixture, expected, stream, done) { 12 | stream 13 | .once('data', function (file) { 14 | assert(file.isBuffer()); 15 | assert.strictEqual(String(file.contents), expected); 16 | done(); 17 | }) 18 | .end(new File({ 19 | base: path.resolve('www'), 20 | path: path.resolve('www', 'pages', 'index.html'), 21 | contents: fixture 22 | })); 23 | } 24 | 25 | describe('Buffer mode', function () { 26 | it('should replace blocks', function (done) { 27 | var fixture = fs.readFileSync(path.join('test', 'fixture.html')); 28 | var expected = fs.readFileSync(path.join('test', 'expected.html'), 'utf8'); 29 | 30 | var stream = plugin({ 31 | css: 'css/combined.css', 32 | js_files: ['js/one.js', 'js/two.js?ts=123', 'js/three.js?v=v1.5.3-1-g91cd575'], 33 | js_files_tpl: { 34 | src: 'js/with_tpl.js', 35 | tpl: '' 36 | }, 37 | js_files_tpl_multiple: { 38 | src: ['js/with_tpl.js', 'js/with_tpl_2.js'], 39 | tpl: '' 40 | }, 41 | js_files_tpl_2vars: { 42 | src: [['js/with_tpl_2vars1.js', 'js/with_tpl_2vars2.js']], 43 | tpl: '' 44 | }, 45 | js_files_tpl_2vars_multiple: { 46 | src: [['js/with_tpl_2vars1.js', 'js/with_tpl_2vars2.js'], ['js/with_tpl_2vars1_2.js', 'js/with_tpl_2vars2_2.js']], 47 | tpl: '' 48 | }, 49 | js_files_x_tpl: { 50 | src: null, 51 | tpl: '' 52 | }, 53 | js_files_x_tpl_src: { 54 | src: 'js', 55 | tpl: '' 56 | }, 57 | js_files_x_tpl_multiple: { 58 | src: ['js/with_tpl.js', 'js/with_tpl_2.js'], 59 | tpl: '' 60 | }, 61 | js_files_x_tpl_2vars: { 62 | src: [['js/with_tpl_2vars1.js', 'js/with_tpl_2vars2.js']], 63 | tpl: '' 64 | }, 65 | js_files_x_tpl_2vars_multiple: { 66 | src: [['js/with_tpl_2vars1.js', 'js/with_tpl_2vars2.js'], ['js/with_tpl_2vars1_2.js', 'js/with_tpl_2vars2_2.js']], 67 | tpl: '' 68 | }, 69 | 'lorem-ipsum': 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', 70 | 'stream-simple': stringToStream('Stream simple replacement').pipe(source('fake-vinyl.txt')), 71 | 'stream-advanced': { 72 | src: stringToStream('Stream advanced replacement').pipe(source('fake-vinyl.txt')) 73 | }, 74 | 'stream-special': { 75 | src: stringToStream('Stream $$ special replacement pattern').pipe(source('fake-vinyl.txt')) 76 | } 77 | }); 78 | 79 | compare(fixture, expected, stream, done); 80 | }); 81 | 82 | it('should work with inline html', function (done) { 83 | var fixture = ''; 84 | var expected = ''; 85 | 86 | var stream = plugin({css: 'css/combined.css'}); 87 | compare(new Buffer(fixture), expected, stream, done); 88 | }); 89 | 90 | it('should not fail if there are no build tags at all', function (done) { 91 | var fixture = ''; 92 | 93 | var stream = plugin({css: 'css/combined.css'}); 94 | compare(new Buffer(fixture), fixture, stream, done); 95 | }); 96 | 97 | describe('Options', function () { 98 | 99 | describe('keepUnassigned', function () { 100 | it('Should keep empty blocks', function (done) { 101 | var fixture = '\n\nSome text\n\n'; 102 | var expected = '\nSome text\n'; 103 | 104 | var stream = plugin({}, {keepUnassigned: true}); 105 | compare(new Buffer(fixture), expected, stream, done); 106 | }); 107 | 108 | it('Should remove empty blocks', function (done) { 109 | var fixture = '\n\nSome text\n\n'; 110 | var expected = '\n'; 111 | 112 | var stream = plugin(); 113 | compare(new Buffer(fixture), expected, stream, done); 114 | }); 115 | }); 116 | 117 | describe('keepBlockTags', function () { 118 | it('Should keep placeholder tags without arguments', function (done) { 119 | var fixture = '\n\nSome text\n\n'; 120 | var expected = '\n\n\n'; 121 | 122 | var stream = plugin({}, {keepBlockTags: true}); 123 | compare(new Buffer(fixture), expected, stream, done); 124 | }); 125 | 126 | it('Should keep placeholder tags with arguments', function (done) { 127 | var fixture = '\n\nSome text\n\n'; 128 | var expected = '\n\nipsum\n\n'; 129 | 130 | var stream = plugin({lorem: 'ipsum'}, {keepBlockTags: true}); 131 | compare(new Buffer(fixture), expected, stream, done); 132 | }); 133 | 134 | it('Should remove placeholder tags without arguments', function (done) { 135 | var fixture = '\n\nSome text\n\n'; 136 | var expected = '\n'; 137 | 138 | var stream = plugin(); 139 | compare(new Buffer(fixture), expected, stream, done); 140 | }); 141 | 142 | it('Should remove placeholder tags with arguments', function (done) { 143 | var fixture = '\n\nSome text\n\n'; 144 | var expected = '\nipsum\n'; 145 | 146 | var stream = plugin({lorem: 'ipsum'}); 147 | compare(new Buffer(fixture), expected, stream, done); 148 | }); 149 | 150 | it('Should keep indentation', function (done) { 151 | var fixture = '\n \n Some text\n \n'; 152 | var expected = '\n \n \n'; 153 | 154 | var stream = plugin({}, {keepBlockTags: true}); 155 | compare(new Buffer(fixture), expected, stream, done); 156 | }); 157 | }); 158 | 159 | describe('resolvePaths', function () { 160 | it('Should resolve relative paths', function (done) { 161 | var fixture = '\n\n\n\n'; 162 | var expected = '\n\n'; 163 | 164 | var stream = plugin({js: 'lib/script.js'}, {resolvePaths: true}); 165 | compare(new Buffer(fixture), expected, stream, done); 166 | }); 167 | }); 168 | }); 169 | 170 | describe('Legacy versions', function () { 171 | it('[version <1.2] should keep empty blocks (keepUnassigned = true)', function (done) { 172 | var fixture = '\n\nThis should be removed if "keepUnassigned" is false\n\n'; 173 | var expected = '\nThis should be removed if "keepUnassigned" is false\n'; 174 | 175 | var stream = plugin({}, true); 176 | compare(new Buffer(fixture), expected, stream, done); 177 | }); 178 | 179 | it('[version <1.2] should remove empty blocks (keepUnassigned = false)', function (done) { 180 | var fixture = '\n\nThis should be removed if "keepUnassigned" is false\n\n'; 181 | var expected = '\n'; 182 | 183 | var stream = plugin(); 184 | compare(new Buffer(fixture), expected, stream, done); 185 | }); 186 | }); 187 | 188 | }); 189 | -------------------------------------------------------------------------------- /test/expected.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Test file 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. 37 | 38 | Stream simple replacement 39 | 40 | Stream advanced replacement 41 | 42 | Stream $$ special replacement pattern 43 | 44 | 45 | -------------------------------------------------------------------------------- /test/fixture.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Test file 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /test/null.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var plugin = require('..'); 4 | var File = require('vinyl'); 5 | var assert = require('assert'); 6 | 7 | describe('null files', function () { 8 | it('should be passed through', function (done) { 9 | plugin() 10 | .once('data', function (file) { 11 | assert(file.isNull()); 12 | done(); 13 | }) 14 | .end(new File({contents: null})); 15 | }); 16 | }); 17 | -------------------------------------------------------------------------------- /test/stream.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var plugin = require('..'); 4 | var fs = require('fs'); 5 | var path = require('path'); 6 | var File = require('vinyl'); 7 | var assert = require('assert'); 8 | var concatStream = require('concat-stream'); 9 | var stringToStream = require('from2-string'); 10 | var source = require('vinyl-source-stream'); 11 | 12 | function compare(fixture, expected, stream, done) { 13 | var fakeFile = new File({ 14 | base: path.resolve('www'), 15 | path: path.resolve('www', 'pages', 'index.html'), 16 | contents: fixture 17 | }); 18 | 19 | stream.write(fakeFile); 20 | 21 | stream.once('data', function (file) { 22 | assert(file.isStream()); 23 | 24 | file.contents.pipe(concatStream({encoding: 'string'}, function (data) { 25 | assert.equal(data, expected); 26 | done(); 27 | })); 28 | }); 29 | } 30 | 31 | describe('Stream mode', function () { 32 | it('should replace blocks', function (done) { 33 | var fixture = fs.createReadStream(path.join('test', 'fixture.html')); 34 | var expected = fs.readFileSync(path.join('test', 'expected.html'), 'utf8'); 35 | 36 | var stream = plugin({ 37 | css: 'css/combined.css', 38 | js_files: ['js/one.js', 'js/two.js?ts=123', 'js/three.js?v=v1.5.3-1-g91cd575'], 39 | js_files_tpl: { 40 | src: 'js/with_tpl.js', 41 | tpl: '' 42 | }, 43 | js_files_tpl_multiple: { 44 | src: ['js/with_tpl.js', 'js/with_tpl_2.js'], 45 | tpl: '' 46 | }, 47 | js_files_tpl_2vars: { 48 | src: [['js/with_tpl_2vars1.js', 'js/with_tpl_2vars2.js']], 49 | tpl: '' 50 | }, 51 | js_files_tpl_2vars_multiple: { 52 | src: [['js/with_tpl_2vars1.js', 'js/with_tpl_2vars2.js'], ['js/with_tpl_2vars1_2.js', 'js/with_tpl_2vars2_2.js']], 53 | tpl: '' 54 | }, 55 | js_files_x_tpl: { 56 | src: null, 57 | tpl: '' 58 | }, 59 | js_files_x_tpl_src: { 60 | src: 'js', 61 | tpl: '' 62 | }, 63 | js_files_x_tpl_multiple: { 64 | src: ['js/with_tpl.js', 'js/with_tpl_2.js'], 65 | tpl: '' 66 | }, 67 | js_files_x_tpl_2vars: { 68 | src: [['js/with_tpl_2vars1.js', 'js/with_tpl_2vars2.js']], 69 | tpl: '' 70 | }, 71 | js_files_x_tpl_2vars_multiple: { 72 | src: [['js/with_tpl_2vars1.js', 'js/with_tpl_2vars2.js'], ['js/with_tpl_2vars1_2.js', 'js/with_tpl_2vars2_2.js']], 73 | tpl: '' 74 | }, 75 | 'lorem-ipsum': 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.', 76 | 'stream-simple': stringToStream('Stream simple replacement').pipe(source('fake-vinyl.txt')), 77 | 'stream-advanced': { 78 | src: stringToStream('Stream advanced replacement').pipe(source('fake-vinyl.txt')) 79 | }, 80 | 'stream-special': { 81 | src: stringToStream('Stream $$ special replacement pattern').pipe(source('fake-vinyl.txt')) 82 | } 83 | }); 84 | 85 | compare(fixture, expected, stream, done); 86 | }); 87 | 88 | it('should work with inline html', function (done) { 89 | var fixture = ''; 90 | var expected = ''; 91 | 92 | var stream = plugin({css: 'css/combined.css'}); 93 | compare(stringToStream(fixture), expected, stream, done); 94 | }); 95 | 96 | it('should not fail if there are no build tags at all', function (done) { 97 | var fixture = ''; 98 | 99 | var stream = plugin({css: 'css/combined.css'}); 100 | compare(stringToStream(fixture), fixture, stream, done); 101 | }); 102 | 103 | describe('Options', function () { 104 | 105 | describe('keepUnassigned', function () { 106 | it('Should keep empty blocks', function (done) { 107 | var fixture = '\n\nSome text\n\n'; 108 | var expected = '\nSome text\n'; 109 | 110 | var stream = plugin({}, {keepUnassigned: true}); 111 | compare(stringToStream(fixture), expected, stream, done); 112 | }); 113 | 114 | it('Should remove empty blocks', function (done) { 115 | var fixture = '\n\nSome text\n\n'; 116 | var expected = '\n'; 117 | 118 | var stream = plugin(); 119 | compare(stringToStream(fixture), expected, stream, done); 120 | }); 121 | }); 122 | 123 | describe('keepBlockTags', function () { 124 | it('Should keep placeholder tags without arguments', function (done) { 125 | var fixture = '\n\nSome text\n\n'; 126 | var expected = '\n\n\n'; 127 | 128 | var stream = plugin({}, {keepBlockTags: true}); 129 | compare(stringToStream(fixture), expected, stream, done); 130 | }); 131 | 132 | it('Should keep placeholder tags with arguments', function (done) { 133 | var fixture = '\n\nSome text\n\n'; 134 | var expected = '\n\nipsum\n\n'; 135 | 136 | var stream = plugin({lorem: 'ipsum'}, {keepBlockTags: true}); 137 | compare(stringToStream(fixture), expected, stream, done); 138 | }); 139 | 140 | it('Should remove placeholder tags without arguments', function (done) { 141 | var fixture = '\n\nSome text\n\n'; 142 | var expected = '\n'; 143 | 144 | var stream = plugin(); 145 | compare(stringToStream(fixture), expected, stream, done); 146 | }); 147 | 148 | it('Should remove placeholder tags with arguments', function (done) { 149 | var fixture = '\n\nSome text\n\n'; 150 | var expected = '\nipsum\n'; 151 | 152 | var stream = plugin({lorem: 'ipsum'}); 153 | compare(stringToStream(fixture), expected, stream, done); 154 | }); 155 | 156 | it('Should keep indentation', function (done) { 157 | var fixture = '\n \n Some text\n \n'; 158 | var expected = '\n \n \n'; 159 | 160 | var stream = plugin({}, {keepBlockTags: true}); 161 | compare(stringToStream(fixture), expected, stream, done); 162 | }); 163 | }); 164 | 165 | describe('resolvePaths', function () { 166 | it('Should resolve relative paths', function (done) { 167 | var fixture = '\n\n\n\n'; 168 | var expected = '\n\n'; 169 | 170 | var stream = plugin({js: 'lib/script.js'}, {resolvePaths: true}); 171 | compare(stringToStream(fixture), expected, stream, done); 172 | }); 173 | }); 174 | }); 175 | 176 | describe('Legacy versions', function () { 177 | it('[version <1.2] should keep empty blocks (keepUnused = true)', function (done) { 178 | var fixture = '\n\nThis should not be removed\n\n'; 179 | var expected = '\nThis should not be removed\n'; 180 | 181 | var stream = plugin({}, true); 182 | compare(stringToStream(fixture), expected, stream, done); 183 | }); 184 | 185 | it('[version <1.2] should remove empty blocks (keepUnused = false)', function (done) { 186 | var fixture = '\n\nThis should be removed\n\n'; 187 | var expected = '\n'; 188 | 189 | var stream = plugin(); 190 | compare(stringToStream(fixture), expected, stream, done); 191 | }); 192 | }); 193 | 194 | }); 195 | --------------------------------------------------------------------------------