diff --git a/bin/polymer-css-build b/bin/polymer-css-build index c54b30d..122e68a 100755 --- a/bin/polymer-css-build +++ b/bin/polymer-css-build @@ -57,6 +57,11 @@ const options = [ }, defaultValue: 2, description: 'Which version of Polymer to build for. Determines if URLs are transformed in templates, and whether to transform to . Supported versions are `1` and `2`. Version 2 is default' + }, + { + name: 'single-style', + type: Boolean, + description: 'Merge all element and custom styles into a single style. This will only work with the "--build-for-shady" flag, and only if browser supports native CSS Custom Properties.' } ]; @@ -92,6 +97,12 @@ if (args.output.length > 0 && args.output.length !== args.file.length) { process.exit(1); } +if (args['single-style'] && !args['build-for-shady']) { + console.error('--single-style requires --build-for-shady'); + printHelp(); + process.exit(1); +} + const fs = require('fs'); const docs = args.file.map(f => ({ url: f, @@ -109,5 +120,6 @@ polycss(docs, args).then((docs) => { console.error(err.message); } else { console.error(err); - } process.exit(1); + } + process.exit(1); }) diff --git a/index.js b/index.js index 85fd685..994fcb4 100644 --- a/index.js +++ b/index.js @@ -103,7 +103,7 @@ function getAndFixDomModuleStyles(domModule) { if (!template) { template = dom5.constructors.element('template'); const content = dom5.constructors.fragment(); - styles.forEach(s => dom5.append(content, s)); + styles.forEach((s) => dom5.append(content, s)); dom5.append(template, content); dom5.append(domModule, template); } else { @@ -504,6 +504,15 @@ function buildInlineDocumentSet(analysis) { return inlineHTMLDocumentSet; } +const styleMoverScript = `(function() { + var currentScript = document.currentScript || document._currentScript; + if (!currentScript) return; + var ownerDocument = currentScript.ownerDocument; + if (ownerDocument !== window.document) { + window.document.head.appendChild(currentScript.previousElementSibling); + }})(); + `.trim(); + async function polymerCssBuild(paths, options = {}) { const nativeShadow = options ? !options['build-for-shady'] : true; const polymerVersion = options['polymer-version'] || 2; @@ -655,6 +664,63 @@ async function polymerCssBuild(paths, options = {}) { text = CssParse.stringify(ast, true); dom5.setTextContent(s, text); }); + + // if specified, merge all styles into one, + // in the order of element registration + if (options && options['single-style']) { + let finalStyleText = ''; + // base path from first document + const [firstDocument, ] = analysis.getFeatures({kind: 'html-document', externalPackages: true}); + const extractStylePath = firstDocument.sourceRange.file; + /** @type {!Object} */ + const tagMap = {}; + // invert scope map, tagName -> style + for (const style of flatStyles) { + const scope = scopeMap.get(style); + if (scope) { + tagMap[scopeMap.get(style)] = style; + } + } + const unscopedStyleIds = []; + // first, custom-styles + for (const customStyle of customStyles) { + const text = dom5.getTextContent(customStyle); + finalStyleText += pathResolver.rewriteURL(customStyle.__ownerDocument, extractStylePath, text); + dom5.remove(customStyle); + } + // next element styles in order of element registration + for (const {tagName} of analysis.getFeatures({kind: 'polymer-element', externalPackages: true})) { + const style = tagMap[tagName]; + if (!style) { + continue; + } + unscopedStyleIds.push(...(getAttributeArray(style, 'include'))); + const text = dom5.getTextContent(style); + finalStyleText += pathResolver.rewriteURL(style.__ownerDocument, extractStylePath, text); + dom5.remove(style); + } + // add unscoped styles to the end + for (const id of unscopedStyleIds) { + const domModule = domModuleMap[id]; + if (!domModule) { + continue; + } + const styles = getAndFixDomModuleStyles(domModule); + for (const style of styles) { + finalStyleText += dom5.getTextContent(style); + dom5.remove(style); + } + } + const finalStyle = dom5.constructors.element('style'); + dom5.setAttribute(finalStyle, 'css-build-single', ''); + dom5.setTextContent(finalStyle, finalStyleText); + const htmlDocument = firstDocument.parsedDocument.ast; + const head = dom5.query(htmlDocument, pred.hasTagName('head')); + dom5.append(head, finalStyle); + const script = dom5.constructors.element('script'); + dom5.setTextContent(script, styleMoverScript); + dom5.append(head, script); + } // update inline HTML documents for (const inlineDocument of inlineHTMLDocumentSet) { updateInlineDocument(inlineDocument);