From 342ef523c4f7896dc15c7c47de31d42c2fa16c28 Mon Sep 17 00:00:00 2001 From: jmajoor Date: Fri, 1 Sep 2017 12:30:25 -0700 Subject: [PATCH 1/4] Add support for embedding style templates. Supports both css and less. The default processing is css templates. You can use the styleType configuration option to enabled the "less" processor. {sourceType: 'ts', styleType: 'less', styleOptions: {compress: true}} Added test cases. --- index.js | 3 +- lib/Angular1Processor.js | 33 ++--- lib/Angular2TypeScriptStylesProcessor.js | 140 +++++++++++++++++++++ package.json | 6 +- test/cases/angular2-less/directive.js | 5 + test/cases/angular2-less/embedded.js | 5 + test/cases/angular2-less/template.less | 6 + test/cases/angular2-styleUrls/directive.js | 5 + test/cases/angular2-styleUrls/embedded.js | 5 + test/cases/angular2-styleUrls/template.css | 3 + test/mocha.js | 10 +- 11 files changed, 201 insertions(+), 20 deletions(-) create mode 100644 lib/Angular2TypeScriptStylesProcessor.js create mode 100644 test/cases/angular2-less/directive.js create mode 100644 test/cases/angular2-less/embedded.js create mode 100644 test/cases/angular2-less/template.less create mode 100644 test/cases/angular2-styleUrls/directive.js create mode 100644 test/cases/angular2-styleUrls/embedded.js create mode 100644 test/cases/angular2-styleUrls/template.css diff --git a/index.js b/index.js index da885c5..c7b7458 100644 --- a/index.js +++ b/index.js @@ -5,6 +5,7 @@ var PluginError = gutil.PluginError; var ProcessorEngine = require('./lib/ProcessorEngine'); var Angular1Processor = require('./lib/Angular1Processor'); var Angular2TypeScriptTemplateProcessor = require('./lib/Angular2TypeScriptProcessor'); +var Angular2TypeScriptStyleTemplateProcessor = require('./lib/Angular2TypeScriptStylesProcessor'); var utils = require('./lib/utils'); const PLUGIN_NAME = 'gulp-angular-embed-template'; @@ -16,7 +17,7 @@ module.exports = function (options) { delete options.sourceType; switch (sourceType) { case 'ts': - options.processors = [new Angular1Processor(), new Angular2TypeScriptTemplateProcessor()]; + options.processors = [new Angular1Processor(), new Angular2TypeScriptTemplateProcessor(), new Angular2TypeScriptStyleTemplateProcessor()]; break; case 'js': default: diff --git a/lib/Angular1Processor.js b/lib/Angular1Processor.js index 688471d..47069c2 100644 --- a/lib/Angular1Processor.js +++ b/lib/Angular1Processor.js @@ -9,20 +9,6 @@ var RegexpProcessor = require('./RegexpProcessor'); const TEMPLATE_BEGIN = Buffer('template:\''); const TEMPLATE_END = Buffer('\''); -function escapeSingleQuotes(string) { - const ESCAPING = { - '\'': '\\\'', - '\\': '\\\\', - '\n': '\\n', - '\r': '\\r', - '\u2028': '\\u2028', - '\u2029': '\\u2029' - }; - return string.replace(/['\\\n\r\u2028\u2029]/g, function (character) { - return ESCAPING[character]; - }); -} - var Angular1Processor = extend(RegexpProcessor, { init : function(config) { this._super.init(config); @@ -82,6 +68,7 @@ var Angular1Processor = extend(RegexpProcessor, { } } + var _this = this; var embedTemplate = this.embedTemplate.bind(this); var minimizer = this.minimizer; fs.readFile(templatePath, {encoding: this.config.templateEncoding}, function(err, templateContent) { @@ -96,7 +83,7 @@ var Angular1Processor = extend(RegexpProcessor, { return; } - var templateBuffer = Buffer(escapeSingleQuotes(minifiedContent)); + var templateBuffer = Buffer(_this.escapeSingleQuotes(minifiedContent)); cb(embedTemplate(match, templateBuffer)); }); }); @@ -108,7 +95,21 @@ var Angular1Processor = extend(RegexpProcessor, { length: match[0].length, replace: [TEMPLATE_BEGIN, templateBuffer, TEMPLATE_END] } - } + }, + + escapeSingleQuotes: function(string) { + const ESCAPING = { + '\'': '\\\'', + '\\': '\\\\', + '\n': '\\n', + '\r': '\\r', + '\u2028': '\\u2028', + '\u2029': '\\u2029' + }; + return string.replace(/['\\\n\r\u2028\u2029]/g, function (character) { + return ESCAPING[character]; + }); + } }); module.exports = Angular1Processor; diff --git a/lib/Angular2TypeScriptStylesProcessor.js b/lib/Angular2TypeScriptStylesProcessor.js new file mode 100644 index 0000000..a433867 --- /dev/null +++ b/lib/Angular2TypeScriptStylesProcessor.js @@ -0,0 +1,140 @@ +var fs = require('fs'); +var pathModule = require('path'); +var cssProcessor = require('clean-css'); +var lessProcessor = require('less'); + +var extend = require('./utils').extend; +var Angular1Processor = require('./Angular1Processor'); + +//const TEMPLATE_BEGIN = Buffer('styles:string[]=['); +const TEMPLATE_BEGIN = Buffer('styles:['); +const TEMPLATE_END = Buffer(']'); + +var Angular2TypeScriptStylesProcessor = extend(Angular1Processor, { + init : function(config) { + this._super.init(config); + + if (!this.config.styleOptions) { + this.config.styleOptions = {}; + } + + var styleType = this.config.styleType; + var styleOptions = this.config.styleOptions; + switch (styleType) { + case 'less': + this.minimizer = { + template: function(path) { + return path.replace(/\.css$/, ".less"); + }, + process: function(path, source, cb) { + var processorOptions = Object.assign({}, styleOptions); + processorOptions["filename"] = path; + lessProcessor.render(source, processorOptions, function(err, minified) { + if (err) { + cb(minified == null ? err : minified.errors, null); + return; + } + cb(null, minified.css); + }); + } + }; + break; + case 'css': + default: + this.minimizer = { + template: function(path) { + return path; + }, + process: function(path, source, cb) { + new cssProcessor(styleOptions).minify(source, function(err, minified) { + if (err) { + cb(minified.errors, null); + return; + } + cb(null, minified.styles); + }); + } + }; + } + }, + /** + * @override + */ + getPattern : function() { + // for typescript: 'styleUrls: string[] = ["template.css"]' + //return '[\'"]?styleUrls[\'"]?[\\s]*:[\\s]*string\[][\\s]*=[\\s]*(\[[^](.[^]*?)\])'; + return '[\'"]?styleUrls[\'"]?[\\s]*:[\\s]*(\[[^](.[^]*?)\])'; + }, + + /** + * Find next "styleUrls:", and try to replace url with content if template available, less then maximum size. + * This is recursive function: it call itself until one of two condition happens: + * - error happened (error emitted in pipe and stop recursive calls) + * - no 'styleUrls' left (call 'fileCallback' and stop recursive calls) + * + * @param {Object} fileContext source file content + * @param {Object} match Regexp.exec result + * @param {Function} cb to call after match replaced + * @param {Function} onErr error handler + */ + replaceMatch : function(fileContext, match, cb, onErr) { + var urls = JSON.parse(match[1].replace(/'/g, '"')); + var relativeTemplatePath = match[1]; + var templatePath = pathModule.join(fileContext.path, relativeTemplatePath); + var warnNext = function(msg) { + this.logger.warn(msg); + cb(); + }.bind(this); + var onError = this.config.skipErrors ? warnNext : onErr; + + var embedTemplate = this.embedTemplate.bind(this); + var minimizer = this.minimizer; + + var _this = this; + var templateBuffers = []; + var numFiles = urls.length; + urls.map(function (relativeTemplatePath) { + var templatePath = pathModule.join(fileContext.path, minimizer.template(relativeTemplatePath)); + _this.logger.debug('template path: %s', templatePath); + + if (_this.config.maxSize) { + var fileStat = fs.statSync(templatePath); + if (fileStat && fileStat.size > _this.config.maxSize) { + warnNext('template file "' + templatePath + '" exceeds configured max size "' + _this.config.maxSize + '" actual size is "' + fileStat.size + '"'); + return; + } + } + + fs.readFile(templatePath, {encoding: _this.config.templateEncoding}, function(err, templateContent) { + if (err) { + onError('Can\'t read template file: "' + templatePath + '". Error details: ' + err); + return; + } + minimizer.process(templatePath, templateContent, function (err, minifiedContent) { + if (err) { + onError('Error while minifying angular style template "' + templatePath + '". Error from "style minimizer" plugin: ' + err); + return; + } + var beginTmpl = templateBuffers.length == 0 ? '\'' : ',\n\''; + var num = templateBuffers.push(new Buffer(beginTmpl + _this.escapeSingleQuotes(minifiedContent) + '\'')); + if (num == numFiles) { + cb(embedTemplate(match, Buffer.concat(templateBuffers))); + } + }); + }); + }); + }, + + /** + * @override + */ + embedTemplate : function(match, templateBuffer) { + return { + start : match.index, + length: match[0].length, + replace: [TEMPLATE_BEGIN, templateBuffer, TEMPLATE_END] + } + } +}); + +module.exports = Angular2TypeScriptStylesProcessor; \ No newline at end of file diff --git a/package.json b/package.json index f6c6901..945a82e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "gulp-angular-embed-templates", - "version": "2.3.0", + "version": "2.3.1", "description": "gulp plugin to include the contents of angular templates inside directive's code", "main": "index.js", "scripts": { @@ -30,7 +30,9 @@ "minimize": "^2.0.0", "through2": "^2.0.1", "htmlparser2": "~3.9.1", - "object-assign": "4.1.0" + "object-assign": "4.1.0", + "clean-css": "^4.1.7", + "less": "^2.7.2" }, "devDependencies": { "mocha": "^3.0.2", diff --git a/test/cases/angular2-less/directive.js b/test/cases/angular2-less/directive.js new file mode 100644 index 0000000..49e4a4d --- /dev/null +++ b/test/cases/angular2-less/directive.js @@ -0,0 +1,5 @@ +@Component({ + selector: "my-component", + styleUrls: ["template.css"], + directives: [ROUTER_DIRECTIVES] +}) \ No newline at end of file diff --git a/test/cases/angular2-less/embedded.js b/test/cases/angular2-less/embedded.js new file mode 100644 index 0000000..47bb39a --- /dev/null +++ b/test/cases/angular2-less/embedded.js @@ -0,0 +1,5 @@ +@Component({ + selector: "my-component", + styles:['.my-style{padding-right:4px}.my-style:hover{border:1px}'], + directives: [ROUTER_DIRECTIVES] +}) \ No newline at end of file diff --git a/test/cases/angular2-less/template.less b/test/cases/angular2-less/template.less new file mode 100644 index 0000000..7f889e3 --- /dev/null +++ b/test/cases/angular2-less/template.less @@ -0,0 +1,6 @@ +.my-style { + padding-right: 4px; + &:hover { + border: 1px; + } +} \ No newline at end of file diff --git a/test/cases/angular2-styleUrls/directive.js b/test/cases/angular2-styleUrls/directive.js new file mode 100644 index 0000000..49e4a4d --- /dev/null +++ b/test/cases/angular2-styleUrls/directive.js @@ -0,0 +1,5 @@ +@Component({ + selector: "my-component", + styleUrls: ["template.css"], + directives: [ROUTER_DIRECTIVES] +}) \ No newline at end of file diff --git a/test/cases/angular2-styleUrls/embedded.js b/test/cases/angular2-styleUrls/embedded.js new file mode 100644 index 0000000..f94ee22 --- /dev/null +++ b/test/cases/angular2-styleUrls/embedded.js @@ -0,0 +1,5 @@ +@Component({ + selector: "my-component", + styles:['.my-style{padding-right:4px}'], + directives: [ROUTER_DIRECTIVES] +}) \ No newline at end of file diff --git a/test/cases/angular2-styleUrls/template.css b/test/cases/angular2-styleUrls/template.css new file mode 100644 index 0000000..698174c --- /dev/null +++ b/test/cases/angular2-styleUrls/template.css @@ -0,0 +1,3 @@ +.my-style { + padding-right: 4px; +} \ No newline at end of file diff --git a/test/mocha.js b/test/mocha.js index 56e889c..75c5e10 100644 --- a/test/mocha.js +++ b/test/mocha.js @@ -217,5 +217,13 @@ describe('gulp-angular-embed-templates', function () { it('should allow to remove attribute quotes', function (done) { testEmbed('attr-quotes-remove', done, {minimize:{quotes: false}}); - }) + }) + + it('should embed styleUrls: path in Angular2.x just fine', function(done) { + testEmbed('angular2-styleUrls', done, {sourceType: 'ts', debug:true}); + }); + + it('should embed styleUrls with less: path in Angular2.x just fine', function(done) { + testEmbed('angular2-less', done, {sourceType: 'ts', styleType: 'less', styleOptions: {compress: true}}); + }); }); \ No newline at end of file From 6613d94fd55e7362d20c04ab4ac43acf641ce211 Mon Sep 17 00:00:00 2001 From: jmajoor Date: Fri, 1 Sep 2017 12:36:11 -0700 Subject: [PATCH 2/4] The default lowerCaseAttributeNames setting was no longer properly applied. Resulting in 2 broken testcases. Make sure the default is set. --- lib/Angular1Processor.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/Angular1Processor.js b/lib/Angular1Processor.js index 47069c2..3ad93b1 100644 --- a/lib/Angular1Processor.js +++ b/lib/Angular1Processor.js @@ -21,8 +21,12 @@ var Angular1Processor = extend(RegexpProcessor, { } this.minimizer = new Minimize(this.config.minimize); if (!this.config.minimize.parser) { + var htmlOptions = this.config.minimize.dom || {lowerCaseAttributeNames:false}; + if (htmlOptions.lowerCaseAttributeNames === undefined) { + htmlOptions.lowerCaseAttributeNames = false; + } this.minimizer.htmlparser = new html.Parser( - new html.DomHandler(this.minimizer.emits('read')), this.config.minimize.dom || {lowerCaseAttributeNames:false} + new html.DomHandler(this.minimizer.emits('read')), htmlOptions ); } From 9e9e7d27ba83c335e947ebc49bf730447553883c Mon Sep 17 00:00:00 2001 From: jmajoor Date: Fri, 1 Sep 2017 14:06:22 -0700 Subject: [PATCH 3/4] Update changelog version 2.3.1 --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index cfd5f2a..0798b47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,11 @@ * ability to embed ng-include * update javadoc with comparison with gulp-angular-templatecache +2.3.1 / 2017-09-01 +================== + * Add support for embedding style templates. Support includes CSS (default) and LESS. You can use the styleType configuration option to enable the "less" processor. + {sourceType: 'ts', styleType: 'less', styleOptions: {compress: true}} + 2.3.0 / 2016-08-08 ================== * Keep attribute quotes by default (add quotes if they missed in source code). Removing quotes caused serious of issues with bindings. You can get old behaviour by specifying config property minimize: {quotes: false} From 14c967c420e068a9ca3df7f51c7b85632ea5fb79 Mon Sep 17 00:00:00 2001 From: jmajoor Date: Fri, 15 Sep 2017 18:54:09 -0700 Subject: [PATCH 4/4] Include style template processing to readme --- README.md | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index fc64423..06fd10b 100644 --- a/README.md +++ b/README.md @@ -83,6 +83,7 @@ src +-hello-world |-hello-world-component.ts +-hello-world-template.html + +-hello-world-template.css ``` `hello-world-component.ts`: @@ -93,11 +94,13 @@ class Component extends Directive { controller: Controller; controllerAs: string = "vm"; templateUrl: string = "angular2-template.html"; + styleUrls: string[] = ["hello-world-template.css"] } // or @View({ ... - templateUrl: 'angular2-template.html' + templateUrl: 'angular2-template.html', + styleUrls: ["hello-world-template.css"] }) ``` @@ -109,6 +112,14 @@ class Component extends Directive { ``` +`angular2-template.css`: + +```css +.my-style { + padding-right: 4px; +} +``` + `gulpfile.js`: ```javascript @@ -129,11 +140,13 @@ class Component extends Directive { restrict: string = "E"; controller: Controller; controllerAs: string = "vm"; + styles: string[] = ['.my-style{padding-right:4px}'] template:string='{{index}}'; } // or @View({ ... + styles:['.my-style{padding-right:4px}'], template:'{{index}}' }) ``` @@ -149,6 +162,14 @@ Type: `String`. Default value: 'js'. Available values: - 'js' both for Angular 1.x syntax `templateUrl: 'path'` and Angular 2.x syntax `@View({templateUrl: 'path'})` - 'ts' additionally support Angular 2.x TypeScript syntax `class Component {templateUrl: string = 'path'}` +#### options.styleType +Type: `String`. Default value: 'css'. Available values: +- 'css' Use a CSS style processor for style URL templates. +- 'less' Use a LESS style processor for style URL templates. + +#### options.styleOptions +Type: `Object`. Options passed on to the style processor. For example `styleOptions: {compress: true}` will enabled the compression option on the style processor and embedded the compressed styles. + #### options.basePath Type: `String`. By default plugin use path specified in 'templateUrl' as a relative path to corresponding '.js' file (file with 'templateUrl'). This option allow to specify another basePath to search templates as 'basePath'+'templateUrl'