Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: atlassian-labs/compiled
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: @compiled/react@0.13.1
Choose a base ref
...
head repository: atlassian-labs/compiled
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: @compiled/react@0.14.0
Choose a head ref

Commits on May 1, 2023

  1. Throw error when expressionToString runs infinite loop (#1450)

    * Throw error when expressionToString runs infinite loop
    dddlr authored May 1, 2023

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    9aa6909 View commit details
  2. chore(deps): update babel monorepo to ^7.21.5 (#1451)

    Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
    renovate[bot] authored May 1, 2023

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    8675711 View commit details

Commits on May 2, 2023

  1. Version Packages (#1452)

    Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
    atlas-dst-bot and github-actions[bot] authored May 2, 2023

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    57e2088 View commit details

Commits on May 8, 2023

  1. chore(deps): update babel monorepo to ^7.21.8 (#1453)

    Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
    renovate[bot] authored May 8, 2023

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    cad98fe View commit details

Commits on May 15, 2023

  1. chore(deps): update definitelytyped (#1454)

    Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
    renovate[bot] authored May 15, 2023

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    c15a0bf View commit details

Commits on May 22, 2023

  1. chore(deps): update dependency @changesets/cli to ^2.26.1 (#1456)

    Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
    renovate[bot] authored May 22, 2023

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    49163cb View commit details

Commits on May 29, 2023

  1. chore(deps): update definitelytyped (#1457)

    Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
    renovate[bot] authored May 29, 2023

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    37334f5 View commit details

Commits on Jun 1, 2023

  1. chore(deps): update eslint packages (#1460)

    Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
    renovate[bot] authored Jun 1, 2023

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    d70b51e View commit details
  2. fix: upgrade jscodeshift from 0.14.0 to 0.15.0 (#1459)

    Snyk has created this PR to upgrade jscodeshift from 0.14.0 to 0.15.0.
    
    See this package in npm:
    https://www.npmjs.com/package/jscodeshift
    
    See this project in Snyk:
    https://app.snyk.io/org/engineering-container-scanning/project/1591c2ef-1ec9-4a0a-9843-615ca2a55191?utm_source=github&utm_medium=referral&page=upgrade-pr
    
    Co-authored-by: snyk-bot <snyk-bot@snyk.io>
    Co-authored-by: Grant Wong <2908767+dddlr@users.noreply.github.com>
    3 people authored Jun 1, 2023

    Partially verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    We cannot verify signatures from co-authors, and some of the co-authors attributed to this commit require their commits to be signed.
    Copy the full SHA
    c983e53 View commit details

Commits on Jun 5, 2023

  1. chore(deps): update dependency @emotion/is-prop-valid to ^1.2.1 (#1462)

    Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
    renovate[bot] authored Jun 5, 2023

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    4de1e93 View commit details

Commits on Jun 16, 2023

  1. updates codeshiftconfig to suit new version of cli (#1463)

    Co-authored-by: Daniel Del Core <ddelcore@atlassian.com>
    danieldelcore and Daniel Del Core authored Jun 16, 2023

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    d56b25d View commit details

Commits on Jun 19, 2023

  1. chore(deps): update dependency @types/react to ^17.0.62 (#1465)

    Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
    renovate[bot] authored Jun 19, 2023

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    068008f View commit details

Commits on Jun 26, 2023

  1. chore(deps): update dependency csstype to ^3.1.2 (#1466)

    Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
    renovate[bot] authored Jun 26, 2023

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    d02d6cb View commit details

Commits on Jul 3, 2023

  1. chore(deps): update dependency @changesets/cli to ^2.26.2 (#1469)

    Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
    renovate[bot] authored Jul 3, 2023

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    1661d88 View commit details
  2. chore(deps): update dependency prettier to ^2.8.8 (#1470)

    Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
    renovate[bot] authored Jul 3, 2023

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    0013694 View commit details

Commits on Jul 4, 2023

  1. Skip shorthand property expansion if CSS variable given as value (#1467)

    * Skip shorthand property expansion if CSS variable given as value
    
    * Update tests
    
    * Remove useless line from test case
    
    * Add changeset
    dddlr authored Jul 4, 2023

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    a24c157 View commit details
  2. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    3d9dfa7 View commit details
  3. Version Packages (#1473)

    Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
    atlas-dst-bot and github-actions[bot] authored Jul 4, 2023

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    33f286c View commit details

Commits on Jul 6, 2023

  1. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    8add86d View commit details

Commits on Jul 10, 2023

  1. chore(deps): update dependency resolve to ^1.22.2 (#1478)

    Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
    renovate[bot] authored Jul 10, 2023

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    192bf73 View commit details
  2. chore(deps): update dependency semver to ^7.5.4 (#1479)

    Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
    renovate[bot] authored Jul 10, 2023

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    cde8b81 View commit details

Commits on Jul 17, 2023

  1. chore(deps): update dependency storybook-addon-pseudo-states to ^1.15…

    ….5 (#1480)
    
    Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
    renovate[bot] authored Jul 17, 2023

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    e7a3d3e View commit details
  2. chore(deps): update postcss packages (#1481)

    Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
    renovate[bot] authored Jul 17, 2023

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    474a21c View commit details

Commits on Aug 3, 2023

  1. Support default parameters in arrow functions (#1484)

    * Add support for default parameters in arrow functions
    
    * Throw error when array destructuring (or other unsupported syntax) passed into arrow function
    dddlr authored Aug 3, 2023

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    487bbd4 View commit details
  2. chore(deps): update dependency postcss to ^8.4.27 (#1482)

    Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
    renovate[bot] authored Aug 3, 2023

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    8e37caf View commit details

Commits on Aug 7, 2023

  1. chore(deps): update dependency resolve to ^1.22.4 (#1489)

    Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
    renovate[bot] authored Aug 7, 2023

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    da5657c View commit details

Commits on Aug 11, 2023

  1. Forbid imported styles and function parameters in css attribute (#1491)

    * Add import and function parameters to the scope of no-css-prop-without-css-function
    
    * Add support for 'as const'
    
    * Migrate eslint rule to typescript
    
    * Add documentation
    
    * Handle non-deconstructed parameter properties
    
    * Refactor no-css-prop-without-css-function
    dddlr authored Aug 11, 2023

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    9cfda8e View commit details

Commits on Aug 21, 2023

  1. Version Packages (#1486)

    Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
    atlas-dst-bot and github-actions[bot] authored Aug 21, 2023

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    d44f8ea View commit details

Commits on Aug 25, 2023

  1. eslint plugin: Allow function parameters/imported values in edge case (

    …#1494)
    
    Allow function parameters/imported values on left hand side of logical expressions in css attribute (A && B, A || B, A ?? B)
    dddlr authored Aug 25, 2023

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    4090408 View commit details
  2. Version Packages (#1497)

    Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
    atlas-dst-bot and github-actions[bot] authored Aug 25, 2023

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    39daf9e View commit details

Commits on Aug 29, 2023

  1. CSS Map alternative compilation approach (#1496)

    * Add CSS Map
    
    * Update packages/babel-plugin/src/css-prop/__tests__/css-map.test.ts
    
    Co-authored-by: Jake Lane <jlane2@atlassian.com>
    
    * Update packages/babel-plugin/src/css-prop/__tests__/css-map.test.ts
    
    Co-authored-by: Jake Lane <jlane2@atlassian.com>
    
    * Remove console.log
    
    * Update function name
    
    * Add CSS Map to Parcel example
    
    * Refactor error handling
    
    * Add doc to cssMap
    
    * Regenerate cssMap Flow types
    
    * Update test
    
    * Update error messages
    
    * Implement alternative compilation method
    
    * Update packages/babel-plugin/src/types.ts
    
    Co-authored-by: Jake Lane <jlane2@atlassian.com>
    
    * Add integration tests, storybooks, and vr tests for CSS Map
    
    * Add changeset
    
    ---------
    
    Co-authored-by: Jake Lane <jlane2@atlassian.com>
    liamqma and JakeLane authored Aug 29, 2023

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    4a2174c View commit details

Commits on Aug 31, 2023

  1. Update cssmap types (#1499)

    * Update cssmap types
    
    * Replace readonly
    
    * Add changeset
    liamqma authored Aug 31, 2023

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    c5377cd View commit details
  2. Version Packages (#1498)

    Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
    atlas-dst-bot and github-actions[bot] authored Aug 31, 2023

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    9c9b056 View commit details
Showing with 2,278 additions and 728 deletions.
  1. BIN .loki/reference/chrome_laptop_css_map_Conditional_Styles.png
  2. BIN .loki/reference/chrome_laptop_css_map_Dynamic_Variant.png
  3. BIN .loki/reference/chrome_laptop_css_map_Merge_Styles.png
  4. BIN .loki/reference/chrome_laptop_css_map_Variant_As_Prop.png
  5. +12 −0 examples/parcel/CHANGELOG.md
  6. +1 −1 examples/parcel/package.json
  7. +2 −0 examples/parcel/src/app.jsx
  8. +12 −0 examples/parcel/src/ui/css-map.jsx
  9. +1 −1 examples/ssr/package.json
  10. +13 −0 examples/webpack/CHANGELOG.md
  11. +1 −1 examples/webpack/package.json
  12. +2 −0 examples/webpack/src/app.jsx
  13. +12 −0 examples/webpack/src/ui/css-map.jsx
  14. +3 −3 fixtures/babel-component/package.json
  15. +8 −0 fixtures/parcel-optimizer-test-app/CHANGELOG.md
  16. +2 −2 fixtures/parcel-optimizer-test-app/package.json
  17. +8 −0 fixtures/parcel-transformer-test-app/CHANGELOG.md
  18. +2 −2 fixtures/parcel-transformer-test-app/package.json
  19. +8 −0 fixtures/parcel-transformer-test-compress-class-name-app/CHANGELOG.md
  20. +2 −2 fixtures/parcel-transformer-test-compress-class-name-app/package.json
  21. +8 −0 fixtures/parcel-transformer-test-custom-resolver-app/CHANGELOG.md
  22. +2 −2 fixtures/parcel-transformer-test-custom-resolver-app/package.json
  23. +8 −0 fixtures/parcel-transformer-test-extract-app/CHANGELOG.md
  24. +2 −2 fixtures/parcel-transformer-test-extract-app/package.json
  25. +13 −12 package.json
  26. +6 −6 packages/babel-plugin-strip-runtime/package.json
  27. +29 −0 packages/babel-plugin/CHANGELOG.md
  28. +12 −12 packages/babel-plugin/package.json
  29. +9 −1 packages/babel-plugin/src/babel-plugin.ts
  30. +135 −0 packages/babel-plugin/src/css-map/__tests__/index.test.ts
  31. +153 −0 packages/babel-plugin/src/css-map/index.ts
  32. +85 −0 packages/babel-plugin/src/css-prop/__tests__/css-map.test.ts
  33. +25 −8 packages/babel-plugin/src/css-prop/__tests__/object-literal.test.ts
  34. +78 −0 packages/babel-plugin/src/styled/__tests__/call-expression.test.ts
  35. +6 −0 packages/babel-plugin/src/types.ts
  36. +70 −0 packages/babel-plugin/src/utils/__tests__/normalize-props.usage.test.ts
  37. +38 −0 packages/babel-plugin/src/utils/css-builders.ts
  38. +15 −0 packages/babel-plugin/src/utils/is-compiled.ts
  39. +136 −24 packages/babel-plugin/src/utils/normalize-props-usage.ts
  40. +3 −0 packages/babel-plugin/src/utils/object-property-to-string.ts
  41. +6 −0 packages/babel-plugin/src/utils/transform-css-items.ts
  42. +14 −1 packages/babel-plugin/src/utils/types.ts
  43. +0 −11 packages/codemods/codeshift.config.js
  44. +1 −1 packages/codemods/package.json
  45. +13 −3 packages/codemods/src/index.ts
  46. +6 −0 packages/css/CHANGELOG.md
  47. +3 −3 packages/css/package.json
  48. +15 −1 packages/css/src/plugins/expand-shorthands/index.ts
  49. +12 −0 packages/eslint-plugin/CHANGELOG.md
  50. +4 −4 packages/eslint-plugin/package.json
  51. +31 −0 packages/eslint-plugin/src/rules/no-css-prop-without-css-function/README.md
  52. +343 −134 packages/eslint-plugin/src/rules/no-css-prop-without-css-function/__tests__/rule.test.ts
  53. +148 −53 packages/eslint-plugin/src/rules/no-css-prop-without-css-function/index.ts
  54. +4 −0 packages/eslint-plugin/src/test-utils.ts
  55. +50 −0 packages/eslint-plugin/src/utils/ast.ts
  56. +1 −1 packages/jest/package.json
  57. +1 −1 packages/jest/src/index.js.flow
  58. +1 −1 packages/jest/src/matchers.js.flow
  59. +1 −1 packages/jest/src/types.js.flow
  60. +7 −0 packages/parcel-optimizer/CHANGELOG.md
  61. +4 −4 packages/parcel-optimizer/package.json
  62. +21 −0 packages/parcel-transformer/CHANGELOG.md
  63. +6 −6 packages/parcel-transformer/package.json
  64. +10 −0 packages/react/CHANGELOG.md
  65. +3 −3 packages/react/package.json
  66. +1 −1 packages/react/src/class-names/index.js.flow
  67. +52 −0 packages/react/src/css-map/__tests__/index.test.tsx
  68. +26 −0 packages/react/src/css-map/index.js.flow
  69. +27 −0 packages/react/src/css-map/index.ts
  70. +1 −1 packages/react/src/css/index.js.flow
  71. +2 −1 packages/react/src/index.js.flow
  72. +1 −0 packages/react/src/index.ts
  73. +1 −1 packages/react/src/keyframes/index.js.flow
  74. +1 −1 packages/react/src/runtime.js.flow
  75. +1 −1 packages/react/src/runtime/ac.js.flow
  76. +1 −1 packages/react/src/runtime/ax.js.flow
  77. +1 −1 packages/react/src/runtime/cache.js.flow
  78. +1 −1 packages/react/src/runtime/css-custom-property.js.flow
  79. +1 −1 packages/react/src/runtime/dev-warnings.js.flow
  80. +1 −1 packages/react/src/runtime/index.js.flow
  81. +1 −1 packages/react/src/runtime/is-server-environment.js.flow
  82. +1 −1 packages/react/src/runtime/sheet.js.flow
  83. +1 −1 packages/react/src/runtime/style-cache.js.flow
  84. +1 −1 packages/react/src/runtime/style.js.flow
  85. +1 −1 packages/react/src/runtime/types.js.flow
  86. +1 −1 packages/react/src/styled/index.js.flow
  87. +4 −4 packages/react/src/types.js.flow
  88. +3 −3 packages/react/src/types.ts
  89. +1 −1 packages/react/src/utils/error.js.flow
  90. +1 −1 packages/utils/package.json
  91. +22 −0 packages/webpack-loader/CHANGELOG.md
  92. +6 −6 packages/webpack-loader/package.json
  93. +3 −0 scripts/flow-types.sh
  94. +62 −0 stories/css-map.tsx
  95. +425 −389 yarn.lock
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

Unable to render rich display

Invalid image source.

12 changes: 12 additions & 0 deletions examples/parcel/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# @compiled/parcel-app

## 1.3.0

### Minor Changes

- 4a2174c5: Implement the `cssMap` API to enable library users to dynamically choose a varied set of CSS rules.

### Patch Changes

- Updated dependencies [4a2174c5]
- Updated dependencies [c5377cdb]
- @compiled/react@0.14.0

## 1.2.0

### Minor Changes
2 changes: 1 addition & 1 deletion examples/parcel/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@compiled/parcel-app",
"version": "1.2.0",
"version": "1.3.0",
"private": true,
"scripts": {
"build": "parcel build ./src/index.html",
2 changes: 2 additions & 0 deletions examples/parcel/src/app.jsx
Original file line number Diff line number Diff line change
@@ -9,6 +9,7 @@ import '@compiled/react';

import { primary } from './constants';
import Annotated from './ui/annotated';
import CSSMap from './ui/css-map';
import {
CustomFileExtensionStyled,
customFileExtensionCss,
@@ -29,5 +30,6 @@ export const App = () => (
<React.Suspense fallback={<div>Loading...</div>}>
<AsyncComponent />
</React.Suspense>
<CSSMap variant="danger">CSS Map</CSSMap>
</>
);
12 changes: 12 additions & 0 deletions examples/parcel/src/ui/css-map.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { css, cssMap } from '@compiled/react';

const styles = cssMap({
danger: {
color: 'red',
},
success: {
color: 'green',
},
});

export default ({ variant, children }) => <div css={css(styles[variant])}>{children}</div>;
2 changes: 1 addition & 1 deletion examples/ssr/package.json
Original file line number Diff line number Diff line change
@@ -19,7 +19,7 @@
"babel-preset-razzle": "^4.2.18",
"html-webpack-plugin": "^5.5.0",
"mini-css-extract-plugin": "^0.9.0",
"postcss": "^8.4.21",
"postcss": "^8.4.27",
"razzle": "^4.2.18",
"razzle-dev-utils": "^4.2.18",
"webpack": "^5.76.1",
13 changes: 13 additions & 0 deletions examples/webpack/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,18 @@
# @compiled/webpack-app

## 1.3.0

### Minor Changes

- 4a2174c5: Implement the `cssMap` API to enable library users to dynamically choose a varied set of CSS rules.

### Patch Changes

- Updated dependencies [4a2174c5]
- Updated dependencies [c5377cdb]
- @compiled/react@0.14.0
- @compiled/webpack-loader@0.11.3

## 1.2.0

### Minor Changes
2 changes: 1 addition & 1 deletion examples/webpack/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@compiled/webpack-app",
"version": "1.2.0",
"version": "1.3.0",
"private": true,
"scripts": {
"build": "webpack",
2 changes: 2 additions & 0 deletions examples/webpack/src/app.jsx
Original file line number Diff line number Diff line change
@@ -4,6 +4,7 @@ import { Suspense, lazy } from 'react';

import { primary } from './common/constants';
import Annotated from './ui/annotated';
import CSSMap from './ui/css-map';
import {
CustomFileExtensionStyled,
customFileExtensionCss,
@@ -23,5 +24,6 @@ export const App = () => (
<CustomFileExtensionStyled>Custom File Extension Styled</CustomFileExtensionStyled>
<div css={customFileExtensionCss}>Custom File Extension CSS</div>
<Annotated />
<CSSMap variant="danger">CSS Map</CSSMap>
</>
);
12 changes: 12 additions & 0 deletions examples/webpack/src/ui/css-map.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { css, cssMap } from '@compiled/react';

const styles = cssMap({
danger: {
color: 'red',
},
success: {
color: 'green',
},
});

export default ({ variant, children }) => <div css={css(styles[variant])}>{children}</div>;
6 changes: 3 additions & 3 deletions fixtures/babel-component/package.json
Original file line number Diff line number Diff line change
@@ -10,9 +10,9 @@
"@compiled/react": "*"
},
"devDependencies": {
"@babel/cli": "^7.21.0",
"@babel/core": "^7.21.4",
"@babel/preset-env": "^7.21.4",
"@babel/cli": "^7.21.5",
"@babel/core": "^7.21.8",
"@babel/preset-env": "^7.21.5",
"@babel/preset-react": "^7.18.6",
"@compiled/babel-plugin": "*"
},
8 changes: 8 additions & 0 deletions fixtures/parcel-optimizer-test-app/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# @compiled/parcel-optimizer-test-app

## 0.1.2

### Patch Changes

- Updated dependencies [4a2174c5]
- Updated dependencies [c5377cdb]
- @compiled/react@0.14.0

## 0.1.1

### Patch Changes
4 changes: 2 additions & 2 deletions fixtures/parcel-optimizer-test-app/package.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"name": "@compiled/parcel-optimizer-test-app",
"version": "0.1.1",
"version": "0.1.2",
"private": true,
"dependencies": {
"@compiled/react": "^0.13.0",
"@compiled/react": "^0.14.0",
"react": "^17.0.2",
"react-dom": "^17.0.2"
},
8 changes: 8 additions & 0 deletions fixtures/parcel-transformer-test-app/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# @compiled/parcel-transformer-test-app

## 0.1.2

### Patch Changes

- Updated dependencies [4a2174c5]
- Updated dependencies [c5377cdb]
- @compiled/react@0.14.0

## 0.1.1

### Patch Changes
4 changes: 2 additions & 2 deletions fixtures/parcel-transformer-test-app/package.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"name": "@compiled/parcel-transformer-test-app",
"version": "0.1.1",
"version": "0.1.2",
"private": true,
"dependencies": {
"@compiled/react": "^0.13.0",
"@compiled/react": "^0.14.0",
"react": "^17.0.2",
"react-dom": "^17.0.2"
},
Original file line number Diff line number Diff line change
@@ -1 +1,9 @@
# @compiled/parcel-transformer-test-compress-class-name-app

## 0.2.1

### Patch Changes

- Updated dependencies [4a2174c5]
- Updated dependencies [c5377cdb]
- @compiled/react@0.14.0
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"name": "@compiled/parcel-transformer-test-compress-class-name-app",
"version": "0.2.0",
"version": "0.2.1",
"private": true,
"dependencies": {
"@compiled/react": "^0.13.0",
"@compiled/react": "^0.14.0",
"react": "^17.0.2",
"react-dom": "^17.0.2"
},
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# @compiled/parcel-transformer-test-custom-resolver-app

## 0.1.2

### Patch Changes

- Updated dependencies [4a2174c5]
- Updated dependencies [c5377cdb]
- @compiled/react@0.14.0

## 0.1.1

### Patch Changes
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"name": "@compiled/parcel-transformer-test-custom-resolver-app",
"version": "0.1.1",
"version": "0.1.2",
"private": true,
"dependencies": {
"@compiled/react": "^0.13.0",
"@compiled/react": "^0.14.0",
"react": "^17.0.2",
"react-dom": "^17.0.2"
},
8 changes: 8 additions & 0 deletions fixtures/parcel-transformer-test-extract-app/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# @compiled/parcel-transformer-test-extract-app

## 0.1.2

### Patch Changes

- Updated dependencies [4a2174c5]
- Updated dependencies [c5377cdb]
- @compiled/react@0.14.0

## 0.1.1

### Patch Changes
4 changes: 2 additions & 2 deletions fixtures/parcel-transformer-test-extract-app/package.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"name": "@compiled/parcel-transformer-test-extract-app",
"version": "0.1.1",
"version": "0.1.2",
"private": true,
"dependencies": {
"@compiled/react": "^0.13.0",
"@compiled/react": "^0.14.0",
"react": "^17.0.2",
"react-dom": "^17.0.2"
},
25 changes: 13 additions & 12 deletions package.json
Original file line number Diff line number Diff line change
@@ -58,13 +58,14 @@
"resolutions": {
"css-what": ">=6.1.0",
"nth-check": ">=2.1.1",
"semver": "^7.5.4",
"typescript": "^4.9.5"
},
"devDependencies": {
"@babel/preset-env": "^7.21.4",
"@babel/preset-env": "^7.21.5",
"@babel/preset-react": "^7.18.6",
"@babel/preset-typescript": "^7.21.4",
"@changesets/cli": "^2.26.0",
"@babel/preset-typescript": "^7.21.5",
"@changesets/cli": "^2.26.2",
"@compiled-private/module-a": "*",
"@compiled/babel-plugin": "*",
"@compiled/jest": "*",
@@ -76,33 +77,33 @@
"@storybook/react": "^6.5.16",
"@types/jest": "^27.5.2",
"@types/node": "^18.13.0",
"@types/react": "^17.0.58",
"@types/react-dom": "^17.0.19",
"@types/react": "^17.0.62",
"@types/react-dom": "^17.0.20",
"@types/svgo": "^2.6.4",
"@typescript-eslint/eslint-plugin": "^5.54.0",
"@typescript-eslint/parser": "^5.54.0",
"@typescript-eslint/eslint-plugin": "^5.59.8",
"@typescript-eslint/parser": "^5.59.8",
"babel-loader": "^9.1.2",
"eslint": "^8.35.0",
"eslint": "^8.41.0",
"eslint-plugin-flowtype": "^8.0.3",
"eslint-plugin-import": "^2.27.5",
"eslint-plugin-json-files": "^2.1.0",
"eslint-plugin-json-files": "^2.2.0",
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-react-hooks": "^4.6.0",
"flow-bin": "^0.183.0",
"flowgen": "^1.20.1",
"flowgen": "^1.21.0",
"husky": "^4.3.8",
"jest": "^29.4.3",
"jest-environment-jsdom": "^29.4.3",
"jest-extended": "^0.11.5",
"jest-watch-typeahead": "^0.6.5",
"loki": "^0.31.1",
"prettier": "^2.8.4",
"prettier": "^2.8.8",
"pretty-quick": "^3.1.3",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"size-limit": "^8.1.2",
"storybook-addon-performance": "^0.16.1",
"storybook-addon-pseudo-states": "^1.15.2",
"storybook-addon-pseudo-states": "^1.15.5",
"ts-node": "^10.9.1",
"ts-transform-define": "^0.1.10",
"tsconfig-paths": "^4.1.2",
12 changes: 6 additions & 6 deletions packages/babel-plugin-strip-runtime/package.json
Original file line number Diff line number Diff line change
@@ -19,16 +19,16 @@
"src"
],
"dependencies": {
"@babel/core": "^7.21.4",
"@babel/helper-plugin-utils": "^7.20.2",
"@babel/core": "^7.21.8",
"@babel/helper-plugin-utils": "^7.21.5",
"@babel/template": "^7.20.7",
"@babel/traverse": "^7.21.4",
"@babel/types": "^7.21.4",
"@babel/traverse": "^7.21.5",
"@babel/types": "^7.21.5",
"@compiled/utils": "^0.8.0"
},
"devDependencies": {
"@compiled/babel-plugin": "*",
"@types/babel__core": "^7.20.0",
"prettier": "^2.8.4"
"@types/babel__core": "^7.20.1",
"prettier": "^2.8.8"
}
}
29 changes: 29 additions & 0 deletions packages/babel-plugin/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,34 @@
# @compiled/babel-plugin

## 0.22.0

### Minor Changes

- 4a2174c5: Implement the `cssMap` API to enable library users to dynamically choose a varied set of CSS rules.

## 0.21.0

### Minor Changes

- 487bbd46: Support default parameters in arrow functions, and explicitly throw error when using unsupported syntax in arrow function parameters

## 0.20.0

### Minor Changes

- a24c157c: Skip expansion of shorthand properties (e.g. padding, margin) if they have dynamic values (e.g. CSS variables, ternary expressions, arrow functions)

### Patch Changes

- Updated dependencies [a24c157c]
- @compiled/css@0.12.0

## 0.19.1

### Patch Changes

- 9aa6909a: Add error when expressionToString runs infinite loop ("Error: Maximum call stack size exceeded")

## 0.19.0

### Minor Changes
24 changes: 12 additions & 12 deletions packages/babel-plugin/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@compiled/babel-plugin",
"version": "0.19.0",
"version": "0.22.0",
"description": "A familiar and performant compile time CSS-in-JS library for React.",
"homepage": "https://compiledcssinjs.com/docs/pkg-babel-plugin",
"bugs": "https://github.com/atlassian-labs/compiled/issues/new?assignees=&labels=bug&template=bug_report.md",
@@ -19,28 +19,28 @@
"src"
],
"dependencies": {
"@babel/core": "^7.21.4",
"@babel/generator": "^7.21.4",
"@babel/helper-plugin-utils": "^7.20.2",
"@babel/parser": "^7.21.4",
"@babel/core": "^7.21.8",
"@babel/generator": "^7.21.5",
"@babel/helper-plugin-utils": "^7.21.5",
"@babel/parser": "^7.21.8",
"@babel/plugin-syntax-jsx": "^7.21.4",
"@babel/plugin-transform-flow-strip-types": "^7.21.0",
"@babel/template": "^7.20.7",
"@babel/traverse": "^7.21.4",
"@babel/types": "^7.21.4",
"@compiled/css": "^0.11.0",
"@babel/traverse": "^7.21.5",
"@babel/types": "^7.21.5",
"@compiled/css": "^0.12.0",
"@compiled/utils": "^0.8.0",
"@emotion/is-prop-valid": "^1.2.0",
"resolve": "^1.22.0"
"@emotion/is-prop-valid": "^1.2.1",
"resolve": "^1.22.4"
},
"devDependencies": {
"@compiled-private/module-a": "^0.1.0",
"@compiled/benchmark": "1.1.0",
"@types/babel__core": "^7.20.0",
"@types/babel__core": "^7.20.1",
"@types/benchmark": "^2.1.2",
"@types/resolve": "^1.20.2",
"benchmark": "^2.1.4",
"prettier": "^2.8.4",
"prettier": "^2.8.8",
"ts-node": "^10.9.1",
"tsconfig-paths": "^4.1.2"
}
10 changes: 9 additions & 1 deletion packages/babel-plugin/src/babel-plugin.ts
Original file line number Diff line number Diff line change
@@ -8,6 +8,7 @@ import * as t from '@babel/types';
import { unique, preserveLeadingComments } from '@compiled/utils';

import { visitClassNamesPath } from './class-names';
import { visitCssMapPath } from './css-map';
import { visitCssPropPath } from './css-prop';
import { visitStyledPath } from './styled';
import type { State } from './types';
@@ -20,6 +21,7 @@ import {
isCompiledKeyframesTaggedTemplateExpression,
isCompiledStyledCallExpression,
isCompiledStyledTaggedTemplateExpression,
isCompiledCSSMapCallExpression,
} from './utils/is-compiled';
import { normalizePropsUsage } from './utils/normalize-props-usage';

@@ -39,6 +41,7 @@ export default declare<State>((api) => {
inherits: jsxSyntax,
pre() {
this.sheets = {};
this.cssMap = {};
let cache: Cache;

if (this.opts.cache === true) {
@@ -150,7 +153,7 @@ export default declare<State>((api) => {
return;
}

(['styled', 'ClassNames', 'css', 'keyframes'] as const).forEach((apiName) => {
(['styled', 'ClassNames', 'css', 'keyframes', 'cssMap'] as const).forEach((apiName) => {
if (
state.compiledImports &&
t.isIdentifier(specifier.node?.imported) &&
@@ -171,6 +174,11 @@ export default declare<State>((api) => {
path: NodePath<t.TaggedTemplateExpression> | NodePath<t.CallExpression>,
state: State
) {
if (isCompiledCSSMapCallExpression(path.node, state)) {
visitCssMapPath(path, { context: 'root', state, parentPath: path });
return;
}

const hasStyles =
isCompiledCSSTaggedTemplateExpression(path.node, state) ||
isCompiledStyledTaggedTemplateExpression(path.node, state) ||
135 changes: 135 additions & 0 deletions packages/babel-plugin/src/css-map/__tests__/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import type { TransformOptions } from '../../test-utils';
import { transform as transformCode } from '../../test-utils';
import { ErrorMessages } from '../index';

describe('css map', () => {
const transform = (code: string, opts: TransformOptions = {}) =>
transformCode(code, { pretty: false, ...opts });

const styles = `{
danger: {
color: 'red',
backgroundColor: 'red'
},
success: {
color: 'green',
backgroundColor: 'green'
}
}`;

it('should transform css map', () => {
const actual = transform(`
import { cssMap } from '@compiled/react';
const styles = cssMap(${styles});
`);

expect(actual).toInclude(
'const styles={danger:"_syaz5scu _bfhk5scu",success:"_syazbf54 _bfhkbf54"};'
);
});

it('should error out if variants are not defined at the top-most scope of the module.', () => {
expect(() => {
transform(`
import { cssMap } from '@compiled/react';
const styles = {
map1: cssMap(${styles}),
}
`);
}).toThrow(ErrorMessages.DEFINE_MAP);

expect(() => {
transform(`
import { cssMap } from '@compiled/react';
const styles = () => cssMap(${styles})
`);
}).toThrow(ErrorMessages.DEFINE_MAP);
});

it('should error out if cssMap receives more than one argument', () => {
expect(() => {
transform(`
import { cssMap } from '@compiled/react';
const styles = cssMap(${styles}, ${styles})
`);
}).toThrow(ErrorMessages.NUMBER_OF_ARGUMENT);
});

it('should error out if cssMap does not receive an object', () => {
expect(() => {
transform(`
import { cssMap } from '@compiled/react';
const styles = cssMap('color: red')
`);
}).toThrow(ErrorMessages.ARGUMENT_TYPE);
});

it('should error out if spread element is used', () => {
expect(() => {
transform(`
import { css, cssMap } from '@compiled/react';
const styles = cssMap({
...base
});
`);
}).toThrow(ErrorMessages.NO_SPREAD_ELEMENT);
});

it('should error out if object method is used', () => {
expect(() => {
transform(`
import { css, cssMap } from '@compiled/react';
const styles = cssMap({
danger() {}
});
`);
}).toThrow(ErrorMessages.NO_OBJECT_METHOD);
});

it('should error out if variant object is dynamic', () => {
expect(() => {
transform(`
import { css, cssMap } from '@compiled/react';
const styles = cssMap({
danger: otherStyles
});
`);
}).toThrow(ErrorMessages.STATIC_VARIANT_OBJECT);
});

it('should error out if styles include runtime variables', () => {
expect(() => {
transform(`
import { css, cssMap } from '@compiled/react';
const styles = cssMap({
danger: {
color: canNotBeStaticallyEvulated
}
});
`);
}).toThrow(ErrorMessages.STATIC_VARIANT_OBJECT);
});

it('should error out if styles include conditional CSS', () => {
expect(() => {
transform(`
import { css, cssMap } from '@compiled/react';
const styles = cssMap({
danger: {
color: canNotBeStaticallyEvulated ? 'red' : 'blue'
}
});
`);
}).toThrow(ErrorMessages.STATIC_VARIANT_OBJECT);
});
});
153 changes: 153 additions & 0 deletions packages/babel-plugin/src/css-map/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import type { NodePath } from '@babel/core';
import * as t from '@babel/types';

import type { Metadata } from '../types';
import { buildCodeFrameError } from '../utils/ast';
import { buildCss } from '../utils/css-builders';
import { transformCssItems } from '../utils/transform-css-items';

// The messages are exported for testing.
export enum ErrorMessages {
NO_TAGGED_TEMPLATE = 'cssMap function cannot be used as a tagged template expression.',
NUMBER_OF_ARGUMENT = 'cssMap function can only receive one argument.',
ARGUMENT_TYPE = 'cssMap function can only receive an object.',
DEFINE_MAP = 'CSS Map must be declared at the top-most scope of the module.',
NO_SPREAD_ELEMENT = 'Spread element is not supported in CSS Map.',
NO_OBJECT_METHOD = 'Object method is not supported in CSS Map.',
STATIC_VARIANT_OBJECT = 'The variant object must be statically defined.',
}

const createErrorMessage = (message: string): string => {
return `
${message}
To correctly implement a CSS Map, follow the syntax below:
\`\`\`
import { css, cssMap } from '@compiled/react';
const borderStyleMap = cssMap({
none: { borderStyle: 'none' },
solid: { borderStyle: 'solid' },
});
const Component = ({ borderStyle }) => <div css={css(borderStyleMap[borderStyle])} />
\`\`\`
`;
};

/**
* Takes `cssMap` function expression and then transforms it to a record of class names and sheets.
*
* For example:
* ```
* const styles = cssMap({
* none: { color: 'red' },
* solid: { color: 'green' },
* });
* ```
* gets transformed to
* ```
* const styles = {
* danger: "_syaz5scu",
* success: "_syazbf54",
* };
* ```
*
* @param path {NodePath} The path to be evaluated.
* @param meta {Metadata} Useful metadata that can be used during the transformation
*/
export const visitCssMapPath = (
path: NodePath<t.CallExpression> | NodePath<t.TaggedTemplateExpression>,
meta: Metadata
): void => {
// We don't support tagged template expressions.
if (t.isTaggedTemplateExpression(path.node)) {
throw buildCodeFrameError(
createErrorMessage(ErrorMessages.DEFINE_MAP),
path.node,
meta.parentPath
);
}

// We need to ensure CSS Map is declared at the top-most scope of the module.
if (!t.isVariableDeclarator(path.parent) || !t.isIdentifier(path.parent.id)) {
throw buildCodeFrameError(
createErrorMessage(ErrorMessages.DEFINE_MAP),
path.node,
meta.parentPath
);
}

// We need to ensure cssMap receives only one argument.
if (path.node.arguments.length !== 1) {
throw buildCodeFrameError(
createErrorMessage(ErrorMessages.NUMBER_OF_ARGUMENT),
path.node,
meta.parentPath
);
}

// We need to ensure the argument is an objectExpression.
if (!t.isObjectExpression(path.node.arguments[0])) {
throw buildCodeFrameError(
createErrorMessage(ErrorMessages.ARGUMENT_TYPE),
path.node,
meta.parentPath
);
}

const totalSheets: string[] = [];
path.replaceWith(
t.objectExpression(
path.node.arguments[0].properties.map((property) => {
if (t.isSpreadElement(property)) {
throw buildCodeFrameError(
createErrorMessage(ErrorMessages.NO_SPREAD_ELEMENT),
property.argument,
meta.parentPath
);
}

if (t.isObjectMethod(property)) {
throw buildCodeFrameError(
createErrorMessage(ErrorMessages.NO_OBJECT_METHOD),
property.key,
meta.parentPath
);
}

if (!t.isObjectExpression(property.value)) {
throw buildCodeFrameError(
createErrorMessage(ErrorMessages.STATIC_VARIANT_OBJECT),
property.value,
meta.parentPath
);
}

const { css, variables } = buildCss(property.value, meta);

if (variables.length) {
throw buildCodeFrameError(
createErrorMessage(ErrorMessages.STATIC_VARIANT_OBJECT),
property.value,
meta.parentPath
);
}

const { sheets, classNames } = transformCssItems(css, meta);
totalSheets.push(...sheets);

if (classNames.length !== 1) {
throw buildCodeFrameError(
createErrorMessage(ErrorMessages.STATIC_VARIANT_OBJECT),
property,
meta.parentPath
);
}

return t.objectProperty(property.key, classNames[0]);
})
)
);

// We store sheets in the meta state so that we can use it later to generate Compiled component.
meta.state.cssMap[path.parent.id.name] = totalSheets;
};
85 changes: 85 additions & 0 deletions packages/babel-plugin/src/css-prop/__tests__/css-map.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import type { TransformOptions } from '../../test-utils';
import { transform as transformCode } from '../../test-utils';

describe('css map behaviour', () => {
beforeAll(() => {
process.env.AUTOPREFIXER = 'off';
});

afterAll(() => {
delete process.env.AUTOPREFIXER;
});

const transform = (code: string, opts: TransformOptions = {}) =>
transformCode(code, { pretty: false, ...opts });

const styles = `
import { css, cssMap } from '@compiled/react';
const styles = cssMap({
danger: {
color: 'red',
backgroundColor: 'red'
},
success: {
color: 'green',
backgroundColor: 'green'
}
});
`;

it('should evaluate css map with various syntactic patterns', () => {
const actual = transform(
`
${styles}
<div css={[
foo && styles['danger'],
props.foo && styles['danger'],
styles.success,
styles['danger'],
styles[variant],
styles[\`danger\`],
styles[isDanger?'danger':'success'],
styles['dang' + 'er'],
styles[props.variant],
{ color: 'blue' }
]} />;
`,
{ pretty: true }
);

expect(actual).toMatchInlineSnapshot(`
"import * as React from "react";
import { ax, ix, CC, CS } from "@compiled/react/runtime";
const _5 = "._syaz13q2{color:blue}";
const _4 = "._bfhkbf54{background-color:green}";
const _3 = "._syazbf54{color:green}";
const _2 = "._bfhk5scu{background-color:red}";
const _ = "._syaz5scu{color:red}";
const styles = {
danger: "_syaz5scu _bfhk5scu",
success: "_syazbf54 _bfhkbf54",
};
<CC>
<CS>{[_, _2, _3, _4, _5]}</CS>
{
<div
className={ax([
foo && styles["danger"],
props.foo && styles["danger"],
styles.success,
styles["danger"],
styles[variant],
styles[\`danger\`],
styles[isDanger ? "danger" : "success"],
styles["dang" + "er"],
styles[props.variant],
"_syaz13q2",
])}
/>
}
</CC>;
"
`);
});
});
Original file line number Diff line number Diff line change
@@ -606,10 +606,7 @@ describe('css prop object literal', () => {
}}>hello world</div>
`);

expect(actual).toInclude('{padding-top:0}');
expect(actual).toInclude('{padding-right:var(--_1xlms2h)}');
expect(actual).toInclude('{padding-bottom:0}');
expect(actual).toInclude('{padding-left:var(--_1xlms2h)}');
expect(actual).toInclude('{padding:0 var(--_1xlms2h)}');
});

it('should parse an inline string interpolation delimited by multiple spaces', () => {
@@ -625,10 +622,7 @@ describe('css prop object literal', () => {
}}>hello world</div>
`);

expect(actual).toInclude('{padding-top:0}');
expect(actual).toInclude('{padding-right:var(--_1xlms2h)}');
expect(actual).toInclude('{padding-bottom:0}');
expect(actual).toInclude('{padding-left:0}');
expect(actual).toInclude('{padding:0 var(--_1xlms2h) 0 0}');
});

it('should parse an inline string interpolation delimited by multiple spaces and suffix', () => {
@@ -784,4 +778,27 @@ describe('css prop object literal', () => {

expect(actual).toInclude('{display:grid}');
});

it('should correctly expand shorthand property with ternary expression', () => {
const actual = transform(`
import { css } from '@compiled/react';
const morePadding = true;
<div css={{
padding: morePadding ? "10px 20px 30px 40px" : "4px 8px",
}}>
Hello world
</div>
`);

expect(actual).toMatchInlineSnapshot(`
"import*as React from'react';import{ax,ix,CC,CS}from"@compiled/react/runtime";const _4="._19bv1ylp{padding-left:40px}";const _3="._n3td1ul9{padding-bottom:30px}";const _2="._u5f3gktf{padding-right:20px}";const _="._ca0q19bv{padding-top:10px}";const morePadding=true;<CC>
<CS>{[_,_2,_3,_4]}</CS>
{<div className={ax(["_ca0q19bv _u5f3gktf _n3td1ul9 _19bv1ylp"])}>
Hello world
</div>}
</CC>;"
`);
});
});
78 changes: 78 additions & 0 deletions packages/babel-plugin/src/styled/__tests__/call-expression.test.ts
Original file line number Diff line number Diff line change
@@ -641,4 +641,82 @@ describe('styled object call expression', () => {
"
`);
});

it('should refuse to expand shorthand property when value is unknown at build time (arrow function)', () => {
const actual = transform(`
import { styled } from '@compiled/react';
const Container = styled.div({
padding: ({ customPadding }) => customPadding,
});
`);

expect(actual).toMatchInlineSnapshot(`
"const _ = "._1yt414tu{padding:var(--_1hhnq9y)}";
const Container = forwardRef(
({ as: C = "div", style: __cmpls, ...__cmplp }, __cmplr) => {
const { customPadding, ...__cmpldp } = __cmplp;
return (
<CC>
<CS>{[_]}</CS>
<C
{...__cmpldp}
style={{
...__cmpls,
"--_1hhnq9y": ix(__cmplp.customPadding),
}}
ref={__cmplr}
className={ax(["_1yt414tu", __cmplp.className])}
/>
</CC>
);
}
);
"
`);
});

it('should refuse to expand shorthand property when value is unknown at build time (ternary expression)', () => {
const actual = transform(`
import { styled } from '@compiled/react';
const Container = styled.div({
padding: ({ morePadding }) => morePadding ? morePadding : '4px 8px',
});
`);

expect(actual).toMatchInlineSnapshot(`
"const _5 = "._19bvftgi{padding-left:8px}";
const _4 = "._n3td1y44{padding-bottom:4px}";
const _3 = "._u5f3ftgi{padding-right:8px}";
const _2 = "._ca0q1y44{padding-top:4px}";
const _ = "._1yt41v0o{padding:var(--_1dm0vu2)}";
const Container = forwardRef(
({ as: C = "div", style: __cmpls, ...__cmplp }, __cmplr) => {
const { morePadding, ...__cmpldp } = __cmplp;
return (
<CC>
<CS>{[_, _2, _3, _4, _5]}</CS>
<C
{...__cmpldp}
style={{
...__cmpls,
"--_1dm0vu2": ix(__cmplp.morePadding),
}}
ref={__cmplr}
className={ax([
"",
__cmplp.morePadding
? "_1yt41v0o"
: "_ca0q1y44 _u5f3ftgi _n3td1y44 _19bvftgi",
__cmplp.className,
])}
/>
</CC>
);
}
);
"
`);
});
});
6 changes: 6 additions & 0 deletions packages/babel-plugin/src/types.ts
Original file line number Diff line number Diff line change
@@ -98,6 +98,7 @@ export interface State extends PluginPass {
css?: string;
keyframes?: string;
styled?: string;
cssMap?: string;
};

importedCompiledImports?: {
@@ -141,6 +142,11 @@ export interface State extends PluginPass {
* Files that have been included in this pass.
*/
includedFiles: string[];

/**
* Holds a record of currently evaluated CSS Map and its sheets in the module.
*/
cssMap: Record<string, string[]>;
}

interface CommonMetadata {
Original file line number Diff line number Diff line change
@@ -59,6 +59,16 @@ describe('normalizePropsUsage', () => {
expect(actual).toInclude(
`(${P_NAME}) => ${P_NAME}.theme.colors.dark ? ${P_NAME}.theme.colors.dark.red : "black"`
);

const actual2 = transform(`
styled.div({
color: ({ theme: { colors: { dark } } }) => dark ? dark.red : 'black',
});
`);

expect(actual2).toInclude(
`(${P_NAME}) => ${P_NAME}.theme.colors.dark ? ${P_NAME}.theme.colors.dark.red : "black"`
);
});

describe('rest element', () => {
@@ -82,5 +92,65 @@ describe('normalizePropsUsage', () => {
expect(actual).toInclude(`color: (${P_NAME}) => ${P_NAME}.theme.colors.light`);
});
});

describe('deconstructing arrays in props', () => {
it('throws an error when deconstructing arrays in props', () => {
const actual = () => {
transform(`
styled.div({
width: ({ a: [, second] }) => \`\${second}px\`,
});
`);
};

expect(actual).toThrow(
'Compiled does not support arrays given in the parameters of an arrow function.'
);
});
});

describe('default parameters', () => {
it('reconstructs default parameters in props', () => {
// Each parameter is in a separate embedded expression
const actual = transform(`
styled.div({
padding: ({ a, b = 16 }) => \`\${b}px \${a}px\`,
});
`);

expect(actual).toInclude(`(${P_NAME}) => \`\${${P_NAME}.b ?? 16}px \${${P_NAME}.a}px\``);

// Both parameters are in the same embedded expression
const actual2 = transform(`
styled.div({
padding: ({ a, b = 16 }) => \`\${a + b}px\`,
});
`);

expect(actual2).toInclude(`(${P_NAME}) => \`\${${P_NAME}.a + (${P_NAME}.b ?? 16)}px\``);
});

it('reconstructs default parameters in nested props', () => {
const actual = transform(`
styled.div({
color: ({ theme: { colors: {dark = '#aaa', ...rest} } }) => dark,
});
`);

expect(actual).toInclude(`color: (${P_NAME}) => ${P_NAME}.theme.colors.dark ?? "#aaa"`);
});

it('reconstructs default parameters in props (alternative syntax)', () => {
const actual = transform(`
styled.div({
padding: ({ a, b } = { a: 100, b: 200 }) => \`\${a}px \${b}px\`,
});
`);

expect(actual).toInclude(
`padding: (__cmplp) => \`\${__cmplp.a ?? 100}px \${__cmplp.b ?? 200}px\``
);
});
});
});
});
38 changes: 38 additions & 0 deletions packages/babel-plugin/src/utils/css-builders.ts
Original file line number Diff line number Diff line change
@@ -28,9 +28,33 @@ import type {
CssItem,
LogicalCssItem,
SheetCssItem,
CssMapItem,
PartialBindingWithMeta,
} from './types';

/**
* Retrieves the leftmost identity from a given expression.
*
* For example:
* Given a member expression "colors.primary.500", the function will return "colors".
*
* @param expression The expression to be evaluated.
* @returns {string} The leftmost identity in the expression.
*/
const findBindingIdentifier = (
expression: t.Expression | t.V8IntrinsicIdentifier
): t.Identifier | undefined => {
if (t.isIdentifier(expression)) {
return expression;
} else if (t.isCallExpression(expression)) {
return findBindingIdentifier(expression.callee);
} else if (t.isMemberExpression(expression)) {
return findBindingIdentifier(expression.object);
}

return undefined;
};

/**
* Will normalize the value of a `content` CSS property to ensure it has quotations around it.
* This is done to replicate both how Styled Components behaves,
@@ -804,6 +828,13 @@ export const buildCss = (node: t.Expression | t.Expression[], meta: Metadata): C
}

if (t.isMemberExpression(node)) {
const bindingIdentifier = findBindingIdentifier(node);
if (bindingIdentifier && meta.state.cssMap[bindingIdentifier.name]) {
return {
css: [{ type: 'map', expression: node, name: bindingIdentifier.name, css: '' }],
variables: [],
};
}
const { value, meta: updatedMeta } = evaluateExpression(node, meta);
return buildCss(value, updatedMeta);
}
@@ -877,6 +908,13 @@ export const buildCss = (node: t.Expression | t.Expression[], meta: Metadata): C
};
}

if (item.type === 'map') {
return {
...item,
expression: t.logicalExpression(node.operator, expression, item.expression),
} as CssMapItem;
}

const logicalItem: LogicalCssItem = {
type: 'logical',
css: getItemCss(item),
15 changes: 15 additions & 0 deletions packages/babel-plugin/src/utils/is-compiled.ts
Original file line number Diff line number Diff line change
@@ -47,6 +47,21 @@ export const isCompiledKeyframesCallExpression = (
t.isIdentifier(node.callee) &&
node.callee.name === state.compiledImports?.keyframes;

/**
* Returns `true` if the node is using `cssMap` from `@compiled/react` as a call expression
*
* @param node {t.Node} The node that is being checked
* @param state {State} Plugin state
* @returns {boolean} Whether the node is a compiled cssMap
*/
export const isCompiledCSSMapCallExpression = (
node: t.Node,
state: State
): node is t.CallExpression =>
t.isCallExpression(node) &&
t.isIdentifier(node.callee) &&
node.callee.name === state.compiledImports?.cssMap;

/**
* Returns `true` if the node is using `keyframes` from `@compiled/react` as a tagged template expression
*
160 changes: 136 additions & 24 deletions packages/babel-plugin/src/utils/normalize-props-usage.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { NodePath } from '@babel/traverse';
import type { Binding, NodePath } from '@babel/traverse';
import * as t from '@babel/types';

import { PROPS_IDENTIFIER_NAME } from '../constants';
@@ -15,18 +15,129 @@ const buildObjectChain = (path: NodePath<t.Node> | null, currentChain: string[]
const { parentPath } = path;
const { node } = parentPath;

// Skip over any default parameters when traversing,
// otherwise they mess up the recursion
if (t.isAssignmentPattern(node)) {
return buildObjectChain(parentPath, currentChain);
}

// e.g. an arrow function like
// ({ a: [, second] }) => ...
if (t.isArrayPattern(node)) {
throw new Error(
'Compiled does not support arrays given in the parameters of an arrow function.'
);
}

if (t.isObjectProperty(node) && t.isIdentifier(node.key)) {
currentChain.unshift(node.key.name);
}

return buildObjectChain(parentPath.parentPath, currentChain);
// When the listKey of the current path is 'params', this means
// that the current path === the entire props object (e.g. { a, b = 5 }).
// So we stop recursively traversing after we pass this point.
//
// (Note that we are recursing upwards two parents at a time.)
if (parentPath.listKey !== 'params') {
return buildObjectChain(parentPath.parentPath, currentChain);
}
}

currentChain.unshift(PROPS_IDENTIFIER_NAME);

return currentChain;
};

/**
* Given an object pattern (e.g. { width, height = 16 }) from the
* parameters of an arrow function, return an object with the
* default parameters (if any) found. For example, { height: 16 }.
*
* @param path a deconstructed object within the function parameters
* @returns an object where the keys are arguments with default
* parameters, and the values are their default values
*/
const getDefaultParameters = (
path: NodePath<t.Identifier | t.RestElement | t.Pattern>
): Record<string, t.Expression> => {
const node = path.node;
const assignments: Record<string, t.Expression> = {};

const FindAllAssignmentsVisitor = {
AssignmentPattern(path: NodePath<t.AssignmentPattern>) {
const { node } = path;
if (t.isIdentifier(node.left)) {
assignments[node.left.name] = node.right;
} else {
throw new Error(
`This syntax for assignments in arrow function parameters isn't supported by Compiled. (Left-hand side ${node.left.type} and right-hand side ${node.right.type})`
);
}
},
};

const FindAllPropertiesVisitor = {
ObjectProperty(path: NodePath<t.ObjectProperty>) {
const { node } = path;
if (t.isIdentifier(node.key) && t.isExpression(node.value)) {
assignments[node.key.name] = node.value;
} else {
throw new Error(
`This syntax for objects in arrow function parameters isn't supported by Compiled. (Left-hand side ${node.key.type} and right-hand side ${node.value.type})`
);
}
},
};

if (t.isObjectPattern(node)) {
// e.g. ({ a: 20, b: 30 }) => ...
path.traverse(FindAllAssignmentsVisitor);
} else if (t.isAssignmentPattern(node)) {
// e.g. ({ a, b } = {a: 20, b: 30 }) => ...
path.traverse(FindAllPropertiesVisitor);
}
return assignments;
};

const normalizeDestructuredString = (
node: t.Identifier,
path: NodePath<t.ArrowFunctionExpression>
): void => {
path.scope.getBinding(node.name)?.referencePaths.forEach((reference) => {
reference.replaceWith(t.identifier(PROPS_IDENTIFIER_NAME));
});
};

const normalizeDestructuredObject = (
bindings: Record<string, Binding>,
values: Record<string, t.Expression>,
destructedPaths: Record<string, NodePath<t.Identifier>>
): void => {
for (const key in destructedPaths) {
const binding = bindings[key];

if (binding.references) {
const objectChain = buildObjectChain(destructedPaths[key]);

binding.referencePaths.forEach((reference) => {
const defaultValue = values[key];
if (defaultValue) {
// Handle default parameter
//
// Note that this differs from default parameters, in that
// passing null to the function will still result in the
// default value being used.
reference.replaceWith(
t.logicalExpression('??', t.identifier(objectChain.join('.')), defaultValue)
);
} else {
reference.replaceWithSourceString(objectChain.join('.'));
}
});
}
}
};

const arrowFunctionVisitor = {
ArrowFunctionExpression(path: NodePath<t.ArrowFunctionExpression>) {
const [propsParam] = path.get('params');
@@ -35,29 +146,30 @@ const arrowFunctionVisitor = {
const { node } = propsParam;

if (t.isIdentifier(node) && node.name !== PROPS_IDENTIFIER_NAME) {
path.scope.getBinding(node.name)?.referencePaths.forEach((reference) => {
reference.replaceWith(t.identifier(PROPS_IDENTIFIER_NAME));
});
// If destructuring
} else if (t.isObjectPattern(node)) {
normalizeDestructuredString(node, path);
} else if (t.isObjectPattern(node) || t.isAssignmentPattern(node)) {
// We need to destructure a props parameter, i.e. the parameter
// of a function like
//
// (object pattern)
// ({ width, height = 16 }) => `${height}px ${width}px`
//
// (assignment pattern)
// ({ width, height } = { height: 200 }) => `${height}px ${width}px`

const destructedPaths: Record<
string,
NodePath<t.Identifier>
// @ts-expect-error
// Property 'getBindingIdentifierPaths' does not exist on type 'NodePath<Identifier | RestElement | Pattern>'.
// But available since v6.20.0
// https://github.com/babel/babel/pull/4876
> = propsParam.getBindingIdentifierPaths();

const { bindings } = path.scope;
// @ts-expect-error
// getBindingIdentifierPaths not in @babel/traverse types
// But available since v6.20.0
// https://github.com/babel/babel/pull/4876
const destructedPaths = propsParam.getBindingIdentifierPaths();

for (const key in destructedPaths) {
const binding = bindings[key];

if (binding.references) {
const objectChain = buildObjectChain(destructedPaths[key]);

binding.referencePaths.forEach((reference) => {
reference.replaceWithSourceString(objectChain.join('.'));
});
}
}
const values = getDefaultParameters(propsParam);

normalizeDestructuredObject(bindings, values, destructedPaths);
}

propsParam.replaceWith(t.identifier(PROPS_IDENTIFIER_NAME));
3 changes: 3 additions & 0 deletions packages/babel-plugin/src/utils/object-property-to-string.ts
Original file line number Diff line number Diff line change
@@ -56,6 +56,9 @@ const expressionToString: ExpressionToString = (expression, meta) => {
// handles {[key]: 'value'}
if (t.isIdentifier(expression)) {
const evaluatedExpression = evaluateExpression(expression, meta);
if (evaluatedExpression.value === expression) {
throw new Error(`Cannot statically evaluate the value of "${expression.name}".`);
}

return expressionToString(evaluatedExpression.value, evaluatedExpression.meta);
}
6 changes: 6 additions & 0 deletions packages/babel-plugin/src/utils/transform-css-items.ts
Original file line number Diff line number Diff line change
@@ -74,6 +74,12 @@ const transformCssItem = (
),
};

case 'map':
return {
sheets: meta.state.cssMap[item.name],
classExpression: item.expression,
};

default:
const css = transformCss(getItemCss(item), meta.state.opts);
const className = compressClassNamesForRuntime(
15 changes: 14 additions & 1 deletion packages/babel-plugin/src/utils/types.ts
Original file line number Diff line number Diff line change
@@ -27,7 +27,20 @@ export interface SheetCssItem {
css: string;
}

export type CssItem = UnconditionalCssItem | ConditionalCssItem | LogicalCssItem | SheetCssItem;
export interface CssMapItem {
name: string;
type: 'map';
expression: t.Expression;
// We can remove this once we local transform other CssItem types
css: string;
}

export type CssItem =
| UnconditionalCssItem
| ConditionalCssItem
| LogicalCssItem
| SheetCssItem
| CssMapItem;

export type Variable = {
name: string;
11 changes: 0 additions & 11 deletions packages/codemods/codeshift.config.js

This file was deleted.

2 changes: 1 addition & 1 deletion packages/codemods/package.json
Original file line number Diff line number Diff line change
@@ -32,7 +32,7 @@
"dependencies": {
"@compiled/utils": "^0.8.0",
"chalk": "^4.1.2",
"jscodeshift": "^0.14.0"
"jscodeshift": "^0.15.0"
},
"devDependencies": {
"@types/jscodeshift": "^0.11.6"
16 changes: 13 additions & 3 deletions packages/codemods/src/index.ts
Original file line number Diff line number Diff line change
@@ -7,6 +7,16 @@ export type {
Visitor,
} from './plugins/types';

export { default as emotionToCompiled } from './transforms/emotion-to-compiled';
export { default as styledComponentsToCompiled } from './transforms/styled-components-to-compiled';
export { default as styledComponentsInnerRefToRef } from './transforms/styled-components-inner-ref-to-ref';
import emotionToCompiled from './transforms/emotion-to-compiled';
import styledComponentsInnerRefToRef from './transforms/styled-components-inner-ref-to-ref';
import styledComponentsToCompiled from './transforms/styled-components-to-compiled';

export { emotionToCompiled, styledComponentsToCompiled, styledComponentsInnerRefToRef };

export default {
presets: {
'emotion-to-compiled': emotionToCompiled,
'styled-components-to-compiled': styledComponentsToCompiled,
'styled-components-inner-ref-to-ref': styledComponentsInnerRefToRef,
},
};
6 changes: 6 additions & 0 deletions packages/css/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# @compiled/css

## 0.12.0

### Minor Changes

- a24c157c: Skip expansion of shorthand properties (e.g. padding, margin) if they have dynamic values (e.g. CSS variables, ternary expressions, arrow functions)

## 0.11.0

### Minor Changes
6 changes: 3 additions & 3 deletions packages/css/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@compiled/css",
"version": "0.11.0",
"version": "0.12.0",
"description": "A familiar and performant compile time CSS-in-JS library for React.",
"homepage": "https://compiledcssinjs.com/docs/pkg-css",
"bugs": "https://github.com/atlassian-labs/compiled/issues/new?assignees=&labels=bug&template=bug_report.md",
@@ -26,10 +26,10 @@
"@compiled/utils": "^0.8.0",
"autoprefixer": "^10.4.14",
"cssnano-preset-default": "^5.2.14",
"postcss": "^8.4.21",
"postcss": "^8.4.27",
"postcss-nested": "^5.0.6",
"postcss-normalize-whitespace": "^5.1.1",
"postcss-selector-parser": "^6.0.11",
"postcss-selector-parser": "^6.0.13",
"postcss-values-parser": "^6.0.2"
},
"devDependencies": {
16 changes: 15 additions & 1 deletion packages/css/src/plugins/expand-shorthands/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Plugin } from 'postcss';
import { parse } from 'postcss-values-parser';
import { parse, type ChildNode } from 'postcss-values-parser';

import { background } from './background';
import { flex } from './flex';
@@ -56,6 +56,15 @@ const shorthands: Record<string, ConversionFunction> = {
*/
};

const valueIsNotSafeToExpand = (node: ChildNode): boolean => {
// This is the case where a CSS variable is given as the value, e.g.
// `padding: var(--_fl6vf6)`. Value of _fl6vf6 is unknown, so this
// cannot be expanded safely.
//
// https://github.com/atlassian-labs/compiled/issues/1331
return node.type === 'func' && node.isVar;
};

/**
* PostCSS plugin that expands shortform properties to their longform equivalents.
*/
@@ -68,12 +77,17 @@ export const expandShorthands = (): Plugin => {
if (!expand) {
return;
}

const valueNode = parse(decl.value);
if (valueNode.nodes.some(valueIsNotSafeToExpand)) {
return;
}

const longforms = expand(valueNode);
if (!longforms) {
throw new Error('Longform properties were not returned!');
}

/** Return early if not replacing a node */
if (longforms.length === 1 && longforms[0].prop === undefined) {
return;
12 changes: 12 additions & 0 deletions packages/eslint-plugin/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
# @compiled/eslint-plugin

## 0.8.1

### Patch Changes

- 40904082: Allow function parameters and imported values for left side of any logical expression in `css` attribute (A && B, A || B, A ?? B)

## 0.8.0

### Minor Changes

- 9cfda8ef: no-css-prop-without-css-function: Forbid imported styles and function parameters in `css` attribute

## 0.7.0

### Minor Changes
8 changes: 4 additions & 4 deletions packages/eslint-plugin/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@compiled/eslint-plugin",
"version": "0.7.0",
"version": "0.8.1",
"description": "A familiar and performant compile time CSS-in-JS library for React.",
"homepage": "https://compiledcssinjs.com/docs/pkg-eslint-plugin",
"bugs": "https://github.com/atlassian-labs/compiled/issues/new?assignees=&labels=bug&template=bug_report.md",
@@ -20,11 +20,11 @@
"src"
],
"devDependencies": {
"@babel/eslint-parser": "^7.21.3",
"@babel/eslint-parser": "^7.21.8",
"@types/estree": "^1.0.1",
"@types/estree-jsx": "^1.0.0",
"@typescript-eslint/parser": "^5.54.0",
"eslint": "^8.35.0",
"@typescript-eslint/parser": "^5.59.8",
"eslint": "^8.41.0",
"outdent": "^0.8.0",
"typescript": "^4.9.5"
},
Original file line number Diff line number Diff line change
@@ -6,6 +6,12 @@ Using the CSS import also improves readability and type-safety, as errors will s

When defining a CSS prop value, CSS-in-JS libraries often will not be able to figure out which library you want to use. This can cause undefined behaviour depending on how the bundler visits the file.

This rule also forbids using the `css` props with a value that Compiled cannot determine at build time, as listed below. These are situations that would cause Compiled to error at build time, or undefined behaviour if Compiled is used alongside another library (e.g. styled-components).

- imported values
- function parameters/props
- value that uses an undefined variable

## Rule details

👎 Examples of **incorrect** code for this rule:
@@ -51,3 +57,28 @@ const styles = css`

const Component = () => <div css={styles} />;
```

👎 Examples of **incorrect** code for this rule:

```js
import React from 'react';

const CoolComponent = ({ styles }) => {
return <MyComponent css={styles} />;
};
```

👍 Examples of **correct** code for this rule:

```js
import React from 'react';
import { css } from '@compiled/react';

const CoolComponent = () => {
const styles = css({
color: 'red';
});

return <MyComponent css={styles} />;
}
```

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,93 +1,188 @@
import type { Rule, Scope } from 'eslint';
import type { Node } from 'estree';
import type { JSXExpressionContainer } from 'estree-jsx';
import type { TSESTree, TSESLint } from '@typescript-eslint/utils';

import { findCompiledImportDeclarations } from '../../utils/ast';
import {
findTSCompiledImportDeclarations,
isDOMElement,
traverseUpToJSXOpeningElement,
} from '../../utils/ast';
import { addImportToDeclaration, buildImportDeclaration } from '../../utils/ast-to-string';

type Q<T> = T extends Scope.Definition ? (T['type'] extends 'Variable' ? T : never) : never;
type VariableDefinition = Q<Scope.Definition>;
type Q<T> = T extends TSESLint.Scope.Definition
? T['type'] extends 'Variable'
? T
: never
: never;
type VariableDefinition = Q<TSESLint.Scope.Definition>;
type ParameterDefinition = TSESLint.Scope.Definitions.ParameterDefinition;

const findStyleNodes = (node: Node, references: Scope.Reference[], context: Rule.RuleContext) => {
type CSSValue = TSESTree.Expression | TSESTree.JSXEmptyExpression;
type Reference = TSESLint.Scope.Reference;
type Context = TSESLint.RuleContext<string, readonly []>;

const findNodeReference = (
references: Reference[],
node: TSESTree.Expression
): Reference | undefined => {
return references.find((reference) => reference.identifier === node);
};

const handleIdentifier = (node: TSESTree.Identifier, references: Reference[], context: Context) => {
// Resolve the variable for the reference
const reference = findNodeReference(references, node);
const definition = reference?.resolved?.defs.find(
(def): def is VariableDefinition => def.type === 'Variable'
);

// Traverse to the variable value
if (definition && definition.node.init) {
findStyleNodes(definition.node.init, references, context);
} else {
const isImported = reference?.resolved?.defs.find((def) => def.type === 'ImportBinding');
const isFunctionParameter = reference?.resolved?.defs.find((def) => def.type === 'Parameter');

const jsxElement = traverseUpToJSXOpeningElement(node);

// css property on DOM elements are always fine, e.g.
// <div css={...}> instead of <MyComponent css={...}>
if (jsxElement.name.type === 'JSXIdentifier' && isDOMElement(jsxElement.name.name)) {
return;
}

if (isImported) {
context.report({
messageId: 'importedInvalidCssUsage',
node,
});
} else if (isFunctionParameter) {
context.report({
messageId: 'functionParameterInvalidCssUsage',
node,
});
} else {
context.report({
messageId: 'otherInvalidCssUsage',
node,
});
}
}
};

const handleMemberExpression = (
node: TSESTree.MemberExpression,
references: Reference[],
context: Context
) => {
const reference = findNodeReference(references, node.object);
const definition = reference?.resolved?.defs.find(
(def): def is ParameterDefinition => def.type === 'Parameter'
);

if (definition) {
context.report({
messageId: 'functionParameterInvalidCssUsage',
node,
});
}
};

const fixWrapper = (node: CSSValue, context: Context) => {
function* fix(fixer: TSESLint.RuleFixer) {
const compiledImports = findTSCompiledImportDeclarations(context);
const source = context.getSourceCode();

if (compiledImports.length > 0) {
// Import found, add the specifier to it
const [firstCompiledImport] = compiledImports;
const specifiersString = addImportToDeclaration(firstCompiledImport, ['css']);

yield fixer.replaceText(firstCompiledImport, specifiersString);
} else {
// Import not found, add a new one
yield fixer.insertTextAfter(
source.ast.body[0],
`\n${buildImportDeclaration('css', '@compiled/react')}`
);
}

if (node.type === 'ObjectExpression') {
const parent = node.parent;
if (parent && parent.type === 'TSAsExpression') {
yield fixer.replaceText(parent, `css(${source.getText(node)})`);
} else {
yield fixer.insertTextBefore(node, 'css(');
yield fixer.insertTextAfter(node, ')');
}
} else {
yield fixer.insertTextBefore(node, 'css');
}
}

return fix;
};

const findStyleNodes = (node: CSSValue, references: Reference[], context: Context): void => {
if (node.type === 'ArrayExpression') {
node.elements.forEach((arrayElement) => {
if (arrayElement) {
if (arrayElement && arrayElement.type !== 'SpreadElement') {
findStyleNodes(arrayElement, references, context);
}
});
} else if (node.type === 'LogicalExpression') {
// Traverse both values in the logical expression
findStyleNodes(node.left, references, context);
findStyleNodes(node.right, references, context);
} else if (node.type === 'ConditionalExpression') {
// Traverse both return values in the conditional expression
findStyleNodes(node.consequent, references, context);
findStyleNodes(node.alternate, references, context);
} else if (node.type === 'Identifier') {
// Resolve the variable for the reference
const reference = references.find((reference) => reference.identifier === node);
const definition = reference?.resolved?.defs.find(
(def): def is VariableDefinition => def.type === 'Variable'
);

// Traverse to the variable value
if (definition && definition.node.init) {
findStyleNodes(definition.node.init, references, context);
}
handleIdentifier(node, references, context);
} else if (node.type === 'MemberExpression') {
// Since we don't support MemberExpression yet, we don't have a contract for what it should look like
// We can skip this for now, until we implement the CSS map API
handleMemberExpression(node, references, context);
} else if (node.type === 'ObjectExpression' || node.type === 'TemplateLiteral') {
// We found an object expression that was not wrapped, report
context.report({
messageId: 'noCssFunction',
node,
*fix(fixer) {
const compiledImports = findCompiledImportDeclarations(context);
if (compiledImports.length > 0) {
// Import found, add the specifier to it
const [firstCompiledImport] = compiledImports;
const specifiersString = addImportToDeclaration(firstCompiledImport, ['css']);

yield fixer.replaceText(firstCompiledImport, specifiersString);
} else {
// Import not found, add a new one
const source = context.getSourceCode();
yield fixer.insertTextAfter(
source.ast.body[0],
`\n${buildImportDeclaration('css', '@compiled/react')}`
);
}

if (node.type === 'ObjectExpression') {
yield fixer.insertTextBefore(node, 'css(');
yield fixer.insertTextAfter(node, ')');
} else {
yield fixer.insertTextBefore(node, 'css');
}
},
fix: fixWrapper(node, context),
});
} else if (node.type === 'TSAsExpression') {
// TSAsExpression is anything in the form "X as Y", e.g.:
// const abc = { ... } as const;
return findStyleNodes(node.expression, references, context);
}
};

const createNoCssPropWithoutCssFunctionRule = (): Rule.RuleModule['create'] => (context) => ({
'JSXAttribute[name.name="css"] JSXExpressionContainer': (node: Rule.Node): void => {
const { references } = context.getScope();
const createNoCssPropWithoutCssFunctionRule =
(): TSESLint.RuleModule<string>['create'] => (context) => ({
'JSXAttribute[name.name="css"] JSXExpressionContainer': (
node: TSESTree.JSXExpressionContainer
): void => {
const { references } = context.getScope();

findStyleNodes((node as JSXExpressionContainer).expression, references, context);
},
});
findStyleNodes(node.expression, references, context);
},
});

export const noCssPropWithoutCssFunctionRule: Rule.RuleModule = {
export const noCssPropWithoutCssFunctionRule: TSESLint.RuleModule<string> = {
defaultOptions: [],
meta: {
docs: {
url: 'https://github.com/atlassian-labs/compiled/tree/master/packages/eslint-plugin/src/rules/no-css-prop-without-css-function',
recommended: 'error',
description:
'Disallows `css` prop usages without wrapping in the `css` import from `@compiled/react`. Also forbids `css` prop usages where Compiled cannot determine whether the `css` import is included at build time.',
},
messages: {
noCssFunction: 'css prop values are required to use the css import from @compiled/react',
importedInvalidCssUsage:
'Compiled cannot determine the value of imported values in the css attribute at build time. If this component uses Compiled, this will cause a build error or invalid CSS! Consider moving the value into the same file.',
functionParameterInvalidCssUsage:
'Compiled cannot determine the value of function props in the css attribute at build time. If this component uses Compiled, this will cause a build error or invalid CSS! Consider moving the value into the same file.',
otherInvalidCssUsage:
'Compiled cannot determine the value of this expression in the css attribute at build time. If this component uses Compiled, this will cause a build error or invalid CSS! Consider moving the value into the same file.',
},
type: 'problem',
fixable: 'code',
schema: [],
},
create: createNoCssPropWithoutCssFunctionRule(),
};
4 changes: 4 additions & 0 deletions packages/eslint-plugin/src/test-utils.ts
Original file line number Diff line number Diff line change
@@ -30,6 +30,10 @@ export const tester = new RuleTester(baseTesterConfig);
export const typeScriptTester = new RuleTester({
...baseTesterConfig,
parser: require.resolve('@typescript-eslint/parser'),
parserOptions: {
...baseTesterConfig.parserOptions,
ecmaFeatures: { jsx: true },
},
});

export const createAliasedInvalidTestCase = (
50 changes: 50 additions & 0 deletions packages/eslint-plugin/src/utils/ast.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import type { TSESTree, TSESLint } from '@typescript-eslint/utils';
import type { Rule } from 'eslint';
import type { ImportDeclaration, ImportSpecifier } from 'estree';

@@ -18,6 +19,55 @@ export const findCompiledImportDeclarations = (context: Rule.RuleContext): Impor
);
};

/**
* Re-implementation of findCompiledImportDeclarations for typescript-eslint.
*
* Given a rule, return any `@compiled/react` import declarations in the source code.
*
* @param context Rule context
* @returns a list of import declarations
*/
export const findTSCompiledImportDeclarations = (
context: TSESLint.RuleContext<string, readonly unknown[]>
): TSESTree.ImportDeclaration[] => {
return context
.getSourceCode()
.ast.body.filter(
(node): node is TSESTree.ImportDeclaration =>
node.type === 'ImportDeclaration' && node.source.value === COMPILED_IMPORT
);
};

/**
* Returns whether the element is a DOM element, which is all lowercase...
* as opposed to a React component, which is capitalized.
*
* @param elementName
* @returns whether the element is a DOM element (true) or a React component (false)
*/
export const isDOMElement = (elementName: string): boolean =>
elementName.charAt(0) !== elementName.charAt(0).toUpperCase() &&
elementName.charAt(0) === elementName.charAt(0).toLowerCase();

/**
* Traverses up the AST until it reaches a JSXOpeningElement. Used in conjunction with
* isDOMElement to detect whether the enclosing element is a DOM element or not.
*
* @param node
* @returns a JSXOpeningElement
*/
export const traverseUpToJSXOpeningElement = (node: TSESTree.Node): TSESTree.JSXOpeningElement => {
while (node.parent && node.type !== 'JSXOpeningElement') {
return traverseUpToJSXOpeningElement(node.parent);
}

if (node.type === 'JSXOpeningElement') {
return node;
}

throw new Error('Could not find JSXOpeningElement');
};

/**
* Returns the first declaration that has the import.
*
2 changes: 1 addition & 1 deletion packages/jest/package.json
Original file line number Diff line number Diff line change
@@ -24,6 +24,6 @@
},
"devDependencies": {
"@types/css": "^0.0.33",
"csstype": "^3.1.1"
"csstype": "^3.1.2"
}
}
2 changes: 1 addition & 1 deletion packages/jest/src/index.js.flow
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Flowtype definitions for index
* Generated by Flowgen from a Typescript Definition
* Flowgen v1.20.1
* Flowgen v1.21.0
* @flow
*/
import type { MatchFilter } from './types';
2 changes: 1 addition & 1 deletion packages/jest/src/matchers.js.flow
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Flowtype definitions for matchers
* Generated by Flowgen from a Typescript Definition
* Flowgen v1.20.1
* Flowgen v1.21.0
* @flow
*/
import type { MatchFilter } from './types';
2 changes: 1 addition & 1 deletion packages/jest/src/types.js.flow
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Flowtype definitions for types
* Generated by Flowgen from a Typescript Definition
* Flowgen v1.20.1
* Flowgen v1.21.0
* @flow
*/
import type { Pseudos } from 'csstype';
7 changes: 7 additions & 0 deletions packages/parcel-optimizer/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
# @compiled/parcel-optimizer

## 0.4.1

### Patch Changes

- Updated dependencies [a24c157c]
- @compiled/css@0.12.0

## 0.4.0

### Minor Changes
8 changes: 4 additions & 4 deletions packages/parcel-optimizer/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@compiled/parcel-optimizer",
"version": "0.4.0",
"version": "0.4.1",
"description": "A familiar and performant compile time CSS-in-JS library for React.",
"bugs": "https://github.com/atlassian-labs/compiled/issues/new?assignees=&labels=bug&template=bug_report.md",
"repository": {
@@ -19,18 +19,18 @@
"src"
],
"dependencies": {
"@compiled/css": "^0.11.0",
"@compiled/css": "^0.12.0",
"@compiled/utils": "^0.8.0",
"@parcel/plugin": "^2.8.3",
"posthtml": "^0.16.6",
"posthtml-insert-at": "^0.2.7"
},
"devDependencies": {
"@babel/types": "^7.21.4",
"@babel/types": "^7.21.5",
"@parcel/core": "^2.8.3",
"@parcel/fs": "^2.8.3",
"@parcel/types": "^2.8.3",
"prettier": "^2.8.4"
"prettier": "^2.8.8"
},
"engines": {
"parcel": "^2.0.0"
21 changes: 21 additions & 0 deletions packages/parcel-transformer/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
# @compiled/parcel-transformer

## 0.13.3

### Patch Changes

- Updated dependencies [4a2174c5]
- @compiled/babel-plugin@0.22.0

## 0.13.2

### Patch Changes

- Updated dependencies [487bbd46]
- @compiled/babel-plugin@0.21.0

## 0.13.1

### Patch Changes

- Updated dependencies [a24c157c]
- @compiled/babel-plugin@0.20.0

## 0.13.0

### Minor Changes
12 changes: 6 additions & 6 deletions packages/parcel-transformer/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@compiled/parcel-transformer",
"version": "0.13.0",
"version": "0.13.3",
"description": "A familiar and performant compile time CSS-in-JS library for React.",
"homepage": "https://compiledcssinjs.com/docs/pkg-parcel-transformer",
"bugs": "https://github.com/atlassian-labs/compiled/issues/new?assignees=&labels=bug&template=bug_report.md",
@@ -20,9 +20,9 @@
"src"
],
"dependencies": {
"@babel/core": "^7.21.4",
"@babel/generator": "^7.21.4",
"@compiled/babel-plugin": "^0.19.0",
"@babel/core": "^7.21.8",
"@babel/generator": "^7.21.5",
"@compiled/babel-plugin": "^0.22.0",
"@compiled/babel-plugin-strip-runtime": "^0.19.0",
"@compiled/utils": "^0.8.0",
"@parcel/plugin": "^2.8.3",
@@ -33,8 +33,8 @@
"@parcel/core": "^2.8.3",
"@parcel/fs": "^2.8.3",
"@parcel/types": "^2.8.3",
"@types/babel__core": "^7.20.0",
"prettier": "^2.8.4"
"@types/babel__core": "^7.20.1",
"prettier": "^2.8.8"
},
"engines": {
"parcel": "^2.0.0"
10 changes: 10 additions & 0 deletions packages/react/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# @compiled/react

## 0.14.0

### Minor Changes

- 4a2174c5: Implement the `cssMap` API to enable library users to dynamically choose a varied set of CSS rules.

### Patch Changes

- c5377cdb: Ensure that the return types of `css` and `cssMap` are readonly.

## 0.13.1

### Patch Changes
6 changes: 3 additions & 3 deletions packages/react/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@compiled/react",
"version": "0.13.1",
"version": "0.14.0",
"description": "A familiar and performant compile time CSS-in-JS library for React.",
"keywords": [
"compiled",
@@ -72,13 +72,13 @@
"jsx-dev-runtime"
],
"dependencies": {
"csstype": "^3.1.1"
"csstype": "^3.1.2"
},
"devDependencies": {
"@compiled/benchmark": "^1.1.0",
"@testing-library/react": "^12.1.5",
"@types/jsdom": "^16.2.15",
"@types/react-dom": "^17.0.19",
"@types/react-dom": "^17.0.20",
"jsdom": "^19.0.0",
"react": "^17.0.2",
"react-dom": "^17.0.2"
2 changes: 1 addition & 1 deletion packages/react/src/class-names/index.js.flow
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Flowtype definitions for index
* Generated by Flowgen from a Typescript Definition
* Flowgen v1.20.1
* Flowgen v1.21.0
* @flow
*/
import type { Node } from 'react';
52 changes: 52 additions & 0 deletions packages/react/src/css-map/__tests__/index.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/** @jsxImportSource @compiled/react */
// eslint-disable-next-line import/no-extraneous-dependencies
import { css, cssMap } from '@compiled/react';
import { render } from '@testing-library/react';

describe('css map', () => {
const styles = cssMap({
danger: {
color: 'red',
},
success: {
color: 'green',
},
});

it('should generate css based on the selected variant', () => {
const Foo = ({ variant }: { variant: keyof typeof styles }) => (
<div css={styles[variant]}>hello world</div>
);
const { getByText, rerender } = render(<Foo variant="danger" />);

expect(getByText('hello world')).toHaveCompiledCss('color', 'red');

rerender(<Foo variant="success" />);
expect(getByText('hello world')).toHaveCompiledCss('color', 'green');
});

it('should statically access a variant', () => {
const Foo = () => <div css={styles.danger}>hello world</div>;
const { getByText } = render(<Foo />);

expect(getByText('hello world')).toHaveCompiledCss('color', 'red');
});

it('should merge styles', () => {
const hover = css({ ':hover': { color: 'red' } });
const Foo = () => <div css={[hover, styles.success]}>hello world</div>;
const { getByText } = render(<Foo />);

expect(getByText('hello world')).toHaveCompiledCss('color', 'green');
expect(getByText('hello world')).toHaveCompiledCss('color', 'red', { target: ':hover' });
});

it('should conditionally apply variant', () => {
const Foo = ({ isDanger }: { isDanger: boolean }) => (
<div css={isDanger && styles.danger}>hello world</div>
);
const { getByText } = render(<Foo isDanger={true} />);

expect(getByText('hello world')).toHaveCompiledCss('color', 'red');
});
});
26 changes: 26 additions & 0 deletions packages/react/src/css-map/index.js.flow
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* Flowtype definitions for index
* Generated by Flowgen from a Typescript Definition
* Flowgen v1.21.0
* @flow
*/
import type { CSSProps, CssObject } from '../types';
/**
* ## cssMap
*
* Creates a collection of named CSS rules that are statically typed and useable with other Compiled APIs.
* For further details [read the documentation](https://compiledcssinjs.com/docs/api-cssmap).
* @example ```
* const borderStyleMap = cssMap({
* none: { borderStyle: 'none' },
* solid: { borderStyle: 'solid' },
* });
* const Component = ({ borderStyle }) => <div css={css(borderStyleMap[borderStyle])} />
*
* <Component borderStyle="solid" />
* ```
*/
declare type returnType<T: string, P> = { [key: T]: CSSProps<P> };
declare export default function cssMap<T: string, TProps>(_styles: {
[key: T]: CssObject<TProps> | CssObject<TProps>[],
}): $ReadOnly<returnType<T, TProps>>;
27 changes: 27 additions & 0 deletions packages/react/src/css-map/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { CSSProps, CssObject } from '../types';
import { createSetupError } from '../utils/error';

/**
* ## cssMap
*
* Creates a collection of named CSS rules that are statically typed and useable with other Compiled APIs.
* For further details [read the documentation](https://compiledcssinjs.com/docs/api-cssmap).
*
* @example
* ```
* const borderStyleMap = cssMap({
* none: { borderStyle: 'none' },
* solid: { borderStyle: 'solid' },
* });
* const Component = ({ borderStyle }) => <div css={css(borderStyleMap[borderStyle])} />
*
* <Component borderStyle="solid" />
* ```
*/
type returnType<T extends string, P> = Record<T, CSSProps<P>>;

export default function cssMap<T extends string, TProps = unknown>(
_styles: Record<T, CssObject<TProps> | CssObject<TProps>[]>
): Readonly<returnType<T, TProps>> {
throw createSetupError();
}
2 changes: 1 addition & 1 deletion packages/react/src/css/index.js.flow
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Flowtype definitions for index
* Generated by Flowgen from a Typescript Definition
* Flowgen v1.20.1
* Flowgen v1.21.0
* @flow
*/
import type { CSSProps, CssObject, CssFunction } from '../types';
3 changes: 2 additions & 1 deletion packages/react/src/index.js.flow
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Flowtype definitions for index
* Generated by Flowgen from a Typescript Definition
* Flowgen v1.20.1
* Flowgen v1.21.0
* @flow
*/
import type { CssFunction, CSSProps, CssType } from './types';
@@ -10,3 +10,4 @@ declare export { keyframes } from './keyframes';
declare export { styled } from './styled';
declare export { ClassNames } from './class-names';
declare export { default as css } from './css';
declare export { default as cssMap } from './css-map';
1 change: 1 addition & 0 deletions packages/react/src/index.ts
Original file line number Diff line number Diff line change
@@ -9,6 +9,7 @@ export { keyframes } from './keyframes';
export { styled } from './styled';
export { ClassNames } from './class-names';
export { default as css } from './css';
export { default as cssMap } from './css-map';

// Pass through the (classic) jsx runtime.
// Compiled currently doesn't define its own and uses this purely to enable a local jsx namespace.
2 changes: 1 addition & 1 deletion packages/react/src/keyframes/index.js.flow
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Flowtype definitions for index
* Generated by Flowgen from a Typescript Definition
* Flowgen v1.20.1
* Flowgen v1.21.0
* @flow
*/
import type { BasicTemplateInterpolations, CSSProps } from '../types';
2 changes: 1 addition & 1 deletion packages/react/src/runtime.js.flow
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Flowtype definitions for runtime
* Generated by Flowgen from a Typescript Definition
* Flowgen v1.20.1
* Flowgen v1.21.0
* @flow
*/
declare export { CC, CS, ax, ac, clearAcCache, ix } from './runtime/index';
2 changes: 1 addition & 1 deletion packages/react/src/runtime/ac.js.flow
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Flowtype definitions for ac
* Generated by Flowgen from a Typescript Definition
* Flowgen v1.20.1
* Flowgen v1.21.0
* @flow
*/
/**
2 changes: 1 addition & 1 deletion packages/react/src/runtime/ax.js.flow
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Flowtype definitions for ax
* Generated by Flowgen from a Typescript Definition
* Flowgen v1.20.1
* Flowgen v1.21.0
* @flow
*/
/**
2 changes: 1 addition & 1 deletion packages/react/src/runtime/cache.js.flow
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Flowtype definitions for cache
* Generated by Flowgen from a Typescript Definition
* Flowgen v1.20.1
* Flowgen v1.21.0
* @flow
*/
/**
2 changes: 1 addition & 1 deletion packages/react/src/runtime/css-custom-property.js.flow
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Flowtype definitions for css-custom-property
* Generated by Flowgen from a Typescript Definition
* Flowgen v1.20.1
* Flowgen v1.21.0
* @flow
*/
/**
2 changes: 1 addition & 1 deletion packages/react/src/runtime/dev-warnings.js.flow
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Flowtype definitions for dev-warnings
* Generated by Flowgen from a Typescript Definition
* Flowgen v1.20.1
* Flowgen v1.21.0
* @flow
*/
declare export var analyzeCssInDev: (sheet: string) => void;
2 changes: 1 addition & 1 deletion packages/react/src/runtime/index.js.flow
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Flowtype definitions for index
* Generated by Flowgen from a Typescript Definition
* Flowgen v1.20.1
* Flowgen v1.21.0
* @flow
*/
declare export { default as CS } from './style';
2 changes: 1 addition & 1 deletion packages/react/src/runtime/is-server-environment.js.flow
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Flowtype definitions for is-server-environment
* Generated by Flowgen from a Typescript Definition
* Flowgen v1.20.1
* Flowgen v1.21.0
* @flow
*/
/**
2 changes: 1 addition & 1 deletion packages/react/src/runtime/sheet.js.flow
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Flowtype definitions for sheet
* Generated by Flowgen from a Typescript Definition
* Flowgen v1.20.1
* Flowgen v1.21.0
* @flow
*/
import type { Bucket, StyleSheetOpts } from './types';
2 changes: 1 addition & 1 deletion packages/react/src/runtime/style-cache.js.flow
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Flowtype definitions for style-cache
* Generated by Flowgen from a Typescript Definition
* Flowgen v1.20.1
* Flowgen v1.21.0
* @flow
*/
import type { ProviderComponent, UseCacheHook } from './types';
2 changes: 1 addition & 1 deletion packages/react/src/runtime/style.js.flow
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Flowtype definitions for style
* Generated by Flowgen from a Typescript Definition
* Flowgen v1.20.1
* Flowgen v1.21.0
* @flow
*/
import type { StyleSheetOpts } from './types';
2 changes: 1 addition & 1 deletion packages/react/src/runtime/types.js.flow
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Flowtype definitions for types
* Generated by Flowgen from a Typescript Definition
* Flowgen v1.20.1
* Flowgen v1.21.0
* @flow
*/
export interface StyleSheetOpts {
2 changes: 1 addition & 1 deletion packages/react/src/styled/index.js.flow
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Flowtype definitions for index
* Generated by Flowgen from a Typescript Definition
* Flowgen v1.20.1
* Flowgen v1.21.0
* @flow
*/
import type { ComponentType } from 'react';
8 changes: 4 additions & 4 deletions packages/react/src/types.js.flow
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Flowtype definitions for types
* Generated by Flowgen from a Typescript Definition
* Flowgen v1.20.1
* Flowgen v1.21.0
* @flow
*/
import * as CSS from 'csstype';
@@ -19,11 +19,11 @@ export type CssType<TProps> = CssObject<TProps> | FunctionInterpolation<TProps>
/**
* These are all the CSS props that will exist.
*/
export type CSSProps<TProps> = CSS.Properties<CssFunction<TProps>>;
export type CssObject<TProps> = {
export type CSSProps<TProps> = $ReadOnly<CSS.Properties<CssFunction<TProps>>>;
export type CssObject<TProps> = $ReadOnly<{
...CSSProps<TProps>,
[key: string]: CssFunction<TProps>,
};
}>;
export type CssFunction<TProps = mixed> =
| CssType<TProps>
| BasicTemplateInterpolations
6 changes: 3 additions & 3 deletions packages/react/src/types.ts
Original file line number Diff line number Diff line change
@@ -21,11 +21,11 @@ export type CssType<TProps> =
/**
* These are all the CSS props that will exist.
*/
export type CSSProps<TProps> = CSS.Properties<CssFunction<TProps>>;
export type CSSProps<TProps> = Readonly<CSS.Properties<CssFunction<TProps>>>;

export type CssObject<TProps> = {
export type CssObject<TProps> = Readonly<{
[key: string]: CssFunction<TProps>;
};
}>;

// CSS inside of a CSS expression
export type CssFunction<TProps = unknown> =
2 changes: 1 addition & 1 deletion packages/react/src/utils/error.js.flow
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/**
* Flowtype definitions for error
* Generated by Flowgen from a Typescript Definition
* Flowgen v1.20.1
* Flowgen v1.21.0
* @flow
*/
declare export var createSetupError: () => Error;
2 changes: 1 addition & 1 deletion packages/utils/package.json
Original file line number Diff line number Diff line change
@@ -23,7 +23,7 @@
"source-map": "^0.7.4"
},
"devDependencies": {
"@babel/traverse": "^7.21.4",
"@babel/traverse": "^7.21.5",
"@types/convert-source-map": "^1.5.2"
}
}
22 changes: 22 additions & 0 deletions packages/webpack-loader/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,27 @@
# @compiled/webpack-loader

## 0.11.3

### Patch Changes

- Updated dependencies [4a2174c5]
- @compiled/babel-plugin@0.22.0

## 0.11.2

### Patch Changes

- Updated dependencies [487bbd46]
- @compiled/babel-plugin@0.21.0

## 0.11.1

### Patch Changes

- Updated dependencies [a24c157c]
- @compiled/css@0.12.0
- @compiled/babel-plugin@0.20.0

## 0.11.0

### Minor Changes
12 changes: 6 additions & 6 deletions packages/webpack-loader/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@compiled/webpack-loader",
"version": "0.11.0",
"version": "0.11.3",
"description": "A familiar and performant compile time CSS-in-JS library for React.",
"homepage": "https://compiledcssinjs.com/docs/pkg-webpack-loader",
"bugs": "https://github.com/atlassian-labs/compiled/issues/new?assignees=&labels=bug&template=bug_report.md",
@@ -20,18 +20,18 @@
"src"
],
"dependencies": {
"@babel/core": "^7.21.4",
"@babel/parser": "^7.21.4",
"@compiled/babel-plugin": "^0.19.0",
"@babel/core": "^7.21.8",
"@babel/parser": "^7.21.8",
"@compiled/babel-plugin": "^0.22.0",
"@compiled/babel-plugin-strip-runtime": "^0.19.0",
"@compiled/css": "^0.11.0",
"@compiled/css": "^0.12.0",
"@compiled/utils": "^0.8.0",
"enhanced-resolve": "^5.12.0",
"loader-utils": "^2.0.4",
"webpack-sources": "^3.2.3"
},
"devDependencies": {
"@compiled/react": "^0.13.0",
"@compiled/react": "^0.14.0",
"babel-loader": "^9.1.2",
"css-loader": "^6.7.3",
"memfs": "^3.4.13",
3 changes: 3 additions & 0 deletions scripts/flow-types.sh
Original file line number Diff line number Diff line change
@@ -46,6 +46,9 @@ generate() {
# Use readonly array to handle flow strict mode
sed -i.bak -E 's/css: CssObject<TProps> \| CssObject<TProps>\[\],/css: CssObject<TProps> \| \$ReadOnlyArray<CssObject<TProps>>,/g' "$file" && rm "$file.bak"

# Replace Readonly with $ReadOnly
sed -i.bak -E 's/Readonly</$ReadOnly</g' "$file" && rm "$file.bak"

# Rename JSX.IntrinsicElements to existing flow type
sed -i.bak -E 's/JSX.IntrinsicElements/$JSXIntrinsics/g' "$file" && rm "$file.bak"

62 changes: 62 additions & 0 deletions stories/css-map.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { cssMap } from '@compiled/react';
import { useState } from 'react';

export default {
title: 'css map',
};

const styles = cssMap({
success: {
color: 'green',
':hover': {
color: 'DarkGreen',
},
'@media (max-width: 800px)': {
color: 'SpringGreen',
},
},
danger: {
color: 'red',
':hover': {
color: 'DarkRed',
},
'@media (max-width: 800px)': {
color: 'Crimson',
},
},
});

export const DynamicVariant = (): JSX.Element => {
const [variant, setVariant] = useState<keyof typeof styles>('success');

return (
<>
<div
css={{
'> *': {
margin: '5px',
},
}}>
<button onClick={() => setVariant('success')}>success</button>
<button onClick={() => setVariant('danger')}>danger</button>
<div css={styles[variant]}>hello world</div>
</div>
</>
);
};

export const VariantAsProp = (): JSX.Element => {
const Component = ({ variant }: { variant: keyof typeof styles }) => (
<div css={styles[variant]}>hello world</div>
);
return <Component variant={'success'} />;
};

export const MergeStyles = (): JSX.Element => {
return <div css={[styles.danger, { backgroundColor: 'green' }]}>hello world</div>;
};

export const ConditionalStyles = (): JSX.Element => {
const isDanger = true;
return <div css={styles[isDanger ? 'danger' : 'success']}>hello world</div>;
};
814 changes: 425 additions & 389 deletions yarn.lock

Large diffs are not rendered by default.