├── .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 |
--------------------------------------------------------------------------------