inline-critical/cli.js

188 lines
3.9 KiB
JavaScript
Raw Permalink Normal View History

2015-06-25 06:27:18 +02:00
#!/usr/bin/env node
2015-06-25 06:27:18 +02:00
'use strict';
2017-03-20 22:42:19 +01:00
const os = require('os');
const fs = require('fs');
const meow = require('meow');
2018-12-30 23:41:29 +01:00
const chalk = require('chalk');
2017-03-20 22:42:19 +01:00
const indentString = require('indent-string');
const stdin = require('get-stdin');
const css = require('css');
const escapeRegExp = require('lodash.escaperegexp');
const defaults = require('lodash.defaults');
2018-03-05 20:56:57 +01:00
const inlineCritical = require('.');
2015-06-25 06:27:18 +02:00
2017-03-20 22:42:19 +01:00
let ok;
2018-03-05 20:56:57 +01:00
const help = `
Usage: inline-critical <input> [<option>]
2015-06-25 06:27:18 +02:00
2018-03-05 20:56:57 +01:00
Options:
-c, --css Path to CSS file
-h, --html Path to HTML file
-i, --ignore Skip matching stylesheets
-m, --minify Minify the styles before inlining (default)
2020-05-20 00:31:59 +02:00
-p, --preload Adds preload tags
2018-03-05 20:56:57 +01:00
-e, --extract Remove the inlined styles from any stylesheets referenced in the HTML
-b, --base Is used when extracting styles to find the files references by href attributes
-s, --selector Optionally defines the element used by loadCSS as a reference for inlining
2019-03-26 17:31:54 +01:00
2020-05-20 00:31:59 +02:00
--polyfill Use loadCSS polyfill instead of media=print
2019-03-26 17:31:54 +01:00
--noscript Position of noscript fallback ('body' - end of body, 'head' - end of head, false - no noscript)
2018-03-05 20:56:57 +01:00
`;
const cli = meow(help, {
2018-12-18 12:51:30 +01:00
autoHelp: true,
autoVersion: true,
flags: {
css: {
type: 'string',
alias: 'c',
},
html: {
type: 'string',
alias: 'h',
},
ignore: {
type: 'string',
alias: 'i',
2020-05-23 00:36:56 +02:00
isMultiple: true,
2018-12-18 12:51:30 +01:00
},
minify: {
type: 'boolean',
alias: 'm',
2018-12-30 23:41:29 +01:00
default: true,
2018-12-18 12:51:30 +01:00
},
extract: {
type: 'boolean',
alias: 'e',
},
base: {
type: 'string',
alias: 'b',
},
selector: {
type: 'string',
alias: 's',
},
2020-05-20 00:31:59 +02:00
preload: {
type: 'boolean',
alias: 'p',
default: false,
},
polyfill: {
type: 'boolean',
default: false,
},
2019-03-26 17:31:54 +01:00
noscript: {
type: 'string',
},
2018-12-18 12:51:30 +01:00
},
2015-06-25 06:27:18 +02:00
});
2017-03-20 22:42:19 +01:00
// Cleanup cli flags
2020-05-23 00:36:56 +02:00
cli.flags = Object.entries(cli.flags).reduce((result, [key, value]) => {
if (key.length <= 1) {
2020-05-23 00:36:56 +02:00
return result;
}
2015-06-25 06:27:18 +02:00
switch (key) {
case 'css':
case 'html':
try {
2020-05-23 00:36:56 +02:00
result[key] = read(value);
} catch (_) {}
break;
case 'base':
2020-05-23 00:36:56 +02:00
result.basePath = value;
break;
case 'ignore':
2020-05-23 00:36:56 +02:00
if (!Array.isArray(value)) {
value = [value];
}
2020-05-23 00:36:56 +02:00
result.ignore = (value || []).map((ignore) => {
// Check regex
2020-05-20 07:28:30 +02:00
const {groups} = /^\/(?<expression>.*)\/(?<flags>[igmy]+)?$/.exec(ignore) || {};
const {expression, flags} = groups || {};
2020-05-20 07:28:30 +02:00
if (groups) {
return new RegExp(escapeRegExp(expression), flags);
2018-12-18 12:51:30 +01:00
}
2019-06-13 23:31:53 +02:00
return ignore;
});
break;
default:
2020-05-23 00:36:56 +02:00
result[key] = value;
break;
}
2015-06-25 06:27:18 +02:00
2020-05-23 00:36:56 +02:00
return result;
}, {});
2015-06-25 06:27:18 +02:00
2018-09-15 23:16:53 +02:00
function processError(err) {
process.stderr.write(chalk.red(indentString(`Error: ${err.message || err}`, 2)));
2018-12-18 12:51:30 +01:00
process.stderr.write(os.EOL);
2018-12-30 23:41:29 +01:00
process.stderr.write(indentString(help, 2));
process.exit(1);
2015-06-25 06:27:18 +02:00
}
function read(file) {
2018-12-18 12:51:30 +01:00
try {
return fs.readFileSync(file, 'utf8');
} catch (error) {
processError(error);
}
2015-06-25 06:27:18 +02:00
}
function run(data) {
2020-05-23 00:36:56 +02:00
const options_ = defaults(cli.flags, {basePath: process.cwd()});
2018-12-18 12:51:30 +01:00
ok = true;
2015-11-13 22:59:10 +01:00
2018-12-18 12:51:30 +01:00
if (data) {
// Detect html
try {
css.parse(data);
2020-05-23 00:36:56 +02:00
options_.css = data;
} catch (_) {
2020-05-23 00:36:56 +02:00
options_.html = data;
2015-06-25 06:27:18 +02:00
}
2018-12-18 12:51:30 +01:00
}
2015-06-25 06:27:18 +02:00
2020-05-23 00:36:56 +02:00
(cli.input || []).forEach((file) => {
const temporary = read(file);
2015-06-25 06:27:18 +02:00
try {
2020-05-23 00:36:56 +02:00
css.parse(temporary);
options_.css = temporary;
} catch (_) {
2020-05-23 00:36:56 +02:00
options_.html = temporary;
2015-06-25 06:27:18 +02:00
}
2018-12-18 12:51:30 +01:00
});
2020-05-23 00:36:56 +02:00
if (!options_.html || !options_.css) {
2018-12-18 12:51:30 +01:00
cli.showHelp();
}
2020-05-23 00:36:56 +02:00
const {html, css: styles, ...options} = options_;
2018-12-18 12:51:30 +01:00
try {
const out = inlineCritical(html, styles, options);
process.stdout.write(out.toString(), process.exit);
} catch (error) {
processError(error);
}
2015-06-25 06:27:18 +02:00
}
2017-03-20 22:42:19 +01:00
// Get stdin
2015-09-16 06:28:54 +02:00
stdin().then(run);
2017-03-20 22:42:19 +01:00
setTimeout(() => {
2018-12-18 12:51:30 +01:00
if (ok) {
return;
}
2019-06-13 23:31:53 +02:00
2018-12-18 12:51:30 +01:00
run();
}, 100);