mirror of
https://github.com/KevinMidboe/inline-html.git
synced 2025-10-29 17:40:29 +00:00
Refactor. Update dependencies. Update tests. Make img plugin async.
This commit is contained in:
@@ -3,10 +3,7 @@
|
|||||||
Inline local assets referenced in an HTML document.
|
Inline local assets referenced in an HTML document.
|
||||||
|
|
||||||
[](https://www.npmjs.com/package/inline-html)
|
[](https://www.npmjs.com/package/inline-html)
|
||||||
[](https://www.npmjs.com/package/inline-html)
|
|
||||||
[](https://travis-ci.org/panosoft/inline-html)
|
[](https://travis-ci.org/panosoft/inline-html)
|
||||||
[](https://david-dm.org/panosoft/inline-html)
|
|
||||||
[](https://www.npmjs.com/package/inline-html)
|
|
||||||
|
|
||||||
This library parses HTML, embeds the contents of local assets that are referenced within that HTML, and returns a new inlined HTML string.
|
This library parses HTML, embeds the contents of local assets that are referenced within that HTML, and returns a new inlined HTML string.
|
||||||
|
|
||||||
|
|||||||
133
lib/css-url.js
Normal file
133
lib/css-url.js
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
const datauri = require('datauri');
|
||||||
|
const isLocalPath = require('is-local-path');
|
||||||
|
const isTemplateExpression = require('./is-template-expression');
|
||||||
|
const path = require('path');
|
||||||
|
const postcss = require('postcss');
|
||||||
|
const postcssUrl = require('postcss-url');
|
||||||
|
const R = require('ramda');
|
||||||
|
const string = require('string');
|
||||||
|
const url = require('url');
|
||||||
|
|
||||||
|
const collapseWhitespace = str => string(str).collapseWhitespace().toString();
|
||||||
|
const forEachIndexed = R.addIndex(R.forEach);
|
||||||
|
const format = (path) => url.format(path);
|
||||||
|
const parse = (path) => url.parse(path);
|
||||||
|
const resolve = path.resolve;
|
||||||
|
|
||||||
|
const augmentError = (error, filename, files) => {
|
||||||
|
if (!error.filename) error.filename = filename;
|
||||||
|
error.files = R.uniq(R.concat(files, error.files || []));
|
||||||
|
return error;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns url path without query string and hash if present.
|
||||||
|
*
|
||||||
|
* @param path
|
||||||
|
*
|
||||||
|
* @returns path
|
||||||
|
*/
|
||||||
|
const cleanUrl = R.pipe(
|
||||||
|
parse,
|
||||||
|
R.pick(['protocol', 'host', 'pathname']),
|
||||||
|
format,
|
||||||
|
decodeURI
|
||||||
|
);
|
||||||
|
/**
|
||||||
|
* Convert local url data type paths to datauris.
|
||||||
|
*
|
||||||
|
* @param css
|
||||||
|
* @param filename
|
||||||
|
* @returns {{css: (css|any), files: Array}}
|
||||||
|
*/
|
||||||
|
const inlineUrl = R.curry((filename, css) => {
|
||||||
|
const basePath = path.dirname(filename);
|
||||||
|
var files = [];
|
||||||
|
const inline = url => {
|
||||||
|
try {
|
||||||
|
if (isLocalPath(url) && !isTemplateExpression(url)) {
|
||||||
|
url = cleanUrl(url);
|
||||||
|
url = resolve(basePath, url);
|
||||||
|
files = R.append(url, files);
|
||||||
|
url = datauri(url);
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
catch (error) { throw augmentError(error, filename, files); }
|
||||||
|
};
|
||||||
|
css = postcss()
|
||||||
|
.use(postcssUrl({ url: inline }))
|
||||||
|
.process(css)
|
||||||
|
.css;
|
||||||
|
files = R.uniq(files);
|
||||||
|
return { css, files };
|
||||||
|
});
|
||||||
|
|
||||||
|
const inlineStyles = ($, filename) => {
|
||||||
|
var files = [];
|
||||||
|
try {
|
||||||
|
const styles = $('style')
|
||||||
|
.toArray();
|
||||||
|
|
||||||
|
const contents = R.map(style => {
|
||||||
|
const css = $(style).html();
|
||||||
|
const result = inlineUrl(filename, css);
|
||||||
|
files = R.concat(files, result.files);
|
||||||
|
return result.css;
|
||||||
|
}, styles);
|
||||||
|
|
||||||
|
forEachIndexed((style, index) => $(style).html(contents[index]), styles);
|
||||||
|
|
||||||
|
return { $, files };
|
||||||
|
}
|
||||||
|
catch (error) { throw augmentError(error, filename, files); }
|
||||||
|
};
|
||||||
|
|
||||||
|
const prefix = 'selector {';
|
||||||
|
const suffix = '}';
|
||||||
|
const matchStyle = new RegExp(`^${prefix}\\s*(.*)\\s*${suffix}$`);
|
||||||
|
const wrap = style => `${prefix}${style}${suffix}`;
|
||||||
|
const unwrap = rule => rule.replace(matchStyle, '$1');
|
||||||
|
|
||||||
|
const inlineStyleAttributes = ($, filename) => {
|
||||||
|
var files = [];
|
||||||
|
try {
|
||||||
|
const elements = $('*')
|
||||||
|
.filter('[style]')
|
||||||
|
.toArray();
|
||||||
|
|
||||||
|
const styles = R.map(element => {
|
||||||
|
var style = $(element).attr('style');
|
||||||
|
const rule = wrap(style);
|
||||||
|
const result = inlineUrl(filename, rule);
|
||||||
|
files = R.concat(files, result.files);
|
||||||
|
style = R.pipe( collapseWhitespace, unwrap )(result.css);
|
||||||
|
return style;
|
||||||
|
}, elements);
|
||||||
|
|
||||||
|
forEachIndexed((element, index) => $(element).attr('style', styles[index]), elements);
|
||||||
|
|
||||||
|
return { $, files };
|
||||||
|
}
|
||||||
|
catch (error) { throw augmentError(error, filename, files); }
|
||||||
|
};
|
||||||
|
|
||||||
|
const inlineCssUrl = function ($, filename) {
|
||||||
|
var files = [];
|
||||||
|
try {
|
||||||
|
var result;
|
||||||
|
result = inlineStyles($, filename);
|
||||||
|
$ = result.$;
|
||||||
|
files = R.concat(files, result.files);
|
||||||
|
|
||||||
|
result = inlineStyleAttributes($, filename);
|
||||||
|
$ = result.$;
|
||||||
|
files = R.concat(files, result.files);
|
||||||
|
|
||||||
|
files = R.uniq(files);
|
||||||
|
return { $, files };
|
||||||
|
}
|
||||||
|
catch (error) { throw augmentError(error, filename, files); }
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = inlineCssUrl;
|
||||||
48
lib/img.js
Normal file
48
lib/img.js
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
const co = require('co');
|
||||||
|
const datauri = require('datauri').promises;
|
||||||
|
const isLocalPath = require('is-local-path');
|
||||||
|
const isTemplateExpression = require('./is-template-expression');
|
||||||
|
const path = require('path');
|
||||||
|
const R = require('ramda');
|
||||||
|
|
||||||
|
const forEachIndexed = R.addIndex(R.forEach);
|
||||||
|
const resolve = R.curry((a,b) => path.resolve(a,b));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inline sourced image files
|
||||||
|
*
|
||||||
|
* @param {Object} $
|
||||||
|
* Parsed HTML source to inline
|
||||||
|
* @param {String} filename
|
||||||
|
* Filename used to resolve relative sources being inlined
|
||||||
|
*/
|
||||||
|
const inlineImg = co.wrap(function * ($, filename) {
|
||||||
|
var files;
|
||||||
|
const basedir = path.dirname(filename);
|
||||||
|
const getAttr = R.curry((attr, element) => $(element).attr(attr));
|
||||||
|
const setAttr = R.curry((attr, element, value) => $(element).attr(attr, value));
|
||||||
|
const getFilename = R.pipe(getAttr('src'), resolve(basedir));
|
||||||
|
try {
|
||||||
|
const images = $('img')
|
||||||
|
.filter((index, element) => {
|
||||||
|
const source = $(element).attr('src');
|
||||||
|
return isLocalPath(source) && !isTemplateExpression(source);
|
||||||
|
})
|
||||||
|
.toArray();
|
||||||
|
|
||||||
|
const filenames = R.map(getFilename, images);
|
||||||
|
files = R.uniq(filenames);
|
||||||
|
const uris = yield R.map(datauri, filenames);
|
||||||
|
|
||||||
|
forEachIndexed((image, index) => setAttr('src', image, uris[index]), images);
|
||||||
|
|
||||||
|
return { $, files };
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
error.filename = filename;
|
||||||
|
error.files = files;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = inlineImg;
|
||||||
59
lib/index.js
59
lib/index.js
@@ -1,12 +1,13 @@
|
|||||||
var co = require('co');
|
const cheerio = require('cheerio');
|
||||||
var fs = require('mz/fs');
|
const co = require('co');
|
||||||
var inlineStyle = require('./inline-style');
|
const fs = require('mz/fs');
|
||||||
var inlineImg = require('./inline-img');
|
const inlineCssUrl = require('./css-url');
|
||||||
var inlineLess = require('./inline-less');
|
const inlineImg = require('./img');
|
||||||
var R = require('ramda');
|
const inlineLess = require('./link-less');
|
||||||
var Ru = require('@panosoft/ramda-utils');
|
const R = require('ramda');
|
||||||
|
const Ru = require('@panosoft/ramda-utils');
|
||||||
|
|
||||||
var inlineHtml = {};
|
var inline = {};
|
||||||
/**
|
/**
|
||||||
* Embed referenced local assets within and HTML file.
|
* Embed referenced local assets within and HTML file.
|
||||||
*
|
*
|
||||||
@@ -15,44 +16,46 @@ var inlineHtml = {};
|
|||||||
*
|
*
|
||||||
* @return {Promise}
|
* @return {Promise}
|
||||||
*/
|
*/
|
||||||
inlineHtml.html = co.wrap(function * (html, options) {
|
inline.html = co.wrap(function * (html, options) {
|
||||||
options = Ru.defaults({
|
options = Ru.defaults({
|
||||||
filename: null,
|
filename: null,
|
||||||
less: {},
|
less: {},
|
||||||
verbose: false
|
verbose: false
|
||||||
}, options || {});
|
}, options || {});
|
||||||
var filename = options.filename;
|
|
||||||
|
|
||||||
// Embed assets
|
const filename = options.filename;
|
||||||
var files = [filename];
|
var files = [filename];
|
||||||
try {
|
try {
|
||||||
var lessResult = yield inlineLess(html, filename, options.less);
|
var $ = cheerio.load(html, {decodeEntities: false});
|
||||||
html = lessResult.html;
|
|
||||||
files = R.concat(files, lessResult.files);
|
|
||||||
|
|
||||||
var styleResult = inlineStyle(html, filename);
|
var result;
|
||||||
html = styleResult.html;
|
result = yield inlineLess($, filename, options);
|
||||||
files = R.concat(files, styleResult.files);
|
$ = result.$;
|
||||||
|
files = R.concat(files, result.files);
|
||||||
|
|
||||||
var imgResult = inlineImg(html, filename);
|
result = inlineCssUrl($, filename);
|
||||||
html = imgResult.html;
|
$ = result.$;
|
||||||
files = R.concat(files, imgResult.files);
|
files = R.concat(files, result.files);
|
||||||
|
|
||||||
|
result = yield inlineImg($, filename);
|
||||||
|
$ = result.$;
|
||||||
|
files = R.concat(files, result.files);
|
||||||
|
|
||||||
|
html = $.xml();
|
||||||
|
files = R.uniq(files);
|
||||||
|
return (options.verbose ? { html, files } : html);
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
if (!error.filename) error.filename = filename;
|
if (!error.filename) error.filename = filename;
|
||||||
error.files = R.uniq(R.concat(files, error.files || []));
|
error.files = R.uniq(R.concat(files, error.files || []));
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
files = R.uniq(files);
|
|
||||||
var result = { html, files };
|
|
||||||
return (options.verbose ? result : result.html);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
inlineHtml.file = co.wrap(function * (filename, options) {
|
inline.file = co.wrap(function * (filename, options) {
|
||||||
var html = yield fs.readFile(filename, 'utf8');
|
const html = yield fs.readFile(filename, 'utf8');
|
||||||
options = R.merge(options || {}, {filename});
|
options = R.merge(options || {}, {filename});
|
||||||
return yield inlineHtml.html(html, options);
|
return yield inline.html(html, options);
|
||||||
});
|
});
|
||||||
|
|
||||||
module.exports = inlineHtml;
|
module.exports = inline;
|
||||||
|
|||||||
@@ -1,61 +0,0 @@
|
|||||||
var datauri = require('datauri');
|
|
||||||
var isLocalPath = require('is-local-path');
|
|
||||||
var isTemplateExpression = require('./is-template-expression');
|
|
||||||
var path = require('path');
|
|
||||||
var postcss = require('postcss');
|
|
||||||
var postcssUrl = require('postcss-url');
|
|
||||||
var R = require('ramda');
|
|
||||||
var url = require('url');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns url path without query string and hash if present.
|
|
||||||
*
|
|
||||||
* @param path
|
|
||||||
*
|
|
||||||
* @returns path
|
|
||||||
*/
|
|
||||||
var clean = function (path) {
|
|
||||||
path = url.parse(path);
|
|
||||||
path = R.pick(['protocol', 'host', 'pathname'], path);
|
|
||||||
path = url.format(path);
|
|
||||||
path = decodeURI(path);
|
|
||||||
return path;
|
|
||||||
};
|
|
||||||
/**
|
|
||||||
* Convert local url data type paths to datauris.
|
|
||||||
*
|
|
||||||
* @param css
|
|
||||||
* @param filename
|
|
||||||
* @returns {{css: (css|any), files: Array}}
|
|
||||||
*/
|
|
||||||
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)) {
|
|
||||||
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
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = inlineUrl;
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
var cheerio = require('cheerio');
|
|
||||||
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((index, element) => {
|
|
||||||
var path = $(element).attr('src');
|
|
||||||
return isLocalPath(path) && !isTemplateExpression(path);
|
|
||||||
});
|
|
||||||
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
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = inline;
|
|
||||||
@@ -1,67 +0,0 @@
|
|||||||
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 = $('<style>').html(outputs[index].css);
|
|
||||||
$(element).replaceWith(style);
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
html: $.xml(),
|
|
||||||
files
|
|
||||||
};
|
|
||||||
});
|
|
||||||
module.exports = inlineLess;
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
var cheerio = require('cheerio');
|
|
||||||
var inlineUrl = require('./inline-css-url');
|
|
||||||
var R = require('ramda');
|
|
||||||
var string = require('string');
|
|
||||||
|
|
||||||
var prefix = 'element {';
|
|
||||||
var suffix = '}';
|
|
||||||
var wrap = function (value) {
|
|
||||||
return prefix + value + suffix;
|
|
||||||
};
|
|
||||||
var unwrap = function (value) {
|
|
||||||
var regexp = new RegExp('^' + prefix + '\\s*(.*)\\s*' + suffix + '$');
|
|
||||||
return value.replace(regexp, '$1');
|
|
||||||
};
|
|
||||||
var inlineStyle = function (html, filename) {
|
|
||||||
var files = [];
|
|
||||||
var $ = cheerio.load(html, {decodeEntities: false});
|
|
||||||
|
|
||||||
try {
|
|
||||||
// style elements
|
|
||||||
var $styles = $('style');
|
|
||||||
$styles.each((index, element) => {
|
|
||||||
var css = $(element).html();
|
|
||||||
var result = inlineUrl(css, filename);
|
|
||||||
files = R.concat(files, result.files);
|
|
||||||
$(element).html(result.css);
|
|
||||||
});
|
|
||||||
|
|
||||||
// style attributes
|
|
||||||
var $attributes = $('*').filter('[style]');
|
|
||||||
$attributes.each((index, element) => {
|
|
||||||
var css = $(element).attr('style');
|
|
||||||
css = wrap(css);
|
|
||||||
var result = inlineUrl(css, filename);
|
|
||||||
files = R.concat(files, result.files);
|
|
||||||
css = string(result.css).collapseWhitespace().toString();
|
|
||||||
css = unwrap(css);
|
|
||||||
$(element).attr('style', css);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
if (!error.filename) error.filename = filename;
|
|
||||||
error.files = R.uniq(R.concat(files, error.files || []));
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
files = R.uniq(files);
|
|
||||||
return {
|
|
||||||
html: $.xml(),
|
|
||||||
files
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = inlineStyle;
|
|
||||||
@@ -7,8 +7,6 @@
|
|||||||
* @param {String} path
|
* @param {String} path
|
||||||
* @return {Boolean}
|
* @return {Boolean}
|
||||||
*/
|
*/
|
||||||
var isTemplateExpression = function (path) {
|
const isTemplateExpression = path => /^{{.*}}$/.test(path);
|
||||||
return /^{{.*}}$/.test(path);
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = isTemplateExpression;
|
module.exports = isTemplateExpression;
|
||||||
|
|||||||
58
lib/link-less.js
Normal file
58
lib/link-less.js
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
const co = require('co');
|
||||||
|
const fs = require('mz/fs');
|
||||||
|
const isLocalPath = require('is-local-path');
|
||||||
|
const less = require('less');
|
||||||
|
const path = require('path');
|
||||||
|
const R = require('ramda');
|
||||||
|
const Ru = require('@panosoft/ramda-utils');
|
||||||
|
|
||||||
|
const forEachIndexed = R.addIndex(R.forEach);
|
||||||
|
const resolve = R.curry((a,b) => path.resolve(a,b));
|
||||||
|
|
||||||
|
const render = R.curryN(2, co.wrap(function * (options, filename) {
|
||||||
|
options = R.merge(options || {}, { filename });
|
||||||
|
const contents = yield fs.readFile(filename, 'utf8');
|
||||||
|
return yield less.render(contents, options);
|
||||||
|
}));
|
||||||
|
/**
|
||||||
|
* Inline linked less files
|
||||||
|
*
|
||||||
|
* @param {Object} $
|
||||||
|
* Parsed HTML source to inline
|
||||||
|
* @param {String} filename
|
||||||
|
* Filename to apply to the HTML source being inlined
|
||||||
|
* @param {Object} [options]
|
||||||
|
* @param {Object} [options.less]
|
||||||
|
* LESS compiler options
|
||||||
|
*/
|
||||||
|
const inlineLess = co.wrap(function * ($, filename, options) {
|
||||||
|
options = Ru.defaults({ less: {} }, options || {});
|
||||||
|
options = Ru.defaults({ relativeUrls: true }, options.less);
|
||||||
|
var files = [];
|
||||||
|
const basedir = path.dirname(filename);
|
||||||
|
const getAttr = R.curry((attr, element) => $(element).attr(attr));
|
||||||
|
const getStylesheet = R.pipe(getAttr('href'), resolve(basedir));
|
||||||
|
try {
|
||||||
|
const links = $('link[rel="stylesheet/less"]')
|
||||||
|
.filter((index, link) => isLocalPath($(link).attr('href')))
|
||||||
|
.toArray();
|
||||||
|
|
||||||
|
const stylesheets = R.map(getStylesheet, links);
|
||||||
|
files = R.concat(files, stylesheets);
|
||||||
|
const outputs = yield R.map(render(options), stylesheets);
|
||||||
|
const imports = R.flatten(R.map(R.prop('imports'), outputs));
|
||||||
|
files = R.concat(files, imports);
|
||||||
|
const styles = R.map(output => $('<style>').html(output.css), outputs);
|
||||||
|
|
||||||
|
forEachIndexed((link, index) => $(link).replaceWith(styles[index]), links);
|
||||||
|
|
||||||
|
files = R.uniq(files);
|
||||||
|
return { $, files };
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
if (!error.filename) error.filename = filename;
|
||||||
|
error.files = R.uniq(files);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
module.exports = inlineLess;
|
||||||
@@ -5,21 +5,22 @@
|
|||||||
"repository": "panosoft/inline-html",
|
"repository": "panosoft/inline-html",
|
||||||
"main": "lib/index.js",
|
"main": "lib/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "mocha --harmony_arrow_functions"
|
"test": "mocha",
|
||||||
|
"test:debug": "mocha --debug-brk"
|
||||||
},
|
},
|
||||||
"author": "",
|
"author": "",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@panosoft/ramda-utils": "^0.1.12",
|
"@panosoft/ramda-utils": "^0.2.0",
|
||||||
"cheerio": "^0.19.0",
|
"cheerio": "^0.19.0",
|
||||||
"co": "^4.6.0",
|
"co": "^4.6.0",
|
||||||
"datauri": "^0.8.0",
|
"datauri": "^0.8.0",
|
||||||
"is-local-path": "^0.1.0",
|
"is-local-path": "^0.1.0",
|
||||||
"less": "^2.5.1",
|
"less": "^2.5.1",
|
||||||
"mz": "^2.0.0",
|
"mz": "^2.0.0",
|
||||||
"postcss": "^5.0.0",
|
"postcss": "^5.0.12",
|
||||||
"postcss-url": "^5.0.0",
|
"postcss-url": "^5.0.0",
|
||||||
"ramda": "^0.17.1",
|
"ramda": "^0.18.0",
|
||||||
"string": "^3.3.0"
|
"string": "^3.3.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
108
test/index.js
108
test/index.js
@@ -203,8 +203,60 @@ describe('inline-html', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
it('image: include all sources in error.files up until and including invalid source', () => {
|
||||||
|
return co(function * () {
|
||||||
|
var filename = path.resolve(__dirname, 'index.html');
|
||||||
|
var valid = 'fixtures/file.txt';
|
||||||
|
var invalid = 'missing.png';
|
||||||
|
var resolvedInvalid = path.resolve(path.dirname(filename), invalid);
|
||||||
|
var resolvedValid = path.resolve(path.dirname(filename), valid);
|
||||||
|
var html = `
|
||||||
|
<img src="${valid}" >
|
||||||
|
<img src="${invalid}" >
|
||||||
|
`;
|
||||||
|
try {
|
||||||
|
yield inline.html(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(resolvedValid);
|
||||||
|
expect(error).to.have.property('files').that.contains(resolvedInvalid);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
// inline-style
|
// inline-style
|
||||||
it('throw error when html style attribute syntax invalid', () => {
|
it('throw error when style element syntax invalid', () => {
|
||||||
|
return co(function * () {
|
||||||
|
var filename = path.resolve(__dirname, 'index.html');
|
||||||
|
var html = `<style>div {</style>`;
|
||||||
|
try {
|
||||||
|
yield inline.html(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(filename);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('throw error when html style url invalid', () => {
|
||||||
|
return co(function * () {
|
||||||
|
var filename = path.resolve(__dirname, 'index.html');
|
||||||
|
var url = 'missing.png';
|
||||||
|
var resolvedUrl = path.resolve(path.dirname(filename), url);
|
||||||
|
var html = `<style>div { background-image: url('${url}'); }</style>`;
|
||||||
|
try {
|
||||||
|
yield inline.html(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(resolvedUrl);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('throw error when style attribute syntax invalid', () => {
|
||||||
return co(function * () {
|
return co(function * () {
|
||||||
var filename = path.resolve(__dirname, 'index.html');
|
var filename = path.resolve(__dirname, 'index.html');
|
||||||
var html = `<div style="background url()"></div>`;
|
var html = `<div style="background url()"></div>`;
|
||||||
@@ -234,33 +286,69 @@ describe('inline-html', () => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('throw error when html style syntax invalid', () => {
|
it('include valid and invalid paths in error.files when html style url invalid', () => {
|
||||||
return co(function * () {
|
return co(function * () {
|
||||||
var filename = path.resolve(__dirname, 'index.html');
|
var filename = path.resolve(__dirname, 'index.html');
|
||||||
var html = `<style>div {</style>`;
|
var validUrl = 'fixtures/file.txt';
|
||||||
|
var invalidUrl = 'missing.png';
|
||||||
|
var resolvedInvalidUrl = path.resolve(path.dirname(filename), invalidUrl);
|
||||||
|
var resolvedValidUrl = path.resolve(path.dirname(filename), validUrl);
|
||||||
|
var html = `
|
||||||
|
<style>div {background-image: url("${validUrl}");}</style>
|
||||||
|
<style>div { background-image: url('${invalidUrl}'); }</style>
|
||||||
|
`;
|
||||||
try {
|
try {
|
||||||
yield inline.html(html, {filename});
|
yield inline.html(html, {filename});
|
||||||
throw new Error('No error thrown');
|
throw new Error('No error thrown');
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
expect(error).to.have.property('filename').that.equals(filename);
|
expect(error).to.have.property('filename').that.equals(filename);
|
||||||
expect(error).to.have.property('files').that.contains(filename);
|
expect(error).to.have.property('files').that.contains(resolvedValidUrl);
|
||||||
|
expect(error).to.have.property('files').that.contains(resolvedInvalidUrl);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
it('throw error when html style url invalid', () => {
|
it('include valid and invalid paths in error.files when html attribute url invalid', () => {
|
||||||
return co(function * () {
|
return co(function * () {
|
||||||
var filename = path.resolve(__dirname, 'index.html');
|
var filename = path.resolve(__dirname, 'index.html');
|
||||||
var url = 'missing.png';
|
var validUrl = 'fixtures/file.txt';
|
||||||
var resolvedUrl = path.resolve(path.dirname(filename), url);
|
var invalidUrl = 'missing.png';
|
||||||
var html = `<style>div { background-image: url('${url}'); }</style>`;
|
var resolvedInvalidUrl = path.resolve(path.dirname(filename), invalidUrl);
|
||||||
|
var resolvedValidUrl = path.resolve(path.dirname(filename), validUrl);
|
||||||
|
var html = `
|
||||||
|
<div style="background-image: url('${validUrl}')"></div>
|
||||||
|
<div style="background-image: url('${invalidUrl}')"></div>
|
||||||
|
`;
|
||||||
try {
|
try {
|
||||||
yield inline.html(html, {filename});
|
yield inline.html(html, {filename});
|
||||||
throw new Error('No error thrown');
|
throw new Error('No error thrown');
|
||||||
}
|
}
|
||||||
catch (error) {
|
catch (error) {
|
||||||
expect(error).to.have.property('filename').that.equals(filename);
|
expect(error).to.have.property('filename').that.equals(filename);
|
||||||
expect(error).to.have.property('files').that.contains(resolvedUrl);
|
expect(error).to.have.property('files').that.contains(resolvedValidUrl);
|
||||||
|
expect(error).to.have.property('files').that.contains(resolvedInvalidUrl);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
it('include valid and invalid paths in error.files when style element valid and html attribute invalid', () => {
|
||||||
|
return co(function * () {
|
||||||
|
var filename = path.resolve(__dirname, 'index.html');
|
||||||
|
var validUrl = 'fixtures/file.txt';
|
||||||
|
var invalidUrl = 'missing.png';
|
||||||
|
var resolvedInvalidUrl = path.resolve(path.dirname(filename), invalidUrl);
|
||||||
|
var resolvedValidUrl = path.resolve(path.dirname(filename), validUrl);
|
||||||
|
var html = `
|
||||||
|
<style>div {background-image: url("${validUrl}");}</style>
|
||||||
|
<div style="background-image: url('${invalidUrl}')"></div>
|
||||||
|
`;
|
||||||
|
try {
|
||||||
|
yield inline.html(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(resolvedValidUrl);
|
||||||
|
expect(error).to.have.property('files').that.contains(resolvedInvalidUrl);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -317,7 +405,6 @@ describe('inline-html', () => {
|
|||||||
return co(function * () {
|
return co(function * () {
|
||||||
var filename = path.resolve(__dirname, 'fixtures/index.html');
|
var filename = path.resolve(__dirname, 'fixtures/index.html');
|
||||||
var lessBasename = 'invalidUrl.less';
|
var lessBasename = 'invalidUrl.less';
|
||||||
var lessFilename = path.resolve(path.dirname(filename), lessBasename);
|
|
||||||
var badUrl = path.resolve(path.dirname(filename), 'missing.png');
|
var badUrl = path.resolve(path.dirname(filename), 'missing.png');
|
||||||
var html = `<link rel="stylesheet/less" href="${lessBasename}">`;
|
var html = `<link rel="stylesheet/less" href="${lessBasename}">`;
|
||||||
try {
|
try {
|
||||||
@@ -333,5 +420,4 @@ describe('inline-html', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user