diff --git a/lib/index.js b/lib/index.js
index 62a0b1c..0b9e0ba 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -1,13 +1,21 @@
var co = require('co');
-var cheerio = require('cheerio');
var fs = require('mz/fs');
var inlineStyle = require('./inline-style');
var inlineImg = require('./inline-img');
-var inlineLinkLess = require('./inline-link-less');
+var inlineLess = require('./inline-less');
var R = require('ramda');
var Ru = require('@panosoft/ramda-utils');
-var inline = co.wrap(function * (html, options) {
+/**
+ * Embed referenced local assets within and HTML file.
+ *
+ * @param {String} html
+ * Filename or html string.
+ * @param {Object} options
+ *
+ * @return {Promise}
+ */
+var inlineHtml = co.wrap(function * (html, options) {
options = Ru.defaults({
filename: null,
less: {},
@@ -19,39 +27,37 @@ var inline = co.wrap(function * (html, options) {
html = yield fs.readFile(filename, 'utf8');
}
catch (error) {
- if (error.code === 'ENOENT') {
- filename = options.filename;
- }
- else {
- throw error;
- }
+ if (error.code === 'ENOENT') filename = options.filename;
+ else throw error;
}
+
+ // Embed assets
var files = [filename];
+ try {
+ var lessResult = yield inlineLess(html, filename, options.less);
+ html = lessResult.html;
+ files = R.concat(files, lessResult.files);
- // Inline links
- var lessResult = yield inlineLinkLess(html, filename, options.less);
- html = lessResult.html;
- files.push(lessResult.files);
+ var styleResult = inlineStyle(html, filename);
+ html = styleResult.html;
+ files = R.concat(files, styleResult.files);
- // TODO inline links: css
-
- // TODO inline scripts
- // browserify js? => scriptify
-
- // Inline paths -> datauris
- var styleResult = inlineStyle(html, filename); // Inline styles
- html = styleResult.html;
- files.push(styleResult.files);
-
- var imgResult = inlineImg(html, filename); // Inline images
- html = imgResult.html;
- files.push(imgResult.files);
+ var imgResult = inlineImg(html, filename);
+ html = imgResult.html;
+ files = R.concat(files, imgResult.files);
+ }
+ catch (error) {
+ if (!error.filename) error.filename = filename;
+ error.files = R.uniq(R.concat(files, error.files || []));
+ throw error;
+ }
+ files = R.uniq(files);
var result = {
- html: html,
- files: R.uniq(R.flatten(files, true))
+ html,
+ files
};
return (options.verbose ? result : result.html);
});
-module.exports = inline;
+module.exports = inlineHtml;
diff --git a/lib/inline-css-url.js b/lib/inline-css-url.js
index 5e71a82..0dad98f 100644
--- a/lib/inline-css-url.js
+++ b/lib/inline-css-url.js
@@ -28,26 +28,34 @@ var clean = function (path) {
* @param filename
* @returns {{css: (css|any), files: Array}}
*/
-var inline = function (css, filename) {
+var inlineUrl = function (css, filename) {
var files = [];
var basePath = path.dirname(filename);
var result = postcss()
.use(postcssUrl({
url: function (urlPath) {
if (isLocalPath(urlPath) && !isTemplateExpression(urlPath)) {
- urlPath = clean(urlPath);
- urlPath = path.resolve(basePath, urlPath);
- files.push(urlPath);
- urlPath = datauri(urlPath);
+ try {
+ urlPath = clean(urlPath);
+ urlPath = path.resolve(basePath, urlPath);
+ files = R.append(urlPath, files);
+ urlPath = datauri(urlPath);
+ }
+ catch (error) {
+ error.filename = filename;
+ error.files = R.uniq(files);
+ throw error;
+ }
}
return urlPath;
}
}))
.process(css);
+ files = R.uniq(files);
return {
css: result.css,
- files: files
+ files
};
};
-module.exports = inline;
+module.exports = inlineUrl;
diff --git a/lib/inline-img.js b/lib/inline-img.js
index f68c1f6..419ce47 100644
--- a/lib/inline-img.js
+++ b/lib/inline-img.js
@@ -3,25 +3,34 @@ var datauri = require('datauri');
var isLocalPath = require('is-local-path');
var isTemplateExpression = require('./is-template-expression');
var path = require('path');
+var R = require('ramda');
var inline = function (html, filename) {
var files = [];
var basedir = path.dirname(filename);
var $ = cheerio.load(html, {decodeEntities: false});
- var images = $('img').filter(function (index, element) {
+ var $images = $('img').filter((index, element) => {
var path = $(element).attr('src');
return isLocalPath(path) && !isTemplateExpression(path);
});
- images.each(function (index, element) {
- var src = $(element).attr('src');
- var filename = path.resolve(basedir, src);
- files.push(filename);
- src = datauri(filename);
- $(element).attr('src', src);
- });
+ try {
+ $images.each((index, element) => {
+ var source = $(element).attr('src');
+ var filename = path.resolve(basedir, source);
+ files = R.append(filename, files);
+ var uri = datauri(filename);
+ $(element).attr('src', uri);
+ });
+ }
+ catch (error) {
+ error.filename = filename;
+ error.files = R.uniq(files);
+ throw error;
+ }
+ files = R.uniq(files);
return {
html: $.xml(),
- files: files
+ files
};
};
diff --git a/lib/inline-less.js b/lib/inline-less.js
new file mode 100644
index 0000000..cad44d9
--- /dev/null
+++ b/lib/inline-less.js
@@ -0,0 +1,67 @@
+var co = require('co');
+var cheerio = require('cheerio');
+var fs = require('mz/fs');
+var isLocalPath = require('is-local-path');
+var less = require('less');
+var path = require('path');
+var R = require('ramda');
+var Ru = require('@panosoft/ramda-utils');
+
+var render = co.wrap(function * (filename, options) {
+ options = R.merge(options || {}, { filename });
+ var contents = yield fs.readFile(filename, 'utf8');
+ return yield less.render(contents, options);
+});
+/**
+ * @param {String} html
+ * HTML source to inline
+ * @param {String} filename
+ * Filename to apply to the HTML source being inlined
+ * @param {Object} options
+ * LESS compiler options
+ */
+var inlineLess = co.wrap(function * (html, filename, options) {
+ options = Ru.defaults({
+ relativeUrls: true
+ }, options || {});
+ var basedir = path.dirname(filename);
+
+ // get links
+ var $ = cheerio.load(html, {decodeEntities: false});
+ var $links = $('link[rel="stylesheet/less"]')
+ .filter((index, element) => isLocalPath($(element).attr('href')));
+
+ // render LESS stylesheets
+ var files = [];
+ var outputs = [];
+ try {
+ $links.each((index, element) => {
+ var href = $(element).attr('href');
+ var filename = path.resolve(basedir, href);
+ files = R.append(filename, files);
+ outputs = R.append(render(filename, options), outputs);
+ });
+ outputs = yield outputs;
+ }
+ catch (error) {
+ if (!error.filename) error.filename = filename;
+ error.files = R.uniq(files);
+ throw error;
+ }
+
+ // include imported filenames in files array
+ files = R.concat(files, R.flatten(R.map(output => output.imports, outputs)));
+ files = R.uniq(files);
+
+ // replace links
+ $links.each((index, element) => {
+ var style = $('`;
return expect(inline(html(filename))).to.eventually.equal(html(uri));
});
+
+ // Error handling
+ // inline-img
+ it('throw error when html image source invalid', () => {
+ return co(function * () {
+ var filename = path.resolve(__dirname, 'index.html');
+ var source = 'missing.png';
+ var html = ``;
+ var resolvedSource = path.resolve(path.dirname(filename), source);
+ try {
+ yield inline(html, {filename});
+ throw new Error('No error thrown');
+ }
+ catch (error) {
+ expect(error).to.have.property('filename').that.equals(filename);
+ expect(error).to.have.property('files').that.contains(resolvedSource);
+ }
+ });
+ });
+ // inline-style
+ it('throw error when html style attribute syntax invalid', () => {
+ return co(function * () {
+ var filename = path.resolve(__dirname, 'index.html');
+ var html = `