Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vinyl #120

Merged
merged 31 commits into from
Oct 17, 2016
Merged

Vinyl #120

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
cdfe67c
add testcase for #119
bezoerb Jan 27, 2016
b43b811
use vinyl
bezoerb Jan 27, 2016
33f7709
Keep relative images relative
bezoerb Jan 27, 2016
67e06c4
consider absolute stylesheet refs
bezoerb Jan 27, 2016
81ab07e
Guess source path for piped html
bezoerb Jan 28, 2016
6949df1
tests for auto pathPrefix
bezoerb Jan 28, 2016
8029e73
travis tweaks
bezoerb Jan 28, 2016
32490d9
Merge branch 'master' into vinyl
bezoerb Jan 28, 2016
121f0b8
eslint fixes
bezoerb Jan 28, 2016
b663cb8
minor changes
bezoerb Jan 28, 2016
9319361
doc blocks
bezoerb Jan 28, 2016
846c233
compatibility to vinyl < v0.5.3
bezoerb Jan 29, 2016
883564e
Check vinyl
bezoerb Feb 1, 2016
24f1870
Check vinyl
bezoerb Feb 1, 2016
307cd1f
remove unused setMaxListeners(0)
bezoerb Feb 4, 2016
a87976b
guess path to html source
bezoerb Feb 7, 2016
ca32bda
fixed jsdoc
bezoerb Feb 7, 2016
dd0a5a6
add 'folder' option
bezoerb Feb 8, 2016
0877723
updated jsdoc
bezoerb Feb 8, 2016
2571d78
text change
bezoerb Feb 8, 2016
087cbd0
Merge branch 'master' into vinyl
bezoerb Apr 14, 2016
2c9cf36
WiP
bezoerb Aug 12, 2016
64eebb1
Merge branch 'master' into vinyl
bezoerb Aug 12, 2016
eedd20a
fixed tests
bezoerb Aug 14, 2016
16889d8
run all tests
bezoerb Aug 14, 2016
49df47f
some text changes
bezoerb Aug 14, 2016
4b56cee
fixed tests
bezoerb Aug 16, 2016
1dcee24
force re-run appveyor after penthouse fix
bezoerb Aug 17, 2016
9ead9fe
Merge remote-tracking branch 'origin/master' into vinyl
bezoerb Sep 28, 2016
1f12d95
fixed tests
bezoerb Sep 28, 2016
2e902b3
fixed last test
bezoerb Sep 28, 2016
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -213,7 +213,8 @@ critical.generate({
| base | `string` | `path.dirname(src)` or `process.cwd()` | Base directory in which the source and destination are to be written |
| html | `string` | | HTML source to be operated against. This option takes precedence over the `src` option |
| src | `string` | | Location of the HTML source to be operated against |
| dest | `string` | | Location of where to save the output of an operation |
| dest | `string` | | Location of where to save the output of an operation (will be relative to base if no absolute path is set) |
| destFolder | `string` | `''` | Subfolder relative to base directory. Only relevant without src (if raw html is provided) or if the destination is outside base |
| width | `integer` | `900` | Width of the target viewport |
| height | `integer` | `1300` | Height of the target viewport |
| dimensions | `array` | `[]` | An array of objects containing height and width. Takes precedence over `width` and `height` if set
Expand Down
7 changes: 5 additions & 2 deletions cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
'use strict';
var os = require('os');
var path = require('path');
var chalk = require('chalk');
var meow = require('meow');
var indentString = require('indent-string');
var stdin = require('get-stdin');
Expand All @@ -24,6 +25,7 @@ var help = [
' -i, --inline Generate the HTML with inlined critical-path CSS',
' -e, --extract Extract inlined styles from referenced stylesheets',
' -p, --pathPrefix Path to prepend CSS assets with (defaults to /) ',
' -f, --folder HTML Subfolder (default: \'\')',
' --ii, --inlineImages Inline images',
' --ignore RegExp, @type or selector to ignore',
' --include RegExp, @type or selector to include',
Expand All @@ -46,6 +48,7 @@ var cli = meow({
c: 'css',
w: 'width',
h: 'height',
f: 'folder',
H: 'htmlTarget',
i: 'inline',
I: 'ignore',
Expand Down Expand Up @@ -116,9 +119,9 @@ cli.flags = _.reduce(cli.flags, function (res, val, key) {
}, {});

function error(err) {
process.stderr.write(indentString(err.message || err, 1, ' Error: '));
process.stderr.write(indentString((chalk.red('Error: ') + err.message || err), 3));
process.stderr.write(os.EOL);
process.stderr.write(indentString(help.join(os.EOL), 1, ' '));
process.stderr.write(indentString(help.join(os.EOL), 3));
process.exit(1);
}

Expand Down
45 changes: 35 additions & 10 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,37 @@ var inliner = require('./lib/inline-styles');

Promise.promisifyAll(fs);

/**
* Normalize options
*
* @param opts
*/
function prepareOptions(opts) {
if (!opts) {
opts = {};
}

var options = _.defaults(opts, {
base: file.guessBasePath(opts),
dimensions: [{
height: opts.height || 900,
width: opts.width || 1300
}]
});

// set dest relative to base if isn't specivied absolute
if (options.dest && !path.isAbsolute(options.dest)) {
options.dest = path.join(options.base, options.dest);
}

// set dest relative to base if isn't specivied absolute
if (options.destFolder && !path.isAbsolute(options.destFolder)) {
options.destFolder = path.join(options.base, options.destFolder);
}

return options;
}

/**
* Critical path CSS generation
* @param {object} opts Options
Expand All @@ -22,13 +53,7 @@ Promise.promisifyAll(fs);
* @return {Promise}|undefined
*/
exports.generate = function (opts, cb) {
opts = _.defaults(opts || {}, {
base: file.guessBasePath(opts || {}),
dimensions: [{
height: opts.height || 900,
width: opts.width || 1300
}]
});
opts = prepareOptions(opts);

// generate critical css
var corePromise = core.generate(opts);
Expand All @@ -48,10 +73,10 @@ exports.generate = function (opts, cb) {
// inline
if (opts.inline) {
corePromise = Promise.props({
html: file.getContentPromise(opts),
file: file.getVinylPromise(opts),
css: corePromise
}).then(function (result) {
return sourceInliner(result.html, result.css, {
return sourceInliner(result.file.contents.toString(), result.css, {
minify: opts.minify || false,
extract: opts.extract || false,
basePath: opts.base || process.cwd()
Expand Down Expand Up @@ -161,7 +186,7 @@ exports.stream = function (opts) {
}

var options = _.assign(opts || {}, {
html: file.contents.toString()
src: file
});

exports.generate(options, function (err, data) {
Expand Down
201 changes: 120 additions & 81 deletions lib/core.js
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,122 @@ function startServer(opts) {
});
}

/**
* Append stylesheets to result
* @param opts
* @returns {function}
*/
function appendStylesheets(opts) {
return function (htmlfile) {
// consider opts.css and map to array if it's a string
if (opts.css) {
htmlfile.stylesheets = typeof opts.css === 'string' ? [opts.css] : opts.css;
return htmlfile;
}

// Oust extracts a list of your stylesheets
var stylesheets = oust(htmlfile.contents.toString(), 'stylesheets');
debug('Stylesheets: ' + stylesheets);
stylesheets = stylesheets.map(file.resourcePath(htmlfile, opts));
return Promise.map(stylesheets, file.assertLocal(opts)).then(function (stylesheets) {
htmlfile.stylesheets = stylesheets;
return htmlfile;
});
};
}

/**
* Inline images using postcss-image-inliner
* @param opts
* @returns {function}
*/
function inlineImages(opts) {
return function _inlineImages(vinyl) {
if (opts.inlineImages) {
var inlineOptions = {
assetPaths: _.uniq((opts.assetPaths || []).concat([path.dirname(vinyl.path), opts.base])),
maxFileSize: opts.maxImageFileSize || 10240
};
debug('inlineImages', inlineOptions);
return postcss([imageInliner(inlineOptions)])
.process(vinyl.contents.toString('utf8'))
.then(function (contents) {
vinyl.contents = new Buffer(contents.css);
return vinyl;
});
}

return vinyl;
};
}

/**
* Helper function create vinyl objects
* @param opts
* @returns {function}
*/
function vinylize(opts) {
return function _vinylize(filepath) {
debug('vinylize', path.resolve(filepath));
return file.getVinylPromise({
src: path.resolve(filepath),
base: opts.base
});
};
}

/**
* Read css source, inline images and normalize relative paths
* @param opts
* @returns {function}
*/
function processStylesheets(opts) {
return function (htmlfile) {
debug('processStylesheets', htmlfile.stylesheets);
return Promise.map(htmlfile.stylesheets, vinylize(opts))
.map(inlineImages(opts))
.map(file.replaceAssetPaths(htmlfile, opts))
.reduce(function (total, stylesheet) {
return total + os.EOL + stylesheet.contents.toString('utf8');
}, '')
.then(function (css) {
htmlfile.cssPath = tempfile('.css');
// add file to garbage collector so it get's removed on exit
gc.addFile(htmlfile.cssPath);

return fs.writeFileAsync(htmlfile.cssPath, css).then(function () {
return htmlfile;
});
});
};
}

/**
* Fire up a server as pentouse doesn't like filesystem paths on windows
* and let pentouse compute the critical css for us
* @param dimensions
* @param {object} opts Options passed to critical
* @returns {function}
*/
function computeCritical(dimensions, opts) {
return function _computeCritical(htmlfile) {
return startServer(opts).then(function (server) {
debug('Processing: ' + htmlfile.path + ' [' + dimensions.width + 'x' + dimensions.height + ']');
return penthouseAsync({
url: file.getPenthouseUrl(opts, htmlfile, server.port),
css: htmlfile.cssPath,
forceInclude: opts.include || [],
timeout: opts.timeout,
maxEmbeddedBase64Length: opts.maxImageFileSize || 10240,
width: dimensions.width,
height: dimensions.height
}).finally(function () {
server.instance.close();
});
});
};
}

/**
* Critical path CSS generation
* @param {object} opts Options
Expand All @@ -85,87 +201,10 @@ function generate(opts) {

return Promise.map(opts.dimensions, function (dimensions) {
// use content to fetch used css files
return file.getContentPromise(opts).then(function (html) {
// consider opts.css and map to array if it's a string
if (opts.css) {
return typeof opts.css === 'string' ? [opts.css] : opts.css;
}

// Oust extracts a list of your stylesheets
var stylesheets = oust(html.toString(), 'stylesheets').map(file.resourcePath(opts));
debug('Stylesheets: ' + stylesheets);
return Promise.map(stylesheets, file.assertLocal(opts));
// read files
}).map(function (fileName) {
return fs.readFileAsync(fileName, 'utf8').then(function (content) {
// get path to css file
var dir = path.dirname(fileName);

if (opts.inlineImages) {
return postcss([imageInliner({
assetPaths: _.uniq((opts.assetPaths || []).concat([dir, opts.base])),
maxFileSize: opts.maxImageFileSize || 10240
})]).process(content).then(function (result) {
return {
dir: path.dirname(fileName),
content: result
};
});
}

return {
dir: path.dirname(fileName),
content: content
};
}).then(function (data) {
var content = data.content;
var dir = data.dir;

// normalize relative paths
return content.toString().replace(/url\(['"]?([^'"\)]+)['"]?\)/g, function (match, filePath) {
// do nothing for absolute paths, urls and data-uris
if (/^data:/.test(filePath) || /(?:^\/)|(?::\/\/)/.test(filePath)) {
return match;
}

// create path relative to opts.base
var relativeToBase = path.relative(path.resolve(opts.base), path.resolve(path.join(dir, filePath)));
var pathPrefix = (typeof opts.pathPrefix === 'undefined') ? '/' : opts.pathPrefix;
return file.normalizePath(match.replace(filePath, path.join(pathPrefix, relativeToBase)));
});
});

// combine all css files to one bid stylesheet
}).reduce(function (total, contents) {
return total + os.EOL + contents;

// write contents to tmp file
}, '').then(function (css) {
var csspath = tempfile('.css');
// add file to garbage collector so it get's removed on exit
gc.addFile(csspath);
return fs.writeFileAsync(csspath, css).then(function () {
return csspath;
});
// let penthouseAsync do the rest
}).then(function (csspath) {
return startServer(opts).then(function (server) {
debug('Processing: ' + opts.url + ' [' + dimensions.width + 'x' + dimensions.height + ']');
return penthouseAsync({
url: file.getPenthouseUrl(opts, server.port),
css: csspath,
forceInclude: opts.include || [],
timeout: opts.timeout,
maxEmbeddedBase64Length: opts.maxImageFileSize || 10240,
// viewport width
width: dimensions.width,
// viewport height
height: dimensions.height
}).finally(function () {
server.instance.close();
});
});
});
return file.getVinylPromise(opts)
.then(appendStylesheets(opts))
.then(processStylesheets(opts))
.then(computeCritical(dimensions, opts));
}).then(function (criticalCSS) {
criticalCSS = combineCss(criticalCSS);

Expand Down
Loading