Skip to content

Commit

Permalink
added: support for dynamic attributes
Browse files Browse the repository at this point in the history
  • Loading branch information
emaphp committed Mar 15, 2016
1 parent f7dcb49 commit 3865c22
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 36 deletions.
19 changes: 19 additions & 0 deletions file-loader.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
var loaderUtils = require('loader-utils');

module.exports = function (source) {
if (this.cacheable) {
this.cacheable();
}
var query = this.query instanceof Object ? this.query : loaderUtils.parseQuery(this.query);

var allLoadersButThisOne = this.loaders.filter(function(loader) {
return loader.module !== module.exports;
});

// This loader shouldn't kick in if there is any other loader
if (allLoadersButThisOne.length > 0) {
return source;
}

return 'module.exports = ' + JSON.stringify(query.url);
};
126 changes: 91 additions & 35 deletions lib/attributeParser.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,59 +3,102 @@ var url = require('url');
var Parser = require("fastparse");
var loaderUtils = require('loader-utils');

// AttributeContext class
var AttributeContext = function(isRelevantTagAttr, usid, root) {
this.currentDirective = null;
/**
* HELPERS
*/

// Reminder: path.isAbsolute is not available in Node 0.10.x
var pathIsAbsolute = function (attrValue) {
return path.resolve(attrValue) == path.normalize(attrValue);
};

// Detects is a given resource string includes a custom loader
var hasCustomLoader = function (resourcePath) {
var resource = resourcePath.split('!\.');
if (resource.length > 1)
return resource;
return false;
};

// Checks whether a string contains a template expression
var isTemplate = function (expression) {
return /\{\{.*\}\}/.test(expression);
};

/**
* ATTRIBUTECONTEXT CLASS
*/
var AttributeContext = function(isRelevantTagAttr, usid, root, parseDynamicRoutes) {
this.matches = [];
this.isRelevantTagAttr = isRelevantTagAttr;
this.usid = usid;

this.data = {};
this.root = root;
this.parseDynamicRoutes = parseDynamicRoutes;

// The ident method builds a unique string expression
// that replaces an attribute value.
// This value is replaced again for the expected expression
// once the template has been transformed to a function
this.ident = function() {
return "____" + usid + Math.random() + "____";
};
this.data = {};
this.root = root;
};

// The replaceMatches method does an initial parse
// that replaces attribute values with a unique expression
AttributeContext.prototype.replaceMatches = function(content) {
var self = this;
content = [content];
this.matches.reverse();

this.matches.forEach(function(match) {
// Ignore if path is absolute and no root path has been defined
// Reminder: path.isAbsolute is not available in 0.10.x
if ((path.resolve(match.value) == path.normalize(match.value)) && self.root === undefined) {
return;
// Determine if the attribute contains a template expresssion
if (match.containsTemplate) {
// Check if path should be modified to include the "root" option
if (pathIsAbsolute(match.value) && self.root !== undefined) {
var x = content.pop();
content.push(x.substr(match.start + match.length));
content.push(self.parseDynamicRoutes ? loaderUtils.urlToRequest(match.value, self.root) : match.value);
content.push(x.substr(0, match.start));
}
} else {
// Ignore if path is absolute and no root path has been defined
if (pathIsAbsolute(match.value) && self.root === undefined) {
return;
}

// Ignore if is a URL
if (!loaderUtils.isUrlRequest(match.value, self.root)) {
return;
}

var uri = url.parse(match.value);
if (uri.hash !== null && uri.hash !== undefined) {
uri.hash = null;
match.value = uri.format();
match.length = match.value.length;
}

do {
var ident = self.ident();
} while (self.data[ident]);

self.data[ident] = match;

var x = content.pop();
content.push(x.substr(match.start + match.length));
content.push(ident);
content.push(x.substr(0, match.start));
}

// Ignore if is a URL
if (!loaderUtils.isUrlRequest(match.value, self.root)) {
return;
}

var uri = url.parse(match.value);
if (uri.hash !== null && uri.hash !== undefined) {
uri.hash = null;
match.value = uri.format();
match.length = match.value.length;
}

do {
var ident = self.ident();
} while (self.data[ident]);

self.data[ident] = match;

var x = content.pop();
content.push(x.substr(match.start + match.length));
content.push(ident);
content.push(x.substr(0, match.start));
});

content.reverse();
return content.join('');
};

// Replaces the expressions inserted by replaceMatches with the corresponding requires
AttributeContext.prototype.resolveAttributes = function(content) {
var regex = new RegExp('____' + this.usid + '[0-9\\.]+____', 'g');
var self = this;
Expand All @@ -64,20 +107,33 @@ AttributeContext.prototype.resolveAttributes = function(content) {
return match;
}

return '" + require(' + JSON.stringify(loaderUtils.urlToRequest(self.data[match].value, self.root)) + ') + "';
var url = self.data[match].value;

// Make resource available through file-loader
var fallbackLoader = require.resolve('../file-loader.js') + '?url=' + encodeURIComponent(url);
return "' + require(" + JSON.stringify(fallbackLoader + '!' + loaderUtils.urlToRequest(url, self.root)) + ") + '";
});
};

/**
* PARSER
*/

// Process a tag attribute
var processMatch = function(match, strUntilValue, name, value, index) {
var self = this;
var containsTemplate = false;

// Check if attribute is included in the "attributes" option
if (!this.isRelevantTagAttr(this.currentTag, name)) {
return;
}

this.matches.push({
start: index + strUntilValue.length,
length: value.length,
value: value
value: value,
containsTemplate: isTemplate(value)
});
};

Expand Down Expand Up @@ -108,4 +164,4 @@ var parser = new Parser(specs);
module.exports = function parse(html, isRelevantTagAttr, usid, root) {
var context = new AttributeContext(isRelevantTagAttr, usid, root);
return parser.parse('outside', html, context);
};
};
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "handlebars-template-loader",
"version": "0.5.7",
"version": "0.6.0",
"description": "A Handlebars template loader for Webpack",
"main": "index.js",
"homepage": "https://github.com/emaphp/handlebars-template-loader",
Expand Down
11 changes: 11 additions & 0 deletions test/loaderTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,4 +90,15 @@ describe('loader', function () {
done();
});
});

it('should leave dynamic attribute unaltered', function (done) {
testTemplate(loader, 'dynamic-attribute.html', {
query: {
root: '/bar'
}
}, function (output) {
assert.equal(removeFirstline(output), loadOutput('dynamic-attribute.txt'));
done();
});
});
});
2 changes: 2 additions & 0 deletions test/templates/dynamic-attribute.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
<img src="images/{{imgFile}}" alt="{{imgDescription}}">
<img src="/images/{{imgFile}}">
11 changes: 11 additions & 0 deletions test/templates/output/dynamic-attribute.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module.exports = (Handlebars['default'] || Handlebars).template({"compiler":[6,">= 2.0.0-beta.1"],"main":function(depth0,helpers,partials,data) {
var helper, alias1=helpers.helperMissing, alias2="function", alias3=this.escapeExpression;

return "<img src=\"images/"
+ alias3(((helper = (helper = helpers.imgFile || (depth0 != null ? depth0.imgFile : depth0)) != null ? helper : alias1),(typeof helper === alias2 ? helper.call(depth0,{"name":"imgFile","hash":{},"data":data}) : helper)))
+ "\" alt=\""
+ alias3(((helper = (helper = helpers.imgDescription || (depth0 != null ? depth0.imgDescription : depth0)) != null ? helper : alias1),(typeof helper === alias2 ? helper.call(depth0,{"name":"imgDescription","hash":{},"data":data}) : helper)))
+ "\">\n<img src=\"/images/"
+ alias3(((helper = (helper = helpers.imgFile || (depth0 != null ? depth0.imgFile : depth0)) != null ? helper : alias1),(typeof helper === alias2 ? helper.call(depth0,{"name":"imgFile","hash":{},"data":data}) : helper)))
+ "\">";
},"useData":true});

0 comments on commit 3865c22

Please sign in to comment.