├── .eslintignore
├── .eslintrc.json
├── .gitignore
├── .travis.yml
├── README.md
├── index.js
├── lib
├── moduleId.js
├── scriptParser.js
├── templateId.js
└── urlParser.js
├── package.json
└── test
├── entry.js
├── src
└── template.html
├── test.sh
├── unit
└── libs.js
├── webpack.config.js
└── webpack2.config.js
/.eslintignore:
--------------------------------------------------------------------------------
1 | test/out/*
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "google",
3 | "env": {
4 | "node": true,
5 | "mocha": true
6 | },
7 | "rules": {
8 | "comma-dangle": ["error", "always-multiline"],
9 | "curly": ["error", "all"],
10 | "indent": ["error", 4],
11 | "max-len": ["error", 120],
12 | "no-implicit-coercion": 0,
13 | "no-var": 0
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .idea
2 | node_modules
3 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - "4"
4 | - "6"
5 | - "node"
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Angular Template loader for webpack
2 |
3 | Puts HTML partials in the Angular's $templateCache so directives can use templates without initial downloading.
4 |
5 | ## Webpack and loaders
6 |
7 | Webpack is the [webpack](http://webpack.github.io/) and it's module bundler. [Loaders](http://webpack.github.io/docs/using-loaders.html) wrap content in the javascript code that executes in the browser.
8 |
9 | # Install
10 |
11 | `npm install ng-cache-loader`
12 |
13 | # Usage
14 |
15 | You can require html partials via `ng-cache-loader`:
16 |
17 | ``` javascript
18 | require('ng-cache!./demo/template/myPartial.html');
19 | ```
20 |
21 | Partial will be available as `ng-include="'myPartial.html'"`
22 | or `templateUrl: 'myPartial.html'`.
23 |
24 | Note that the inline require syntax is used in examples for simplicity.
25 | It's recommended to use webpack [config](#webpack-config).
26 |
27 | ## Named templates
28 |
29 | You can wrap template in the `script` tag:
30 |
31 | ``` html
32 |
33 |
34 |
37 | ```
38 |
39 | You can have multiple templates in one file:
40 |
41 | ``` html
42 |
43 |
44 |
47 |
48 |
51 | ```
52 |
53 | You can mix named templates and simple markup:
54 |
55 | ``` html
56 |
57 |
58 |
61 |
62 |
63 |
...
64 |
65 |
68 | ```
69 |
70 | ## Prefix
71 |
72 | Prefix adds path left of template name:
73 |
74 | ``` javascript
75 | require('ng-cache?prefix=public/templates!./path/to/myPartial.html')
76 | // => ng-include="'public/templates/myPartial.html'"
77 | ```
78 |
79 | Prefix can mask the real directory with the explicit value
80 | or retrieve the real directory name (use `*` or `[dir]`):
81 |
82 | ``` javascript
83 | require('ng-cache?prefix=public/*/templates!./path/to/myPartial.html')
84 | // => ng-include="'public/path/templates/myPartial.html'"
85 | ```
86 |
87 | Prefix can strip the real directory name (use `//`):
88 |
89 | ``` javascript
90 | require('ng-cache?prefix=public/*//*/templates!./far/far/away/path/to/myPartial.html')
91 | // => ng-include="'public/far/path/templates/myPartial.html'"
92 | ```
93 |
94 | Prefix can be extended through a directory tree (use `**` or `[dirs]`). See the next section.
95 |
96 | ## Root
97 |
98 | You can specify root directory for templates separated by a colon `prefix=root:**`.
99 | It is enough to specify a single directory name. Prefix counts real template path from right to left and takes first (rightmost) occurance of the root directory.
100 |
101 | ```
102 | /User/packman/Projects/packman/
103 | ├─ app/tmpls/field.html
104 | └─ components/skins/tmpls/yellow.html
105 | ```
106 |
107 | With this directory tree you require templates from the inside of `app/tmpls` and `components/skins/tmpls`:
108 |
109 | ``` javascript
110 | require('ng-cache?prefix=packman:**!./field.html')
111 | // => ng-include="'app/tmpls/field.html'"
112 |
113 | require('ng-cache?prefix=packman:**!./yellow.html')
114 | // => ng-include="'components/skins/tmpls/yellow.html'"
115 | ```
116 |
117 | It is also possible to combine wildcards in prefix, i.e. `prefix=packman:**/tmpls//*`.
118 |
119 | ## Name
120 |
121 | Use `name` query parameter to strip file extension or add suffix:
122 |
123 | ``` javascript
124 | //
125 | require('ng-cache?name=[name].tpl!./field.html')
126 | // => ng-include="'field.tpl'"
127 |
128 | require('ng-cache?name=[name]-foo.[ext]!./field.html')
129 | // => ng-include="'field-foo.html'"
130 | ```
131 |
132 | Note. File extension are defined as any char sequence after the last `.`.
133 |
134 | ## Module
135 |
136 | By default, templates will be added to the default AngularJS 'ng' module run() method.
137 | Use this parameter to use a different module name:
138 |
139 | ``` javascript
140 | require('ng-cache?module=moduleName!./path/to/myPartial.html')
141 | ```
142 |
143 | If the module does not exist it is created.
144 |
145 | #### Dynamic pattern for Module:
146 |
147 | ``` javascript
148 | require('ng-cache?module=moduleName.[root]&prefix=packs:**!./packs/path/to/myPartial.html')
149 | ```
150 |
151 | In such query `[root]` means that first part of `templateId` *(here it is `path/to/myPartial.html`)* will be stripped out and placed as a part of `moduleId`.
152 |
153 | In current example resulting values:
154 | - `moduleId`: `"moduleName.path"`
155 | - `templateId`: `"to/myPartial.html"`
156 |
157 | Useful in case you want save few bytes.
158 |
159 | ## Template id
160 |
161 | To obtain template id use `exportIdOnly` query parameter. Loader exports `id` of a template.
162 |
163 | ```javascript
164 | var template = require('ng-cache?exportIdOnly!./field.html')
165 |
166 | $('#wrapper').html(``);
167 | angular.bootstrap($('#bootstrapElement'), [someModule]);
168 | ```
169 |
170 | To obtain both template id and html partial use `exportId` query parameter. Loader exports object with `id` and `template` keys.
171 |
172 | ```javascript
173 | var template = require('ng-cache?exportId!./field.html')
174 |
175 | $('#wrapper').html(``);
176 | angular.bootstrap($('#bootstrapElement'), [someModule]);
177 | ```
178 |
179 | ## Webpack config
180 |
181 | Match `.html` extension with loader:
182 |
183 | ``` javascript
184 | module: {
185 | loaders: [
186 | {
187 | test: /\.html$/,
188 | loader: "ng-cache?prefix=[dir]/[dir]"
189 | }
190 | ]
191 | }
192 | ```
193 |
194 | Note that the inline require syntax is used in examples for simplicity. It's recommended to use webpack config.
195 | Please see this [comment](https://github.com/webpack/webpack/issues/1626#issuecomment-156758230)
196 | and the [manual](https://webpack.github.io/docs/using-loaders.html#loaders-in-require).
197 |
198 | ## HTML minification
199 |
200 | Minification is enabled by default. You can switch it off by setting `-minimize`:
201 | ```javascript
202 | ng-cache?-minimize
203 | ```
204 |
205 | The [html-minifier](https://github.com/kangax/html-minifier) is used for templates minification with the default options:
206 | ```javascript
207 | {
208 | removeComments: true,
209 | removeCommentsFromCDATA: true,
210 | collapseWhitespace: true,
211 | conservativeCollapse: true,
212 | preserveLineBreaks: true,
213 | removeEmptyAttributes: true,
214 | keepClosingSlash: true
215 | }
216 | ```
217 |
218 | You can override any of options with the negative query parameter:
219 |
220 | ```javascript
221 | ng-cache?-conservativeCollapse&-preserveLineBreaks
222 | ```
223 |
224 | Or you can extend defaults with `minimizeOptions`:
225 | ```javascript
226 | var minimizeOptions = {
227 | conservativeCollapse: false,
228 | preserveLineBreaks: false
229 | };
230 | module.exports = {
231 | module: {
232 | loaders: [
233 | {test: /\.html$/, loader: 'ng-cache?minimizeOptions=' + JSON.stringify(minimizeOptions)}
234 | ]
235 | }
236 | }
237 | ```
238 |
239 | ## URL resolve
240 |
241 | Relative links to the local images are resolved by default (to prevent it use `-url` query param).
242 |
243 | ``` html
244 |
245 |
246 |
247 |
248 |
249 | ```
250 |
251 | Use this in conjunction with [url-loader](https://github.com/webpack/url-loader). For instance:
252 |
253 | ``` javascript
254 | require('url?name=img/[name].[ext]!ng-cache!./templates/myPartial.html')
255 | ```
256 |
257 | Using webpack config is more convenience:
258 |
259 | ``` javascript
260 | module: {
261 | loaders: [
262 | { test: /\.html$/, loader: "ng-cache?prefix=[dir]/[dir]" },
263 | { test: /\.png$/, loader: 'url?name=img/[name].[ext]&mimetype=image/png' },
264 | { test: /\.gif$/, loader: 'url?name=img/[name].[ext]&mimetype=image/gif' }
265 | ]
266 | },
267 | ```
268 |
269 | To switch off url resolving use `-url` query param:
270 |
271 | ``` javascript
272 | require('ng-cache?-url!./myPartial.html')
273 | ```
274 |
275 | # License
276 |
277 | MIT (http://www.opensource.org/licenses/mit-license.php)
278 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | /*
2 | MIT License http://www.opensource.org/licenses/mit-license.php
3 | Author Andrey Koperskiy @teux
4 | */
5 | var extend = require("extend");
6 | var htmlMinifier = require("html-minifier");
7 | var loaderUtils = require("loader-utils");
8 | var scriptParser = require('./lib/scriptParser.js');
9 | var urlParser = require('./lib/urlParser.js');
10 | var getTemplateId = require('./lib/templateId.js');
11 | var getModuleId = require('./lib/moduleId.js');
12 |
13 | var PRE_STUB = 'var angular=window.angular,ngModule;\n' +
14 | 'try {ngModule=angular.module(["${mod}"])}\n' +
15 | 'catch(e){ngModule=angular.module("${mod}",[])}';
16 |
17 | var STUB = 'var v${i}=${val};\n' +
18 | 'var id${i}="${key}";\n' +
19 | // added injector to load $templateCache for dynamic chunks
20 | 'var inj=angular.element(window.document).injector();\n' +
21 | 'if(inj){inj.get("$templateCache").put(id${i},v${i});}\n' +
22 | 'else{ngModule.run(["$templateCache",function(c){c.put(id${i},v${i})}]);}';
23 |
24 | /**
25 | * Replaces placeholders with values.
26 | * @param {string} stub Template ith placeholders.
27 | * @param {Object} values Key-value pairs.
28 | * @return {string} Resolved template.
29 | */
30 | function supplant(stub, values) {
31 | return stub.replace(/\$\{([^}]+)\}/g, function(match, key) {
32 | return values[key] || match;
33 | });
34 | }
35 | /**
36 | * Replaces urls with `require(url)` for further processing with url-loader or file-loader.
37 | * @param {Object} query ng-cache-loader options.
38 | * @param {string} src Template text.
39 | * @return {string} JSON
40 | */
41 | function resolveUrl(query, src) {
42 | return query.url === false ?
43 | JSON.stringify(src) :
44 | urlParser(src);
45 | }
46 |
47 | module.exports = function(source) {
48 | var opts = {
49 | minimize: true,
50 | // next are html-minifier default options
51 | removeComments: true,
52 | removeCommentsFromCDATA: true,
53 | collapseWhitespace: true,
54 | conservativeCollapse: true,
55 | preserveLineBreaks: true,
56 | removeEmptyAttributes: true,
57 | keepClosingSlash: true,
58 | };
59 | var minimizeOpts;
60 | var moduleId = 'ng';
61 | var result = [];
62 | var scripts;
63 | var html;
64 | var scr;
65 |
66 | if (this.cacheable) {
67 | this.cacheable();
68 | }
69 |
70 | // webpack1 or webpack2 with legacy query format
71 | if (typeof this.query === 'string') {
72 | // minimizeOpts is JSON inside query string
73 | minimizeOpts = this.query.match(/&?minimizeOptions[\s\n]*=[\s\n]*([^&]*)/);
74 |
75 | if (minimizeOpts) {
76 | this.query = this.query.replace(minimizeOpts[0], '');
77 | }
78 |
79 | try {
80 | minimizeOpts = minimizeOpts && JSON.parse(minimizeOpts[1]);
81 | } catch (e) {
82 | throw new Error('Invalid value of query parameter minimizeOptions');
83 | }
84 | }
85 |
86 | // Parse query and append minimize options
87 | var options = loaderUtils.getOptions ?
88 | loaderUtils.getOptions(this) :
89 | loaderUtils.parseQuery(this.query);
90 | extend(opts, minimizeOpts, options);
91 |
92 | if (opts.minimize) {
93 | try {
94 | source = htmlMinifier.minify(source, extend({}, opts));
95 | } catch (e) {
96 | this.emitWarning(e.toString() + '\nUsing unminified HTML');
97 | }
98 | }
99 |
100 | scripts = scriptParser.parse('root', source, {scripts: []}).scripts;
101 | source = Array.prototype.slice.apply(source);
102 |
103 | // Prepare named templates
104 | while (scripts.length) {
105 | scr = scripts.pop();
106 | html = source
107 | .splice(scr.idx, scr.len)
108 | .splice(scr.contIdx, scr.contLen)
109 | .join('');
110 | if (scr.id) {
111 | result.push({
112 | key: scr.id,
113 | val: resolveUrl(opts, html),
114 | i: result.length + 1,
115 | });
116 | } else {
117 | source.splice(scr.idx, 0, html);
118 | }
119 | }
120 | // Prepare the ramaining templates (means w/o `script` tag or w/o `id` attribute)
121 | source = source.join('');
122 |
123 | if (/[^\s]/.test(source) || !result.length) {
124 | var templateId = getTemplateId.call(this, source);
125 | var mod = getModuleId.call(this, templateId);
126 | moduleId = mod.moduleId;
127 | result.push({
128 | key: mod.templateId,
129 | val: resolveUrl(opts, source),
130 | i: result.length + 1,
131 | });
132 | }
133 |
134 | result = result.map(supplant.bind(null, STUB));
135 |
136 | // Return template string or id/template pair as module exports
137 | if (opts.exportId) {
138 | result.push('exports.id=id' + result.length + ';\nexports.template=v' + result.length + ';');
139 | } else if (opts.exportIdOnly) {
140 | result.push('module.exports=id' + result.length + ';');
141 | } else {
142 | result.push('module.exports=v' + result.length + ';');
143 | }
144 | result.unshift(supplant(PRE_STUB, {mod: moduleId}));
145 |
146 | return result.join('\n');
147 | };
148 |
--------------------------------------------------------------------------------
/lib/moduleId.js:
--------------------------------------------------------------------------------
1 | /*
2 | MIT License http://www.opensource.org/licenses/mit-license.php
3 | Author Alexander Merkulov @merqlove
4 | */
5 | var loaderUtils = require("loader-utils");
6 |
7 | /**
8 | * Process Module ID.
9 | * @param {string} templateId Template ID.
10 | * @return {object} Module ID, New Template ID.
11 | */
12 | module.exports = function(templateId) {
13 | var root = '[root]';
14 | var sep = '/';
15 | var defaultName = 'ng';
16 |
17 | var query = loaderUtils.getOptions ?
18 | loaderUtils.getOptions(this) :
19 | loaderUtils.parseQuery(this.query);
20 | var moduleId = query.module || defaultName;
21 | var newTemplateId = templateId;
22 |
23 | if(~moduleId.indexOf(root)) {
24 | var path = templateId.split(sep);
25 | var suffix = path.shift();
26 | moduleId = moduleId.replace(root, suffix);
27 | newTemplateId = path.join(sep);
28 | }
29 |
30 | return {
31 | moduleId: moduleId,
32 | templateId: newTemplateId
33 | };
34 | };
35 |
--------------------------------------------------------------------------------
/lib/scriptParser.js:
--------------------------------------------------------------------------------
1 | /*
2 | MIT License http://www.opensource.org/licenses/mit-license.php
3 | Author Andrey Koperskiy @teux
4 | */
5 | var Parser = require('fastparse');
6 |
7 | module.exports = new Parser({
8 | root: {
9 | '
9 |
10 |
11 |
Second View
12 |
About me
13 |
14 |
15 |
20 |
21 |
22 |
Second View
23 |
About you
24 |
25 |
26 |
--------------------------------------------------------------------------------
/test/test.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # unit
4 | ./node_modules/mocha/bin/mocha test/unit
5 |
6 | # build example in test/out/main.out.js
7 | ./node_modules/webpack/bin/webpack.js --config=test/webpack.config.js
--------------------------------------------------------------------------------
/test/unit/libs.js:
--------------------------------------------------------------------------------
1 | /**
2 | * Created by teux on 04.09.15.
3 | */
4 | var expect = require('chai').expect;
5 | var path = require('path');
6 | var cwd = process.cwd();
7 | var libPath;
8 | var lib;
9 |
10 | libPath = 'lib/templateId';
11 | moduleLibPath = 'lib/moduleId';
12 | lib = require(path.join(cwd, libPath));
13 | moduleLib = require(path.join(cwd, moduleLibPath));
14 |
15 | describe(libPath, function() {
16 | var resources = [
17 | '/Users/myself/Projects/packman/web_modules/angular-ui-bootstrap/template/popover/popover.html',
18 | '/Users/myself/Projects/packman/components/article/actions/actions.html',
19 | 'W:\\Projects\\packman\\web_modules\\angular-ui-bootstrap\\template\\popover\\popover.html',
20 | 'W:\\Projects\\packman\\components\\article\\actions\\actions.html',
21 | ];
22 | /**
23 | * Calls library.
24 | * @param {string} resource Absolute template path.
25 | * @param {string} [prefix] Prefix.
26 | * @return {string} Result
27 | */
28 | var run = function(resource, prefix) {
29 | var params = {resource: resource};
30 |
31 | params.query = prefix !== undefined ?
32 | '?prefix=' + prefix :
33 | '?';
34 | return lib.call(params);
35 | };
36 |
37 | var getFile = function(path) {
38 | return path.replace(/\\/g, '/').split('/').pop();
39 | };
40 |
41 | var alternation = function(res, pref) {
42 | var odd;
43 | var abs;
44 |
45 | pref = pref.replace(/\\/g, '/').split('/');
46 |
47 | if (pref[0] === '') {
48 | abs = true;
49 | pref.shift();
50 | }
51 | odd = !!~['[dir]', '[dirs]', '*', '**'].indexOf(pref[0]);
52 |
53 | res = res.replace(/\\/g, '/').split('/');
54 | res = res.slice(res.length - 5);
55 | pref[odd ? 0 : 1] = res[odd ? 0 : 1];
56 | pref[odd ? 2 : 3] = res[odd ? 2 : 3];
57 | if (abs) {
58 | pref.unshift('');
59 | }
60 | pref.push(res[4]);
61 | return pref.join('/');
62 | };
63 |
64 | describe('without prefix', function() {
65 | it('#should return file name', function() {
66 | resources.forEach(function(res) {
67 | var file = getFile(res);
68 | expect(run(res)).to.equal(file);
69 | expect(run(res), '').to.equal(file);
70 | });
71 | });
72 | });
73 |
74 | describe('explicit prefix', function() {
75 | it('#should be relative', function() {
76 | var pref = 'app/tmpl';
77 | resources.forEach(function(res) {
78 | expect(run(res, pref)).to.equal(pref + '/' + getFile(res));
79 | });
80 | });
81 | it('#should be absolute', function() {
82 | var pref = '/app/tmpl';
83 | resources.forEach(function(res) {
84 | expect(run(res, pref)).to.equal(pref + '/' + getFile(res));
85 | });
86 | });
87 | it('#should be absolute without unnecessary slash', function() {
88 | var pref = '/app/tmpl/';
89 | resources.forEach(function(res) {
90 | expect(run(res, pref)).to.equal(pref + getFile(res));
91 | });
92 | });
93 | });
94 |
95 | describe('explicit prefix (Win style)', function() {
96 | it('#should be relative', function() {
97 | var pref = 'app\\tmpl';
98 | resources.forEach(function(res) {
99 | expect(run(res, pref)).to.equal('app/tmpl/' + getFile(res));
100 | });
101 | });
102 | it('#should be absolute', function() {
103 | var pref = '\\app\\tmpl';
104 | resources.forEach(function(res) {
105 | expect(run(res, pref)).to.equal('/app/tmpl/' + getFile(res));
106 | });
107 | });
108 | it('#should be absolute without unnecessary slash', function() {
109 | var pref = '\\app\\tmpl\\';
110 | resources.forEach(function(res) {
111 | expect(run(res, pref)).to.equal('/app/tmpl/' + getFile(res));
112 | });
113 | });
114 | });
115 |
116 | describe('take from path (relative prefix)', function() {
117 | it('#should take odd chunks', function() {
118 | var pref = '*/dir1/*/dir2';
119 | resources.forEach(function(res) {
120 | expect(run(res, pref)).to.equal(alternation(res, pref));
121 | });
122 | pref = '[dir]/dir1/[dir]/dir2';
123 | resources.forEach(function(res) {
124 | expect(run(res, pref)).to.equal(alternation(res, pref));
125 | });
126 | });
127 | it('#should take even chunks', function() {
128 | var pref = 'dir1/*/dir2/*';
129 | resources.forEach(function(res) {
130 | expect(run(res, pref)).to.equal(alternation(res, pref));
131 | });
132 | pref = 'dir1/[dir]/dir2/[dir]';
133 | resources.forEach(function(res) {
134 | expect(run(res, pref)).to.equal(alternation(res, pref));
135 | });
136 | });
137 | });
138 |
139 | describe('take from path (absolute prefix)', function() {
140 | it('#should take odd chunks', function() {
141 | var pref = '/*/dir1/*/dir2';
142 | resources.forEach(function(res) {
143 | expect(run(res, pref)).to.equal(alternation(res, pref));
144 | });
145 | pref = '/[dir]/dir1/[dir]/dir2';
146 | resources.forEach(function(res) {
147 | expect(run(res, pref)).to.equal(alternation(res, pref));
148 | });
149 | });
150 | it('#should take even chunks', function() {
151 | var pref = '/dir1/*/dir2/*';
152 | resources.forEach(function(res) {
153 | expect(run(res, pref)).to.equal(alternation(res, pref));
154 | });
155 | pref = '/dir1/[dir]/dir2/[dir]';
156 | resources.forEach(function(res) {
157 | expect(run(res, pref)).to.equal(alternation(res, pref));
158 | });
159 | });
160 | });
161 |
162 | describe('take from path (relative prefix Win style)', function() {
163 | it('#should take odd chunks', function() {
164 | var pref = '*\\dir1\\*\\dir2';
165 | resources.forEach(function(res) {
166 | expect(run(res, pref)).to.equal(alternation(res, pref));
167 | });
168 | pref = '[dir]\\dir1\\[dir]\\dir2';
169 | resources.forEach(function(res) {
170 | expect(run(res, pref)).to.equal(alternation(res, pref));
171 | });
172 | });
173 | it('#should take even chunks', function() {
174 | var pref = 'dir1\\*\\dir2\\*';
175 | resources.forEach(function(res) {
176 | expect(run(res, pref)).to.equal(alternation(res, pref));
177 | });
178 | pref = 'dir1\\[dir]\\dir2\\[dir]';
179 | resources.forEach(function(res) {
180 | expect(run(res, pref)).to.equal(alternation(res, pref));
181 | });
182 | });
183 | });
184 |
185 | describe('take from path (absolute prefix Win style)', function() {
186 | it('#should take odd chunks', function() {
187 | var pref = '\\*\\dir1\\*\\dir2';
188 | resources.forEach(function(res) {
189 | expect(run(res, pref)).to.equal(alternation(res, pref));
190 | });
191 | pref = '\\[dir]\\dir1\\[dir]\\dir2';
192 | resources.forEach(function(res) {
193 | expect(run(res, pref)).to.equal(alternation(res, pref));
194 | });
195 | });
196 | it('#should take even chunks', function() {
197 | var pref = '\\dir1\\*\\dir2\\*';
198 | resources.forEach(function(res) {
199 | expect(run(res, pref)).to.equal(alternation(res, pref));
200 | });
201 | pref = '\\dir1\\[dir]\\dir2\\[dir]';
202 | resources.forEach(function(res) {
203 | expect(run(res, pref)).to.equal(alternation(res, pref));
204 | });
205 | });
206 | });
207 |
208 | describe('strip dir', function() {
209 | var res = [
210 | '/Users/myself/Projects/packman/web_modules/angular-ui-bootstrap/template/popover/popover.html',
211 | 'W:\\Projects\\packman\\web_modules\\angular-ui-bootstrap\\template\\popover\\popover.html',
212 | ];
213 |
214 | it('#should strip last dir', function() {
215 | expect(run(res[0], '*/dir1/*//')).to.equal('web_modules/dir1/template/popover.html');
216 | expect(run(res[0], '/*/dir1/*//')).to.equal('/web_modules/dir1/template/popover.html');
217 | expect(run(res[1], '*/dir1/*//')).to.equal('web_modules/dir1/template/popover.html');
218 | expect(run(res[1], '/*/dir1/*//')).to.equal('/web_modules/dir1/template/popover.html');
219 | });
220 | it('#should strip first dir', function() {
221 | expect(run(res[0], '//dir1/*/*/')).to.equal('/dir1/template/popover/popover.html');
222 | expect(run(res[1], '//dir1/*/*/')).to.equal('/dir1/template/popover/popover.html');
223 | });
224 | it('#should strip middle and last dir', function() {
225 | expect(run(res[0], '*/dir1//*//')).to.equal('packman/dir1/template/popover.html');
226 | expect(run(res[0], '/*/dir1//*//')).to.equal('/packman/dir1/template/popover.html');
227 | expect(run(res[1], '*/dir1//*//')).to.equal('packman/dir1/template/popover.html');
228 | expect(run(res[1], '/*/dir1//*//')).to.equal('/packman/dir1/template/popover.html');
229 | });
230 | });
231 |
232 | describe('strip dir (Win style)', function() {
233 | var res = [
234 | '/Users/myself/Projects/packman/web_modules/angular-ui-bootstrap/template/popover/popover.html',
235 | 'W:\\Projects\\packman\\web_modules\\angular-ui-bootstrap\\template\\popover\\popover.html',
236 | ];
237 |
238 | it('#should strip last dir', function() {
239 | expect(run(res[0], '*\\dir1\\*\\\\')).to.equal('web_modules/dir1/template/popover.html');
240 | expect(run(res[0], '\\*\\dir1\\*\\\\')).to.equal('/web_modules/dir1/template/popover.html');
241 | expect(run(res[1], '*\\dir1/*\\\\')).to.equal('web_modules/dir1/template/popover.html');
242 | expect(run(res[1], '\\*\\dir1\\*\\\\')).to.equal('/web_modules/dir1/template/popover.html');
243 | });
244 | it('#should strip first dir', function() {
245 | expect(run(res[0], '\\\\dir1\\*\\*\\')).to.equal('/dir1/template/popover/popover.html');
246 | expect(run(res[1], '\\\\dir1\\*\\*\\')).to.equal('/dir1/template/popover/popover.html');
247 | });
248 | it('#should strip middle and last dir', function() {
249 | expect(run(res[0], '*\\dir1\\\\*\\\\')).to.equal('packman/dir1/template/popover.html');
250 | expect(run(res[0], '\\*\\dir1\\\\*\\\\')).to.equal('/packman/dir1/template/popover.html');
251 | expect(run(res[1], '*\\dir1\\\\*\\\\')).to.equal('packman/dir1/template/popover.html');
252 | expect(run(res[1], '\\*\\dir1\\\\*\\\\')).to.equal('/packman/dir1/template/popover.html');
253 | });
254 | });
255 |
256 | describe('cross take from path', function() {
257 | var res = [
258 | '/Users/myself/Projects/packman/web_modules/angular-ui-bootstrap/template/popover/popover.html',
259 | 'W:\\Projects\\packman\\web_modules\\angular-ui-bootstrap\\template\\popover\\popover.html',
260 | ];
261 | it('#should be same', function() {
262 | expect(run(res[0], '/**')).to.equal(res[0]);
263 | expect(run(res[0], '**')).to.equal(res[0].replace(/^\//, ''));
264 | expect(run(res[1], '**')).to.equal(res[1].replace(/\\/g, '/'));
265 | });
266 | it('#should strip first chunk', function() {
267 | expect(run(res[0], '//**'))
268 | .to.equal('/myself/Projects/packman/web_modules/angular-ui-bootstrap/template/popover/popover.html');
269 | expect(run(res[1], '//**'))
270 | .to.equal('/Projects/packman/web_modules/angular-ui-bootstrap/template/popover/popover.html');
271 | });
272 | });
273 |
274 | describe('relative root and full extend', function() {
275 | var prefs = [
276 | 'Projects/packman:**',
277 | 'Projects/packman/:**',
278 | 'Projects/packman/:/**',
279 | 'Projects/packman/:/**/',
280 | 'Projects/packman:/**',
281 | 'Projects/packman:/**/',
282 | 'Projects/packman:[dirs]',
283 | 'Projects/packman/:[dirs]',
284 | 'Projects/packman/:/[dirs]',
285 | 'Projects/packman/:/[dirs]/',
286 | 'Projects/packman:/[dirs]',
287 | 'Projects/packman:/[dirs]/',
288 | 'Projects\\packman:**',
289 | 'Projects\\packman\\:**',
290 | 'Projects\\packman\\:\\**',
291 | 'Projects\\packman\\:\\**\\',
292 | 'Projects\\packman:\\**',
293 | 'Projects\\packman:\\**\\',
294 | 'Projects\\packman:[dirs]',
295 | 'Projects\\packman\\:[dirs]',
296 | 'Projects\\packman\\:\\[dirs]',
297 | 'Projects\\packman\\:\\[dirs]\\',
298 | 'Projects\\packman:\\[dirs]',
299 | 'Projects\\packman:\\[dirs]\\',
300 | ];
301 | var sample = function(res, pref) {
302 | pref = pref.replace(/\\/g, '/').split(':');
303 | res = res.replace(/\\/g, '/').split(pref.shift()).pop();
304 |
305 | var path = pref[0][0] === '/' ?
306 | res.replace(/^\/?/, '/') :
307 | res.replace(/^\//, '');
308 | return path;
309 | };
310 |
311 | it('#should extend', function() {
312 | prefs.forEach(function(pref) {
313 | resources.forEach(function(res) {
314 | expect(run(res, pref)).to.equal(sample(res, pref));
315 | });
316 | });
317 | });
318 | });
319 |
320 | describe('absolute root and full extend', function() {
321 | var sample = function(res, pref) {
322 | var root;
323 | var path;
324 |
325 | root = pref.replace(/\\/g, '/').split(':');
326 | pref = root.pop();
327 | root = root.join(':');
328 | res = res.replace(/\\/g, '/').split(root).pop();
329 | path = pref[0][0] === '/' ?
330 | res.replace(/^\/?/, '/') :
331 | res.replace(/^\//, '');
332 | return path;
333 | };
334 |
335 | it('#should extend', function() {
336 | var resources = [
337 | '/Users/myself/Projects/packman/web_modules/angular-ui-bootstrap/template/popover/popover.html',
338 | '/Users/myself/Projects/packman/components/article/actions/actions.html',
339 | ];
340 | [
341 | '/Users/myself/Projects/packman:**',
342 | '/Users/myself/Projects/packman/:**',
343 | '/Users/myself/Projects/packman/:/**',
344 | '/Users/myself/Projects/packman/:/**/',
345 | '/Users/myself/Projects/packman:/**',
346 | '/Users/myself/Projects/packman:/**/',
347 | '/Users/myself/Projects/packman:[dirs]',
348 | '/Users/myself/Projects/packman/:[dirs]',
349 | '/Users/myself/Projects/packman/:/[dirs]',
350 | '/Users/myself/Projects/packman/:/[dirs]/',
351 | '/Users/myself/Projects/packman:/[dirs]',
352 | '/Users/myself/Projects/packman:/[dirs]/',
353 | ].forEach(function(pref) {
354 | resources.forEach(function(res) {
355 | expect(run(res, pref)).to.equal(sample(res, pref));
356 | });
357 | });
358 | });
359 |
360 | it('#should extend (Win style)', function() {
361 | resources = [
362 | 'W:\\Projects\\packman\\web_modules\\angular-ui-bootstrap\\template\\popover\\popover.html',
363 | 'W:\\Projects\\packman\\components\\article\\actions\\actions.html',
364 | ];
365 | [
366 | 'W:\\Projects\\packman:**',
367 | 'W:\\Projects\\packman\\:**',
368 | 'W:\\Projects\\packman\\:\\**',
369 | 'W:\\Projects\\packman\\:\\**\\',
370 | 'W:\\Projects\\packman:\\**',
371 | 'W:\\Projects\\packman:\\**\\',
372 | 'W:\\Projects\\packman:[dirs]',
373 | 'W:\\Projects\\packman\\:[dirs]',
374 | 'W:\\Projects\\packman\\:\\[dirs]',
375 | 'W:\\Projects\\packman\\:\\[dirs]\\',
376 | 'W:\\Projects\\packman:\\[dirs]',
377 | 'W:\\Projects\\packman:\\[dirs]\\',
378 | ].forEach(function(pref) {
379 | resources.forEach(function(res) {
380 | expect(run(res, pref)).to.equal(sample(res, pref));
381 | });
382 | });
383 | });
384 | });
385 |
386 | describe('file name pattern', function() {
387 | var params = {
388 | resource: 'template/popover/popover.html',
389 | };
390 |
391 | it('#should strip extension', function() {
392 | params.query = '?name=[name]';
393 | expect(lib.call(params)).to.equal('popover');
394 | params.query += '&prefix=*/*';
395 | expect(lib.call(params)).to.equal('template/popover/popover');
396 | });
397 |
398 | it('#should replace extension', function() {
399 | params.query = '?name=[name].tpl';
400 | expect(lib.call(params)).to.equal('popover.tpl');
401 | params.query += '&prefix=*/*';
402 | expect(lib.call(params)).to.equal('template/popover/popover.tpl');
403 | });
404 |
405 | it('#should swap name and extension', function() {
406 | params.query = '?name=[ext].[name]';
407 | expect(lib.call(params)).to.equal('html.popover');
408 | params.query += '&prefix=*/*';
409 | expect(lib.call(params)).to.equal('template/popover/html.popover');
410 | });
411 |
412 | it('#should swap name and extension', function() {
413 | params.query = '?name=[ext].[name]';
414 | expect(lib.call(params)).to.equal('html.popover');
415 | params.query += '&prefix=*/*';
416 | expect(lib.call(params)).to.equal('template/popover/html.popover');
417 | });
418 |
419 | it('#should append suffix', function() {
420 | params.query = '?name=[name]-foo.[ext]';
421 | expect(lib.call(params)).to.equal('popover-foo.html');
422 | });
423 |
424 | it('#should replace name with literal', function() {
425 | params.query = '?name=foo';
426 | expect(lib.call(params)).to.equal('foo');
427 | });
428 |
429 | it('#should ignore invalid name pattern', function() {
430 | params.query = '?name=[name].[ext].[ext]';
431 | expect(lib.call(params)).to.equal('popover.html');
432 | params.query += '&prefix=*/*';
433 | expect(lib.call(params)).to.equal(params.resource);
434 | });
435 | });
436 |
437 | describe('module id pattern', function() {
438 | var params = {
439 | resource: 'template/popover/popover.html'
440 | };
441 |
442 | it('#should replace root', function() {
443 | templateId = 'some/popover.html';
444 | params.query = '?name=[file].[ext]&module=web.[root]';
445 | expect(moduleLib.call(params, templateId)).to.deep.equal({moduleId: 'web.some', templateId: 'popover.html'});
446 | });
447 |
448 | it('#should replace nothing', function() {
449 | templateId = 'some/popover.html';
450 | params.query = '?name=[file].[ext]&module=some';
451 | expect(moduleLib.call(params, templateId)).to.deep.equal({moduleId: 'some', templateId: 'some/popover.html'});
452 | });
453 |
454 | it('#should be ng by default', function() {
455 | templateId = 'some/popover.html';
456 | params.query = '?name=[file].[ext]';
457 | expect(moduleLib.call(params, templateId)).to.deep.equal({moduleId: 'ng', templateId: 'some/popover.html'});
458 | });
459 | });
460 |
461 | describe('various casese', function() {
462 | var resources = [
463 | '/Users/myself/Projects/packman/web_modules/angular-ui-bootstrap/template/popover/popover.html',
464 | 'W:\\Projects\\packman\\web_modules\\angular-ui-bootstrap\\template\\popover\\popover.html',
465 | ];
466 | it('#should extend, replace, strip and take', function() {
467 | resources.forEach(function(res) {
468 | expect(run(res, 'packman:**/dir1//*')).to.equal('web_modules/dir1/popover/popover.html');
469 | expect(run(res, 'packman:*/dir1/**')).to.equal('web_modules/dir1/template/popover/popover.html');
470 | expect(run(res, 'packman:*//**')).to.equal('web_modules/template/popover/popover.html');
471 | expect(run(res, 'packman:/**/dir1//')).to.equal('/web_modules/angular-ui-bootstrap/dir1/popover.html');
472 | });
473 | });
474 | it('#should convert second `**` to `*`', function() {
475 | resources.forEach(function(res) {
476 | expect(run(res, 'packman:**/dir1//**')).to.equal('web_modules/dir1/popover/popover.html');
477 | expect(run(res, 'packman:/**/dir1/**'))
478 | .to.equal('/web_modules/angular-ui-bootstrap/dir1/popover/popover.html');
479 | });
480 | });
481 | });
482 |
483 | describe('test root prefix', function() {
484 | var resources = [
485 | '/Users/myself/Projects/packman/web_modules/angular-ui-bootstrap/partials/foo/foo.html',
486 | '/Users/myself/Projects/packman/web_modules/angular-ui-bootstrap/partials/template.html',
487 | 'W:\\Projects\\packman\\web_modules\\angular-ui-bootstrap\\partials\\another_template.html',
488 | ];
489 | it('#should not have leading slash template in subfolder of root folder', function() {
490 | expect(run(resources[0], 'partials:**')).to.equal('foo/foo.html');
491 | });
492 |
493 | it('#should not have leading slash for template in root folder', function() {
494 | resources.forEach(function(res) {
495 | expect(run(resources[1], 'partials:**')).to.equal('template.html');
496 | });
497 | });
498 |
499 | it('#[win] should not have leading slash for template in root folder', function() {
500 | resources.forEach(function(res) {
501 | expect(run(resources[2], 'partials:**')).to.equal('another_template.html');
502 | });
503 | });
504 | });
505 | });
506 |
--------------------------------------------------------------------------------
/test/webpack.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 |
3 | var minimizeOptions = JSON.stringify({
4 | removeComments: true,
5 | removeCommentsFromCDATA: true,
6 | collapseWhitespace: true,
7 | conservativeCollapse: false,
8 | preserveLineBreaks: true,
9 | removeEmptyAttributes: true,
10 | keepClosingSlash: true,
11 | });
12 |
13 | module.exports = {
14 | context: __dirname,
15 | entry: path.resolve(__dirname, 'entry.js'),
16 | output: {
17 | path: path.resolve(__dirname, 'out'),
18 | filename: '[name].out.js',
19 | },
20 | module: {
21 | loaders: [
22 | {
23 | test: /\.html$/,
24 | loader: '../index.js?prefix=grot/[dir]//[dir]//tmpl&module=appModule&name=[name].tpl&-exportId' +
25 | '&minimizeOptions=' + minimizeOptions + '&conservativeCollapse',
26 | },
27 | ],
28 | },
29 | };
30 |
--------------------------------------------------------------------------------
/test/webpack2.config.js:
--------------------------------------------------------------------------------
1 | var path = require('path');
2 |
3 | module.exports = {
4 | context: __dirname,
5 | entry: path.resolve(__dirname, 'entry.js'),
6 | output: {
7 | path: path.resolve(__dirname, 'out'),
8 | filename: '[name].out.js',
9 | },
10 | module: {
11 | rules: [{
12 | test: /\.html$/,
13 | use:[{
14 | loader: '../index.js',
15 | options: {
16 | prefix: 'grot/[dir]//[dir]//tmpl',
17 | module: 'appModule',
18 | name: '[name].tpl',
19 | exportId: false,
20 | minimizeOptions: {
21 | removeComments: true,
22 | removeCommentsFromCDATA: true,
23 | collapseWhitespace: true,
24 | conservativeCollapse: false,
25 | preserveLineBreaks: true,
26 | removeEmptyAttributes: true,
27 | keepClosingSlash: true,
28 | },
29 | },
30 | }],
31 | }],
32 | },
33 | };
34 |
--------------------------------------------------------------------------------