diff --git a/apps/rollup-example/index.js b/apps/rollup-example/index.js index f6c20d21..2effc50a 100644 --- a/apps/rollup-example/index.js +++ b/apps/rollup-example/index.js @@ -11,9 +11,11 @@ import stylex from '@stylexjs/stylex'; import { lotsOfStyles } from './lotsOfStyles'; +import { lotsOfStylesDynamic } from './lotsOfStylesDynamic.js'; const styles = lotsOfStyles.map((defs) => Object.values(defs)); +const dynamicStyles = lotsOfStylesDynamic.map((defs) => Object.values(defs)); export default function App() { - return stylex.props(styles); + return stylex.props(styles, dynamicStyles); } diff --git a/apps/rollup-example/lotsOfStylesDynamic.js b/apps/rollup-example/lotsOfStylesDynamic.js new file mode 100644 index 00000000..5491eb70 --- /dev/null +++ b/apps/rollup-example/lotsOfStylesDynamic.js @@ -0,0 +1,812 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * + */ + +'use strict'; + +import * as stylex from '@stylexjs/stylex'; + +export const lotsOfStylesDynamic = [ + stylex.create({ + dynamicHeight: (height) => ({ + height, + }), + dynamicPadding: (paddingTop, paddingBottom) => ({ + paddingTop, + paddingBottom, + }), + dynamicTextColor: (textColor) => ({ + color: textColor, + }), + }), + stylex.create({ + dynamicHeightWithStatic: (height) => ({ + height, + backgroundColor: 'var(--background-color)', + }), + dynamicPaddingWithStatic: (paddingTop, paddingBottom) => ({ + paddingTop, + paddingBottom, + margin: '8px', + }), + dynamicTextColorWithStatic: (textColor) => ({ + color: textColor, + fontSize: '16px', + }), + }), + stylex.create({ + dynamicHeight: (height) => ({ height }), + dynamicPadding: (paddingTop, paddingBottom) => ({ + paddingTop, + paddingBottom, + }), + dynamicTextColor: (textColor) => ({ color: textColor }), + dynamicFontSize: (fontSize) => ({ fontSize }), + dynamicFontWeight: (fontWeight) => ({ fontWeight }), + dynamicLineHeight: (lineHeight) => ({ lineHeight }), + dynamicLetterSpacing: (letterSpacing) => ({ letterSpacing }), + dynamicTextTransform: (textTransform) => ({ textTransform }), + dynamicTextDecoration: (textDecoration) => ({ textDecoration }), + }), + stylex.create({ + dynamicFontSizeWithStatic: (fontSize) => ({ + fontSize, + fontFamily: 'Arial, sans-serif', + }), + dynamicFontWeightWithStatic: (fontWeight) => ({ + fontWeight, + fontStyle: 'normal', + }), + dynamicLineHeightWithStatic: (lineHeight) => ({ + lineHeight, + textAlign: 'left', + }), + dynamicLetterSpacingWithStatic: (letterSpacing) => ({ + letterSpacing, + wordWrap: 'break-word', + }), + dynamicTextTransformWithStatic: (textTransform) => ({ + textTransform, + textShadow: 'none', + }), + dynamicTextDecorationWithStatic: (textDecoration) => ({ + textDecoration, + boxSizing: 'border-box', + }), + }), + stylex.create({ + dynamicWidth: (width) => ({ width }), + dynamicMinWidth: (minWidth) => ({ minWidth }), + dynamicMaxWidth: (maxWidth) => ({ maxWidth }), + dynamicHeight2: (height) => ({ height }), + dynamicMinHeight: (minHeight) => ({ minHeight }), + dynamicMaxHeight: (maxHeight) => ({ maxHeight }), + dynamicBorderRadius: (borderRadius) => ({ borderRadius }), + dynamicBoxShadow: (boxShadow) => ({ boxShadow }), + dynamicBackgroundImage: (backgroundImage) => ({ backgroundImage }), + dynamicBackgroundSize: (backgroundSize) => ({ backgroundSize }), + dynamicBackgroundPosition: (backgroundPosition) => ({ backgroundPosition }), + dynamicBackgroundRepeat: (backgroundRepeat) => ({ backgroundRepeat }), + }), + stylex.create({ + dynamicOpacity: (opacity) => ({ opacity }), + dynamicVisibility: (visibility) => ({ visibility }), + dynamicDisplay: (display) => ({ display }), + dynamicPosition: (position) => ({ position }), + dynamicTop: (top) => ({ top }), + dynamicRight: (right) => ({ right }), + dynamicBottom: (bottom) => ({ bottom }), + dynamicLeft: (left) => ({ left }), + dynamicZIndex: (zIndex) => ({ zIndex }), + dynamicOverflow: (overflow) => ({ overflow }), + dynamicOverflowX: (overflowX) => ({ overflowX }), + dynamicOverflowY: (overflowY) => ({ overflowY }), + }), + stylex.create({ + dynamicCursor: (cursor) => ({ cursor }), + dynamicOutline: (outline) => ({ outline }), + dynamicOutlineWidth: (outlineWidth) => ({ outlineWidth }), + dynamicOutlineStyle: (outlineStyle) => ({ outlineStyle }), + dynamicOutlineColor: (outlineColor) => ({ outlineColor }), + dynamicListStyle: (listStyle) => ({ listStyle }), + dynamicListStyleType: (listStyleType) => ({ listStyleType }), + dynamicListStylePosition: (listStylePosition) => ({ listStylePosition }), + dynamicListStyleImage: (listStyleImage) => ({ listStyleImage }), + }), + stylex.create({ + dynamicInput: (caretColor) => ({ + 'caret-color': caretColor, + }), + dynamicDisplayInherit: (display) => ({ + display, + }), + dynamicInherit: ( + alignContent, + alignItems, + flexDirection, + flexGrow, + flexShrink, + height, + justifyContent, + maxHeight, + maxWidth, + minHeight, + minWidth, + position, + width, + ) => ({ + alignContent, + alignItems, + flexDirection, + flexGrow, + flexShrink, + height, + justifyContent, + maxHeight, + maxWidth, + minHeight, + minWidth, + position, + width, + }), + }), + stylex.create({ + dynamicRailContent: (fontSize, margin) => ({ + fontSize, + margin, + }), + dynamicRailItem: (marginBottom) => ({ + marginBottom, + }), + dynamicRoot: (flexGrow, listStyleType, margin) => ({ + flexGrow, + listStyleType, + margin, + }), + dynamicWidgetSet: (display, marginTop) => ({ + display, + marginTop, + }), + }), + stylex.create({ + dynamicContainer: (marginInlineEnd) => ({ + marginInlineEnd, + }), + dynamicKeyInfo: ( + backgroundColor, + borderWidth, + borderStyle, + borderColor, + borderRadius, + marginInlineEnd, + padding, + ) => ({ + backgroundColor, + borderWidth, + borderStyle, + borderColor, + borderRadius, + marginInlineEnd, + padding, + }), + dynamicKeyInfoItem: (marginTop) => ({ + marginTop, + }), + }), + stylex.create({ + dynamicBlueBackground: (backgroundColor, color, padding) => ({ + backgroundColor, + color, + padding, + }), + dynamicRedBackground: (backgroundColor, color, padding) => ({ + backgroundColor, + color, + padding, + }), + dynamicWhiteBackground: (backgroundColor, color, padding) => ({ + backgroundColor, + color, + padding, + }), + dynamicContainer: (borderWidth, borderStyle, marginInlineEnd) => ({ + borderWidth, + borderStyle, + marginInlineEnd, + }), + dynamicInputWrapper: (marginTop) => ({ + marginTop, + }), + }), + stylex.create({ + dynamicGreenBackground: (backgroundColor, color, padding) => ({ + backgroundColor, + color, + padding, + }), + dynamicSection: (marginBottom) => ({ + marginBottom, + }), + }), + stylex.create({ + dynamicKeyInfo: ( + borderWidth, + borderStyle, + borderColor, + borderRadius, + display, + lineHeight, + margin, + minWidth, + padding, + paddingInlineEnd, + paddingInlineStart, + textAlign, + ) => ({ + borderWidth, + borderStyle, + borderColor, + borderRadius, + display, + lineHeight, + margin, + minWidth, + padding, + paddingInlineEnd, + paddingInlineStart, + textAlign, + }), + }), + stylex.create({ + dynamicList: (paddingBottom, paddingTop) => ({ + paddingBottom, + paddingTop, + }), + dynamicListItem: (paddingTop) => ({ + paddingTop, + }), + dynamicPlus: (marginInline) => ({ + marginInline, + }), + }), + stylex.create({ + dynamicWrapperFocusable: (outline) => ({ + ':focus': { + outline, + }, + }), + }), + stylex.create({ + dynamicHeader: (display, flexGrow, flexShrink, flexBasis) => ({ + display, + flexGrow, + flexShrink, + flexBasis, + }), + }), + stylex.create({ + dynamicDialog: (backgroundColor, height, width) => ({ + backgroundColor, + height, + width, + }), + }), + stylex.create({ + dynamicRoot: (backgroundColor, height, width, overflowY) => ({ + backgroundColor, + height, + width, + overflowY, + }), + }), + stylex.create({ + dynamicContainer: ( + backgroundColor, + display, + flexDirection, + height, + width, + ) => ({ + backgroundColor, + display, + flexDirection, + height, + width, + }), + dynamicColumnLayout: (display, flexGrow, flexShrink, minHeight) => ({ + display, + flexGrow, + flexShrink, + minHeight, + }), + dynamicBodyContainer: (width, flexGrow, display, justifyContent) => ({ + width, + flexGrow, + display, + justifyContent, + }), + dynamicCardWrapper: ( + display, + flexDirection, + flexShrink, + flexGrow, + minWidth, + margin, + ) => ({ + display, + flexDirection, + flexShrink, + flexGrow, + minWidth, + margin, + }), + dynamicRoot: ( + backgroundColor, + borderRadius, + boxShadow, + display, + marginBlock, + minHeight, + maxWidth, + width, + flexGrow, + alignSelf, + flexDirection, + overflow, + ) => ({ + backgroundColor, + borderRadius, + boxShadow, + display, + marginBlock, + minHeight, + maxWidth, + width, + flexGrow, + alignSelf, + flexDirection, + overflow, + }), + }), + stylex.create({ + dynamicButton: (width, height, marginInlineEnd, borderRadius) => ({ + width, + height, + marginInlineEnd, + borderRadius, + }), + dynamicBody: (width, height, borderRadius, marginBlock) => ({ + width, + height, + borderRadius, + marginBlock, + }), + dynamicMeta: (width, height, borderRadius) => ({ + width, + height, + borderRadius, + }), + }), + stylex.create({ + dynamicHeader: (display, flexGrow, flexShrink, flexBasis) => ({ + display, + flexGrow, + flexShrink, + flexBasis, + }), + dynamicHeaderContainer: ( + backgroundColor, + borderBottomWidth, + borderBottomStyle, + borderBottomColor, + textAlign, + padding, + width, + ) => ({ + backgroundColor, + borderBottomWidth, + borderBottomStyle, + borderBottomColor, + textAlign, + padding, + width, + }), + dynamicPlaceholder: (marginInlineStart, marginTop) => ({ + marginInlineStart, + marginTop, + }), + dynamicPlaceholderWithBugNub: (marginInlineStart, marginTop) => ({ + marginInlineStart, + marginTop, + }), + dynamicTitle: (maxWidth) => ({ + maxWidth, + }), + }), + stylex.create({ + dynamicShareFeedbackSticky: (position, end, bottom, zIndex) => ({ + position, + end, + bottom, + zIndex, + }), + dynamicRoot: (height) => ({ + height, + }), + dynamicScrollable: (overflowY, padding, height) => ({ + overflowY, + padding, + height, + }), + dynamicContent: (display, flexDirection, width, margin) => ({ + display, + flexDirection, + width, + margin, + }), + dynamicEditor: ( + boxSizing, + backgroundColor, + borderRadius, + boxShadow, + display, + marginTop, + marginBottom, + maxWidth, + width, + minHeight, + flexGrow, + alignSelf, + flexDirection, + padding, + ) => ({ + boxSizing, + backgroundColor, + borderRadius, + boxShadow, + display, + marginTop, + marginBottom, + maxWidth, + width, + minHeight, + flexGrow, + alignSelf, + flexDirection, + padding, + }), + dynamicHeader: (mediaPrintDisplay) => ({ + '@media print': { + display: mediaPrintDisplay, + }, + }), + dynamicEditorContainer: (display, justifyContent, height) => ({ + display, + justifyContent, + height, + }), + dynamicEditorInnerContainer: (width, zIndex) => ({ + width, + zIndex, + }), + dynamicSidebarContainer: ( + width, + flexShrink, + paddingTop, + backgroundColor, + overflowY, + height, + ) => ({ + width, + flexShrink, + paddingTop, + backgroundColor, + overflowY, + height, + }), + dynamicInnerContainer: (marginTop, height) => ({ + marginTop, + height, + }), + dynamicNoteContainer: (display, flexDirection, height) => ({ + display, + flexDirection, + height, + }), + }), + stylex.create({ + dynamicPhotoStyle: (paddingTop, height) => ({ + paddingTop, + height, + }), + }), + stylex.create({ + dynamicMetricCardContent: ( + borderTopStyle, + borderInlineStartStyle, + borderInlineEndStyle, + borderBottomStyle, + borderTopWidth, + borderInlineStartWidth, + borderInlineEndWidth, + borderBottomWidth, + boxSizing, + display, + flexGrow, + flexShrink, + marginTop, + marginInlineEnd, + marginBottom, + marginInlineStart, + minHeight, + minWidth, + paddingTop, + paddingInlineEnd, + paddingBottom, + paddingInlineStart, + position, + zIndex, + flexDirection, + justifyContent, + alignItems, + height, + ) => ({ + borderTopStyle, + borderInlineStartStyle, + borderInlineEndStyle, + borderBottomStyle, + borderTopWidth, + borderInlineStartWidth, + borderInlineEndWidth, + borderBottomWidth, + boxSizing, + display, + flexGrow, + flexShrink, + marginTop, + marginInlineEnd, + marginBottom, + marginInlineStart, + minHeight, + minWidth, + paddingTop, + paddingInlineEnd, + paddingBottom, + paddingInlineStart, + position, + zIndex, + flexDirection, + justifyContent, + alignItems, + height, + }), + dynamicReactionRoot: ( + display, + alignItems, + paddingInlineStart, + marginInlineEnd, + ) => ({ + display, + alignItems, + paddingInlineStart, + marginInlineEnd, + }), + dynamicReactionContainer: ( + width, + height, + borderColor, + borderRadius, + borderStyle, + borderWidth, + marginInlineStart, + position, + ) => ({ + width, + height, + borderColor, + borderRadius, + borderStyle, + borderWidth, + marginInlineStart, + position, + }), + dynamicIconContainer: ( + marginInlineEnd, + width, + height, + alignItems, + borderRadius, + borderWidth, + boxSizing, + display, + justifyContent, + padding, + position, + ) => ({ + marginInlineEnd, + width, + height, + alignItems, + borderRadius, + borderWidth, + boxSizing, + display, + justifyContent, + padding, + position, + }), + dynamicIconColorViewers: (backgroundColor) => ({ backgroundColor }), + dynamicIconColorComments: (backgroundColor) => ({ backgroundColor }), + dynamicIconColorQuestions: (backgroundColor) => ({ backgroundColor }), + dynamicIconColorLikeReaction: (backgroundColor) => ({ backgroundColor }), + }), + stylex.create({ + dynamicContainer: (backgroundColor, height) => ({ + backgroundColor, + height, + }), + }), + stylex.create({ + dynamicRoot: (backgroundColor, width, height, maxHeight) => ({ + backgroundColor, + width, + height, + maxHeight, + }), + }), + stylex.create({ + dynamicContainer: (backgroundColor, display, height, margin, width) => ({ + backgroundColor, + display, + height, + margin, + width, + }), + dynamicBackgroundTeams: (backgroundColor) => ({ backgroundColor }), + }), + stylex.create({ + dynamicTitle: (width, borderRadius, height) => ({ + width, + borderRadius, + height, + }), + dynamicSubtitle: (width, borderRadius, height) => ({ + width, + borderRadius, + height, + }), + }), + stylex.create({ + dynamicDefaultResponsiveWidth: (width, minWidth, maxWidth) => ({ + width, + minWidth, + maxWidth, + }), + dynamicContentScroll: (overflowY, paddingInline, height) => ({ + overflowY, + paddingInline, + height, + }), + dynamicMsteams: (marginInlineStart) => ({ marginInlineStart }), + }), + stylex.create({ + dynamicGlimmer: (height) => ({ height }), + dynamicIcon: (marginInlineEnd, position, top) => ({ + marginInlineEnd, + position, + top, + }), + dynamicOuterCard: (backgroundColor, height, borderRadius) => ({ + backgroundColor, + height, + borderRadius, + }), + dynamicBackgroundTeams: (backgroundColor) => ({ backgroundColor }), + }), + stylex.create({ + dynamicBody: (marginTop) => ({ marginTop }), + }), + stylex.create({ + dynamicVideoOptionsCard: ( + padding, + marginBottom, + borderRadius, + boxShadow, + ) => ({ + padding, + marginBottom, + borderRadius, + boxShadow, + }), + }), + stylex.create({ + padding: { + paddingBottom: 'var(--p-space-4)', + paddingInline: 'var(--p-space-4)', + paddingTop: 'var(--p-space-2)', + }, + dynamicPadding: (padding) => ({ + padding, + }), + }), + stylex.create({ + vert16: { + paddingBlock: 16, + }, + dynamicVert: (paddingBlock) => ({ + paddingBlock, + }), + }), + stylex.create({ + item: { + listStyleType: 'disc', + }, + dynamicItem: (listStyleType) => ({ + listStyleType, + }), + }), + stylex.create({ + container: { + display: 'flex', + flexDirection: 'column', + height: '100%', + justifyContent: 'space-between', + padding: '0px 16px', + }, + dynamicContainer: ( + display, + flexDirection, + height, + justifyContent, + padding, + ) => ({ + display, + flexDirection, + height, + justifyContent, + padding, + }), + }), + stylex.create({ + root: { + backgroundColor: 'var(--surface-background)', + borderRadius: 8, + boxShadow: '0 2px 12px var(--shadow-2)', + boxSizing: 'border-box', + display: 'flex', + flexDirection: 'column', + height: '100%', + justifyContent: 'space-between', + padding: '16px', + width: '100%', + }, + dynamicRoot: ( + backgroundColor, + borderRadius, + boxShadow, + boxSizing, + display, + flexDirection, + height, + justifyContent, + padding, + width, + ) => ({ + backgroundColor, + borderRadius, + boxShadow, + boxSizing, + display, + flexDirection, + height, + justifyContent, + padding, + width, + }), + }), +]; diff --git a/packages/babel-plugin/__tests__/evaluation/stylex-import-evaluation-test.js b/packages/babel-plugin/__tests__/evaluation/stylex-import-evaluation-test.js index c7034ba5..bdaa48a9 100644 --- a/packages/babel-plugin/__tests__/evaluation/stylex-import-evaluation-test.js +++ b/packages/babel-plugin/__tests__/evaluation/stylex-import-evaluation-test.js @@ -369,7 +369,7 @@ describe('Evaluation of imported values works based on configuration', () => { import 'otherFile.stylex'; import { MyTheme } from 'otherFile.stylex'; _inject2(".__hashed_var__b69i2g{--__hashed_var__1jqb1tb:var(----__hashed_var__1jqb1tb)}", 1); - _inject2("@property ----__hashed_var__1jqb1tb { inherits: false }", 0); + _inject2("@property ----__hashed_var__1jqb1tb { syntax: \\"*\\"; inherits: false; initial-value: \\"*\\";}", 0); const styles = { color: color => [{ "--__hashed_var__1jqb1tb": color == null ? null : "__hashed_var__b69i2g", @@ -393,7 +393,7 @@ describe('Evaluation of imported values works based on configuration', () => { [ "----__hashed_var__1jqb1tb", { - "ltr": "@property ----__hashed_var__1jqb1tb { inherits: false }", + "ltr": "@property ----__hashed_var__1jqb1tb { syntax: "*"; inherits: false; initial-value: "*";}", "rtl": null, }, 0, diff --git a/packages/babel-plugin/__tests__/stylex-transform-create-test.js b/packages/babel-plugin/__tests__/stylex-transform-create-test.js index 985a3f0e..a16c0439 100644 --- a/packages/babel-plugin/__tests__/stylex-transform-create-test.js +++ b/packages/babel-plugin/__tests__/stylex-transform-create-test.js @@ -1323,7 +1323,7 @@ describe('@stylexjs/babel-plugin', () => { import stylex from 'stylex'; _inject2(".xrkmrrc{background-color:red}", 3000); _inject2(".xfx01vb{color:var(--color)}", 3000); - _inject2("@property --color { inherits: false }", 0); + _inject2("@property --color { syntax: \\"*\\"; inherits: false; initial-value: \\"*\\";}", 0); export const styles = { default: color => [{ backgroundColor: "xrkmrrc", @@ -1353,7 +1353,7 @@ describe('@stylexjs/babel-plugin', () => { import stylex from 'stylex'; _inject2(".xrkmrrc{background-color:red}", 3000); _inject2(".x1bl4301{width:var(--width)}", 4000); - _inject2("@property --width { inherits: false }", 0); + _inject2("@property --width { syntax: \\"*\\"; inherits: false; initial-value: \\"*\\";}", 0); export const styles = { default: width => [{ backgroundColor: "xrkmrrc", @@ -1387,7 +1387,7 @@ describe('@stylexjs/babel-plugin', () => { _inject2(".xrkmrrc{background-color:red}", 3000); _inject2(".xfx01vb{color:var(--color)}", 3000); _inject2(".x1mqxbix{color:black}", 3000); - _inject2("@property --color { inherits: false }", 0); + _inject2("@property --color { syntax: \\"*\\"; inherits: false; initial-value: \\"*\\";}", 0); export const styles = { default: color => [{ backgroundColor: "xrkmrrc", @@ -1419,7 +1419,7 @@ describe('@stylexjs/babel-plugin', () => { var _inject2 = _inject; import stylex from 'stylex'; _inject2(".x15mgraa{--background-color:var(----background-color)}", 1); - _inject2("@property ----background-color { inherits: false }", 0); + _inject2("@property ----background-color { syntax: \\"*\\"; inherits: false; initial-value: \\"*\\";}", 0); export const styles = { default: bgColor => [{ "--background-color": bgColor == null ? null : "x15mgraa", @@ -1449,7 +1449,7 @@ describe('@stylexjs/babel-plugin', () => { import stylex from 'stylex'; _inject2(".x1gykpug:hover{background-color:red}", 3130); _inject2(".xtyu0qe:hover{color:var(--1ijzsae)}", 3130); - _inject2("@property --1ijzsae { inherits: false }", 0); + _inject2("@property --1ijzsae { syntax: \\"*\\"; inherits: false; initial-value: \\"*\\";}", 0); export const styles = { default: color => [{ ":hover_backgroundColor": "x1gykpug", @@ -1482,7 +1482,7 @@ describe('@stylexjs/babel-plugin', () => { _inject2(".xrkmrrc{background-color:red}", 3000); _inject2(".xfx01vb{color:var(--color)}", 3000); _inject2(".x1mqxbix{color:black}", 3000); - _inject2("@property --color { inherits: false }", 0); + _inject2("@property --color { syntax: \\"*\\"; inherits: false; initial-value: \\"*\\";}", 0); export const styles = { default: color => [{ backgroundColor: "xrkmrrc", @@ -1514,7 +1514,7 @@ describe('@stylexjs/babel-plugin', () => { var _inject2 = _inject; import stylex from 'stylex'; _inject2(".x15mgraa{--background-color:var(----background-color)}", 1); - _inject2("@property ----background-color { inherits: false }", 0); + _inject2("@property ----background-color { syntax: \\"*\\"; inherits: false; initial-value: \\"*\\";}", 0); export const styles = { default: bgColor => [{ "--background-color": bgColor == null ? null : "x15mgraa", @@ -1550,7 +1550,7 @@ describe('@stylexjs/babel-plugin', () => { _inject2(".x1n25116{color:var(--4xs81a)}", 3000); _inject2("@media (min-width: 1000px){.xtljkjt.xtljkjt:hover{color:green}}", 3330); _inject2(".x17z2mba:hover{color:blue}", 3130); - _inject2("@property --4xs81a { inherits: false }", 0); + _inject2("@property --4xs81a { syntax: \\"*\\"; inherits: false; initial-value: \\"*\\";}", 0); export const styles = { default: color => [{ backgroundColor: "xrkmrrc", @@ -1587,8 +1587,8 @@ describe('@stylexjs/babel-plugin', () => { _inject2(".x1n25116{color:var(--4xs81a)}", 3000); _inject2("@media (min-width: 1000px){.xtljkjt.xtljkjt:hover{color:green}}", 3330); _inject2(".x1d4gdy3:hover{color:var(--w5m4kq)}", 3130); - _inject2("@property --4xs81a { inherits: false }", 0); - _inject2("@property --w5m4kq { inherits: false }", 0); + _inject2("@property --4xs81a { syntax: \\"*\\"; inherits: false; initial-value: \\"*\\";}", 0); + _inject2("@property --w5m4kq { syntax: \\"*\\"; inherits: false; initial-value: \\"*\\";}", 0); export const styles = { default: color => [{ backgroundColor: "xrkmrrc", @@ -1632,9 +1632,9 @@ describe('@stylexjs/babel-plugin', () => { _inject2(".x1k44ad6{margin-left:var(--14mfytm)}", 3000, ".x1k44ad6{margin-right:var(--14mfytm)}"); _inject2(".x10ktymb:hover{margin-left:var(--yepcm9)}", 3130, ".x10ktymb:hover{margin-right:var(--yepcm9)}"); _inject2(".x17zef60{margin-top:var(--marginTop)}", 4000); - _inject2("@property --14mfytm { inherits: false }", 0); - _inject2("@property --yepcm9 { inherits: false }", 0); - _inject2("@property --marginTop { inherits: false }", 0); + _inject2("@property --14mfytm { syntax: \\"*\\"; inherits: false; initial-value: \\"*\\";}", 0); + _inject2("@property --yepcm9 { syntax: \\"*\\"; inherits: false; initial-value: \\"*\\";}", 0); + _inject2("@property --marginTop { syntax: \\"*\\"; inherits: false; initial-value: \\"*\\";}", 0); export const styles = { default: margin => [{ backgroundColor: "xrkmrrc", diff --git a/packages/babel-plugin/src/visitors/stylex-create/index.js b/packages/babel-plugin/src/visitors/stylex-create/index.js index a3511f61..328b696f 100644 --- a/packages/babel-plugin/src/visitors/stylex-create/index.js +++ b/packages/babel-plugin/src/visitors/stylex-create/index.js @@ -127,7 +127,7 @@ export default function transformStyleXCreate( dynamicFnsNames.forEach((fnsName) => { injectedInheritStyles[fnsName] = { priority: 0, - ltr: `@property ${fnsName} { inherits: false }`, + ltr: `@property ${fnsName} { syntax: "*"; inherits: false; initial-value: "*";}`, rtl: null, }; }); diff --git a/packages/cli/__tests__/compile-stylex-folder-test.js b/packages/cli/__tests__/compile-stylex-folder-test.js index aee94059..2287047a 100644 --- a/packages/cli/__tests__/compile-stylex-folder-test.js +++ b/packages/cli/__tests__/compile-stylex-folder-test.js @@ -15,7 +15,7 @@ import { compileDirectory } from '../src/transform'; import * as cacheModule from '../src/cache'; import { getDefaultCachePath } from '../src/cache'; -import fs from 'fs'; +const fs = require('fs').promises; import { isDir, getRelativePath } from '../src/files'; import * as path from 'path'; @@ -23,36 +23,48 @@ const cp = require('child_process'); process.chdir('__tests__/__mocks__'); -function clearTestDir(config: CliConfig) { +async function clearTestDir(config: CliConfig) { for (const output of config.output) { - fs.rmSync(output, { recursive: true, force: true }); + await fs.rm(output, { recursive: true, force: true }); } } -function runCli(args: string, config: CliConfig, onClose: () => void) { +function runCli( + args: string, + config: CliConfig, + onClose: () => void | Promise, +) { const cmd = 'node ' + path.resolve('../../lib/index.js ') + args; console.log(cmd); const script = cp.exec(cmd); - script.addListener('error', (err) => { - clearTestDir(config); + + script.addListener('error', async (err) => { + await clearTestDir(config); throw err; }); - script.stderr.on('data', (data) => { + + script.stderr.on('data', async (data) => { process.kill(script.pid); - clearTestDir(config); + await clearTestDir(config); throw new Error('failed to start StyleX CLI', data); }); - script.addListener('close', onClose); + + script.addListener('close', () => { + Promise.resolve(onClose()).catch((err) => { + throw new Error(`Error in onClose callback: ${err.message}`); + }); + }); } const snapshot = './snapshot'; const cachePath = getDefaultCachePath(); describe('compiling __mocks__/source to __mocks__/src correctly such that it matches __mocks__/snapshot', () => { - afterAll(() => { - fs.rmSync(config.output, { recursive: true, force: true }); - fs.rmSync(cachePath, { recursive: true, force: true }); + afterAll(async () => { + await fs.rm(config.output, { recursive: true, force: true }); + await fs.rm(cachePath, { recursive: true, force: true }); }); + // need to resolve to absolute paths because the compileDirectory function is expecting them. const config: TransformConfig = { input: path.resolve('./source'), @@ -70,24 +82,31 @@ describe('compiling __mocks__/source to __mocks__/src correctly such that it mat }, }; - afterAll(() => fs.rmSync(config.output, { recursive: true, force: true })); + afterAll(async () => { + await fs.rm(config.output, { recursive: true, force: true }); + }); - test(config.input, () => { - expect(isDir(config.input)).toBe(true); + test(config.input, async () => { + expect(await isDir(config.input)).toBe(true); }); test(config.output, async () => { - fs.mkdirSync(config.output, { recursive: true }); - expect(isDir(config.output)).toBe(true); + await fs.mkdir(config.output, { recursive: true }); + expect(await isDir(config.output)).toBe(true); await compileDirectory(config); - const outputDir = fs.readdirSync(config.output, { recursive: true }); + const outputDir = await fs.readdir(config.output); for (const file of outputDir) { const outputPath = path.join(config.output, file); const snapshotPath = path.join(snapshot, file); - expect(fs.existsSync(snapshotPath)).toBe(true); + expect( + await fs + .access(snapshotPath) + .then(() => true) + .catch(() => false), + ).toBe(true); if (path.extname(outputPath) === '.js') { - const outputContent = fs.readFileSync(outputPath).toString(); - const snapshotContent = fs.readFileSync(snapshotPath).toString(); + const outputContent = await fs.readFile(outputPath, 'utf-8'); + const snapshotContent = await fs.readFile(snapshotPath, 'utf-8'); expect(outputContent).toEqual(snapshotContent); } } @@ -103,23 +122,29 @@ describe('cli works with -i and -o args', () => { watch: false, babelPresets: [], }; - afterAll(() => clearTestDir(config)); + afterAll(async () => await clearTestDir(config)); + test('script start', (done) => { - const onClose = () => { + const onClose = async () => { for (const dir of config.output) { - const outputDir = fs.readdirSync(dir, { recursive: true }); + const outputDir = await fs.readdir(dir); for (const file of outputDir) { const snapshotDir = path.resolve(path.join(snapshot, file)); - expect(fs.existsSync(snapshotDir)).toBe(true); + expect( + await fs + .access(snapshotDir) + .then(() => true) + .catch(() => false), + ).toBe(true); const outputPath = path.join(dir, file); if (path.extname(outputPath) === '.js') { - const outputContent = fs.readFileSync(outputPath).toString(); - const snapshotContent = fs.readFileSync(snapshotDir).toString(); + const outputContent = await fs.readFile(outputPath, 'utf-8'); + const snapshotContent = await fs.readFile(snapshotDir, 'utf-8'); expect(outputContent).toEqual(snapshotContent); } } } - clearTestDir(config); + await clearTestDir(config); done(); }; @@ -132,16 +157,22 @@ describe('cli works with -i and -o args', () => { input: [path.resolve(config.input[0])], output: [path.resolve(config.output[0])], }; - const onClose = () => { - expect(fs.existsSync(config.output[0])).toBe(true); + const onClose = async () => { + expect( + await fs + .access(config.output[0]) + .then(() => true) + .catch(() => false), + ).toBe(true); done(); }; - clearTestDir(absConfig); - runCli( - `-i ${absConfig.input[0]} -o ${absConfig.output[0]}`, - absConfig, - onClose, - ); + clearTestDir(absConfig).then(() => { + runCli( + `-i ${absConfig.input[0]} -o ${absConfig.output[0]}`, + absConfig, + onClose, + ); + }); }, 10000); }); @@ -154,15 +185,16 @@ describe('cli works with multiple inputs and outputs', () => { watch: false, babelPresets: [], }; - afterAll(() => clearTestDir(config)); + afterAll(async () => await clearTestDir(config)); + test('script compiles multiple directories', (done) => { - const onClose = () => { + const onClose = async () => { let isSecondOutput = false; for (const dir of config.output) { if (dir.endsWith('src2')) { isSecondOutput = true; } - const outputDir = fs.readdirSync(dir, { recursive: true }); + const outputDir = await fs.readdir(dir); for (const file of outputDir) { if (isSecondOutput) { expect(file).not.toContain(config.styleXBundleName); @@ -170,15 +202,20 @@ describe('cli works with multiple inputs and outputs', () => { const outputPath = path.join(dir, file); const snapshotDir = isSecondOutput ? snapshot + '2' : snapshot; const snapshotPath = path.join(snapshotDir, file); - expect(fs.existsSync(snapshotPath)).toBe(true); + expect( + await fs + .access(snapshotPath) + .then(() => true) + .catch(() => false), + ).toBe(true); if (path.extname(outputPath) === '.js') { - const outputContent = fs.readFileSync(outputPath).toString(); - const snapshotContent = fs.readFileSync(snapshotPath).toString(); + const outputContent = await fs.readFile(outputPath, 'utf-8'); + const snapshotContent = await fs.readFile(snapshotPath, 'utf-8'); expect(outputContent).toEqual(snapshotContent); } } } - clearTestDir(config); + await clearTestDir(config); done(); }; const input = config.input.join(' '); @@ -194,7 +231,7 @@ describe('individual testing of util functions', () => { output: './src', cssBundleName: 'stylex_bundle.css', }; - test('file to relative css path', () => { + test('file to relative css path', async () => { const mockFileName = './src/pages/home/page.js'; const relativePath = getRelativePath( mockFileName, @@ -221,7 +258,6 @@ describe('cache mechanism works as expected', () => { copiedNodeModules: false, }, }; - beforeEach(() => { writeSpy = jest.spyOn(cacheModule, 'writeCache'); }); @@ -230,25 +266,27 @@ describe('cache mechanism works as expected', () => { writeSpy.mockRestore(); }); - afterAll(() => { - fs.rmSync(config.output, { recursive: true, force: true }); - fs.rmSync(cachePath, { recursive: true, force: true }); + afterAll(async () => { + await fs.rm(config.output, { recursive: true, force: true }); + await fs.rm(cachePath, { recursive: true, force: true }); }); test('first compilation populates the cache', async () => { - fs.mkdirSync(config.output, { recursive: true }); - fs.mkdirSync(cachePath, { recursive: true }); + await fs.mkdir(config.output, { recursive: true }); + await fs.mkdir(cachePath, { recursive: true }); writeSpy = jest.spyOn(cacheModule, 'writeCache'); await compileDirectory(config); expect(writeSpy).toHaveBeenCalledTimes(3); - const cacheFiles = fs.readdirSync(cachePath); + const cacheFiles = await fs.readdir(cachePath); expect(cacheFiles.length).toEqual(3); for (const cacheFile of cacheFiles) { const cacheFilePath = path.join(cachePath, cacheFile); - const cacheContent = JSON.parse(fs.readFileSync(cacheFilePath, 'utf-8')); + const cacheContent = JSON.parse( + await fs.readFile(cacheFilePath, 'utf-8'), + ); expect(cacheContent).toHaveProperty('inputHash'); expect(cacheContent).toHaveProperty('outputHash'); expect(cacheContent).toHaveProperty('collectedCSS'); @@ -267,18 +305,21 @@ describe('cache mechanism works as expected', () => { const mockFilePath = path.join(config.input, 'index.js'); const mockFileOutputPath = path.join(config.output, 'index.js'); const newContent = 'console.log("Updated content");'; - const originalContent = fs.readFileSync(mockFilePath, 'utf-8'); - const originalOutputContent = fs.readFileSync(mockFileOutputPath, 'utf-8'); + const originalContent = await fs.readFile(mockFilePath, 'utf-8'); + const originalOutputContent = await fs.readFile( + mockFileOutputPath, + 'utf-8', + ); - fs.appendFileSync(mockFilePath, newContent, 'utf-8'); + await fs.appendFile(mockFilePath, newContent, 'utf-8'); await compileDirectory(config); // Ensure index.js is rewritten due to cache invalidation expect(writeSpy).toHaveBeenCalledTimes(1); - fs.writeFileSync(mockFilePath, originalContent, 'utf-8'); - fs.writeFileSync(mockFileOutputPath, originalOutputContent, 'utf-8'); + await fs.writeFile(mockFilePath, originalContent, 'utf-8'); + await fs.writeFile(mockFileOutputPath, originalOutputContent, 'utf-8'); writeSpy.mockRestore(); }); @@ -304,16 +345,26 @@ describe('CLI works with a custom cache path', () => { }; config.cachePath = customCachePath; - beforeEach(() => { - if (fs.existsSync(customCachePath)) { - fs.rmSync(customCachePath, { recursive: true, force: true }); + beforeEach(async () => { + if ( + await fs + .access(customCachePath) + .then(() => true) + .catch(() => false) + ) { + await fs.rm(customCachePath, { recursive: true, force: true }); } }); - afterAll(() => { - fs.rmSync(config.output, { recursive: true, force: true }); - if (fs.existsSync(customCachePath)) { - fs.rmSync(customCachePath, { recursive: true, force: true }); + afterAll(async () => { + await fs.rm(config.output, { recursive: true, force: true }); + if ( + await fs + .access(customCachePath) + .then(() => true) + .catch(() => false) + ) { + await fs.rm(customCachePath, { recursive: true, force: true }); } }); @@ -327,14 +378,24 @@ describe('CLI works with a custom cache path', () => { path.relative(config.input, customFilePath) + '.json', ); - expect(fs.existsSync(customCachePath)).toBe(true); - expect(fs.existsSync(cacheFilePath)).toBe(true); - - const cacheData = JSON.parse(fs.readFileSync(cacheFilePath, 'utf-8')); + expect( + await fs + .access(customCachePath) + .then(() => true) + .catch(() => false), + ).toBe(true); + expect( + await fs + .access(cacheFilePath) + .then(() => true) + .catch(() => false), + ).toBe(true); + + const cacheData = JSON.parse(await fs.readFile(cacheFilePath, 'utf-8')); expect(cacheData).toHaveProperty('inputHash'); expect(cacheData).toHaveProperty('outputHash'); expect(cacheData).toHaveProperty('collectedCSS'); - fs.rmSync(cacheFilePath, { recursive: true, force: true }); + await fs.rm(cacheFilePath, { recursive: true, force: true }); }); }); diff --git a/packages/eslint-plugin/__tests__/stylex-valid-styles-test.js b/packages/eslint-plugin/__tests__/stylex-valid-styles-test.js index e9a9f4c1..78ef1011 100644 --- a/packages/eslint-plugin/__tests__/stylex-valid-styles-test.js +++ b/packages/eslint-plugin/__tests__/stylex-valid-styles-test.js @@ -39,6 +39,21 @@ eslintTester.run('stylex-valid-styles', rule.default, { } }); `, + ` + import stylex from "stylex"; + const styles = stylex.create({ + validStyle: { + marginInlineStart: "10px", + marginInlineEnd: "5px", + marginInline: "15px", + marginBlock: "20px", + paddingInlineStart: "8px", + paddingInlineEnd: "12px", + paddingInline: "10px", + paddingBlock: "16px", + }, + }); + `, ` import stylex from 'stylex'; const start = 'start'; @@ -583,6 +598,72 @@ eslintTester.run('stylex-valid-styles', rule.default, { }, ], }, + { + code: ` + import stylex from 'stylex'; + const styles = stylex.create({ + invalidStyle: { + marginStart: '10px', + marginEnd: '5px', + marginHorizontal: '15px', + marginVertical: '15px', + paddingStart: '10px', + paddingEnd: '5px', + paddingHorizontal: '5px', + paddingVertical: '15px', + }, + }); + `, + errors: [ + { + message: + 'The key "marginStart" is not a standard CSS property. Did you mean "marginInlineStart"?', + }, + { + message: + 'The key "marginEnd" is not a standard CSS property. Did you mean "marginInlineEnd"?', + }, + { + message: + 'The key "marginHorizontal" is not a standard CSS property. Did you mean "marginInline"?', + }, + { + message: + 'The key "marginVertical" is not a standard CSS property. Did you mean "marginBlock"?', + }, + { + message: + 'The key "paddingStart" is not a standard CSS property. Did you mean "paddingInlineStart"?', + }, + { + message: + 'The key "paddingEnd" is not a standard CSS property. Did you mean "paddingInlineEnd"?', + }, + { + message: + 'The key "paddingHorizontal" is not a standard CSS property. Did you mean "paddingInline"?', + }, + { + message: + 'The key "paddingVertical" is not a standard CSS property. Did you mean "paddingBlock"?', + }, + ], + output: ` + import stylex from 'stylex'; + const styles = stylex.create({ + invalidStyle: { + marginInlineStart: '10px', + marginInlineEnd: '5px', + marginInline: '15px', + marginBlock: '15px', + paddingInlineStart: '10px', + paddingInlineEnd: '5px', + paddingInline: '5px', + paddingBlock: '15px', + }, + }); + `, + }, { code: "import stylex from 'stylex'; stylex.create({default: {textAlign: 'lfet'}});", errors: [ diff --git a/packages/eslint-plugin/src/stylex-valid-styles.js b/packages/eslint-plugin/src/stylex-valid-styles.js index 2f36eddf..584a5ddb 100644 --- a/packages/eslint-plugin/src/stylex-valid-styles.js +++ b/packages/eslint-plugin/src/stylex-valid-styles.js @@ -1595,6 +1595,17 @@ const SupportedVendorSpecificCSSProperties = { ), }; +const validStyleMapping: $ReadOnly<{ [key: string]: ?string }> = { + marginStart: 'marginInlineStart', + marginEnd: 'marginInlineEnd', + marginHorizontal: 'marginInline', + marginVertical: 'marginBlock', + paddingStart: 'paddingInlineStart', + paddingEnd: 'paddingInlineEnd', + paddingHorizontal: 'paddingInline', + paddingVertical: 'paddingBlock', +}; + const SVGProperties = { colorInterpolation: makeUnionRule('auto', 'sRGB', 'linearRGB'), // colorRendering: color, @@ -2292,6 +2303,7 @@ const stylexValidStyles = { meta: { type: 'problem', hasSuggestions: true, + fixable: 'code', docs: { descriptions: 'Enforce that you create valid stylex styles', category: 'Possible Errors', @@ -2635,10 +2647,41 @@ const stylexValidStyles = { const distance = getDistance(key, cssProp, 2); return distance <= 2; }); + + const replacementKey = + style.key.type === 'Identifier' && validStyleMapping[style.key.name] + ? validStyleMapping[style.key.name] + : style.key.type === 'Literal' && + typeof style.key.value === 'string' && + validStyleMapping[style.key.value] + ? validStyleMapping[style.key.value] + : null; + + let originalKey = ''; + + if (style.key.type === 'Identifier') { + originalKey = style.key.name; + } else if ( + style.key.type === 'Literal' && + typeof style.key.value === 'string' + ) { + originalKey = style.key.value; + } + return context.report({ node: style.key, loc: style.key.loc, - message: 'This is not a key that is allowed by stylex', + message: + replacementKey && + (style.key.type === 'Identifier' || style.key.type === 'Literal') + ? `The key "${originalKey}" is not a standard CSS property. Did you mean "${replacementKey}"?` + : 'This is not a key that is allowed by stylex', + fix: (fixer) => { + if (replacementKey) { + return fixer.replaceText(style.key, replacementKey); + } + return null; + }, suggest: closestKey != null ? [ diff --git a/typos.toml b/typos.toml index 8551b792..61bae8b8 100644 --- a/typos.toml +++ b/typos.toml @@ -2,9 +2,10 @@ extend-exclude = [ "*.snap", "apps/rollup-example/lotsOfStyles.js", + "apps/rollup-example/lotsOfStylesDynamic.js", "flow-typed/*", ] [default.extend-words] # Ignore false-positives -stylex = "stylex" +stylex = "stylex" \ No newline at end of file