├── .gitignore ├── .jshintrc ├── .npmignore ├── .travis.yml ├── CONTRIBUTORS.md ├── Gruntfile.js ├── LICENSE ├── README.md ├── package-lock.json ├── package.json ├── tasks ├── angular-templates.js └── lib │ ├── appender.js │ └── compiler.js └── test ├── angular-templates_test.js ├── expected ├── callback_module.js ├── custom_angular.js ├── custom_bootstrap.js ├── custom_concat.js ├── custom_concat_usemin_not_found.js ├── custom_htmlmin.js ├── custom_module.js ├── custom_prefix.js ├── custom_source.js ├── custom_url.js ├── default_module.js ├── empty_file.js ├── full_url.js ├── html5.js ├── linebreak.js ├── regexp.js ├── relative_url.js ├── relative_url_expand_three.js ├── relative_url_expand_three_two.js ├── single_quotes.js ├── standalone.js ├── task_htmlmin.js ├── undefined_file.js ├── unmerged_files │ ├── empty.js │ ├── html5.js │ ├── one.js │ ├── undefined.js │ └── usemin.js ├── usemin.html ├── usemin │ ├── all.js │ ├── bar.css │ ├── bar.js │ └── foo.js ├── useminUgly.html └── useminUgly │ ├── all.js │ ├── bar.css │ ├── bar.js │ └── foo.js └── fixtures ├── empty.html ├── html5.html ├── linebreak.html ├── one.html ├── regexp.html ├── three ├── three.html └── three_two.html ├── two └── two.html ├── undefined.html ├── unmerged ├── level2 │ ├── empty.html │ ├── html5.html │ └── level3 │ │ └── one.html ├── undefined.html └── usemin.html ├── usemin.html ├── usemin ├── bar.css ├── bar.js └── foo.js ├── useminUgly.html └── useminUgly ├── bar.css ├── bar.js └── foo.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | tmp 4 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "curly": true, 3 | "eqeqeq": true, 4 | "immed": true, 5 | "latedef": true, 6 | "newcap": true, 7 | "noarg": true, 8 | "sub": true, 9 | "undef": true, 10 | "boss": true, 11 | "eqnull": true, 12 | "node": true 13 | } 14 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /npm-debug.log 3 | /tmp 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | language: node_js 3 | node_js: 4 | - 0.12 5 | - 4.2 6 | cache: 7 | directories: 8 | - node_modules 9 | -------------------------------------------------------------------------------- /CONTRIBUTORS.md: -------------------------------------------------------------------------------- 1 | # grunt-angular-templates Contributors 2 | 3 | *Source: [Contributor Graph][1]* 4 | 5 | 6 | - [Eric Clemmons](https://github.com/ericclemmons/grunt-angular-templates/commits?author=@ericclemmons) 7 | - [Chris Gross](https://github.com/ericclemmons/grunt-angular-templates/commits?author=@cgross) 8 | - [Mario J. Barchéin Molina](https://github.com/ericclemmons/grunt-angular-templates/commits?author=@mbarchein) 9 | - [Robert Klep](https://github.com/ericclemmons/grunt-angular-templates/commits?author=@robertklep) 10 | - [Kai Groner](https://github.com/ericclemmons/grunt-angular-templates/commits?author=@groner) 11 | - [DallonF](https://github.com/ericclemmons/grunt-angular-templates/commits?author=@dallonf) 12 | - [Mike Brevoort](https://github.com/ericclemmons/grunt-angular-templates/commits?author=@mbrevoort) 13 | - [Sid Wood](https://github.com/ericclemmons/grunt-angular-templates/commits?author=@sidwood) 14 | - [Tadeusz Wójcik](https://github.com/ericclemmons/grunt-angular-templates/commits?author=@codefather) 15 | - [Joe Grund](https://github.com/ericclemmons/grunt-angular-templates/commits?author=@jgrund) 16 | 17 | 18 | [1]: https://github.com/ericclemmons/grunt-angular-templates/contributors 19 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /* 2 | * grunt-angular-templates 3 | * https://github.com/ericclemmons/grunt-angular-templates 4 | * 5 | * Copyright (c) 2013 Eric Clemmons 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | 'use strict'; 10 | 11 | module.exports = function(grunt) { 12 | 13 | grunt.initConfig({ 14 | clean: { 15 | tests: 'tmp' 16 | }, 17 | copy: { 18 | tmp: { 19 | files: [{ 20 | expand: true, 21 | cwd: 'test/fixtures', 22 | src: ['usemin.html', 'useminUgly.html', 'usemin/*', 'useminUgly/*'], 23 | dest: 'tmp/' 24 | }] 25 | } 26 | }, 27 | nodeunit: { 28 | tests: ['test/*.js'] 29 | }, 30 | watch: { 31 | tests: '<%= nodeunit.tests %>', 32 | tasks: 'default' 33 | }, 34 | jshint: { 35 | all: ['Gruntfile.js', 'tasks/**/*.js', '<%= nodeunit.tests %>'], 36 | options: { 37 | jshintrc: '.jshintrc', 38 | } 39 | }, 40 | concat: { 41 | custom_concat: { 42 | src: 'test/fixtures/one.html', 43 | dest: 'tmp/custom_concat_combined.js', 44 | options: { 45 | separator: '\n\n' 46 | } 47 | } 48 | }, 49 | usemin: { 50 | html: 'tmp/useminUgly.html' 51 | }, 52 | useminWithoutUglify: { 53 | html: 'tmp/usemin.html' 54 | }, 55 | useminPrepare: { 56 | html: 'test/fixtures/useminUgly.html', 57 | options: { 58 | dest: 'tmp', 59 | staging: 'tmp', 60 | flow: { 61 | html: { 62 | steps: { 63 | js: ['concat', 'uglify'], 64 | css: ['concat', 'cssmin'] 65 | } 66 | } 67 | } 68 | } 69 | }, 70 | useminPrepareWithoutUglify: { 71 | html: 'test/fixtures/usemin.html', 72 | options: { 73 | dest: 'tmp', 74 | staging: 'tmp', 75 | flow: { 76 | html: { 77 | steps: { 78 | js: ['concat'], 79 | css: ['concat'] 80 | } 81 | } 82 | } 83 | } 84 | }, 85 | cssmin: {}, 86 | 87 | // All supported examples should be here 88 | ngtemplates: { 89 | // Change `angular` namespace to something else 90 | custom_angular: { 91 | src: ['test/fixtures/one.html', 'test/fixtures/two/**/*.html'], 92 | dest: 'tmp/custom_angular.js', 93 | options: { 94 | angular: 'myAngular' 95 | } 96 | }, 97 | 98 | // Custom CommonJS bootstrapper 99 | custom_bootstrap: { 100 | src: ['test/fixtures/one.html', 'test/fixtures/two/**/*.html'], 101 | dest: 'tmp/custom_bootstrap.js', 102 | options: { 103 | bootstrap: function(module, script) { 104 | return 'module.exports = function($templateCache) {\n' + script + '\n};\n'; 105 | } 106 | } 107 | }, 108 | 109 | // Append dest to existing concat target 110 | custom_concat: { 111 | src: ['test/fixtures/one.html', 'test/fixtures/two/**/*.html'], 112 | dest: 'tmp/custom_concat.js', 113 | options: { 114 | concat: 'custom_concat' 115 | } 116 | }, 117 | 118 | custom_usemin: { 119 | src: ['test/fixtures/one.html', 'test/fixtures/two/**/*.html'], 120 | dest: 'tmp/custom_concat_usemin.js', 121 | options: { 122 | usemin: 'useminUgly/all.js' 123 | } 124 | }, 125 | 126 | custom_usemin_not_found: { 127 | src: ['test/fixtures/one.html', 'test/fixtures/two/**/*.html'], 128 | dest: 'tmp/custom_concat_usemin_not_found.js', 129 | options: { 130 | usemin: 'useminUgly/not_found.js' 131 | } 132 | }, 133 | 134 | html5: { 135 | src: ['test/fixtures/html5.html'], 136 | dest: 'tmp/html5.js' 137 | }, 138 | 139 | // Minify the HTML 140 | custom_htmlmin: { 141 | src: ['test/fixtures/one.html', 'test/fixtures/two/**/*.html'], 142 | dest: 'tmp/custom_htmlmin.js', 143 | options: { 144 | htmlmin: { 145 | collapseBooleanAttributes: true, 146 | collapseWhitespace: true, 147 | removeAttributeQuotes: true, 148 | removeComments: true, 149 | removeEmptyAttributes: true, 150 | removeRedundantAttributes: true, 151 | removeScriptTypeAttributes: true, 152 | removeStyleLinkTypeAttributes: true 153 | } 154 | } 155 | }, 156 | 157 | missing_htmlmin: { 158 | src: ['test/fixtures/one.html', 'test/fixtures/two/**/*.html'], 159 | dest: 'tmp/missing_htmlmin.js', 160 | options: { 161 | htmlmin: null 162 | } 163 | }, 164 | 165 | // Minify the HTML, but using another tasks' settings 166 | task_htmlmin: { 167 | src: ['test/fixtures/one.html', 'test/fixtures/two/**/*.html'], 168 | dest: 'tmp/task_htmlmin.js', 169 | options: { 170 | htmlmin: '<%= ngtemplates.custom_htmlmin.options.htmlmin %>' 171 | } 172 | }, 173 | 174 | // Default `module` option to the sub-task name (`default_module`) 175 | default_module: { 176 | src: ['test/fixtures/one.html', 'test/fixtures/two/**/*.html'], 177 | dest: 'tmp/default_module.js' 178 | }, 179 | 180 | // Customize angular module 181 | custom_module: { 182 | src: ['test/fixtures/one.html', 'test/fixtures/two/**/*.html'], 183 | dest: 'tmp/custom_module.js', 184 | options: { 185 | module: 'customModule' 186 | } 187 | }, 188 | 189 | // Customize angular module 190 | callback_module: { 191 | src: ['test/fixtures/one.html', 'test/fixtures/two/**/*.html'], 192 | dest: 'tmp/callback_module.js', 193 | options: { 194 | module: function(url, options) { 195 | return url.split('/').join('.'); 196 | }, 197 | url: function(file) { 198 | return file.replace('.html', ''); 199 | } 200 | } 201 | }, 202 | 203 | // Customize template URL prefix 204 | custom_prefix: { 205 | src: ['test/fixtures/one.html', 'test/fixtures/two/**/*.html'], 206 | dest: 'tmp/custom_prefix.js', 207 | options: { 208 | prefix: '/static' 209 | } 210 | }, 211 | 212 | // Customize template source 213 | custom_source: { 214 | src: ['test/fixtures/one.html', 'test/fixtures/two/**/*.html'], 215 | dest: 'tmp/custom_source.js', 216 | options: { 217 | source: function(source, url) { 218 | return "\n" + source; 219 | } 220 | } 221 | }, 222 | 223 | // Module should be new & have [] defined 224 | standalone: { 225 | src: ['test/fixtures/one.html', 'test/fixtures/two/**/*.html'], 226 | dest: 'tmp/standalone.js', 227 | options: { 228 | standalone: true 229 | } 230 | }, 231 | 232 | // URLs should match path exactly 233 | full_url: { 234 | src: ['test/fixtures/one.html', 'test/fixtures/two/**/*.html'], 235 | dest: 'tmp/full_url.js' 236 | }, 237 | 238 | // URLs should match path, sans the `cwd` 239 | relative_url: { 240 | cwd: 'test/fixtures', 241 | src: ['one.html', 'two/**/*.html'], 242 | dest: 'tmp/relative_url.js' 243 | }, 244 | 245 | // URLs should match path, sans the `cwd` 246 | relative_url_expand: { 247 | expand: true, 248 | cwd: 'test/fixtures', 249 | src: ['three/**/*.html'], 250 | dest: 'tmp', 251 | ext: '.js' 252 | }, 253 | 254 | // Customize URLs to not have an extension 255 | custom_url: { 256 | src: ['test/fixtures/one.html', 'test/fixtures/two/**/*.html'], 257 | dest: 'tmp/custom_url.js', 258 | options: { 259 | url: function(url) { 260 | return url.replace('.html', ''); 261 | } 262 | } 263 | }, 264 | 265 | // Empty file 266 | empty_file: { 267 | src: 'test/fixtures/empty.html', 268 | dest: 'tmp/empty_file.js' 269 | }, 270 | 271 | // undefined file 272 | undefined_file: { 273 | src: 'test/fixtures/undefined.html', 274 | dest: 'tmp/undefined_file.js' 275 | }, 276 | 277 | single_quotes: { 278 | src: 'test/fixtures/one.html', 279 | dest: 'tmp/single_quotes.js', 280 | options: { 281 | quotes: 'single' 282 | } 283 | }, 284 | 285 | linebreak: { 286 | src: 'test/fixtures/linebreak.html', 287 | dest: 'tmp/linebreak.js', 288 | }, 289 | 290 | regexp: { 291 | src: 'test/fixtures/regexp.html', 292 | dest: 'tmp/regexp.js' 293 | }, 294 | 295 | usemin_no_uglify: { 296 | src: ['test/fixtures/one.html', 'test/fixtures/two/**/*.html'], 297 | dest: 'tmp/custom_concat_usemin_no_uglify.js', 298 | options: { 299 | usemin: 'usemin/all.js' 300 | } 301 | }, 302 | 303 | // bunch of files at different level in a directory (unmerged in dest) 304 | unmerged_files: { 305 | src: 'test/fixtures/unmerged/**/*.html', 306 | dest: 'tmp/unmerged/', 307 | options: { 308 | merge: false 309 | } 310 | } 311 | } 312 | }); 313 | 314 | grunt.registerTask('useminPrepareWithoutUglify', function () { 315 | grunt.config.set('concat.generated', null); 316 | grunt.config.set('uglify.generated', null); 317 | var useminPrepareWithoutUglify = grunt.config('useminPrepareWithoutUglify'); 318 | grunt.config.set('useminPrepare', useminPrepareWithoutUglify); 319 | grunt.task.run('useminPrepare'); 320 | }); 321 | 322 | grunt.registerTask('useminWithoutUglify', function () { 323 | var useminWithoutUglify = grunt.config('useminWithoutUglify'); 324 | grunt.config.set('usemin', useminWithoutUglify); 325 | grunt.task.run('usemin'); 326 | }); 327 | 328 | var prettyTemplates = [ 329 | 'usemin_no_uglify' 330 | ]; 331 | 332 | grunt.registerTask('uglyTemplates', function(){ 333 | var templateKeys = Object.keys(grunt.config('ngtemplates')); 334 | var tasks = []; 335 | 336 | for(var i in templateKeys){ 337 | if(prettyTemplates.indexOf(templateKeys[i]) === -1){ 338 | tasks.push('ngtemplates:' + templateKeys[i]); 339 | } 340 | } 341 | 342 | grunt.task.run(tasks); 343 | }); 344 | 345 | grunt.registerTask('prettyTemplates', function(){ 346 | var templateKeys = Object.keys(grunt.config('ngtemplates')); 347 | var tasks = []; 348 | 349 | for(var i in templateKeys){ 350 | if(prettyTemplates.indexOf(templateKeys[i]) !== -1){ 351 | tasks.push('ngtemplates:' + templateKeys[i]); 352 | } 353 | } 354 | 355 | grunt.task.run(tasks); 356 | }); 357 | 358 | // Load local tasks. 359 | grunt.loadTasks('tasks'); 360 | grunt.loadNpmTasks('grunt-contrib-clean'); 361 | grunt.loadNpmTasks('grunt-contrib-concat'); 362 | grunt.loadNpmTasks('grunt-contrib-copy'); 363 | grunt.loadNpmTasks('grunt-contrib-cssmin'); 364 | grunt.loadNpmTasks('grunt-contrib-jshint'); 365 | grunt.loadNpmTasks('grunt-contrib-nodeunit'); 366 | grunt.loadNpmTasks('grunt-contrib-uglify'); 367 | grunt.loadNpmTasks('grunt-usemin'); 368 | 369 | grunt.registerTask('default', [ 370 | 'jshint', 371 | 'clean', 372 | 'copy', 373 | 'useminPrepare', 374 | 'uglyTemplates', 375 | 'concat', 376 | 'uglify', 377 | 'cssmin', 378 | 'usemin', 379 | 'useminPrepareWithoutUglify', 380 | 'prettyTemplates', 381 | 'concat', 382 | 'useminWithoutUglify', 383 | 'nodeunit' 384 | ]); 385 | }; 386 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Eric Clemmons 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-angular-templates 2 | 3 | [](https://travis-ci.org/ericclemmons/grunt-angular-templates) 4 | [](https://david-dm.org/ericclemmons/grunt-angular-templates) 5 | [](https://david-dm.org/ericclemmons/grunt-angular-templates#info=devDependencies&view=table) 6 | 7 | > Speed up your AngularJS app by automatically minifying, combining, 8 | > and automatically caching your HTML templates with `$templateCache`. 9 | 10 | Here's an example of the output created by this task from multiple `.html` files: 11 | 12 | ```js 13 | angular.module('app').run(["$templateCache", function($templateCache) { 14 | $templateCache.put("home.html", 15 | // contents for home.html ... 16 | ); 17 | ... 18 | $templateCache.put("src/app/templates/button.html", 19 | // contents for button.html 20 | ); 21 | }]); 22 | ``` 23 | 24 | Then, when you use `ng-include` or `templateUrl` with `$routeProvider`, 25 | the template is already loaded without an extra AJAX request! 26 | 27 | 28 | ## Table of Contents 29 | 30 | - [Installation](#installation) 31 | - [Options](#options) 32 | - [Usage](#usage) 33 | - [Examples](#examples) 34 | - [Changelog](#changelog) 35 | - [License](#license) 36 | 37 | 38 | ## Installation 39 | 40 | *This plugin requires [Grunt][1] `~0.4.0`* 41 | 42 | *Usemin integration requires [grunt-usemin][5] `~2.0.0`* 43 | 44 | Install the plugin: 45 | 46 | $ npm install grunt-angular-templates --save-dev 47 | 48 | Enable the plugin within your `Gruntfile`: 49 | 50 | ```js 51 | grunt.loadNpmTasks('grunt-angular-templates'); 52 | ``` 53 | 54 | 55 | ## Options 56 | 57 | ### angular 58 | 59 | > Global namespace for Angular. 60 | 61 | If you use `angular.noConflict()`, then set this value to whatever you 62 | re-assign angular to. Otherwise, it defaults to `angular`. 63 | 64 | ### bootstrap 65 | 66 | > Callback to modify the bootstraper that registers the templates with `$templateCache`. 67 | 68 | By default, the bootstrap script wraps `function($templateCache) { ... }` with: 69 | 70 | ```js 71 | angular.module('app').run(['$templateCache', ... ]); 72 | ``` 73 | 74 | If you want to create your own wrapper so you register the templates as an 75 | AMD or CommonJS module, set the `bootstrap` option to something like: 76 | 77 | ```js 78 | bootstrap: function(module, script) { 79 | return 'module.exports[module] = ' + script + ';'; 80 | } 81 | ``` 82 | 83 | ### concat 84 | 85 | > Name of `concat` target to append the compiled template path to. 86 | 87 | This is especially handy if you combine your scripts using 88 | [grunt-contrib-concat][4] or [grunt-usemin][5]. 89 | 90 | ### htmlmin 91 | 92 | > Object containing [htmlmin options][2] that will *significantly* reduce 93 | the filesize of the compiled templates. 94 | 95 | Without this, the HTML (whitespace and all) will be faithfully compiled 96 | down into the final `.js` file. Minifying that file will only cut down 97 | on the *Javascript* code, not the *HTML* within the strings. 98 | 99 | Note - this does incur a performance cost. Simply leave out this option to 100 | prevent minificaiton. 101 | 102 | I recommend using the following settings for production: 103 | 104 | ```js 105 | htmlmin: { 106 | collapseBooleanAttributes: true, 107 | collapseWhitespace: true, 108 | keepClosingSlash: true, // Only if you are using SVG in HTML 109 | removeAttributeQuotes: true, 110 | removeComments: true, // Only if you don't use comment directives! 111 | removeEmptyAttributes: true, 112 | removeRedundantAttributes: true, 113 | removeScriptTypeAttributes: true, 114 | removeStyleLinkTypeAttributes: true 115 | } 116 | ``` 117 | 118 | ### module 119 | 120 | > `String` of the `angular.module` to register templates with. 121 | 122 | If not specified, it will automatically be the name of the `ngtemplates` 123 | subtask (e.g. `app`, based on the examples below). 124 | 125 | ### prefix 126 | 127 | > `String` to prefix template URLs with. 128 | Defaults to `''` 129 | 130 | If you need to use absolute urls: 131 | 132 | ```js 133 | ngtemplates: { 134 | app: { 135 | options: { 136 | prefix: '/' 137 | } 138 | } 139 | } 140 | ``` 141 | 142 | If you serve static assets from another directory, you specify that as well. 143 | 144 | ### source 145 | 146 | > Callback to modify the template's source code. 147 | 148 | If you would like to prepend a comment, strip whitespace, or do 149 | post-processing on the HTML that `ngtemplates` doesn't otherwise do, 150 | use this function. 151 | 152 | ### append 153 | 154 | > Boolean to indicate the templates should be appended to dest instead of replacing it. 155 | Normally grunt-angular-templates creates a new file at `dest`. 156 | This option makes it append the compiled templates to the `dest` file rather than replace its contents. 157 | This is just a useful alternative to creating a temporary `dest` file and concatting it to your application. 158 | 159 | 160 | ### standalone 161 | 162 | > Boolean indicated if the templates are part of an existing module or a standalone. 163 | Defaults to `false`. 164 | 165 | - If the value is `false`, the module will look like `angular.module('app')`, meaning `app` module is retrieved. 166 | - If the value is `true`, the module will look like `angular.module('app', [])`, meaning `app` module is created. 167 | 168 | ### url 169 | 170 | > Callback to modify the template's `$templateCache` URL. 171 | 172 | Normally, this isn't needed as specifying your files with `cwd` 173 | ensures that URLs load via both AJAX and `$templateCache`. 174 | 175 | ### usemin 176 | 177 | > Path to `` usemin target 178 | 179 | This should be the output path of the compiled JS indicated in your HTML, 180 | such as `path/to/output.js` shown here. 181 | 182 | ### quotes 183 | 184 | > Use single or double quotes to wrap the template strings 185 | 186 | Defaults to 'double', other option is 'single' 187 | 188 | ## Usage 189 | 190 | 191 | ### Compiling HTML Templates 192 | 193 | After configuring your `ngtemplates` task, you can either run the 194 | task directly: 195 | 196 | $ grunt ngtemplates 197 | 198 | Or, bake it into an existing task: 199 | 200 | ```js 201 | grunt.registerTask('default', [ 'jshint', 'ngtemplates', 'concat' ]); 202 | ``` 203 | 204 | ### Including Compiled Templates 205 | 206 | Finally, you have to load the compiled templates' `.js` file into your 207 | application. 208 | 209 | 210 | #### Using HTML 211 | 212 | ```html 213 | 214 | ``` 215 | 216 | 217 | #### Using Grunt's `concat` task: 218 | 219 | This is my personal preference, since you don't have to worry about 220 | what the destination file is actually called. 221 | 222 | ```js 223 | concat: { 224 | app: { 225 | src: [ '**.js', '<%= ngtemplates.app.dest %>' ], 226 | dest: [ 'app.js' ] 227 | } 228 | } 229 | ``` 230 | 231 | #### Using [grunt-usemin][5] 232 | 233 | Using the following HTML as an example: 234 | 235 | ```html 236 | 237 | 238 | 239 | 240 | ``` 241 | 242 | **Do not use the `concat` option**, even though grunt-usemin generates a `concat.generated` 243 | object behind the scenes. Instead, use the `usemin` option to indicate the anticipated 244 | output filepath from grunt-usemin. 245 | 246 | ```js 247 | ngtemplates: { 248 | app: { 249 | src: '**.html', 250 | dest: 'template.js', 251 | options: { 252 | usemin: 'dist/vendors.js' // <~~ This came from the block 253 | } 254 | } 255 | } 256 | ``` 257 | 258 | **Note**: Earlier versions of grunt-usemin (*correctly, in my opinion*) would have generated 259 | a `concat['dist/vendors.js']` object for each build section in the HTML. Now, 260 | because there's a single `concat.generated` object with **all** JS/CSS files within it, 261 | I'm back-tracking the proper `concat` target for you. 262 | 263 | ## Examples 264 | 265 | 266 | ### Register HTML Templates in `app` Module 267 | 268 | ```js 269 | ngtemplates: { 270 | app: { 271 | src: '**.html', 272 | dest: 'templates.js' 273 | } 274 | } 275 | ``` 276 | 277 | 278 | ### Register Relative Template URLs 279 | 280 | Normally, your app, templates, & server are in separate folders, which means 281 | that the template URL is **different** from the file path. 282 | 283 | ```js 284 | ngtemplates: { 285 | app: { 286 | cwd: 'src/app', 287 | src: 'templates/**.html', 288 | dest: 'build/app.templates.js' 289 | } 290 | } 291 | ``` 292 | 293 | This will store the template URL as `templates/home.html` instead of 294 | `src/app/templates/home.html`, which would cause a 404. 295 | 296 | 297 | ### Minify Template HTML 298 | 299 | Simply pass the [same options][2] as the `htmlmin` task: 300 | 301 | ```js 302 | ngtemplates: { 303 | app: { 304 | src: '**.html', 305 | dest: 'templates.js', 306 | options: { 307 | htmlmin: { collapseWhitespace: true, collapseBooleanAttributes: true } 308 | } 309 | } 310 | } 311 | ``` 312 | 313 | Or, if you already have an existing `htmlmin` task, you can reference it: 314 | 315 | ```js 316 | ngtemplates: { 317 | app: { 318 | src: '**.html', 319 | dest: 'templates.js', 320 | options: { 321 | htmlmin: '<%= htmlmin.app %>' 322 | } 323 | } 324 | } 325 | ``` 326 | 327 | 328 | ### Customize Template URL 329 | 330 | Suppose you only use `ngtemplates` when on production, but locally you serve 331 | templates via Node, sans the `.html` extension. 332 | 333 | You can specify a `url` callback to further customize the registered URL: 334 | 335 | ```js 336 | ngtemplates: { 337 | app: { 338 | src: '**.html', 339 | dest: 'templates.js', 340 | options: { 341 | url: function(url) { return url.replace('.html', ''); } 342 | } 343 | } 344 | } 345 | ``` 346 | 347 | 348 | ### Customize Output 349 | 350 | Some people like [AMD & RequireJS][3] and would like wrap the output 351 | in AMD or something else (don't ask me why!): 352 | 353 | ```js 354 | ngtemplates: { 355 | app: { 356 | src: '**.html', 357 | dest: 'templates.js', 358 | options: { 359 | bootstrap: function(module, script) { 360 | return 'define(' + module + ', [], function() { return { init: ' + script + ' }; });'; 361 | } 362 | } 363 | } 364 | } 365 | ``` 366 | 367 | You will be able to custom everything surrounding `$templateCache.put(...)`. 368 | 369 | 370 | ## Changelog 371 | 372 | - v1.1.0 - Added the `merge` option to allow templates to maintain directory structure if set to `false` ([#114](https://github.com/ericclemmons/grunt-angular-templates/pull/114)) 373 | - v1.0.4 - Updated html-minifier to 2.1.2 ([#162](https://github.com/ericclemmons/grunt-angular-templates/pull/162)) 374 | - v1.0.3 - Fixes issue with using usemin without uglify ([#153](https://github.com/ericclemmons/grunt-angular-templates/pull/153)) 375 | - v1.0.2 - Fixes issue with escaping carriage returns ([#147](https://github.com/ericclemmons/grunt-angular-templates/pull/147)), Fixes issue with escaping backslashes ([#146](https://github.com/ericclemmons/grunt-angular-templates/pull/146)) 376 | - v1.0.1 - Log error instead of warning when minify fails ([#139](https://github.com/ericclemmons/grunt-angular-templates/pull/139)) 377 | - v1.0.0 - Updated unit tests for performance and bumps dependency versions ([#143](https://github.com/ericclemmons/grunt-angular-templates/pull/143)) 378 | - v0.6.0 - Adds `quotes` options to allow wrapping in single instead of double quotes ([#142](https://github.com/ericclemmons/grunt-angular-templates/pull/142)) 379 | - v0.5.9 - Fixes over-matching on `cwd` when `expand:true` 380 | - v0.5.8 - Fixes `cwd` being part of the $templateCache string when `expand:true` ([#134](https://github.com/ericclemmons/grunt-angular-templates/pull/134)), Added verbose logging for minify ([#136](https://github.com/ericclemmons/grunt-angular-templates/pull/136)) 381 | - v0.5.7 – Improve error messages ([#100](https://github.com/ericclemmons/grunt-angular-templates/pull/100)) 382 | - v0.5.6 – Updated `html-minifier` to correct whitespace issues. ([96](https://github.com/ericclemmons/grunt-angular-templates/pull/96)) 383 | - v0.5.5 – Add `append` option to concat, not overwrite the `dest`. ([#89](https://github.com/ericclemmons/grunt-angular-templates/pull/89)) 384 | - v0.5.4 – Specifying an invalid `usemin` option still creates file ([#84](https://github.com/ericclemmons/grunt-angular-templates/pull/84)) 385 | - v0.5.3 – Fix bug with Underscore templates ([#79](https://github.com/ericclemmons/grunt-angular-templates/pull/79)) 386 | - v0.5.2 – Fix `usemin` matching issue on Windows ([#80](https://github.com/ericclemmons/grunt-angular-templates/pull/80)) 387 | - v0.5.1 – Add `usemin` option form v0.4.10 388 | - v0.5.0 – Works with `grunt-usemin` ([#44](https://github.com/ericclemmons/grunt-angular-templates/issues/44)) 389 | - v0.4.10 – Add `usemin` option 390 | - v0.4.9 – Improve `prefix` and support for URLs ([#57](https://github.com/ericclemmons/grunt-angular-templates/pull/57)) 391 | - v0.4.8 – Compiled assets are JSHint-able ([#58](https://github.com/ericclemmons/grunt-angular-templates/pull/58)) 392 | - v0.4.7 – Fix bug for when htmlmin is not an Object ([#56](https://github.com/ericclemmons/grunt-angular-templates/issues/56)) 393 | - v0.4.6 – Add `prefix` option for easier URL prefixes ([#53](https://github.com/ericclemmons/grunt-angular-templates/pull/53)) 394 | - v0.4.5 – Attempt to better normalize templates based on current OS ([#52](https://github.com/ericclemmons/grunt-angular-templates/pull/52)) 395 | - v0.4.4 – Fixed regression caused by `htmlmin` ([#54](https://github.com/ericclemmons/grunt-angular-templates/pull/54)) 396 | - v0.4.3 - `options.concat` targets on Windows convert `/` to `\\`. [#48](https://github.com/ericclemmons/grunt-angular-templates/issues/48) 397 | - v0.4.2 - Fix for using `grunt-env` to change environments. Thanks to @FredrikAppelros ([#20](https://github.com/ericclemmons/grunt-express-server/pull/20)) 398 | - v0.4.1 – Fix bug with empty files. 399 | - v0.4.0 – Complete rewrite. 400 | - v0.3.12 – Whoops, forgot to make `htmlmin` a regular dependency. Thanks @rubenv ([#37](https://github.com/ericclemmons/grunt-angular-templates/pull/37)) 401 | - v0.3.11 – Add `htmlmin` option that supports both an `{ ... }` and `<%= htmlmin.options %>` for existing tasks. 402 | - v0.3.10 – Fix *unknown concat target* bug on windows, thanks to @trask ([#31](https://github.com/ericclemmons/grunt-angular-templates/pull/31)) 403 | - v0.3.9 – Allow the creation of a new module via `module.define`, thanks to @sidwood ([#28](https://github.com/ericclemmons/grunt-angular-templates/pull/28)) 404 | - v0.3.8 – Fix error that occurs when adding 0-length files, thanks to @robertklep ([#27](https://github.com/ericclemmons/grunt-angular-templates/pull/27)) 405 | - v0.3.7 – Add `noConflict` option to work with [angular.noConflict](https://github.com/angular/angular.js/pull/1535), thanks to @mbrevoort ([#26](https://github.com/ericclemmons/grunt-angular-templates/pull/26)) 406 | - v0.3.6 – Fix issue with dading to `concat` task when it's an array, thanks to @codefather ([#23](https://github.com/ericclemmons/grunt-angular-templates/pull/23)) 407 | - v0.3.5 – Preserver line endings in templates, thanks to @groner ([#21](https://github.com/ericclemmons/grunt-angular-templates/pull/21)) 408 | - v0.3.4 – Attempt to fix a bug with `Path`, thanks to @cgross ([#19](https://github.com/ericclemmons/grunt-angular-templates/issues/19)) 409 | - v0.3.3 – Add `concat` option for automatically adding compiled template file to existing `concat` (or `usemin`-created) task, thanks to @cgross ([#17](https://github.com/ericclemmons/grunt-angular-templates/pull/17)) 410 | - v0.3.2 – Add `module` option for setting which module the templates will be added to, thanks to @sidwood ([#20](https://github.com/ericclemmons/grunt-angular-templates/pull/20)) 411 | - v0.3.1 – Add `prepend` option for modifying final `$templateCache` IDs, thanks to @mbarchein. ([#16](https://github.com/ericclemmons/grunt-angular-templates/pull/16)) 412 | - v0.3.0 – **BC break** - Templates are added to an existing module (e.g. `myapp`) rather than being their own `myapp.templates` module to be manually included, thanks to @geddesign. ([#10](https://github.com/ericclemmons/grunt-angular-templates/issues/10)) 413 | - v0.2.2 – Escape backslashes, thanks to @dallonf. ([#9](https://github.com/ericclemmons/grunt-angular-templates/pull/9)) 414 | - v0.2.1 – Remove `./bin/grunt-angular-templates`. No need for it! 415 | - v0.2.0 – Update to Grunt 0.4, thanks to @jgrund. ([#5](https://github.com/ericclemmons/grunt-angular-templates/issues/5)) 416 | - v0.1.3 – Convert `\\` to `/` in template IDs (for on win32 systems) ([#3](https://github.com/ericclemmons/grunt-angular-templates/issues/3)) 417 | - v0.1.2 – Added NPM keywords 418 | - v0.1.1 – [Fails to combine multiple templates](https://github.com/ericclemmons/grunt-angular-templates/issues/1). Added directions to README on how to integrate with AngularJS app. Integrated with TravisCI 419 | - v0.1.0 – Released to [NPM](https://npmjs.org/package/grunt-angular-templates) 420 | 421 | 422 | ## License 423 | 424 | Copyright (c) 2013 Eric Clemmons 425 | Licensed under the MIT license. 426 | 427 | 428 | [1]: http://gruntjs.com/ 429 | [2]: https://github.com/gruntjs/grunt-contrib-htmlmin 430 | [3]: http://requirejs.org/docs/whyamd.html 431 | [4]: https://github.com/gruntjs/grunt-contrib-concat 432 | [5]: https://github.com/yeoman/grunt-usemin 433 | 434 | 435 | [](https://bitdeli.com/free "Bitdeli Badge") 436 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "grunt-angular-templates", 3 | "description": "Grunt build task to concatenate & register your AngularJS templates in the $templateCache", 4 | "version": "1.2.0", 5 | "homepage": "https://github.com/ericclemmons/grunt-angular-templates", 6 | "author": { 7 | "name": "Eric Clemmons", 8 | "email": "eric@smarterspam.com" 9 | }, 10 | "repository": { 11 | "type": "git", 12 | "url": "git://github.com/ericclemmons/grunt-angular-templates.git" 13 | }, 14 | "bugs": { 15 | "url": "https://github.com/ericclemmons/grunt-angular-templates/issues" 16 | }, 17 | "licenses": [ 18 | { 19 | "type": "MIT", 20 | "url": "https://github.com/ericclemmons/grunt-angular-templates/blob/master/LICENSE-MIT" 21 | } 22 | ], 23 | "main": "Gruntfile.js", 24 | "engines": { 25 | "node": ">= 0.8.0" 26 | }, 27 | "scripts": { 28 | "test": "./node_modules/.bin/grunt" 29 | }, 30 | "dependencies": { 31 | "html-minifier": "~4.0.0" 32 | }, 33 | "devDependencies": { 34 | "grunt": "~0.4.0", 35 | "grunt-cli": "~0.1.6", 36 | "grunt-contrib-cssmin": "~0.14.0", 37 | "grunt-contrib-clean": "~0.7.0", 38 | "grunt-contrib-concat": "~0.5.1", 39 | "grunt-contrib-jshint": "~0.12.0", 40 | "grunt-contrib-nodeunit": "~0.4.1", 41 | "grunt-contrib-uglify": "~0.11.0", 42 | "grunt-usemin": "~3.1.1", 43 | "grunt-contrib-copy": "~0.8.2" 44 | }, 45 | "keywords": [ 46 | "gruntplugin", 47 | "angular", 48 | "template", 49 | "templates", 50 | "concat" 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /tasks/angular-templates.js: -------------------------------------------------------------------------------- 1 | /* 2 | * grunt-angular-templates 3 | * https://github.com/ericclemmons/grunt-angular-templates 4 | * 5 | * Copyright (c) 2013 Eric Clemmons 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | 'use strict'; 10 | 11 | var Compiler = require('./lib/compiler'); 12 | var Appender = require('./lib/appender'); 13 | var fs = require('fs'); 14 | 15 | module.exports = function(grunt) { 16 | 17 | var bootstrapper = function(module, script, options) { 18 | return options.angular+".module('"+module+"'"+(options.standalone ? ', []' : '')+").run(['$templateCache', function($templateCache) {\n"+script+"\n}]);\n"; 19 | }; 20 | 21 | var ngtemplatesTask = function() { 22 | var options = this.options({ 23 | angular: 'angular', 24 | bootstrap: bootstrapper, 25 | concat: null, 26 | htmlmin: {}, 27 | module: this.target, 28 | prefix: '', 29 | source: function(source) { return source; }, 30 | standalone: false, 31 | url: function(path) { return path; }, 32 | usemin: null, 33 | append: false, 34 | quotes: 'double', 35 | merge: true 36 | }); 37 | 38 | grunt.verbose.writeflags(options, 'Options'); 39 | 40 | this.files.forEach(function(file) { 41 | if (!file.src.length) { 42 | grunt.log.warn('No templates found'); 43 | } 44 | 45 | var expanded = file.orig.expand; 46 | var cwd = file.orig.expand ? file.orig.cwd : file.cwd; 47 | 48 | var compiler = new Compiler(grunt, options, cwd, expanded); 49 | var appender = new Appender(grunt); 50 | var modules = compiler.modules(file.src); 51 | var compiled = []; 52 | 53 | for (var module in modules) { 54 | if (options.merge) { 55 | compiled.push(compiler.compile(module, modules[module])); 56 | } else { 57 | //Compiling each file to the same module 58 | for (var j = 0; j < file.src.length; j++) { 59 | compiled.push(compiler.compile(module, [file.src[j]])); 60 | } 61 | } 62 | } 63 | 64 | if (options.append){ 65 | fs.appendFileSync(file.dest, compiled.join('\n')); 66 | grunt.log.writeln('File ' + file.dest.cyan + ' updated.'); 67 | } 68 | else{ 69 | if (options.merge) { 70 | grunt.file.write(file.dest, compiled.join('\n')); 71 | grunt.log.writeln('File ' + file.dest.cyan + ' created.'); 72 | } else { 73 | //Writing compiled file to the same relative location as source, without merging them together 74 | for (var i = 0; i < compiled.length; i++) { 75 | var dest = file.dest + file.src[i]; 76 | //Change extension to js from html/htm 77 | dest = dest.replace(/(html|htm)$/i, "js"); 78 | grunt.file.write(dest, compiled[i]); 79 | grunt.log.writeln('File ' + dest.cyan + ' created.'); 80 | } 81 | } 82 | } 83 | 84 | 85 | if (options.usemin) { 86 | if (appender.save('generated', appender.concatUseminFiles(options.usemin, file))) { 87 | grunt.log.writeln('Added ' + file.dest.cyan + ' to ' + ('').yellow); 88 | } 89 | } 90 | 91 | if (options.concat) { 92 | if (appender.save(options.concat, appender.concatFiles(options.concat, file))) { 93 | grunt.log.writeln('Added ' + file.dest.cyan + ' to ' + ('concat:' + options.concat).yellow); 94 | } 95 | } 96 | }); 97 | }; 98 | 99 | grunt.registerMultiTask('ngtemplates', 'Compile AngularJS templates for $templateCache', ngtemplatesTask); 100 | 101 | }; 102 | -------------------------------------------------------------------------------- /tasks/lib/appender.js: -------------------------------------------------------------------------------- 1 | /* 2 | * grunt-angular-templates 3 | * https://github.com/ericclemmons/grunt-angular-templates 4 | * 5 | * Copyright (c) 2013 Eric Clemmons 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | 'use strict'; 10 | 11 | var Path = require('path'); 12 | 13 | /** 14 | * Utility for modifying other grunt tasks 15 | * @param {Object} grunt Grunt global object 16 | */ 17 | var Appender = function(grunt) { 18 | 19 | /** 20 | * Append compiled templates path to concat target's src(s) 21 | * @param {String} target Concat target 22 | * @param {Object} file Grunt File object for compiled templates 23 | * @param {Function} filter (Optional) Filter to reduce matching src(s) 24 | * @return {Array} Modified concat[target].files 25 | */ 26 | this.concatFiles = function(target, file, filter) { 27 | // Apparently concat targets are normalized if paths 28 | if (process.platform === 'win32') { 29 | target = target.replace(/\//g, '\\'); 30 | } 31 | 32 | var config = grunt.config(['concat', target]); 33 | 34 | if (!config) { 35 | grunt.log.warn('Concat target not found: ' + target.red); 36 | 37 | return false; 38 | } 39 | 40 | // Grunt handles files 400 different ways. Not me. 41 | var files = grunt.task.normalizeMultiTaskFiles(config) 42 | // Only work on the original src/dest, since files.src is a [GETTER] 43 | .map(function(files) { 44 | return files.orig; 45 | }) 46 | ; 47 | 48 | files 49 | // Optionally filter original src/dest(s) 50 | .filter(filter || function(files) { 51 | // Default to only appending to JS targets 52 | return files.src.filter(function(file) { 53 | return '.js' === file.substr(-3); 54 | }).length; 55 | }) 56 | // Append compiled templates file to src 57 | .forEach(function(files) { 58 | files.src.push(file.dest); 59 | }) 60 | ; 61 | 62 | return files; 63 | }; 64 | 65 | /** 66 | * Append compiled templates to matching usemin output path 67 | * @param {String} path Path from tag 68 | * @param {Object} file Grunt File object for compiled templates 69 | * @return {Array} files Modified concat.generated.files 70 | */ 71 | this.concatUseminFiles = function(path, file) { 72 | var config = grunt.config('uglify.generated'); 73 | var willUglify = !!config; 74 | 75 | if (!config) { 76 | config = grunt.config('concat.generated'); 77 | } 78 | 79 | if (!config) { 80 | grunt.log.warn('Usemin has not created ' + 'uglify.generated'.red + ' or ' + 'concat.generated'.red + ' yet!'); 81 | 82 | return false; 83 | } 84 | 85 | // Find uglify destination(s) matching output path 86 | var matches = grunt.task.normalizeMultiTaskFiles(config) 87 | .map(function(files) { 88 | return files.orig; 89 | }) 90 | .filter(function(files) { 91 | return Path.normalize(path) === files.dest.substr(-path.length); 92 | }) 93 | ; 94 | 95 | // *Something* should've matched 96 | if (!matches.length) { 97 | grunt.log.warn('Could not find usemin.generated path matching: ' + path.red); 98 | 99 | return false; 100 | } 101 | 102 | var match = matches.shift(); 103 | 104 | // Only support one match 105 | if (matches.length > 1) { 106 | grunt.log.warn('Multiple matches for ' + path.yellow + '. Using ' + match.dest); 107 | } 108 | 109 | var target = willUglify ? match.src.pop() : match.dest; 110 | 111 | // Finally, modify concat target sourced by matching uglify target 112 | return this.concatFiles('generated', file, function(files) { 113 | return target === files.dest; 114 | }); 115 | }; 116 | 117 | /** 118 | * Save Grunt Files array to existing grunt concat target 119 | * @param {String} target Concat target 120 | * @param {Array} files Modified concat[target].files 121 | * @return {Array} files 122 | */ 123 | this.save = function(target, files) { 124 | grunt.config(['concat', target], { 125 | files: files || grunt.config(['concat', target, 'files']), 126 | options: grunt.config(['concat', target, 'options']) || {} 127 | }); 128 | 129 | return files; 130 | }; 131 | }; 132 | 133 | module.exports = Appender; 134 | -------------------------------------------------------------------------------- /tasks/lib/compiler.js: -------------------------------------------------------------------------------- 1 | /* 2 | * grunt-angular-templates 3 | * https://github.com/ericclemmons/grunt-angular-templates 4 | * 5 | * Copyright (c) 2013 Eric Clemmons 6 | * Licensed under the MIT license. 7 | */ 8 | 9 | 'use strict'; 10 | 11 | var minify = require('html-minifier').minify; 12 | var Url = require('url'); 13 | 14 | /** 15 | * Angular Template Compiler 16 | * @param {Object} grunt Grunt global variable 17 | * @param {Object} options Task options 18 | * @param {String} cwd Determines if paths are relative or not 19 | * @return {Object} 20 | */ 21 | var Compiler = function(grunt, options, cwd, expanded) { 22 | 23 | /** 24 | * Wrap individual cache registration script in bootstrap function 25 | * 26 | * @param {String} script Multiline string of `$templateCache.put(...)` 27 | * @return {String} Final template aggregate script 28 | */ 29 | this.bootstrap = function(module, script) { 30 | return options.bootstrap(module, script, options); 31 | }; 32 | 33 | /** 34 | * Wrap HTML template in `$templateCache.put(...)` 35 | * @param {String} template Multiline HTML template string 36 | * @param {String} url URL to act as template ID 37 | * @return {String} Template wrapped in `$templateCache.put(...)` 38 | */ 39 | this.cache = function(template, url, prefix) { 40 | var path = prefix; 41 | 42 | // Force trailing slash 43 | if (path.length) { 44 | path = path.replace(/\/?$/, '/'); 45 | } 46 | 47 | if(cwd && expanded){ 48 | var cwdRegExp = new RegExp('^' + cwd + '\/?'); 49 | url = url.replace(cwdRegExp, ''); 50 | } 51 | 52 | // Append formatted URL 53 | path += Url.format( Url.parse( url.replace(/\\/g, '/') ) ); 54 | 55 | return "\n $templateCache.put('" + path + "',\n " + template + "\n );\n"; 56 | }; 57 | 58 | /** 59 | * Convert list of files into Javascript that caches their contents 60 | * @param {String} module Module name 61 | * @param {Array} files List of files relative to `cwd` 62 | * @return {String} Final template aggregate script 63 | */ 64 | this.compile = function(module, files) { 65 | var paths = files.map(this.path).filter(function(path) { 66 | if (!grunt.file.exists(path)) { 67 | grunt.log.warn('Template "' + path + '" not found.'); 68 | return false; 69 | } 70 | 71 | return true; 72 | }); 73 | 74 | var script = " 'use strict';" + grunt.util.linefeed; 75 | 76 | script += paths 77 | .map(this.load) 78 | .map(this.minify) 79 | .map(function(source, i) { 80 | return this.customize(source, paths[i]); 81 | }.bind(this)) 82 | .map(this.stringify) 83 | .map(function(string, i) { 84 | return this.cache(string, this.url(files[i]), options.prefix); 85 | }.bind(this)) 86 | .map(grunt.util.normalizelf) 87 | .join(grunt.util.linefeed) 88 | ; 89 | 90 | return this.bootstrap(module, script); 91 | }; 92 | 93 | /** 94 | * Customize template source 95 | * @param {String} source Possibly minified template source 96 | * @param {String} path Path to template file 97 | * @return {String} 98 | */ 99 | this.customize = function(source, path) { 100 | if (typeof options.source === 'function') { 101 | return options.source(source, path, options); 102 | } 103 | 104 | return source; 105 | }; 106 | 107 | /** 108 | * Load template path 109 | * @param {String} path Template path 110 | * @return {String} Template source 111 | */ 112 | this.load = function(path) { 113 | return grunt.file.read(path); 114 | }; 115 | 116 | /** 117 | * Run template source through htmlmin 118 | * @param {String} source Template source 119 | * @return {String} Minified template 120 | */ 121 | this.minify = function(source) { 122 | if (options.htmlmin && Object.keys(options.htmlmin).length) { 123 | try { 124 | grunt.verbose.writeln('Minifying file: ' +source); 125 | source = minify(source, options.htmlmin); 126 | } catch (err) { 127 | grunt.log.error(err + '\n\n' + source + '\n\n'); 128 | } 129 | } 130 | 131 | return source; 132 | }; 133 | 134 | /** 135 | * Get static or dynamic module name from file. 136 | * @param {String} file File name 137 | * @return {String} 138 | */ 139 | this.module = function(file) { 140 | if (typeof options.module === 'function') { 141 | return options.module(file, options); 142 | } 143 | 144 | return options.module; 145 | }; 146 | 147 | /** 148 | * Group files into individual modules 149 | * @param {Array} files Files 150 | * @return {Object} Key/Value pair of module + files 151 | */ 152 | this.modules = function(files) { 153 | var modules = {}; 154 | 155 | files.forEach(function(file) { 156 | var module = this.module(file); 157 | 158 | if (!modules[module]) { 159 | modules[module] = []; 160 | } 161 | 162 | modules[module].push(file); 163 | }, this); 164 | 165 | return modules; 166 | }; 167 | 168 | /** 169 | * Get path to template file on filesystem 170 | * @param {String} file Name of file relative to `cwd` 171 | * @return {String} Template path 172 | */ 173 | this.path = function(file) { 174 | if (cwd && !expanded) { 175 | return cwd + '/' + file; 176 | } 177 | 178 | return file; 179 | }; 180 | 181 | /** 182 | * Convert template source Javascript-friendly lines 183 | * @param {String} source Template source 184 | * @return {String} 185 | */ 186 | this.stringify = function(source) { 187 | return source.split(/^/gm).map(function(line) { 188 | var quote = options.quotes === 'single' ? '\'' : '"'; 189 | 190 | line = line.replace(/\\/g, '\\\\'); 191 | line = line.replace(/\n/g, '\\n'); 192 | line = line.replace(/\r/g, '\\r'); 193 | var quoteRegExp = new RegExp(quote, 'g'); 194 | line = line.replace(quoteRegExp, '\\' + quote); 195 | 196 | return quote + line + quote; 197 | }).join(' +\n ') || '""'; 198 | }; 199 | 200 | /** 201 | * Convert file name to URL 202 | * @param {String} file File name 203 | * @return {String} URL 204 | */ 205 | this.url = function(file) { 206 | if (typeof options.url === 'function') { 207 | return options.url(file, options); 208 | } 209 | 210 | return file; 211 | }; 212 | 213 | }; 214 | 215 | module.exports = Compiler; 216 | -------------------------------------------------------------------------------- /test/angular-templates_test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var grunt = require('grunt'); 4 | var fs = require('fs'); 5 | 6 | exports.ngtemplates = { 7 | 8 | custom_angular: function(test) { 9 | test.expect(1); 10 | 11 | var actual = grunt.file.read('tmp/custom_angular.js'); 12 | var expected = grunt.file.read('test/expected/custom_angular.js'); 13 | 14 | test.equal(expected, actual); 15 | test.done(); 16 | }, 17 | 18 | custom_bootstrap: function(test) { 19 | test.expect(1); 20 | 21 | var actual = grunt.file.read('tmp/custom_bootstrap.js'); 22 | var expected = grunt.file.read('test/expected/custom_bootstrap.js'); 23 | 24 | test.equal(expected, actual); 25 | test.done(); 26 | }, 27 | 28 | custom_concat: function(test) { 29 | test.expect(1); 30 | 31 | var actual = grunt.file.read('tmp/custom_concat_combined.js'); 32 | var expected = grunt.file.read('test/expected/custom_concat.js'); 33 | 34 | test.equal(expected, actual); 35 | test.done(); 36 | }, 37 | 38 | custom_usemin: function(test) { 39 | test.expect(5); 40 | 41 | test.equal(grunt.file.read('test/expected/useminUgly.html'), grunt.file.read('tmp/useminUgly.html')); 42 | test.equal(grunt.file.read('test/expected/useminUgly/foo.js').slice(0, -1), grunt.file.read('tmp/useminUgly/foo.js')); 43 | test.equal(grunt.file.read('test/expected/useminUgly/bar.js').slice(0, -1), grunt.file.read('tmp/useminUgly/bar.js')); 44 | test.equal(grunt.file.read('test/expected/useminUgly/all.js').slice(0, -1), grunt.file.read('tmp/useminUgly/all.js')); 45 | test.equal(grunt.file.read('test/expected/useminUgly/bar.css'), grunt.file.read('tmp/useminUgly/bar.css')); 46 | 47 | test.done(); 48 | }, 49 | 50 | custom_usemin_no_uglify: function(test) { 51 | test.expect(5); 52 | 53 | test.equal(grunt.file.read('test/expected/usemin.html'), grunt.file.read('tmp/usemin.html')); 54 | test.equal(grunt.file.read('test/expected/usemin/foo.js'), grunt.file.read('tmp/usemin/foo.js')); 55 | test.equal(grunt.file.read('test/expected/usemin/bar.js'), grunt.file.read('tmp/usemin/bar.js')); 56 | test.equal(grunt.file.read('test/expected/usemin/all.js'), grunt.file.read('tmp/usemin/all.js')); 57 | test.equal(grunt.file.read('test/expected/usemin/bar.css'), grunt.file.read('tmp/usemin/bar.css')); 58 | 59 | test.done(); 60 | }, 61 | 62 | custom_usemin_not_found: function(test) { 63 | test.expect(1); 64 | 65 | test.equal(grunt.file.read('test/expected/custom_concat_usemin_not_found.js'), grunt.file.read('tmp/custom_concat_usemin_not_found.js')); 66 | 67 | test.done(); 68 | }, 69 | 70 | html5: function(test) { 71 | test.expect(1); 72 | 73 | var actual = grunt.file.read('tmp/html5.js'); 74 | var expected = grunt.file.read('test/expected/html5.js'); 75 | 76 | test.equal(expected, actual); 77 | test.done(); 78 | }, 79 | 80 | custom_htmlmin: function(test) { 81 | test.expect(1); 82 | 83 | var actual = grunt.file.read('tmp/custom_htmlmin.js'); 84 | var expected = grunt.file.read('test/expected/custom_htmlmin.js'); 85 | 86 | test.equal(expected, actual); 87 | test.done(); 88 | }, 89 | 90 | task_htmlmin: function(test) { 91 | test.expect(1); 92 | 93 | var actual = grunt.file.read('tmp/task_htmlmin.js'); 94 | var expected = grunt.file.read('test/expected/task_htmlmin.js'); 95 | 96 | test.equal(expected, actual); 97 | test.done(); 98 | }, 99 | 100 | default_module: function(test) { 101 | test.expect(1); 102 | 103 | var actual = grunt.file.read('tmp/default_module.js'); 104 | var expected = grunt.file.read('test/expected/default_module.js'); 105 | 106 | test.equal(expected, actual); 107 | test.done(); 108 | }, 109 | 110 | custom_module: function(test) { 111 | test.expect(1); 112 | 113 | var actual = grunt.file.read('tmp/custom_module.js'); 114 | var expected = grunt.file.read('test/expected/custom_module.js'); 115 | 116 | test.equal(expected, actual); 117 | test.done(); 118 | }, 119 | 120 | callback_module: function(test) { 121 | test.expect(1); 122 | 123 | var actual = grunt.file.read('tmp/callback_module.js'); 124 | var expected = grunt.file.read('test/expected/callback_module.js'); 125 | 126 | test.equal(expected, actual); 127 | test.done(); 128 | }, 129 | 130 | custom_prefix: function(test) { 131 | test.expect(1); 132 | 133 | var actual = grunt.file.read('tmp/custom_prefix.js'); 134 | var expected = grunt.file.read('test/expected/custom_prefix.js'); 135 | 136 | test.equal(expected, actual); 137 | test.done(); 138 | }, 139 | 140 | custom_source: function(test) { 141 | test.expect(1); 142 | 143 | var actual = grunt.file.read('tmp/custom_source.js'); 144 | var expected = grunt.file.read('test/expected/custom_source.js'); 145 | 146 | test.equal(expected, actual); 147 | test.done(); 148 | }, 149 | 150 | standalone: function(test) { 151 | test.expect(1); 152 | 153 | var actual = grunt.file.read('tmp/standalone.js'); 154 | var expected = grunt.file.read('test/expected/standalone.js'); 155 | 156 | test.equal(expected, actual); 157 | test.done(); 158 | }, 159 | 160 | full_url: function(test) { 161 | test.expect(1); 162 | 163 | var actual = grunt.file.read('tmp/full_url.js'); 164 | var expected = grunt.file.read('test/expected/full_url.js'); 165 | 166 | test.equal(expected, actual); 167 | test.done(); 168 | }, 169 | 170 | relative_url: function(test) { 171 | test.expect(1); 172 | 173 | var actual = grunt.file.read('tmp/relative_url.js'); 174 | var expected = grunt.file.read('test/expected/relative_url.js'); 175 | 176 | test.equal(expected, actual); 177 | test.done(); 178 | }, 179 | 180 | relative_url_expand: function(test) { 181 | test.expect(2); 182 | 183 | var actual = grunt.file.read('tmp/three/three.js'); 184 | var expected = grunt.file.read('test/expected/relative_url_expand_three.js'); 185 | 186 | test.equal(expected, actual); 187 | 188 | actual = grunt.file.read('tmp/three/three_two.js'); 189 | expected = grunt.file.read('test/expected/relative_url_expand_three_two.js'); 190 | 191 | test.equal(expected, actual); 192 | 193 | test.done(); 194 | }, 195 | 196 | custom_url: function(test) { 197 | test.expect(1); 198 | 199 | var actual = grunt.file.read('tmp/custom_url.js'); 200 | var expected = grunt.file.read('test/expected/custom_url.js'); 201 | 202 | test.equal(expected, actual); 203 | test.done(); 204 | }, 205 | 206 | empty_file: function(test) { 207 | test.expect(1); 208 | 209 | var actual = grunt.file.read('tmp/empty_file.js'); 210 | var expected = grunt.file.read('test/expected/empty_file.js'); 211 | 212 | test.equal(expected, actual); 213 | test.done(); 214 | }, 215 | 216 | undefined_file: function(test) { 217 | test.expect(1); 218 | 219 | var actual = grunt.file.read('tmp/undefined_file.js'); 220 | var expected = grunt.file.read('test/expected/undefined_file.js'); 221 | 222 | test.equal(expected, actual); 223 | test.done(); 224 | }, 225 | 226 | single_quote: function(test) { 227 | test.expect(1); 228 | 229 | var actual = grunt.file.read('tmp/single_quotes.js'); 230 | var expected = grunt.file.read('test/expected/single_quotes.js'); 231 | 232 | test.equal(expected, actual); 233 | test.done(); 234 | }, 235 | 236 | linebreak: function(test) { 237 | test.expect(1); 238 | 239 | var actual = grunt.file.read('tmp/linebreak.js'); 240 | var expected = grunt.file.read('test/expected/linebreak.js'); 241 | 242 | test.equal(expected, actual); 243 | test.done(); 244 | }, 245 | 246 | regexp: function(test) { 247 | test.expect(1); 248 | 249 | var actual = grunt.file.read('tmp/regexp.js'); 250 | var expected = grunt.file.read('test/expected/regexp.js'); 251 | 252 | test.equal(expected, actual); 253 | test.done(); 254 | }, 255 | 256 | unmerged_files: function(test) { 257 | test.expect(5); 258 | 259 | test.equal(grunt.file.read('tmp/unmerged/test/fixtures/unmerged/undefined.js'), grunt.file.read('test/expected/unmerged_files/undefined.js')); 260 | test.equal(grunt.file.read('tmp/unmerged/test/fixtures/unmerged/usemin.js'), grunt.file.read('test/expected/unmerged_files/usemin.js')); 261 | test.equal(grunt.file.read('tmp/unmerged/test/fixtures/unmerged/level2/empty.js'), grunt.file.read('test/expected/unmerged_files/empty.js')); 262 | test.equal(grunt.file.read('tmp/unmerged/test/fixtures/unmerged/level2/html5.js'), grunt.file.read('test/expected/unmerged_files/html5.js')); 263 | test.equal(grunt.file.read('tmp/unmerged/test/fixtures/unmerged/level2/level3/one.js'), grunt.file.read('test/expected/unmerged_files/one.js')); 264 | test.done(); 265 | } 266 | 267 | }; 268 | -------------------------------------------------------------------------------- /test/expected/callback_module.js: -------------------------------------------------------------------------------- 1 | angular.module('test.fixtures.one.html').run(['$templateCache', function($templateCache) { 2 | 'use strict'; 3 | 4 | $templateCache.put('test/fixtures/one', 5 | "
I am one.
\n" + 8 | "\n" + 9 | "\n" 14 | ); 15 | 16 | }]); 17 | 18 | angular.module('test.fixtures.two.two.html').run(['$templateCache', function($templateCache) { 19 | 'use strict'; 20 | 21 | $templateCache.put('test/fixtures/two/two', 22 | "I am one.
\n" + 8 | "\n" + 9 | "\n" 14 | ); 15 | 16 | 17 | $templateCache.put('test/fixtures/two/two.html', 18 | "I am one.
\n" + 8 | "\n" + 9 | "\n" 14 | ); 15 | 16 | 17 | $templateCache.put('test/fixtures/two/two.html', 18 | "I am one.
4 | 5 | 10 | -------------------------------------------------------------------------------- /test/expected/custom_concat_usemin_not_found.js: -------------------------------------------------------------------------------- 1 | angular.module('custom_usemin_not_found').run(['$templateCache', function($templateCache) { 2 | 'use strict'; 3 | 4 | $templateCache.put('test/fixtures/one.html', 5 | "I am one.
\n" + 8 | "\n" + 9 | "\n" 14 | ); 15 | 16 | 17 | $templateCache.put('test/fixtures/two/two.html', 18 | "I am one.
" 8 | ); 9 | 10 | 11 | $templateCache.put('test/fixtures/two/two.html', 12 | "I am one.
\n" + 8 | "\n" + 9 | "\n" 14 | ); 15 | 16 | 17 | $templateCache.put('test/fixtures/two/two.html', 18 | "I am one.
\n" + 8 | "\n" + 9 | "\n" 14 | ); 15 | 16 | 17 | $templateCache.put('/static/test/fixtures/two/two.html', 18 | "I am one.
\n" + 9 | "\n" + 10 | "\n" 15 | ); 16 | 17 | 18 | $templateCache.put('test/fixtures/two/two.html', 19 | "\n" + 20 | "I am one.
\n" + 8 | "\n" + 9 | "\n" 14 | ); 15 | 16 | 17 | $templateCache.put('test/fixtures/two/two', 18 | "I am one.
\n" + 8 | "\n" + 9 | "\n" 14 | ); 15 | 16 | 17 | $templateCache.put('test/fixtures/two/two.html', 18 | "I am one.
\n" + 8 | "\n" + 9 | "\n" 14 | ); 15 | 16 | 17 | $templateCache.put('test/fixtures/two/two.html', 18 | "\n" +
17 | " Howdy\n"
18 | );
19 |
20 | }]);
21 |
--------------------------------------------------------------------------------
/test/expected/linebreak.js:
--------------------------------------------------------------------------------
1 | angular.module('linebreak').run(['$templateCache', function($templateCache) {
2 | 'use strict';
3 |
4 | $templateCache.put('test/fixtures/linebreak.html',
5 | ""
8 | );
9 |
10 | }]);
11 |
--------------------------------------------------------------------------------
/test/expected/regexp.js:
--------------------------------------------------------------------------------
1 | angular.module('regexp').run(['$templateCache', function($templateCache) {
2 | 'use strict';
3 |
4 | $templateCache.put('test/fixtures/regexp.html',
5 | "Regexp\n" + 6 | "\n" + 7 | "\n" 11 | ); 12 | 13 | }]); 14 | -------------------------------------------------------------------------------- /test/expected/relative_url.js: -------------------------------------------------------------------------------- 1 | angular.module('relative_url').run(['$templateCache', function($templateCache) { 2 | 'use strict'; 3 | 4 | $templateCache.put('one.html', 5 | "One\n" + 6 | "\n" + 7 | "I am one. \n" + 8 | "\n" + 9 | "\n" 14 | ); 15 | 16 | 17 | $templateCache.put('two/two.html', 18 | "Two\n" + 19 | "\n" + 20 | "\n" + 21 | "\n" + 22 | "\n" 23 | ); 24 | 25 | }]); 26 | -------------------------------------------------------------------------------- /test/expected/relative_url_expand_three.js: -------------------------------------------------------------------------------- 1 | angular.module('relative_url_expand').run(['$templateCache', function($templateCache) { 2 | 'use strict'; 3 | 4 | $templateCache.put('three/three.html', 5 | "Three\n" + 6 | "\n" + 7 | "\n" + 8 | "\n" + 9 | "\n" 10 | ); 11 | 12 | }]); 13 | -------------------------------------------------------------------------------- /test/expected/relative_url_expand_three_two.js: -------------------------------------------------------------------------------- 1 | angular.module('relative_url_expand').run(['$templateCache', function($templateCache) { 2 | 'use strict'; 3 | 4 | $templateCache.put('three/three_two.html', 5 | "Three Two\n" + 6 | "\n" + 7 | "\n" + 8 | "\n" + 9 | "\n" 10 | ); 11 | 12 | }]); 13 | -------------------------------------------------------------------------------- /test/expected/single_quotes.js: -------------------------------------------------------------------------------- 1 | angular.module('single_quotes').run(['$templateCache', function($templateCache) { 2 | 'use strict'; 3 | 4 | $templateCache.put('test/fixtures/one.html', 5 | 'One\n' + 6 | '\n' + 7 | 'I am one. \n' + 8 | '\n' + 9 | '\n' 14 | ); 15 | 16 | }]); 17 | -------------------------------------------------------------------------------- /test/expected/standalone.js: -------------------------------------------------------------------------------- 1 | angular.module('standalone', []).run(['$templateCache', function($templateCache) { 2 | 'use strict'; 3 | 4 | $templateCache.put('test/fixtures/one.html', 5 | "One\n" + 6 | "\n" + 7 | "I am one. \n" + 8 | "\n" + 9 | "\n" 14 | ); 15 | 16 | 17 | $templateCache.put('test/fixtures/two/two.html', 18 | "Two\n" + 19 | "\n" + 20 | "\n" + 21 | "\n" + 22 | "\n" 23 | ); 24 | 25 | }]); 26 | -------------------------------------------------------------------------------- /test/expected/task_htmlmin.js: -------------------------------------------------------------------------------- 1 | angular.module('task_htmlmin').run(['$templateCache', function($templateCache) { 2 | 'use strict'; 3 | 4 | $templateCache.put('test/fixtures/one.html', 5 | "OneI am one. " 8 | ); 9 | 10 | 11 | $templateCache.put('test/fixtures/two/two.html', 12 | "Two" 13 | ); 14 | 15 | }]); 16 | -------------------------------------------------------------------------------- /test/expected/undefined_file.js: -------------------------------------------------------------------------------- 1 | angular.module('undefined_file').run(['$templateCache', function($templateCache) { 2 | 'use strict'; 3 | 4 | $templateCache.put('test/fixtures/undefined.html', 5 | "Undefined\n" + 6 | "\n" + 7 | "I am undefined. \n" + 8 | "\n" + 9 | "\n" 14 | ); 15 | 16 | }]); 17 | -------------------------------------------------------------------------------- /test/expected/unmerged_files/empty.js: -------------------------------------------------------------------------------- 1 | angular.module('unmerged_files').run(['$templateCache', function($templateCache) { 2 | 'use strict'; 3 | 4 | $templateCache.put('test/fixtures/unmerged/level2/empty.html', 5 | "" 6 | ); 7 | 8 | }]); 9 | -------------------------------------------------------------------------------- /test/expected/unmerged_files/html5.js: -------------------------------------------------------------------------------- 1 | angular.module('unmerged_files').run(['$templateCache', function($templateCache) { 2 | 'use strict'; 3 | 4 | $templateCache.put('test/fixtures/unmerged/level2/html5.html', 5 | "\n" +
6 | " \n" +
7 | " Self-closing, sucka!\n" +
8 | " \n" +
11 | "\n" +
12 | "\n" + 9 | " \n" + 13 | "\n" + 14 | "
|