diff --git a/packages/styled/babel-plugin/.babelrc b/packages/styled/babel-plugin/.babelrc new file mode 100644 index 0000000000..aaf733d3d8 --- /dev/null +++ b/packages/styled/babel-plugin/.babelrc @@ -0,0 +1,6 @@ +{ + "presets": [ + "module:metro-react-native-babel-preset" + ] + // "plugins": ["transform-remove-console"] +} \ No newline at end of file diff --git a/packages/styled/babel-plugin/.gitignore b/packages/styled/babel-plugin/.gitignore new file mode 100644 index 0000000000..3d3e2719a3 --- /dev/null +++ b/packages/styled/babel-plugin/.gitignore @@ -0,0 +1,28 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +node_modules +.pnp +.pnp.js + +# misc +.DS_Store +*.pem + +# build +dist +lib + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env.local +.env.development.local +.env.test.local +.env.production.local + +# turbo +.turbo diff --git a/packages/styled/babel-plugin/.nvmrc b/packages/styled/babel-plugin/.nvmrc new file mode 100644 index 0000000000..5dbac1ed0f --- /dev/null +++ b/packages/styled/babel-plugin/.nvmrc @@ -0,0 +1 @@ +v16.13.0 \ No newline at end of file diff --git a/packages/styled/babel-plugin/CHANGELOG.md b/packages/styled/babel-plugin/CHANGELOG.md new file mode 100644 index 0000000000..b729b8411a --- /dev/null +++ b/packages/styled/babel-plugin/CHANGELOG.md @@ -0,0 +1,29 @@ +# @gluestack-style/babel-plugin-styled-resolver + +## 1.0.2 + +### Patch Changes + +- Added dynamic config resolution [PR](https://github.com/gluestack/gluestack-style/pull/550) + +## 1.0.1 + +### Features + +- Added utility props resolution [PR](https://github.com/gluestack/gluestack-style/pull/519) + +## 0.1.14 + +### Patch Changes + +- Fixes + + - Support for `createConfig` API. + +## 0.1.0 + +## Features + +- Add path based resolution +- Add library name alias +- Add support for styled function aliasing diff --git a/packages/styled/babel-plugin/README.md b/packages/styled/babel-plugin/README.md new file mode 100644 index 0000000000..b1ab4b48aa --- /dev/null +++ b/packages/styled/babel-plugin/README.md @@ -0,0 +1,35 @@ +# @gluestack-style/babel-plugin-styled-resolver + +## Installation + +To use `@gluestack-style/babel-plugin-styled-resolver`, all you need to do is install the +`@gluestack-style/babel-plugin-styled-resolver` package: + +```sh +$ yarn add @gluestack-style/babel-plugin-styled-resolver + +# or + +$ npm i @gluestack-style/babel-plugin-styled-resolver +``` + +## Usage + +Add Babel plugin to your app `babel.config.js`. + +```jsx +const path = require('path'); +const gluestackStyleResolver = require('@gluestack-style/babel-plugin-styled-resolver'); +module.exports = function (api) { + api.cache(true); + return { + presets: ['babel-preset-expo'], + plugins: [gluestackStyleResolver], + }; +}; +``` + +Just make sure your babel.config.js and gluestack-style.config.js/ts are in the same directory. We suggest you keep both of them at the root of your app codebase. + +More guides on how to get started are available +[here](https://gluestack.io/style). diff --git a/packages/styled/babel-plugin/package.json b/packages/styled/babel-plugin/package.json new file mode 100644 index 0000000000..e0185fb2d2 --- /dev/null +++ b/packages/styled/babel-plugin/package.json @@ -0,0 +1,69 @@ +{ + "name": "@gluestack-style/extract-styles", + "version": "0.0.1", + "description": "A gluestack-style babel plugin that transpiles your styled function calls and resolves the component styling in build time.", + "keywords": [ + "css-in-js", + "babel-plugin", + "server-side rendering", + "ssr" + ], + "homepage": "https://github.com/gluestack/gluestack-ui/tree/main/packages/styled/babel-plugin-styled-resolver#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/gluestack/gluestack-ui.git" + }, + "main": "lib/commonjs/index", + "types": "lib/typescript/index.d.ts", + "module": "lib/module/index", + "react-native": "src/index", + "source": "src/index", + "typings": "lib/typescript/index.d.ts", + "scripts": { + "prepare": "bob build", + "release": "release-it", + "build": "bob build", + "clean": "rm -rf lib", + "watch": "npx nodemon --watch src/index.js --watch src/extract-styles.js --watch src/extract-config.ts --exec 'yarn build'" + }, + "peerDependencies": { + "@gluestack-style/react": ">=1.0" + }, + "devDependencies": { + "@babel/cli": "^7.19.3", + "@babel/preset-env": "^7.20.2", + "@types/lodash.merge": "^4.6.7", + "babel-plugin-transform-remove-console": "^6.9.4", + "react-native-builder-bob": "^0.20.1", + "tsconfig": "*", + "typescript": "^4.7.4" + }, + "dependencies": { + "@babel/core": "^7.23.5", + "@babel/generator": "^7.20.5", + "@babel/parser": "^7.20.5", + "@babel/plugin-transform-typescript": "^7.20.2", + "@babel/preset-typescript": "^7.18.6", + "@babel/traverse": "^7.20.5", + "@gluestack-style/build-config": "*", + "lodash.merge": "^4.6.2" + }, + "react-native-builder-bob": { + "source": "src", + "output": "lib", + "targets": [ + "commonjs", + "typescript", + [ + "module", + { + "babelrc": true + } + ] + ] + }, + "files": [ + "lib/", + "src/" + ] +} diff --git a/packages/styled/babel-plugin/src/extract-config.ts b/packages/styled/babel-plugin/src/extract-config.ts new file mode 100644 index 0000000000..292cac2775 --- /dev/null +++ b/packages/styled/babel-plugin/src/extract-config.ts @@ -0,0 +1,94 @@ +import { + convertTokensToCssVariables, + platformSpecificSpaceUnits, + //@ts-ignore +} from '@gluestack-style/react/lib/commonjs/utils'; +//@ts-ignore +import { resolveThemes } from '@gluestack-style/react/lib/commonjs/createConfig'; +//@ts-ignore +import { updateOrderUnResolvedMap } from '@gluestack-style/react/lib/commonjs/updateOrderUnResolvedMap'; +import { + convertStyledToStyledVerbosed, + //@ts-ignore +} from '@gluestack-style/react/lib/commonjs/convertSxToSxVerbosed'; +const { + stableHash, +} = require('@gluestack-style/react/lib/commonjs/stableHash'); +//@ts-ignore +import { StyleInjector } from '@gluestack-style/react/lib/commonjs/style-sheet'; +//@ts-ignore +import { propertyTokenMap } from '@gluestack-style/react/lib/commonjs/propertyTokenMap'; + +function extractGlobalStyles(CONFIG: any = {}, theme: any = {}) { + const GluestackStyleSheet = new StyleInjector(); + const verbosedTheme = convertStyledToStyledVerbosed(theme); + + const globalStyleHash = stableHash({ + ...theme, + }); + + const { styledIds } = updateOrderUnResolvedMap( + verbosedTheme, + globalStyleHash, + 'global', + {}, + GluestackStyleSheet, + 'web' + ); + + const toBeInjected = GluestackStyleSheet.resolve(styledIds, CONFIG, { + propertyTokenMap, + }); + + const current_global_map = GluestackStyleSheet.getStyleMap(); + + const orderedResolvedTheme = []; + + current_global_map?.forEach((styledResolved: any) => { + if (styledIds.includes(styledResolved?.meta?.cssId)) { + orderedResolvedTheme.push(styledResolved); + } + }); + + return toBeInjected; +} + +export function extractGluestackConfig(gluestackConfig: any) { + let configWithPlatformSpecificUnits: any = platformSpecificSpaceUnits( + { ...gluestackConfig }, + 'web' + ); + + if (gluestackConfig?.themes) { + Object.keys(gluestackConfig.themes).forEach((key) => { + configWithPlatformSpecificUnits.themes[key] = platformSpecificSpaceUnits( + //@ts-ignore + { tokens: gluestackConfig.themes[key] }, + 'web' + )?.tokens; + }); + } + + configWithPlatformSpecificUnits = resolveThemes( + configWithPlatformSpecificUnits + ); + + let cssVariablesConovertedTokens = convertTokensToCssVariables( + configWithPlatformSpecificUnits + )?.trim('\n'); + + cssVariablesConovertedTokens += '\n'; + + const globalStylesCSS = extractGlobalStyles( + configWithPlatformSpecificUnits, + configWithPlatformSpecificUnits?.globalStyle + ); + + Object.keys(globalStylesCSS).forEach((type) => { + globalStylesCSS[type].forEach(({ cssRuleset }: any) => { + cssVariablesConovertedTokens += `${cssRuleset}\n`; + }); + }); + + return cssVariablesConovertedTokens; +} diff --git a/packages/styled/babel-plugin/src/extract-styles.js b/packages/styled/babel-plugin/src/extract-styles.js new file mode 100644 index 0000000000..4203d72d4f --- /dev/null +++ b/packages/styled/babel-plugin/src/extract-styles.js @@ -0,0 +1,1362 @@ +const fs = require('fs'); +const path = require('path'); +const babel = require('@babel/parser'); +const generate = require('@babel/generator').default; +const babelPresetTypeScript = require('@babel/preset-typescript'); +const traverse = require('@babel/traverse').default; +const types = require('@babel/types'); + +const { + convertStyledToStyledVerbosed, + convertSxToSxVerbosed, +} = require('@gluestack-style/react/lib/commonjs/convertSxToSxVerbosed'); +const { + propertyTokenMap, +} = require('@gluestack-style/react/lib/commonjs/propertyTokenMap'); +const { + stableHash, +} = require('@gluestack-style/react/lib/commonjs/stableHash'); +const { + CSSPropertiesMap, + reservedKeys, +} = require('@gluestack-style/react/lib/commonjs/core/styled-system'); +const { + StyleInjector, +} = require('@gluestack-style/react/lib/commonjs/style-sheet/index'); +const { + updateOrderUnResolvedMap, +} = require('@gluestack-style/react/lib/commonjs/updateOrderUnResolvedMap'); +const { deepMerge } = require('@gluestack-style/react/lib/commonjs/utils'); +const { + setObjectKeyValue, +} = require('@gluestack-style/react/lib/commonjs/core/utils'); +const { + checkAndReturnUtilityProp, +} = require('@gluestack-style/react/lib/commonjs/core/convert-utility-to-sx'); +const { + generateMergedThemeTokens, + resolvePlatformTheme, +} = require('@gluestack-style/react/lib/commonjs/utils'); + +const BUILD_TIME_GLUESTACK_STYLESHEET = new StyleInjector(); +let configThemePath = []; +let ConfigDefault = {}; + +const convertExpressionContainerToStaticObject = ( + properties, + result = {}, + keyPath = [], + propsToBePersist = {} +) => { + try { + properties?.forEach((property, index) => { + const nodeName = property.key.name ?? property.key.value; + if (property.value.type === 'ObjectExpression') { + keyPath.push(nodeName); + convertExpressionContainerToStaticObject( + property.value.properties, + result, + keyPath, + propsToBePersist + ); + keyPath.pop(); + } else if (property.value.type === 'Identifier') { + if (property.key.value) { + setObjectKeyValue( + propsToBePersist, + [...keyPath, nodeName], + property.value.name + ); + } + if (property.key.name) { + setObjectKeyValue( + propsToBePersist, + [...keyPath, nodeName], + property.value.name + ); + } + } else { + if (property.key.value) { + setObjectKeyValue( + result, + [...keyPath, property.key.value], + property.value.value + ); + } + + if (property.key.name) { + setObjectKeyValue( + result, + [...keyPath, property.key.name], + property.value.value + ); + } + } + }); + return { + result, + propsToBePersist, + }; + } catch (err) { + throw new Error( + `Error while converting expression container to static object. Error: ${err.message} Stack: ${err.stack}.` + ); + } +}; + +function findThemeAndComponentConfig(node) { + let themeNode = null; + let componentConfigNode = null; + node.forEach((prop) => { + const propKey = prop.key.name ? prop.key.name : prop.key.value; + if (propKey === 'theme') { + themeNode = prop; + } else if (propKey === 'componentConfig') { + componentConfigNode = prop; + } + }); + + return { + themeNode, + componentConfigNode, + }; +} + +function addQuotesToObjectKeys(code) { + const ast = babel.parse(`var a = ${code}`, { + presets: [babelPresetTypeScript], + plugins: ['typescript'], + sourceType: 'module', + }); + + traverse(ast, { + ObjectProperty: (objectPropertyPath) => { + if (types.isTemplateLiteral(objectPropertyPath.node.value)) { + objectPropertyPath.node.value = types.stringLiteral( + objectPropertyPath.node.value.quasis[0].value.raw + ); + } + if (types.isIdentifier(objectPropertyPath.node.key)) { + objectPropertyPath.node.key = types.stringLiteral( + objectPropertyPath.node.key.name + ); + } + if (types.isNumericLiteral(objectPropertyPath.node.key)) { + objectPropertyPath.node.key = types.stringLiteral( + objectPropertyPath.node.key.extra.raw + ); + } + if (types.isStringLiteral(objectPropertyPath.node.value)) { + objectPropertyPath.node.value = types.stringLiteral( + objectPropertyPath.node.value.value + ); + } + }, + }); + + let initAst; + + traverse(ast, { + ObjectProperty: (objectPropertyPath) => { + if (types.isArrayExpression(objectPropertyPath?.node?.value)) { + let arrayElements = objectPropertyPath.node.value.elements; + const dynamicElementsIndex = []; + arrayElements.forEach((element, index) => { + if ( + types.isNewExpression(element) || + types.isIdentifier(element) || + types.isTemplateLiteral(element) + ) { + dynamicElementsIndex.push(index); + } + }); + + arrayElements = arrayElements.filter( + (element, index) => !dynamicElementsIndex.includes(index) + ); + objectPropertyPath.node.value.elements = arrayElements; + } else if ( + types.isIdentifier(objectPropertyPath?.node?.value) || + types.isTemplateLiteral(objectPropertyPath?.node?.value) || + types.isConditionalExpression(objectPropertyPath?.node?.value) + ) { + objectPropertyPath.remove(); + } + }, + }); + + traverse(ast, { + VariableDeclarator: (variableDeclaratorPath) => { + initAst = variableDeclaratorPath.node.init; + }, + }); + + const { code: output } = generate(initAst, { + sourceType: 'module', + presets: [babelPresetTypeScript], + plugins: ['typescript'], + }); + + return output; +} +const merge = require('lodash.merge'); + +function isReservedKey(key, variantProps = []) { + return ( + Object.values(reservedKeys).some((obj) => obj.key === key) || + key === 'variants' || + key === 'compoundVariants' || + key === 'props' || + key === 'defaultProps' || + key.startsWith('_') || + variantProps.includes(key) + ); +} + +function getSkippedPluginProps( + theme, + variantProps = [], + keyPath = [], + skippedProps = {}, + themeProps = {} +) { + Object.keys(theme).forEach((themeKey) => { + if (typeof theme[themeKey] === 'object') { + if (isReservedKey(themeKey, variantProps)) { + keyPath.push(themeKey); + getSkippedPluginProps( + theme[themeKey], + variantProps, + keyPath, + skippedProps, + themeProps + ); + keyPath.pop(); + } else { + keyPath.push(themeKey); + setObjectKeyValue(skippedProps, keyPath, theme[themeKey]); + keyPath.pop(); + } + } else { + keyPath.push(themeKey); + setObjectKeyValue(themeProps, keyPath, theme[themeKey]); + keyPath.pop(); + } + }); + + return { themeProps, skippedProps }; +} + +// Function to find a property by key path +function findProperty(obj, keyPath) { + if (keyPath.length === 0) return obj; + + let key = keyPath[0]; + let prop = obj.properties.find((prop) => prop.key.value === key); + + if (prop && keyPath.length > 1) { + return findProperty(prop.value, keyPath.slice(1)); + } else { + return prop; + } +} + +// Function to merge properties of two objects +function mergeProperties(obj1, obj2, keyPath = []) { + obj2.properties.forEach((prop2) => { + let newKeyPath = [...keyPath, prop2.key.value]; + let prop1 = findProperty(obj1, newKeyPath); + + if (prop1) { + if ( + types.isObjectExpression(prop1.value) && + types.isObjectExpression(prop2.value) + ) { + // If both properties are objects, merge them recursively + mergeProperties(prop1.value, prop2.value, newKeyPath); + } else { + // If the properties are not objects, replace the property in obj1 with the property from obj2 + prop1.value = prop2.value; + } + } else { + // If the property does not exist in obj1, add it + let parentObj = findProperty(obj1, keyPath); + if (parentObj && parentObj.type === 'ObjectExpression') { + parentObj.properties.push( + types.objectProperty( + types.stringLiteral(prop2.key.value), + prop2.value + ) + ); + } else { + // Handle the case where the parent object doesn't exist + console.error( + `Parent object not found for key path: ${keyPath.join('.')}` + ); + } + } + }); +} + +// // Function to merge properties of two objects +// function mergeProperties(obj1, obj2) { +// obj2.properties.forEach((prop2) => { +// let prop1 = obj1.properties.find( +// (prop1) => prop1.key.value === prop2.key.value +// ); + +// if (prop1) { +// if ( +// types.isObjectExpression(prop1.value) && +// types.isObjectExpression(prop2.value) +// ) { +// // If both properties are objects, merge them recursively +// mergeProperties(prop1.value, prop2.value); +// } else { +// // If the properties are not objects, replace the property in obj1 with the property from obj2 +// prop1.value = prop2.value; +// } +// } else { +// // If the property does not exist in obj1, add it +// obj1.properties.push(prop2); +// } +// }); +// } + +function mergeASTs(ast1, obj) { + if (Object.keys(obj).length === 0) return ast1; + + let mergedAST = null; + + const ast2 = babel.parse(`var a = ${JSON.stringify(obj)}`, { + presets: [babelPresetTypeScript], + plugins: ['typescript'], + sourceType: 'module', + }); + + traverse(ast2, { + // For each ObjectExpression in the second AST + ObjectExpression(objectExpressionPath) { + // Merge the properties of the objects + mergeProperties(ast1, objectExpressionPath.node); + objectExpressionPath.stop(); + }, + }); + + const { code: output } = generate(ast1); + + const outputAST = babel.parse(`var a = ${output}`, { + presets: [babelPresetTypeScript], + plugins: ['typescript'], + sourceType: 'module', + }); + + traverse(outputAST, { + VariableDeclarator(variableDeclaratorPath) { + mergedAST = variableDeclaratorPath.node.init; + }, + }); + + return mergedAST; +} + +function getBuildTimeParams( + theme, + componentConfig, + extendedConfig, + outputLibrary, + platform, + type, + disableExtraction +) { + resolvePlatformTheme(theme, process.env.GLUESTACK_STYLE_TARGET); + const mergedPropertyConfig = { + ...ConfigDefault?.propertyTokenMap, + ...propertyTokenMap, + }; + const componentExtendedConfig = merge( + {}, + { + ...ConfigDefault, + propertyTokenMap: { ...mergedPropertyConfig }, + } + ); + + if (theme && Object.keys(theme).length > 0) { + let verbosedTheme = {}; + + const variantProps = []; + + Object.keys(theme?.variants ?? {}).forEach((key) => { + variantProps.push(key); + Object.keys(theme?.variants?.[key]).forEach((prop) => { + variantProps.push(prop); + }); + }); + + const { skippedProps, themeProps } = getSkippedPluginProps( + theme, + variantProps + ); + + verbosedTheme = convertStyledToStyledVerbosed(themeProps); + + let componentHash = stableHash({ + ...verbosedTheme, + ...componentConfig, + ...extendedConfig, + }); + + if (outputLibrary) { + componentHash = outputLibrary + '-' + componentHash; + } + + const { styledIds, verbosedStyleIds } = updateOrderUnResolvedMap( + verbosedTheme, + componentHash, + type, + componentConfig, + BUILD_TIME_GLUESTACK_STYLESHEET, + platform + ); + + const toBeInjected = BUILD_TIME_GLUESTACK_STYLESHEET.resolve( + styledIds, + componentExtendedConfig, + {} + ); + + const current_global_map = BUILD_TIME_GLUESTACK_STYLESHEET.getStyleMap(); + + const orderedResolvedTheme = []; + + current_global_map?.forEach((styledResolved) => { + if (styledIds.includes(styledResolved?.meta?.cssId)) { + orderedResolvedTheme.push(styledResolved); + } + }); + + const styleIdsAst = generateObjectAst(verbosedStyleIds); + + const toBeInjectedAst = generateObjectAst(toBeInjected); + + const orderedResolvedAst = generateArrayAst(orderedResolvedTheme); + + const orderedStyleIdsArrayAst = types.arrayExpression( + styledIds?.map((cssId) => types.stringLiteral(cssId)) + ); + + const resultParamsNode = types.objectExpression([ + types.objectProperty( + types.stringLiteral('orderedResolved'), + orderedResolvedAst + ), + types.objectProperty( + types.stringLiteral('disableExtraction'), + types.booleanLiteral(disableExtraction) + ), + types.objectProperty( + types.stringLiteral('toBeInjected'), + toBeInjectedAst + ), + types.objectProperty( + types.stringLiteral('styledIds'), + orderedStyleIdsArrayAst + ), + types.objectProperty( + types.stringLiteral('verbosedStyleIds'), + styleIdsAst + ), + ]); + + return { + node: resultParamsNode, + styles: toBeInjected, + skippedThemeProps: skippedProps, + }; + } + return { node: null, styles: {}, skippedThemeProps: {} }; +} + +function replaceSingleQuotes(str) { + let inDoubleQuotes = false; + let newStr = ''; + for (let i = 0; i < str.length; i++) { + if (str[i] === '"') { + inDoubleQuotes = !inDoubleQuotes; + } + if (str[i] === "'" && !inDoubleQuotes) { + newStr += '"'; + } else { + newStr += str[i]; + } + } + return newStr; +} + +function getObjectFromAstNode(node) { + let objectCode = generate(node).code; + + objectCode = objectCode?.replace(/as const/g, ''); + + objectCode = addQuotesToObjectKeys( + objectCode.replace( + /\\"|"(?:\\"|[^"])*"|(\/\/.*|\/\*[\s\S]*?\*\/)/g, + (m, g) => (g ? '' : m) + ) + ); + // Checking for single quotes and replacing it with " while keeping in mind to not replace single quotes inside double quotes + objectCode = replaceSingleQuotes(objectCode); + // console.log(objectCode, ' <==================|++++>> object code'); + + return JSON.parse(objectCode); +} + +function isEmptyObjectExpression(node) { + if (types.isObjectExpression(node)) { + const properties = node.properties; + if (properties.length === 0) { + return true; + } else { + // Recursively check the properties of the object + for (const prop of properties) { + if (!isEmptyObjectExpression(prop.value)) { + return false; + } + } + return true; + } + } + return false; +} + +function removeLiteralPropertiesFromObjectProperties(code) { + const ast = babel.parse(`var a = ${code}`, { + presets: [babelPresetTypeScript], + plugins: ['typescript'], + sourceType: 'module', + }); + + traverse(ast, { + ObjectExpression: (objectExpressionPath) => { + objectExpressionPath.traverse({ + ObjectProperty(objectPropertyPath) { + const { value } = objectPropertyPath.node; + + objectPropertyPath.traverse({ + StringLiteral: (stringPath) => { + stringPath; + }, + }); + + if ( + value.type === 'StringLiteral' || + value.type === 'NumericLiteral' + ) { + objectPropertyPath.remove(); + } + }, + }); + }, + }); + + traverse(ast, { + ObjectExpression: (objectExpressionPath) => { + const properties = objectExpressionPath.node.properties; + const nonEmptyProperties = properties.filter( + (prop) => !isEmptyObjectExpression(prop.value) + ); + + if (nonEmptyProperties.length !== properties.length) { + objectExpressionPath.node.properties = nonEmptyProperties; + } + + // Recursively traverse nested object expressions + objectExpressionPath.traverse({ + ObjectExpression(innerPath) { + const innerProperties = innerPath.node.properties; + const nonEmptyInnerProperties = innerProperties.filter( + (prop) => !isEmptyObjectExpression(prop.value) + ); + + if (nonEmptyInnerProperties.length !== innerProperties.length) { + innerPath.node.properties = nonEmptyInnerProperties; + } + }, + }); + }, + }); + + let initAst; + + traverse(ast, { + VariableDeclarator: (variableDeclaratorPath) => { + initAst = variableDeclaratorPath.node.init; + }, + }); + + return initAst; +} + +function getIdentifiersObjectFromAstNode(node) { + const objectCode = generate(node).code; + + return removeLiteralPropertiesFromObjectProperties( + objectCode.replace( + /\\"|"(?:\\"|[^"])*"|(\/\/.*|\/\*[\s\S]*?\*\/)/g, + (m, g) => (g ? '' : m) + ) + ); +} + +function generateObjectAst(obj) { + const properties = Object.entries(obj).map(([key, value]) => { + if (typeof value === 'undefined') { + return; + } else if (typeof value === 'object' && !Array.isArray(value)) { + return types.objectProperty( + types.stringLiteral(key), + generateObjectAst(value) + ); + } else if (typeof value === 'object' && Array.isArray(value)) { + const elements = value.map((val) => { + if (typeof val === 'string') { + return types.stringLiteral(val); + } else { + return generateObjectAst(val); + } + }); + return types.objectProperty( + types.stringLiteral(key), + types.arrayExpression(elements) + ); + } else if (typeof value === 'boolean') { + return types.objectProperty( + types.stringLiteral(key), + types.booleanLiteral(value) + ); + } else { + return types.objectProperty( + types.stringLiteral(key), + typeof value === 'number' + ? types.numericLiteral(value) + : types.stringLiteral(value) + ); + } + }); + + return types.objectExpression(properties.filter((property) => property)); +} +function generateArrayAst(arr) { + return types.arrayExpression(arr.map((obj) => generateObjectAst(obj))); +} + +function isImportedFromLibrary(libraries, importName) { + if (libraries.includes(importName)) { + return true; + } + return false; +} + +function isImportFromAbsolutePath( + absolutePaths, + filePath, + importedAbsolutePath +) { + filePath.pop(); + + const finalAbsolutePath = path.resolve( + filePath.join('/'), + importedAbsolutePath + ); + if (absolutePaths.includes(finalAbsolutePath)) { + return true; + } + return false; +} + +export function parseAndExtractConfig(code, opts) { + try { + const ast = parseAst(code); + + return extractStyles(ast, opts); + // return { + // code, + // styles: {}, + // }; + } catch (err) { + throw new Error( + `Error while parsing and extracting styles. Error: ${err.message} Stack: ${err.stack}.\n*************${opts?.filename}*************\n${code}` + ); + } +} + +function parseAst(code) { + try { + let ast = code; + if (typeof code === 'string') { + ast = babel.parse(code, { + sourceType: 'module', + presets: [babelPresetTypeScript], + plugins: [ + // enable jsx and flow syntax + 'typescript', + 'jsx', + ], + }); + } + return ast; + } catch (err) { + throw new Error( + `Error while parsing the code. Error: ${err.message} Stack: ${err.stack}` + ); + } +} + +function extractStyles(ast, opts) { + function checkWebFileExists(filePath) { + if (filePath.includes('node_modules')) { + return false; + } + const ext = path.extname(filePath); + const dirname = path.dirname(filePath); + const basename = path.basename(filePath, ext); + const webFilePath = path.join(dirname, `${basename}.web${ext}`); + return fs.existsSync(webFilePath); + } + + let styledImportName = ''; + let styledAlias = ''; + let styledAliasImportedName = ''; + let tempPropertyResolverNode; + let platform = 'all'; + let currentFileName = 'file not found!'; + let outputLibrary; + let componentSXProp; + const guessingStyledComponents = []; + const styled = ['@gluestack-style/react', '@gluestack-ui/themed']; + const components = ['@gluestack-ui/themed']; + let configPath; + let isStyledPathConfigured = false; + let isComponentsPathConfigured = false; + const targetPlatform = process.env.GLUESTACK_STYLE_TARGET; + let createStyleImportedName = ''; + let createComponentsImportedName = ''; + const CREATE_STYLE = 'createStyle'; + const CREATE_COMPONENTS = 'createComponents'; + + let cssStyles = {}; + + ConfigDefault = opts?.config; + configThemePath = opts?.configThemePath; + + ConfigDefault = generateMergedThemeTokens(ConfigDefault); + + traverse(ast, { + ImportDeclaration: (importPath) => { + currentFileName = opts?.filename; + styledAlias = opts?.styledAlias; + outputLibrary = opts?.outputLibrary || outputLibrary; + + if (opts?.platform) { + platform = opts?.platform; + } else { + platform = 'all'; + } + + if (!currentFileName.includes('node_modules')) { + if (currentFileName.includes('.web.')) { + platform = 'web'; + } else if (checkWebFileExists(currentFileName)) { + platform = 'native'; + } + } + + if ( + opts?.styled && + Array.isArray(opts?.styled) && + !isStyledPathConfigured + ) { + styled.push(...opts?.styled); + isStyledPathConfigured = true; + } + + if ( + opts?.components && + Array.isArray(opts?.components) && + !isComponentsPathConfigured + ) { + components.push(...opts?.components); + isComponentsPathConfigured = true; + } + + const importName = importPath.node.source.value; + + const filePath = opts?.filename.split('/'); + + if ( + isImportFromAbsolutePath(components, filePath, importName) || + isImportedFromLibrary(components, importName) + ) { + importPath.traverse({ + ImportSpecifier(importSpecifierPath) { + guessingStyledComponents.push(importSpecifierPath.node.local.name); + }, + }); + } + + if ( + isImportFromAbsolutePath(styled, filePath, importName) || + isImportedFromLibrary(styled, importName) + ) { + importPath.traverse({ + ImportSpecifier(importSpecifierPath) { + if (importSpecifierPath.node.imported.name === 'styled') { + styledImportName = importSpecifierPath.node.local.name; + } + if (importSpecifierPath.node.imported.name === CREATE_STYLE) { + createStyleImportedName = importSpecifierPath.node.local.name; + } + if (importSpecifierPath.node.imported.name === CREATE_COMPONENTS) { + createComponentsImportedName = + importSpecifierPath.node.local.name; + } + if (importSpecifierPath.node.imported.name === styledAlias) { + styledAliasImportedName = importSpecifierPath.node.local.name; + } + }, + }); + } + }, + AssignmentExpression: (expressionPath) => { + if ( + expressionPath?.node?.right?.callee?.name === styledAliasImportedName || + expressionPath?.node?.right?.callee?.name === styledImportName + ) { + const componentName = expressionPath?.parent?.id?.name; + + if (componentName) { + guessingStyledComponents.push(componentName); + } + } + }, + CallExpression: (callExpressionPath) => { + if (!opts.filename?.includes('node_modules')) { + const calleeName = callExpressionPath.node.callee.name; + if ( + calleeName === styledAliasImportedName || + calleeName === styledImportName || + calleeName === createComponentsImportedName || + calleeName === createStyleImportedName + ) { + callExpressionPath.traverse({ + ObjectProperty(ObjectPath) { + if (types.isIdentifier(ObjectPath.node.value)) { + if (ObjectPath.node.value.name === 'undefined') { + ObjectPath.remove(); + } + } + }, + }); + } + if ( + calleeName === styledAliasImportedName || + calleeName === styledImportName + ) { + const componentName = callExpressionPath?.parent?.id?.name; + + if (componentName) { + guessingStyledComponents.push(componentName); + } + + const args = callExpressionPath.node.arguments; + + const componentThemeNode = args[1]; + // optional case + const componentConfigNode = args[2] ?? types.objectExpression([]); + const extendedThemeNode = args[3] ?? types.objectExpression([]); + + if ( + !( + types.isIdentifier(componentThemeNode) || + types.isIdentifier(componentConfigNode) || + types.isIdentifier(extendedThemeNode) + ) + ) { + // args[1] = t.objectExpression([]); + + const extendedThemeNodeProps = []; + if (extendedThemeNode && extendedThemeNode?.properties) { + extendedThemeNode?.properties.forEach((prop) => { + if (prop.key.name === 'propertyResolver') { + tempPropertyResolverNode = prop; + } else { + extendedThemeNodeProps.push(prop); + } + }); + extendedThemeNode.properties = extendedThemeNodeProps; + } + + const theme = getObjectFromAstNode(componentThemeNode); + const ExtendedConfig = getObjectFromAstNode(extendedThemeNode); + const componentConfig = getObjectFromAstNode(componentConfigNode); + + if (extendedThemeNode && tempPropertyResolverNode) { + extendedThemeNode.properties.push(tempPropertyResolverNode); + } + + const { node, styles, skippedThemeProps } = getBuildTimeParams( + theme, + componentConfig, + ExtendedConfig, + outputLibrary, + platform, + 'boot', + opts?.disableExtraction + ); + + cssStyles = { ...cssStyles, ...styles }; + + const themeWithIdentifier = + getIdentifiersObjectFromAstNode(componentThemeNode); + + const mergedAST = mergeASTs(themeWithIdentifier, skippedThemeProps); + + args[1] = mergedAST; + + if (node) { + while (args.length < 4) { + args.push(types.objectExpression([])); + } + + if (!args[4]) { + args.push(node); + } else { + args[4] = node; + } + } + } + + // console.log( + // '<==================|++++>> final ', + // generate(path.node).code + // ); + // console.log( + // args, + // // resolvedStyles, + // // orderedResolved, + // // ...path.node.arguments, + // // generate(resultParamsNode).code, + // // resultParamsNode, + // // generate(path.node).code, + // 'code' + // ); + // console.log('\n\n >>>>>>>>>>>>>>>>>>>>>\n'); + + // console.log('final', generate(path.node).code); + // console.log('\n >>>>>>>>>>>>>>>>>>>>>\n\n'); + } + if (calleeName === createStyleImportedName) { + const args = callExpressionPath.node.arguments; + + const componentThemeNode = args[0]; + const componentConfigNode = args[1] ?? types.objectExpression([]); + + if ( + !( + types.isIdentifier(componentThemeNode) || + types.isIdentifier(componentConfigNode) + ) + ) { + const theme = getObjectFromAstNode(componentThemeNode); + const componentConfig = getObjectFromAstNode(componentConfigNode); + + const { node, styles, skippedThemeProps } = getBuildTimeParams( + theme, + componentConfig, + {}, + outputLibrary, + platform, + 'extended', + opts?.disableExtraction + ); + + cssStyles = { ...cssStyles, ...styles }; + + const themeWithIdentifier = + getIdentifiersObjectFromAstNode(componentThemeNode); + + const mergedAST = mergeASTs(themeWithIdentifier, skippedThemeProps); + + args[1] = mergedAST; + + if (node) { + while (args.length < 3) { + args.push(types.objectExpression([])); + } + if (!args[2]) { + args.push(node); + } else { + args[2] = node; + } + } + } + } + if (calleeName === createComponentsImportedName) { + /* + extended theme components AST + { + box: { + theme: {}, + }, + button: { + theme: {}, + }, + } + */ + const extendedThemeComponents = + callExpressionPath.node.arguments[0].properties; + + if (Array.isArray(extendedThemeComponents)) { + extendedThemeComponents.forEach((property) => { + if ( + !types.isIdentifier(property.value) && + !types.isTemplateLiteral(property.value) && + !types.isConditionalExpression(property.value) + ) { + const { themeNode, componentConfigNode } = + findThemeAndComponentConfig(property.value.properties); + + const theme = themeNode + ? getObjectFromAstNode(themeNode?.value) + : {}; + const componentConfig = componentConfigNode + ? getObjectFromAstNode(componentConfigNode?.value) + : {}; + + const { node, styles } = getBuildTimeParams( + theme, + componentConfig, + {}, + outputLibrary, + platform, + 'extended', + opts?.disableExtraction + ); + + cssStyles = { ...cssStyles, ...styles }; + + if (node) { + property.value.properties.push( + types.objectProperty( + types.stringLiteral('BUILD_TIME_PARAMS'), + node + ) + ); + } + } + }); + } + } + } + }, + JSXOpeningElement: (jsxOpeningElementPath) => { + if ( + jsxOpeningElementPath.node.name && + jsxOpeningElementPath.node.name.name && + guessingStyledComponents.includes(jsxOpeningElementPath.node.name.name) + ) { + const propsToBePersist = []; + let sxPropsWithIdentifier = {}; + + const mergedPropertyConfig = { + ...ConfigDefault?.propertyTokenMap, + ...propertyTokenMap, + }; + + const styledSystemProps = { + ...CSSPropertiesMap, + ...ConfigDefault?.aliases, + }; + + const componentExtendedConfig = merge( + {}, + { + ...ConfigDefault, + propertyTokenMap: { ...mergedPropertyConfig }, + } + ); + + const sxPropsConvertedUtilityProps = {}; + + const prefixedMediaQueries = {}; + + if (componentExtendedConfig?.tokens?.mediaQueries) { + Object.keys(componentExtendedConfig?.tokens?.mediaQueries).forEach( + (key) => { + prefixedMediaQueries[key] = { + key: `@${key}`, + isMediaQuery: true, + }; + } + ); + + Object.assign(reservedKeys, { ...prefixedMediaQueries }); + } + + const attr = jsxOpeningElementPath?.node?.attributes; + attr.forEach((attribute, index) => { + if (types.isJSXAttribute(attribute)) { + const propName = attribute?.name?.name; + const propValue = attribute?.value; + + if (types.isJSXExpressionContainer(propValue)) { + if ( + types.isIdentifier(propValue.expression) || + types.isConditionalExpression(propValue.expression) + ) { + propsToBePersist.push(attribute); + } else if ( + types.isObjectExpression(propValue.expression) && + propName !== 'sx' + ) { + const utilityPropsWithIdentifier = + getIdentifiersObjectFromAstNode(propValue.expression); + + const objectProperties = propValue.expression.properties; + const { result: objectValue } = + convertExpressionContainerToStaticObject(objectProperties); + + const { + prop: propString, + propPath, + value: utilityPropValue, + } = checkAndReturnUtilityProp( + propName, + objectValue, + styledSystemProps, + [], + reservedKeys + ); + + if (propString) { + propsToBePersist.push(attribute); + } else { + if (propPath && propPath.length > 0) { + setObjectKeyValue( + sxPropsConvertedUtilityProps, + propPath, + utilityPropValue + ); + + if ( + utilityPropsWithIdentifier && + utilityPropsWithIdentifier.properties && + utilityPropsWithIdentifier.properties.length > 0 + ) { + propsToBePersist.push( + types.jsxAttribute( + types.jsxIdentifier(propName), + types.jsxExpressionContainer( + utilityPropsWithIdentifier + ) + ) + ); + } + } + } + } else { + if (propName === 'sx') { + const objectProperties = propValue.expression.properties; + + sxPropsWithIdentifier = getIdentifiersObjectFromAstNode( + propValue.expression + ); + + const { result: sxPropsObject } = + convertExpressionContainerToStaticObject(objectProperties); + componentSXProp = sxPropsObject; + } else if ( + types.isStringLiteral(propValue.expression) || + types.isNumericLiteral(propValue.expression) + ) { + const { + prop: propString, + propPath, + value: utilityPropValue, + } = checkAndReturnUtilityProp( + propName, + propValue.expression.value, + styledSystemProps, + [], + reservedKeys + ); + + if (propString) { + propsToBePersist.push(attribute); + } else { + if (propPath && propPath.length > 0) { + setObjectKeyValue( + sxPropsConvertedUtilityProps, + propPath, + utilityPropValue + ); + } + } + } else { + propsToBePersist.push(attribute); + } + } + } else { + const { + prop: propString, + propPath, + value: utilityPropValue, + } = checkAndReturnUtilityProp( + propName, + propValue?.value, + styledSystemProps, + [], + reservedKeys + ); + if (propString) { + propsToBePersist.push(attribute); + } else { + if (propPath && propPath.length > 0) { + setObjectKeyValue( + sxPropsConvertedUtilityProps, + propPath, + utilityPropValue + ); + } + } + } + } + }); + + jsxOpeningElementPath.node.attributes.splice( + 0, + jsxOpeningElementPath.node.attributes.length + ); + + jsxOpeningElementPath.node.attributes = propsToBePersist; + + const sx = deepMerge( + { ...sxPropsConvertedUtilityProps }, + { ...componentSXProp } + ); + if (Object.keys(sx).length > 0) { + const verbosedSx = convertSxToSxVerbosed(sx); + + const inlineSxTheme = { + baseStyle: verbosedSx, + }; + + let sxHash = stableHash(sx); + + if (outputLibrary) { + sxHash = outputLibrary + '-' + sxHash; + } + + const { styledIds, verbosedStyleIds } = updateOrderUnResolvedMap( + inlineSxTheme, + sxHash, + 'inline', + {}, + BUILD_TIME_GLUESTACK_STYLESHEET, + platform + ); + + const toBeInjected = BUILD_TIME_GLUESTACK_STYLESHEET.resolve( + styledIds, + componentExtendedConfig, + {}, + true, + 'inline' + ); + + cssStyles = { + ...cssStyles, + ...toBeInjected, + }; + + const current_global_map = + BUILD_TIME_GLUESTACK_STYLESHEET.getStyleMap(); + + const orderedResolvedTheme = []; + + current_global_map?.forEach((styledResolved) => { + if (styledIds.includes(styledResolved?.meta?.cssId)) { + orderedResolvedTheme.push(styledResolved); + } + }); + + const styleIdsAst = generateObjectAst(verbosedStyleIds); + + const orderResolvedArrayExpression = []; + + orderedResolvedTheme.forEach((styledResolved) => { + delete styledResolved.toBeInjected; + if (targetPlatform !== 'web') { + delete styledResolved.original; + delete styledResolved.meta.cssRulesSet; + delete styledResolved.meta.weight; + delete styledResolved.type; + delete styledResolved.componentHash; + delete styledResolved.extendedConfig; + delete styledResolved.value; + } + const orderedResolvedAst = generateObjectAst(styledResolved); + orderResolvedArrayExpression.push(orderedResolvedAst); + }); + + jsxOpeningElementPath.node.attributes.push( + types.jsxAttribute( + types.jsxIdentifier('verbosedStyleIds'), + types.jsxExpressionContainer(styleIdsAst) + ) + ); + + jsxOpeningElementPath.node.attributes.push( + types.jsxAttribute( + types.jsxIdentifier('orderedResolved'), + types.jsxExpressionContainer( + types.arrayExpression(orderResolvedArrayExpression) + ) + ) + ); + + if (opts?.disableExtraction) { + jsxOpeningElementPath.node.attributes.push( + types.jsxAttribute( + types.jsxIdentifier('disableExtraction'), + types.jsxExpressionContainer(types.booleanLiteral(true)) + ) + ); + } + } + + if ( + sxPropsWithIdentifier && + sxPropsWithIdentifier.properties && + sxPropsWithIdentifier.properties.length > 0 + ) { + jsxOpeningElementPath.node.attributes.push( + types.jsxAttribute( + types.jsxIdentifier('sx'), + types.jsxExpressionContainer(sxPropsWithIdentifier) + ) + ); + } + + componentSXProp = undefined; + } + }, + }); + + return { + ast, + code: generate(ast).code, + styles: cssStyles, + }; +} diff --git a/packages/styled/babel-plugin/src/index.js b/packages/styled/babel-plugin/src/index.js new file mode 100644 index 0000000000..96a8656d71 --- /dev/null +++ b/packages/styled/babel-plugin/src/index.js @@ -0,0 +1,2 @@ +export * from './extract-config'; +export * from './extract-styles'; diff --git a/packages/styled/babel-plugin/tsconfig.json b/packages/styled/babel-plugin/tsconfig.json new file mode 100644 index 0000000000..03e617ed16 --- /dev/null +++ b/packages/styled/babel-plugin/tsconfig.json @@ -0,0 +1,29 @@ +{ + "extends": "expo/tsconfig.base", + "compilerOptions": { + "baseUrl": ".", + "emitDeclarationOnly": true, + "noEmit": false, + "declaration": true, + "allowUnreachableCode": false, + "allowUnusedLabels": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "jsx": "react", + "lib": ["esnext", "dom"], + "module": "esnext", + "moduleResolution": "node", + "noFallthroughCasesInSwitch": true, + "noImplicitReturns": true, + "noImplicitUseStrict": false, + "noStrictGenericChecks": false, + "noUnusedLocals": false, + "noUnusedParameters": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "strict": true, + "target": "esnext" + }, + "include": ["src"], + "exclude": ["lib", "node_modules"] +} diff --git a/packages/styled/build-config/.babelrc b/packages/styled/build-config/.babelrc new file mode 100644 index 0000000000..aaf733d3d8 --- /dev/null +++ b/packages/styled/build-config/.babelrc @@ -0,0 +1,6 @@ +{ + "presets": [ + "module:metro-react-native-babel-preset" + ] + // "plugins": ["transform-remove-console"] +} \ No newline at end of file diff --git a/packages/styled/build-config/.gitignore b/packages/styled/build-config/.gitignore new file mode 100644 index 0000000000..3d3e2719a3 --- /dev/null +++ b/packages/styled/build-config/.gitignore @@ -0,0 +1,28 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +node_modules +.pnp +.pnp.js + +# misc +.DS_Store +*.pem + +# build +dist +lib + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env.local +.env.development.local +.env.test.local +.env.production.local + +# turbo +.turbo diff --git a/packages/styled/build-config/.nvmrc b/packages/styled/build-config/.nvmrc new file mode 100644 index 0000000000..5dbac1ed0f --- /dev/null +++ b/packages/styled/build-config/.nvmrc @@ -0,0 +1 @@ +v16.13.0 \ No newline at end of file diff --git a/packages/styled/build-config/CHANGELOG.md b/packages/styled/build-config/CHANGELOG.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/styled/build-config/README.md b/packages/styled/build-config/README.md new file mode 100644 index 0000000000..b1ab4b48aa --- /dev/null +++ b/packages/styled/build-config/README.md @@ -0,0 +1,35 @@ +# @gluestack-style/babel-plugin-styled-resolver + +## Installation + +To use `@gluestack-style/babel-plugin-styled-resolver`, all you need to do is install the +`@gluestack-style/babel-plugin-styled-resolver` package: + +```sh +$ yarn add @gluestack-style/babel-plugin-styled-resolver + +# or + +$ npm i @gluestack-style/babel-plugin-styled-resolver +``` + +## Usage + +Add Babel plugin to your app `babel.config.js`. + +```jsx +const path = require('path'); +const gluestackStyleResolver = require('@gluestack-style/babel-plugin-styled-resolver'); +module.exports = function (api) { + api.cache(true); + return { + presets: ['babel-preset-expo'], + plugins: [gluestackStyleResolver], + }; +}; +``` + +Just make sure your babel.config.js and gluestack-style.config.js/ts are in the same directory. We suggest you keep both of them at the root of your app codebase. + +More guides on how to get started are available +[here](https://gluestack.io/style). diff --git a/packages/styled/build-config/package.json b/packages/styled/build-config/package.json new file mode 100644 index 0000000000..23a84e6af2 --- /dev/null +++ b/packages/styled/build-config/package.json @@ -0,0 +1,58 @@ +{ + "name": "@gluestack-style/build-config", + "version": "0.0.1", + "description": "A gluestack-style babel plugin that transpiles your styled function calls and resolves the component styling in build time.", + "keywords": [ + "css-in-js", + "babel-plugin", + "server-side rendering", + "ssr" + ], + "homepage": "https://github.com/gluestack/gluestack-ui/tree/main/packages/styled/babel-plugin-styled-resolver#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/gluestack/gluestack-ui.git" + }, + "main": "lib/commonjs/index", + "types": "lib/typescript/index.d.ts", + "module": "lib/module/index", + "react-native": "src/index", + "source": "src/index", + "typings": "lib/typescript/index.d.ts", + "scripts": { + "prepare": "bob build", + "release": "release-it", + "build": "bob build", + "clean": "rm -rf lib", + "watch": "npx nodemon --watch src/index.ts --watch src/transform.ts --exec 'yarn build'" + }, + "peerDependencies": { + "@gluestack-style/react": ">=1.0" + }, + "devDependencies": { + "babel-plugin-transform-remove-console": "^6.9.4", + "tsconfig": "*", + "typescript": "^4.7.4" + }, + "dependencies": { + "esbuild": "^0.19.8" + }, + "react-native-builder-bob": { + "source": "src", + "output": "lib", + "targets": [ + "commonjs", + "typescript", + [ + "module", + { + "babelrc": true + } + ] + ] + }, + "files": [ + "lib/", + "src/" + ] +} diff --git a/packages/styled/build-config/src/index.ts b/packages/styled/build-config/src/index.ts new file mode 100644 index 0000000000..1a53d302d3 --- /dev/null +++ b/packages/styled/build-config/src/index.ts @@ -0,0 +1,205 @@ +const fs = require('fs'); +const path = require('path'); +const esbuild = require('esbuild'); + +const OUTPUT_FILE = `./.gluestack/config-${process.ppid}.js`; +const MOCK_LIBRARY = `./mock-${process.ppid}.js`; + +function getConfigPath() { + const isConfigJSExist = fs.existsSync( + path.join(process.cwd(), './gluestack-style.config.js') + ); + const isGlueStackUIConfigJSExist = fs.existsSync( + path.join(process.cwd(), './gluestack-ui.config.js') + ); + const isConfigTSExist = fs.existsSync( + path.join(process.cwd(), './gluestack-style.config.ts') + ); + const isGlueStackUIConfigTSExist = fs.existsSync( + path.join(process.cwd(), './gluestack-ui.config.ts') + ); + + if (isConfigJSExist) return './gluestack-style.config.js'; + if (isConfigTSExist) return './gluestack-style.config.ts'; + if (isGlueStackUIConfigJSExist) return './gluestack-ui.config.js'; + if (isGlueStackUIConfigTSExist) return './gluestack-ui.config.ts'; + + throw new Error( + 'gluestack-style.config.js or gluestack-style.config.ts / gluestack-ui.config.js or gluestack-ui.config.ts not found in the root directory' + ); +} + +const mockLibrary = `const react = { + forwardRef: () => {}, + createElement: () => {}, +}; +const reactNative = { + Platform: { + select: () => {}, + }, + StyleSheet: { + create: () => {}, + }, + PixelRatio: { + getFontScale: () => {}, + }, +}; +const gluestackStyleReact = { + createConfig: (config) => { + return config; + }, + createStyle: (config) => { + return config; + }, + createComponents: (config) => { + return config; + }, +}; +const gluestackStyleAnimationResolver = { + AnimationResolver: class { + constructor() {} + }, +}; +const gluestackStyleLegendMotionAnimationDriver = { +}; +const gluestackStyleMotiAnimationDriver = { +}; + +module.exports = { + ...react, + ...reactNative, + ...gluestackStyleReact, + ...gluestackStyleAnimationResolver, + ...gluestackStyleLegendMotionAnimationDriver, + ...gluestackStyleMotiAnimationDriver, +} +`; + +const getEsBuildConfigOptions = ( + inputDir: string, + outputDir: string = OUTPUT_FILE, + mockedLibraryPath: string = MOCK_LIBRARY +) => { + const entryPoint = inputDir ?? getConfigPath(); + + if (!entryPoint) { + throw new Error( + 'gluestack-style.config.js or gluestack-style.config.ts not found in the root directory' + ); + } + + const esbuildConfigOptions = { + entryPoints: [entryPoint], + bundle: true, + outfile: outputDir, + format: 'iife', + globalName: 'config', + // banner: { + // js: globals, + // }, + alias: { + 'react-native': mockedLibraryPath, + '@gluestack-style/react': mockedLibraryPath, + '@gluestack-style/animation-resolver': mockedLibraryPath, + '@gluestack-style/legend-motion-animation-driver': mockedLibraryPath, + '@gluestack-style/moti-animation-driver': mockedLibraryPath, + }, + target: 'node18', + footer: { + js: 'module.exports = config;', + }, + resolveExtensions: ['.js', '.ts', '.tsx', '.jsx', '.json'], + platform: 'node', + external: [ + 'react', + 'react-native', + '@gluestack-style/react', + '@gluestack-style/animation-resolver', + '@gluestack-style/legend-motion-animation-driver', + '@gluestack-style/moti-animation-driver', + mockedLibraryPath, + ], + }; + return esbuildConfigOptions; +}; + +function cleanup() { + if (fs.existsSync(`${process.cwd()}/.gluestack`)) { + fs.rmSync( + `${process.cwd()}/.gluestack`, + { recursive: true, force: true }, + (err: any) => { + if (err) { + console.error(err); + } else { + // eslint-disable-next-line no-console + console.log(`Removed ${process.cwd()}/.gluestack`); + // eslint-disable-next-line no-console + console.log('Preparing for build...'); + } + } + ); + } +} + +function buildConfig( + inputDir: string, + outputDir: string, + mockLibraryPath: string +) { + try { + const esbuildConfigOptions = getEsBuildConfigOptions( + inputDir, + outputDir, + mockLibraryPath + ); + esbuild.buildSync(esbuildConfigOptions); + } catch (err) { + console.error(err); + } +} + +function buildMockLibrary(mockedLibraryPath: string) { + const gluestackFolderPath = path.join(process.cwd(), './.gluestack'); + const mockLibraryFullPath = path.resolve( + gluestackFolderPath, + mockedLibraryPath + ); + if (!fs.existsSync(gluestackFolderPath)) { + fs.mkdirSync(gluestackFolderPath); + } + + fs.writeFileSync(mockLibraryFullPath, mockLibrary); +} + +function cleanupAndBuildConfig( + inputDir: string, + outputDir: string, + mockedLibraryPath: string +) { + try { + cleanup(); + buildMockLibrary(mockedLibraryPath); + buildConfig(inputDir, outputDir, mockedLibraryPath); + } catch (err) { + console.error(err); + } +} + +export const getConfig = ( + inputDir: string, + outputDir: string = OUTPUT_FILE, + mockLibraryPath: string = MOCK_LIBRARY +) => { + try { + if (inputDir) { + cleanupAndBuildConfig(inputDir, outputDir, mockLibraryPath); + const configFile = require(`${process.cwd()}/${outputDir}`); + return configFile; + } else { + return {}; + } + } catch (err) { + console.error(err); + } +}; diff --git a/packages/styled/build-config/tsconfig.json b/packages/styled/build-config/tsconfig.json new file mode 100644 index 0000000000..03e617ed16 --- /dev/null +++ b/packages/styled/build-config/tsconfig.json @@ -0,0 +1,29 @@ +{ + "extends": "expo/tsconfig.base", + "compilerOptions": { + "baseUrl": ".", + "emitDeclarationOnly": true, + "noEmit": false, + "declaration": true, + "allowUnreachableCode": false, + "allowUnusedLabels": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "jsx": "react", + "lib": ["esnext", "dom"], + "module": "esnext", + "moduleResolution": "node", + "noFallthroughCasesInSwitch": true, + "noImplicitReturns": true, + "noImplicitUseStrict": false, + "noStrictGenericChecks": false, + "noUnusedLocals": false, + "noUnusedParameters": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "strict": true, + "target": "esnext" + }, + "include": ["src"], + "exclude": ["lib", "node_modules"] +} diff --git a/packages/styled/metro-plugin/.babelrc b/packages/styled/metro-plugin/.babelrc new file mode 100644 index 0000000000..f422d9feb3 --- /dev/null +++ b/packages/styled/metro-plugin/.babelrc @@ -0,0 +1,4 @@ +{ + "presets": ["module:metro-react-native-babel-preset"] + // "plugins": ["transform-remove-console"] +} diff --git a/packages/styled/metro-plugin/.gitignore b/packages/styled/metro-plugin/.gitignore new file mode 100644 index 0000000000..3d3e2719a3 --- /dev/null +++ b/packages/styled/metro-plugin/.gitignore @@ -0,0 +1,28 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +node_modules +.pnp +.pnp.js + +# misc +.DS_Store +*.pem + +# build +dist +lib + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env.local +.env.development.local +.env.test.local +.env.production.local + +# turbo +.turbo diff --git a/packages/styled/metro-plugin/.nvmrc b/packages/styled/metro-plugin/.nvmrc new file mode 100644 index 0000000000..5dbac1ed0f --- /dev/null +++ b/packages/styled/metro-plugin/.nvmrc @@ -0,0 +1 @@ +v16.13.0 \ No newline at end of file diff --git a/packages/styled/metro-plugin/CHANGELOG.md b/packages/styled/metro-plugin/CHANGELOG.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/styled/metro-plugin/README.md b/packages/styled/metro-plugin/README.md new file mode 100644 index 0000000000..b1ab4b48aa --- /dev/null +++ b/packages/styled/metro-plugin/README.md @@ -0,0 +1,35 @@ +# @gluestack-style/babel-plugin-styled-resolver + +## Installation + +To use `@gluestack-style/babel-plugin-styled-resolver`, all you need to do is install the +`@gluestack-style/babel-plugin-styled-resolver` package: + +```sh +$ yarn add @gluestack-style/babel-plugin-styled-resolver + +# or + +$ npm i @gluestack-style/babel-plugin-styled-resolver +``` + +## Usage + +Add Babel plugin to your app `babel.config.js`. + +```jsx +const path = require('path'); +const gluestackStyleResolver = require('@gluestack-style/babel-plugin-styled-resolver'); +module.exports = function (api) { + api.cache(true); + return { + presets: ['babel-preset-expo'], + plugins: [gluestackStyleResolver], + }; +}; +``` + +Just make sure your babel.config.js and gluestack-style.config.js/ts are in the same directory. We suggest you keep both of them at the root of your app codebase. + +More guides on how to get started are available +[here](https://gluestack.io/style). diff --git a/packages/styled/metro-plugin/package.json b/packages/styled/metro-plugin/package.json new file mode 100644 index 0000000000..d7d6f8ab1f --- /dev/null +++ b/packages/styled/metro-plugin/package.json @@ -0,0 +1,60 @@ +{ + "name": "@gluestack-style/metro-plugin", + "version": "0.0.1", + "description": "A gluestack-style babel plugin that transpiles your styled function calls and resolves the component styling in build time.", + "keywords": [ + "css-in-js", + "babel-plugin", + "server-side rendering", + "ssr" + ], + "homepage": "https://github.com/gluestack/gluestack-ui/tree/main/packages/styled/babel-plugin-styled-resolver#readme", + "repository": { + "type": "git", + "url": "git+https://github.com/gluestack/gluestack-ui.git" + }, + "main": "lib/commonjs/index", + "types": "lib/typescript/index.d.ts", + "module": "lib/module/index", + "react-native": "src/index", + "source": "src/index", + "typings": "lib/typescript/index.d.ts", + "scripts": { + "prepare": "bob build", + "release": "release-it", + "build": "bob build", + "clean": "rm -rf lib", + "watch": "npx nodemon --watch src/index.ts --watch src/transform.ts --exec 'yarn build'" + }, + "peerDependencies": { + "@gluestack-style/react": ">=1.0" + }, + "dependencies": { + "@gluestack-style/build-config": "*", + "@gluestack-style/extract-styles": "*" + }, + "devDependencies": { + "@types/fs-extra": "^11.0.4", + "babel-plugin-transform-remove-console": "^6.9.4", + "tsconfig": "*", + "typescript": "^4.7.4" + }, + "react-native-builder-bob": { + "source": "src", + "output": "lib", + "targets": [ + "commonjs", + "typescript", + [ + "module", + { + "babelrc": true + } + ] + ] + }, + "files": [ + "lib/", + "src/" + ] +} diff --git a/packages/styled/metro-plugin/src/index.ts b/packages/styled/metro-plugin/src/index.ts new file mode 100644 index 0000000000..19a643405d --- /dev/null +++ b/packages/styled/metro-plugin/src/index.ts @@ -0,0 +1,51 @@ +import { getConfig } from '@gluestack-style/build-config'; +import { extractGluestackConfig } from '@gluestack-style/extract-styles'; +import fs from 'fs'; +import path from 'path'; + +export const withGluestackStyle = (config: any, options: any) => { + const defaultOptions = { + output: 'gluestack.css', + disableExtraction: false, + configThemePath: [], + }; + + const configPath = options?.configPath; + const output = options?.output ?? defaultOptions.output; + const configThemePath = + options?.configThemePath ?? defaultOptions.configThemePath; + + const cssFilePath = path.join(process.cwd(), output); + + const gluestackConfig = getConfig(configPath)?.config; + + let actualGluestackConfig = gluestackConfig; + + if (configThemePath.length > 0) { + configThemePath.forEach((currentPath: string) => { + actualGluestackConfig = actualGluestackConfig?.[currentPath]; + }); + } + + const cssVariablesConovertedTokens = extractGluestackConfig( + actualGluestackConfig + ); + + fs.writeFileSync(cssFilePath, cssVariablesConovertedTokens); + + const ogTransformPath = config?.transformerPath; + + config.transformerPath = require.resolve('./transform'); + + config.transformer = { + ...config.transformer, + ogTransformPath, + options: { + ...defaultOptions, + ...options, + config: actualGluestackConfig, + }, + }; + + return config; +}; diff --git a/packages/styled/metro-plugin/src/transform.ts b/packages/styled/metro-plugin/src/transform.ts new file mode 100644 index 0000000000..e9589b64b1 --- /dev/null +++ b/packages/styled/metro-plugin/src/transform.ts @@ -0,0 +1,63 @@ +import { outputFile, pathExists, appendFile } from 'fs-extra'; +const worker = require('metro-transform-worker'); +const path = require('path'); +const { parseAndExtractConfig } = require('@gluestack-style/extract-styles'); + +export async function transform( + config: any, + rootDirectory: any, + filename: any, + data: any, + options: any +) { + const ogPath = config?.ogTransformPath || config.transformerPath; + const transformer = ogPath ? require(ogPath) : worker; + const output = config?.options?.output; + + const fileNameExtensions = ['.js', '.ts', '.jsx', '.tsx']; + + const outputDir = path.join(rootDirectory, output); + + if ( + !filename?.includes('node_modules') && + !filename?.includes('.expo') && + fileNameExtensions.some((ext) => filename.endsWith(ext)) + ) { + let content = ``; + const sourceCode = data.toString('utf8'); + const { code, styles } = parseAndExtractConfig(sourceCode, { + ...config?.options, + filename, + }); + + // console.log( + // `**************${filename}*****************\n${code}\n*************OUTPUT=${outputDir}************\n${content}\n` + // ); + + Object.keys(styles).forEach((type) => { + styles[type].forEach(({ cssRuleset }: any) => { + content += `${cssRuleset} `; + }); + }); + + if (await pathExists(outputDir)) { + await appendFile(outputDir, content, { + encoding: 'utf-8', + }); + } else { + await outputFile(outputDir, content, { + encoding: 'utf8', + }); + } + + return transformer.transform( + config, + rootDirectory, + filename, + code.toString('utf8'), + options + ); + } + + return transformer.transform(config, rootDirectory, filename, data, options); +} diff --git a/packages/styled/metro-plugin/tsconfig.json b/packages/styled/metro-plugin/tsconfig.json new file mode 100644 index 0000000000..03e617ed16 --- /dev/null +++ b/packages/styled/metro-plugin/tsconfig.json @@ -0,0 +1,29 @@ +{ + "extends": "expo/tsconfig.base", + "compilerOptions": { + "baseUrl": ".", + "emitDeclarationOnly": true, + "noEmit": false, + "declaration": true, + "allowUnreachableCode": false, + "allowUnusedLabels": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "jsx": "react", + "lib": ["esnext", "dom"], + "module": "esnext", + "moduleResolution": "node", + "noFallthroughCasesInSwitch": true, + "noImplicitReturns": true, + "noImplicitUseStrict": false, + "noStrictGenericChecks": false, + "noUnusedLocals": false, + "noUnusedParameters": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "strict": true, + "target": "esnext" + }, + "include": ["src"], + "exclude": ["lib", "node_modules"] +} diff --git a/packages/styled/react/src/StyledProvider.tsx b/packages/styled/react/src/StyledProvider.tsx index ac6ea4b4d2..fc43f045e4 100644 --- a/packages/styled/react/src/StyledProvider.tsx +++ b/packages/styled/react/src/StyledProvider.tsx @@ -53,15 +53,18 @@ export const StyledProvider: React.FC<{ children?: React.ReactNode; globalStyles?: any; _experimentalNestedProvider?: boolean; + disableInjection?: boolean; }> = ({ config, colorMode, children, globalStyles, _experimentalNestedProvider, + disableInjection = false, }) => { const inlineStyleMap: any = React.useRef({ initialStyleInjected: false, + injectedCssTags: [], }); const { themes } = useTheme(); @@ -112,12 +115,12 @@ export const StyledProvider: React.FC<{ return configWithPlatformSpecificUnits; }, [config]); - if (Platform.OS === 'web' && globalStyles) { + if (Platform.OS === 'web' && globalStyles && !disableInjection) { const globalStyleInjector = createGlobalStylesWeb(globalStyles); globalStyleInjector({ ...currentConfig, propertyTokenMap }); } - if (Platform.OS === 'web') { + if (Platform.OS === 'web' && !disableInjection) { const cssVariables = convertTokensToCssVariables(currentConfig); injectGlobalCssStyle(cssVariables, 'variables'); } @@ -187,35 +190,18 @@ export const StyledProvider: React.FC<{ useSafeLayoutEffect(() => { if (Platform.OS === 'web' && typeof window !== 'undefined') { - const toBeInjectedStyles: any = {}; - if (inlineStyleMap.current.initialStyleInjected) { return; } - Object.keys(inlineStyleMap.current).forEach((key: any) => { - if (key !== 'initialStyleInjected') { - const styles = inlineStyleMap.current[key]; - - if (!toBeInjectedStyles[key]) { - toBeInjectedStyles[key] = document.createDocumentFragment(); + if (typeof window !== 'undefined') { + if (inlineStyleMap?.current?.injectedCssTags?.length > 0) { + const wrapperBlock = document.querySelector('#gs-injected'); + if (wrapperBlock) { + wrapperBlock.append(...inlineStyleMap?.current?.injectedCssTags); } - - styles.forEach((style: any) => { - if (!document.getElementById(style.id)) { - toBeInjectedStyles[key].appendChild(style); - } - }); } - }); - - Object.keys(toBeInjectedStyles).forEach((key) => { - let wrapperElement = document.querySelector('#' + key); - if (wrapperElement) { - wrapperElement.appendChild(toBeInjectedStyles[key]); - } - // delete inlineStyleMap.current[key]; - }); + } inlineStyleMap.current.initialStyleInjected = true; } @@ -237,6 +223,7 @@ export const StyledProvider: React.FC<{ setAnimationDriverData, inlineStyleMap: inlineStyleMap.current, isConfigSet: true, + disableInjection, }; if (_experimentalNestedProvider) { diff --git a/packages/styled/react/src/core/styled-system.ts b/packages/styled/react/src/core/styled-system.ts index 997e09f39a..62c5e2327d 100644 --- a/packages/styled/react/src/core/styled-system.ts +++ b/packages/styled/react/src/core/styled-system.ts @@ -14,6 +14,11 @@ export const CSSPropertiesMap = { borderStartEndRadius: '0', borderEndStartRadius: '0', borderWidth: '0', + backdropFilter: 'none', + gridTemplateColumns: 'none', + gridColumnGap: 'normal', + gridColumn: 'auto / auto', + boxShadow: 'none', bottom: 'auto', direction: 'ltr', display: 'flex', @@ -64,6 +69,11 @@ export const CSSPropertiesMap = { transformOrigin: 'initial', backfaceVisibility: 'visible', backgroundColor: 'transparent', + background: 'transparent', + filter: 'none', + backgroundClip: 'border-box', + WebkitBackgroundClip: 'border-box', + WebkitTextFillColor: 'initial', borderBottomLeftRadius: '0', borderBottomRightRadius: '0', borderColor: 'initial', diff --git a/packages/styled/react/src/createConfig.ts b/packages/styled/react/src/createConfig.ts index 99c59fb75d..70e6c220f4 100644 --- a/packages/styled/react/src/createConfig.ts +++ b/packages/styled/react/src/createConfig.ts @@ -7,7 +7,6 @@ import { propertyTokenMap } from './propertyTokenMap'; import { updateOrderUnResolvedMap } from './updateOrderUnResolvedMap'; import { GluestackStyleSheet } from './style-sheet'; import { resolvePlatformTheme } from './utils'; -import { Platform } from 'react-native'; /********************* PLUGINS *****************************/ @@ -135,7 +134,11 @@ export const resolveThemes = (config: any) => { return newConfig; }; -export const resolveComponentTheme = (config: any, componentTheme: any) => { +export const resolveComponentTheme = ( + config: any, + componentTheme: any, + platform: any +) => { const configWithPropertyTokenMap = config; let resolvedTheme = componentTheme; @@ -148,20 +151,27 @@ export const resolveComponentTheme = (config: any, componentTheme: any) => { resolvedTheme = resolveTheme( component.theme, configWithPropertyTokenMap, - component?.componentConfig + component?.componentConfig, + platform ); } else { - const toBeInjected = GluestackStyleSheet.update( - component.BUILD_TIME_PARAMS?.orderedResolved - ); - component.BUILD_TIME_PARAMS.toBeInjected = toBeInjected; + if (component.BUILD_TIME_PARAMS?.disableExtraction) { + const toBeInjected = GluestackStyleSheet.update( + component.BUILD_TIME_PARAMS?.orderedResolved + ); + component.BUILD_TIME_PARAMS.toBeInjected = toBeInjected; + } resolvedTheme = component; } return resolvedTheme; }; -export const resolveComponentThemes = (config: any, components: any) => { +export const resolveComponentThemes = ( + config: any, + components: any, + platform: any +) => { let newComponents: any = {}; const configWithPropertyTokenMap = { ...config, @@ -178,7 +188,8 @@ export const resolveComponentThemes = (config: any, components: any) => { newComponents[componentName] = resolveTheme( component.theme, configWithPropertyTokenMap, - component?.componentConfig + component?.componentConfig, + platform ); } else { GluestackStyleSheet.update(component.BUILD_TIME_PARAMS?.orderedResolved); @@ -192,11 +203,12 @@ export const resolveComponentThemes = (config: any, components: any) => { export const resolveTheme = ( componentTheme: {}, _config: any, - extendedConfig?: any + extendedConfig?: any, + platform?: any ) => { const versboseComponentTheme = convertStyledToStyledVerbosed(componentTheme); - resolvePlatformTheme(versboseComponentTheme, Platform.OS); + resolvePlatformTheme(versboseComponentTheme, platform); const componentHash = stableHash({ ...versboseComponentTheme, diff --git a/packages/styled/react/src/resolver/StyledValueToCSSObject.ts b/packages/styled/react/src/resolver/StyledValueToCSSObject.ts index dae1d69a4c..e78c4ebbbc 100644 --- a/packages/styled/react/src/resolver/StyledValueToCSSObject.ts +++ b/packages/styled/react/src/resolver/StyledValueToCSSObject.ts @@ -20,25 +20,13 @@ export function themeStyledValueToCSSObject( ) { let themeResolved1: any = {}; if (CONFIG?.themes) { - // const tokens = deepClone(CONFIG.tokens); - // Object.keys(CONFIG?.themes).forEach((key: any) => { - // const themeTokens = CONFIG?.themes[key]; - // Object.keys(themeTokens).forEach((tokenKey1: any) => { - // Object.keys(themeTokens[tokenKey1]).forEach((tokenKey: any) => { - // delete tokens[tokenKey1][tokenKey]; - // }); - // }); - // }); - - // debugger; - Object.keys(CONFIG?.themes).forEach((themeName: any) => { if (themeName !== 'tokens') { const themeResolved = StyledValueToCSSObject( input, { ...CONFIG, - tokens: CONFIG?.themes?.tokens[themeName], + tokens: CONFIG?.themes?.tokens?.[themeName], }, ignoreKeys, true diff --git a/packages/styled/react/src/resolver/injectComponentAndDescendantStyles.ts b/packages/styled/react/src/resolver/injectComponentAndDescendantStyles.ts index 54cef5eab8..9ad52c5ddf 100644 --- a/packages/styled/react/src/resolver/injectComponentAndDescendantStyles.ts +++ b/packages/styled/react/src/resolver/injectComponentAndDescendantStyles.ts @@ -6,10 +6,12 @@ import { getDescendantResolvedBaseStyle, getDescendantResolvedVariantStyle, } from './getComponentStyle'; +import { INTERNAL_updateCSSStyleInOrderedResolved } from '../updateCSSStyleInOrderedResolved'; +import { orderWithCssSelectors } from '../utils/css-injector/utils/inject'; export function injectComponentAndDescendantStyles( orderedResolved: OrderedSXResolved, - styleTagId?: string, + styleTagId: string, type: 'boot' | 'inline' = 'boot', _GluestackStyleSheet: StyleInjector = GluestackStyleSheet, platform: string = '', @@ -17,6 +19,11 @@ export function injectComponentAndDescendantStyles( ignoreKeys: Set = new Set(), CONFIG: any = {} ) { + const INLINE_BASE = type + '-base'; + const INLINE_VARIANT = type + '-variant'; + const INLINE_DESCENDANT_BASE = type + '-descendant-base'; + const INLINE_DESCENDANT_VARIANT = type + '-descendant-variant'; + const [ componentOrderResolvedBaseStyle, componentOrderResolvedBaseStateStyle, @@ -35,12 +42,49 @@ export function injectComponentAndDescendantStyles( descendantOrderResolvedVariantStateStyle, ] = getDescendantResolvedVariantStyle(orderedResolved); + INTERNAL_updateCSSStyleInOrderedResolved( + [ + ...componentOrderResolvedBaseStyle, + ...componentOrderResolvedBaseStateStyle, + ], + styleTagId, + true, + orderWithCssSelectors[INLINE_BASE] + ); + INTERNAL_updateCSSStyleInOrderedResolved( + [ + ...componentOrderResolvedVariantStyle, + ...componentOrderResolvedVariantStateStyle, + ], + styleTagId, + true, + orderWithCssSelectors[INLINE_VARIANT] + ); + INTERNAL_updateCSSStyleInOrderedResolved( + [ + ...descendantOrderResolvedBaseStyle, + ...descendantOrderResolvedBaseStateStyle, + ], + styleTagId, + true, + orderWithCssSelectors[INLINE_DESCENDANT_BASE] + ); + INTERNAL_updateCSSStyleInOrderedResolved( + [ + ...descendantOrderResolvedVariantStyle, + ...descendantOrderResolvedVariantStateStyle, + ], + styleTagId, + true, + orderWithCssSelectors[INLINE_DESCENDANT_VARIANT] + ); + const componentOrderResolvedBaseStyleIds = GluestackStyleSheet.declare( [ ...componentOrderResolvedBaseStyle, ...componentOrderResolvedBaseStateStyle, ], - type + '-base', + INLINE_BASE, styleTagId ? styleTagId : 'css-injected-boot-time', {} ); @@ -49,16 +93,17 @@ export function injectComponentAndDescendantStyles( ...descendantOrderResolvedBaseStyle, ...descendantOrderResolvedBaseStateStyle, ], - type + '-descendant-base', + INLINE_DESCENDANT_BASE, styleTagId ? styleTagId : 'css-injected-boot-time-descendant', {} ); + const componentOrderResolvedVariantStyleIds = GluestackStyleSheet.declare( [ ...componentOrderResolvedVariantStyle, ...componentOrderResolvedVariantStateStyle, ], - type + '-variant', + INLINE_VARIANT, styleTagId ? styleTagId : 'css-injected-boot-time', {} ); @@ -67,7 +112,7 @@ export function injectComponentAndDescendantStyles( ...descendantOrderResolvedVariantStyle, ...descendantOrderResolvedVariantStateStyle, ], - type + '-descendant-variant', + INLINE_DESCENDANT_VARIANT, styleTagId ? styleTagId : 'css-injected-boot-time-descendant', {} ); diff --git a/packages/styled/react/src/style-sheet/index.ts b/packages/styled/react/src/style-sheet/index.ts index 1526c6ab47..129e032025 100644 --- a/packages/styled/react/src/style-sheet/index.ts +++ b/packages/styled/react/src/style-sheet/index.ts @@ -10,6 +10,7 @@ import { resolveTokensFromConfig, } from '../utils'; import { inject } from '../utils/css-injector'; +import { orderWithCssSelectors } from '../utils/css-injector/utils/inject'; export type DeclarationType = 'boot' | 'forwarded'; const cssVariableRegex = /var\(--([^)]+)\)/; @@ -61,12 +62,10 @@ function getNativeValuesFromCSSVariables(styleObject: any, CONFIG: any) { export class StyleInjector { #globalStyleMap: any; #toBeInjectedIdsArray: Array; - #idCounter: number; constructor() { this.#globalStyleMap = new Map(); this.#toBeInjectedIdsArray = []; - this.#idCounter = 0; } declare( @@ -82,10 +81,10 @@ export class StyleInjector { ...styledResolved, type: _wrapperElementId, componentHash: _styleTagId, - id: this.#idCounter, + // id: this.#idCounter, extendedConfig, }); - this.#idCounter++; + // this.#idCounter++; styleIds.push(styledResolved.meta.cssId); } }); @@ -98,7 +97,7 @@ export class StyleInjector { CONFIG: any, ExtendedConfig: any, resolve: any = true, - declarationType: string = 'boot', + _declarationType: string = 'boot', ignoreKeys: Set = new Set() ) { let componentExtendedConfig = CONFIG; @@ -120,14 +119,12 @@ export class StyleInjector { theme, componentExtendedConfig, styledResolved.componentHash, - CONFIG, - declarationType, ignoreKeys ); } const type = styledResolved?.type; - const styleTag = styledResolved?.componentHash; + const styleTag = styledResolved?.meta?.cssId; const cssRuleset = styledResolved?.meta?.cssRuleset; if (!toBeInjected[type]) { @@ -138,12 +135,12 @@ export class StyleInjector { if (!cummialtiveCssRuleset) { toBeInjected[type].set(styleTag, { - id: styledResolved.id, + // id: styledResolved.id, cssRuleset: cssRuleset ?? '', }); } else { toBeInjected[type].set(styleTag, { - id: cummialtiveCssRuleset?.id, + // id: cummialtiveCssRuleset?.id, cssRuleset: cummialtiveCssRuleset?.cssRuleset + cssRuleset, }); } @@ -156,7 +153,7 @@ export class StyleInjector { const resolvedThemeNativeValue: any = {}; Object.keys(styledResolved?.themeResolved).forEach((key) => { - const currentThemeStyleObj = styledResolved?.themeResolved[key]; + const currentThemeStyleObj = styledResolved?.themeResolved?.[key]; const resolvedCurrentThemeNativeValue = getNativeValuesFromCSSVariables( currentThemeStyleObj, @@ -184,12 +181,12 @@ export class StyleInjector { orderResolvedStyleMap.forEach((styledResolved: any) => { this.#globalStyleMap.set(styledResolved.meta.cssId, styledResolved); - this.#idCounter++; + // this.#idCounter++; this.#toBeInjectedIdsArray.push(styledResolved.meta.cssId); const type = styledResolved?.type; - const styleTag = styledResolved?.componentHash; + const styleTag = styledResolved?.meta?.cssId; const cssRuleset = styledResolved?.meta?.cssRuleset; if (!toBeInjected[type]) { @@ -200,12 +197,12 @@ export class StyleInjector { if (!cummialtiveCssRuleset) { toBeInjected[type].set(styleTag, { - id: styledResolved.id, + // id: styledResolved.id, cssRuleset: cssRuleset ?? '', }); } else { toBeInjected[type].set(styleTag, { - id: cummialtiveCssRuleset?.id, + // id: cummialtiveCssRuleset?.id, cssRuleset: cummialtiveCssRuleset?.cssRuleset + cssRuleset, }); } @@ -216,8 +213,8 @@ export class StyleInjector { inject(toBeInjected: any = {}, inlineStyleMap: any) { Object.keys(toBeInjected).forEach((type) => { - toBeInjected[type].forEach(({ id, cssRuleset }: any, styleTag: any) => { - this.injectStyles(cssRuleset, type, styleTag, inlineStyleMap, id); + toBeInjected[type].forEach(({ cssRuleset }: any, styleTag: any) => { + this.injectStyles(cssRuleset, type, styleTag, inlineStyleMap); }); }); } @@ -227,11 +224,8 @@ export class StyleInjector { theme: any, componentExtendedConfig: any, componentHashKey: any, - CONFIG: any, - declarationType: string = 'boot', ignoreKeys: Set = new Set() ) { - const prefixClassName = declarationType === 'inline' ? 'gs' : ''; componentTheme.resolved = StyledValueToCSSObject( theme, componentExtendedConfig, @@ -248,7 +242,7 @@ export class StyleInjector { if (componentTheme.meta && componentTheme.meta.queryCondition) { const queryCondition = resolveTokensFromConfig( - CONFIG, + componentExtendedConfig, { condition: componentTheme.meta.queryCondition, }, @@ -260,7 +254,7 @@ export class StyleInjector { const cssData: any = getCSSIdAndRuleset( componentTheme, componentHashKey, - prefixClassName + orderWithCssSelectors[componentTheme?.type] ?? '' ); componentTheme.meta.cssRuleset = cssData.rules.style; @@ -274,16 +268,14 @@ export class StyleInjector { cssRuleset: any, _wrapperType: any, _styleTagId: any, - inlineStyleMap: any, - id: any + inlineStyleMap: any ) { if (cssRuleset) { inject( `@media screen {${cssRuleset}}`, _wrapperType as any, _styleTagId, - inlineStyleMap, - id + inlineStyleMap ); } } diff --git a/packages/styled/react/src/styled.tsx b/packages/styled/react/src/styled.tsx index 4d1190a6e5..2038166455 100644 --- a/packages/styled/react/src/styled.tsx +++ b/packages/styled/react/src/styled.tsx @@ -31,7 +31,6 @@ import { useStyled } from './StyledProvider'; import { useTheme } from './Theme'; import { propertyTokenMap } from './propertyTokenMap'; import { Platform, StyleSheet } from 'react-native'; -import { INTERNAL_updateCSSStyleInOrderedResolved } from './updateCSSStyleInOrderedResolved'; import { generateStylePropsFromCSSIds } from './generateStylePropsFromCSSIds'; import { get, onChange } from './core/colorMode'; @@ -848,6 +847,7 @@ export function verboseStyled( ExtendedConfig?: any, BUILD_TIME_PARAMS?: { orderedResolved: OrderedSXResolved; + disableExtraction?: boolean; verbosedStyleIds: { component: StyleIds; descendant: StyleIds; @@ -857,7 +857,6 @@ export function verboseStyled( }, nonVerbosedTheme?: any ) { - // const componentName = componentStyleConfig?.componentName; const componentHash = stableHash({ ...theme, ...componentStyleConfig, @@ -893,41 +892,36 @@ export function verboseStyled( descendant: StyleIds; }; let orderedCSSIds: any = []; + //@ts-ignore const isStyledComponent = Component?.isStyledComponent; - // const orderedUnResolvedTheme = updateOrderUnResolvedMap( - // theme, - // componentHash, - // declarationType, - // ExtendedConfig - // ); - - // styleIds = getStyleIds(orderedUnResolvedTheme, componentStyleConfig); if (BUILD_TIME_PARAMS?.orderedResolved) { - orderedResolved = BUILD_TIME_PARAMS?.orderedResolved; - orderedCSSIds = BUILD_TIME_PARAMS?.styledIds; + if (BUILD_TIME_PARAMS?.disableExtraction) { + orderedResolved = BUILD_TIME_PARAMS?.orderedResolved; + orderedCSSIds = BUILD_TIME_PARAMS?.styledIds; - BUILD_TIME_PARAMS.toBeInjected = - GluestackStyleSheet.update(orderedResolved); - } else { - const { styledIds: g, verbosedStyleIds } = updateOrderUnResolvedMap( - theme, - componentHash, - declarationType, - componentStyleConfig, - GluestackStyleSheet, - Platform.OS, - isStyledComponent - ); + BUILD_TIME_PARAMS.toBeInjected = + GluestackStyleSheet.update(orderedResolved); + } + } - orderedCSSIds = g; + const { styledIds: g, verbosedStyleIds } = updateOrderUnResolvedMap( + theme, + componentHash, + declarationType, + componentStyleConfig, + GluestackStyleSheet, + Platform.OS, + isStyledComponent + ); - styleIds = verbosedStyleIds; - } + orderedCSSIds = g; + + styleIds = verbosedStyleIds; if (BUILD_TIME_PARAMS?.verbosedStyleIds) { - styleIds = BUILD_TIME_PARAMS?.verbosedStyleIds; + styleIds = deepMergeArray(styleIds, BUILD_TIME_PARAMS?.verbosedStyleIds); } function injectSx( @@ -959,9 +953,6 @@ export function verboseStyled( baseStyle: sx, }; - // if (Platform.OS === '') - // console.log(sxHash, GluestackStyleSheet.getStyleMap(), 'hash here'); - resolvePlatformTheme(inlineSxTheme, Platform.OS); const sxStyledResolved = styledToStyledResolved( // @ts-ignore @@ -974,7 +965,6 @@ export function verboseStyled( // @ts-ignore sxStyledResolved.baseStyle.styledValueResolvedWithMeta; - // sxStyledResolved.baseStyle.styledValueResolvedWithMeta = addThemeConditionInMeta(componentTheme, CONFIG); const colorModeComponentThemes: any = sxStyledResolved.baseStyle?.colorMode; @@ -1003,12 +993,6 @@ export function verboseStyled( const orderedSXResolved = styledResolvedToOrderedSXResolved(sxStyledResolved); - INTERNAL_updateCSSStyleInOrderedResolved( - orderedSXResolved, - sxHash, - true, - 'gs' - ); injectComponentAndDescendantStyles( orderedSXResolved, @@ -1041,13 +1025,12 @@ export function verboseStyled( const StyledComponent = ( { //@ts-ignore + disableExtraction: BUILD_TIME_DISABLE_EXTRACTION, orderedResolved: BUILD_TIME_ORDERED_RESOLVED = [], //@ts-ignore verbosedStyleIds: BUILD_TIME_VERBOSED_STYLE_IDS = {}, //@ts-ignore states, - // styledIds: BUILD_TIME_STYLE_IDS = [], - // sxHash: BUILD_TIME_sxHash = '', ...componentProps }: any, ref: React.ForwardedRef

@@ -1062,6 +1045,9 @@ export function verboseStyled( //@ts-ignore let themeDefaultProps = { ...theme.baseStyle?.props }; + const themeWithVariants = { + variants: styleIds?.component?.variants, + }; const sxComponentStyleIds = useRef({}); const sxDescendantStyleIds: any = useRef({}); @@ -1090,8 +1076,6 @@ export function verboseStyled( : get(); if (!styleHashCreated) { - // eslint-disable-next-line react-hooks/rules-of-hooks - CONFIG = { ...styledContext.config, propertyTokenMap, @@ -1153,16 +1137,12 @@ export function verboseStyled( // Injecting style if (EXTENDED_THEME) { // RUN Middlewares - const resolvedComponentExtendedTheme = resolveComponentTheme( CONFIG, - EXTENDED_THEME + EXTENDED_THEME, + Platform.OS ); - componentExtendedTheme = resolvedComponentExtendedTheme.theme; - - // const resolvedComponentExtendedTheme = EXTENDED_THEME; - if (Object.keys(EXTENDED_THEME?.BUILD_TIME_PARAMS ?? {}).length > 0) { const EXTENDED_THEME_BUILD_TIME_PARAMS = EXTENDED_THEME?.BUILD_TIME_PARAMS; @@ -1170,25 +1150,30 @@ export function verboseStyled( styleIds, EXTENDED_THEME_BUILD_TIME_PARAMS?.verbosedStyleIds ); - GluestackStyleSheet.inject( - EXTENDED_THEME_BUILD_TIME_PARAMS?.toBeInjected, - styledContext.inlineStyleMap - ); - } else { - // Merge of Extended Config Style ID's with Component Style ID's - deepMergeArray( - styleIds, - resolvedComponentExtendedTheme?.verbosedStyleIds - ); - - const extendedStylesToBeInjected = GluestackStyleSheet.resolve( - resolvedComponentExtendedTheme?.styledIds, - CONFIG, - componentExtendedConfig, - true, - 'extended', - ignoreKeys - ); + if ( + EXTENDED_THEME?.BUILD_TIME_PARAMS?.disableExtraction || + !styledContext?.disableInjection + ) { + GluestackStyleSheet.inject( + EXTENDED_THEME_BUILD_TIME_PARAMS?.toBeInjected, + styledContext.inlineStyleMap + ); + } + } + // Merge of Extended Config Style ID's with Component Style ID's + deepMergeArray( + styleIds, + resolvedComponentExtendedTheme?.verbosedStyleIds + ); + const extendedStylesToBeInjected = GluestackStyleSheet.resolve( + resolvedComponentExtendedTheme?.styledIds, + CONFIG, + componentExtendedConfig, + true, + 'extended', + ignoreKeys + ); + if (Platform.OS === 'web') { GluestackStyleSheet.inject( extendedStylesToBeInjected, styledContext.inlineStyleMap @@ -1211,39 +1196,33 @@ export function verboseStyled( orderedCSSIds = [...orderedCSSIds, ...globalStyleIds]; } + const toBeInjected = GluestackStyleSheet.resolve( + orderedCSSIds, + CONFIG, + componentExtendedConfig, + true, + 'boot', + ignoreKeys + ); + + if (Platform.OS === 'web') { + GluestackStyleSheet.inject(toBeInjected, styledContext.inlineStyleMap); + } + if ( - !BUILD_TIME_PARAMS || - !BUILD_TIME_PARAMS?.orderedResolved || - BUILD_TIME_PARAMS?.orderedResolved.length === 0 + Platform.OS === 'web' && + (BUILD_TIME_PARAMS?.disableExtraction || + !styledContext?.disableInjection) ) { - const toBeInjected = GluestackStyleSheet.resolve( - orderedCSSIds, - CONFIG, - componentExtendedConfig, - true, - 'boot', - ignoreKeys + GluestackStyleSheet.inject( + BUILD_TIME_PARAMS?.toBeInjected, + styledContext.inlineStyleMap ); - - if (Platform.OS === 'web') { - GluestackStyleSheet.inject( - toBeInjected, - styledContext.inlineStyleMap - ); - } - } else { - if (Platform.OS === 'web') { - //@ts-ignore - GluestackStyleSheet.inject( - BUILD_TIME_PARAMS.toBeInjected, - styledContext.inlineStyleMap - ); - } } theme = deepMerge(theme, componentExtendedTheme); - // @ts-ignore - Object.assign(themeDefaultProps, theme?.baseStyle?.props); + + Object.assign(themeDefaultProps, styleIds?.component?.baseStyle?.props); Object.assign(styledSystemProps, CONFIG?.aliases); @@ -1291,12 +1270,12 @@ export function verboseStyled( const { variantProps: defaultVariantProps, restProps: defaultThemePropsWithoutVariants, - } = getVariantProps(themeDefaultProps, theme); + } = getVariantProps(themeDefaultProps, themeWithVariants); const { variantProps: inlineVariantProps, restProps: inlineComponentPropsWithoutVariants, - } = getVariantProps(incomingComponentProps, theme); + } = getVariantProps(incomingComponentProps, themeWithVariants); const variantProps = Object.assign(defaultVariantProps, inlineVariantProps); @@ -1326,12 +1305,17 @@ export function verboseStyled( const sxStyleIds: any = React.useRef(BUILD_TIME_VERBOSED_STYLE_IDS); if (BUILD_TIME_ORDERED_RESOLVED.length > 0 && !isClient.current) { - const toBeInjected = GluestackStyleSheet.update( - BUILD_TIME_ORDERED_RESOLVED - ); + if (BUILD_TIME_DISABLE_EXTRACTION || !styledContext?.disableInjection) { + const toBeInjected = GluestackStyleSheet.update( + BUILD_TIME_ORDERED_RESOLVED + ); - if (Platform.OS === 'web') { - GluestackStyleSheet.inject(toBeInjected, styledContext.inlineStyleMap); + if (Platform.OS === 'web') { + GluestackStyleSheet.inject( + toBeInjected, + styledContext.inlineStyleMap + ); + } } sxStyleIds.current = BUILD_TIME_VERBOSED_STYLE_IDS; @@ -1569,11 +1553,20 @@ export function verboseStyled( const orderedSXResolved = [ ...orderedPassingSXResolved, ...orderedComponentSXResolved, - ...BUILD_TIME_ORDERED_RESOLVED, + ...(BUILD_TIME_DISABLE_EXTRACTION || !styledContext?.disableInjection + ? BUILD_TIME_ORDERED_RESOLVED + : []), ]; // console.setStartTimeStamp('getStyleIds'); sxStyleIds.current = getStyleIds(orderedSXResolved, componentStyleConfig); + if (BUILD_TIME_DISABLE_EXTRACTION || !styledContext?.disableInjection) { + sxStyleIds.current = deepMergeArray( + sxStyleIds.current, + BUILD_TIME_VERBOSED_STYLE_IDS + ); + } + /// // Setting variants to sx property for inline variant resolution //@ts-ignore @@ -2205,53 +2198,10 @@ export function styled( } ) { const nonVerbosedTheme = theme; - // const DEBUG_TAG = componentStyleConfig?.DEBUG; - // const DEBUG = - // process.env.NODE_ENV === 'development' && DEBUG_TAG ? false : false; - - // const componentName = componentStyleConfig?.componentName; - // const componentExtendedTheme = extendedThemeConfig?.theme; - // const componentExtended_build_time_params = - // extendedThemeConfig?.BUILD_TIME_PARAMS; - // let mergedBuildTimeParams: any; - - if (BUILD_TIME_PARAMS) { - // mergedBuildTimeParams = Array( - // { ...BUILD_TIME_PARAMS }, - // { ...componentExtended_build_time_params } - // ); - } - - // let styledObj = { ...theme }; - // if (componentExtendedTheme) { - // styledObj = deepMerge({ ...theme }, { ...componentExtendedTheme }); - // } - - // // move inside stylehash created - // let plugins = [...getInstalledPlugins()]; - - // if (ExtendedConfig?.plugins) { - // // @ts-ignore - // plugins = [...plugins, ...ExtendedConfig?.plugins]; - // } - - // for (const pluginName in plugins) { - // // @ts-ignore - // [styledObj, , , Component] = plugins[pluginName]?.inputMiddleWare

( - // styledObj, - // true, - // true, - // Component - // ); - // } - - // theme = styledObj; - - // move inside stylehash created const sxConvertedObject = convertStyledToStyledVerbosed(theme); - let StyledComponent = verboseStyled( + const StyledComponent = verboseStyled( Component, sxConvertedObject, componentStyleConfig, @@ -2263,39 +2213,5 @@ export function styled( // @ts-ignore StyledComponent.isAnimatedComponent = Component.isAnimatedComponent; - // move before returning component from verboseStyled - - // @ts-ignore - // plugins?.reverse(); - // for (const pluginName in plugins) { - // // @ts-ignore - // if (plugins[pluginName]?.componentMiddleWare) { - // // @ts-ignore - // StyledComponent = plugins[pluginName]?.componentMiddleWare({ - // Component: StyledComponent, - // theme, - // componentStyleConfig, - // ExtendedConfig, - // }); - // } - // } - // move before returning component from verboseStyled - - // for (const pluginName in plugins) { - // const compWrapper = - // // @ts-ignore - // typeof plugins[pluginName].wrapperComponentMiddleWare === 'function' - // ? // @ts-ignore - // plugins[pluginName].wrapperComponentMiddleWare() - // : null; - - // if (compWrapper) { - // for (const key of Object.keys(compWrapper)) { - // // @ts-ignore - // StyledComponent[key] = compWrapper[key]; - // } - // } - // } - return StyledComponent; } diff --git a/packages/styled/react/src/updateCSSStyleInOrderedResolved.web.ts b/packages/styled/react/src/updateCSSStyleInOrderedResolved.web.ts index 4c5467d91b..a73a19b173 100644 --- a/packages/styled/react/src/updateCSSStyleInOrderedResolved.web.ts +++ b/packages/styled/react/src/updateCSSStyleInOrderedResolved.web.ts @@ -8,7 +8,6 @@ export function getCSSIdAndRuleset( prefixClassName: string = '' // path: Path ) { - const hasState = styleValueResolvedWithMeta.meta.path?.includes('state'); const toBeInjectedStyle: { style: any; condition?: any; @@ -45,8 +44,7 @@ export function getCSSIdAndRuleset( path: styleValueResolvedWithMeta?.meta?.path, data: styleValueResolvedWithMeta.original, }), - prefixClassName, - hasState + prefixClassName ); return cssObject; diff --git a/packages/styled/react/src/utils.ts b/packages/styled/react/src/utils.ts index aa9fe6ac60..8e059175ee 100644 --- a/packages/styled/react/src/utils.ts +++ b/packages/styled/react/src/utils.ts @@ -1,3 +1,5 @@ +import { CSSPropertiesMap } from './core/styled-system'; +import { propertyTokenMap } from './propertyTokenMap'; import type { Config } from './types'; import { deepClone } from './utils/cssify/utils/common'; @@ -77,7 +79,7 @@ export function convertTokensToCssVariables(currentConfig: any) { // Recursively process nested objects acc += objectToCssVariables(variableValue, `${prefix}${key}-`); } else { - acc += `${convertToUnicodeString(variableName)}: ${variableValue};\n`; + acc += `${convertToUnicodeString(variableName)}: ${variableValue}; `; } return acc; @@ -86,13 +88,13 @@ export function convertTokensToCssVariables(currentConfig: any) { const tokens = currentConfig?.tokens; const cssVariables = objectToCssVariables(tokens); - let content = `:root {\n${cssVariables}}`; + let content = `:root {${cssVariables}}`; if (currentConfig.themes) { Object.keys(currentConfig.themes).forEach((key) => { const theme = currentConfig.themes[key]; const cssVariables = objectToCssVariables(theme); - content += `\n\n[data-theme-id=${key}] {\n${cssVariables}}`; + content += `[data-theme-id=${key}] {${cssVariables}}`; }); } @@ -149,7 +151,11 @@ export function resolveAliasesFromConfig( const aliasResolvedProps: any = {}; Object.keys(props).map((key) => { - if (!ignoreKeys.has(key)) { + if ( + !ignoreKeys.has(key) && + //@ts-ignore + (config?.aliases?.[key] || propertyTokenMap[key] || CSSPropertiesMap[key]) + ) { if (config?.aliases?.[key]) { aliasResolvedProps[config.aliases?.[key]] = props[key]; } else { diff --git a/packages/styled/react/src/utils/css-injector/utils/inject.ts b/packages/styled/react/src/utils/css-injector/utils/inject.ts index 29894ee3f4..d8aa691166 100644 --- a/packages/styled/react/src/utils/css-injector/utils/inject.ts +++ b/packages/styled/react/src/utils/css-injector/utils/inject.ts @@ -1,9 +1,4 @@ -type IWrapperType = - | 'global' - | 'boot' - | 'inline' - | 'boot-descendant' - | 'inline-descendant'; +import type { IWrapperType } from '../../../types'; export const WRAPPER_BLOCK_PREFIX = 'gs-injected'; @@ -11,6 +6,54 @@ export const hasCss = (_id: any, _text: any) => {}; export const addCss = (_id: any, _text: any) => {}; +const order: IWrapperType[] = [ + 'global', + 'forwarded-base', + 'forwarded-descendant-base', + 'forwarded-variant', + 'forwarded-descendant-variant', + // base + 'boot-base', + 'extended-base', + 'composed-base', + 'boot-base-state', + 'extended-base-state', + 'composed-base-state', + // descendant-base + 'boot-descendant-base', + 'extended-descendant-base', + 'composed-descendant-base', + 'boot-descendant-base-state', + // variant + 'boot-variant', + 'extended-variant', + 'composed-variant', + 'boot-variant-state', + 'extended-variant-state', + 'composed-variant-state', + // descendant-variant + 'boot-descendant-variant', + 'extended-descendant-variant', + 'composed-descendant-variant', + 'boot-descendant-variant-state', + 'extended-descendant-variant-state', + 'composed-descendant-variant-state', + // inline + 'inline-descendant-base', + 'passing-base', + 'inline-variant', + 'inline-base', + 'inline-base-state', +]; + +export const orderWithCssSelectors: any = {}; + +order.reduce((prev: any, ele: any) => { + const cssSelector = prev + `.gs`; + Object.assign(orderWithCssSelectors, { [ele]: cssSelector }); + return cssSelector; +}, ''); + export const injectCss = ( _css: any, _wrapperType: IWrapperType, diff --git a/packages/styled/react/src/utils/css-injector/utils/inject.web.ts b/packages/styled/react/src/utils/css-injector/utils/inject.web.ts index f59354c73d..8ac0f6fb1b 100644 --- a/packages/styled/react/src/utils/css-injector/utils/inject.web.ts +++ b/packages/styled/react/src/utils/css-injector/utils/inject.web.ts @@ -2,9 +2,9 @@ import React from 'react'; import { Platform } from 'react-native'; import type { IWrapperType } from '../../../types'; -type IToBeFlushedStyles = { [key in IWrapperType]?: any }; +// type IToBeFlushedStyles = { [key in IWrapperType]?: any }; -const toBeFlushedStyles: IToBeFlushedStyles = {}; +const toBeFlushedStyles: any = {}; const order: IWrapperType[] = [ 'global', @@ -16,20 +16,17 @@ const order: IWrapperType[] = [ 'boot-base', 'extended-base', 'composed-base', - 'boot-base-state', - 'extended-base-state', - 'composed-base-state', // descendant-base 'boot-descendant-base', 'extended-descendant-base', 'composed-descendant-base', - 'boot-descendant-base-state', - 'extended-descendant-base-state', - 'composed-descendant-base-state', // variant 'boot-variant', 'extended-variant', 'composed-variant', + 'boot-base-state', + 'extended-base-state', + 'composed-base-state', 'boot-variant-state', 'extended-variant-state', 'composed-variant-state', @@ -37,8 +34,10 @@ const order: IWrapperType[] = [ 'boot-descendant-variant', 'extended-descendant-variant', 'composed-descendant-variant', + 'boot-descendant-base-state', 'boot-descendant-variant-state', 'extended-descendant-variant-state', + 'composed-descendant-base-state', 'composed-descendant-variant-state', // inline 'inline-descendant-base', @@ -48,6 +47,14 @@ const order: IWrapperType[] = [ 'inline-base-state', ]; +export const orderWithCssSelectors: any = {}; + +order.reduce((prev: any, ele: any) => { + const cssSelector = prev + `.gs`; + Object.assign(orderWithCssSelectors, { [ele]: cssSelector }); + return cssSelector; +}, ''); + const WRAPPER_BLOCK_PREFIX = 'gs-injected'; if (typeof window !== 'undefined') { @@ -64,20 +71,6 @@ if (typeof window !== 'undefined') { createdWrapperBlockDiv.id = WRAPPER_BLOCK_PREFIX; wrapperBlockDiv = document.head.appendChild(createdWrapperBlockDiv); } - - // document.head - - order.forEach((orderKey) => { - let wrapperElement = document.getElementById( - `${WRAPPER_BLOCK_PREFIX}-${orderKey}` - ); - if (!wrapperElement) { - wrapperElement = document.createElement('div'); - wrapperElement.id = `${WRAPPER_BLOCK_PREFIX}-${orderKey}`; - - wrapperBlockDiv?.appendChild(wrapperElement); - } - }); } } @@ -92,23 +85,17 @@ const createStyle = (styleTagId: any, css: any) => { export const injectCss = ( css: any, - wrapperType: IWrapperType, + _wrapperType: IWrapperType, styleTagId: string, - inlineStyleMap?: any, - id?: any + inlineStyleMap?: any ) => { - if (!toBeFlushedStyles[wrapperType]) { - toBeFlushedStyles[wrapperType] = {}; - } - if (toBeFlushedStyles[wrapperType][styleTagId]) { - // toBeFlushedStyles[wrapperType][styleTagId].push(css); - } else { - toBeFlushedStyles[wrapperType][styleTagId] = [css]; + if (!toBeFlushedStyles[styleTagId]) { + toBeFlushedStyles[styleTagId] = css; } if (typeof window !== 'undefined') { - let wrapperElement = document.querySelector( - '#' + `${WRAPPER_BLOCK_PREFIX}-${wrapperType}` + const wrapperElement = document.querySelector( + '#' + `${WRAPPER_BLOCK_PREFIX}` ); if (wrapperElement) { let style = wrapperElement.querySelector(`[id='${styleTagId}']`); @@ -116,16 +103,7 @@ export const injectCss = ( if (!style) { style = createStyle(styleTagId, css); if (inlineStyleMap && !inlineStyleMap?.initialStyleInjected) { - const styleMapId = `${WRAPPER_BLOCK_PREFIX}-${wrapperType}`; - const inlineMapStyles = inlineStyleMap[styleMapId]; - - if (inlineMapStyles) { - inlineMapStyles[id] = style; - } else { - inlineStyleMap[styleMapId] = []; - inlineStyleMap[styleMapId][id] = style; - } - // console.log('hello here >>>> there'); + inlineStyleMap.injectedCssTags.push(style); } else { // console.log('hello here >>>>'); wrapperElement.appendChild(style); @@ -146,35 +124,20 @@ export const injectGlobalCss = ( }; export const flush = (): Array => { - let toBeFlushedStylesGlobal = [] as any; - - order.forEach((orderKey) => { - const styleChildren: any = []; - if (toBeFlushedStyles[orderKey]) { - Object.keys(toBeFlushedStyles[orderKey]).forEach((styleTagId) => { - let rules = toBeFlushedStyles[orderKey][styleTagId]; - - styleChildren.push( - React.createElement('style', { - id: styleTagId, - key: styleTagId, - dangerouslySetInnerHTML: { - __html: rules.join('\n'), - }, - }) - ); - }); - } + const toBeFlushedStylesGlobal: any = []; + + Object.keys(toBeFlushedStyles).forEach((styleTagId: any) => { + //@ts-ignore + const css = toBeFlushedStyles[styleTagId]; toBeFlushedStylesGlobal.push( - React.createElement( - 'div', - { - id: `${WRAPPER_BLOCK_PREFIX}-${orderKey}`, - key: `${WRAPPER_BLOCK_PREFIX}-${orderKey}`, + React.createElement('style', { + id: styleTagId, + key: styleTagId, + dangerouslySetInnerHTML: { + __html: css, }, - styleChildren - ) + }) ); }); diff --git a/packages/styled/react/src/utils/cssify/create-stylesheet/index.ts b/packages/styled/react/src/utils/cssify/create-stylesheet/index.ts index 1863e34dfc..7cd24a9609 100644 --- a/packages/styled/react/src/utils/cssify/create-stylesheet/index.ts +++ b/packages/styled/react/src/utils/cssify/create-stylesheet/index.ts @@ -5,7 +5,6 @@ const createStyleSheet = ( stylesObject: any, dataHash: string = 'media', prefixClassName: string = '', - hasState: boolean = false, prefixColorMode: string = 'gs-' ) => { if (!stylesObject) return { ids: {}, styles: {}, fullStyles: {} }; @@ -25,43 +24,44 @@ const createStyleSheet = ( typeof colorMode === 'string' ? colorMode : condition?.colorMode; const colorSchemeQuery = createQuery(finalColorMode); - const css = createDeclarationBlock(style); + if (Object.keys(style).length > 0) { + const css = createDeclarationBlock(style); - const themeCssObj = {} as any; - // if (themeCondition) { - // Object.keys(themeCondition).forEach((themeName) => { - // const themeConditionValue = themeCondition[themeName]; - // themeCssObj[themeName] = createDeclarationBlock(themeConditionValue); - // }); - // } - // console.log(css, style, 'css', mediaQuery, 'mediaQuery', colorSchemeQuery); + const themeCssObj = {} as any; + // if (themeCondition) { + // Object.keys(themeCondition).forEach((themeName) => { + // const themeConditionValue = themeCondition[themeName]; + // themeCssObj[themeName] = createDeclarationBlock(themeConditionValue); + // }); + // } + // console.log(css, style, 'css', mediaQuery, 'mediaQuery', colorSchemeQuery); - // const stringHash = `cssinjected-${hash(`${key}${css}`)}`; + // const stringHash = `cssinjected-${hash(`${key}${css}`)}`; - const rule = createCssRule( - mediaQuery, - colorSchemeQuery, - finalColorMode, - dataHash, - css, - 'style', - prefixClassName, - prefixColorMode, - hasState, - theme, - themeCssObj - ); + const rule = createCssRule( + mediaQuery, + colorSchemeQuery, + finalColorMode, + dataHash, + css, + 'style', + prefixClassName, + prefixColorMode, + theme, + themeCssObj + ); - delete cleanStyles[key]; + delete cleanStyles[key]; - ids = { - ...ids, - [key]: `${ids?.[key] ? ids[key] + ' ' : ''}${dataHash}`, - }; - rules = { - ...rules, - [key]: rule, - }; + ids = { + ...ids, + [key]: `${ids?.[key] ? ids[key] + ' ' : ''}${dataHash}`, + }; + rules = { + ...rules, + [key]: rule, + }; + } }); // console.log(rules, 'ids here'); diff --git a/packages/styled/react/src/utils/cssify/utils/common.ts b/packages/styled/react/src/utils/cssify/utils/common.ts index f029e462f7..16021ded10 100644 --- a/packages/styled/react/src/utils/cssify/utils/common.ts +++ b/packages/styled/react/src/utils/cssify/utils/common.ts @@ -103,18 +103,17 @@ const createCssRule = ( dataType: string, prefixClassName: string, prefixColorMode: string, - hasState: boolean, themeCondition: any, _themeCssObj?: any ) => { const dataMediaSelector = `[data-${dataType}~="${stringHash}"]`; - const stateRulePrefix = hasState ? '.gs' : ''; - const inlineRulePrefix = prefixClassName ? `.${prefixClassName}` : ''; + + const inlineRulePrefix = prefixClassName ? `${prefixClassName}` : ''; const colorModeRulePrefix = prefixColorMode && colorMode ? `.${prefixColorMode}${colorMode}` : ''; - const mediaQueryPrefix = `.gs`; + const mediaQueryPrefix = ''; //`.gs`; - const inlineAndStatePrefix = `${inlineRulePrefix}${stateRulePrefix}`; + const inlineAndStatePrefix = `${inlineRulePrefix}`; let rule = ``; const themeConditionArray = themeCondition ? themeCondition.split('.') : []; diff --git a/yarn.lock b/yarn.lock index b7f3adbdd0..67fa4e2565 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7597,6 +7597,14 @@ "@types/qs" "*" "@types/serve-static" "*" +"@types/fs-extra@^11.0.4": + version "11.0.4" + resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-11.0.4.tgz#e16a863bb8843fba8c5004362b5a73e17becca45" + integrity sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ== + dependencies: + "@types/jsonfile" "*" + "@types/node" "*" + "@types/glob@*": version "8.1.0" resolved "https://registry.npmjs.org/@types/glob/-/glob-8.1.0.tgz" @@ -7685,6 +7693,13 @@ resolved "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz" integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== +"@types/jsonfile@*": + version "6.1.4" + resolved "https://registry.yarnpkg.com/@types/jsonfile/-/jsonfile-6.1.4.tgz#614afec1a1164e7d670b4a7ad64df3e7beb7b702" + integrity sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ== + dependencies: + "@types/node" "*" + "@types/lodash.merge@^4.6.7": version "4.6.9" resolved "https://registry.npmjs.org/@types/lodash.merge/-/lodash.merge-4.6.9.tgz"