├── .gitignore ├── .travis.yml ├── README.md ├── index.js ├── package.json └── test └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "0.10" -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | gulp-concat-sourcemap [![Build Status](https://travis-ci.org/mikach/gulp-concat-sourcemap.png?branch=master)](https://travis-ci.org/mikach/gulp-concat-sourcemap) 2 | ===================== 3 | 4 | Concatenate files and generate a source map file. 5 | 6 | Based on https://github.com/wearefractal/gulp-concat 7 | 8 | ### Usage 9 | 10 | ```javascript 11 | var concat = require('gulp-concat-sourcemap'); 12 | 13 | gulp.task('concat', function() { 14 | gulp.src(['file1.js', './js/*.js', 'file2.js']) 15 | .pipe(concat('all.js', options)) 16 | .pipe(gulp.dest('./dist/')); 17 | }); 18 | ``` 19 | 20 | ### Options 21 | 22 | `options.sourcesContent` 23 | An optional flag that tells the source map generator whether or not to include all original sources in the map. 24 | 25 | `options.sourceRoot` 26 | An optional root for all relative URLs in the source map. 27 | 28 | `options.prefix` 29 | Skip prefix for original filenames that appear in source maps. For example {prefix: 3} will drop 3 directories from file names. 30 | 31 | `options.sourceMappingBaseURL` 32 | Add this to the beginning of sourceMappingURL. -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | var through = require('through'); 6 | 7 | var PluginError = require('gulp-util').PluginError; 8 | var File = require('gulp-util').File; 9 | 10 | var SourceMapConsumer = require('source-map').SourceMapConsumer; 11 | var SourceMapGenerator = require('source-map').SourceMapGenerator; 12 | var SourceNode = require('source-map').SourceNode; 13 | 14 | module.exports = function(fileName, opts) { 15 | if (!fileName) { 16 | throw new PluginError('gulp-concat-sourcemap', 'Missing fileName option for gulp-concat-sourcemap'); 17 | } 18 | 19 | opts = opts || {}; 20 | 21 | var firstFile = null; 22 | 23 | var sourceNode = new SourceNode(); 24 | 25 | function bufferContents(file) { 26 | if (file.isNull()) return; // ignore 27 | if (file.isStream()) return this.emit('error', new PluginError('gulp-concat-sourcemap', 'Streaming not supported')); 28 | 29 | if (!firstFile) firstFile = file; 30 | 31 | var rel = path.relative(file.cwd, file.path).replace(/\\/g, '/'); 32 | 33 | if(opts.prefix) { 34 | var p = opts.prefix; 35 | while(p-- > 0) { 36 | rel = rel.substring(rel.indexOf('/') + 1); 37 | } 38 | } 39 | 40 | // remove utf-8 byte order mark 41 | if (file.contents[0] === 0xEF && file.contents[1] === 0xBB && file.contents[2] === 0xBF) { 42 | file.contents = file.contents.slice(3); 43 | } 44 | 45 | if(file.sourceMap && file.sourceMap.mappings != '') { 46 | sourceNode.add(SourceNode.fromStringWithSourceMap(file.contents.toString('utf8') + '\n\n', new SourceMapConsumer(file.sourceMap))); 47 | } else { 48 | file.contents.toString('utf8').split('\n').forEach(function(line, j){ 49 | sourceNode.add(new SourceNode(j + 1, 0, rel, line + '\n')); 50 | }); 51 | sourceNode.add('\n'); 52 | } 53 | 54 | 55 | if (opts.sourcesContent) { 56 | sourceNode.setSourceContent(rel, file.contents.toString('utf8')); 57 | } 58 | } 59 | 60 | function endStream(){ 61 | if (!firstFile) return this.emit('end'); 62 | 63 | var contentPath = path.join(firstFile.base, fileName), 64 | mapPath = contentPath + '.map'; 65 | 66 | if(!firstFile.sourceMap) { 67 | if (/\.css$/.test(fileName)) { 68 | sourceNode.add('/*# sourceMappingURL=' + (opts.sourceMappingBaseURL || '') + fileName + '.map' + ' */'); 69 | } else { 70 | sourceNode.add('//# sourceMappingURL=' + (opts.sourceMappingBaseURL || '') + fileName + '.map'); 71 | } 72 | } 73 | 74 | var codeMap = sourceNode.toStringWithSourceMap({ 75 | file: fileName, 76 | sourceRoot: opts.sourceRoot || '' 77 | }); 78 | 79 | var sourceMap = codeMap.map.toJSON(); 80 | 81 | sourceMap.file = path.basename(sourceMap.file); 82 | 83 | var contentFile = new File({ 84 | cwd: firstFile.cwd, 85 | base: firstFile.base, 86 | path: contentPath, 87 | contents: new Buffer(codeMap.code) 88 | }); 89 | 90 | if(firstFile.sourceMap){ 91 | contentFile.sourceMap = sourceMap; 92 | } else { 93 | var mapFile = new File({ 94 | cwd: firstFile.cwd, 95 | base: firstFile.base, 96 | path: mapPath, 97 | contents: new Buffer(JSON.stringify(sourceMap, null, ' ')) 98 | }); 99 | } 100 | 101 | this.emit('data', contentFile); 102 | if(!firstFile.sourceMap) this.emit('data', mapFile); 103 | this.emit('end'); 104 | } 105 | 106 | return through(bufferContents, endStream); 107 | }; 108 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gulp-concat-sourcemap", 3 | "version": "1.3.1", 4 | "description": "Concatenate files and generate a source map file", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "mocha --reporter spec" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git://github.com/mikach/gulp-concat-sourcemap.git" 12 | }, 13 | "keywords": [ 14 | "gulpplugin", 15 | "gulp", 16 | "concat", 17 | "sourcemap" 18 | ], 19 | "author": { 20 | "name": "Michael", 21 | "email": "mikach27@gmail.com" 22 | }, 23 | "license": "MIT", 24 | "bugs": { 25 | "url": "https://github.com/mikach/gulp-concat-sourcemap/issues" 26 | }, 27 | "homepage": "https://github.com/mikach/gulp-concat-sourcemap", 28 | "dependencies": { 29 | "through": "^2.3.4", 30 | "gulp-util": "^2.2.14", 31 | "source-map": "0.1.33" 32 | }, 33 | "devDependencies": { 34 | "mocha": "^1.18.2", 35 | "chai": "^1.9.0" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | var chai = require('chai') 2 | 3 | var expect = chai.expect; 4 | 5 | var Stream = require('stream'); 6 | 7 | var File = require('gulp-util').File; 8 | 9 | var concat = require('..'); 10 | 11 | describe('gulp-concat-sourcemap', function() { 12 | it('should be a stream', function() { 13 | expect(concat('file.js')).to.be.instanceOf(Stream); 14 | }); 15 | 16 | it('should concat files', function(done) { 17 | var file1 = new File({ 18 | cwd: 'test', 19 | base: '/test', 20 | path: 'test/file1.js', 21 | contents: new Buffer('console.log(\'Hello\');') 22 | }); 23 | 24 | var file2 = new File({ 25 | cwd: 'test', 26 | base: '/test', 27 | path: 'test/file2.js', 28 | contents: new Buffer('console.log(\'World\');') 29 | }); 30 | 31 | var stream = concat('file.js'); 32 | 33 | var contentFile; 34 | stream.on('data', function(newFile) { 35 | if (!contentFile) { // contentFile 36 | contentFile = newFile; 37 | expect(String(newFile.contents)).to.be.equal( 38 | 'console.log(\'Hello\');' + 39 | '\n\nconsole.log(\'World\');\n\n' + 40 | '//# sourceMappingURL=file.js.map' 41 | ); 42 | } else { // mapFile 43 | expect(String(newFile.contents)).to.be.equal( 44 | '{\n \"version\": 3,\n \"file\": \"file.js\",' + 45 | '\n \"sources\": [\n \"file1.js\",\n \"file2.js\"\n ]' + 46 | ',\n \"names\": [],\n \"mappings\": \"AAAA;;ACAA\"\n}' 47 | ); 48 | done(); 49 | } 50 | }); 51 | 52 | stream.write(file1); 53 | stream.write(file2); 54 | stream.end(); 55 | }); 56 | 57 | it('should prefix sourcemap output', function(done) { 58 | var file1 = new File({ 59 | base: '/test', 60 | path: 'test/path/file1.js', 61 | contents: new Buffer('console.log(\'Hello\');') 62 | }); 63 | 64 | var file2 = new File({ 65 | base: '/test', 66 | path: 'test/path/file2.js', 67 | contents: new Buffer('console.log(\'World\');') 68 | }); 69 | 70 | var stream = concat('file.js', {prefix: 2}); 71 | 72 | var contentFile; 73 | stream.on('data', function(newFile) { 74 | if (!contentFile) { // contentFile 75 | contentFile = newFile; 76 | expect(String(newFile.contents)).to.be.equal( 77 | 'console.log(\'Hello\');' + 78 | '\n\nconsole.log(\'World\');\n\n' + 79 | '//# sourceMappingURL=file.js.map' 80 | ); 81 | } else { // mapFile 82 | expect(String(newFile.contents)).to.be.equal( 83 | '{\n \"version\": 3,\n \"file\": \"file.js\",' + 84 | '\n \"sources\": [\n \"file1.js\",\n \"file2.js\"\n ]' + 85 | ',\n \"names\": [],\n \"mappings\": \"AAAA;;ACAA\"\n}' 86 | ); 87 | done(); 88 | } 89 | }); 90 | 91 | stream.write(file1); 92 | stream.write(file2); 93 | stream.end(); 94 | }); 95 | 96 | it('should add sourceMappingBaseURL', function(done) { 97 | var file1 = new File({ 98 | cwd: 'test', 99 | base: '/test', 100 | path: 'test/file1.js', 101 | contents: new Buffer('console.log(\'Hello\');') 102 | }); 103 | 104 | var file2 = new File({ 105 | cwd: 'test', 106 | base: '/test', 107 | path: 'test/file2.js', 108 | contents: new Buffer('console.log(\'World\');') 109 | }); 110 | 111 | var stream = concat('file.js', { sourceMappingBaseURL: 'scripts/' }); 112 | 113 | var contentFile; 114 | stream.on('data', function(newFile) { 115 | if (!contentFile) { // contentFile 116 | contentFile = newFile; 117 | expect(String(newFile.contents)).to.be.equal( 118 | 'console.log(\'Hello\');' + 119 | '\n\nconsole.log(\'World\');\n\n' + 120 | '//# sourceMappingURL=scripts/file.js.map' 121 | ); 122 | } else { // mapFile 123 | expect(String(newFile.contents)).to.be.equal( 124 | '{\n \"version\": 3,\n \"file\": \"file.js\",' + 125 | '\n \"sources\": [\n \"file1.js\",\n \"file2.js\"\n ]' + 126 | ',\n \"names\": [],\n \"mappings\": \"AAAA;;ACAA\"\n}' 127 | ); 128 | done(); 129 | } 130 | }); 131 | 132 | stream.write(file1); 133 | stream.write(file2); 134 | stream.end(); 135 | }); 136 | it('should generate source content inline', function(done){ 137 | var file1 = new File({ 138 | base: '/test', 139 | path: 'test/path/file1.js', 140 | contents: new Buffer('console.log(\'Hello\');') 141 | }); 142 | 143 | var file2 = new File({ 144 | base: '/test', 145 | path: 'test/path/file2.js', 146 | contents: new Buffer('console.log(\'World\');') 147 | }); 148 | 149 | var stream = concat('file.js', {prefix: 2, sourcesContent: true}); 150 | 151 | var contentFile; 152 | stream.on('data', function(newFile) { 153 | if (!contentFile) { // contentFile 154 | contentFile = newFile; 155 | expect(String(newFile.contents)).to.be.equal( 156 | 'console.log(\'Hello\');' + 157 | '\n\nconsole.log(\'World\');\n\n' + 158 | '//# sourceMappingURL=file.js.map' 159 | ); 160 | } else { // mapFile 161 | expect(String(newFile.contents)).to.be.equal( 162 | "{\n " + 163 | "\"version\": 3,\n " + 164 | "\"file\": \"file.js\",\n " + 165 | "\"sources\": [\n " + 166 | "\"file1.js\",\n " + 167 | "\"file2.js\"\n ],\n " + 168 | "\"names\": [],\n " + 169 | "\"mappings\": \"AAAA;;ACAA\",\n " + 170 | "\"sourcesContent\": [\n " + 171 | "\"console.log('Hello');\",\n " + 172 | "\"console.log('World');\"\n " + 173 | "]\n" + 174 | "}"); 175 | done(); 176 | } 177 | }); 178 | 179 | stream.write(file1); 180 | stream.write(file2); 181 | stream.end(); 182 | }); 183 | 184 | it('should not produce a sourcemap file if sourceMap is truthy on the input file', function(done) { 185 | var file1 = new File({ 186 | cwd: 'test', 187 | base: '/test', 188 | path: 'test/file1.js', 189 | contents: new Buffer('console.log(\'Hello\');') 190 | }); 191 | file1.sourceMap = {version: 3, file:'file1.js', sources: ['file1.js'], mappings:""} 192 | 193 | var file2 = new File({ 194 | cwd: 'test', 195 | base: '/test', 196 | path: 'test/file2.js', 197 | contents: new Buffer('console.log(\'World\');') 198 | }); 199 | file2.sourceMap = {version: 3, file:'file2.js', sources: ['file2.js'], mappings:""} 200 | 201 | var stream = concat('file.js', { sourceMappingBaseURL: 'scripts/' }); 202 | 203 | stream.on('data', function(newFile) { 204 | expect(String(newFile.contents)).to.equal( 205 | 'console.log(\'Hello\');' + 206 | '\n\nconsole.log(\'World\');\n\n' 207 | ); 208 | expect(newFile.sourceMap).to.exist; 209 | expect(JSON.stringify(newFile.sourceMap, null, ' ')).to.be.equal( 210 | '{\n \"version\": 3,\n \"file\": \"file.js\",' + 211 | '\n \"sources\": [\n \"file1.js\",\n \"file2.js\"\n ]' + 212 | ',\n \"names\": [],\n \"mappings\": \"AAAA;;ACAA\"\n}' 213 | ); 214 | done(); 215 | }); 216 | 217 | stream.write(file1); 218 | stream.write(file2); 219 | stream.end(); 220 | }); 221 | 222 | it('should use the input sourcemap if it is available', function(done) { 223 | var file1 = new File({ 224 | cwd: 'test', 225 | base: '/test', 226 | path: 'test/file1.js', 227 | contents: new Buffer('console.log(\'Hello\');') 228 | }); 229 | file1.sourceMap = {version: 3, file:'file1.js', sources: ['file1.coffee'], mappings:"AAAA,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE;"} 230 | 231 | var file2 = new File({ 232 | cwd: 'test', 233 | base: '/test', 234 | path: 'test/file2.js', 235 | contents: new Buffer('console.log(\'World\');') 236 | }); 237 | file2.sourceMap = {version: 3, file:'file2.js', sources: ['file2.coffee'], mappings:"AAAA,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE;"} 238 | 239 | var stream = concat('file.js', { sourceMappingBaseURL: 'scripts/' }); 240 | 241 | stream.on('data', function(newFile) { 242 | expect(String(newFile.contents)).to.equal( 243 | 'console.log(\'Hello\');' + 244 | '\n\nconsole.log(\'World\');\n\n' 245 | ); 246 | expect(newFile.sourceMap).to.exist; 247 | expect(JSON.stringify(newFile.sourceMap, null, ' ')).to.be.equal( 248 | '{\n \"version\": 3,\n \"file\": \"file.js\",' + 249 | '\n \"sources\": [\n \"file1.coffee\",\n \"file2.coffee\"\n ]' + 250 | ',\n \"names\": [],\n \"mappings\": \"AAAA,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE;;ACAnB,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE\"\n}' 251 | ); 252 | done(); 253 | }); 254 | 255 | stream.write(file1); 256 | stream.write(file2); 257 | stream.end(); 258 | }); 259 | }); --------------------------------------------------------------------------------