20 Commits

Author SHA1 Message Date
Alexandre Gigliotti
aa0af6102f 0.2.0 2015-10-14 07:34:10 -07:00
Alexandre Gigliotti
bd793daae4 Change api to separate html file and string processing. 2015-10-14 07:33:49 -07:00
Alexandre Gigliotti
2c981a6038 Update dependency. 2015-10-13 11:13:04 -07:00
Alexandre Gigliotti
3aedd5fcc4 0.1.11 2015-10-07 14:27:01 -07:00
Alexandre Gigliotti
c7ca7bfc75 Fix bug where bundler stops watching files when an error occurs. 2015-10-07 13:52:02 -07:00
Alexandre Gigliotti
f28f0e718a 0.1.10 2015-09-25 13:39:08 -07:00
Alexandre Gigliotti
ff5e4401f7 Fix bug where template expressions were interpreted as paths. 2015-09-25 13:38:32 -07:00
Alexandre Gigliotti
99eed58f96 Replace lodash with ramda. 2015-09-21 10:16:38 -07:00
Alexandre Gigliotti
dad1267607 Fix bug where assets with a space in their path could not be bundled. 2015-09-11 09:17:10 -07:00
Alexandre Gigliotti
f0b9b3cc1e Update dependencies. 2015-09-11 09:10:04 -07:00
Alexandre Gigliotti
f143065dbf 0.1.9 2015-08-26 15:09:16 -07:00
Alexandre Gigliotti
4501132051 Updated build config. 2015-08-26 15:08:09 -07:00
Alexandre Gigliotti
006d059e1b 0.1.8 2015-08-26 15:06:04 -07:00
Alexandre Gigliotti
0a743f6b2e Updated build config. 2015-08-26 15:05:59 -07:00
Alexandre Gigliotti
7bad6838e7 0.1.7 2015-08-26 10:32:44 -07:00
Alexandre Gigliotti
0bd64b1308 Added support for html being a string or filename. Added tests. Updated dependencies. Updated docs. 2015-08-26 10:31:29 -07:00
Alexandre Gigliotti
251431a110 Fixed bug where query strings and hashes were not ignored on css url data type local paths. 2015-08-25 15:08:53 -07:00
Alexandre Gigliotti
e8545de30a Fixed bug where <br/> was returned as <br>. 2015-08-25 13:34:21 -07:00
Alexandre Gigliotti
4a3b6f9d50 0.1.6 2015-08-17 09:17:57 -07:00
Alexandre Gigliotti
5b88484888 Updated README. 2015-08-17 09:17:50 -07:00
28 changed files with 699 additions and 277 deletions

4
.gitignore vendored
View File

@@ -1,2 +1,4 @@
node_modules
test/test.html
.idea
*.log
test/test.html

View File

@@ -1,3 +1,7 @@
language: node_js
node_js:
- "node"
- "iojs"
notifications:
email:
on_success: change
on_failure: always

174
README.md
View File

@@ -2,99 +2,143 @@
Inline local assets referenced in an HTML document.
[![npm](https://img.shields.io/npm/v/inline-html.svg)]()
[![npm](https://img.shields.io/npm/l/inline-html.svg)]()
[![Travis](https://img.shields.io/travis/panosoft/inline-html.svg)]()
[![David](https://img.shields.io/david/panosoft/inline-html.svg)]()
[![npm](https://img.shields.io/npm/dm/inline-html.svg)]()
[![npm version](https://img.shields.io/npm/v/inline-html.svg)](https://www.npmjs.com/package/inline-html)
[![npm license](https://img.shields.io/npm/l/inline-html.svg)](https://www.npmjs.com/package/inline-html)
[![Travis](https://img.shields.io/travis/panosoft/inline-html.svg)](https://travis-ci.org/panosoft/inline-html)
[![David](https://img.shields.io/david/panosoft/inline-html.svg)](https://david-dm.org/panosoft/inline-html)
[![npm downloads](https://img.shields.io/npm/dm/inline-html.svg)](https://www.npmjs.com/package/inline-html)
## Installation
This library parses HTML, embeds the contents of local assets that are referenced within that HTML, and returns a new inlined HTML string.
npm install inline-html
The following HTML elements and CSS data types are inlined:
- Images - The source path is replaced with a datauri.
- Linked LESS stylesheets - The LESS is compiled and the result is inlined within a `<style>` element. Note that `@imports` are processed as well.
- CSS url data types - The reference path is replaced with a datauri. These can be used in linked stylesheets, style elements, and element style attributes.
## Usage
This:
```js
var inlineHtml = require('inline-html');
inlineHtml('path/to/file.html').then(function (html) {
...
});
```
Turns this:
```html
<link rel="stylesheet/less" href="main.less"/>
<style>
div { background-image: url('path/to/file'); }
</style>
<div style="background-image: url('path/to/file');"></div>
<img src="path/to/file"/>
```
Into this:
```html
<style>
@font-face { src: url('data:...'); }
div { background-image: url('data:...'); }
</style>
<style>
div { background-image: url('data:...'); }
</style>
<div style="background-image: url('data:...');"></div>
<img src="data:..."/>
```
Where:
Assuming ...
- `main.less`
```css
@import (inline) 'main.css';
div { background-image: url('path/to/file'); }
```
```css
@import (less) 'main.css';
div { background-image: url('path/to/file'); }
```
- `main.css`
```css
@font-face { src: url('path/to/file'); }
```css
@font-face { src: url('path/to/file'); }
```
Then ...
```js
var co = require('co');
var inline = require('inline-html');
co(function * () {
var html = `
<link rel="stylesheet/less" href="main.less"/>
<style> div { background-image: url('path/to/file'); } </style>
<div style="background-image: url('path/to/file');"></div>
<img src="path/to/file"/>
`;
html = yield inline.html(html);
console.log(html);
/**
<style>
@font-face { src: url('data:...'); }
div { background-image: url('data:...'); }
</style>
<style> div { background-image: url('data:...'); } </style>
<div style="background-image: url('data:...');"></div>
<img src="data:..."/>
*/
});
```
## Installation
```sh
npm install inline-html
```
## API
### inlineHtml( filename [, options] )
- [`inline.html`](#html)
- [`inline.file`](#file)
- [`Results`](#results)
Reads an HTML file and embeds the contents of local assets referenced by the following elements and data types:
---
- LESS stylesheets - The LESS is compiled and the result is inlined within a `<style>` element.
<a name="html"/>
### inline.html ( html [, options] )
<link rel="stylesheet/less" href="main.less"/> -> <style>...</style>
Parses an HTML string and embeds referenced local assets into the HTML.
- CSS url data types - The reference path is replaced with a datauri. These can be used in linked stylesheets, style elements, and element style attributes.
Returns a `Promise` that is fulfilled with an `html` string or an instance of [`Results`](#results) depending on the value of `options.verbose`.
url('file.ext') -> url('data:...')
__Arguments__
- Images - The source path is replaced with a datauri.
- `html` - An HTML string to inline.
<img src="file.ext"/> -> <img src="data:..."/>
Returns a `Promise` that is fulfilled with an `html` string or a `results` object depending on the value of `options.verbose`.
- `options`
- `filename` - The filename used to resolve relative paths. If this option is not provided, relative paths will be resolved relative to the process's current working directory.
- `less` - An object containing LESS options to pass to the less compiler. Defaults to `{}`.
- `verbose` - A boolean that determines the promises fulfillment value. Supported values are:
- `true`: An instance of [`Results`](#results).
- `false`: An `html` string. (_Default_)
__Example__
```js
co(function * () {
var html = yield inline.html(`<img src="test.png">`);
console.log(html); // <img src="data:...">
});
```
---
<a name="file"/>
### inline.file ( filename [, options] )
Reads an HTML file and embeds referenced local assets into the HTML.
Returns a `Promise` that is fulfilled with an `html` string or an instance of [`Results`](#results) depending on the value of `options.verbose`.
__Arguments__
- `html` - A filename of an HTML file to inline. Relative file paths are resolved relative to the filename's directory.
#### Arguments
- `filename` - The filename of the HTML file to be inlined. Relative file paths are resolved relative to the filename directory.
- `options`
- `less` - An object containing LESS options to pass to the less compiler. Defaults to `{}`.
- `verbose` - A boolean that determines the promises fulfillment value. Defaults to `false`.
- `true`: promise is resolved with an instance of `Results`
- `false`: promise is resolved with `html`
- `verbose` - A boolean that determines the promises fulfillment value. Supported values are:
- `true`: An instance of [`Results`](#results).
- `false`: An `html` string. (_Default_)
#### Results object
__Example__
The `Promise` returned by this function is optionally fulfilled with a `results` object that has the following properties:
```js
co(function * () {
html = yield inline.file(`index.html`);
console.log(html); // <img src="data:...">
});
```
---
<a name="results"/>
### Results
The `Promise` returned by these functions is optionally fulfilled with a `results` object that has the following properties:
- `html` - The inlined html
- `files` - An array of filenames of the inlined local assets.
- `files` - An array of filenames for the local assets that were inlined.

View File

@@ -1,43 +1,58 @@
var _ = require('lodash');
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 * (filename, options) {
options = _.defaults(options || {}, {
var inlineHtml = {};
/**
* Embed referenced local assets within and HTML file.
*
* @param {String} html
* @param {Object} options
*
* @return {Promise}
*/
inlineHtml.html = co.wrap(function * (html, options) {
options = Ru.defaults({
filename: null,
less: {},
verbose: false
});
var html = yield fs.readFile(filename, 'utf8');
}, options || {});
var filename = options.filename;
// 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
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;
}
// 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 result = {
html: html,
files: _.unique(_.flatten(files, true))
};
files = R.uniq(files);
var result = { html, files };
return (options.verbose ? result : result.html);
});
module.exports = inline;
inlineHtml.file = co.wrap(function * (filename, options) {
var html = yield fs.readFile(filename, 'utf8');
options = R.merge(options || {}, {filename});
return yield inlineHtml.html(html, options);
});
module.exports = inlineHtml;

View File

@@ -1,28 +1,61 @@
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 url = require('postcss-url');
var postcssUrl = require('postcss-url');
var R = require('ramda');
var url = require('url');
var inline = function (css, filename) {
/**
* 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(url({
url: function (url) {
if (isLocalPath(url)) {
url = path.resolve(basePath, url);
files.push(url);
url = datauri(url);
.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 url;
return urlPath;
}
}))
.process(css);
files = R.uniq(files);
return {
css: result.css,
files: files
files
};
};
module.exports = inline;
module.exports = inlineUrl;

View File

@@ -1,25 +1,36 @@
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(function (index, element) {
return isLocalPath($(element).attr('src'));
});
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);
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: $.html(),
files: files
html: $.xml(),
files
};
};

67
lib/inline-less.js Normal file
View 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;

View File

@@ -1,64 +0,0 @@
var _ = require('lodash');
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 url = require('url');
var render = co.wrap(function * (filename, options) {
options = _.assign(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 = _.defaults(options || {}, {
relativeUrls: true
});
// 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(_.map(outputs, function (output) {
return output.imports;
}));
return {
html: $.html(),
files: files
};
});
module.exports = inline;

View File

@@ -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: $.html(),
files: files
html: $.xml(),
files
};
};
module.exports = inline;
module.exports = inlineStyle;

View File

@@ -0,0 +1,14 @@
/**
* Tests whether a path is a mustache template expression.
*
* Note: would be best if this was file extension specific
* (i.e. *.hbs => test for `{{ }}` )
*
* @param {String} path
* @return {Boolean}
*/
var isTemplateExpression = function (path) {
return /^{{.*}}$/.test(path);
};
module.exports = isTemplateExpression;

View File

@@ -1,24 +1,30 @@
{
"name": "inline-html",
"version": "0.1.5",
"version": "0.2.0",
"description": "Inline local assets referenced in an HTML document.",
"repository": "panosoft/inline-html",
"main": "lib/index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"test": "mocha --harmony_arrow_functions"
},
"author": "",
"license": "MIT",
"dependencies": {
"@panosoft/ramda-utils": "^0.1.12",
"cheerio": "^0.19.0",
"co": "^4.6.0",
"datauri": "^0.7.1",
"datauri": "^0.8.0",
"is-local-path": "^0.1.0",
"less": "^2.5.1",
"lodash": "^3.10.0",
"mz": "^2.0.0",
"postcss": "^4.1.16",
"postcss-url": "^4.0.0",
"postcss": "^5.0.0",
"postcss-url": "^5.0.0",
"ramda": "^0.17.1",
"string": "^3.3.0"
},
"devDependencies": {
"chai": "^3.2.0",
"chai-as-promised": "^5.1.0",
"mocha": "^2.2.5"
}
}

View File

@@ -1,8 +0,0 @@
body {
border: solid 4px green;
}
#import {
background-image: url('./person.png');
background-size: contain;
background-repeat: no-repeat;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

1
test/fixtures/basic.css vendored Normal file
View File

@@ -0,0 +1 @@
div { color: blue; }

1
test/fixtures/basic.less vendored Normal file
View File

@@ -0,0 +1 @@
div { color: blue; }

1
test/fixtures/css-url.html vendored Normal file
View File

@@ -0,0 +1 @@
<style>div { background-image: url("file.txt"); }</style>

1
test/fixtures/file space.txt vendored Normal file
View File

@@ -0,0 +1 @@
Test.

1
test/fixtures/file.txt vendored Normal file
View File

@@ -0,0 +1 @@
Test.

1
test/fixtures/img.html vendored Normal file
View File

@@ -0,0 +1 @@
<img src="file.txt"/>

2
test/fixtures/import.less vendored Normal file
View File

@@ -0,0 +1,2 @@
@import 'basic.less';
@import (inline) 'basic.css';

View File

@@ -1,25 +0,0 @@
<!doctype html>
<html>
<head>
<link rel="stylesheet/less" href="main.less"/>
<style>
div {
padding-left: 2em;
}
#style {
background-image: url('./assets/person.png');
background-size: contain;
background-repeat: no-repeat;
}
</style>
</head>
<body>
<div id="link">Link</div>
<div id="import">Import</div>
<div id="style">Style</div>
<div id="attribute" style="background-image: url('./assets/person.png'); background-size: contain; background-repeat: no-repeat;">Attribute</div>
<img height="32px" width="32px" src="./assets/person.png"/>Image
{{> partial}}
{{helper}}
</body>
</html>

1
test/fixtures/invalidImport.less vendored Normal file
View File

@@ -0,0 +1 @@
@import (less) 'missing.css';

1
test/fixtures/invalidSyntax.less vendored Normal file
View File

@@ -0,0 +1 @@
div {

3
test/fixtures/invalidUrl.less vendored Normal file
View File

@@ -0,0 +1,3 @@
div {
background-image: url('missing.png');
}

View File

@@ -1,7 +0,0 @@
@import (less) './assets/imported.css';
#link {
background-image: url('assets/person.png');
background-size: contain;
background-repeat: no-repeat;
}

View File

@@ -1,13 +1,337 @@
var co = require('co');
var datauri = require('datauri');
var expect = require('chai')
.use(require('chai-as-promised'))
.expect;
var fs = require('fs');
var inline = require('../lib');
var path = require('path');
var fs = require('fs');
var filepath = path.resolve(__dirname, './fixtures/index.html');
var options = {verbose: true};
describe('inline-html', () => {
inline(filepath, options)
.then(function (html) {
console.log(html);
fs.writeFileSync('./test.html', html);
})
.catch(console.error);
describe('file()', () => {
it('exists', () => expect(inline).to.have.property('file').that.is.a('function'));
it('accept html filename', () => {
var filename = path.resolve(__dirname, './fixtures/file.txt');
var content = fs.readFileSync(filename, 'utf8');
return expect(inline.file(filename)).to.eventually.equal(content);
});
it('options.filename: ignored for css url path resolution', () => {
var filename = path.resolve(__dirname, 'fixtures/css-url.html'); // file contains url path relative to itself
var options = { filename: __filename }; // sets filename relative to this test file
return expect(inline.file(filename, options)).to.eventually.match(/"data:.*,.*"/);
});
it('options.filename: ignored for img src path resolution', () => {
var filename = path.resolve(__dirname, 'fixtures/img.html'); // file contains src relative to itself
var options = { filename: __filename }; // sets filename relative to this test file
return expect(inline.file(filename, options)).to.eventually.match(/"data:.*,.*"/);
});
it('throw error when file not found', () => expect(inline.file('missing.html')).to.eventually.be.rejected);
});
describe('html()', () => {
it('exists', () => expect(inline).to.have.property('html').that.is.a('function'));
it('accept html string', () => {
var html = 'HTML';
return expect(inline.html(html)).to.eventually.equal(html);
});
it('inline link less', () => {
var filename = path.resolve(__dirname, 'fixtures/basic.less');
var html = `<link rel="stylesheet/less" href="${filename}"/>`;
return expect(inline.html(html)).to.eventually.match(/<style>[^]*<\/style>/);
});
it('inline link less imports', () => {
var filename = path.resolve(__dirname, 'fixtures/import.less');
var html = `<link rel="stylesheet/less" href="${filename}"/>`;
return expect(inline.html(html)).to.eventually.match(/<style>[^]*<\/style>/)
.and.not.match(/@import/);
});
it('inline css url path in style element', () => {
var filename = path.resolve(__dirname, 'fixtures/file.txt');
var html = `<style>div {background-image: url("${filename}");}</style>`;
return expect(inline.html(html)).to.eventually.match(/data:.*,.*/);
});
it('inline css url path in element style attribute', () => {
var filename = path.resolve(__dirname, 'fixtures/file.txt');
var html = `<div style="background-image: url('${filename}');"></div>`;
return expect(inline.html(html)).to.eventually.match(/data:.*,.*/);
});
it('inline img src', () => {
var filename = path.resolve(__dirname, 'fixtures/file.txt');
var html = `<img src="${filename}"/>`;
return expect(inline.html(html)).to.eventually.match(/data:.*,.*/);
});
it('options.verbose: return results object if true', () => {
var filename = path.resolve(__dirname, 'fixtures/file.txt');
var html = `<img src="${filename}"/>`;
var options = { verbose: true };
return expect(inline.html(html, options)).to.eventually.be.an('object')
.that.contains.keys(['html', 'files']);
});
it('options.verbose: return html if false', () => {
var filename = path.resolve(__dirname, 'fixtures/file.txt');
var html = `<img src="${filename}"/>`;
var options = { verbose: false };
return expect(inline.html(html, options)).to.eventually.be.a('string');
});
it('options.verbose: default false', () => {
var filename = path.resolve(__dirname, 'fixtures/file.txt');
var html = `<img src="${filename}"/>`;
return expect(inline.html(html)).to.eventually.be.a('string');
});
it('options.filename: set basepath for css url path resolution', () => {
var filename = path.resolve(__dirname, 'fixtures/fake.html');
var dirname = path.dirname(filename);
var url = 'file.txt'; // Note: path relative to filename's dirname
var uri = datauri(path.resolve(dirname, url));
var html = (path) => `<style>div { background-image: url('${path}'); }</style>`;
var options = { filename: filename };
return expect(inline.html(html(url), options)).to.eventually.equal(html(uri));
});
it('options.filename: set basepath for img src path resolution', () => {
var filename = path.resolve(__dirname, 'fixtures/fake.html');
var dirname = path.dirname(filename);
var url = 'file.txt'; // Note: path relative to filename's dirname
var uri = datauri(path.resolve(dirname, url));
var html = (path) => `<img src="${path}"/>`;
var options = { filename: filename };
return expect(inline.html(html(url), options)).to.eventually.equal(html(uri));
});
it('options.filename: default to cwd for css url path resolution', () => {
var url = 'test/fixtures/file.txt'; // Note: this is relative to cwd
var uri = datauri(url);
var html = (path) => `<style>div { background-image: url('${path}'); }</style>`;
return expect(inline.html(html(url))).to.eventually.equal(html(uri));
});
it('options.filename: default to cwd for img src path resolution', () => {
var url = 'test/fixtures/file.txt'; // Note: path relative to cwd
var uri = datauri(url);
var html = (path) => `<img src="${path}"/>`;
return expect(inline.html(html(url))).to.eventually.equal(html(uri));
});
it('options.filename: included in results.files for img src if options.verbose true', () => {
var filename = path.resolve(__dirname, 'fixtures/file.txt');
var html = `<img src="${filename}"/>`;
var options = { verbose: true };
return expect(inline.html(html, options)).to.eventually.have.property('files')
.that.is.an('array')
.that.contains(filename);
});
it('options.filename: included in results.files for css url path if options.verbose true', () => {
var filename = path.resolve(__dirname, 'fixtures/file.txt');
var html = `<style>div { background-image: url('${filename}'); }</style>`;
var options = { verbose: true };
return expect(inline.html(html, options)).to.eventually.have.property('files')
.that.is.an('array')
.that.contains(filename);
});
it('preserve self closing tags', () => {
var html = '<br/>';
return expect(inline.html(html)).to.eventually.equal(html);
});
it('preserve partials', () => {
var html = '{{> partial}}';
return expect(inline.html(html)).to.eventually.equal(html);
});
it('preserve helpers', () => {
var html = '{{helper}}';
return expect(inline.html(html)).to.eventually.equal(html);
});
it('ignore css url remote paths', () => {
var html = `<style> div { background-image: url('http://test.com/file.txt?query=string#hash'); }</style>`;
return expect(inline.html(html)).to.eventually.equal(html);
});
it('ignore img src remote paths', () => {
var html = `<img src="http://test.com/file.txt?query=string#hash"/>`;
return expect(inline.html(html)).to.eventually.equal(html);
});
it('ignore css url template expression paths', () => {
var html = `<style> div { background-image: url({{path}}); }</style>`;
return expect(inline.html(html)).to.eventually.equal(html);
});
it('ignore img src template expression paths', () => {
var html = `<img src="{{path}}"/>`;
return expect(inline.html(html)).to.eventually.equal(html);
});
it('ignore query strings and hashes on local paths', () => {
var filename = path.resolve(__dirname, 'fixtures/file.txt');
var url = `${filename}?query=string#hash`;
var uri = datauri(filename);
var html = (source) => `<style> div { background-image: url('${source}'); }</style>`;
return expect(inline.html(html(url))).to.eventually.equal(html(uri));
});
it('handle assets with a space in their filename', () => {
var filename = path.resolve(__dirname, 'fixtures/file space.txt');
var uri = datauri(filename);
var html = (source) => `<style> div { background-image: url('${source}'); }</style>`;
return expect(inline.html(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(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(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(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(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);
}
});
});
// 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(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/index.html');
var lessBasename = 'invalidImport.less';
var lessFilename = path.resolve(path.dirname(filename), lessBasename);
var html = `<link rel="stylesheet/less" href="${lessBasename}">`;
try {
yield inline.html(html, {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/index.html');
var lessBasename = 'invalidSyntax.less';
var lessFilename = path.resolve(path.dirname(filename), lessBasename);
var html = `<link rel="stylesheet/less" href="${lessBasename}">`;
try {
yield inline.html(html, {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/index.html');
var lessBasename = 'invalidUrl.less';
var lessFilename = path.resolve(path.dirname(filename), lessBasename);
var badUrl = path.resolve(path.dirname(filename), 'missing.png');
var html = `<link rel="stylesheet/less" href="${lessBasename}">`;
try {
yield inline.html(html, {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);
}
});
});
});
});

View File

@@ -1,7 +0,0 @@
var inline = require('../lib/inline-css-url');
var fs = require('fs');
var filePath = './fixtures/assets/imported.css';
var css = fs.readFileSync(filePath, 'utf8');
var result = inline(css, filePath);
console.log(result.css);

View File

@@ -1,10 +0,0 @@
var inline = require('../lib/inline-link-less');
var fs = require('fs');
var path = require('path');
var filepath = path.resolve(__dirname, './fixtures/index.html');
var html = fs.readFileSync(filepath, 'utf8');
inline(html, filepath, {})
.then(console.log)
.catch(console.error);