diff --git a/packages/react-styles/.gitignore b/packages/react-styles/.gitignore index ff57b974033..493ec68a943 100644 --- a/packages/react-styles/.gitignore +++ b/packages/react-styles/.gitignore @@ -1 +1 @@ -/css \ No newline at end of file +css \ No newline at end of file diff --git a/packages/react-styles/package.json b/packages/react-styles/package.json index 9d02f451316..c4685b2823a 100644 --- a/packages/react-styles/package.json +++ b/packages/react-styles/package.json @@ -8,26 +8,23 @@ "*.css", "*.scss" ], - "description": "", + "description": "CSS-in-JS class maps and utilities for PatternFly.", "author": "Red Hat", "publishConfig": { "access": "public", "tag": "prerelease-v4" }, "scripts": { - "build": "yarn build:babel && yarn build:types && yarn build:css && node ./scripts/copyStyles.js", + "build": "yarn build:babel && yarn build:types && yarn build:css && node scripts/copyStyles.js", "build:babel": "concurrently \"yarn build:babel:esm && yarn build:babel:umd\" \"yarn build:babel:cjs\"", "build:babel:cjs": "babel --source-maps --extensions \".js,.ts,.tsx\" src --out-dir dist/js --presets=@babel/preset-env", "build:babel:esm": "babel --source-maps --extensions \".js,.ts,.tsx\" src --out-dir dist/esm", "build:babel:umd": "babel --source-maps --extensions \".js\" dist/esm --out-dir dist/umd --plugins=transform-es2015-modules-umd", "build:types": "tsc -p tsconfig.gen-dts.json", - "build:css": "node src/generateClasses.js && tsc && node src/removeTS.js", + "build:css": "node scripts/writeClassMaps.js", "clean": "rimraf dist css", "develop": "yarn build:babel:esm --skip-initial-build --watch --verbose" }, - "dependencies": { - "camel-case": "^3.0.0" - }, "devDependencies": { "@babel/cli": "^7.0.0", "@babel/core": "^7.0.0", @@ -42,6 +39,7 @@ "@patternfly/patternfly": "4.6.0", "babel-plugin-transform-es2015-modules-umd": "^6.24.1", "babel-plugin-typescript-to-proptypes": "^0.17.1", + "camel-case": "^3.0.0", "css": "^2.2.3", "cssstyle": "^0.3.1", "fbjs-scripts": "^0.8.3", diff --git a/packages/react-styles/scripts/generateClassMaps.js b/packages/react-styles/scripts/generateClassMaps.js new file mode 100644 index 00000000000..6716bd9488f --- /dev/null +++ b/packages/react-styles/scripts/generateClassMaps.js @@ -0,0 +1,74 @@ +const path = require('path'); +const fs = require('fs-extra'); +const glob = require('glob'); +const camelcase = require('camel-case'); + +/** + * @param {string} cssString - CSS string + */ +function getCSSClasses(cssString) { + return cssString.match(/(\.)(?!\d)([^\s.,{[>+~#:)]*)(?![^{]*})/g); +} + +/** + * @param {string} className - Class name + */ +function formatClassName(className) { + return camelcase(className.replace(/pf-((c|l|m|u|is|has)-)?/g, '')); +} + +/** + * @param {string} className - Class name + */ +function isModifier(className) { + return Boolean(className && className.startsWith) && className.startsWith('.pf-m-'); +} + +/** + * @param {string} cssString - CSS string + */ +function getClassMaps(cssString) { + const res = {}; + const distinctClasses = new Set(getCSSClasses(cssString)); + + distinctClasses.forEach(className => { + const key = formatClassName(className); + const value = className.replace('.', '').trim(); + if (isModifier(className)) { + res.modifiers = res.modifiers || {}; + res.modifiers[key] = value; + } else { + res[key] = value; + } + }); + + const ordered = {}; + Object.keys(res) + .sort() + .forEach(key => (ordered[key] = res[key])); + + return ordered; +} + +/** + * @returns {any} Map of file names to classMaps + */ +function generateClassMaps() { + const pfStylesDir = path.dirname(require.resolve('@patternfly/patternfly/patternfly.css')); + + const patternflyCSSFiles = glob.sync('**/*.css', { + cwd: pfStylesDir, + ignore: ['assets/**', '*.css'], + absolute: true + }); + const srcCSSFiles = glob.sync('src/css/**/*.css'); + + const res = {}; + [...patternflyCSSFiles, ...srcCSSFiles].forEach(file => (res[file] = getClassMaps(fs.readFileSync(file, 'utf8')))); + + return res; +} + +module.exports = { + generateClassMaps +}; diff --git a/packages/react-styles/scripts/writeClassMaps.js b/packages/react-styles/scripts/writeClassMaps.js new file mode 100644 index 00000000000..39e39d88c5f --- /dev/null +++ b/packages/react-styles/scripts/writeClassMaps.js @@ -0,0 +1,46 @@ +const { join, basename, resolve, relative, dirname } = require('path'); +const { outputFileSync, copyFileSync } = require('fs-extra'); +const { generateClassMaps } = require('./generateClassMaps'); + +const outDir = resolve(__dirname, '../css'); + +const writeCJSExport = (file, classMap) => + outputFileSync( + join(outDir, file.replace(/.css$/, '.js')), + ` +"use strict"; +exports.__esModule = true; +require('./${basename(file, '.css.js')}'); +exports.default = ${JSON.stringify(classMap, null, 2)}; +`.trim() + ); + +const writeDTSExport = (file, classMap) => + outputFileSync( + join(outDir, file.replace(/.css$/, '.d.ts')), + ` +import './${basename(file, '.css.js')}'; +declare const _default: ${JSON.stringify(classMap, null, 2)}; +export default _default; +`.trim() + ); + +/** + * @param {any} classMaps Map of file names to classMaps + */ +function writeClassMaps(classMaps) { + const pfStylesDir = dirname(require.resolve('@patternfly/patternfly/patternfly.css')); + + Object.entries(classMaps).forEach(([file, classMap]) => { + const outPath = file.includes(pfStylesDir) ? relative(pfStylesDir, file) : relative('src/css', file); + + writeCJSExport(outPath, classMap); + writeDTSExport(outPath, classMap); + copyFileSync(file, join(outDir, outPath)); + }); + + // eslint-disable-next-line no-console + console.log('Wrote', Object.keys(classMaps).length * 3, 'CSS-in-JS files'); +} + +writeClassMaps(generateClassMaps()); diff --git a/packages/react-styles/src/generateClasses.js b/packages/react-styles/src/generateClasses.js deleted file mode 100644 index 13276c1077b..00000000000 --- a/packages/react-styles/src/generateClasses.js +++ /dev/null @@ -1,113 +0,0 @@ -const camelcase = require('camel-case'); - -const glob = require('glob'); -const { dirname, basename, resolve, join, parse } = require('path'); -const { readFileSync } = require('fs'); -const { outputFileSync } = require('fs-extra'); - -const outDir = resolve(__dirname, '../css'); -const pfStylesDir = dirname(require.resolve('@patternfly/patternfly/patternfly.css')); - -const cssFiles = glob.sync('**/*.css', { - cwd: pfStylesDir, - ignore: ['assets/**', '*ie11*.css', '*.css'] -}); - -/* Copy @patternfly/patternfly styles */ -cssFiles.forEach(filePath => { - const absFilePath = resolve(pfStylesDir, filePath); - const cssContent = readFileSync(absFilePath, 'utf8'); - const cssOutputPath = getCSSOutputPath(outDir, filePath); - const newClass = cssToJSNew(cssContent, `./${basename(cssOutputPath)}`); - - outputFileSync(cssOutputPath, cssContent); - outputFileSync(cssOutputPath.replace('.css', '.ts'), newClass); -}); - -/* Copy inline styles in the src/css folder */ -const inlineCssFiles = glob.sync('src/css/**/*.css'); - -inlineCssFiles.forEach(filePath => { - const absFilePath = resolve(filePath); - const cssContent = readFileSync(absFilePath, 'utf8'); - const cssOutputPath = getCSSOutputPath(outDir, filePath).replace('src/css/', ''); - const newClass = cssToJSNew(cssContent, `./${basename(cssOutputPath)}`); - - outputFileSync(cssOutputPath, cssContent); - outputFileSync(cssOutputPath.replace('.css', '.ts'), newClass); -}); - -/** - * @param {string} cssString - CSS string - * @param {string} cssOutputPath - Path string - */ -function cssToJSNew(cssString, cssOutputPath = '') { - const cssClasses = getCSSClasses(cssString); - // eslint-disable-next-line no-undef - const distinctValues = [...new Set(cssClasses)]; - const classDeclaration = []; - const modifiersDeclaration = []; - - distinctValues.forEach(className => { - const key = formatClassName(className); - const cleanClass = className.replace('.', '').trim(); - if (isModifier(className)) { - modifiersDeclaration.push(`'${key}': '${cleanClass}'`); - } else { - classDeclaration.push(`${key}: '${cleanClass}'`); - } - }); - const classSection = classDeclaration.length > 0 ? `${classDeclaration.join(',\n ')},` : ''; - - return `import '${cssOutputPath}'; - -export default { - ${classSection} - modifiers: { - ${modifiersDeclaration.join(',\n ')} - } -}`; -} - -/** - * @param {string} cssString - CSS string - */ -function getCSSClasses(cssString) { - return cssString.match(/(\.)(?!\d)([^\s\.,{\[>+~#:)]*)(?![^{]*})/g); //eslint-disable-line -} - -/** - * @param {string} className - Class name - */ -function formatClassName(className) { - return camelcase(className.replace(/pf-((c|l|m|u|is|has)-)?/g, '')); -} - -/** - * @param {string} className - Class name - */ -function isModifier(className) { - return Boolean(className && className.startsWith) && className.startsWith('.pf-m-'); -} - -/** - * @param {any} absFilePath - Absolute file path - * @param {any} pathToCSSFile - Path to CSS file - */ -function getCSSOutputPath(absFilePath, pathToCSSFile) { - return join(absFilePath, getFormattedCSSOutputPath(pathToCSSFile)); -} - -/** - * @param {any} pathToCSSFile - Path to CSS file - */ -function getFormattedCSSOutputPath(pathToCSSFile) { - const { dir, name } = parse(pathToCSSFile); - let formattedDir = dir; - const nodeText = 'node_modules'; - const nodeIndex = formattedDir.lastIndexOf(nodeText); - if (nodeIndex !== -1) { - formattedDir = formattedDir.substring(nodeIndex + nodeText.length); - } - return join(formattedDir, `${name}.css`); -} diff --git a/packages/react-styles/src/removeTS.js b/packages/react-styles/src/removeTS.js deleted file mode 100644 index c6a1468a7e5..00000000000 --- a/packages/react-styles/src/removeTS.js +++ /dev/null @@ -1,4 +0,0 @@ -const glob = require('glob'); -const fs = require('fs'); - -glob.sync('css/**/*.ts', { ignore: ['**/*.d.ts'] }).forEach(file => fs.unlinkSync(file)); diff --git a/packages/react-styles/tsconfig.json b/packages/react-styles/tsconfig.json deleted file mode 100644 index 367e832ddc9..00000000000 --- a/packages/react-styles/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "compilerOptions": { - "baseUrl": ".", - "module": "commonjs", - "moduleResolution": "node", - "skipLibCheck": true, - "strict": true, - "target": "es5", - "declaration": true - }, - "include": [ - "./css/*", - "./css/**/*" - ] -} \ No newline at end of file diff --git a/packages/react-tokens/scripts/writeTokens.js b/packages/react-tokens/scripts/writeTokens.js index c214c233fbd..51b7a942cc4 100644 --- a/packages/react-tokens/scripts/writeTokens.js +++ b/packages/react-tokens/scripts/writeTokens.js @@ -82,6 +82,9 @@ exports.__esModule = true; ${index.map(file => `__export(require('./${file}'));`).join('\n')} `.trim() ); + + // eslint-disable-next-line no-console + console.log('Wrote', index.length * 3 + 3, 'token files'); } writeTokens(generateTokens()); diff --git a/scripts/incrementalBuild.js b/scripts/incrementalBuild.js index 893f8337dd4..f221e14567a 100644 --- a/scripts/incrementalBuild.js +++ b/scripts/incrementalBuild.js @@ -22,6 +22,8 @@ const getSrcDirs = packageName => { return ['src', 'sass']; case '@patternfly/react-tokens': return ['scripts']; + case '@patternfly/react-styles': + return ['src', 'scripts']; default: return ['src']; }