From c30350d011aea6903e44ffbad2aea40f07b9eed5 Mon Sep 17 00:00:00 2001 From: Alexandre Gigliotti Date: Fri, 4 Dec 2015 15:06:06 -0800 Subject: [PATCH] Add support for inlining CSS stylesheets. Add tests. Update docs. --- README.md | 18 ++--- lib/index.js | 7 +- lib/link-css.js | 70 ++++++++++++++++++ package.json | 5 +- test/fixtures/basic.css | 1 + test/fixtures/basic.less | 3 +- test/fixtures/import.css | 1 + test/fixtures/invalid-import.css | 1 + test/fixtures/invalid-import.less | 2 +- test/fixtures/invalid-syntax.css | 1 + test/fixtures/invalid-url.css | 1 + test/fixtures/invalid-url.less | 4 +- test/fixtures/nested-import.css | 1 + test/fixtures/url.css | 1 + test/fixtures/url.less | 1 + test/index.js | 117 +++++++++++++++++++++++++++--- 16 files changed, 204 insertions(+), 30 deletions(-) create mode 100644 lib/link-css.js create mode 100644 test/fixtures/import.css create mode 100644 test/fixtures/invalid-import.css create mode 100644 test/fixtures/invalid-syntax.css create mode 100644 test/fixtures/invalid-url.css create mode 100644 test/fixtures/nested-import.css create mode 100644 test/fixtures/url.css create mode 100644 test/fixtures/url.less diff --git a/README.md b/README.md index 5dbb843..87a55f1 100644 --- a/README.md +++ b/README.md @@ -11,9 +11,11 @@ The following HTML elements and CSS data types are inlined: - Scripts - The source path is read and inlined. -- Images - The source path is replaced with a datauri. +- Linked CSS stylesheets - The stylesheet is read and inlined within a `
@@ -59,13 +61,9 @@ co(function * () { html = yield inline.html(html); console.log(html); /** - - + + +
diff --git a/lib/index.js b/lib/index.js index ad6282e..25ba070 100644 --- a/lib/index.js +++ b/lib/index.js @@ -4,6 +4,7 @@ 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'); @@ -19,7 +20,7 @@ var inline = {}; */ inline.html = co.wrap(function * (html, options) { options = Ru.defaults({ - filename: null, + filename: '.', less: {}, verbose: false }, options || {}); @@ -34,6 +35,10 @@ inline.html = co.wrap(function * (html, options) { $ = result.$; files = R.concat(files, result.files); + result = yield inlineLinkCss($, filename, options); + $ = result.$; + files = R.concat(files, result.files); + result = inlineCssUrl($, filename, options); $ = result.$; files = R.concat(files, result.files); diff --git a/lib/link-css.js b/lib/link-css.js new file mode 100644 index 0000000..f33c5cb --- /dev/null +++ b/lib/link-css.js @@ -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 => $('