mirror of
https://github.com/KevinMidboe/inline-html.git
synced 2025-12-08 20:29:06 +00:00
Compare commits
41 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4e761d12a5 | ||
|
|
c9b71e782d | ||
|
|
5d0351585e | ||
|
|
c30350d011 | ||
|
|
22ce5e3f25 | ||
|
|
897d87436a | ||
|
|
bde7bc9a0b | ||
|
|
b3d479224e | ||
|
|
3881565a28 | ||
|
|
d919e535ef | ||
|
|
c7dd00818b | ||
|
|
068dab8e8f | ||
|
|
6e924c49ac | ||
|
|
7fa4ab9ddb | ||
|
|
aa0af6102f | ||
|
|
bd793daae4 | ||
|
|
2c981a6038 | ||
|
|
3aedd5fcc4 | ||
|
|
c7ca7bfc75 | ||
|
|
f28f0e718a | ||
|
|
ff5e4401f7 | ||
|
|
99eed58f96 | ||
|
|
dad1267607 | ||
|
|
f0b9b3cc1e | ||
|
|
f143065dbf | ||
|
|
4501132051 | ||
|
|
006d059e1b | ||
|
|
0a743f6b2e | ||
|
|
7bad6838e7 | ||
|
|
0bd64b1308 | ||
|
|
251431a110 | ||
|
|
e8545de30a | ||
|
|
4a3b6f9d50 | ||
|
|
5b88484888 | ||
|
|
dff5f1160b | ||
|
|
abb3dd95a7 | ||
|
|
8f27f9d04f | ||
|
|
76ff6ca703 | ||
|
|
8e3a9b0390 | ||
|
|
56a6f43657 | ||
|
|
d5c6a6b540 |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,2 +1,4 @@
|
||||
node_modules
|
||||
test/test.html
|
||||
.idea
|
||||
*.log
|
||||
test/test.html
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
language: node_js
|
||||
node_js:
|
||||
- "node"
|
||||
- "4"
|
||||
notifications:
|
||||
email:
|
||||
on_success: change
|
||||
on_failure: always
|
||||
|
||||
202
README.md
202
README.md
@@ -1,89 +1,153 @@
|
||||
# inline-html
|
||||
|
||||
Embed local assets in an HTML document.
|
||||
Inline local assets referenced in an HTML document.
|
||||
|
||||
[]()
|
||||
[]()
|
||||
[]()
|
||||
[]()
|
||||
[](https://www.npmjs.com/package/inline-html)
|
||||
[](https://travis-ci.org/panosoft/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:
|
||||
|
||||
## Usage
|
||||
- Scripts - The source path is read and inlined.
|
||||
|
||||
This:
|
||||
- Linked CSS stylesheets - The stylesheet is read and inlined within a `<style>` element. Note that nested `@import`'s are also inlined.
|
||||
|
||||
var inlineHtml = require('inline-html');
|
||||
inlineHtml('path/to/file.html').then(function (html) {
|
||||
...
|
||||
});
|
||||
|
||||
Turns this:
|
||||
|
||||
<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:
|
||||
|
||||
<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:
|
||||
|
||||
- `main.less`
|
||||
|
||||
@import (inline) 'main.css';
|
||||
div { background-image: url('path/to/file'); }
|
||||
|
||||
- `main.css`
|
||||
|
||||
@font-face { src: url('path/to/file'); }
|
||||
|
||||
## API
|
||||
|
||||
### inlineHtml( filename [, options] )
|
||||
|
||||
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.
|
||||
|
||||
<link rel="stylesheet/less" href="main.less"/> -> <style>...</style>
|
||||
|
||||
- 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.
|
||||
|
||||
url('file.ext') -> url('data:...')
|
||||
- Linked LESS stylesheets - The LESS is compiled and the output is inlined within a `<style>` element. Note that `@import`'s are also inlined.
|
||||
|
||||
- Images - The source path is replaced with a datauri.
|
||||
|
||||
<img src="file.ext"/> -> <img src="data:..."/>
|
||||
- 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 a `results` object depending on the value of `options.verbose`.
|
||||
Also, `inline-html` calls can be statically evaluated and included in Browserify bundles using the [`html-inlinify`](https://github.com/panosoft/html-inlinify) transform.
|
||||
|
||||
## Usage
|
||||
|
||||
Assuming ...
|
||||
|
||||
- `main.js`
|
||||
|
||||
```js
|
||||
var a = 1;
|
||||
```
|
||||
|
||||
- `main.less`
|
||||
|
||||
```css
|
||||
div { background-image: url('path/to/file'); }
|
||||
```
|
||||
|
||||
- `main.css`
|
||||
|
||||
```css
|
||||
@font-face { src: url('path/to/file'); }
|
||||
```
|
||||
|
||||
Then ...
|
||||
|
||||
```js
|
||||
var co = require('co');
|
||||
var inline = require('inline-html');
|
||||
|
||||
co(function * () {
|
||||
var html = `
|
||||
<script src="main.js"></script>
|
||||
<link rel="stylesheet" href="main.css"/>
|
||||
<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);
|
||||
/**
|
||||
<script> var a = 1; </script>
|
||||
<style> @font-face { src: url('data:...'); } </style>
|
||||
<style> 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
|
||||
|
||||
- [`inline.html`](#html)
|
||||
- [`inline.file`](#file)
|
||||
- [`Results`](#results)
|
||||
|
||||
---
|
||||
|
||||
<a name="html"></a>
|
||||
### inline.html ( html [, options] )
|
||||
|
||||
Parses an HTML string 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` - An HTML string to inline.
|
||||
|
||||
|
||||
- `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"></a>
|
||||
### 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"></a>
|
||||
### 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.
|
||||
|
||||
126
lib/css-url.js
Normal file
126
lib/css-url.js
Normal file
@@ -0,0 +1,126 @@
|
||||
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 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(
|
||||
url.parse,
|
||||
R.pick(['protocol', 'host', 'pathname']),
|
||||
url.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);
|
||||
const replaceStyle = (style, index) => $(style).html(contents[index]);
|
||||
forEachIndexed(replaceStyle, 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);
|
||||
const replaceElementStyle = (element, index) => $(element).attr('style', styles[index]);
|
||||
forEachIndexed(replaceElementStyle, 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;
|
||||
43
lib/img.js
Normal file
43
lib/img.js
Normal file
@@ -0,0 +1,43 @@
|
||||
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);
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
const basedir = path.dirname(filename);
|
||||
var files;
|
||||
try {
|
||||
const images = $('img')
|
||||
.filter((index, element) => {
|
||||
const source = $(element).attr('src');
|
||||
return isLocalPath(source) && !isTemplateExpression(source);
|
||||
})
|
||||
.toArray();
|
||||
const getFilename = element => path.resolve(basedir, $(element).attr('src'));
|
||||
const filenames = R.map(getFilename, images);
|
||||
files = R.uniq(filenames);
|
||||
const uris = yield R.map(datauri, filenames);
|
||||
const replaceImageSource = (image, index) => $(image).attr('src', uris[index]);
|
||||
forEachIndexed(replaceImageSource, images);
|
||||
return { $, files };
|
||||
}
|
||||
catch (error) {
|
||||
error.filename = filename;
|
||||
error.files = files;
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = inlineImg;
|
||||
88
lib/index.js
88
lib/index.js
@@ -1,43 +1,71 @@
|
||||
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');
|
||||
const cheerio = require('cheerio');
|
||||
const co = require('co');
|
||||
const fs = require('mz/fs');
|
||||
const inlineCssUrl = require('./css-url');
|
||||
const inlineImg = require('./img');
|
||||
const inlineLess = require('./link-less');
|
||||
const inlineLinkCss = require('./link-css');
|
||||
const inlineScript = require('./script');
|
||||
const R = require('ramda');
|
||||
const Ru = require('@panosoft/ramda-utils');
|
||||
|
||||
var inline = co.wrap(function * (filename, options) {
|
||||
options = _.defaults(options || {}, {
|
||||
var inline = {};
|
||||
/**
|
||||
* Embed referenced local assets within and HTML file.
|
||||
*
|
||||
* @param {String} html
|
||||
* @param {Object} options
|
||||
*
|
||||
* @return {Promise}
|
||||
*/
|
||||
inline.html = co.wrap(function * (html, options) {
|
||||
options = Ru.defaults({
|
||||
filename: '.',
|
||||
less: {},
|
||||
verbose: false
|
||||
});
|
||||
var html = yield fs.readFile(filename, 'utf8');
|
||||
}, options || {});
|
||||
|
||||
const filename = options.filename;
|
||||
var files = [filename];
|
||||
try {
|
||||
var $ = cheerio.load(html, {decodeEntities: false});
|
||||
|
||||
// Inline links
|
||||
var lessResult = yield inlineLinkLess(html, filename, options.less);
|
||||
html = lessResult.html;
|
||||
files.push(lessResult.files);
|
||||
var result;
|
||||
result = yield inlineLess($, filename, options);
|
||||
$ = result.$;
|
||||
files = R.concat(files, result.files);
|
||||
|
||||
// TODO inline links: css
|
||||
result = yield inlineLinkCss($, filename, options);
|
||||
$ = result.$;
|
||||
files = R.concat(files, result.files);
|
||||
|
||||
// TODO inline scripts
|
||||
// browserify js? => scriptify
|
||||
result = inlineCssUrl($, filename, options);
|
||||
$ = result.$;
|
||||
files = R.concat(files, result.files);
|
||||
|
||||
// Inline paths -> datauris
|
||||
var styleResult = inlineStyle(html, filename); // Inline styles
|
||||
html = styleResult.html;
|
||||
files.push(styleResult.files);
|
||||
result = yield inlineImg($, filename, options);
|
||||
$ = result.$;
|
||||
files = R.concat(files, result.files);
|
||||
|
||||
var imgResult = inlineImg(html, filename); // Inline images
|
||||
html = imgResult.html;
|
||||
files.push(imgResult.files);
|
||||
result = yield inlineScript($, filename, options);
|
||||
$ = result.$;
|
||||
files = R.concat(files, result.files);
|
||||
|
||||
var result = {
|
||||
html: html,
|
||||
files: _.unique(_.flatten(files, true))
|
||||
};
|
||||
return (options.verbose ? result : result.html);
|
||||
html = $.xml();
|
||||
files = R.uniq(files);
|
||||
return (options.verbose ? { html, files } : html);
|
||||
}
|
||||
catch (error) {
|
||||
if (!error.filename) error.filename = filename;
|
||||
error.files = R.uniq(R.concat(files, error.files || []));
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
inline.file = co.wrap(function * (filename, options) {
|
||||
const html = yield fs.readFile(filename, 'utf8');
|
||||
options = R.merge(options || {}, {filename});
|
||||
return yield inline.html(html, options);
|
||||
});
|
||||
|
||||
module.exports = inline;
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
var datauri = require('datauri');
|
||||
var isLocalPath = require('is-local-path');
|
||||
var path = require('path');
|
||||
var postcss = require('postcss');
|
||||
var url = require('postcss-url');
|
||||
|
||||
var inline = 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);
|
||||
}
|
||||
return url;
|
||||
}
|
||||
}))
|
||||
.process(css);
|
||||
return {
|
||||
css: result.css,
|
||||
files: files
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = inline;
|
||||
@@ -1,26 +0,0 @@
|
||||
var cheerio = require('cheerio');
|
||||
var datauri = require('datauri');
|
||||
var isLocalPath = require('is-local-path');
|
||||
var path = require('path');
|
||||
|
||||
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);
|
||||
});
|
||||
return {
|
||||
html: $.html(),
|
||||
files: files
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = inline;
|
||||
@@ -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;
|
||||
@@ -1,44 +0,0 @@
|
||||
var cheerio = require('cheerio');
|
||||
var inlineUrl = require('./inline-css-url');
|
||||
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 inline = 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);
|
||||
});
|
||||
|
||||
return {
|
||||
html: $.html(),
|
||||
files: files
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = inline;
|
||||
12
lib/is-template-expression.js
Normal file
12
lib/is-template-expression.js
Normal file
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* 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}
|
||||
*/
|
||||
const isTemplateExpression = path => /^{{.*}}$/.test(path);
|
||||
|
||||
module.exports = isTemplateExpression;
|
||||
70
lib/link-css.js
Normal file
70
lib/link-css.js
Normal file
@@ -0,0 +1,70 @@
|
||||
const co = require('co');
|
||||
const fs = require('mz/fs');
|
||||
const isLocalPath = require('is-local-path');
|
||||
const isTemplateExpression = require('./is-template-expression');
|
||||
const path = require('path');
|
||||
const postcss = require('postcss');
|
||||
const postcssImport = require('postcss-import');
|
||||
const postcssUrl = require('postcss-url');
|
||||
const R = require('ramda');
|
||||
|
||||
const forEachIndexed = R.addIndex(R.forEach);
|
||||
const render = R.curryN(2, co.wrap(function * (filename, href) {
|
||||
var imports;
|
||||
const processor = postcss()
|
||||
.use(postcssImport({ async: true, onImport: files => imports = files }))
|
||||
.use(postcssUrl({ url: 'rebase' }));
|
||||
try {
|
||||
const css = yield fs.readFile(href, 'utf8');
|
||||
const result = yield processor.process(css, { from: href, to: filename });
|
||||
const output = {css: result.css, imports};
|
||||
return output;
|
||||
}
|
||||
catch (error) {
|
||||
// process uses error.file = href
|
||||
// import uses error.fileName
|
||||
// readFile uses nothing => use filename
|
||||
error.filename = error.file || error.fileName || filename;
|
||||
throw error;
|
||||
}
|
||||
}));
|
||||
/**
|
||||
* Inline liked CSS stylesheets by replacing link elements with
|
||||
* style elements that contain the css file contents.
|
||||
* @param {Object} $
|
||||
* Parsed HTML source to inline
|
||||
* @param {String} [filename='.']
|
||||
* Filename of the HTML document contained within $
|
||||
* @return {Promise}
|
||||
*/
|
||||
const inlineLinkCss = co.wrap(function * ($, filename) {
|
||||
// TODO consider: explicitly default filename = '.'?
|
||||
// path.dirname(null || undefined) -> '.'
|
||||
const basedir = path.dirname(filename);
|
||||
var files = [];
|
||||
try {
|
||||
const links = $('link[rel="stylesheet"]')
|
||||
.filter((index, link) => {
|
||||
const href = $(link).attr('href');
|
||||
return isLocalPath(href) && !isTemplateExpression(href);
|
||||
})
|
||||
.toArray();
|
||||
const getHref = element => path.resolve(basedir, $(element).attr('href'));
|
||||
const hrefs = R.map(getHref, links);
|
||||
files = R.concat(files, hrefs);
|
||||
const outputs = yield R.map(render(filename), hrefs);
|
||||
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);
|
||||
const replaceLink = (link, index) => $(link).replaceWith(styles[index]);
|
||||
forEachIndexed(replaceLink, links);
|
||||
files = R.uniq(files);
|
||||
return { $, files };
|
||||
}
|
||||
catch (error) {
|
||||
if (!error.filename) error.filename = filename;
|
||||
error.files = R.uniq(files);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
module.exports = inlineLinkCss;
|
||||
65
lib/link-less.js
Normal file
65
lib/link-less.js
Normal file
@@ -0,0 +1,65 @@
|
||||
const co = require('co');
|
||||
const fs = require('mz/fs');
|
||||
const isLocalPath = require('is-local-path');
|
||||
const isTemplateExpression = require('./is-template-expression');
|
||||
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 render = R.curryN(3, co.wrap(function * (options, basedir, href) {
|
||||
const relativePath = path.relative(basedir, path.dirname(href));
|
||||
const rootpath = relativePath ? `${relativePath}/` : false;
|
||||
options = R.merge(options || {}, {
|
||||
async: true,
|
||||
relativeUrls: true,
|
||||
filename: href,
|
||||
rootpath
|
||||
});
|
||||
const contents = yield fs.readFile(href, '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 = options.less;
|
||||
const basedir = path.dirname(filename);
|
||||
var files = [];
|
||||
try {
|
||||
const links = $('link[rel="stylesheet/less"]')
|
||||
.filter((index, link) => {
|
||||
const href = $(link).attr('href');
|
||||
return isLocalPath(href) && !isTemplateExpression(href);
|
||||
})
|
||||
.toArray();
|
||||
const getHref = element => path.resolve(basedir, $(element).attr('href'));
|
||||
const hrefs = R.map(getHref, links);
|
||||
files = R.concat(files, hrefs);
|
||||
const outputs = yield R.map(render(options, basedir), hrefs);
|
||||
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);
|
||||
const replaceLink = (link, index) => $(link).replaceWith(styles[index]);
|
||||
forEachIndexed(replaceLink, 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;
|
||||
36
lib/script.js
Normal file
36
lib/script.js
Normal file
@@ -0,0 +1,36 @@
|
||||
const co = require('co');
|
||||
const fs = require('mz/fs');
|
||||
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 inlineScript = co.wrap(function * ($, filename) {
|
||||
var basedir = path.dirname(filename);
|
||||
var files;
|
||||
try {
|
||||
const scripts = $('script')
|
||||
.filter((index, element) => {
|
||||
const source = $(element).attr('src');
|
||||
return isLocalPath(source) && !isTemplateExpression(source);
|
||||
})
|
||||
.toArray();
|
||||
const getFilename = element => path.resolve(basedir, $(element).attr('src'));
|
||||
const filenames = R.map(getFilename, scripts);
|
||||
files = R.uniq(filenames);
|
||||
const readFile = filename => fs.readFile(filename, 'utf8');
|
||||
const contents = yield R.map(readFile, filenames);
|
||||
const replaceScript = (script, index) => $(script).attr('src', null).html(contents[index]);
|
||||
forEachIndexed(replaceScript, scripts);
|
||||
return { $, files };
|
||||
}
|
||||
catch (error) {
|
||||
error.filename = filename;
|
||||
error.files = files;
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = inlineScript;
|
||||
27
package.json
27
package.json
@@ -1,24 +1,35 @@
|
||||
{
|
||||
"name": "inline-html",
|
||||
"version": "0.1.2",
|
||||
"description": "Embed local assets in an HTML document.",
|
||||
"version": "0.2.4",
|
||||
"description": "Inline local assets referenced in an HTML document.",
|
||||
"repository": "panosoft/inline-html",
|
||||
"engines": {
|
||||
"node": ">=4.0.0"
|
||||
},
|
||||
"main": "lib/index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
"test": "mocha",
|
||||
"test:debug": "mocha --debug-brk"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@panosoft/ramda-utils": "^0.2.0",
|
||||
"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.12",
|
||||
"postcss-import": "^7.1.3",
|
||||
"postcss-url": "^5.0.0",
|
||||
"ramda": "^0.18.0",
|
||||
"string": "^3.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"chai": "^3.2.0",
|
||||
"chai-as-promised": "^5.1.0",
|
||||
"mocha": "^2.2.5"
|
||||
}
|
||||
}
|
||||
|
||||
8
test/fixtures/assets/imported.css
vendored
8
test/fixtures/assets/imported.css
vendored
@@ -1,8 +0,0 @@
|
||||
body {
|
||||
border: solid 4px green;
|
||||
}
|
||||
#import {
|
||||
background-image: url('./person.png');
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
BIN
test/fixtures/assets/person.png
vendored
BIN
test/fixtures/assets/person.png
vendored
Binary file not shown.
|
Before Width: | Height: | Size: 5.7 KiB |
2
test/fixtures/basic.css
vendored
Normal file
2
test/fixtures/basic.css
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/* basic.css */
|
||||
div { color: blue; }
|
||||
2
test/fixtures/basic.less
vendored
Normal file
2
test/fixtures/basic.less
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/* basic.less */
|
||||
div { color: blue; }
|
||||
1
test/fixtures/css-url.html
vendored
Normal file
1
test/fixtures/css-url.html
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<style>div { background-image: url("file.txt"); }</style>
|
||||
1
test/fixtures/file space.txt
vendored
Normal file
1
test/fixtures/file space.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
Test.
|
||||
1
test/fixtures/file.txt
vendored
Normal file
1
test/fixtures/file.txt
vendored
Normal file
@@ -0,0 +1 @@
|
||||
Test.
|
||||
1
test/fixtures/img.html
vendored
Normal file
1
test/fixtures/img.html
vendored
Normal file
@@ -0,0 +1 @@
|
||||
<img src="file.txt"/>
|
||||
1
test/fixtures/import.css
vendored
Normal file
1
test/fixtures/import.css
vendored
Normal file
@@ -0,0 +1 @@
|
||||
@import 'basic.css';
|
||||
2
test/fixtures/import.less
vendored
Normal file
2
test/fixtures/import.less
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
@import 'basic.less';
|
||||
@import (inline) 'basic.css';
|
||||
25
test/fixtures/index.html
vendored
25
test/fixtures/index.html
vendored
@@ -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/invalid-import.css
vendored
Normal file
1
test/fixtures/invalid-import.css
vendored
Normal file
@@ -0,0 +1 @@
|
||||
@import 'missing.css';
|
||||
1
test/fixtures/invalid-import.less
vendored
Normal file
1
test/fixtures/invalid-import.less
vendored
Normal file
@@ -0,0 +1 @@
|
||||
@import 'missing.less';
|
||||
1
test/fixtures/invalid-syntax.css
vendored
Normal file
1
test/fixtures/invalid-syntax.css
vendored
Normal file
@@ -0,0 +1 @@
|
||||
div {
|
||||
1
test/fixtures/invalid-syntax.less
vendored
Normal file
1
test/fixtures/invalid-syntax.less
vendored
Normal file
@@ -0,0 +1 @@
|
||||
div {
|
||||
1
test/fixtures/invalid-url.css
vendored
Normal file
1
test/fixtures/invalid-url.css
vendored
Normal file
@@ -0,0 +1 @@
|
||||
div { background-image: url('missing.png'); }
|
||||
1
test/fixtures/invalid-url.less
vendored
Normal file
1
test/fixtures/invalid-url.less
vendored
Normal file
@@ -0,0 +1 @@
|
||||
div { background-image: url('missing.png'); }
|
||||
7
test/fixtures/main.less
vendored
7
test/fixtures/main.less
vendored
@@ -1,7 +0,0 @@
|
||||
@import (less) './assets/imported.css';
|
||||
|
||||
#link {
|
||||
background-image: url('assets/person.png');
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
1
test/fixtures/nested-import.css
vendored
Normal file
1
test/fixtures/nested-import.css
vendored
Normal file
@@ -0,0 +1 @@
|
||||
@import 'import.css';
|
||||
1
test/fixtures/nested-import.less
vendored
Normal file
1
test/fixtures/nested-import.less
vendored
Normal file
@@ -0,0 +1 @@
|
||||
@import 'import.less';
|
||||
1
test/fixtures/url.css
vendored
Normal file
1
test/fixtures/url.css
vendored
Normal file
@@ -0,0 +1 @@
|
||||
div { background-image: url('file.txt'); }
|
||||
1
test/fixtures/url.less
vendored
Normal file
1
test/fixtures/url.less
vendored
Normal file
@@ -0,0 +1 @@
|
||||
div { background-image: url('file.txt'); }
|
||||
569
test/index.js
569
test/index.js
@@ -1,13 +1,560 @@
|
||||
var inline = require('../lib');
|
||||
var path = require('path');
|
||||
var fs = require('fs');
|
||||
const co = require('co');
|
||||
const datauri = require('datauri');
|
||||
const expect = require('chai')
|
||||
.use(require('chai-as-promised'))
|
||||
.expect;
|
||||
const fs = require('fs');
|
||||
const inline = require('../lib');
|
||||
const path = require('path');
|
||||
|
||||
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', () => {
|
||||
const filename = path.resolve(__dirname, './fixtures/file.txt');
|
||||
const content = fs.readFileSync(filename, 'utf8');
|
||||
return expect(inline.file(filename)).to.eventually.equal(content);
|
||||
});
|
||||
|
||||
it('options.filename: always reset to current filename and used for css url path resolution', () => {
|
||||
const filename = path.resolve(__dirname, 'fixtures/css-url.html'); // file contains url path relative to itself
|
||||
const options = { filename: __filename }; // sets filename relative to this test file
|
||||
return expect(inline.file(filename, options)).to.eventually.match(/"data:.*,.*"/);
|
||||
});
|
||||
it('options.filename: always reset to current filename and used for img src path resolution', () => {
|
||||
const filename = path.resolve(__dirname, 'fixtures/img.html'); // file contains src relative to itself
|
||||
const 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', () => {
|
||||
const html = 'HTML';
|
||||
return expect(inline.html(html)).to.eventually.equal(html);
|
||||
});
|
||||
it('preserve self closing tags', () => {
|
||||
const html = '<br/>';
|
||||
return expect(inline.html(html)).to.eventually.equal(html);
|
||||
});
|
||||
it('preserve partials', () => {
|
||||
const html = '{{> partial}}';
|
||||
return expect(inline.html(html)).to.eventually.equal(html);
|
||||
});
|
||||
it('preserve helpers', () => {
|
||||
const html = '{{helper}}';
|
||||
return expect(inline.html(html)).to.eventually.equal(html);
|
||||
});
|
||||
|
||||
it('options.filename: default to cwd for css url path resolution', () => {
|
||||
const url = 'test/fixtures/file.txt'; // Note: this is relative to cwd
|
||||
const uri = datauri(url);
|
||||
|
||||
const html = (path) => `<style>div { background-image: url('${path}'); }</style>`;
|
||||
return expect(inline.html(html(url))).to.eventually.equal(html(uri));
|
||||
});
|
||||
it('options.filename: set basepath for css url path resolution', () => {
|
||||
const filename = path.resolve(__dirname, 'fixtures/fake.html');
|
||||
const dirname = path.dirname(filename);
|
||||
|
||||
const url = 'file.txt'; // Note: path relative to filename's dirname
|
||||
const uri = datauri(path.resolve(dirname, url));
|
||||
|
||||
const html = (path) => `<style>div { background-image: url('${path}'); }</style>`;
|
||||
const options = { filename: filename };
|
||||
return expect(inline.html(html(url), options)).to.eventually.equal(html(uri));
|
||||
});
|
||||
|
||||
it('options.filename: default to cwd for img src path resolution', () => {
|
||||
const url = 'test/fixtures/file.txt'; // Note: path relative to cwd
|
||||
const uri = datauri(url);
|
||||
|
||||
const html = (path) => `<img src="${path}"/>`;
|
||||
return expect(inline.html(html(url))).to.eventually.equal(html(uri));
|
||||
});
|
||||
it('options.filename: set basepath for img src path resolution', () => {
|
||||
const filename = path.resolve(__dirname, 'fixtures/fake.html');
|
||||
const dirname = path.dirname(filename);
|
||||
|
||||
const url = 'file.txt'; // Note: path relative to filename's dirname
|
||||
const uri = datauri(path.resolve(dirname, url));
|
||||
|
||||
const html = (path) => `<img src="${path}"/>`;
|
||||
const options = { filename: filename };
|
||||
return expect(inline.html(html(url), options)).to.eventually.equal(html(uri));
|
||||
});
|
||||
|
||||
it('options.filename: included in results.files for img src if options.verbose true', () => {
|
||||
const filename = path.resolve(__dirname, 'fixtures/file.txt');
|
||||
const html = `<img src="${filename}"/>`;
|
||||
const 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', () => {
|
||||
const filename = path.resolve(__dirname, 'fixtures/file.txt');
|
||||
const html = `<style>div { background-image: url('${filename}'); }</style>`;
|
||||
const options = { verbose: true };
|
||||
return expect(inline.html(html, options)).to.eventually.have.property('files')
|
||||
.that.is.an('array')
|
||||
.that.contains(filename);
|
||||
});
|
||||
|
||||
|
||||
it('options.verbose: return results object if true', () => {
|
||||
const filename = path.resolve(__dirname, 'fixtures/file.txt');
|
||||
const html = `<img src="${filename}"/>`;
|
||||
const 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', () => {
|
||||
const filename = path.resolve(__dirname, 'fixtures/file.txt');
|
||||
const html = `<img src="${filename}"/>`;
|
||||
const options = { verbose: false };
|
||||
return expect(inline.html(html, options)).to.eventually.be.a('string');
|
||||
});
|
||||
it('options.verbose: default false', () => {
|
||||
const filename = path.resolve(__dirname, 'fixtures/file.txt');
|
||||
const html = `<img src="${filename}"/>`;
|
||||
return expect(inline.html(html)).to.eventually.be.a('string');
|
||||
});
|
||||
|
||||
|
||||
describe('css-url', () => {
|
||||
it('inline local url in style element', () => {
|
||||
const filename = path.resolve(__dirname, 'fixtures/file.txt');
|
||||
const html = `<style>div {background-image: url("${filename}");}</style>`;
|
||||
return expect(inline.html(html)).to.eventually.match(/data:.*,.*/);
|
||||
});
|
||||
it('inline local url in style attribute', () => {
|
||||
const filename = path.resolve(__dirname, 'fixtures/file.txt');
|
||||
const html = `<div style="background-image: url('${filename}');"></div>`;
|
||||
return expect(inline.html(html)).to.eventually.match(/data:.*,.*/);
|
||||
});
|
||||
it('ignore remote url', () => {
|
||||
const 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 template expression url', () => {
|
||||
const html = `<style> div { background-image: url({{path}}); }</style>`;
|
||||
return expect(inline.html(html)).to.eventually.equal(html);
|
||||
});
|
||||
it('ignore url query strings and hashes', () => {
|
||||
const filename = path.resolve(__dirname, 'fixtures/file.txt');
|
||||
const url = `${filename}?query=string#hash`;
|
||||
const uri = datauri(filename);
|
||||
const html = (source) => `<style> div { background-image: url('${source}'); }</style>`;
|
||||
return expect(inline.html(html(url))).to.eventually.equal(html(uri));
|
||||
});
|
||||
it('handle url with spaces', () => {
|
||||
const filename = path.resolve(__dirname, 'fixtures/file space.txt');
|
||||
const uri = datauri(filename);
|
||||
const html = (source) => `<style> div { background-image: url('${source}'); }</style>`;
|
||||
return expect(inline.html(html(filename))).to.eventually.equal(html(uri));
|
||||
});
|
||||
it('throw when syntax invalid in style element', () => co(function * () {
|
||||
const filename = path.resolve(__dirname, 'index.html');
|
||||
const 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 when syntax invalid in style attribute', () => co(function * () {
|
||||
const filename = path.resolve(__dirname, 'index.html');
|
||||
const 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 when url invalid in style element', () => co(function * () {
|
||||
const filename = path.resolve(__dirname, 'index.html');
|
||||
const url = 'missing.png';
|
||||
const resolvedUrl = path.resolve(path.dirname(filename), url);
|
||||
const 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 when url invalid in style attribute ', () => co(function * () {
|
||||
const filename = path.resolve(__dirname, 'index.html');
|
||||
const url = 'missing.png';
|
||||
const resolvedUrl = path.resolve(path.dirname(filename), url);
|
||||
const 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('include all urls in error.files up until and including invalid url in style element', () => co(function * () {
|
||||
const filename = path.resolve(__dirname, 'index.html');
|
||||
const validUrl = 'fixtures/file.txt';
|
||||
const invalidUrl = 'missing.png';
|
||||
const resolvedInvalidUrl = path.resolve(path.dirname(filename), invalidUrl);
|
||||
const resolvedValidUrl = path.resolve(path.dirname(filename), validUrl);
|
||||
const html = `
|
||||
<style>div {background-image: url('${validUrl}');}</style>
|
||||
<style>div {background-image: url('${invalidUrl}');}</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(resolvedValidUrl);
|
||||
expect(error).to.have.property('files').that.contains(resolvedInvalidUrl);
|
||||
}
|
||||
}));
|
||||
it('include all urls in error.files up until and including invalid url in style attribute', () => co(function * () {
|
||||
const filename = path.resolve(__dirname, 'index.html');
|
||||
const validUrl = 'fixtures/file.txt';
|
||||
const invalidUrl = 'missing.png';
|
||||
const resolvedInvalidUrl = path.resolve(path.dirname(filename), invalidUrl);
|
||||
const resolvedValidUrl = path.resolve(path.dirname(filename), validUrl);
|
||||
const html = `
|
||||
<div style="background-image: url('${validUrl}')"></div>
|
||||
<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);
|
||||
}
|
||||
}));
|
||||
it('include all urls in error.files up until and including invalid url when style element valid and style attribute invalid', () => co(function * () {
|
||||
const filename = path.resolve(__dirname, 'index.html');
|
||||
const validUrl = 'fixtures/file.txt';
|
||||
const invalidUrl = 'missing.png';
|
||||
const resolvedInvalidUrl = path.resolve(path.dirname(filename), invalidUrl);
|
||||
const resolvedValidUrl = path.resolve(path.dirname(filename), validUrl);
|
||||
const 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);
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
||||
describe('img', () => {
|
||||
it('inline local source', () => {
|
||||
const filename = path.resolve(__dirname, 'fixtures/file.txt');
|
||||
const html = `<img src="${filename}"/>`;
|
||||
return expect(inline.html(html)).to.eventually.match(/data:.*,.*/);
|
||||
});
|
||||
it('ignore remote source', () => {
|
||||
const html = `<img src="http://test.com/file.txt?query=string#hash"/>`;
|
||||
return expect(inline.html(html)).to.eventually.equal(html);
|
||||
});
|
||||
it('ignore template expression source', () => {
|
||||
const html = `<img src="{{path}}"/>`;
|
||||
return expect(inline.html(html)).to.eventually.equal(html);
|
||||
});
|
||||
it('throw when src invalid', () => co(function * () {
|
||||
const filename = path.resolve(__dirname, 'index.html');
|
||||
const source = 'missing.png';
|
||||
const html = `<img src="${source}" >`;
|
||||
const 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);
|
||||
}
|
||||
}));
|
||||
it('include all sources in error.files up until and including invalid source', () => co(function * () {
|
||||
const filename = path.resolve(__dirname, 'index.html');
|
||||
const valid = 'fixtures/file.txt';
|
||||
const invalid = 'missing.png';
|
||||
const resolvedInvalid = path.resolve(path.dirname(filename), invalid);
|
||||
const resolvedValid = path.resolve(path.dirname(filename), valid);
|
||||
const 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);
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
||||
describe('link-css', () => {
|
||||
const filename = path.resolve(__dirname, 'index.html');
|
||||
const link = (href) => `<link rel="stylesheet" href="${href}"/>`;
|
||||
it('inline local href', () => co(function * () {
|
||||
const href = path.resolve(__dirname, 'fixtures/basic.css');
|
||||
const html = link(href);
|
||||
const result = yield inline.html(html);
|
||||
expect(result).to.match(/<style>[^]*basic.css[^]*<\/style>/);
|
||||
}));
|
||||
it('ignore remote href', () => co(function * () {
|
||||
const html = link('http://test.com/main.less');
|
||||
const result = yield inline.html(html);
|
||||
expect(result).to.equal(html);
|
||||
}));
|
||||
it('ignore template expression href', () => co(function * () {
|
||||
const html = link('{{href}}');
|
||||
const result = yield inline.html(html);
|
||||
expect(result).to.equal(html);
|
||||
}));
|
||||
it('inline local nested imports', () => co(function * () {
|
||||
const href = path.resolve(__dirname, 'fixtures/nested-import.css');
|
||||
const html = link(href);
|
||||
const result = yield inline.html(html);
|
||||
return expect(result).to.match(/<style>[^]*basic.css[^]*<\/style>/)
|
||||
.and.not.match(/@import/);
|
||||
}));
|
||||
it('rebase urls relative to html filename', () => co(function * () {
|
||||
const href = 'fixtures/url.css';
|
||||
const html = link(href);
|
||||
const result = yield inline.html(html, { filename });
|
||||
expect(result).to.match(/<style>[^]*url\('data:.*,.*'\)[^]*<\/style>/);
|
||||
}));
|
||||
it('throw error when href invalid', () => co(function * () {
|
||||
const invalid = 'fixtures/missing.css';
|
||||
const invalidResolved = path.resolve(path.dirname(filename), invalid);
|
||||
const html = link(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(invalidResolved);
|
||||
}
|
||||
}));
|
||||
it('throw error when import path invalid', () => co(function * () {
|
||||
const invalid = 'fixtures/invalid-import.css';
|
||||
const invalidResolved = path.resolve(path.dirname(filename), invalid);
|
||||
const html = link(invalid);
|
||||
try {
|
||||
yield inline.html(html, { filename });
|
||||
throw new Error('No error thrown');
|
||||
}
|
||||
catch (error) {
|
||||
expect(error).to.have.property('filename').that.equals(invalidResolved);
|
||||
expect(error).to.have.property('files').that.contains(invalidResolved);
|
||||
}
|
||||
}));
|
||||
it('throw error when css syntax invalid', () => co(function * () {
|
||||
const invalid = 'fixtures/invalid-syntax.css';
|
||||
const invalidResolved = path.resolve(path.dirname(filename), invalid);
|
||||
const html = link(invalid);
|
||||
try {
|
||||
yield inline.html(html, { filename });
|
||||
throw new Error('No error thrown');
|
||||
}
|
||||
catch (error) {
|
||||
expect(error).to.have.property('filename').that.equals(invalidResolved);
|
||||
expect(error).to.have.property('files').that.contains(invalidResolved);
|
||||
}
|
||||
}));
|
||||
it('include all local hrefs in error.files when error encountered', () => co(function * () {
|
||||
const valid = 'fixtures/basic.css';
|
||||
const invalid = 'fixtures/missing.css';
|
||||
const validResolved = path.resolve(path.dirname(filename), valid);
|
||||
const invalidResolved = path.resolve(path.dirname(filename), invalid);
|
||||
const html = `${link(invalid)}${link(valid)}`;
|
||||
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(validResolved);
|
||||
expect(error).to.have.property('files').that.contains(invalidResolved);
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
||||
describe('link-less', () => {
|
||||
it('inline local href', () => {
|
||||
const filename = path.resolve(__dirname, 'fixtures/basic.less');
|
||||
const html = `<link rel="stylesheet/less" href="${filename}"/>`;
|
||||
return expect(inline.html(html)).to.eventually.match(/<style>[^]*basic.less[^]*<\/style>/);
|
||||
});
|
||||
it('ignore remote href', () => {
|
||||
const html = `<link rel="stylesheet/less" href="http://test.com/main.less"/>`;
|
||||
return expect(inline.html(html)).to.eventually.equal(html);
|
||||
});
|
||||
it('ignore template expression href', () => {
|
||||
const html = `<link rel="stylesheet/less" href="{{href}}"/>`;
|
||||
return expect(inline.html(html)).to.eventually.equal(html);
|
||||
});
|
||||
it('inline local nested imports', () => {
|
||||
const filename = path.resolve(__dirname, 'fixtures/nested-import.less');
|
||||
const html = `<link rel="stylesheet/less" href="${filename}"/>`;
|
||||
return expect(inline.html(html)).to.eventually.match(/<style>[^]*basic.less[^]*basic.css[^]*<\/style>/)
|
||||
.and.not.match(/@import/);
|
||||
});
|
||||
it('rebase urls relative to html filename', () => {
|
||||
const filename = path.resolve(__dirname, 'index.html');
|
||||
const href = 'fixtures/url.less';
|
||||
const html = `<link rel="stylesheet/less" href="${href}"/>`;
|
||||
return expect(inline.html(html, { filename })).to.eventually.match(/<style>[^]*url\('data:.*,.*'\)[^]*<\/style>/);
|
||||
});
|
||||
it('throw error when link href invalid', () => co(function * () {
|
||||
const filename = path.resolve(__dirname, 'index.html');
|
||||
const href = 'missing.less';
|
||||
const resolvedHref = path.resolve(path.dirname(filename), href);
|
||||
const 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', () => co(function * () {
|
||||
const filename = path.resolve(__dirname, 'fixtures/index.html');
|
||||
const lessBasename = 'invalid-import.less';
|
||||
const lessFilename = path.resolve(path.dirname(filename), lessBasename);
|
||||
const 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', () => co(function * () {
|
||||
const filename = path.resolve(__dirname, 'fixtures/index.html');
|
||||
const lessBasename = 'invalid-syntax.less';
|
||||
const lessFilename = path.resolve(path.dirname(filename), lessBasename);
|
||||
const 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('include all local hrefs in error.files when error encountered', () => co(function * () {
|
||||
const filename = path.resolve(__dirname, 'index.html');
|
||||
const valid = 'fixtures/basic.css';
|
||||
const invalid = 'fixtures/missing.css';
|
||||
const validResolved = path.resolve(path.dirname(filename), valid);
|
||||
const invalidResolved = path.resolve(path.dirname(filename), invalid);
|
||||
const html = `
|
||||
<link rel="stylesheet/less" href="${invalid}">
|
||||
<link rel="stylesheet/less" href="${valid}">
|
||||
`;
|
||||
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(validResolved);
|
||||
expect(error).to.have.property('files').that.contains(invalidResolved);
|
||||
}
|
||||
}));
|
||||
});
|
||||
|
||||
describe('script', () => {
|
||||
it('inline local src', () => {
|
||||
const source = path.resolve(__dirname, 'fixtures/file.txt');
|
||||
const html = `<script src="${source}"></script>`;
|
||||
const contents = fs.readFileSync(source, 'utf8');
|
||||
return expect(inline.html(html)).to.eventually.equal(`<script>${contents}</script>`);
|
||||
});
|
||||
it('ignore remote src', () => {
|
||||
const html = `<script src="http://test.com/file.txt?query=string#hash"/>`;
|
||||
return expect(inline.html(html)).to.eventually.equal(html);
|
||||
});
|
||||
it('ignore template expression src', () => {
|
||||
const html = `<script src="{{path}}"/>`;
|
||||
return expect(inline.html(html)).to.eventually.equal(html);
|
||||
});
|
||||
it('throw when source invalid', () => co(function * () {
|
||||
const filename = path.resolve(__dirname, 'index.html');
|
||||
const source = 'missing.js';
|
||||
const html = `<script src="${source}"></script>`;
|
||||
const 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);
|
||||
}
|
||||
})
|
||||
);
|
||||
it('include all sources in error.files up until and including invalid source', () => {
|
||||
return co(function * () {
|
||||
const filename = path.resolve(__dirname, 'index.html');
|
||||
const valid = 'fixtures/file.txt';
|
||||
const invalid = 'missing.js';
|
||||
const resolvedInvalid = path.resolve(path.dirname(filename), invalid);
|
||||
const resolvedValid = path.resolve(path.dirname(filename), valid);
|
||||
const html = `
|
||||
<script src="${valid}"></script>
|
||||
<script src="${invalid}"></script>
|
||||
`;
|
||||
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);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
@@ -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);
|
||||
Reference in New Issue
Block a user