var path = require('path'); var prettyBytes = require('pretty-bytes'); var parse5 = require('parse5'); var select = require('css-select'); var treeAdapter = require('parse5-htmlparser2-tree-adapter'); var css = require('css'); var chalk = require('chalk'); function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; } function _interopNamespace(e) { if (e && e.__esModule) return e; var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n['default'] = e; return n; } var path__default = /*#__PURE__*/_interopDefaultLegacy(path); var prettyBytes__default = /*#__PURE__*/_interopDefaultLegacy(prettyBytes); var parse5__default = /*#__PURE__*/_interopDefaultLegacy(parse5); var select__default = /*#__PURE__*/_interopDefaultLegacy(select); var treeAdapter__default = /*#__PURE__*/_interopDefaultLegacy(treeAdapter); var css__default = /*#__PURE__*/_interopDefaultLegacy(css); var chalk__default = /*#__PURE__*/_interopDefaultLegacy(chalk); /** * Copyright 2018 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ const PARSE5_OPTS = { treeAdapter: treeAdapter__default['default'] }; /** * Parse HTML into a mutable, serializable DOM Document. * The DOM implementation is an htmlparser2 DOM enhanced with basic DOM mutation methods. * @param {String} html HTML to parse into a Document instance */ function createDocument(html) { const document = parse5__default['default'].parse(html, PARSE5_OPTS); defineProperties(document, DocumentExtensions); // Extend Element.prototype with DOM manipulation methods. const scratch = document.createElement('div'); // Get a reference to the base Node class - used by createTextNode() document.$$Node = scratch.constructor; const elementProto = Object.getPrototypeOf(scratch); defineProperties(elementProto, ElementExtensions); elementProto.ownerDocument = document; return document; } /** * Serialize a Document to an HTML String * @param {Document} document A Document, such as one created via `createDocument()` */ function serializeDocument(document) { return parse5__default['default'].serialize(document, PARSE5_OPTS); } /** * Methods and descriptors to mix into Element.prototype */ const ElementExtensions = { /** @extends htmlparser2.Element.prototype */ nodeName: { get() { return this.tagName.toUpperCase(); } }, id: reflectedProperty('id'), className: reflectedProperty('class'), insertBefore(child, referenceNode) { if (!referenceNode) return this.appendChild(child); treeAdapter__default['default'].insertBefore(this, child, referenceNode); return child; }, appendChild(child) { treeAdapter__default['default'].appendChild(this, child); return child; }, removeChild(child) { treeAdapter__default['default'].detachNode(child); }, remove() { treeAdapter__default['default'].detachNode(this); }, textContent: { get() { return getText(this); }, set(text) { this.children = []; treeAdapter__default['default'].insertText(this, text); } }, setAttribute(name, value) { if (this.attribs == null) this.attribs = {}; if (value == null) value = ''; this.attribs[name] = value; }, removeAttribute(name) { if (this.attribs != null) { delete this.attribs[name]; } }, getAttribute(name) { return this.attribs != null && this.attribs[name]; }, hasAttribute(name) { return this.attribs != null && this.attribs[name] != null; }, getAttributeNode(name) { const value = this.getAttribute(name); if (value != null) return { specified: true, value }; } }; /** * Methods and descriptors to mix into the global document instance * @private */ const DocumentExtensions = { /** @extends htmlparser2.Document.prototype */ // document is just an Element in htmlparser2, giving it a nodeType of ELEMENT_NODE. // TODO: verify if these are needed for css-select nodeType: { get() { return 9; } }, contentType: { get() { return 'text/html'; } }, nodeName: { get() { return '#document'; } }, documentElement: { get() { // Find the first element within the document return this.childNodes.filter(child => String(child.tagName).toLowerCase() === 'html'); } }, compatMode: { get() { const compatMode = { 'no-quirks': 'CSS1Compat', quirks: 'BackCompat', 'limited-quirks': 'CSS1Compat' }; return compatMode[treeAdapter__default['default'].getDocumentMode(this)]; } }, body: { get() { return this.querySelector('body'); } }, createElement(name) { return treeAdapter__default['default'].createElement(name, null, []); }, createTextNode(text) { // there is no dedicated createTextNode equivalent exposed in htmlparser2's DOM const Node = this.$$Node; return new Node({ type: 'text', data: text, parent: null, prev: null, next: null }); }, querySelector(sel) { return select__default['default'].selectOne(sel, this.documentElement); }, querySelectorAll(sel) { if (sel === ':root') { return this; } return select__default['default'](sel, this.documentElement); } }; /** * Essentially `Object.defineProperties()`, except function values are assigned as value descriptors for convenience. * @private */ function defineProperties(obj, properties) { for (const i in properties) { const value = properties[i]; Object.defineProperty(obj, i, typeof value === 'function' ? { value } : value); } } /** * Create a property descriptor defining a getter/setter pair alias for a named attribute. * @private */ function reflectedProperty(attributeName) { return { get() { return this.getAttribute(attributeName); }, set(value) { this.setAttribute(attributeName, value); } }; } /** * Helper to get the text content of a node * https://github.com/fb55/domutils/blob/master/src/stringify.ts#L21 * @private */ function getText(node) { if (Array.isArray(node)) return node.map(getText).join(''); if (treeAdapter__default['default'].isElementNode(node)) return node.name === 'br' ? '\n' : getText(node.children); if (treeAdapter__default['default'].isTextNode(node)) return node.data; return ''; } /** * Copyright 2018 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ /** * Parse a textual CSS Stylesheet into a Stylesheet instance. * Stylesheet is a mutable ReworkCSS AST with format similar to CSSOM. * @see https://github.com/reworkcss/css * @private * @param {String} stylesheet * @returns {css.Stylesheet} ast */ function parseStylesheet(stylesheet) { return css__default['default'].parse(stylesheet); } /** * Serialize a ReworkCSS Stylesheet to a String of CSS. * @private * @param {css.Stylesheet} ast A Stylesheet to serialize, such as one returned from `parseStylesheet()` * @param {Object} options Options to pass to `css.stringify()` * @param {Boolean} [options.compress] Compress CSS output (removes comments, whitespace, etc) */ function serializeStylesheet(ast, options) { return css__default['default'].stringify(ast, options); } /** * Converts a walkStyleRules() iterator to mark nodes with `.$$remove=true` instead of actually removing them. * This means they can be removed in a second pass, allowing the first pass to be nondestructive (eg: to preserve mirrored sheets). * @private * @param {Function} iterator Invoked on each node in the tree. Return `false` to remove that node. * @returns {(rule) => void} nonDestructiveIterator */ function markOnly(predicate) { return rule => { const sel = rule.selectors; if (predicate(rule) === false) { rule.$$remove = true; } rule.$$markedSelectors = rule.selectors; if (rule._other) { rule._other.$$markedSelectors = rule._other.selectors; } rule.selectors = sel; }; } /** * Apply filtered selectors to a rule from a previous markOnly run. * @private * @param {css.Rule} rule The Rule to apply marked selectors to (if they exist). */ function applyMarkedSelectors(rule) { if (rule.$$markedSelectors) { rule.selectors = rule.$$markedSelectors; } if (rule._other) { applyMarkedSelectors(rule._other); } } /** * Recursively walk all rules in a stylesheet. * @private * @param {css.Rule} node A Stylesheet or Rule to descend into. * @param {Function} iterator Invoked on each node in the tree. Return `false` to remove that node. */ function walkStyleRules(node, iterator) { if (node.stylesheet) return walkStyleRules(node.stylesheet, iterator); node.rules = node.rules.filter(rule => { if (rule.rules) { walkStyleRules(rule, iterator); } rule._other = undefined; rule.filterSelectors = filterSelectors; return iterator(rule) !== false; }); } /** * Recursively walk all rules in two identical stylesheets, filtering nodes into one or the other based on a predicate. * @private * @param {css.Rule} node A Stylesheet or Rule to descend into. * @param {css.Rule} node2 A second tree identical to `node` * @param {Function} iterator Invoked on each node in the tree. Return `false` to remove that node from the first tree, true to remove it from the second. */ function walkStyleRulesWithReverseMirror(node, node2, iterator) { if (node2 === null) return walkStyleRules(node, iterator); if (node.stylesheet) return walkStyleRulesWithReverseMirror(node.stylesheet, node2.stylesheet, iterator); [node.rules, node2.rules] = splitFilter(node.rules, node2.rules, (rule, index, rules, rules2) => { const rule2 = rules2[index]; if (rule.rules) { walkStyleRulesWithReverseMirror(rule, rule2, iterator); } rule._other = rule2; rule.filterSelectors = filterSelectors; return iterator(rule) !== false; }); } // Like [].filter(), but applies the opposite filtering result to a second copy of the Array without a second pass. // This is just a quicker version of generating the compliment of the set returned from a filter operation. function splitFilter(a, b, predicate) { const aOut = []; const bOut = []; for (let index = 0; index < a.length; index++) { if (predicate(a[index], index, a, b)) { aOut.push(a[index]); } else { bOut.push(a[index]); } } return [aOut, bOut]; } // can be invoked on a style rule to subset its selectors (with reverse mirroring) function filterSelectors(predicate) { if (this._other) { const [a, b] = splitFilter(this.selectors, this._other.selectors, predicate); this.selectors = a; this._other.selectors = b; } else { this.selectors = this.selectors.filter(predicate); } } const LOG_LEVELS = ['trace', 'debug', 'info', 'warn', 'error', 'silent']; const defaultLogger = { trace(msg) { console.trace(msg); }, debug(msg) { console.debug(msg); }, warn(msg) { console.warn(chalk__default['default'].yellow(msg)); }, error(msg) { console.error(chalk__default['default'].bold.red(msg)); }, info(msg) { console.info(chalk__default['default'].bold.blue(msg)); }, silent() {} }; function createLogger(logLevel) { const logLevelIdx = LOG_LEVELS.indexOf(logLevel); return LOG_LEVELS.reduce((logger, type, index) => { if (index >= logLevelIdx) { logger[type] = defaultLogger[type]; } else { logger[type] = defaultLogger.silent; } return logger; }, {}); } /** * Copyright 2018 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ /** * The mechanism to use for lazy-loading stylesheets. * _[JS]_ indicates that a strategy requires JavaScript (falls back to `