Skip to content

Commit 2ff1a25

Browse files
committed
First working version
1 parent 04ad7ac commit 2ff1a25

7 files changed

+450
-8
lines changed

.editorconfig

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# http://editorconfig.org
2+
root = true
3+
4+
[*]
5+
charset = utf-8
6+
indent_style = space
7+
indent_size = 2
8+
end_of_line = lf
9+
insert_final_newline = true
10+
trim_trailing_whitespace = true
11+
12+
[*.md]
13+
max_line_length = 0
14+
trim_trailing_whitespace = false

LICENSE LICENSE.md

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
MIT License
1+
The MIT License (MIT)
22

3-
Copyright (c) 2017 ZEF Oy
3+
Copyright (c) ZEF Oy
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal
@@ -9,13 +9,13 @@ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
99
copies of the Software, and to permit persons to whom the Software is
1010
furnished to do so, subject to the following conditions:
1111

12-
The above copyright notice and this permission notice shall be included in all
13-
copies or substantial portions of the Software.
12+
The above copyright notice and this permission notice shall be included in
13+
all copies or substantial portions of the Software.
1414

1515
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
1616
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
1717
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1818
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1919
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.
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
THE SOFTWARE.

README.md

+30-2
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,30 @@
1-
# angular-inliner-cli
2-
Inlines template and styles for compiled Angular components
1+
# Angular inline CLI
2+
3+
<a href="https://badge.fury.io/js/angular-inliner-cli"><img src="https://badge.fury.io/js/angular-inliner-cli.svg" align="right" alt="npm version" height="18"></a>
4+
5+
Inlines template and styles for compiled Angular components, modifies the compiled component and metadata files.
6+
7+
### Installing
8+
9+
```bash
10+
$ npm install angular-inliner-cli --save
11+
```
12+
13+
### Usage
14+
15+
```bash
16+
ngi [-s|--silent] [-c|--compress] <directory>
17+
18+
-s, --silent Output only critical errors
19+
-c, --compress Compress files before inlining
20+
21+
<directory> Directory where the compiled files are
22+
```
23+
24+
### Examples
25+
26+
```bash
27+
ngi --compress dist/lib/
28+
```
29+
30+
This would take all component.js and component.metadata.json files and recursively replace the templateUrl and styleUrls with the compressed content.

bin/inliner.js

+114
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
'use strict';
2+
3+
var fs = require('fs');
4+
var path = require('path');
5+
6+
var CleanCSS = require('clean-css');
7+
var htmlMinifier = require('html-minifier');
8+
9+
var htmlMinifierConfig = {
10+
caseSensitive: true,
11+
collapseWhitespace: true,
12+
// Angular bindings break the parser of html-minifer, so the
13+
// following skips the processing of ()="" and []="" attributes
14+
ignoreCustomFragments: [/\s\[.*\]=\"[^\"]*\"/, /\s\([^)"]+\)=\"[^\"]*\"/]
15+
};
16+
17+
module.exports = function(directory, content, compress) {
18+
return processTemplateUrl(directory, content, compress).then((result) =>
19+
processStyleUrls(directory, result, compress));
20+
};
21+
22+
function processTemplateUrl(directory, content, compress) {
23+
var result = content;
24+
25+
var re = /('|"|\s*)templateUrl('|"|\s*):\s*(?:'([^']+)'|"([^"]+)")/g;
26+
27+
var matches = result.match(re);
28+
29+
if (matches === null || matches.length <= 0) {
30+
return Promise.resolve(result);
31+
} else {
32+
matches.forEach(() => {
33+
var exec = re.exec(content);
34+
35+
var url = exec[3], quote = '\'';
36+
37+
if (!url) {
38+
url = exec[4];
39+
quote = '"';
40+
}
41+
42+
var file = fs.readFileSync(path.join(directory, url), 'utf-8');
43+
44+
if (!compress) {
45+
file = htmlMinifier.minify(file, htmlMinifierConfig);
46+
} else {
47+
file = htmlMinifier.minify(file, Object.assign({}, htmlMinifierConfig,
48+
{removeComments: true}));
49+
}
50+
51+
// Escape quotes
52+
file = file.replace(new RegExp(quote, 'g'), '\\' + quote);
53+
54+
// Replace line changes
55+
file = file.split(/[\r\n]+/g).join(quote + ' +\n' + quote);
56+
57+
result = result.replace(exec[0], exec[1] + 'template' + exec[2] +
58+
': ' + quote + file + quote);
59+
});
60+
61+
return Promise.resolve(result);
62+
}
63+
}
64+
65+
function processStyleUrls(directory, content, compress) {
66+
var result = content;
67+
68+
var re = /('|"|\s*)styleUrls('|"|\s*):\s*(\[[^](.[^]*?)\])/g;
69+
70+
var matches = result.match(re);
71+
72+
if (matches === null || matches.length <= 0) {
73+
return Promise.resolve(result);
74+
} else {
75+
return Promise.all(matches.map(() => {
76+
var exec = re.exec(result);
77+
78+
var urls = JSON.parse(exec[3].replace(/'/g, '"'));
79+
80+
return Promise.all(urls.map((url) => {
81+
var file = fs.readFileSync(path.join(directory, url), 'utf-8');
82+
83+
var filesRe = /^[\./]*([^]*)\.(css)$/g;
84+
85+
var filesMatches = url.match(filesRe);
86+
87+
if (filesMatches === null || filesMatches.length <= 0) {
88+
return file;
89+
} else {
90+
var filesExec = filesRe.exec(url);
91+
92+
var filename = filesExec[1];
93+
var extension = filesExec[2];
94+
95+
if (!compress) {
96+
file = file.replace(/[\r\n]/g, '');
97+
} else {
98+
file = new CleanCSS().minify(file).styles;
99+
}
100+
101+
// Escape quotes
102+
file = file.replace(new RegExp('\'', 'g'), '\\\'');
103+
104+
return file;
105+
}
106+
})).then((files) => {
107+
result = result.replace(exec[0], exec[1] + 'styles' + exec[2] +
108+
': [\'' + files.join('') + '\']');
109+
});
110+
})).then(() => {
111+
return result;
112+
});
113+
}
114+
}

index.js

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
#!/usr/bin/env node
2+
3+
(() => {
4+
'use strict';
5+
6+
var fs = require('fs');
7+
var path = require('path');
8+
var glob = require('glob');
9+
var minimist = require('minimist');
10+
11+
var inliner = require('./bin/inliner.js');
12+
13+
var args = minimist(process.argv.slice(2), {
14+
alias: {
15+
s: 'silent',
16+
c: 'compress'
17+
},
18+
boolean: ['silent', 'compress']
19+
});
20+
21+
var directory = args._.slice()[0];
22+
23+
if (!directory) {
24+
console.log('ngi [-s|--silent] [-c|--compress] <directory>');
25+
} else {
26+
glob(directory + '/*component.*(js|metadata.json)', {}, (error, files) => {
27+
if (error) {
28+
console.error('Failed to find component files from: ' + directory);
29+
30+
process.exit(1);
31+
} else {
32+
files.forEach((file) => {
33+
var target = path.join(process.cwd(), file);
34+
35+
var content = fs.readFileSync(target);
36+
37+
if (content) {
38+
if (!args.silent) {
39+
console.log('Procesing: ' + target);
40+
}
41+
42+
inliner(path.dirname(target), content.toString(), args.compress)
43+
.then((result) => {
44+
fs.writeFile(target, result, (error) => {
45+
if (error) {
46+
console.error('Error processing file: ' + file);
47+
console.error('The received error was: ' + error);
48+
}
49+
});
50+
}).catch((error) => {
51+
console.error('Inlining failed with error: ' + error);
52+
});
53+
} else {
54+
console.error('Failed to read content from file: ' + file);
55+
}
56+
});
57+
}
58+
});
59+
}
60+
})();

0 commit comments

Comments
 (0)