From aa360ddfb44ce2be66a0513783ddec1ff6b42e09 Mon Sep 17 00:00:00 2001 From: mauroerta Date: Sun, 16 May 2021 18:12:52 +0200 Subject: [PATCH] feat: cache and benchmarks --- apps/benchmarks/package.json | 21 +++ apps/benchmarks/results/core.md | 65 +++++++++ .../results/morfeo-vs-styled-system.md | 18 +++ apps/benchmarks/src/core/completeStyle.js | 27 ++++ apps/benchmarks/src/core/componentStyle.js | 20 +++ apps/benchmarks/src/core/index.js | 43 ++++++ .../src/core/singleComplexProperty.js | 20 +++ apps/benchmarks/src/core/singleProperty.js | 20 +++ apps/benchmarks/src/core/utils.js | 45 ++++++ apps/benchmarks/src/morfeo-vs/index.js | 7 + .../benchmarks/src/morfeo-vs/styled-system.js | 66 +++++++++ apps/benchmarks/src/morfeo-vs/theme.js | 12 ++ apps/benchmarks/src/utils.js | 53 +++++++ apps/native-sandbox/package.json | 1 - apps/native-sandbox/src/components/Button.tsx | 2 +- .../src/theme/components/button.ts | 49 ++++--- .../src/theme/components/typography.ts | 132 +++++++++--------- lerna.json | 9 +- package.json | 5 +- packages/core/src/parsers/createParsers.ts | 53 ++++++- packages/core/src/types.ts | 1 + packages/core/tests/parsers/cache.test.ts | 82 +++++++++++ packages/core/tsconfig.json | 2 +- packages/native/tests/shadows.test.ts | 10 +- packages/spec/tsconfig.json | 2 +- packages/styled-components-web/src/styled.ts | 16 ++- .../tests/styled.test.tsx | 3 +- scripts/bootstrap-app.mjs | 2 +- .../sym.js => scripts/symlink-packages.mjs | 22 +-- tsconfig.base.json | 2 +- 30 files changed, 682 insertions(+), 128 deletions(-) create mode 100755 apps/benchmarks/package.json create mode 100644 apps/benchmarks/results/core.md create mode 100644 apps/benchmarks/results/morfeo-vs-styled-system.md create mode 100755 apps/benchmarks/src/core/completeStyle.js create mode 100755 apps/benchmarks/src/core/componentStyle.js create mode 100755 apps/benchmarks/src/core/index.js create mode 100755 apps/benchmarks/src/core/singleComplexProperty.js create mode 100755 apps/benchmarks/src/core/singleProperty.js create mode 100755 apps/benchmarks/src/core/utils.js create mode 100644 apps/benchmarks/src/morfeo-vs/index.js create mode 100755 apps/benchmarks/src/morfeo-vs/styled-system.js create mode 100644 apps/benchmarks/src/morfeo-vs/theme.js create mode 100755 apps/benchmarks/src/utils.js create mode 100644 packages/core/tests/parsers/cache.test.ts rename apps/native-sandbox/sym.js => scripts/symlink-packages.mjs (71%) diff --git a/apps/benchmarks/package.json b/apps/benchmarks/package.json new file mode 100755 index 00000000..03447c1f --- /dev/null +++ b/apps/benchmarks/package.json @@ -0,0 +1,21 @@ +{ + "name": "benchmarks", + "version": "0.0.7", + "private": true, + "author": { + "name": "Mauro Erta", + "email": "mauro@vlkstudio.com" + }, + "scripts": { + "morfeo-vs": "node src/morfeo-vs/index.js", + "core": "node src/core/index.js", + "all": "npm run core && npm run morfeo-vs" + }, + "devDependencies": { + "benchmark": "^2.1.4" + }, + "dependencies": { + "@morfeo/core": "^0.0.7", + "styled-system": "5.1.5" + } +} diff --git a/apps/benchmarks/results/core.md b/apps/benchmarks/results/core.md new file mode 100644 index 00000000..6b28f034 --- /dev/null +++ b/apps/benchmarks/results/core.md @@ -0,0 +1,65 @@ +# @morfeo/core + +## single property parser + +```json +{ + "color": "primary" +} +``` + +**regular parsing** 1,674,079 ops/sec ±1.28% (92 runs sampled) + +**with cache enabled** 3,336,631 ops/sec ±0.36% (91 runs sampled) + +Fastest is **with cache enabled** + + +## single complex property parser + +```json +{ + "shadow": "light" +} +``` + +**regular parsing** 875,625 ops/sec ±0.87% (92 runs sampled) + +**with cache enabled** 1,735,908 ops/sec ±0.43% (90 runs sampled) + +Fastest is **with cache enabled** + + +## parsing the style of a theme component + +```json +{ + "componentName": "Box" +} +``` + +**regular parsing** 238,443 ops/sec ±0.67% (91 runs sampled) + +**with cache enabled** 241,772 ops/sec ±0.59% (90 runs sampled) + +Fastest is **with cache enabled** + + +## parsing a complete style + +```json +{ + "px": "m", + "py": "s", + "color": "primary", + "bg": "secondary", + "shadow": "light", + "componentName": "Box" +} +``` + +**regular parsing** 71,908 ops/sec ±0.51% (92 runs sampled) + +**with cache enabled** 94,384 ops/sec ±0.90% (88 runs sampled) + +Fastest is **with cache enabled** diff --git a/apps/benchmarks/results/morfeo-vs-styled-system.md b/apps/benchmarks/results/morfeo-vs-styled-system.md new file mode 100644 index 00000000..143af299 --- /dev/null +++ b/apps/benchmarks/results/morfeo-vs-styled-system.md @@ -0,0 +1,18 @@ +# @morfeo/core vs styled-system + +## resolving a style + +```json +{ + "p": "m", + "m": "s", + "bg": "secondary", + "color": "primary" +} +``` + +**morfeo** 408,433 ops/sec ±0.74% (92 runs sampled) + +**styled system** 345,296 ops/sec ±0.73% (88 runs sampled) + +Fastest is **morfeo** diff --git a/apps/benchmarks/src/core/completeStyle.js b/apps/benchmarks/src/core/completeStyle.js new file mode 100755 index 00000000..1241fe81 --- /dev/null +++ b/apps/benchmarks/src/core/completeStyle.js @@ -0,0 +1,27 @@ +const Benchmark = require('benchmark'); +const { parsers } = require('@morfeo/core'); +const { onCycle, onComplete, onStart, appendInMd } = require('./utils'); + +const suite = new Benchmark.Suite(); + +const style = { + px: 'm', + py: 's', + color: 'primary', + bg: 'secondary', + shadow: 'light', + componentName: 'Box', +}; + +suite + .add('regular parsing', () => { + parsers.resolve({ style, cache: false }); + }) + .add('with cache enabled', () => { + parsers.resolve({ style, cache: true }); + }) + .on('start', () => onStart('parsing a complete style', style)) + .on('cycle', onCycle) + .on('complete', () => onComplete(suite)); + +module.exports = suite; diff --git a/apps/benchmarks/src/core/componentStyle.js b/apps/benchmarks/src/core/componentStyle.js new file mode 100755 index 00000000..c701b601 --- /dev/null +++ b/apps/benchmarks/src/core/componentStyle.js @@ -0,0 +1,20 @@ +const Benchmark = require('benchmark'); +const { parsers } = require('@morfeo/core'); +const { onCycle, onComplete, onStart, appendInMd } = require('./utils'); + +const suite = new Benchmark.Suite(); + +const style = { componentName: 'Box' }; + +suite + .add('regular parsing', () => { + parsers.resolve({ style, cache: false }); + }) + .add('with cache enabled', () => { + parsers.resolve({ style, cache: true }); + }) + .on('start', () => onStart('parsing the style of a theme component', style)) + .on('cycle', onCycle) + .on('complete', () => onComplete(suite)); + +module.exports = suite; diff --git a/apps/benchmarks/src/core/index.js b/apps/benchmarks/src/core/index.js new file mode 100755 index 00000000..0a830e4d --- /dev/null +++ b/apps/benchmarks/src/core/index.js @@ -0,0 +1,43 @@ +const { theme } = require('@morfeo/core'); +const { writeMdTitle } = require('./utils'); +const singlePropertySuite = require('./singleProperty'); +const singleComplexPropertySuite = require('./singleComplexProperty'); +const componentSuite = require('./componentStyle'); +const completeStyleSuite = require('./completeStyle'); + +theme.set({ + colors: { + primary: 'black', + secondary: 'white', + }, + space: { + s: '10px', + m: '20px', + }, + shadows: { + light: { + color: 'primary', + offset: { width: 2, height: 2 }, + opacity: 0.4, + radius: 20, + }, + }, + components: { + Box: { + style: { + bg: 'primary', + color: 'secondary', + px: 'm', + my: 's', + shadow: 'light', + }, + }, + }, +}); + +writeMdTitle(`# @morfeo/core`); + +singlePropertySuite.run({ async: false }); +singleComplexPropertySuite.run({ async: false }); +componentSuite.run({ async: false }); +completeStyleSuite.run({ async: false }); diff --git a/apps/benchmarks/src/core/singleComplexProperty.js b/apps/benchmarks/src/core/singleComplexProperty.js new file mode 100755 index 00000000..d0408dbd --- /dev/null +++ b/apps/benchmarks/src/core/singleComplexProperty.js @@ -0,0 +1,20 @@ +const Benchmark = require('benchmark'); +const { parsers } = require('@morfeo/core'); +const { onCycle, onComplete, onStart, appendInMd } = require('./utils'); + +const suite = new Benchmark.Suite(); + +const style = { shadow: 'light' }; + +suite + .add('regular parsing', () => { + parsers.resolve({ style, cache: false }); + }) + .add('with cache enabled', () => { + parsers.resolve({ style, cache: true }); + }) + .on('start', () => onStart('single complex property parser', style)) + .on('cycle', onCycle) + .on('complete', () => onComplete(suite)); + +module.exports = suite; diff --git a/apps/benchmarks/src/core/singleProperty.js b/apps/benchmarks/src/core/singleProperty.js new file mode 100755 index 00000000..cb8eaac3 --- /dev/null +++ b/apps/benchmarks/src/core/singleProperty.js @@ -0,0 +1,20 @@ +const Benchmark = require('benchmark'); +const { parsers } = require('@morfeo/core'); +const { onCycle, onComplete, onStart } = require('./utils'); + +const suite = new Benchmark.Suite(); + +const style = { color: 'primary' }; + +suite + .add('regular parsing', () => { + parsers.resolve({ style, cache: false }); + }) + .add('with cache enabled', () => { + parsers.resolve({ style, cache: true }); + }) + .on('start', () => onStart('single property parser', style)) + .on('cycle', onCycle) + .on('complete', () => onComplete(suite)); + +module.exports = suite; diff --git a/apps/benchmarks/src/core/utils.js b/apps/benchmarks/src/core/utils.js new file mode 100755 index 00000000..f8068482 --- /dev/null +++ b/apps/benchmarks/src/core/utils.js @@ -0,0 +1,45 @@ +const { + getMdPath, + onCycle: _onCycle, + onStart: _onStart, + writeInMd: _writeInMd, + appendInMd: _appendInMd, + onComplete: _onComplete, + writeMdTitle: _writeMdTitle, +} = require('../utils'); + +const mdPath = getMdPath('core'); + +function writeInMd(text) { + _writeInMd(mdPath, text); +} + +function appendInMd(text) { + appendInMd(mdPath, `\n\n${text}`); +} + +function writeMdTitle(title) { + _writeInMd(mdPath, `${title}`); +} + +function onStart(title, style) { + _onStart(mdPath, title, style); +} + +function onCycle(event) { + _onCycle(mdPath, event); +} + +function onComplete(suite) { + _onComplete(mdPath, suite); +} + +module.exports = { + onCycle, + onStart, + getMdPath, + writeInMd, + appendInMd, + onComplete, + writeMdTitle, +}; diff --git a/apps/benchmarks/src/morfeo-vs/index.js b/apps/benchmarks/src/morfeo-vs/index.js new file mode 100644 index 00000000..788f196e --- /dev/null +++ b/apps/benchmarks/src/morfeo-vs/index.js @@ -0,0 +1,7 @@ +const { theme } = require('@morfeo/core'); +const defaultTheme = require('./theme'); +const styledSystemSuite = require('./styled-system'); + +theme.set(defaultTheme); + +styledSystemSuite.run({ async: false }); diff --git a/apps/benchmarks/src/morfeo-vs/styled-system.js b/apps/benchmarks/src/morfeo-vs/styled-system.js new file mode 100755 index 00000000..960ee0b8 --- /dev/null +++ b/apps/benchmarks/src/morfeo-vs/styled-system.js @@ -0,0 +1,66 @@ +const Benchmark = require('benchmark'); +const { parsers } = require('@morfeo/core'); +const defaultTheme = require('./theme'); +const { + onCycle, + onStart, + onComplete, + getMdPath, + writeMdTitle, +} = require('../utils'); +const { + compose, + space, + color, + typography, + layout, + padding, + margin, + flexbox, + grid, + background, + borders, + position, + shadow, +} = require('styled-system'); + +const allProps = compose( + grid, + space, + color, + layout, + margin, + shadow, + borders, + padding, + flexbox, + position, + typography, + background, +); + +const suite = new Benchmark.Suite(); + +const mdPath = getMdPath('morfeo-vs-styled-system'); + +const style = { + p: 'm', + m: 's', + bg: 'secondary', + color: 'primary', +}; + +writeMdTitle(mdPath, `# @morfeo/core vs styled-system`); + +suite + .add('morfeo', () => { + parsers.resolve({ style, cache: true }); + }) + .add('styled system', () => { + allProps({ ...style, theme: defaultTheme }); + }) + .on('start', () => onStart(mdPath, 'resolving a style', style)) + .on('cycle', event => onCycle(mdPath, event)) + .on('complete', () => onComplete(mdPath, suite)); + +module.exports = suite; diff --git a/apps/benchmarks/src/morfeo-vs/theme.js b/apps/benchmarks/src/morfeo-vs/theme.js new file mode 100644 index 00000000..6a4796e8 --- /dev/null +++ b/apps/benchmarks/src/morfeo-vs/theme.js @@ -0,0 +1,12 @@ +const defaultTheme = { + colors: { + primary: 'black', + secondary: 'white', + }, + space: { + s: '10px', + m: '20px', + }, +}; + +module.exports = defaultTheme; diff --git a/apps/benchmarks/src/utils.js b/apps/benchmarks/src/utils.js new file mode 100755 index 00000000..997b9ac5 --- /dev/null +++ b/apps/benchmarks/src/utils.js @@ -0,0 +1,53 @@ +const fs = require('fs'); +const path = require('path'); + +function getMdPath(fileName) { + return path.resolve(__dirname, `../results`, `${fileName}.md`); +} + +function writeInMd(mdPath, text) { + fs.writeFileSync(mdPath, text); +} + +function appendInMd(mdPath, text) { + fs.appendFileSync(mdPath, `\n\n${text}`); +} + +function writeMdTitle(mdPath, title) { + writeInMd(mdPath, `${title}`); +} + +function onStart(mdPath, title, style) { + appendInMd(mdPath, `## ${title}`); + console.log(`benchmark: ${title}`); + appendInMd( + mdPath, + '```json\n' + `${JSON.stringify(style, undefined, 2)}` + '\n```', + ); +} + +function onCycle(mdPath, event) { + const title = event.target.name; + appendInMd( + mdPath, + `**${title}** ${String(event.target).replace(`${title} x`, '')}`, + ); + + console.log(String(event.target)); +} + +function onComplete(mdPath, suite) { + const result = `Fastest is **${suite.filter('fastest').map('name')}**`; + appendInMd(mdPath, `${result}\n`); + console.log(result); +} + +module.exports = { + onCycle, + onStart, + getMdPath, + writeInMd, + appendInMd, + onComplete, + writeMdTitle, +}; diff --git a/apps/native-sandbox/package.json b/apps/native-sandbox/package.json index da1b26c0..b7688503 100644 --- a/apps/native-sandbox/package.json +++ b/apps/native-sandbox/package.json @@ -6,7 +6,6 @@ "android": "react-native run-android", "ios": "react-native run-ios", "start": "react-native start", - "link": "node ./sym.js", "test": "jest", "lint": "eslint . --ext .js,.jsx,.ts,.tsx" }, diff --git a/apps/native-sandbox/src/components/Button.tsx b/apps/native-sandbox/src/components/Button.tsx index 4c809136..93ccb8fb 100644 --- a/apps/native-sandbox/src/components/Button.tsx +++ b/apps/native-sandbox/src/components/Button.tsx @@ -19,7 +19,7 @@ export const Button: React.FC = ({ const defaultStyle = useStyle(props); return ( - + {withTypography(children, { ...textStyle, color })} ); diff --git a/apps/native-sandbox/src/theme/components/button.ts b/apps/native-sandbox/src/theme/components/button.ts index e4dbbb00..4ec9c5bb 100644 --- a/apps/native-sandbox/src/theme/components/button.ts +++ b/apps/native-sandbox/src/theme/components/button.ts @@ -12,39 +12,46 @@ export const button = { }, variants: { primary: { - bg: 'positive', - textStyle: { - variant: 'p1', - color: 'white', + style: { + bg: 'positive', + }, + props: { + textStyle: { + variant: 'p1', + color: 'white', + }, }, }, 'primary/negative': { - bg: 'negative', - textStyle: { - variant: 'p1', - color: 'positive', + style: { + bg: 'negative', + }, + props: { + textStyle: { + variant: 'p1', + color: 'positive', + }, }, }, secondary: { - bg: 'transparent', - borderWidth: 'xs', - borderColor: 'positive', - textStyle: { - variant: 'p1', - color: 'positive', + style: { bg: 'transparent', borderWidth: 'xs', borderColor: 'positive' }, + props: { + textStyle: { + variant: 'p1', + color: 'positive', + }, }, }, 'secondary/negative': { - bg: 'transparent', - borderWidth: 'xs', - borderColor: 'negative', - textStyle: { - color: 'negative', + style: { bg: 'transparent', borderWidth: 'xs', borderColor: 'negative' }, + props: { + textStyle: { + color: 'negative', + }, }, }, round: { - bg: 'dark', - borderRadius: 'round', + style: { bg: 'dark', borderRadius: 'round' }, }, }, }; diff --git a/apps/native-sandbox/src/theme/components/typography.ts b/apps/native-sandbox/src/theme/components/typography.ts index 2a725b52..473d1394 100644 --- a/apps/native-sandbox/src/theme/components/typography.ts +++ b/apps/native-sandbox/src/theme/components/typography.ts @@ -2,90 +2,94 @@ export const typography = { style: {}, variants: { hero: { - fontSize: 'l', - fontWeight: 'bold', - lineHeight: 'l', - color: 'grey/dark', - }, - 'hero/gradient': { - fontSize: 'l', - fontWeight: 'bold', - lineHeight: 'l', - color: 'grey/dark', - gradient: 'primary', + style: { + fontSize: 'l', + fontWeight: 'bold', + lineHeight: 'l', + color: 'grey/dark', + }, }, h1: { - fontSize: 'xl', - lineHeight: 'xl', - fontWeight: 'bold', - color: 'grey/dark', - }, - 'h1/gradient': { - fontSize: 'xl', - lineHeight: 'xl', - fontWeight: 'bold', - gradient: 'primary', + style: { + fontSize: 'xl', + lineHeight: 'xl', + fontWeight: 'bold', + color: 'grey/dark', + }, }, h2: { - fontSize: 'l', - lineHeight: 'm', - fontWeight: 'bold', - color: 'grey/dark', + style: { + fontSize: 'l', + lineHeight: 'm', + fontWeight: 'bold', + color: 'grey/dark', + }, }, h3: { - fontSize: 'm', - lineHeight: 's', - fontWeight: 'bold', - color: 'grey/dark', + style: { + fontSize: 'm', + lineHeight: 's', + fontWeight: 'bold', + color: 'grey/dark', + }, }, p1: { - fontSize: 'm', - lineHeight: 'm', - fontWeight: 'regular', - color: 'grey/dark', + style: { + fontSize: 'm', + lineHeight: 'm', + fontWeight: 'regular', + color: 'grey/dark', + }, }, p2: { - fontSize: 's', - lineHeight: 'm', - fontWeight: 'regular', - color: 'grey/dark', + style: { + fontSize: 's', + lineHeight: 'm', + fontWeight: 'regular', + color: 'grey/dark', + }, }, p3: { - fontSize: 'xs', - lineHeight: 's', - fontWeight: 'regular', - color: 'grey/dark', + style: { + fontSize: 'xs', + lineHeight: 's', + fontWeight: 'regular', + color: 'grey/dark', + }, }, caption: { - color: 'grey/dark', - fontSize: 'xs', - lineHeight: 'xs', - fontStyle: 'italic', + style: { + color: 'grey/dark', + fontSize: 'xs', + lineHeight: 'xs', + fontStyle: 'italic', + }, }, cta: { - color: 'positive', - fontWeight: 'bold', - fontSize: 'm', - lineHeight: 's', - textDecorationLine: 'underline', - textDecorationColor: 'positive', + style: { + color: 'positive', + fontWeight: 'bold', + fontSize: 'm', + lineHeight: 's', + textDecorationLine: 'underline', + textDecorationColor: 'positive', + }, }, overline: { - fontSize: 's', - lineHeight: 'm', - color: 'grey/dark', - fontWeight: 'bold', - textTransform: 'uppercase', + style: { + fontSize: 's', + lineHeight: 'm', + color: 'grey/dark', + fontWeight: 'bold', + textTransform: 'uppercase', + }, }, link: { - fontSize: 's', - color: 'primary', - fontWeight: 'medium', - }, - totalAmount: { - fontSize: 'xxl', - color: 'dark', - fontWeight: 'black', + style: { + fontSize: 's', + color: 'primary', + fontWeight: 'medium', + }, }, }, }; diff --git a/lerna.json b/lerna.json index e5292faa..2aca7e10 100644 --- a/lerna.json +++ b/lerna.json @@ -1,14 +1,9 @@ { - "packages": [ - "packages/*", - "apps/web-sandbox" - ], + "packages": ["packages/*"], "version": "0.0.7", "command": { "bootstrap": { - "npmClientArgs": [ - "--no-package-lock" - ] + "npmClientArgs": ["--no-package-lock"] } } } diff --git a/package.json b/package.json index 1955dcf1..508cc6f0 100644 --- a/package.json +++ b/package.json @@ -6,8 +6,11 @@ "build": "lerna run build --ignore=web-sandbox", "watch": "lerna run watch", "reset": "npm run clean && npm i && lerna bootstrap && npm run build", + "init:web-sandbox": "node scripts/bootstrap-app.mjs web-sandbox", + "init:native-sandbox": "node scripts/bootstrap-app.mjs native-sandbox", + "init:benchmarks": "node scripts/bootstrap-app.mjs benchmarks", "start:web-sandbox": "npm start --prefix apps/web-sandbox", - "bootstrap:app": "node scripts/bootstrap-app.mjs native-sandbox", + "start:benchmarks": "npm run all --prefix apps/benchmarks", "prepare": "husky install", "prettify": "npm run prettify:js && npm run prettify:ts", "prettify:js": "npx prettier --write \"**/*.js\"", diff --git a/packages/core/src/parsers/createParsers.ts b/packages/core/src/parsers/createParsers.ts index c865a027..198c2a07 100644 --- a/packages/core/src/parsers/createParsers.ts +++ b/packages/core/src/parsers/createParsers.ts @@ -56,6 +56,7 @@ type ParsersContext = { export function createParsers() { let context = { ...INITIAL_PARSERS } as any as ParsersContext; + let cache: any = {}; function get() { return context; @@ -69,6 +70,14 @@ export function createParsers() { context = { ...INITIAL_PARSERS } as any as ParsersContext; } + function getCache() { + return cache; + } + + function resetCache() { + cache = {}; + } + function resolveResponsiveProperty({ property, value, @@ -121,7 +130,10 @@ export function createParsers() { return {}; } - function resolve({ style = {} }: ResolverParams): ResolvedStyle { + function resolve({ + style = {}, + cache: cacheEnabled = true, + }: ResolverParams): ResolvedStyle { const { componentName, ...rest } = style; const properties = Object.keys(rest); @@ -131,23 +143,50 @@ export function createParsers() { value: componentName, style, }) - : {}; + : undefined; + + function getPropertyStyle(property: string) { + const value = style[property]; + const params = { + property, + value, + style, + }; + if ( + cacheEnabled && + (typeof value === 'string' || typeof value === 'number') + ) { + if (cache[property] === undefined) { + cache[property] = {}; + } + if (cache[property][value] !== undefined) { + return cache[property][value]; + } + cache[property][value] = resolveProperty(params); + return cache[property][value]; + } + + return resolveProperty(params); + } const parsedStyle = properties.reduce((acc, property) => { - return deepMerge( - acc, - resolveProperty({ property, value: style[property], style }), - ); + return deepMerge(acc, getPropertyStyle(property)); }, {}); - return deepMerge(defaultComponentStyle, parsedStyle); + return defaultComponentStyle + ? deepMerge(defaultComponentStyle, parsedStyle) + : parsedStyle; } + theme.listen(resetCache); + const parsers = { get, add, reset, resolve, + getCache, + resetCache, resolveProperty, }; diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 56fc1bfc..75e4d864 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -6,6 +6,7 @@ export interface ResolvedStyle {} export type ResolverParams = { style?: Style; + cache?: boolean; }; export interface ParserParams

{ value: Style[P]; diff --git a/packages/core/tests/parsers/cache.test.ts b/packages/core/tests/parsers/cache.test.ts new file mode 100644 index 00000000..e87b49d6 --- /dev/null +++ b/packages/core/tests/parsers/cache.test.ts @@ -0,0 +1,82 @@ +import { Theme } from '@morfeo/spec'; +import { parsers, theme } from '../../src'; + +const THEME: Theme = { + colors: { + primary: 'white', + secondary: 'black', + }, +} as any; + +describe('cache', () => { + beforeEach(() => { + theme.set(THEME); + }); + + afterEach(() => { + parsers.resetCache(); + }); + + test('should set the cache after resolving a style', () => { + parsers.resolve({ + style: { color: 'primary' }, + }); + + expect(parsers.getCache()).toEqual({ + color: { primary: { color: 'white' } }, + }); + }); + + test('should reset the cache after a change of the theme', () => { + parsers.resolve({ + style: { color: 'primary' }, + }); + + theme.set({ + colors: { + primary: 'black', + secondary: 'white', + }, + } as any); + expect(parsers.getCache()).toEqual({}); + }); + + test('should return a different value for the same style if the theme is updated', () => { + const firstStyle = parsers.resolve({ + style: { color: 'primary' }, + }); + theme.set({ + colors: { + primary: 'black', + secondary: 'white', + }, + } as any); + const secondStyle = parsers.resolve({ + style: { color: 'primary' }, + }); + expect(firstStyle).toEqual({ color: 'white' }); + expect(secondStyle).toEqual({ color: 'black' }); + expect(parsers.getCache()).toEqual({ + color: { primary: { color: 'black' } }, + }); + }); + + test('should return a different value for the same style if the theme is updated and the cache is disabled', () => { + const firstStyle = parsers.resolve({ + style: { color: 'primary' }, + cache: false, + }); + theme.set({ + colors: { + primary: 'black', + secondary: 'white', + }, + } as any); + const secondStyle = parsers.resolve({ + style: { color: 'primary' }, + cache: false, + }); + expect(firstStyle).toEqual({ color: 'white' }); + expect(secondStyle).toEqual({ color: 'black' }); + }); +}); diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json index e5bb7df9..9ee04b7b 100644 --- a/packages/core/tsconfig.json +++ b/packages/core/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "outDir": "./build", + "outDir": "./build", }, "include": ["./src"], "exclude": ["./node_modules", "./src/**/*.test.ts", "./src/**/*.spec.ts"] diff --git a/packages/native/tests/shadows.test.ts b/packages/native/tests/shadows.test.ts index ef16479d..5ccb4fe1 100644 --- a/packages/native/tests/shadows.test.ts +++ b/packages/native/tests/shadows.test.ts @@ -43,18 +43,11 @@ const THEME = { }, } as any; -afterAll(() => { +beforeAll(() => { theme.set(THEME); }); describe('shadows', () => { - beforeAll(() => { - theme.set(THEME); - }); - afterAll(() => { - theme.reset(); - }); - test('should generate the property `boxShadow` based on the strong shadow', () => { const result = parsers.resolve({ style: { shadow: 'strong' } }); expect(result).toEqual({ @@ -77,6 +70,7 @@ describe('shadows', () => { test('should generate the property `shadow` based on the medium shadow', () => { const result = parsers.resolve({ style: { shadow: 'medium' } }); + console.log(parsers.cache); expect(result).toEqual({ elevation: 2, shadowColor: 'white', diff --git a/packages/spec/tsconfig.json b/packages/spec/tsconfig.json index e5bb7df9..9ee04b7b 100644 --- a/packages/spec/tsconfig.json +++ b/packages/spec/tsconfig.json @@ -1,7 +1,7 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "outDir": "./build", + "outDir": "./build", }, "include": ["./src"], "exclude": ["./node_modules", "./src/**/*.test.ts", "./src/**/*.spec.ts"] diff --git a/packages/styled-components-web/src/styled.ts b/packages/styled-components-web/src/styled.ts index ca4da972..fd0ec7a7 100644 --- a/packages/styled-components-web/src/styled.ts +++ b/packages/styled-components-web/src/styled.ts @@ -41,6 +41,14 @@ export function attributesParser

( }; } +function getDisplayName(componentName: string, variant?: string) { + if (variant) { + return `${componentName}.${variant}`; + } + + return componentName; +} + const morfeoStyledHandler: MorfeoStyled = ((tag: ComponentTag) => { const { tag: themeTag } = theme.getValue('components', tag as any) || {}; const componentTag = themeTag || tag; @@ -57,9 +65,11 @@ const morfeoStyledHandler: MorfeoStyled = ((tag: ComponentTag) => { return styledFunction(componentProps); } - return styledFunction.attrs(props => - attributesParser(props as any, tag as any), - )( + return styledFunction + .withConfig({ + displayName: getDisplayName(tag, componentProps.variant), + } as any) + .attrs(props => attributesParser(props as any, tag as any))( props => propsParser({ componentName: tag }, componentProps, props) as any, ); diff --git a/packages/styled-components-web/tests/styled.test.tsx b/packages/styled-components-web/tests/styled.test.tsx index c37c60a7..28feb731 100644 --- a/packages/styled-components-web/tests/styled.test.tsx +++ b/packages/styled-components-web/tests/styled.test.tsx @@ -57,7 +57,6 @@ describe('morfeoStyled', () => { const Box = morfeoStyled.Box({}); const tree = renderer.create(Test).toJSON(); expect(tree).toMatchSnapshot(); - expect(tree).toHaveStyleRule('color', 'black'); }); test('should return a component even if default style is not passed', () => { @@ -68,7 +67,7 @@ describe('morfeoStyled', () => { }); test('should change component tag if described inside variant', () => { - const Box = morfeoStyled.Box({}); + const Box = morfeoStyled.Box({ variant: 'primary' }); const tree = renderer.create(Test).toJSON(); expect(tree).toMatchSnapshot(); expect(tree).toHaveProperty('type', 'button'); diff --git a/scripts/bootstrap-app.mjs b/scripts/bootstrap-app.mjs index 91e66c75..a1a15082 100644 --- a/scripts/bootstrap-app.mjs +++ b/scripts/bootstrap-app.mjs @@ -76,7 +76,7 @@ function runBootstrap(appName) { removeInternalPackagesFromPackageJson(appName); shellCommand(`npm`, ['install'], { cwd: `./apps/${appName}` }); restorePackageJson(appName); - shellCommand(`npm`, ['run', 'link'], { cwd: `./apps/${appName}` }); + shellCommand(`node`, ['./scripts/symlink-packages.mjs', appName]); } runBootstrap(APP_NAME); diff --git a/apps/native-sandbox/sym.js b/scripts/symlink-packages.mjs similarity index 71% rename from apps/native-sandbox/sym.js rename to scripts/symlink-packages.mjs index d8486eec..f2e0a4ee 100644 --- a/apps/native-sandbox/sym.js +++ b/scripts/symlink-packages.mjs @@ -1,10 +1,14 @@ -const path = require('path'); -const fs = require('fs'); -const os = require('os'); +import path from 'path'; +import fs from 'fs'; +import os from 'os'; + +const args = process.argv.slice(2); +const [APP_NAME] = args; const symlinkType = os.platform() === 'win32' ? 'junction' : 'dir'; -const packagesPath = path.join(__dirname, '../../packages'); -const rootPath = path.join(__dirname, './node_modules/@morfeo'); +const packagesPath = path.join('./packages'); +const appPath = path.join('apps', APP_NAME); +const rootPath = path.join(appPath, './node_modules/@morfeo'); function getAllInternalPackages() { return fs @@ -15,7 +19,7 @@ function getAllInternalPackages() { function getAppInternalPackages() { const packages = getAllInternalPackages(); - const packageJsonFile = fs.readFileSync(path.join(__dirname, 'package.json')); + const packageJsonFile = fs.readFileSync(path.join(appPath, 'package.json')); const packageJson = JSON.parse(packageJsonFile); const { dependencies, peerDependencies, devDependencies } = packageJson; const allDependencies = { @@ -45,11 +49,11 @@ function linkLocalPack(packName) { } fs.symlink( - path.join(packagesPath, packName), - path.join(rootPath, packName), + path.resolve(packagesPath, packName), + path.resolve(rootPath, packName), symlinkType, function (err) { - console.log(err || packName); + console.log(err || `@morfeo/${packName} symlink done ✅`); }, ); } diff --git a/tsconfig.base.json b/tsconfig.base.json index e0a42955..2014c898 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -5,7 +5,7 @@ "watch": false, "strict": true, "target": "es6", - "module": "es6", + "module": "CommonJS", "allowJs": true, "sourceMap": true, "diagnostics": false,