diff --git a/index.js b/index.js index c282fd3..520813e 100644 --- a/index.js +++ b/index.js @@ -1,11 +1,11 @@ var through2 = require('through2'); -var _ = require('lodash'); +var defaults = require('lodash.defaults'); var StringDecoder = require('string_decoder').StringDecoder; var debug = require('debug')('htmlprep'); var Parser = require('./lib/parser'); exports = module.exports = function(options) { - options = _.defaults(options, { + options = defaults(options, { attrPrefix: null, buildType: 'debug', liveReload: false, // Should livereload script be injected diff --git a/lib/attribute-modifier.js b/lib/attribute-modifier.js index a7bd3cb..1fd0003 100644 --- a/lib/attribute-modifier.js +++ b/lib/attribute-modifier.js @@ -1,4 +1,11 @@ -var _ = require('lodash'); +var defaults = require('lodash.defaults'); +var trimStart = require('lodash.trimstart'); +var trimEnd = require('lodash.trimend'); +var includes = require('lodash.includes'); +var isEmpty = require('lodash.isempty'); +var isString = require('lodash.isstring'); +var toArray = require('lodash.toarray'); +var has = require('lodash.has'); var minimatch = require('minimatch'); var absoluteUrlRe = /^http[s]?:\/\//i; @@ -7,7 +14,12 @@ var linkRelAttributeUrlValues = ['alternate', 'help', 'license', 'next', 'prev', var inlineCssUrlRe = /url\(["']?(.*?)["']?\)/; module.exports = function(options) { - _.defaults(options, {noPathPrefixPatterns: []}); + defaults(options, {noPathPrefixPatterns: []}); + + // Strip leading slashes from prefix patterns + options.noPathPrefixPatterns = options.noPathPrefixPatterns.map(function(pattern) { + return trimStart(pattern, '/'); + }); var customAttribute = require('./custom-attribute')(options.attrPrefix); @@ -40,26 +52,26 @@ module.exports = function(options) { // If the tagName is not a standard src attribute tag, leave it alone. // For example angular2 does this: - if (srcAttr === 'src' && !_.includes(validSrcAttributeTags, tagName)) { + if (srcAttr === 'src' && !includes(validSrcAttributeTags, tagName)) { return; } var attrValue = attribs[srcAttr]; // If the attribute is empty, nothing to do. - if (_.isEmpty(attrValue)) return; + if (isEmpty(attrValue)) return; // If the assetPathPrefix option is specified and there is not a data-src-keep // attribute, then prepend the assetPathPrefix. This is generally to repoint // static assets to an absolute CDN url. - if (options.assetPathPrefix && !_.has(attribs, customAttribute('src-keep'))) { + if (options.assetPathPrefix && !has(attribs, customAttribute('src-keep'))) { attrValue = prependAssetPath(attrValue); } // If the fingerprint option is specified and the data-fingerprint custom attribute // is declared on the tag, then append the fingerprint to the assetPath var fingerprintAttr = customAttribute('fingerprint'); - if (options.fingerprint && _.has(attribs, fingerprintAttr)) { + if (options.fingerprint && has(attribs, fingerprintAttr)) { attrValue += (attrValue.indexOf('?') === -1 ? '?' : '&'); attrValue += options.fingerprintQuery + '=' + options.fingerprint; attribs[fingerprintAttr] = null; @@ -72,7 +84,7 @@ module.exports = function(options) { // Don't prepend the asset path to any path that matches // one of the noAssetPathPrefixes patterns. for (var i = 0; i < options.noPathPrefixPatterns.length; i++) { - if (minimatch(assetPath, options.noPathPrefixPatterns[i])) { + if (minimatch(trimStart(assetPath, '/'), options.noPathPrefixPatterns[i])) { return assetPath; } } @@ -98,7 +110,7 @@ module.exports = function(options) { assetPath = stripExtraLeadingSlash(assetPath); if (assetPath.substr(0, 2) === '//') return assetPath; - if (assetPath[0] !== '/' && !_.isEmpty(options.pathFromRoot)) { + if (assetPath[0] !== '/' && !isEmpty(options.pathFromRoot)) { return slashJoin(options.assetPathPrefix, options.pathFromRoot, assetPath); } return slashJoin(options.assetPathPrefix, assetPath); @@ -141,7 +153,7 @@ module.exports = function(options) { } function replaceBaseUrlPlaceholder(attr) { - if (!_.isString(attr) || !options.baseUrlPlaceholder || !options.baseUrl) return attr; + if (!isString(attr) || !options.baseUrlPlaceholder || !options.baseUrl) return attr; return attr.replace(options.baseUrlPlaceholder, options.baseUrl); } @@ -151,16 +163,16 @@ module.exports = function(options) { } function slashJoin() { - var items = _.toArray(arguments); + var items = toArray(arguments); for (var i = 0; i < items.length; i++) { // Trim off any leading slashes if (i > 0) { - items[i] = _.trimStart(items[i], '/'); + items[i] = trimStart(items[i], '/'); } // If not the last item and the rightmost character is a slash if (i < items.length - 1) { - items[i] = _.trimEnd(items[i], '/'); + items[i] = trimEnd(items[i], '/'); } } @@ -169,14 +181,14 @@ module.exports = function(options) { function isResourceLink(tagName, attribs) { if (tagName !== 'link' || !attribs.href) return false; - if (!_.includes(linkRelAttributeUrlValues, attribs.rel)) return true; + if (!includes(linkRelAttributeUrlValues, attribs.rel)) return true; return false; } // Determine if this tag has an href attribute that is a hyperlink to another URL. function isHrefHyperlink(tagName, attribs) { if (tagName === 'a' && attribs.href) return true; - if (tagName === 'link' && _.includes(linkRelAttributeUrlValues, attribs.rel)) return true; + if (tagName === 'link' && includes(linkRelAttributeUrlValues, attribs.rel)) return true; return false; } diff --git a/lib/parser.js b/lib/parser.js index 22c0fcb..ac3b21b 100644 --- a/lib/parser.js +++ b/lib/parser.js @@ -2,7 +2,12 @@ var EventEmitter = require('events').EventEmitter; var HtmlParser = require('../htmlparser2').Parser; var debug = require('debug')('htmlprep'); var util = require('util'); -var _ = require('lodash'); +var includes = require('lodash.includes'); +var isEmpty = require('lodash.isempty'); +var isString = require('lodash.isstring'); +var isNull = require('lodash.isnull'); +var forEach = require('lodash.foreach'); +var has = require('lodash.has'); var glob = require('glob'); var singletonTags = ['link', 'meta', 'param', 'source', 'area', 'base', 'br', 'col', @@ -92,14 +97,14 @@ Parser.prototype.onOpenTag = function(tagName, attribs) { debug('open %s', tagName); // Trim all the attribs - _.each(attribs, function(value, key) { + forEach(attribs, function(value, key) { attribs[key] = value.trim(); }); // Content variation block if (this._removing !== true) { // var contentVariation = attribs[customAttrs.contentVariation]; - // if (_.isEmpty(contentVariation) === false) { + // if (isEmpty(contentVariation) === false) { // if (context.tagMatch) throw new Error("Invalid nesting of content-variation element"); // context.variations[variation] = new VariationBuffer(); @@ -108,14 +113,14 @@ Parser.prototype.onOpenTag = function(tagName, attribs) { // } // var variationName = attribs[customAttrs.variation]; - // if (_.isEmpty(variationName) === false) { + // if (isEmpty(variationName) === false) { // if (this._tagMatch) // throw new Error("Invalid nesting of variation element"); // if (options.variation && variationName !== options.variation) { // // Check if we have content for this variation name. // var substituteContent = context.contentVariations[variationName]; - // if (_.isEmpty(substituteContent) === false) { + // if (isEmpty(substituteContent) === false) { // // Write the substitute content. // context.writeOutput(substituteContent); // context.startTagMatch(name, true); @@ -129,12 +134,12 @@ Parser.prototype.onOpenTag = function(tagName, attribs) { // If there is a data-placeholder attribute, replace the tag // with the new contents. var placeholder = attribs[this._customAttribute('placeholder')]; - if (_.isEmpty(placeholder) === false) { + if (isEmpty(placeholder) === false) { attribs[this._customAttribute('placeholder')] = null; this._output.push(buildTag(tagName, attribs)); - if (_.isEmpty(this._options.inject[placeholder]) === false) { + if (isEmpty(this._options.inject[placeholder]) === false) { debug('injecting block %s', placeholder); this._output.push(this._options.inject[placeholder]); } @@ -153,13 +158,13 @@ Parser.prototype.onOpenTag = function(tagName, attribs) { if (this._removing === true) return; // If the data-strip attribute is present, remove this tag and everything within it. - if (_.has(attribs, this._customAttribute('strip'))) { + if (has(attribs, this._customAttribute('strip'))) { this.startTagMatch(tagName, true); return; } var buildType = attribs[this._customAttribute('build')]; - if (_.isEmpty(buildType) === false) { + if (isEmpty(buildType) === false) { this.startTagMatch(tagName, buildType !== this._options.buildType); if (this._removing === true) return; @@ -202,7 +207,7 @@ Parser.prototype.onOpenTag = function(tagName, attribs) { this._attributeModifier(tagName, attribs); - this._output.push(buildTag(tagName, attribs, _.includes(singletonTags, tagName))); + this._output.push(buildTag(tagName, attribs, includes(singletonTags, tagName))); }; Parser.prototype.onCloseTag = function(tagName) { @@ -218,7 +223,7 @@ Parser.prototype.onCloseTag = function(tagName) { if (removing === true) return; // Don't close singleton tags - if (_.includes(singletonTags, tagName)) return; + if (includes(singletonTags, tagName)) return; // Special blocks appended to the head if (tagName === 'head') { @@ -267,7 +272,7 @@ Parser.prototype.popTag = function(tagName) { Parser.prototype.appendFingerprintQuery = function(attribs) { var assetPath = attribs.src; - if (_.isEmpty(assetPath)) return; + if (isEmpty(assetPath)) return; // If this is an embedded url, leave it be. if (assetPath.slice(0, 5) === 'data:') return; @@ -301,10 +306,10 @@ Parser.prototype.expandGlobPattern = function(tagName, pattern, attribs) { function buildTag(name, attribs, selfClosing) { var tag = '<' + name; - _.each(attribs, function(attrValue, key) { - if (_.isString(attrValue) && attrValue.length === 0) { + forEach(attribs, function(attrValue, key) { + if (isString(attrValue) && attrValue.length === 0) { tag += ' ' + key; - } else if (_.isNull(attrValue) === false) { + } else if (isNull(attrValue) === false) { tag += ' ' + key + '="' + attrValue + '"'; } }); diff --git a/package.json b/package.json index a3b17d5..8523571 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "htmlprep", - "version": "1.5.2", + "version": "1.5.3", "description": "High-performance streaming HTML pre-processor designed to run in middleware.", "main": "index.js", "scripts": { @@ -33,7 +33,17 @@ "domutils": "^1.5.1", "entities": "^1.1.1", "glob": "^7.0.3", - "lodash": "^4.6.1", + "lodash.defaults": "^4.0.1", + "lodash.foreach": "^4.2.0", + "lodash.has": "^4.3.1", + "lodash.includes": "^4.1.2", + "lodash.isempty": "^4.2.1", + "lodash.isfunction": "^3.0.8", + "lodash.isnull": "^3.0.0", + "lodash.isstring": "^4.0.1", + "lodash.toarray": "^4.2.4", + "lodash.trimend": "^4.3.0", + "lodash.trimstart": "^4.3.0", "minimatch": "^3.0.0", "readable-stream": "^2.0.5", "through2": "^2.0.1" diff --git a/test/attributes.js b/test/attributes.js index 9b11c9e..3fc6417 100644 --- a/test/attributes.js +++ b/test/attributes.js @@ -306,6 +306,22 @@ describe('htmlprep attributes', function() { }); }); + it('noAssetPathPrefixes handles non-leading slash', function(done) { + var html = ''; + + var opts = { + noPathPrefixPatterns: ['/img/*.jpg'], + assetPathPrefix: '//cdn.net/' + }; + + run(html, opts, function(err, output) { + if (err) return done(err); + + assert.equal(output, html); + done(); + }); + }); + it('trims leading whitespace from attributes', function(done) { var html = ' Home'; diff --git a/test/run.js b/test/run.js index db78772..9185c73 100644 --- a/test/run.js +++ b/test/run.js @@ -1,9 +1,9 @@ -var _ = require('lodash'); +var isFunction = require('lodash.isfunction'); var htmlprep = require('../'); var stream = require('stream'); module.exports = function(html, options, callback) { - if (_.isFunction(options)) { + if (isFunction(options)) { callback = options; options = {}; }