├── .npmignore ├── test ├── src │ ├── baggage-test │ │ ├── bagge-test.html │ │ └── baggage-test.js │ ├── test.html │ └── entry.js ├── webpack.config.js └── index.html ├── examples └── baggage │ ├── my-directive │ ├── my-directive.html │ └── my-directive.js │ ├── index.html │ └── webpack.config.js ├── .gitignore ├── package.json ├── LICENSE.md ├── index.js └── README.md /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea 3 | -------------------------------------------------------------------------------- /test/src/baggage-test/bagge-test.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/src/test.html: -------------------------------------------------------------------------------- 1 |
Hello World!
-------------------------------------------------------------------------------- /examples/baggage/my-directive/my-directive.html: -------------------------------------------------------------------------------- 1 | hello {{foo}} 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea 3 | test/test.js 4 | test/npm-debug.log 5 | bundle.js 6 | -------------------------------------------------------------------------------- /test/src/baggage-test/baggage-test.js: -------------------------------------------------------------------------------- 1 | angular.module('testModule') 2 | .directive('baggage', function() { 3 | 4 | return { 5 | restrict: 'E', 6 | templateUrl: require('../../index.js!html-loader!') 7 | } 8 | 9 | }); -------------------------------------------------------------------------------- /test/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | 3 | module.exports = { 4 | entry: { 5 | test: path.join(__dirname, "src/entry.js") 6 | }, 7 | output: { 8 | path: __dirname, 9 | publicPath: "/", 10 | filename: "[name].js", 11 | sourceMapFilename: "[file].map" 12 | } 13 | }; -------------------------------------------------------------------------------- /examples/baggage/my-directive/my-directive.js: -------------------------------------------------------------------------------- 1 | angular.module('baggageExample', []) 2 | .directive('myDirective', function() { 3 | return { 4 | restrict: 'E', 5 | templateUrl: require('./my-directive.html'), 6 | link: function(scope) { 7 | scope.foo = 'world'; 8 | } 9 | } 10 | }); 11 | -------------------------------------------------------------------------------- /test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /examples/baggage/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /examples/baggage/webpack.config.js: -------------------------------------------------------------------------------- 1 | var path = require('path'); 2 | require('../../index.js'); 3 | 4 | module.exports = { 5 | entry: { 6 | test: path.join(__dirname, "my-directive/my-directive.js") 7 | }, 8 | module: { 9 | preLoaders: [ 10 | { test: /\.js$/, loader: 'baggage?[file].html' } 11 | ], 12 | loaders: [ 13 | // replace ../../../index.js with ngtemplate 14 | { test: /\.html$/, loader: "../../../index.js?relativeTo=" + __dirname + "!html" } 15 | ] 16 | }, 17 | output: { 18 | path: __dirname, 19 | publicPath: "/", 20 | filename: "bundle.js", 21 | sourceMapFilename: "bundle.map" 22 | } 23 | }; 24 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ngtemplate-loader", 3 | "version": "2.1.0", 4 | "description": "Include AngularJS templates in the Webpack bundle and preload the template cache.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "./node_modules/webpack/bin/webpack.js --config=test/webpack.config.js" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git://github.com/WearyMonkey/ngtemplate-loader.git" 12 | }, 13 | "keywords": [ 14 | "webpack", 15 | "angularjs", 16 | "loader" 17 | ], 18 | "author": "WearyMonkey", 19 | "licenses": [ 20 | { 21 | "type": "MIT", 22 | "url": "http://www.opensource.org/licenses/mit-license.php" 23 | } 24 | ], 25 | "dependencies": { 26 | "jsesc": "^0.5.0", 27 | "loader-utils": "^1.0.2" 28 | }, 29 | "devDependencies": { 30 | "baggage-loader": "^0.2.1", 31 | "html-loader": "^0.2.2", 32 | "raw-loader": "^0.5.1", 33 | "webpack": "^1.7.0" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 Toby Rahilly 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /test/src/entry.js: -------------------------------------------------------------------------------- 1 | var testTemplateUrl = require('../../index.js?relativeTo=src/!html-loader!./test.html'); 2 | 3 | angular.module('testModule', []) 4 | .directive('testDirective', function() { 5 | return { 6 | restrict: 'E', 7 | templateUrl: testTemplateUrl 8 | } 9 | }); 10 | 11 | console.log(require('../../index.js!html-loader!./test.html')); 12 | console.log(require('../../index.js!raw-loader!./test.html')); 13 | console.log(require('../../index.js?module=testModule!html-loader!./test.html')); 14 | console.log(require('../../index.js?relativeTo=/test/src/!html-loader!./test.html')); 15 | console.log(require('../../index.js?relativeTo=src/!html-loader!./test.html')); 16 | console.log(require('../../index.js?relativeTo=' + __dirname + '/!html-loader!./test.html')); 17 | console.log(require('../../index.js?relativeTo=/' + __dirname + '/!html-loader!./test.html')); 18 | console.log(require('../../index.js?prefix=/prefix!html-loader!./test.html')); 19 | console.log(require('../../index.js?prefix=/prefix/&relativeTo=/' + __dirname + '/!html-loader!./test.html')); 20 | console.log(require('../../index.js?module=[name]&prefix=[folder]&relativeTo=[path]!html-loader!./test.html')); 21 | console.log(require('../../index.js?module=[1]&moduleRegExp=test/(.*)/test!html-loader!./test.html')); 22 | console.log(require('../../index.js?pathSep=\\&prefix=/prefix/&relativeTo=' + __dirname + '/!html-loader!./test.html')); -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var loaderUtils = require("loader-utils"); 2 | var path = require('path'); 3 | var jsesc = require('jsesc'); 4 | 5 | module.exports = function (content) { 6 | this.cacheable && this.cacheable(); 7 | 8 | var options = loaderUtils.getOptions(this) || {}; 9 | var ngModule = getAndInterpolateOption.call(this, 'module', 'ng'); // ng is the global angular module that does not need to explicitly required 10 | var relativeTo = getAndInterpolateOption.call(this, 'relativeTo', ''); 11 | var prefix = getAndInterpolateOption.call(this, 'prefix', ''); 12 | var requireAngular = !!options.requireAngular || false; 13 | var absolute = false; 14 | var pathSep = options.pathSep || '/'; 15 | var resource = this.resource; 16 | var pathSepRegex = new RegExp(escapeRegExp(path.sep), 'g'); 17 | 18 | // if a unix path starts with // we treat is as an absolute path e.g. //Users/wearymonkey 19 | // if we're on windows, then we ignore the / prefix as windows absolute paths are unique anyway e.g. C:\Users\wearymonkey 20 | if (relativeTo[0] === '/') { 21 | if (path.sep === '\\') { // we're on windows 22 | relativeTo = relativeTo.substring(1); 23 | } else if (relativeTo[1] === '/') { 24 | absolute = true; 25 | relativeTo = relativeTo.substring(1); 26 | } 27 | } 28 | 29 | // normalise the path separators 30 | if (path.sep !== pathSep) { 31 | relativeTo = relativeTo.replace(pathSepRegex, pathSep); 32 | prefix = prefix.replace(pathSepRegex, pathSep); 33 | resource = resource.replace(pathSepRegex, pathSep) 34 | } 35 | 36 | var relativeToIndex = resource.indexOf(relativeTo); 37 | if (relativeToIndex === -1 || (absolute && relativeToIndex !== 0)) { 38 | throw new Error('The path for file doesn\'t contain relativeTo param'); 39 | } 40 | 41 | // a custom join of prefix using the custom path sep 42 | var filePath = [prefix, resource.slice(relativeToIndex + relativeTo.length)] 43 | .filter(Boolean) 44 | .join(pathSep) 45 | .replace(new RegExp(escapeRegExp(pathSep) + '+', 'g'), pathSep); 46 | 47 | // Replace the default export with an assigment to the _module_exports variable. 48 | var exportRe = /module\.exports\s*=|export default\s+/g; 49 | if (exportRe.test(content)) { 50 | contentWithVar = content.replace(exportRe, 'var _module_exports =') 51 | } else { 52 | // If there is no default export, try just using the content. 53 | // Probably doesn't work, but it's been here forever so can't remove now :) 54 | contentWithVar = "var _module_exports = " + content; 55 | } 56 | 57 | // Append a snippet that loads the template into the cache, and exports the path. 58 | return contentWithVar + ";\n" + 59 | "var path = '"+jsesc(filePath)+"';\n" + 60 | (requireAngular ? "var angular = require('angular');\n" : "window.") + 61 | "angular.module('" + ngModule + "').run(['$templateCache', function(c) { c.put(path, _module_exports) }]);\n" + 62 | "module.exports = path;"; 63 | 64 | function getAndInterpolateOption(optionKey, def) { 65 | return options[optionKey] 66 | ? loaderUtils.interpolateName(this, options[optionKey], { 67 | context: options.context, 68 | content: content, 69 | regExp: options[optionKey + 'RegExp'] || options['regExp'] 70 | }) 71 | : def 72 | } 73 | 74 | // source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions#Using_Special_Characters 75 | function escapeRegExp(string) { 76 | return string.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, "\\$1"); 77 | } 78 | }; 79 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AngularJS Template loader for [webpack](http://webpack.github.io/) 2 | 3 | Includes your AngularJS templates into your webpack Javascript Bundle. Pre-loads the AngularJS template cache 4 | to remove initial load times of templates. 5 | 6 | ngTemplate loader does not minify or process your HTML at all, and instead uses the standard loaders such as [html-loader](https://github.com/webpack-contrib/html-loader) 7 | or [raw-loader](https://github.com/webpack-contrib/raw-loader). This gives you the flexibility to pick and choose your HTML loaders. 8 | 9 | ## Install 10 | 11 | `npm install ngtemplate-loader --save-dev` 12 | 13 | ## Usage 14 | 15 | [Documentation: Using loaders](http://webpack.github.io/docs/using-loaders.html) 16 | 17 | ngTemplate loader will export the path of the HTML file, so you can use require directly AngularJS with templateUrl parameters e.g. 18 | 19 | ``` javascript 20 | var templateUrl = require('ngtemplate!html!./test.html'); 21 | 22 | app.directive('testDirective', function() { 23 | return { 24 | restrict: 'E', 25 | templateUrl: templateUrl 26 | } 27 | }); 28 | ``` 29 | 30 | 31 | To remove the extra `require`, check out the [Baggage Example](#baggage-example) below. 32 | 33 | ngTemplate creates a JS module that initialises the $templateCache with the HTML under the file path e.g. 34 | 35 | ``` javascript 36 | require('!ngtemplate?relativeTo=/projects/test/app!html!file.html'); 37 | // => generates the javascript: 38 | // angular.module('ng').run(['$templateCache', function(c) { c.put('file.html', '') }]); 39 | ``` 40 | 41 | 42 | ## Beware of requiring from the directive definition 43 | 44 | The following code is wrong, Because it'll operate only after angular bootstraps: 45 | ``` 46 | app.directive('testDirective', function() { 47 | return { 48 | restrict: 'E', 49 | templateUrl: require('ngtemplate!html!./test.html') // <- WRONG ! 50 | } 51 | }); 52 | ``` 53 | 54 | ### `relativeTo` and `prefix` 55 | 56 | You can set the base path of your templates using `relativeTo` and `prefix` parameters. `relativeTo` is used 57 | to strip a matching prefix from the absolute path of the input html file. `prefix` is then appended to path. 58 | 59 | The prefix of the path up to and including the first `relativeTo` match is stripped, e.g. 60 | 61 | ``` javascript 62 | require('!ngtemplate?relativeTo=/src/!html!/test/src/test.html'); 63 | // c.put('test.html', ...) 64 | ``` 65 | 66 | To match the from the start of the absolute path prefix a '//', e.g. 67 | 68 | ``` javascript 69 | require('!ngtemplate?relativeTo=//Users/WearyMonkey/project/test/!html!/test/src/test.html'); 70 | // c.put('src/test.html', ...) 71 | ``` 72 | 73 | You can combine `relativeTo` and `prefix` to replace the prefix in the absolute path, e.g. 74 | 75 | ``` javascript 76 | require('!ngtemplate?relativeTo=src/&prefix=build/!html!/test/src/test.html'); 77 | // c.put('build/test.html', ...) 78 | ``` 79 | 80 | ### `module` 81 | 82 | By default ngTemplate loader adds a run method to the global 'ng' module which does not need to explicitly required by your app. 83 | You can override this by setting the `module` parameter, e.g. 84 | 85 | ``` javascript 86 | require('!ngtemplate?module=myTemplates&relativeTo=/projects/test/app!html!file.html'); 87 | // => returns the javascript: 88 | // angular.module('myTemplates').run(['$templateCache', function(c) { c.put('file.html', '') }]); 89 | ``` 90 | 91 | ### Parameter Interpolation 92 | 93 | `module`, `relativeTo` and `prefix` parameters are interpolated using 94 | [Webpack's standard interpolation rules](https://github.com/webpack/loader-utils#interpolatename). 95 | Interpolation regular expressions can be passed using the extra parameters `moduleRegExp`, `relativeToRegExp` 96 | and `prefixRegExp` which apply to single parameters, or `regExp` which will apply to all three parameters. 97 | 98 | 99 | ### Path Separators (Or using on Windows) 100 | 101 | By default, ngTemplate loader will assume you are using unix style path separators '/' for html paths in your project. 102 | e.g. `templateUrl: '/views/app.html'`. If however you want to use Window's style path separators '\' 103 | e.g. `templateUrl: '\\views\\app.html'` you can override the separator by providing the pathSep parameter. 104 | 105 | ```javascript 106 | require('ngtemplate?pathSep=\\!html!.\\test.html') 107 | ``` 108 | 109 | Make sure you use the same path separator for the `prefix` and `relativeTo` parameters, all templateUrls and in your webpack.config.js file. 110 | 111 | ### Using with npm requires 112 | 113 | This module relies on angular being available on `window` object. However, in cases angular is connected from `node_modules` via `require('angular')`, option to force this module to get the angular should be used: 114 | 115 | ```javascript 116 | require('!ngtemplate?requireAngular!html!file.html'); 117 | 118 | // => generates the javascript: 119 | // var angular = require('angular'); 120 | // angular.module('ng').run(['$templateCache', function(c) { c.put('file.html', '') }]); 121 | ``` 122 | 123 | ## Webpack Config 124 | 125 | It's recommended to adjust your `webpack.config` so `ngtemplate!html!` is applied automatically on all files ending with `.html`. For Webpack 1 this would be something like: 126 | 127 | ``` javascript 128 | module.exports = { 129 | module: { 130 | loaders: [ 131 | { 132 | test: /\.html$/, 133 | loader: 'ngtemplate?relativeTo=' + (path.resolve(__dirname, './app')) + '/!html' 134 | } 135 | ] 136 | } 137 | }; 138 | ``` 139 | For Webpack 2 this would be something like: 140 | 141 | ``` javascript 142 | module.exports = { 143 | module: { 144 | rules: [ 145 | { 146 | test: /\.html$/, 147 | use: [ 148 | { loader:'ngtemplate-loader?relativeTo=' + (path.resolve(__dirname, './app')) }, 149 | { loader: 'html-loader' } 150 | ] 151 | } 152 | ] 153 | } 154 | }; 155 | ``` 156 | Make sure you already have `html-loader` installed. Then you only need to write: `require('file.html')`. 157 | 158 | ## Dynamic Requires 159 | 160 | Webpack's dynamic requires do not implicitly call the IIFE wrapping each 161 | call to `window.angular.module('ng').run(...)`, so if you use them to 162 | require a folder full of partials, you must manually iterate through the 163 | resulting object and resolve each dependency in order to accomodate angular's 164 | side-effects oriented module system: 165 | 166 | ``` javascript 167 | var templates = require.context('.', false, /\.html$/); 168 | 169 | templates.keys().forEach(function(key) { 170 | templates(key); 171 | }); 172 | 173 | ``` 174 | 175 | ## Baggage Example 176 | 177 | ngTemplate loader works well with the [Baggage Loader](https://github.com/deepsweet/baggage-loader) to remove all those 178 | extra HTML and CSS requires. See an example of a directive and webpack.config.js below. Or take a look at more complete 179 | example in the examples/baggage folder. 180 | 181 | With a folder structure: 182 | 183 | ``` 184 | app/ 185 | ├── app.js 186 | ├── index.html 187 | ├── webpack.config.js 188 | └── my-directive/ 189 | ├── my-directive.js 190 | ├── my-directive.css 191 | └── my-directive.html 192 | ``` 193 | 194 | and a webpack.config.js for webpack 1 like: 195 | 196 | ``` javascript 197 | module.exports = { 198 | module: { 199 | preLoaders: [ 200 | { 201 | test: /\.js$/, 202 | loader: 'baggage?[file].html&[file].css' 203 | } 204 | ], 205 | loaders: [ 206 | { 207 | test: /\.html$/, 208 | loader: 'ngtemplate?relativeTo=' + __dirname + '/!html' 209 | } 210 | ] 211 | } 212 | }; 213 | ``` 214 | 215 | For webpack 2 like: 216 | 217 | ``` javascript 218 | module.exports = { 219 | module: { 220 | rules: [ 221 | { 222 | test: /\.js$/, 223 | enforce: 'pre', 224 | use: [{ loader:'baggage?[file].html&[file].css' }] 225 | }, 226 | { 227 | test: /\.html$/, 228 | use: [ 229 | { loader: 'ngtemplate-loader?relativeTo=' + __dirname + '/' }, 230 | { loader: 'html-loader' }] 231 | ] 232 | } 233 | ] 234 | } 235 | }; 236 | ``` 237 | 238 | You can now skip the initial require of html and css like so: 239 | 240 | ``` javascript 241 | app.directive('myDirective', function() { 242 | return { 243 | restrict: 'E', 244 | templateUrl: require('./my-directive.html') 245 | } 246 | }); 247 | ``` 248 | 249 | ## License 250 | 251 | MIT (http://www.opensource.org/licenses/mit-license.php) 252 | --------------------------------------------------------------------------------