From e12fb94ce439d5970b481755c1873d4bc0faeee7 Mon Sep 17 00:00:00 2001 From: Thivi Date: Wed, 22 Mar 2023 17:50:01 +0530 Subject: [PATCH] chore(react): allow components to be imported through subpath chore(react): add mui libraries to dev dependencyies fix(react): fix incorrect export from Footer chore(react): add scripts folder to eslint ignore chore(react): update build scripts fix(react): fix test snapshots chore(react): add package.json files to other folders in the dist folder fix(react): fix merge conflicts chore(react): write custom logic to externalize peer dependencies --- packages/react/.eslintignore | 1 + packages/react/.npmrc | 1 + packages/react/package.json | 25 +- packages/react/rollup.config.cjs | 55 +- packages/react/scripts/build-components.js | 80 ++ packages/react/scripts/fix-files.js | 146 ++++ .../__snapshots__/Badge.test.tsx.snap | 4 +- .../__snapshots__/Breadcrumbs.test.tsx.snap | 2 +- .../CircularProgressAvatar.test.tsx.snap | 4 +- packages/react/src/components/Footer/index.ts | 2 +- .../__snapshots__/Input.test.tsx.snap | 2 +- .../__snapshots__/Link.test.tsx.snap | 2 +- .../__snapshots__/SignIn.test.tsx.snap | 2 +- packages/react/tsconfig.lib.json | 4 +- pnpm-lock.yaml | 813 ++++++++++++++---- 15 files changed, 953 insertions(+), 190 deletions(-) create mode 100644 packages/react/.npmrc create mode 100644 packages/react/scripts/build-components.js create mode 100644 packages/react/scripts/fix-files.js diff --git a/packages/react/.eslintignore b/packages/react/.eslintignore index 0cb9c6f8..a338ecac 100644 --- a/packages/react/.eslintignore +++ b/packages/react/.eslintignore @@ -1,3 +1,4 @@ dist .rollup.cache storybook-static +scripts diff --git a/packages/react/.npmrc b/packages/react/.npmrc new file mode 100644 index 00000000..6c59086d --- /dev/null +++ b/packages/react/.npmrc @@ -0,0 +1 @@ +enable-pre-post-scripts=true diff --git a/packages/react/package.json b/packages/react/package.json index 284b1608..04a25d1e 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -29,6 +29,8 @@ "scripts": { "build": "rollup -c", "build-storybook": "build-storybook --no-manager-cache", + "prebuild": "rimraf dist", + "postbuild": "node scripts/build-components.js", "lint": "pnpm lint:es && pnpm lint:styles", "lint:es": "eslint . --ext .js,.jsx,.ts,.tsx", "lint:styles": "stylelint 'src/**/*.{css,scss,sass}' --config stylelint.config.cjs --allow-empty-input", @@ -38,13 +40,6 @@ "typecheck": "tsc --noemit -p tsconfig.json" }, "dependencies": { - "@emotion/react": "^11.10.5", - "@emotion/styled": "^11.10.5", - "@mui/icons-material": "^5.10.16", - "@mui/lab": "5.0.0-alpha.110", - "@mui/material": "^5.10.16", - "@mui/system": "^5.10.16", - "@mui/utils": "^5.10.16", "@oxygen-ui/primitives": "*", "@oxygen-ui/react-icons": "*", "clsx": "^1.2.1", @@ -52,6 +47,13 @@ }, "devDependencies": { "@babel/core": "^7.20.2", + "@emotion/react": "^11.10.5", + "@emotion/styled": "^11.10.5", + "@mui/icons-material": "^5.10.16", + "@mui/lab": "5.0.0-alpha.110", + "@mui/material": "^5.10.16", + "@mui/system": "^5.10.16", + "@mui/utils": "^5.10.16", "@oxygen-ui/logger": "*", "@rollup/plugin-commonjs": "^23.0.3", "@rollup/plugin-image": "^3.0.1", @@ -85,8 +87,10 @@ "babel-loader": "^8.3.0", "eslint": "8.25.0", "eslint-plugin-mdx": "^2.0.5", + "fs-extra": "^11.1.0", "jest": "29.0.3", "jest-environment-jsdom": "^29.4.1", + "node-sass": "^8.0.0", "postcss": "8.4.16", "prettier": "^2.8.0", "react": "^18.2.0", @@ -109,6 +113,13 @@ "typescript": "^4.9.3" }, "peerDependencies": { + "@emotion/react": "^11.10.5", + "@emotion/styled": "^11.10.5", + "@mui/icons-material": "^5.10.16", + "@mui/lab": "5.0.0-alpha.110", + "@mui/material": "^5.10.16", + "@mui/system": "^5.10.16", + "@mui/utils": "^5.10.16", "react": ">=18.0.0", "react-dom": ">=18.0.0", "typescript": ">=4.0.0" diff --git a/packages/react/rollup.config.cjs b/packages/react/rollup.config.cjs index 86b5f02f..902070e6 100644 --- a/packages/react/rollup.config.cjs +++ b/packages/react/rollup.config.cjs @@ -22,21 +22,43 @@ const typescript = require('@rollup/plugin-typescript'); const dts = require('rollup-plugin-dts'); const postcss = require('rollup-plugin-postcss'); const {terser} = require('rollup-plugin-terser'); -const peerDepsExternal = require('rollup-plugin-peer-deps-external'); const image = require('@rollup/plugin-image'); const pkg = require('./package.json'); +/** + * This method returns a method hat takes the id of a module as an argument and returns true if it should not be + * included in the final bundle and returns false if it should be. + * + * @param {boolean} includeMaterialDeps - True if Material dependencies should be + * included in the bundle; False otherwise. + * @returns A method that takes the id of the module as an argument + */ +const shouldExclude = (includeMaterialDeps = false) => { + const peerDeps = includeMaterialDeps + ? Object.keys(pkg.peerDependencies || {}).filter(id => { + return ( + !id.startsWith('@emotion/react') && + !id.startsWith('@emotion/styled') && + !id.startsWith('@mui/icons-material') && + !id.startsWith('@mui/lab') && + !id.startsWith('@mui/material') && + !id.startsWith('@mui/system') && + !id.startsWith('@mui/utils') + ); + }) + : Object.keys(pkg.peerDependencies || {}); + + const regExps = peerDeps.map(dep => new RegExp(`^${dep}(\\/\.+)*$`)); + return id => regExps.some(regExp => regExp.test(id)); +}; + module.exports = [ { cache: false, input: 'src/index.ts', + external: shouldExclude(), output: [ - { - file: pkg.main, - format: 'cjs', - sourcemap: true, - }, { file: pkg.module, format: 'esm', @@ -44,7 +66,6 @@ module.exports = [ }, ], plugins: [ - peerDepsExternal(), resolve(), commonjs(), typescript({tsconfig: './tsconfig.lib.json'}), @@ -53,6 +74,26 @@ module.exports = [ image(), ], }, + { + cache: false, + input: 'src/index.ts', + external: shouldExclude(true), + output: [ + { + file: pkg.main, + format: 'cjs', + sourcemap: true, + }, + ], + plugins: [ + resolve(), + commonjs(), + typescript({ tsconfig: './tsconfig.lib.json' }), + postcss(), + terser(), + image(), + ], + }, { cache: false, external: [/\.s?css$/], diff --git a/packages/react/scripts/build-components.js b/packages/react/scripts/build-components.js new file mode 100644 index 00000000..78d02e64 --- /dev/null +++ b/packages/react/scripts/build-components.js @@ -0,0 +1,80 @@ +/** + * Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com). All Rights Reserved. + * + * WSO2 LLC. licenses this file to you 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 path = require('path'); +const fs = require('fs-extra'); +const {execSync} = require('child_process'); +const packageJson = require('../package.json'); +const fixFiles = require('./fix-files'); +const {logger} = require('@oxygen-ui/logger'); + +const outputDir = path.resolve(__dirname, '..', 'dist', 'transpiled'); + +logger.log('💅 Transpiling components...'); +execSync( + `npx tsc --project tsconfig.lib.json --declarationDir ${outputDir} --emitDeclarationOnly false --outDir ${outputDir}`, +); +logger.log('💅 Components transpiled! 🎉🎉🎉'); + +logger.log('💅 Copying components...'); +fs.rmSync(path.resolve(__dirname, '..', 'dist','transpiled','src','components','index.js')); +fs.rmSync(path.resolve(__dirname, '..', 'dist', 'transpiled', 'src', 'components', 'index.d.ts')); +fs.copySync(path.resolve(__dirname, '..', 'dist', 'transpiled', 'src', 'components', ''), path.resolve(__dirname, '..', 'dist')); +fs.rmdirSync(path.resolve(__dirname, '..', 'dist', 'transpiled', 'src', 'components'), {recursive: true}); +fs.rmSync(path.resolve(__dirname, '..', 'dist', 'transpiled', 'src', 'index.js')); +fs.rmSync(path.resolve(__dirname, '..', 'dist', 'transpiled', 'src', 'index.d.ts')); +fs.copySync(path.resolve(__dirname, '..', 'dist', 'transpiled', 'src'), path.resolve(__dirname, '..', 'dist', '')); +fs.rmdirSync(path.resolve(__dirname, '..', 'dist', 'transpiled'), {recursive: true}); +logger.log('💅 Components copied! 🎉🎉🎉'); + +const packageJsonContent = { + exports: './index.js', + types: './index.d.ts', +}; + +const componentsDir = path.resolve(__dirname, '..', 'src', 'components'); +const componentFiles = fs.readdirSync(componentsDir); +const srcDir = path.resolve(__dirname, '..', 'src'); +const srcFiles = fs.readdirSync(srcDir); + +logger.log('💅 Creating package.json files...'); +componentFiles.forEach(file => { + const componentDir = path.resolve(componentsDir, file); + if (fs.lstatSync(componentDir).isDirectory()) { + fs.writeFileSync(path.resolve(__dirname, '..', 'dist', file, 'package.json'), JSON.stringify(packageJsonContent)); + } +}); + +srcFiles.forEach(file => { + const dir = path.resolve(srcDir, file); + if (fs.lstatSync(dir).isDirectory() && file !== 'components') { + fs.writeFileSync(path.resolve(__dirname, '..', 'dist', file, 'package.json'), JSON.stringify(packageJsonContent)); + } +}); +logger.log('💅 package.json files created! 🎉🎉🎉'); + +logger.log('💅 Creating root package.json file...'); +packageJson.main = './cjs/index.js'; +packageJson.module = './esm/index.js'; +packageJson.types = './index.d.ts'; +fs.writeFileSync(path.resolve(__dirname, '..', 'dist', 'package.json'), JSON.stringify(packageJson)); +logger.log('💅 Root package.json file created! 🎉🎉🎉'); + +logger.log('💅 Fixing the imports in the components and injecting CSS...'); +fixFiles(); +logger.log('💅 Imports fixed and CSS injected into components! 🎉🎉🎉'); diff --git a/packages/react/scripts/fix-files.js b/packages/react/scripts/fix-files.js new file mode 100644 index 00000000..b5c6d9e7 --- /dev/null +++ b/packages/react/scripts/fix-files.js @@ -0,0 +1,146 @@ +/** + * Copyright (c) 2023, WSO2 LLC. (https://www.wso2.com). All Rights Reserved. + * + * WSO2 LLC. licenses this file to you 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 fs = require('fs'); +const path = require('path'); +const saas = require('node-sass'); +const {logger} = require('@oxygen-ui/logger'); + +module.exports = fixFiles = () => { + //All files and directories in the src/components folder. + const componentFiles = fs.readdirSync(path.resolve(__dirname, '..', 'src', 'components')); + + // All directories in the src/components folder. + const componentDirs = componentFiles.filter(file => { + const componentDir = path.resolve(__dirname, '..', 'src', 'components', file); + + return fs.lstatSync(componentDir).isDirectory(); + }); + + // The directories containing the files bundled by Rollup. + const moduleDirs = ['cjs', 'esm']; + + // The non-component directories in the dist folder. + const nonComponentDirsInDist = fs.readdirSync(path.resolve(__dirname, '..', 'dist')).filter(file => { + return ( + !componentDirs.includes(file) && + fs.lstatSync(path.resolve(__dirname, '..', 'dist', file)).isDirectory() && + !moduleDirs.includes(file) + ); + }); + + /** + * This method fixes the references to the non-component directories in the dist folder such as utils, constants, etc. + * in imports. + * + * @param {string} file - The file to be fixed. + * @returns {string} - The fixed file. + */ + const fixImportStatements = file => { + const regExpString = `(import.*from.*[\"|'])(.*(${nonComponentDirsInDist.join('|')}).*)([\"|'].*)`; + const regExp = new RegExp(regExpString, 'g'); + + return file.replace(regExp, (_fullMatch, groupOne, groupTwo, _groupThree, groupFour) => { + return `${groupOne}${groupTwo.replace('../', '')}${groupFour}`; + }); + }; + + /** + * This method updates imports and injects css into component files. + * + * @param {string} filePath - The path to the file to be fixed. + */ + const fixFiles = filePath => { + fs.readFile(filePath, 'utf8', (err, data) => { + if (err) { + logger.error(err); + + return; + } + + const fixedImports = fixImportStatements(data); + const fixedCSS = loadCSS(fixedImports, filePath); + + fs.writeFile(filePath, fixedCSS, 'utf8', err => { + if (err) { + logger.error(err); + } + }); + }); + }; + + // The JS code that injects CSS into the head tag. + const codeToInjectCSSIntoHeadTag = + "function addCSS(css){var head = document.getElementsByTagName('head')[ 0 ];" + + "var s = document.createElement('style');s.setAttribute('type', 'text/css');" + + 's.appendChild(document.createTextNode(css));head.appendChild(s);}'; + + /** + * This method injects teh `addCSS` function into the file passed as the argument along with the, compiles the + * SASS code of corresponding to the component into CSS, and calls the `addCSS` method with the CSS code as the + * argument. + * + * @param {string} file - The file to be injected with the `addCSS` function. + * @param {string} filePath - The path to the file. + * @returns {string} file - The file with the `addCSS` function injected. + */ + const loadCSS = (file, filePath) => { + const componentDir = filePath.split(path.sep).slice(-2)[0]; + let css = ''; + + file.match(/['|"].*\.scss["|']/g)?.forEach(match => { + const sassFilePath = path.resolve(__dirname, '..', 'src', 'components', componentDir, match.replace(/['|"]/g, '')); + css += saas.renderSync({file: sassFilePath}).css.toString(); + }); + + file = file.replace(/import.*['|"].*\.scss["|'].*/g, ''); + + if (css === '' || path.extname(filePath) === '.ts') { + return file; + } + + file += `${codeToInjectCSSIntoHeadTag}\naddCSS(\'${JSON.stringify(css)}\');`; + + return file; + }; + + /** + * Traverses through the directories and fixes imports and injects CSS. + * + * @param {string} directory - The directory to be parsed. + */ + parseComponents = (directory) => { + fs.readdirSync(directory).forEach(file => { + const absolutePath = path.resolve(directory, file); + if (![...nonComponentDirsInDist, ...moduleDirs].includes(file)) { + if (fs.statSync(absolutePath).isDirectory()) { + parseComponents(absolutePath); + } else { + if ( + (file.endsWith('.js') || file.endsWith('.ts')) && + componentDirs.includes(directory.split(path.sep).slice(-1)[0]) + ) { + fixFiles(absolutePath); + } + } + } + }); + }; + + parseComponents(path.resolve(__dirname, '..', 'dist')); +}; diff --git a/packages/react/src/components/Badge/__tests__/__snapshots__/Badge.test.tsx.snap b/packages/react/src/components/Badge/__tests__/__snapshots__/Badge.test.tsx.snap index 8bfcd7d9..a1330670 100644 --- a/packages/react/src/components/Badge/__tests__/__snapshots__/Badge.test.tsx.snap +++ b/packages/react/src/components/Badge/__tests__/__snapshots__/Badge.test.tsx.snap @@ -4,10 +4,10 @@ exports[`Badge should match the snapshot 1`] = `
diff --git a/packages/react/src/components/Breadcrumbs/__tests__/__snapshots__/Breadcrumbs.test.tsx.snap b/packages/react/src/components/Breadcrumbs/__tests__/__snapshots__/Breadcrumbs.test.tsx.snap index 68ec2711..60befa0a 100644 --- a/packages/react/src/components/Breadcrumbs/__tests__/__snapshots__/Breadcrumbs.test.tsx.snap +++ b/packages/react/src/components/Breadcrumbs/__tests__/__snapshots__/Breadcrumbs.test.tsx.snap @@ -14,7 +14,7 @@ exports[`Breadcrumbs should match the snapshot 1`] = ` class="MuiBreadcrumbs-li" > Home diff --git a/packages/react/src/components/CircularProgressAvatar/__tests__/__snapshots__/CircularProgressAvatar.test.tsx.snap b/packages/react/src/components/CircularProgressAvatar/__tests__/__snapshots__/CircularProgressAvatar.test.tsx.snap index b1ea055f..2719003a 100644 --- a/packages/react/src/components/CircularProgressAvatar/__tests__/__snapshots__/CircularProgressAvatar.test.tsx.snap +++ b/packages/react/src/components/CircularProgressAvatar/__tests__/__snapshots__/CircularProgressAvatar.test.tsx.snap @@ -8,7 +8,7 @@ exports[`CircularProgressAvatar should match the snapshot 1`] = ` role="presentation" >
diff --git a/packages/react/src/components/Footer/index.ts b/packages/react/src/components/Footer/index.ts index b2e1128d..e602745c 100644 --- a/packages/react/src/components/Footer/index.ts +++ b/packages/react/src/components/Footer/index.ts @@ -17,4 +17,4 @@ */ export {default} from './Footer'; -export type {NavbarProps} from './Footer'; +export type {FooterProps} from './Footer'; diff --git a/packages/react/src/components/Input/__test__/__snapshots__/Input.test.tsx.snap b/packages/react/src/components/Input/__test__/__snapshots__/Input.test.tsx.snap index 6fc6b6dd..b29fc555 100644 --- a/packages/react/src/components/Input/__test__/__snapshots__/Input.test.tsx.snap +++ b/packages/react/src/components/Input/__test__/__snapshots__/Input.test.tsx.snap @@ -4,7 +4,7 @@ exports[`Input should match the snapshot 1`] = `
diff --git a/packages/react/src/components/SignIn/__tests__/__snapshots__/SignIn.test.tsx.snap b/packages/react/src/components/SignIn/__tests__/__snapshots__/SignIn.test.tsx.snap index ddc919fa..0ce8f454 100644 --- a/packages/react/src/components/SignIn/__tests__/__snapshots__/SignIn.test.tsx.snap +++ b/packages/react/src/components/SignIn/__tests__/__snapshots__/SignIn.test.tsx.snap @@ -182,7 +182,7 @@ exports[`SignIn should match the snapshot 1`] = ` class="MuiGrid-root MuiGrid-item css-13i4rnv-MuiGrid-root" >