Skip to content

Commit c4a9f89

Browse files
Jonathan RaoultVFK
Jonathan Raoult
authored andcommitted
Support Vinyl files stream as replacement input (#44)
* #25 Support Vinyl files stream as replacement input: - made parseTask totally asynchronous - refactor the top module function to work with the async parsing stage * #25 Support Vinyl files stream as replacement input: - removed useless dependencies - made it works when the src property is given nested arrays * #25 Support Vinyl files stream as replacement input: - added a forgotten undefined check - restored default value for tpl variable * #25 Support Vinyl files stream as replacement input: - replace undefined check by a falsy check * #25 Support Vinyl files stream as replacement input: - added bluebird as a dependency to extend support to Node 0.10 (needed Promise) * #25 Support Vinyl files stream as replacement input: - added documentation * #25 Support Vinyl files stream as replacement input: - fixed the case when using the "{task-name: replacement}" with replacement being a Vinyl stream - added unit tests for Vinyl streams as replacement
1 parent 837ec54 commit c4a9f89

File tree

8 files changed

+155
-61
lines changed

8 files changed

+155
-61
lines changed

lib/common.js

+84-32
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,48 @@
11
'use strict';
22

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+
346
module.exports = {
447
/**
548
* tasks = {
@@ -14,46 +57,55 @@ module.exports = {
1457
options = options || {};
1558

1659
var utilExtensions = /%f|%e/g;
17-
var tasks = {};
60+
var tasksByNames = {};
1861

19-
Object.keys(options).forEach(function (key) {
20-
var item = options[key];
21-
var src = [];
22-
var tpl = null;
23-
var uniqueExtensions = {};
24-
var result;
25-
var srcIsNull;
62+
var tasksPromises = Object.keys(options).map(function (name) {
2663

27-
if (typeof item.src !== 'undefined') {
28-
srcIsNull = item.src === null;
29-
src = src.concat(item.src);
30-
tpl = item.tpl;
31-
} else {
32-
src = src.concat(item);
33-
}
64+
var task = {
65+
src: [],
66+
tpl: null,
67+
uni: {},
68+
srcIsNull: false
69+
};
3470

35-
while (result = utilExtensions.exec(tpl)) {
36-
var type = result[0];
37-
var unique = {};
71+
return Promise
72+
.resolve()
73+
.then(function () {
74+
var item = options[name];
75+
var src = typeof item.src !== 'undefined' ? item.src : item;
3876

39-
if (uniqueExtensions[type]) {
40-
continue;
41-
}
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;
4286

43-
unique.regex = new RegExp(result[0], "g");
44-
unique.value = null;
45-
uniqueExtensions[type] = unique;
46-
}
87+
while (result = utilExtensions.exec(task.tpl)) {
88+
var type = result[0];
89+
var unique = {};
4790

48-
tasks[key] = {
49-
src: src,
50-
tpl: tpl,
51-
uni: uniqueExtensions,
52-
srcIsNull: srcIsNull
53-
};
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+
});
54103
});
55104

56-
return tasks;
105+
return Promise.all(tasksPromises)
106+
.then(function() {
107+
return tasksByNames;
108+
});
57109
},
58110

59111
regexMatchAll: function (string, regexp) {

lib/index.js

+27-22
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ var common = require('./common');
77
var Parser = require('./parser');
88

99
module.exports = function (options, userConfig) {
10-
var tasks = common.parseTasks(options);
10+
var tasksPromise = common.parseTasks(options);
1111

1212
var config = {
1313
keepUnassigned: false,
@@ -24,28 +24,33 @@ module.exports = function (options, userConfig) {
2424
return new Transform({
2525
objectMode: true,
2626
transform: function (file, enc, callback) {
27-
var parser = new Parser(clone(tasks), config, file);
28-
29-
if (file.isBuffer()) {
30-
parser.write(file.contents);
31-
parser.end();
32-
33-
var contents = new Buffer(0);
34-
parser.on('data', function (data) {
35-
contents = Buffer.concat([contents, data]);
36-
});
37-
parser.once('end', function () {
38-
file.contents = contents;
39-
callback(null, file);
40-
});
41-
return;
42-
}
43-
44-
if (file.isStream()) {
45-
file.contents = file.contents.pipe(parser);
46-
}
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+
}
4750

48-
callback(null, file);
51+
callback(null, file);
52+
})
53+
.catch(callback);
4954
}
5055
});
5156
};

package.json

+5-2
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,12 @@
3434
"node": ">= 0.9"
3535
},
3636
"dependencies": {
37+
"bluebird": "^3.1.1",
3738
"clone": "^1.0.2",
3839
"object-assign": "^4.0.1",
3940
"readable-stream": "^2.0.4",
40-
"slash": "^1.0.0"
41+
"slash": "^1.0.0",
42+
"vinyl-buffer": "^1.0.0"
4143
},
4244
"devDependencies": {
4345
"concat-stream": "^1.5.1",
@@ -46,7 +48,8 @@
4648
"istanbul": "^0.4.0",
4749
"istanbul-coveralls": "^1.0.3",
4850
"mocha": "^2.3.4",
49-
"vinyl": "^1.1.0"
51+
"vinyl": "^1.1.0",
52+
"vinyl-source-stream": "^1.1.0"
5053
},
5154
"license": "MIT"
5255
}

readme.md

+16-3
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ Everything here will be replaced
3232
Type: `Object` `{task-name: replacement}`
3333

3434
* **task-name** - The name of the block in your HTML.
35-
* **replacement** - `String|Array|Object` The replacement. See examples below.
35+
* **replacement** - `String|Array|stream.Readable|Object` The replacement. See examples below.
3636

3737
###### Simple example:
3838
```javascript
@@ -62,7 +62,7 @@ htmlreplace({
6262
}
6363
})
6464
```
65-
* **src** - `String|Array` Same thing as in simple example.
65+
* **src** - `String|Array|stream.Readable` Same thing as in simple example.
6666
* **tpl** - `String` Template string. Uses [util.format()](http://nodejs.org/api/util.html#util_util_format_format) internally.
6767

6868
> In the first example `%s` will be replaced with `img/avatar.png` producing `<img src="img/avatar.png" align="left">` as the result.
@@ -87,7 +87,7 @@ htmlreplace({
8787
})
8888

8989
```
90-
* **src** - `null|String|Array` Same as examples above but null if there are no standard replacements in the template.
90+
* **src** - `null|String|Array|stream.Readable` Same as examples above but null if there are no standard replacements in the template.
9191
* **tpl** - `String` Template string. Extended replacements do not use `util.format()` and are performed before standard replacements.
9292

9393
> 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 `<script src="xyzzy.js"></script>`.
@@ -99,6 +99,19 @@ Valid extended replacements are:
9999
* **%f** - this will be replaced with the filename, without an extension.
100100
* **%e** - this will be replaced with the extension including the `.` character.
101101

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: '<style>%s</style>'
110+
}
111+
})
112+
113+
```
114+
102115
#### options
103116
Type: `object`
104117

test/buffer.js

+7-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ var fs = require('fs');
55
var path = require('path');
66
var File = require('vinyl');
77
var assert = require('assert');
8+
var stringToStream = require('from2-string');
9+
var source = require('vinyl-source-stream');
810

911
function compare(fixture, expected, stream, done) {
1012
stream
@@ -64,7 +66,11 @@ describe('Buffer mode', function () {
6466
src: [['js/with_tpl_2vars1.js', 'js/with_tpl_2vars2.js'], ['js/with_tpl_2vars1_2.js', 'js/with_tpl_2vars2_2.js']],
6567
tpl: '<script data-src="%f.data" data-main="%s" src="%s"></script>'
6668
},
67-
'lorem-ipsum': 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'
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+
}
6874
});
6975

7076
compare(fixture, expected, stream, done);

test/expected.html

+4
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,9 @@
3434
<script data-src="index.data" data-main="js/with_tpl_2vars1_2.js" src="js/with_tpl_2vars2_2.js"></script>
3535

3636
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
37+
38+
Stream simple replacement
39+
40+
Stream advanced replacement
3741
</body>
3842
</html>

test/fixture.html

+6
Original file line numberDiff line numberDiff line change
@@ -52,5 +52,11 @@
5252

5353
<!-- build:lorem-ipsum -->
5454
<!-- endbuild -->
55+
56+
<!-- build:stream-simple -->
57+
<!-- endbuild -->
58+
59+
<!-- build:stream-advanced -->
60+
<!-- endbuild -->
5561
</body>
5662
</html>

test/stream.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ var File = require('vinyl');
77
var assert = require('assert');
88
var concatStream = require('concat-stream');
99
var stringToStream = require('from2-string');
10+
var source = require('vinyl-source-stream');
1011

1112
function compare(fixture, expected, stream, done) {
1213
var fakeFile = new File({
@@ -71,7 +72,11 @@ describe('Stream mode', function () {
7172
src: [['js/with_tpl_2vars1.js', 'js/with_tpl_2vars2.js'], ['js/with_tpl_2vars1_2.js', 'js/with_tpl_2vars2_2.js']],
7273
tpl: '<script data-src="%f.data" data-main="%s" src="%s"></script>'
7374
},
74-
'lorem-ipsum': 'Lorem ipsum dolor sit amet, consectetur adipiscing elit.'
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+
}
7580
});
7681

7782
compare(fixture, expected, stream, done);

0 commit comments

Comments
 (0)