├── .gitattributes ├── .github └── workflows │ └── test.yml ├── .gitignore ├── .jshintrc ├── AUTHORS ├── CHANGELOG ├── CONTRIBUTING.md ├── Gruntfile.js ├── LICENSE-MIT ├── README.md ├── docs ├── concat-examples.md ├── concat-options.md └── concat-overview.md ├── package-lock.json ├── package.json ├── tasks ├── concat.js └── lib │ ├── comment.js │ └── sourcemap.js └── test ├── concat_test.js ├── expected ├── custom_options ├── default_options ├── handling_invalid_files ├── overwrite ├── process_dir_path ├── process_function ├── sourcemap2_link.map ├── sourcemap3_embed.map ├── sourcemap_css.css ├── sourcemap_css.css.map ├── sourcemap_inline ├── sourcemap_js.js └── sourcemap_js.js.map ├── fixtures ├── banner.js ├── banner2.js ├── banner3.js ├── css1.css ├── css2.css ├── file0 ├── file1 ├── file2 ├── js1.js ├── js2.js ├── mappedsource ├── mappedsource_embed └── maps │ └── mappedsource_embed.map └── sourcemap_tester.html /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | /test/fixtures/* text eol=lf 3 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | 3 | on: [push, pull_request] 4 | 5 | env: 6 | FORCE_COLOR: 2 7 | 8 | jobs: 9 | run: 10 | name: Node ${{ matrix.node }} on ${{ matrix.os }} 11 | runs-on: ${{ matrix.os }} 12 | 13 | strategy: 14 | fail-fast: false 15 | matrix: 16 | node: [12, 14, 16] 17 | os: [ubuntu-latest, windows-latest] 18 | 19 | steps: 20 | - name: Clone repository 21 | uses: actions/checkout@v2 22 | 23 | - name: Set up Node.js 24 | uses: actions/setup-node@v2 25 | with: 26 | node-version: ${{ matrix.node }} 27 | 28 | - name: Install npm dependencies 29 | run: npm ci 30 | 31 | - name: Run tests 32 | run: npm test 33 | 34 | # We test multiple Windows shells because of prior stdout buffering issues 35 | # filed against Grunt. https://github.com/joyent/node/issues/3584 36 | - name: Run PowerShell tests 37 | run: "npm test # PowerShell" # Pass comment to PS for easier debugging 38 | shell: powershell 39 | if: startsWith(matrix.os, 'windows') 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | tmp 4 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "boss": true, 3 | "curly": true, 4 | "eqeqeq": true, 5 | "eqnull": true, 6 | "immed": true, 7 | "latedef": true, 8 | "newcap": true, 9 | "noarg": true, 10 | "node": true, 11 | "sub": true, 12 | "undef": true, 13 | "unused": true 14 | } 15 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | "Cowboy" Ben Alman (http://benalman.com/) 2 | Tyler Kellen (http://goingslowly.com/) 3 | Dan Wolff (http://danwolff.se/) 4 | Kyle Robinson Young 5 | Vlad Filippov 6 | XhmikosR 7 | Steven Benner 8 | Sindre Sorhus 9 | Michael "Z" Goddard 10 | GilbertSun 11 | cbotsikas 12 | Timo Tijhof 13 | Piotr Yordanov 14 | Nick Schonning 15 | MarcelloDiSimone 16 | Manuel Razzari 17 | Joshua Appelman 18 | Jacob Gable 19 | Brady Wetherington 20 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | v2.1.0: 2 | date: 2022-04-03 3 | changes: 4 | - Updated dependencies 5 | v2.0.0: 6 | date: 2021-10-07 7 | changes: 8 | - Update dependencies 9 | - Requires node.js v12+ 10 | v1.0.1: 11 | date: 2016-04-20 12 | changes: 13 | - Fix for concatenating multiple source map files. 14 | v1.0.0: 15 | date: 2016-02-20 16 | changes: 17 | - Update source-map to 0.5.3. 18 | - Tag Grunt as peerDep to >=0.4.0. 19 | - Make source maps generation a little faster. 20 | - Add `charset:utf-8` to `sourceMappingURL`. 21 | v0.5.1: 22 | date: 2015-02-20 23 | changes: 24 | - Fix path issues with source maps on Windows. 25 | v0.5.0: 26 | date: 2014-07-19 27 | changes: 28 | - Adds `sourceMap` option. 29 | v0.4.0: 30 | date: 2014-03-21 31 | changes: 32 | - README updates. 33 | - Output updates. 34 | v0.3.0: 35 | date: 2013-04-25 36 | changes: 37 | - Add option to process files with a custom function. 38 | v0.2.0: 39 | date: 2013-04-08 40 | changes: 41 | - Don't normalize separator to allow user to set LF even on a Windows environment. 42 | v0.1.3: 43 | date: 2013-02-22 44 | changes: 45 | - Support footer option. 46 | v0.1.2: 47 | date: 2013-02-15 48 | changes: 49 | - First official release for Grunt 0.4.0. 50 | v0.1.2rc6: 51 | date: 2013-01-18 52 | changes: 53 | - Updating grunt/gruntplugin dependencies to rc6. 54 | - Changing in-development grunt/gruntplugin dependency versions from tilde version ranges to specific versions. 55 | v0.1.2rc5: 56 | date: 2013-01-09 57 | changes: 58 | - Updating to work with grunt v0.4.0rc5. 59 | - Switching back to `this.files` API. 60 | v0.1.1: 61 | date: 2012-11-13 62 | changes: 63 | - Switch to `this.file` API internally. 64 | v0.1.0: 65 | date: 2012-10-03 66 | changes: 67 | - Work in progress, not yet officially released. 68 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Please see the [Contributing to grunt](https://gruntjs.com/contributing) guide for information on contributing to this project. 2 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /* 2 | * grunt-contrib-concat 3 | * http://gruntjs.com/ 4 | * 5 | * Copyright (c) 2016 "Cowboy" Ben Alman, contributors 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | 'use strict'; 10 | 11 | module.exports = function(grunt) { 12 | 13 | var path = require('path'); 14 | 15 | // Project configuration. 16 | grunt.initConfig({ 17 | jshint: { 18 | all: [ 19 | 'Gruntfile.js', 20 | 'tasks/**/*.js', 21 | '<%= nodeunit.tests %>' 22 | ], 23 | options: { 24 | jshintrc: '.jshintrc' 25 | } 26 | }, 27 | 28 | // Before generating any new files, remove any previously-created files. 29 | clean: { 30 | tests: ['tmp'] 31 | }, 32 | 33 | // Configuration to be run (and then tested). 34 | bannerProperty: 'AWESOME', 35 | concat: { 36 | default_options: { 37 | files: { 38 | 'tmp/default_options': ['test/fixtures/file1', 'test/fixtures/file2'] 39 | } 40 | }, 41 | custom_options: { 42 | options: { 43 | separator: '\n;\n', 44 | banner: '/* THIS TEST IS <%= bannerProperty %> */\n', 45 | footer: 'dude' 46 | }, 47 | files: { 48 | 'tmp/custom_options': ['test/fixtures/file1', 'test/fixtures/file2'] 49 | } 50 | }, 51 | handling_invalid_files: { 52 | src: ['test/fixtures/file1', 'invalid_file/should_warn/but_not_fail', 'test/fixtures/file2'], 53 | dest: 'tmp/handling_invalid_files', 54 | nonull: true 55 | }, 56 | process_function: { 57 | options: { 58 | process: function(src, filepath) { 59 | return '// Source: ' + filepath + '\n' + 60 | src.replace(/file(\d)/, 'f$1'); 61 | } 62 | }, 63 | files: { 64 | 'tmp/process_function': ['test/fixtures/file1', 'test/fixtures/file2'] 65 | } 66 | }, 67 | dir: { 68 | files: { 69 | // no pattern, just directory given, should not error 70 | 'tmp/process_dir_path': ['test/fixtures'] 71 | } 72 | }, 73 | overwrite_one: { 74 | files: { 75 | 'tmp/overwrite': ['test/fixtures/file1', 'test/fixtures/file2'] 76 | } 77 | }, 78 | overwrite_two: { 79 | files: { 80 | 'tmp/overwrite': ['test/fixtures/banner2.js'] 81 | } 82 | }, 83 | sourcemap_options: { 84 | options: { 85 | banner: '// banner\n// line in banner\n', 86 | separator: '\n// line in separator\n', 87 | footer: '\n// line in footer\n// footer', 88 | sourceMap: true, 89 | sourceMapStyle: 'inline' 90 | }, 91 | files: { 92 | 'tmp/sourcemap_inline': [ 93 | 'test/fixtures/file0', 94 | 'test/fixtures/file2' 95 | ] 96 | } 97 | }, 98 | sourcemap2_options: { 99 | options: { 100 | sourceMap: true, 101 | sourceMapName: function(dest) { 102 | return path.join( 103 | path.dirname(dest), 104 | 'maps', 105 | path.basename(dest) + '.map' 106 | ); 107 | }, 108 | sourceMapStyle: 'link' 109 | }, 110 | files: { 111 | 'tmp/sourcemap2_link': [ 112 | 'test/fixtures/mappedsource', 113 | 'test/fixtures/file2' 114 | ] 115 | } 116 | }, 117 | sourcemap3_options: { 118 | options: { 119 | sourceMap: true, 120 | sourceMapName: 'tmp/sourcemap3_embed_map.map' 121 | }, 122 | files: { 123 | 'tmp/sourcemap3_embed': [ 124 | 'test/fixtures/mappedsource_embed', 125 | 'test/fixtures/file1', 126 | 'test/fixtures/file2' 127 | ] 128 | } 129 | }, 130 | sourcemap_js: { 131 | options: { 132 | banner: '/*\nJS Banner\n*/\n', 133 | sourceMap: true 134 | }, 135 | files: { 136 | 'tmp/sourcemap_js.js': [ 137 | 'test/fixtures/js1.js', 138 | 'test/fixtures/js2.js' 139 | ] 140 | } 141 | }, 142 | sourcemap_css: { 143 | options: { 144 | banner: '/*\nCSS Banner\n*/\n', 145 | sourceMap: true 146 | }, 147 | files: { 148 | 'tmp/sourcemap_css.css': [ 149 | 'test/fixtures/css1.css', 150 | 'test/fixtures/css2.css' 151 | ] 152 | } 153 | } 154 | }, 155 | 156 | // Unit tests. 157 | nodeunit: { 158 | tests: ['test/*_test.js'] 159 | } 160 | 161 | }); 162 | 163 | // Actually load this plugin's task(s). 164 | grunt.loadTasks('tasks'); 165 | 166 | // These plugins provide necessary tasks. 167 | grunt.loadNpmTasks('grunt-contrib-jshint'); 168 | grunt.loadNpmTasks('grunt-contrib-clean'); 169 | grunt.loadNpmTasks('grunt-contrib-nodeunit'); 170 | grunt.loadNpmTasks('grunt-contrib-internal'); 171 | 172 | // Whenever the "test" task is run, first clean the "tmp" dir, then run this 173 | // plugin's task(s), then test the result. 174 | grunt.registerTask('test', ['jshint', 'clean', 'concat', 'nodeunit']); 175 | 176 | // By default, lint and run all tests. 177 | grunt.registerTask('default', ['test', 'build-contrib']); 178 | 179 | }; 180 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 "Cowboy" Ben Alman, contributors 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # grunt-contrib-concat v2.1.0 [](https://github.com/gruntjs/grunt-contrib-concat/actions?workflow=Tests) 2 | 3 | > Concatenate files. 4 | 5 | 6 | 7 | ## Getting Started 8 | 9 | If you haven't used [Grunt](https://gruntjs.com/) before, be sure to check out the [Getting Started](https://gruntjs.com/getting-started) guide, as it explains how to create a [Gruntfile](https://gruntjs.com/sample-gruntfile) as well as install and use Grunt plugins. Once you're familiar with that process, you may install this plugin with this command: 10 | 11 | ```shell 12 | npm install grunt-contrib-concat --save-dev 13 | ``` 14 | 15 | Once the plugin has been installed, it may be enabled inside your Gruntfile with this line of JavaScript: 16 | 17 | ```js 18 | grunt.loadNpmTasks('grunt-contrib-concat'); 19 | ``` 20 | 21 | 22 | 23 | 24 | ## Concat task 25 | _Run this task with the `grunt concat` command._ 26 | 27 | Task targets, files and options may be specified according to the Grunt [Configuring tasks](http://gruntjs.com/configuring-tasks) guide. 28 | 29 | ### Options 30 | 31 | #### separator 32 | Type: `String` 33 | Default: `grunt.util.linefeed` 34 | 35 | Concatenated files will be joined on this string. If you're post-processing concatenated JavaScript files with a minifier, you may need to use a semicolon `';\n'` as the separator. 36 | 37 | #### banner 38 | Type: `String` 39 | Default: `''` 40 | 41 | This string will be prepended to the beginning of the concatenated output. It is processed using [grunt.template.process][], using the default options. 42 | 43 | _(Default processing options are explained in the [grunt.template.process][] documentation)_ 44 | 45 | #### footer 46 | Type: `String` 47 | Default: `''` 48 | 49 | This string will be appended to the end of the concatenated output. It is processed using [grunt.template.process][], using the default options. 50 | 51 | _(Default processing options are explained in the [grunt.template.process][] documentation)_ 52 | 53 | #### stripBanners 54 | Type: `Boolean` `Object` 55 | Default: `false` 56 | 57 | Strip JavaScript banner comments from source files. 58 | 59 | * `false` - No comments are stripped. 60 | * `true` - `/* ... */` block comments are stripped, but _NOT_ `/*! ... */` comments. 61 | * `options` object: 62 | * By default, behaves as if `true` were specified. 63 | * `block` - If true, _all_ block comments are stripped. 64 | * `line` - If true, any contiguous _leading_ `//` line comments are stripped. 65 | 66 | #### process 67 | Type: `Boolean` `Object` `Function` 68 | Default: `false` 69 | 70 | Process source files before concatenating, either as [templates][] or with a custom function. 71 | 72 | * `false` - No processing will occur. 73 | * `true` - Process source files using [grunt.template.process][] defaults. 74 | * `data` object - Process source files using [grunt.template.process][], using the specified options. 75 | * `function(src, filepath)` - Process source files using the given function, called once for each file. The returned value will be used as source code. 76 | 77 | _(Default processing options are explained in the [grunt.template.process][] documentation)_ 78 | 79 | [templates]: https://github.com/gruntjs/grunt-docs/blob/master/grunt.template.md 80 | [grunt.template.process]: https://github.com/gruntjs/grunt-docs/blob/master/grunt.template.md#grunttemplateprocess 81 | 82 | #### sourceMap 83 | Type: `Boolean` 84 | Default: `false` 85 | 86 | Set to true to create a source map. The source map will be created alongside the destination file, and share the same file name with the `.map` extension appended to it. 87 | 88 | #### sourceMapName 89 | Type: `String` `Function` 90 | Default: `undefined` 91 | 92 | To customize the name or location of the generated source map, pass a string to indicate where to write the source map to. If a function is provided, the concat destination is passed as the argument and the return value will be used as the file name. 93 | 94 | #### sourceMapStyle 95 | Type: `String` 96 | Default: `embed` 97 | 98 | Determines the type of source map that is generated. The default value, `embed`, places the content of the sources directly into the map. `link` will reference the original sources in the map as links. `inline` will store the entire map as a data URI in the destination file. 99 | 100 | ### Usage Examples 101 | 102 | #### Concatenating with a custom separator 103 | 104 | In this example, running `grunt concat:dist` (or `grunt concat` because `concat` is a [multi task][multitask]) will concatenate the three specified source files (in order), joining files with `;` and writing the output to `dist/built.js`. 105 | 106 | ```js 107 | // Project configuration. 108 | grunt.initConfig({ 109 | concat: { 110 | options: { 111 | separator: ';', 112 | }, 113 | dist: { 114 | src: ['src/intro.js', 'src/project.js', 'src/outro.js'], 115 | dest: 'dist/built.js', 116 | }, 117 | }, 118 | }); 119 | ``` 120 | 121 | #### Banner comments 122 | 123 | In this example, running `grunt concat:dist` will first strip any preexisting banner comment from the `src/project.js` file, then concatenate the result with a newly-generated banner comment, writing the output to `dist/built.js`. 124 | 125 | This generated banner will be the contents of the `banner` template string interpolated with the config object. In this case, those properties are the values imported from the `package.json` file (which are available via the `pkg` config property) plus today's date. 126 | 127 | _Note: you don't have to use an external JSON file. It's also valid to create the `pkg` object inline in the config. That being said, if you already have a JSON file, you might as well reference it._ 128 | 129 | ```js 130 | // Project configuration. 131 | grunt.initConfig({ 132 | pkg: grunt.file.readJSON('package.json'), 133 | concat: { 134 | options: { 135 | stripBanners: true, 136 | banner: '/*! <%= pkg.name %> - v<%= pkg.version %> - ' + 137 | '<%= grunt.template.today("yyyy-mm-dd") %> */', 138 | }, 139 | dist: { 140 | src: ['src/project.js'], 141 | dest: 'dist/built.js', 142 | }, 143 | }, 144 | }); 145 | ``` 146 | 147 | #### Multiple targets 148 | 149 | In this example, running `grunt concat` will build two separate files. One "basic" version, with the main file essentially just copied to `dist/basic.js`, and another "with_extras" concatenated version written to `dist/with_extras.js`. 150 | 151 | While each concat target can be built individually by running `grunt concat:basic` or `grunt concat:extras`, running `grunt concat` will build all concat targets. This is because `concat` is a [multi task][multitask]. 152 | 153 | ```js 154 | // Project configuration. 155 | grunt.initConfig({ 156 | concat: { 157 | basic: { 158 | src: ['src/main.js'], 159 | dest: 'dist/basic.js', 160 | }, 161 | extras: { 162 | src: ['src/main.js', 'src/extras.js'], 163 | dest: 'dist/with_extras.js', 164 | }, 165 | }, 166 | }); 167 | ``` 168 | 169 | #### Multiple files per target 170 | 171 | Like the previous example, in this example running `grunt concat` will build two separate files. One "basic" version, with the main file essentially just copied to `dist/basic.js`, and another "with_extras" concatenated version written to `dist/with_extras.js`. 172 | 173 | This example differs in that both files are built under the same target. 174 | 175 | Using the `files` object, you can have list any number of source-destination pairs. 176 | 177 | ```js 178 | // Project configuration. 179 | grunt.initConfig({ 180 | concat: { 181 | basic_and_extras: { 182 | files: { 183 | 'dist/basic.js': ['src/main.js'], 184 | 'dist/with_extras.js': ['src/main.js', 'src/extras.js'], 185 | }, 186 | }, 187 | }, 188 | }); 189 | ``` 190 | 191 | #### Dynamic filenames 192 | 193 | Filenames can be generated dynamically by using `<%= %>` delimited underscore templates as filenames. 194 | 195 | In this example, running `grunt concat:dist` generates a destination file whose name is generated from the `name` and `version` properties of the referenced `package.json` file (via the `pkg` config property). 196 | 197 | ```js 198 | // Project configuration. 199 | grunt.initConfig({ 200 | pkg: grunt.file.readJSON('package.json'), 201 | concat: { 202 | dist: { 203 | src: ['src/main.js'], 204 | dest: 'dist/<%= pkg.name %>-<%= pkg.version %>.js', 205 | }, 206 | }, 207 | }); 208 | ``` 209 | 210 | #### Advanced dynamic filenames 211 | 212 | In this more involved example, running `grunt concat` will build two separate files (because `concat` is a [multi task][multitask]). The destination file paths will be expanded dynamically based on the specified templates, recursively if necessary. 213 | 214 | For example, if the `package.json` file contained `{"name": "awesome", "version": "1.0.0"}`, the files `dist/awesome/1.0.0/basic.js` and `dist/awesome/1.0.0/with_extras.js` would be generated. 215 | 216 | ```js 217 | // Project configuration. 218 | grunt.initConfig({ 219 | pkg: grunt.file.readJSON('package.json'), 220 | dirs: { 221 | src: 'src/files', 222 | dest: 'dist/<%= pkg.name %>/<%= pkg.version %>', 223 | }, 224 | concat: { 225 | basic: { 226 | src: ['<%= dirs.src %>/main.js'], 227 | dest: '<%= dirs.dest %>/basic.js', 228 | }, 229 | extras: { 230 | src: ['<%= dirs.src %>/main.js', '<%= dirs.src %>/extras.js'], 231 | dest: '<%= dirs.dest %>/with_extras.js', 232 | }, 233 | }, 234 | }); 235 | ``` 236 | 237 | #### Invalid or Missing Files Warning 238 | If you would like the `concat` task to warn if a given file is missing or invalid be sure to set `nonull` to `true`: 239 | 240 | ```js 241 | grunt.initConfig({ 242 | concat: { 243 | missing: { 244 | src: ['src/invalid_or_missing_file'], 245 | dest: 'compiled.js', 246 | nonull: true, 247 | }, 248 | }, 249 | }); 250 | ``` 251 | 252 | See [configuring files for a task](http://gruntjs.com/configuring-tasks#files) for how to configure file globbing in Grunt. 253 | 254 | 255 | #### Custom process function 256 | If you would like to do any custom processing before concatenating, use a custom process function: 257 | 258 | ```js 259 | grunt.initConfig({ 260 | concat: { 261 | dist: { 262 | options: { 263 | // Replace all 'use strict' statements in the code with a single one at the top 264 | banner: "'use strict';\n", 265 | process: function(src, filepath) { 266 | return '// Source: ' + filepath + '\n' + 267 | src.replace(/(^|\n)[ \t]*('use strict'|"use strict");?\s*/g, '$1'); 268 | }, 269 | }, 270 | files: { 271 | 'dist/built.js': ['src/project.js'], 272 | }, 273 | }, 274 | }, 275 | }); 276 | ``` 277 | 278 | [multitask]: http://gruntjs.com/creating-tasks#multi-tasks 279 | 280 | 281 | ## Release History 282 | 283 | * 2022-04-03 v2.1.0 Updated dependencies 284 | * 2021-10-07 v2.0.0 Update dependencies Requires node.js v12+ 285 | * 2016-04-20 v1.0.1 Fix for concatenating multiple source map files. 286 | * 2016-02-20 v1.0.0 Update source-map to 0.5.3. Tag Grunt as peerDep to >=0.4.0. Make source maps generation a little faster. Add `charset:utf-8` to `sourceMappingURL`. 287 | * 2015-02-20 v0.5.1 Fix path issues with source maps on Windows. 288 | * 2014-07-19 v0.5.0 Adds `sourceMap` option. 289 | * 2014-03-21 v0.4.0 README updates. Output updates. 290 | * 2013-04-25 v0.3.0 Add option to process files with a custom function. 291 | * 2013-04-08 v0.2.0 Don't normalize separator to allow user to set LF even on a Windows environment. 292 | * 2013-02-22 v0.1.3 Support footer option. 293 | * 2013-02-15 v0.1.2 First official release for Grunt 0.4.0. 294 | * 2013-01-18 v0.1.2rc6 Updating grunt/gruntplugin dependencies to rc6. Changing in-development grunt/gruntplugin dependency versions from tilde version ranges to specific versions. 295 | * 2013-01-09 v0.1.2rc5 Updating to work with grunt v0.4.0rc5. Switching back to `this.files` API. 296 | * 2012-11-13 v0.1.1 Switch to `this.file` API internally. 297 | * 2012-10-03 v0.1.0 Work in progress, not yet officially released. 298 | 299 | --- 300 | 301 | Task submitted by ["Cowboy" Ben Alman](http://benalman.com/) 302 | 303 | *This file was generated on Sun Apr 03 2022 07:55:57.* 304 | -------------------------------------------------------------------------------- /docs/concat-examples.md: -------------------------------------------------------------------------------- 1 | # Usage Examples 2 | 3 | ## Concatenating with a custom separator 4 | 5 | In this example, running `grunt concat:dist` (or `grunt concat` because `concat` is a [multi task][multitask]) will concatenate the three specified source files (in order), joining files with `;` and writing the output to `dist/built.js`. 6 | 7 | ```js 8 | // Project configuration. 9 | grunt.initConfig({ 10 | concat: { 11 | options: { 12 | separator: ';', 13 | }, 14 | dist: { 15 | src: ['src/intro.js', 'src/project.js', 'src/outro.js'], 16 | dest: 'dist/built.js', 17 | }, 18 | }, 19 | }); 20 | ``` 21 | 22 | ## Banner comments 23 | 24 | In this example, running `grunt concat:dist` will first strip any preexisting banner comment from the `src/project.js` file, then concatenate the result with a newly-generated banner comment, writing the output to `dist/built.js`. 25 | 26 | This generated banner will be the contents of the `banner` template string interpolated with the config object. In this case, those properties are the values imported from the `package.json` file (which are available via the `pkg` config property) plus today's date. 27 | 28 | _Note: you don't have to use an external JSON file. It's also valid to create the `pkg` object inline in the config. That being said, if you already have a JSON file, you might as well reference it._ 29 | 30 | ```js 31 | // Project configuration. 32 | grunt.initConfig({ 33 | pkg: grunt.file.readJSON('package.json'), 34 | concat: { 35 | options: { 36 | stripBanners: true, 37 | banner: '/*! <%= pkg.name %> - v<%= pkg.version %> - ' + 38 | '<%= grunt.template.today("yyyy-mm-dd") %> */', 39 | }, 40 | dist: { 41 | src: ['src/project.js'], 42 | dest: 'dist/built.js', 43 | }, 44 | }, 45 | }); 46 | ``` 47 | 48 | ## Multiple targets 49 | 50 | In this example, running `grunt concat` will build two separate files. One "basic" version, with the main file essentially just copied to `dist/basic.js`, and another "with_extras" concatenated version written to `dist/with_extras.js`. 51 | 52 | While each concat target can be built individually by running `grunt concat:basic` or `grunt concat:extras`, running `grunt concat` will build all concat targets. This is because `concat` is a [multi task][multitask]. 53 | 54 | ```js 55 | // Project configuration. 56 | grunt.initConfig({ 57 | concat: { 58 | basic: { 59 | src: ['src/main.js'], 60 | dest: 'dist/basic.js', 61 | }, 62 | extras: { 63 | src: ['src/main.js', 'src/extras.js'], 64 | dest: 'dist/with_extras.js', 65 | }, 66 | }, 67 | }); 68 | ``` 69 | 70 | ## Multiple files per target 71 | 72 | Like the previous example, in this example running `grunt concat` will build two separate files. One "basic" version, with the main file essentially just copied to `dist/basic.js`, and another "with_extras" concatenated version written to `dist/with_extras.js`. 73 | 74 | This example differs in that both files are built under the same target. 75 | 76 | Using the `files` object, you can have list any number of source-destination pairs. 77 | 78 | ```js 79 | // Project configuration. 80 | grunt.initConfig({ 81 | concat: { 82 | basic_and_extras: { 83 | files: { 84 | 'dist/basic.js': ['src/main.js'], 85 | 'dist/with_extras.js': ['src/main.js', 'src/extras.js'], 86 | }, 87 | }, 88 | }, 89 | }); 90 | ``` 91 | 92 | ## Dynamic filenames 93 | 94 | Filenames can be generated dynamically by using `<%= %>` delimited underscore templates as filenames. 95 | 96 | In this example, running `grunt concat:dist` generates a destination file whose name is generated from the `name` and `version` properties of the referenced `package.json` file (via the `pkg` config property). 97 | 98 | ```js 99 | // Project configuration. 100 | grunt.initConfig({ 101 | pkg: grunt.file.readJSON('package.json'), 102 | concat: { 103 | dist: { 104 | src: ['src/main.js'], 105 | dest: 'dist/<%= pkg.name %>-<%= pkg.version %>.js', 106 | }, 107 | }, 108 | }); 109 | ``` 110 | 111 | ## Advanced dynamic filenames 112 | 113 | In this more involved example, running `grunt concat` will build two separate files (because `concat` is a [multi task][multitask]). The destination file paths will be expanded dynamically based on the specified templates, recursively if necessary. 114 | 115 | For example, if the `package.json` file contained `{"name": "awesome", "version": "1.0.0"}`, the files `dist/awesome/1.0.0/basic.js` and `dist/awesome/1.0.0/with_extras.js` would be generated. 116 | 117 | ```js 118 | // Project configuration. 119 | grunt.initConfig({ 120 | pkg: grunt.file.readJSON('package.json'), 121 | dirs: { 122 | src: 'src/files', 123 | dest: 'dist/<%= pkg.name %>/<%= pkg.version %>', 124 | }, 125 | concat: { 126 | basic: { 127 | src: ['<%= dirs.src %>/main.js'], 128 | dest: '<%= dirs.dest %>/basic.js', 129 | }, 130 | extras: { 131 | src: ['<%= dirs.src %>/main.js', '<%= dirs.src %>/extras.js'], 132 | dest: '<%= dirs.dest %>/with_extras.js', 133 | }, 134 | }, 135 | }); 136 | ``` 137 | 138 | ## Invalid or Missing Files Warning 139 | If you would like the `concat` task to warn if a given file is missing or invalid be sure to set `nonull` to `true`: 140 | 141 | ```js 142 | grunt.initConfig({ 143 | concat: { 144 | missing: { 145 | src: ['src/invalid_or_missing_file'], 146 | dest: 'compiled.js', 147 | nonull: true, 148 | }, 149 | }, 150 | }); 151 | ``` 152 | 153 | See [configuring files for a task](http://gruntjs.com/configuring-tasks#files) for how to configure file globbing in Grunt. 154 | 155 | 156 | ## Custom process function 157 | If you would like to do any custom processing before concatenating, use a custom process function: 158 | 159 | ```js 160 | grunt.initConfig({ 161 | concat: { 162 | dist: { 163 | options: { 164 | // Replace all 'use strict' statements in the code with a single one at the top 165 | banner: "'use strict';\n", 166 | process: function(src, filepath) { 167 | return '// Source: ' + filepath + '\n' + 168 | src.replace(/(^|\n)[ \t]*('use strict'|"use strict");?\s*/g, '$1'); 169 | }, 170 | }, 171 | files: { 172 | 'dist/built.js': ['src/project.js'], 173 | }, 174 | }, 175 | }, 176 | }); 177 | ``` 178 | 179 | [multitask]: http://gruntjs.com/creating-tasks#multi-tasks 180 | -------------------------------------------------------------------------------- /docs/concat-options.md: -------------------------------------------------------------------------------- 1 | # Options 2 | 3 | ## separator 4 | Type: `String` 5 | Default: `grunt.util.linefeed` 6 | 7 | Concatenated files will be joined on this string. If you're post-processing concatenated JavaScript files with a minifier, you may need to use a semicolon `';\n'` as the separator. 8 | 9 | ## banner 10 | Type: `String` 11 | Default: `''` 12 | 13 | This string will be prepended to the beginning of the concatenated output. It is processed using [grunt.template.process][], using the default options. 14 | 15 | _(Default processing options are explained in the [grunt.template.process][] documentation)_ 16 | 17 | ## footer 18 | Type: `String` 19 | Default: `''` 20 | 21 | This string will be appended to the end of the concatenated output. It is processed using [grunt.template.process][], using the default options. 22 | 23 | _(Default processing options are explained in the [grunt.template.process][] documentation)_ 24 | 25 | ## stripBanners 26 | Type: `Boolean` `Object` 27 | Default: `false` 28 | 29 | Strip JavaScript banner comments from source files. 30 | 31 | * `false` - No comments are stripped. 32 | * `true` - `/* ... */` block comments are stripped, but _NOT_ `/*! ... */` comments. 33 | * `options` object: 34 | * By default, behaves as if `true` were specified. 35 | * `block` - If true, _all_ block comments are stripped. 36 | * `line` - If true, any contiguous _leading_ `//` line comments are stripped. 37 | 38 | ## process 39 | Type: `Boolean` `Object` `Function` 40 | Default: `false` 41 | 42 | Process source files before concatenating, either as [templates][] or with a custom function. 43 | 44 | * `false` - No processing will occur. 45 | * `true` - Process source files using [grunt.template.process][] defaults. 46 | * `data` object - Process source files using [grunt.template.process][], using the specified options. 47 | * `function(src, filepath)` - Process source files using the given function, called once for each file. The returned value will be used as source code. 48 | 49 | _(Default processing options are explained in the [grunt.template.process][] documentation)_ 50 | 51 | [templates]: https://github.com/gruntjs/grunt-docs/blob/master/grunt.template.md 52 | [grunt.template.process]: https://github.com/gruntjs/grunt-docs/blob/master/grunt.template.md#grunttemplateprocess 53 | 54 | ## sourceMap 55 | Type: `Boolean` 56 | Default: `false` 57 | 58 | Set to true to create a source map. The source map will be created alongside the destination file, and share the same file name with the `.map` extension appended to it. 59 | 60 | ## sourceMapName 61 | Type: `String` `Function` 62 | Default: `undefined` 63 | 64 | To customize the name or location of the generated source map, pass a string to indicate where to write the source map to. If a function is provided, the concat destination is passed as the argument and the return value will be used as the file name. 65 | 66 | ## sourceMapStyle 67 | Type: `String` 68 | Default: `embed` 69 | 70 | Determines the type of source map that is generated. The default value, `embed`, places the content of the sources directly into the map. `link` will reference the original sources in the map as links. `inline` will store the entire map as a data URI in the destination file. 71 | -------------------------------------------------------------------------------- /docs/concat-overview.md: -------------------------------------------------------------------------------- 1 | Task targets, files and options may be specified according to the Grunt [Configuring tasks](http://gruntjs.com/configuring-tasks) guide. 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "grunt-contrib-concat", 3 | "description": "Concatenate files.", 4 | "version": "2.1.0", 5 | "author": { 6 | "name": "Grunt Team", 7 | "url": "http://gruntjs.com/" 8 | }, 9 | "repository": "gruntjs/grunt-contrib-concat", 10 | "license": "MIT", 11 | "engines": { 12 | "node": ">=0.12.0" 13 | }, 14 | "main": "tasks/concat.js", 15 | "scripts": { 16 | "test": "grunt test" 17 | }, 18 | "dependencies": { 19 | "chalk": "^4.1.2", 20 | "source-map": "^0.5.3" 21 | }, 22 | "devDependencies": { 23 | "grunt": "^1.5.3", 24 | "grunt-contrib-clean": "^2.0.1", 25 | "grunt-contrib-internal": "^7.0.0", 26 | "grunt-contrib-jshint": "^3.2.0", 27 | "grunt-contrib-nodeunit": "^4.0.0" 28 | }, 29 | "keywords": [ 30 | "gruntplugin" 31 | ], 32 | "files": [ 33 | "tasks" 34 | ], 35 | "appveyor_id": "l42173901ms416km" 36 | } 37 | -------------------------------------------------------------------------------- /tasks/concat.js: -------------------------------------------------------------------------------- 1 | /* 2 | * grunt-contrib-concat 3 | * http://gruntjs.com/ 4 | * 5 | * Copyright (c) 2016 "Cowboy" Ben Alman, contributors 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | 'use strict'; 10 | 11 | module.exports = function(grunt) { 12 | 13 | // Internal lib. 14 | var comment = require('./lib/comment').init(grunt); 15 | var chalk = require('chalk'); 16 | var sourcemap = require('./lib/sourcemap').init(grunt); 17 | 18 | grunt.registerMultiTask('concat', 'Concatenate files.', function() { 19 | // Merge task-specific and/or target-specific options with these defaults. 20 | var options = this.options({ 21 | separator: grunt.util.linefeed, 22 | banner: '', 23 | footer: '', 24 | stripBanners: false, 25 | process: false, 26 | sourceMap: false, 27 | sourceMapName: undefined, 28 | sourceMapStyle: 'embed' 29 | }); 30 | 31 | // Normalize boolean options that accept options objects. 32 | if (options.stripBanners === true) { 33 | options.stripBanners = {}; 34 | } 35 | if (options.process === true) { 36 | options.process = {}; 37 | } 38 | 39 | // Process banner and footer. 40 | var banner = grunt.template.process(options.banner); 41 | var footer = grunt.template.process(options.footer); 42 | 43 | // Set a local variable for whether to build source maps or not. 44 | var sourceMap = options.sourceMap; 45 | 46 | // If content is not embedded and it will be modified, either exit or do 47 | // not make the source map. 48 | if ( 49 | sourceMap && options.sourceMapStyle === 'link' && 50 | (options.stripBanners || options.process) 51 | ) { 52 | // Warn and exit if --force isn't set. 53 | grunt.warn( 54 | 'stripBanners or process option is enabled. ' + 55 | 'Set sourceMapStyle option to \'embed\' or \'inline\'.' 56 | ); 57 | // --force is set, continue on without the source map. 58 | grunt.log.warn('Skipping creation of source maps.'); 59 | // Set sourceMap to false to keep maps from being constructed. 60 | sourceMap = false; 61 | } 62 | 63 | // Iterate over all src-dest file pairs. 64 | this.files.forEach(function(f) { 65 | // Initialize source map objects. 66 | var sourceMapHelper; 67 | if (sourceMap) { 68 | sourceMapHelper = sourcemap.helper(f, options); 69 | sourceMapHelper.add(banner); 70 | } 71 | 72 | // Concat banner + specified files + footer. 73 | var src = banner + f.src.filter(function(filepath) { 74 | // Warn on and remove invalid source files (if nonull was set). 75 | if (!grunt.file.exists(filepath)) { 76 | grunt.log.warn('Source file "' + filepath + '" not found.'); 77 | return false; 78 | } 79 | return true; 80 | }).map(function(filepath, i) { 81 | if (grunt.file.isDir(filepath)) { 82 | return; 83 | } 84 | // Read file source. 85 | var src = grunt.file.read(filepath); 86 | // Process files as templates if requested. 87 | if (typeof options.process === 'function') { 88 | src = options.process(src, filepath); 89 | } else if (options.process) { 90 | src = grunt.template.process(src, options.process); 91 | } 92 | // Strip banners if requested. 93 | if (options.stripBanners) { 94 | src = comment.stripBanner(src, options.stripBanners); 95 | } 96 | // Add the lines of this file to our map. 97 | if (sourceMapHelper) { 98 | src = sourceMapHelper.addlines(src, filepath); 99 | if (i < f.src.length - 1) { 100 | sourceMapHelper.add(options.separator); 101 | } 102 | } 103 | return src; 104 | }).join(options.separator) + footer; 105 | 106 | if (sourceMapHelper) { 107 | sourceMapHelper.add(footer); 108 | sourceMapHelper.write(); 109 | // Add sourceMappingURL to the end. 110 | src += sourceMapHelper.url(); 111 | } 112 | 113 | // Write the destination file. 114 | grunt.file.write(f.dest, src); 115 | 116 | // Print a success message. 117 | grunt.verbose.write('File ' + chalk.cyan(f.dest) + ' created.'); 118 | }); 119 | }); 120 | 121 | }; 122 | -------------------------------------------------------------------------------- /tasks/lib/comment.js: -------------------------------------------------------------------------------- 1 | /* 2 | * grunt-contrib-concat 3 | * http://gruntjs.com/ 4 | * 5 | * Copyright (c) 2016 "Cowboy" Ben Alman, contributors 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | 'use strict'; 10 | 11 | exports.init = function(/*grunt*/) { 12 | var exports = {}; 13 | 14 | // Return the given source code with any leading banner comment stripped. 15 | exports.stripBanner = function(src, options) { 16 | if (!options) { 17 | options = {}; 18 | } 19 | 20 | var m = []; 21 | if (options.line) { 22 | // Strip // ... leading banners. 23 | m.push('(?:.*\\/\\/.*\\r?\\n)+\\s*'); 24 | } 25 | if (options.block) { 26 | // Strips all /* ... */ block comment banners. 27 | m.push('\\/\\*[\\s\\S]*?\\*\\/'); 28 | } else { 29 | // Strips only /* ... */ block comment banners, excluding /*! ... */. 30 | m.push('\\/\\*[^!][\\s\\S]*?\\*\\/'); 31 | } 32 | var re = new RegExp('^\\s*(?:' + m.join('|') + ')\\s*', ''); 33 | return src.replace(re, ''); 34 | }; 35 | 36 | return exports; 37 | }; 38 | -------------------------------------------------------------------------------- /tasks/lib/sourcemap.js: -------------------------------------------------------------------------------- 1 | /* 2 | * grunt-contrib-concat 3 | * http://gruntjs.com/ 4 | * 5 | * Copyright (c) 2016 "Cowboy" Ben Alman, contributors 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | 'use strict'; 10 | 11 | exports.init = function(grunt) { 12 | var exports = {}; 13 | 14 | // Node first party libs 15 | var path = require('path'); 16 | 17 | // Third party libs 18 | var chalk = require('chalk'); 19 | var SourceMap = require('source-map'); 20 | var SourceMapConsumer = SourceMap.SourceMapConsumer; 21 | var SourceMapGenerator = SourceMap.SourceMapGenerator; 22 | 23 | var NO_OP = function(){}; 24 | 25 | function SourceMapConcatHelper(options) { 26 | this.files = options.files; 27 | this.dest = options.dest; 28 | this.options = options.options; 29 | this.line = 1; 30 | this.column = 0; 31 | 32 | // ensure we're using forward slashes, because these are URLs 33 | var file = path.relative(path.dirname(this.dest), this.files.dest).replace(/\\/g, '/'); 34 | var generator = new SourceMapGenerator({ 35 | file: file 36 | }); 37 | this.file = file; 38 | this.generator = generator; 39 | this.addMapping = function(genLine, genCol, orgLine, orgCol, source, name) { 40 | if (!source) { 41 | generator.addMapping({ 42 | generated: {line: genLine, column: genCol} 43 | }); 44 | } else { 45 | if (!name) { 46 | generator.addMapping({ 47 | generated: {line: genLine, column: genCol}, 48 | original: {line: orgLine, column: orgCol}, 49 | source: source 50 | }); 51 | } else { 52 | generator.addMapping({ 53 | generated: {line: genLine, column: genCol}, 54 | original: {line: orgLine, column: orgCol}, 55 | source: source, 56 | name: name 57 | }); 58 | } 59 | } 60 | }; 61 | } 62 | 63 | // Return an object that is used to track sourcemap data between calls. 64 | exports.helper = function(files, options) { 65 | // Figure out the source map destination. 66 | var dest = files.dest; 67 | if (options.sourceMapStyle === 'inline') { 68 | // Leave dest as is. It will be used to compute relative sources. 69 | } else if (typeof options.sourceMapName === 'string') { 70 | dest = options.sourceMapName; 71 | } else if (typeof options.sourceMapName === 'function') { 72 | dest = options.sourceMapName(dest); 73 | } else { 74 | dest += '.map'; 75 | } 76 | 77 | // Inline style and sourceMapName together doesn't work 78 | if (options.sourceMapStyle === 'inline' && options.sourceMapName) { 79 | grunt.log.warn( 80 | 'Source map will be inlined, sourceMapName option ignored.' 81 | ); 82 | } 83 | 84 | return new SourceMapConcatHelper({ 85 | files: files, 86 | dest: dest, 87 | options: options 88 | }); 89 | }; 90 | 91 | // Parse only to increment the generated file's column and line count 92 | SourceMapConcatHelper.prototype.add = function(src) { 93 | this._forEachTokenPosition(src); 94 | }; 95 | 96 | /** 97 | * Parse the source file into tokens and apply the provided callback 98 | * with the position of the token boundaries in the original file, and 99 | * in the generated file. 100 | * 101 | * @param src The sources to tokenize. Required 102 | * @param filename The name of the source file. Optional 103 | * @param callback What to do with the token position indices. Optional 104 | */ 105 | SourceMapConcatHelper.prototype._forEachTokenPosition = function(src, filename, callback) { 106 | var genLine = this.line; 107 | var genCol = this.column; 108 | var orgLine = 1; 109 | var orgCol = 0; 110 | // Tokenize on words, new lines, and white space. 111 | var tokens = src.split(/(\n|[^\S\n]+|\b)/g); 112 | if (!callback) { 113 | callback = NO_OP; 114 | } 115 | for (var i = 0, len = tokens.length; i < len; i++) { 116 | var token = tokens[i]; 117 | if (token) { 118 | // The if statement filters out empty strings. 119 | callback(genLine, genCol, orgLine, orgCol, filename); 120 | if (token === '\n') { 121 | ++orgLine; 122 | ++genLine; 123 | orgCol = 0; 124 | genCol = 0; 125 | } else { 126 | orgCol += token.length; 127 | genCol += token.length; 128 | } 129 | } 130 | } 131 | 132 | this.line = genLine; 133 | this.column = genCol; 134 | }; 135 | 136 | // Add the lines of a given file to the sourcemap. If in the file, store a 137 | // prior sourcemap and return src with sourceMappingURL removed. 138 | SourceMapConcatHelper.prototype.addlines = function(src, filename) { 139 | var sourceMapRegEx = /\n\/[*/][@#]\s+sourceMappingURL=((?:(?!\s+\*\/).)*).*/; 140 | var relativeFilename = path.relative(path.dirname(this.dest), filename); 141 | // sourceMap path references are URLs, so ensure forward slashes are used for paths passed to sourcemap library 142 | relativeFilename = relativeFilename.replace(/\\/g, '/'); 143 | if (sourceMapRegEx.test(src)) { 144 | var sourceMapFile = RegExp.$1; 145 | var sourceMapPath; 146 | 147 | var sourceContent; 148 | // Browserify, as an example, stores a datauri at sourceMappingURL. 149 | if (/data:application\/json;(charset.utf-8;)?base64,([^\s]+)/.test(sourceMapFile)) { 150 | // Set sourceMapPath to the file that the map is inlined. 151 | sourceMapPath = filename; 152 | sourceContent = new Buffer(RegExp.$2, 'base64').toString(); 153 | } else { 154 | // If sourceMapPath is relative, expand relative to the file 155 | // referring to it. 156 | sourceMapPath = path.resolve(path.dirname(filename), sourceMapFile); 157 | sourceContent = grunt.file.read(sourceMapPath); 158 | } 159 | var sourceMapDir = path.dirname(sourceMapPath); 160 | var sourceMap = JSON.parse(sourceContent); 161 | var sourceMapConsumer = new SourceMapConsumer(sourceMap); 162 | // Consider the relative path from source files to new sourcemap. 163 | var sourcePathToSourceMapPath = path.relative(path.dirname(this.dest), sourceMapDir); 164 | // Transfer the existing mappings into this mapping 165 | var initLine = this.line; 166 | var initCol = this.column; 167 | sourceMapConsumer.eachMapping(function(args){ 168 | var source; 169 | if (args.source) { 170 | source = path.join(sourcePathToSourceMapPath, args.source).replace(/\\/g, '/'); 171 | } else { 172 | source = null; 173 | } 174 | this.line = initLine + args.generatedLine - 1; 175 | if (this.line === initLine) { 176 | this.column = initCol + args.generatedColumn; 177 | } else { 178 | this.column = args.generatedColumn; 179 | } 180 | this.addMapping( 181 | this.line, 182 | this.column, 183 | args.originalLine, 184 | args.originalColumn, 185 | source, 186 | args.name 187 | ); 188 | }, this); 189 | if (sourceMap.sources && sourceMap.sources.length && sourceMap.sourcesContent) { 190 | for (var i = 0; i < sourceMap.sources.length; ++i) { 191 | this.generator.setSourceContent( 192 | path.join(sourcePathToSourceMapPath, sourceMap.sources[i]).replace(/\\/g, '/'), 193 | sourceMap.sourcesContent[i] 194 | ); 195 | } 196 | } 197 | // Remove the old sourceMappingURL. 198 | src = src.replace(sourceMapRegEx, ''); 199 | } else { 200 | // Otherwise perform a rudimentary tokenization of the source. 201 | this._forEachTokenPosition(src, relativeFilename, this.addMapping); 202 | } 203 | 204 | if (this.options.sourceMapStyle !== 'link') { 205 | this.generator.setSourceContent(relativeFilename, src); 206 | } 207 | 208 | return src; 209 | }; 210 | 211 | // Return the comment sourceMappingURL that must be appended to the 212 | // concatenated file. 213 | SourceMapConcatHelper.prototype.url = function() { 214 | // Create the map filepath. Either datauri or destination path. 215 | var mapfilepath; 216 | if (this.options.sourceMapStyle === 'inline') { 217 | var inlineMap = new Buffer(this._write()).toString('base64'); 218 | mapfilepath = 'data:application/json;base64,' + inlineMap; 219 | } else { 220 | // Compute relative path to source map destination. 221 | mapfilepath = path.relative(path.dirname(this.files.dest), this.dest); 222 | } 223 | // Create the sourceMappingURL. 224 | var url; 225 | if (/\.css$/.test(this.files.dest)) { 226 | url = '\n/*# sourceMappingURL=' + mapfilepath + ' */'; 227 | } else { 228 | url = '\n//# sourceMappingURL=' + mapfilepath; 229 | } 230 | 231 | return url; 232 | }; 233 | 234 | // Return a string for inline use or write the source map to disk. 235 | SourceMapConcatHelper.prototype._write = function() { 236 | // New sourcemap. 237 | var newSourceMap = this.generator.toJSON(); 238 | // Return a string for inline use or write the map. 239 | if (this.options.sourceMapStyle === 'inline') { 240 | grunt.verbose.writeln( 241 | 'Source map for ' + chalk.cyan(this.files.dest) + ' inlined.' 242 | ); 243 | return JSON.stringify(newSourceMap, null, ''); 244 | } 245 | grunt.file.write( 246 | this.dest, 247 | JSON.stringify(newSourceMap, null, '') 248 | ); 249 | grunt.verbose.writeln('Source map ' + chalk.cyan(this.dest) + ' created.'); 250 | 251 | }; 252 | 253 | // Non-private function to write the sourcemap. Shortcuts if writing a inline 254 | // style map. 255 | SourceMapConcatHelper.prototype.write = function() { 256 | if (this.options.sourceMapStyle !== 'inline') { 257 | this._write(); 258 | } 259 | }; 260 | 261 | return exports; 262 | }; 263 | -------------------------------------------------------------------------------- /test/concat_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var grunt = require('grunt'); 4 | var comment = require('../tasks/lib/comment').init(grunt); 5 | 6 | function getNormalizedFile(filepath) { 7 | return grunt.util.normalizelf(grunt.file.read(filepath)); 8 | } 9 | 10 | exports.concat = { 11 | default_options: function(test) { 12 | test.expect(1); 13 | 14 | var actual = getNormalizedFile('tmp/default_options'); 15 | var expected = getNormalizedFile('test/expected/default_options'); 16 | test.equal(actual, expected, 'should describe what the default behavior is.'); 17 | 18 | test.done(); 19 | }, 20 | custom_options: function(test) { 21 | test.expect(1); 22 | 23 | var actual = getNormalizedFile('tmp/custom_options'); 24 | var expected = getNormalizedFile('test/expected/custom_options'); 25 | test.equal(actual, expected, 'should utilize custom banner, footer and separator.'); 26 | 27 | test.done(); 28 | }, 29 | handling_invalid_files: function(test) { 30 | test.expect(1); 31 | 32 | var actual = getNormalizedFile('tmp/handling_invalid_files'); 33 | var expected = getNormalizedFile('test/expected/handling_invalid_files'); 34 | test.equal(actual, expected, 'will have warned, but should not fail.'); 35 | 36 | test.done(); 37 | }, 38 | strip_banner: function(test) { 39 | test.expect(10); 40 | 41 | var src = getNormalizedFile('test/fixtures/banner.js'); 42 | test.equal(comment.stripBanner(src), grunt.util.normalizelf('// Comment\n\n/* Comment */\n'), 'It should strip the top banner.'); 43 | test.equal(comment.stripBanner(src, {block: true}), grunt.util.normalizelf('// Comment\n\n/* Comment */\n'), 'It should strip the top banner.'); 44 | test.equal(comment.stripBanner(src, {block: true, line: true}), grunt.util.normalizelf('// Comment\n\n/* Comment */\n'), 'It should strip the top banner.'); 45 | 46 | src = getNormalizedFile('test/fixtures/banner2.js'); 47 | test.equal(comment.stripBanner(src), grunt.util.normalizelf('\n/*! SAMPLE\n * BANNER */\n\n// Comment\n\n/* Comment */\n'), 'It should not strip the top banner.'); 48 | test.equal(comment.stripBanner(src, {block: true}), grunt.util.normalizelf('// Comment\n\n/* Comment */\n'), 'It should strip the top banner.'); 49 | test.equal(comment.stripBanner(src, {block: true, line: true}), grunt.util.normalizelf('// Comment\n\n/* Comment */\n'), 'It should strip the top banner.'); 50 | 51 | src = getNormalizedFile('test/fixtures/banner3.js'); 52 | test.equal(comment.stripBanner(src), grunt.util.normalizelf('\n// This is\n// A sample\n// Banner\n\n// But this is not\n\n/* And neither\n * is this\n */\n'), 'It should not strip the top banner.'); 53 | test.equal(comment.stripBanner(src, {block: true}), grunt.util.normalizelf('\n// This is\n// A sample\n// Banner\n\n// But this is not\n\n/* And neither\n * is this\n */\n'), 'It should not strip the top banner.'); 54 | test.equal(comment.stripBanner(src, {line: true}), grunt.util.normalizelf('// But this is not\n\n/* And neither\n * is this\n */\n'), 'It should strip the top banner.'); 55 | test.equal(comment.stripBanner(src, {block: true, line: true}), grunt.util.normalizelf('// But this is not\n\n/* And neither\n * is this\n */\n'), 'It should strip the top banner.'); 56 | test.done(); 57 | }, 58 | process_function: function(test) { 59 | test.expect(1); 60 | 61 | var actual = getNormalizedFile('tmp/process_function'); 62 | var expected = getNormalizedFile('test/expected/process_function'); 63 | test.equal(actual, expected, 'should have processed file content.'); 64 | 65 | test.done(); 66 | }, 67 | process_dir_path: function(test) { 68 | test.expect(1); 69 | 70 | var actual = getNormalizedFile('tmp/process_dir_path'); 71 | var expected = getNormalizedFile('test/expected/process_dir_path'); 72 | test.equal(actual, expected, 'should have nothing.'); 73 | 74 | test.done(); 75 | }, 76 | overwrite: function(test) { 77 | test.expect(1); 78 | 79 | var actual = getNormalizedFile('tmp/overwrite'); 80 | var expected = getNormalizedFile('test/expected/overwrite'); 81 | test.equal(actual, expected, 'should overwrite contents.'); 82 | 83 | test.done(); 84 | }, 85 | sourcemap_options: function(test) { 86 | test.expect(5); 87 | 88 | var actual = getNormalizedFile('tmp/sourcemap_inline'); 89 | var expected = getNormalizedFile('test/expected/sourcemap_inline'); 90 | test.equal(actual, expected, 'should output the source with inlined map.'); 91 | 92 | actual = getNormalizedFile('tmp/maps/sourcemap2_link.map'); 93 | expected = getNormalizedFile('test/expected/sourcemap2_link.map'); 94 | test.equal(actual, expected, 'should output the constructed map.'); 95 | 96 | actual = getNormalizedFile('tmp/sourcemap3_embed_map.map'); 97 | expected = getNormalizedFile('test/expected/sourcemap3_embed.map'); 98 | test.equal(actual, expected, 'should output the constructed map.'); 99 | 100 | actual = getNormalizedFile('tmp/sourcemap_js.js.map'); 101 | expected = getNormalizedFile('test/expected/sourcemap_js.js.map'); 102 | test.equal(actual, expected, 'should output the js map.'); 103 | 104 | actual = getNormalizedFile('tmp/sourcemap_css.css.map'); 105 | expected = getNormalizedFile('test/expected/sourcemap_css.css.map'); 106 | test.equal(actual, expected, 'should output the css map.'); 107 | 108 | test.done(); 109 | } 110 | }; 111 | -------------------------------------------------------------------------------- /test/expected/custom_options: -------------------------------------------------------------------------------- 1 | /* THIS TEST IS AWESOME */ 2 | file1 3 | ; 4 | file2dude -------------------------------------------------------------------------------- /test/expected/default_options: -------------------------------------------------------------------------------- 1 | file1 2 | file2 -------------------------------------------------------------------------------- /test/expected/handling_invalid_files: -------------------------------------------------------------------------------- 1 | file1 2 | file2 -------------------------------------------------------------------------------- /test/expected/overwrite: -------------------------------------------------------------------------------- 1 | 2 | /*! SAMPLE 3 | * BANNER */ 4 | 5 | // Comment 6 | 7 | /* Comment */ 8 | -------------------------------------------------------------------------------- /test/expected/process_dir_path: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gruntjs/grunt-contrib-concat/3ab7d8761ae5d4d131abd5a1c56275270fd7a706/test/expected/process_dir_path -------------------------------------------------------------------------------- /test/expected/process_function: -------------------------------------------------------------------------------- 1 | // Source: test/fixtures/file1 2 | f1 3 | // Source: test/fixtures/file2 4 | f2 -------------------------------------------------------------------------------- /test/expected/sourcemap2_link.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../../test/fixtures/mappedsource","../../test/fixtures/believe","../../test/fixtures/file2"],"names":[],"mappings":"AAAA;ACAI;ADEJ,E;AEFA","file":"../sourcemap2_link","sourcesContent":[null,"map file1",null]} -------------------------------------------------------------------------------- /test/expected/sourcemap3_embed.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../test/fixtures/file1","../test/fixtures/mappedsource_embed","../test/fixtures/file2"],"names":[],"mappings":"AAAA;ACCA,E;ADDA;AEAA","file":"sourcemap3_embed","sourcesContent":["file1","file1","file2"]} -------------------------------------------------------------------------------- /test/expected/sourcemap_css.css: -------------------------------------------------------------------------------- 1 | /* 2 | CSS Banner 3 | */ 4 | body { 5 | text-align: left; 6 | } 7 | 8 | 9 | 10 | /* CSS is cool */ 11 | 12 | 13 | 14 | 15 | 16 | div { 17 | font-family: Roboto; 18 | } 19 | 20 | * { 21 | text-align: right; 22 | } 23 | 24 | 25 | 26 | /** 27 | 28 | Headers 29 | 30 | */ 31 | 32 | h2 { 33 | font-family: Arial; 34 | } 35 | 36 | /*# sourceMappingURL=sourcemap_css.css.map */ -------------------------------------------------------------------------------- /test/expected/sourcemap_css.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../test/fixtures/css1.css","../test/fixtures/css2.css"],"names":[],"mappings":";;;AAAA,IAAI,CAAC,CAAC;AACN,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC;AACnB,CAAC;AACD;AACA;AACA;AACA,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,EAAE;AACjB;AACA;AACA;AACA;AACA;AACA,GAAG,CAAC,CAAC;AACL,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC;AACtB,CAAC;;ACdD,CAAC,CAAC,CAAC;AACH,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC;AACpB,CAAC;AACD;AACA;AACA;AACA,GAAG;AACH;AACA,OAAO;AACP;AACA,EAAE;AACF;AACA,EAAE,CAAC,CAAC;AACJ,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,CAAC;AACrB,CAAC","file":"sourcemap_css.css","sourcesContent":["body {\n text-align: left;\n}\n\n\n\n/* CSS is cool */\n\n\n\n\n\ndiv {\n font-family: Roboto;\n}\n","* {\n text-align: right;\n}\n\n\n\n/**\n\nHeaders\n\n*/\n\nh2 {\n font-family: Arial;\n}\n"]} -------------------------------------------------------------------------------- /test/expected/sourcemap_inline: -------------------------------------------------------------------------------- 1 | // banner 2 | // line in banner 3 | this 4 | file 5 | + many 6 | lines 7 | // line in separator 8 | file2 9 | // line in footer 10 | // footer 11 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL3Rlc3QvZml4dHVyZXMvZmlsZTAiLCIuLi90ZXN0L2ZpeHR1cmVzL2ZpbGUyIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBQUEsSUFBSTtBQUNKLElBQUk7QUFDSixDQUFDLENBQUMsSUFBSTtBQUNOOztBQ0hBIiwiZmlsZSI6InNvdXJjZW1hcF9pbmxpbmUiLCJzb3VyY2VzQ29udGVudCI6WyJ0aGlzXG5maWxlXG4rIG1hbnlcbmxpbmVzIiwiZmlsZTIiXX0= -------------------------------------------------------------------------------- /test/expected/sourcemap_js.js: -------------------------------------------------------------------------------- 1 | /* 2 | JS Banner 3 | */ 4 | var x = 1; 5 | 6 | 7 | /** 8 | * Redefine "x" 9 | * @type {number} 10 | */ 11 | x = 10; 12 | 13 | var y = 2; 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | y = 5; 22 | debugger; 23 | // End of this test file 24 | 25 | //# sourceMappingURL=sourcemap_js.js.map -------------------------------------------------------------------------------- /test/expected/sourcemap_js.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["../test/fixtures/js1.js","../test/fixtures/js2.js"],"names":[],"mappings":";;;AAAA,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACV;AACA;AACA,GAAG;AACH,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;AACf,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC;AACjB,CAAC,EAAE;AACH,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;;ACPP,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACV;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACN,QAAQ,CAAC;AACT,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI","file":"sourcemap_js.js","sourcesContent":["var x = 1;\n\n\n/**\n * Redefine \"x\"\n * @type {number}\n */\nx = 10;\n","var y = 2;\n\n\n\n\n\n\n\ny = 5;\ndebugger;\n// End of this test file\n"]} -------------------------------------------------------------------------------- /test/fixtures/banner.js: -------------------------------------------------------------------------------- 1 | 2 | /* THIS 3 | * IS 4 | * A 5 | * SAMPLE 6 | * BANNER! 7 | */ 8 | 9 | // Comment 10 | 11 | /* Comment */ 12 | -------------------------------------------------------------------------------- /test/fixtures/banner2.js: -------------------------------------------------------------------------------- 1 | 2 | /*! SAMPLE 3 | * BANNER */ 4 | 5 | // Comment 6 | 7 | /* Comment */ 8 | -------------------------------------------------------------------------------- /test/fixtures/banner3.js: -------------------------------------------------------------------------------- 1 | 2 | // This is 3 | // A sample 4 | // Banner 5 | 6 | // But this is not 7 | 8 | /* And neither 9 | * is this 10 | */ 11 | -------------------------------------------------------------------------------- /test/fixtures/css1.css: -------------------------------------------------------------------------------- 1 | body { 2 | text-align: left; 3 | } 4 | 5 | 6 | 7 | /* CSS is cool */ 8 | 9 | 10 | 11 | 12 | 13 | div { 14 | font-family: Roboto; 15 | } 16 | -------------------------------------------------------------------------------- /test/fixtures/css2.css: -------------------------------------------------------------------------------- 1 | * { 2 | text-align: right; 3 | } 4 | 5 | 6 | 7 | /** 8 | 9 | Headers 10 | 11 | */ 12 | 13 | h2 { 14 | font-family: Arial; 15 | } 16 | -------------------------------------------------------------------------------- /test/fixtures/file0: -------------------------------------------------------------------------------- 1 | this 2 | file 3 | + many 4 | lines -------------------------------------------------------------------------------- /test/fixtures/file1: -------------------------------------------------------------------------------- 1 | file1 -------------------------------------------------------------------------------- /test/fixtures/file2: -------------------------------------------------------------------------------- 1 | file2 -------------------------------------------------------------------------------- /test/fixtures/js1.js: -------------------------------------------------------------------------------- 1 | var x = 1; 2 | 3 | 4 | /** 5 | * Redefine "x" 6 | * @type {number} 7 | */ 8 | x = 10; 9 | -------------------------------------------------------------------------------- /test/fixtures/js2.js: -------------------------------------------------------------------------------- 1 | var y = 2; 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | y = 5; 10 | debugger; 11 | // End of this test file 12 | -------------------------------------------------------------------------------- /test/fixtures/mappedsource: -------------------------------------------------------------------------------- 1 | // banner 2 | file1 3 | //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoibWFwcGVkc291cmNlIiwic291cmNlcyI6WyJtYXBwZWRzb3VyY2UiLCJiZWxpZXZlIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBO0FDQUk7QURFSixFIiwic291cmNlc0NvbnRlbnQiOltudWxsLCJtYXAgZmlsZTEiXX0K -------------------------------------------------------------------------------- /test/fixtures/mappedsource_embed: -------------------------------------------------------------------------------- 1 | file1 2 | //# sourceMappingURL=maps/mappedsource_embed.map -------------------------------------------------------------------------------- /test/fixtures/maps/mappedsource_embed.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"mappedsource_embed","sources":["../file1","../mappedsource_embed"],"names":[],"mappings":"AAAA;ACCA,E","sourcesContent":["file1",null]} -------------------------------------------------------------------------------- /test/sourcemap_tester.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 | 8 | 9 | 10 |