Procházet zdrojové kódy

Content styles script do not print duplicated styles and handles at-rule selectors.

Kamil Piechaczek před 6 roky
rodič
revize
bd7e738e79

+ 66 - 30
scripts/docs/build-content-styles.js

@@ -18,7 +18,8 @@ const VARIABLE_USAGE_REGEXP = /var\((--[\w-]+)\)/g;
 
 const contentRules = {
 	selector: [],
-	variables: []
+	variables: [],
+	atRules: {}
 };
 
 const webpackConfig = getWebpackConfig();
@@ -49,35 +50,8 @@ runWebpack( webpackConfig )
 		// CSS variables that are used by the `.ck-content` selector.
 		const usedVariables = new Set();
 
-		const selectorCss = contentRules.selector
-			.map( rule => {
-				// Removes all comments from the rule definition.
-				const cssAsArray = rule.css.replace( /\/\*[^*]+\*\//g, '' ).split( '\n' );
-
-				// We want to fix invalid indentations. We need to find a number of how many indentations we want to remove.
-				// Because the last line ends the block, we can use this value.
-				const lastLineIndent = cssAsArray[ cssAsArray.length - 1 ].length - 1;
-
-				const css = cssAsArray
-					.filter( line => line.trim().length > 0 )
-					.map( ( line, index ) => {
-						// Do not touch the first line. It is always correct.
-						if ( index === 0 ) {
-							return line;
-						}
-
-						return line.slice( lastLineIndent );
-					} )
-					.join( '\n' );
-
-				return `/* ${ rule.file.replace( packagesPath + path.sep, '' ) } */\n${ css }`;
-			} )
-			.filter( rule => {
-				// 1st: path to the CSS file, 2nd: selector definition - start block, 3rd: end block
-				// If the rule contains only 3 lines, it means that it does not define any rules.
-				return rule.split( '\n' ).length > 3;
-			} )
-			.join( '\n' );
+		// `.ck-content` selectors.
+		const selectorCss = transformCssRules( contentRules.selector );
 
 		// Find all CSS variables inside the `.ck-content` selector.
 		let match;
@@ -111,6 +85,18 @@ runWebpack( webpackConfig )
 			}
 		}
 
+		const atRulesDefinitions = [];
+
+		// Additional at-rules
+		for ( const atRuleName of Object.keys( contentRules.atRules ) ) {
+			const rules = transformCssRules( contentRules.atRules[ atRuleName ] )
+				.split( '\n' )
+				.map( line => `\t${ line }` )
+				.join( '\n' );
+
+			atRulesDefinitions.push( `@${ atRuleName } {\n${ rules }\n}` );
+		}
+
 		// Build the final content of the CSS file.
 		let data = [
 			'/*',
@@ -128,6 +114,8 @@ runWebpack( webpackConfig )
 
 		data += '}\n\n';
 		data += selectorCss;
+		data += '\n';
+		data += atRulesDefinitions.join( '\n' );
 
 		return writeFile( path.join( DESTINATION_DIRECTORY, 'content-styles.css' ), data );
 	} )
@@ -239,3 +227,51 @@ function writeFile( file, data ) {
 		} );
 	} );
 }
+
+/**
+ * @param {Array} rules
+ * @returns {String}
+ */
+function transformCssRules( rules ) {
+	return rules
+		.map( rule => {
+			// Removes all comments from the rule definition.
+			const cssAsArray = rule.css.replace( /\/\*[^*]+\*\//g, '' ).split( '\n' );
+
+			// We want to fix invalid indentations. We need to find a number of how many indentations we want to remove.
+			// Because the last line ends the block, we can use this value.
+			const lastLineIndent = cssAsArray[ cssAsArray.length - 1 ].length - 1;
+
+			const css = cssAsArray
+				.filter( line => line.trim().length > 0 )
+				.map( ( line, index ) => {
+					// Do not touch the first line. It is always correct.
+					if ( index === 0 ) {
+						return line;
+					}
+
+					const newLine = line.slice( lastLineIndent );
+
+					// If a line is not a CSS definition, do not touch it.
+					if ( !newLine.match( /[A-Z-_0-9]+:/i ) ) {
+						return newLine;
+					}
+
+					// The line is a CSS definition – let's check whether it ends with a semicolon.
+					if ( newLine.endsWith( ';' ) ) {
+						return newLine;
+					}
+
+					return newLine + ';';
+				} )
+				.join( '\n' );
+
+			return `/* ${ rule.file.replace( packagesPath + path.sep, '' ) } */\n${ css }`;
+		} )
+		.filter( rule => {
+			// 1st: path to the CSS file, 2nd: selector definition - start block, 3rd: end block
+			// If the rule contains only 3 lines, it means that it does not define any rules.
+			return rule.split( '\n' ).length > 3;
+		} )
+		.join( '\n' );
+}

+ 66 - 10
scripts/docs/content-styles/list-content-styles.js

@@ -15,21 +15,77 @@ module.exports = postcss.plugin( 'list-content-styles', function( options ) {
 
 	return root => {
 		root.walkRules( rule => {
-			rule.selectors.forEach( selector => {
+			for ( const selector of rule.selectors ) {
+				const data = {
+					file: root.source.input.file,
+					css: rule.toString()
+				};
+
 				if ( selector.match( ':root' ) ) {
-					variables.push( {
-						file: root.source.input.file,
-						css: rule.toString()
-					} );
+					addDefinition( variables, data );
 				}
 
 				if ( selector.match( '.ck-content' ) ) {
-					selectorStyles.push( {
-						file: root.source.input.file,
-						css: rule.toString()
-					} );
+					if ( rule.parent.name && rule.parent.params ) {
+						const atRule = getAtRuleArray( options.contentRules.atRules, rule.parent.name, rule.parent.params );
+
+						addDefinition( atRule, data );
+					} else {
+						addDefinition( selectorStyles, data );
+					}
 				}
-			} );
+			}
 		} );
 	};
 } );
+
+/**
+ * @param {Object} collection
+ * @param {String} name Name of an `at-rule`.
+ * @param {String} params Parameters that describes the `at-rule`.
+ * @returns {Array}
+ */
+function getAtRuleArray( collection, name, params ) {
+	const definition = `${ name } ${ params }`;
+
+	if ( !collection[ definition ] ) {
+		collection[ definition ] = [];
+	}
+
+	return collection[ definition ];
+}
+
+/**
+ * Checks whether specified definition is duplicated in the colletion.
+ *
+ * @param {Array.<StyleStructure>} collection
+ * @param {StyleStructure} def
+ * @returns {Boolean}
+ */
+function isDuplicatedDefinition( collection, def ) {
+	for ( const item of collection ) {
+		if ( item.file === def.file && item.css === def.css ) {
+			return true;
+		}
+	}
+
+	return false;
+}
+
+/**
+ * Adds definition to the collection if it does not exist in the collection.
+ *
+ * @param {Array.<StyleStructure>} collection
+ * @param {StyleStructure} def
+ */
+function addDefinition( collection, def ) {
+	if ( !isDuplicatedDefinition( collection, def ) ) {
+		collection.push( def );
+	}
+}
+
+/**
+ * @typedef {Object} StyleStructure
+ * @property {String} file An absolute path to the file where a definition is defined.
+ * @property {String} css Definition.
+ */