UNPKG

5.44 kBJavaScriptView Raw
1'use strict';
2
3const path = require('path');
4const fs = require('fs-extra');
5const {assign, defaults, isFunction, isObject, intersection, keys} = require('lodash');
6
7const chalk = require('chalk');
8const sourceInliner = require('inline-critical');
9const Bluebird = require('bluebird');
10const through2 = require('through2');
11const PluginError = require('plugin-error');
12const replaceExtension = require('replace-ext');
13
14const {cleanup} = require('./lib/gc');
15const core = require('./lib/core');
16const file = require('./lib/file-helper');
17
18/**
19 * Normalize options
20 *
21 * @param opts
22 */
23function prepareOptions(opts) {
24 if (!opts) {
25 opts = {};
26 }
27
28 const options = defaults(opts, {
29 base: file.guessBasePath(opts),
30 dimensions: [{
31 height: opts.height || 900,
32 width: opts.width || 1300
33 }]
34 });
35
36 // Set dest relative to base if isn't specivied absolute
37 if (options.dest && !path.isAbsolute(options.dest)) {
38 options.dest = path.join(options.base, options.dest);
39 }
40
41 // Set dest relative to base if isn't specivied absolute
42 if (options.destFolder && !path.isAbsolute(options.destFolder)) {
43 options.destFolder = path.join(options.base, options.destFolder);
44 }
45
46 if (!options.inline && options.extract) {
47 console.error(chalk.red('The extract option requires inline:true'));
48 }
49
50 // Set options for inline-critical
51 options.inline = Boolean(options.inline) && assign({
52 minify: opts.minify || false,
53 extract: opts.extract || false,
54 basePath: opts.base || process.cwd()
55 }, (isObject(options.inline) && options.inline) || {});
56
57 // Set penthouse options
58 options.penthouse = assign({}, {
59 forceInclude: opts.include || [],
60 timeout: opts.timeout || 30000,
61 maxEmbeddedBase64Length: opts.maxImageFileSize || 10240
62 }, options.penthouse || {});
63
64 // Show overwrite warning if penthouse params url, css, witdh or height are present
65 const checkOpts = intersection(keys(options.penthouse), ['url', 'css', 'width', 'height']);
66 if (checkOpts.length > 0) {
67 console.warn(chalk.yellow('Detected presence of penthouse options:'), checkOpts.join(', '));
68 console.warn(chalk.yellow('These options will be overwritten by critical during the process.'));
69 }
70
71 // Order dimensions
72 options.dimensions = options.dimensions.slice().sort((a, b) => (a.width || 0) - (b.width || 0));
73
74 return options;
75}
76
77/**
78 * Critical path CSS generation
79 * @param {object} opts Options
80 * @param {function} cb Callback
81 * @accepts src, base, width, height, dimensions, dest
82 * @return {Promise}|undefined
83 */
84exports.generate = (opts, cb) => {
85 opts = prepareOptions(opts);
86
87 // Generate critical css
88 let corePromise = core.generate(opts);
89
90 // Store generated css
91 if (opts.styleTarget) {
92 corePromise = corePromise.then(output => fs.outputFile(path.resolve(opts.styleTarget), output).then(() => output));
93 }
94
95 // Inline
96 if (opts.inline) {
97 corePromise = Promise.all([file.getVinylPromise(opts), corePromise])
98 .then(([file, css]) => {
99 if (css) {
100 return sourceInliner(file.contents.toString(), css, opts.inline);
101 }
102
103 return file.contents.toString();
104 });
105 }
106
107 // Save to file
108 if (opts.dest) {
109 corePromise = corePromise.then(output => fs.outputFile(path.resolve(opts.dest), output).then(() => output));
110 }
111
112 // Return promise if callback is not defined
113 if (isFunction(cb)) {
114 corePromise.catch(error => { // eslint-disable-line promise/valid-params
115 cleanup();
116 cb(error);
117 throw new Bluebird.CancellationError();
118 }).then(output => {
119 cleanup();
120 cb(null, output.toString());
121 }).catch(Bluebird.CancellationError, () => {
122 console.log('Canceled due to an error');
123 /* Everything already done */
124 });
125 } else {
126 return corePromise.then(output => {
127 cleanup();
128 return output;
129 });
130 }
131};
132
133/**
134 * Deprecated has been removed
135 */
136exports.generateInline = () => {
137 throw new Error('"generateInline" has been removed. Use "generate" with the inline option instead. https://goo.gl/7VbE4b');
138};
139
140/**
141 * Deprecated has been removed
142 */
143exports.inline = () => {
144 throw new Error('"inline" has been removed. Consider using "inline-critical" instead. https://goo.gl/MmTrUZ');
145};
146
147/**
148 * Streams wrapper for critical
149 *
150 * @param {object} opts
151 * @returns {*}
152 */
153exports.stream = opts => {
154 // Return stream
155 return through2.obj(function (file, enc, cb) {
156 if (file.isNull()) {
157 return cb(null, file);
158 }
159
160 if (file.isStream()) {
161 return this.emit('error', new PluginError('critical', 'Streaming not supported'));
162 }
163
164 const options = assign(opts || {}, {
165 src: file
166 });
167
168 exports.generate(options, (err, data) => {
169 if (err) {
170 return cb(new PluginError('critical', err.message));
171 }
172
173 // Rename file if not inlined
174 if (!opts.inline) {
175 file.path = replaceExtension(file.path, '.css');
176 }
177
178 file.contents = Buffer.from(data);
179 cb(err, file);
180 });
181 });
182};