mirror of
https://github.com/KevinMidboe/inline-html.git
synced 2025-10-29 17:40:29 +00:00
Fix bug where bundler stops watching files when an error occurs.
This commit is contained in:
64
lib/index.js
64
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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
67
lib/inline-less.js
Normal file
67
lib/inline-less.js
Normal file
@@ -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 = $('<style>').html(outputs[index].css);
|
||||
$(element).replaceWith(style);
|
||||
});
|
||||
|
||||
return {
|
||||
html: $.xml(),
|
||||
files
|
||||
};
|
||||
});
|
||||
module.exports = inlineLess;
|
||||
@@ -1,65 +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 url = require('url');
|
||||
|
||||
var render = co.wrap(function * (filename, options) {
|
||||
options = R.merge(options || {}, {
|
||||
filename: filename
|
||||
});
|
||||
var contents = yield fs.readFile(filename, 'utf8');
|
||||
return yield less.render(contents, options);
|
||||
});
|
||||
/**
|
||||
* @params html
|
||||
* HTML to inline
|
||||
* @params options
|
||||
* LESS compiler options
|
||||
*/
|
||||
var inline = co.wrap(function * (html, filename, options) {
|
||||
var files = [];
|
||||
var basedir = path.dirname(filename);
|
||||
options = Ru.defaults({
|
||||
relativeUrls: true
|
||||
}, options || {});
|
||||
|
||||
// TODO Import less links
|
||||
// get links
|
||||
var $ = cheerio.load(html, {decodeEntities: false});
|
||||
var links = $('link[rel="stylesheet/less"]')
|
||||
.filter(function (index, element) {
|
||||
return isLocalPath($(element).attr('href'));
|
||||
});
|
||||
|
||||
// render stylesheets
|
||||
var outputs = [];
|
||||
links.each(function (index, element) {
|
||||
var href = $(element).attr('href');
|
||||
var filename = path.resolve(basedir, href);
|
||||
files.push(filename);
|
||||
outputs.push(render(filename, options));
|
||||
});
|
||||
outputs = yield outputs;
|
||||
|
||||
// replace links
|
||||
links.each(function (index, element) {
|
||||
var style = $('<style>').html(outputs[index].css);
|
||||
$(element).replaceWith(style);
|
||||
});
|
||||
|
||||
// create list of imported files from all outputs, unique listing
|
||||
files.push(R.map(function (output) {
|
||||
return output.imports;
|
||||
}, outputs));
|
||||
|
||||
return {
|
||||
html: $.xml(),
|
||||
files: files
|
||||
};
|
||||
});
|
||||
module.exports = inline;
|
||||
@@ -1,5 +1,6 @@
|
||||
var cheerio = require('cheerio');
|
||||
var inlineUrl = require('./inline-css-url');
|
||||
var R = require('ramda');
|
||||
var string = require('string');
|
||||
|
||||
var prefix = 'element {';
|
||||
@@ -11,34 +12,43 @@ var unwrap = function (value) {
|
||||
var regexp = new RegExp('^' + prefix + '\\s*(.*)\\s*' + suffix + '$');
|
||||
return value.replace(regexp, '$1');
|
||||
};
|
||||
var inline = function (html, filename) {
|
||||
var inlineStyle = function (html, filename) {
|
||||
var files = [];
|
||||
var $ = cheerio.load(html, {decodeEntities: false});
|
||||
// style elements
|
||||
var styles = $('style');
|
||||
styles.each(function (index, element) {
|
||||
var css = $(element).html();
|
||||
var result = inlineUrl(css, filename);
|
||||
files.push(result.files);
|
||||
$(element).html(result.css);
|
||||
});
|
||||
|
||||
// style attributes
|
||||
var attributes = $('*').filter('[style]');
|
||||
attributes.each(function (index, element) {
|
||||
var css = $(element).attr('style');
|
||||
css = wrap(css);
|
||||
var result = inlineUrl(css, filename);
|
||||
files.push(result.files);
|
||||
css = string(result.css).collapseWhitespace().toString();
|
||||
css = unwrap(css);
|
||||
$(element).attr('style', css);
|
||||
});
|
||||
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: files
|
||||
files
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = inline;
|
||||
module.exports = inlineStyle;
|
||||
|
||||
7
test/fixtures/errors/lessImport/index.html
vendored
Normal file
7
test/fixtures/errors/lessImport/index.html
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet/less" href="main.less" />
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
||||
1
test/fixtures/errors/lessImport/main.less
vendored
Normal file
1
test/fixtures/errors/lessImport/main.less
vendored
Normal file
@@ -0,0 +1 @@
|
||||
@import (less) 'missing.css';
|
||||
9
test/fixtures/errors/lessSyntax/index.html
vendored
Normal file
9
test/fixtures/errors/lessSyntax/index.html
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet/less" href="main.less" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
1
test/fixtures/errors/lessSyntax/main.less
vendored
Normal file
1
test/fixtures/errors/lessSyntax/main.less
vendored
Normal file
@@ -0,0 +1 @@
|
||||
div {
|
||||
9
test/fixtures/errors/lessUrl/index.html
vendored
Normal file
9
test/fixtures/errors/lessUrl/index.html
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<link rel="stylesheet/less" href="main.less" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
3
test/fixtures/errors/lessUrl/main.less
vendored
Normal file
3
test/fixtures/errors/lessUrl/main.less
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
div {
|
||||
background-image: url('missing.png');
|
||||
}
|
||||
147
test/index.js
147
test/index.js
@@ -1,9 +1,10 @@
|
||||
var inline = require('../lib');
|
||||
var co = require('co');
|
||||
var datauri = require('datauri');
|
||||
var expect = require('chai')
|
||||
.use(require('chai-as-promised'))
|
||||
.expect;
|
||||
var datauri = require('datauri');
|
||||
var fs = require('fs');
|
||||
var inline = require('../lib');
|
||||
var path = require('path');
|
||||
|
||||
describe('inlineHtml', function () {
|
||||
@@ -173,4 +174,146 @@ describe('inlineHtml', function () {
|
||||
var html = (source) => `<style> div { background-image: url('${source}'); }</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 = `<img src="${source}" >`;
|
||||
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 = `<div style="background url()"></div>`;
|
||||
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(filename);
|
||||
}
|
||||
});
|
||||
});
|
||||
it('throw error when html style attribute 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 = `<div style="background-image: url('${url}')"></div>`;
|
||||
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(resolvedUrl);
|
||||
}
|
||||
});
|
||||
});
|
||||
it('throw error when html style syntax invalid', () => {
|
||||
return co(function * () {
|
||||
var filename = path.resolve(__dirname, 'index.html');
|
||||
var html = `<style>div {</style>`;
|
||||
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(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, {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);
|
||||
}
|
||||
});
|
||||
});
|
||||
// inline-link-less
|
||||
it('throw error when link href invalid', () => {
|
||||
return co(function * () {
|
||||
var filename = path.resolve(__dirname, 'index.html');
|
||||
var href = 'missing.less';
|
||||
var resolvedHref = path.resolve(path.dirname(filename), href);
|
||||
var html = `<link rel="stylesheet/less" href="${href}">`;
|
||||
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(resolvedHref);
|
||||
}
|
||||
});
|
||||
});
|
||||
it('throw error when less import invalid', () => {
|
||||
return co(function * () {
|
||||
var filename = path.resolve(__dirname, 'fixtures/errors/lessImport/index.html');
|
||||
var lessFilename = path.resolve(path.dirname(filename), 'main.less');
|
||||
try {
|
||||
yield inline(filename);
|
||||
throw new Error('No error thrown');
|
||||
}
|
||||
catch (error) {
|
||||
expect(error).to.have.property('filename').that.equals(lessFilename);
|
||||
expect(error).to.have.property('files').that.contains(lessFilename);
|
||||
}
|
||||
});
|
||||
});
|
||||
it('throw error when less syntax invalid', () => {
|
||||
return co(function * () {
|
||||
var filename = path.resolve(__dirname, 'fixtures/errors/lessSyntax/index.html');
|
||||
var lessFilename = path.resolve(path.dirname(filename), 'main.less');
|
||||
try {
|
||||
yield inline(filename);
|
||||
throw new Error('No error thrown');
|
||||
}
|
||||
catch (error) {
|
||||
expect(error).to.have.property('filename').that.equals(lessFilename);
|
||||
expect(error).to.have.property('files').that.contains(lessFilename);
|
||||
}
|
||||
});
|
||||
});
|
||||
it('throw error when less url invalid', () => {
|
||||
return co(function * () {
|
||||
var filename = path.resolve(__dirname, 'fixtures/errors/lessUrl/index.html');
|
||||
var lessFilename = path.resolve(path.dirname(filename), 'main.less');
|
||||
var badUrl = path.resolve(path.dirname(filename), 'missing.png');
|
||||
try {
|
||||
yield inline(filename);
|
||||
throw new Error('No error thrown');
|
||||
}
|
||||
catch (error) {
|
||||
// expect error.filename to be html file, not less file, since images
|
||||
// aren't inlined until after the compiled less has been inlined into the html.
|
||||
expect(error).to.have.property('filename').that.equals(filename);
|
||||
expect(error).to.have.property('files').that.contains(badUrl);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user