RFC-73 Migrating our components to Compiled CSS-in-JS

RFCs are a way for Atlassian to share what we’re working on with our valued developer community.

It’s a document for building shared understanding of a topic. It expresses a technical solution, but can also communicate how it should be built or even document standards. The most important aspect of an RFC is that a written specification facilitates feedback and drives consensus. It is not a tool for approving or committing to ideas, but more so a collaborative practice to shape an idea and to find serious flaws early.

Please respect our community guidelines: keep it welcoming and safe by commenting on the idea not the people (especially the author); keep it tidy by keeping on topic; empower the community by keeping comments constructive. Thanks!

Project summary

Atlassian has standardised on Compiled CSS-in-JS as our build-time CSS-in-JS library. Compiled is an Atlassian initiative, and it is already in wide use across our products. We’re completing the transition by migrating all Atlaskit components to Compiled CSS-in-JS.

This includes the Atlassian Design System, Editor, Media, and other components — all packages in the @atlaskit npm scope currently styled using Emotion.

The primary reason we are migrating to Compiled is to improve performance — reducing the runtime performance impact and bundle size of our components, and to better leverage modern React features available in version 18 and onwards, such as concurrent rendering and streaming SSR.

Compiled’s CSS generation creates static CSS at build time, reducing JavaScript bundle size and runtime overhead. This optimisation allows for the removal of duplicate or unused styles, resulting in smaller CSS files that enhance rendering times. Additionally, static CSS files benefit from better browser caching, improving performance during repeat visits.

This move also aligns Atlassian’s coding standards and ultimately benefits both developers and customers by enabling faster product development and more reliable engineering.

We’re excited for you to benefit from this, whether you directly use Compiled in your apps, or by having faster Atlassian products upon which to build your apps for our mutual customers!

Scope of this RFC

In this RFC, we’re seeking feedback on how to best support you as Marketplace partners during this transition, including tooling and guidance.

We intend to add additional support throughout the RFC period and not just at the end, with the “Resolve” date representing the end of new additions.

  • Publish: 07 Nov 2024
  • Discuss: 16 Dec 2024
  • Resolve: 30 Jan 2025

These changes affect apps that use Atlaskit components, built on Forge Custom UI or Connect. If you’re using Forge UI Kit, you’re good to go! No changes will be required.

Timeline for migration of packages

We have migrated a pilot component to Compiled (more detail below), and will soon be starting to migrate all Atlaskit packages.

All migrations to Compiled will be shipped as new Major versions. Our current plan:

  • Oct 2024: Shipped experimental Compiled version of @atlaskit/lozenge component
  • Nov 2024–ongoing: Provide support for Marketplace partners to adopt and troubleshoot
  • Mid Nov 2024: First batch of Atlassian Design System components will be migrated to Compiled
  • Late Nov–Dec 2024: Remaining Atlassian Design System components will be migrated to Compiled, along with the first Editor and Media components
  • Progressively from Jan 2025: All remaining Atlaskit packages will be migrated

Asks (what this would mean for you — action may be required)

If your bundler supports CSS imports (import 'styles.css';), packages using Compiled should continue to work out of the box. We expect this to be the case in most scenarios. However, we ask all Marketplace partners to look out for possible issues, especially if you:

  • Notice visual breaking changes when upgrading
  • Are applying manual style overrides to @atlaskit packages
  • Use xcss via @atlaskit/primitives
  • Use a bundler that is not currently configured to support CSS imports

This migration may come with unforeseen side effects, depending on the type of bundler and/or CSS-in-JS libraries used in conjunction with Atlaskit packages — there are many more combinations than we can test. We are asking for feedback on the following areas:

  • Please try using our experimental Lozenge component (see next section for instructions), and report any unexpected issues or visual breaking changes.
  • Do you experience any observable performance impacts (negative or positive)?
  • For those of you who need to use the Compiled plugin but can’t because you’re using something other than Webpack or Parcel, please share which bundler you’re currently using below, and we will investigate/recommend a way forward.
  • Are there ways this might negatively impact Marketplace partners not yet mentioned here?

We’re here to support you through this transition as best we can!

Testing compatibility using the Lozenge component

Test component entry-point: import Lozenge from '@atlaskit/lozenge/compiled';

To ensure your application is configured correctly for the migration to Compiled CSS-in-JS, we have provided a test component: @atlaskit/lozenge/compiled. This component serves as a verification tool to check if your setup supports the new CSS-in-JS solution effectively.

How to use

  1. Installation: Ensure that you have the latest version of the @atlaskit/lozenge/compiled package installed in your project.
    To check you’re on the correct version, inspect dist/cjs/compiled. You will see an index.compiled.css and an index.js importing that file.
  2. Integration: Integrate the test component into your application as you would with any other Atlaskit component.
+import Lozenge from '@atlaskit/lozenge/compiled';
-import Lozenge from '@atlaskit/lozenge';
  1. Verification: Build and run your application to verify that the component renders correctly without any styling issues. This indicates that your application is correctly configured to handle Compiled CSS-in-JS.

Troubleshooting

  • If you encounter any issues, ensure that your bundler is configured to support Compiled CSS-in-JS. Refer to the specific configuration guidelines for your bundler (e.g. Webpack, Parcel) as outlined in the migration documentation.
  • For Webpack users, make sure to include style-loader and css-loader in your configuration. We highly recommend using @compiled/webpack-loader to avoid visual regressions.

By using this test component, you can confidently proceed knowing that your application is ready to leverage the performance and consistency benefits of Compiled CSS-in-JS.

Update your bundler configuration(s)

The Compiled variants of our packages will be published to npm with internal stylesheets, such as node_modules/@atlaskit/lozenge/cjs/compiled/index.compiled.css. Your bundler must include, combine, and run the Compiled plugins over them. By default, many bundlers will load these automatically, but without @compiled/parcel-config or @compiled/webpack-loader, we cannot guarantee those styles will be ordered correctly. There is a chance of some visual regressions with this migration.

Update your bundler configuration, and if issues persist, please leave a comment below, and we will be available to assist you.

Note: Compiled does not have plugin support for every bundler, so please let us know if you require support for a specific bundler.

Using Parcel (recommended)

yarn install @atlaskit/tokens
yarn install --dev @compiled/parcel-config

Setup your .compiledcssrc:

{
    "transformerBabelPlugins": [["@atlaskit/tokens/babel-plugin"]],
    "extract": true,
    "inlineCss": true,
    "sortShorthand": true
}

Setup your .parcelrc:

{  "extends": ["@parcel/config-default", "@compiled/parcel-config"] }`

For full documentation, refer to https://atlassian.design/get-started/develop#set-up-your-bundling-environment

Using Webpack

Your configuration may vary greatly, this would be a typical production-facing Webpack config:

yarn install @atlaskit/tokens
yarn install --dev @compiled/webpack-loader mini-css-extract-plugin
const { CompiledExtractPlugin } = require('@compiled/webpack-loader');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
module.exports = {
  module: {
    rules: [
      {
        test: /\.(js|ts|tsx)$/,
        use: [
          { loader: 'babel-loader' },
          {
            // ↓↓ Compiled should run last ↓↓
            loader: '@compiled/webpack-loader',
            options: {
              transformerBabelPlugins: ['@atlaskit/tokens/babel-plugin'],
              extract: true,
              inlineCss: true
            }
          }
        ]
      },
      {
        test: /compiled-css\.css$/i,
        use: [MiniCssExtractPlugin.loader, 'css-loader'],
      },
      {
        test: /(?<!compiled-css)(?<!\.compiled)\.css$/,
        use: ['style-loader', 'css-loader'],
      }
    ]
  },
  plugins: [
    new MiniCssExtractPlugin(),
    new CompiledExtractPlugin({ sortShorthand: true })
  ],
}

For full documentation, refer to https://atlassian.design/get-started/develop#set-up-your-bundling-environment

Update your Babel configuration(s)

In order to use Compiled in Jest or CI, you must use @compiled/babel-plugin (this is preconfigured through @compiled/webpack-loader or @compiled/parcel-config).

yarn install @atlaskit/tokens
yarn install --dev @compiled/babel-plugin
// babel.config.js
module.exports = {
  plugins: [
    // This will handle all `token()` calls outside of Compiled
    '@atlaskit/tokens/babel-plugin',
    // ↓↓ Compiled should run last ↓↓
    [
      '@compiled/babel-plugin',
      { transformerBabelPlugins: ['@atlaskit/tokens/babel-plugin'] }
    ],
  ],
}`

For full documentation, refer to https://atlassian.design/get-started/develop#set-up-your-bundling-environment

Want to use Compiled in your app? (optional)

It’s entirely optional for partners to migrate apps to Compiled. We recommend it if you want to access the performance improvements!

If you are interested, Compiled provides automated migration tooling (codemods) to allow you to quickly migrate applications from emotion or styled-components. Please see How to migrate to Compiled? and @compiled/codemods for technical migration guidance.

-import styled from 'styled-components';
+import { styled } from '@compiled/react';

FAQs

Do I need to migrate to Compiled as well?<

No, Compiled can be used alongside other CSS-in-JS libraries. It is possible to safely interop compiled @atlaskit packages with UI components styled with other libraries (Emotion, styled-components, etc.).

However, please be aware of potential style-ordering and selector-related issues where custom overrides or hard-coded selectors may change as a result. This may happen when styling the same element with two different libraries.

What kind of breaking package changes will come with it?

While we typically expect minimal or no breaking visual changes for the majority of applications, we may be actioning some deprecations in the near future, primarily the xcss export from @atlaskit/primitives will be deprecated and replaced with the @atlaskit/css.cssMap package as described on https://atlassian.design/components/css/examples.

There may be similar breaking changes, such as props.cssFn or similar styling APIs that will not work with Compiled that may be removed, but we will try to document this in each package’s major version changelog(s). Most of them are already deprecated or linted against with our eslint tooling.

Will the existing versions of packages still work?

Yes, in the short term. You can continue to use versions prior to the Major version that migrates to Compiled, for example, until you need to test with Compiled. In the long term, we strongly recommend upgrading to benefit from improvements in UX, performance, security, and accessibility.

7 Likes

We do not use parcel, webpack nor babel, but vite to build our app. I did not try it out yet, but just to mention another common build tool, that should be covered in the documentation.

3 Likes

We do use Webpack, but we do not use babel-loader.

Is there a specific reason why this cannot work with normal CSS loaders, like tailwind does? For example

      {
        test: /\.css$/,
        use: [
          { loader: 'style-loader' },
          { loader: 'css-loader' },
          {
            loader: 'postcss-loader',
            options: {
              postcssOptions: {
                plugins: [
                  'postcss-preset-env',
                  'tailwindcss',
                  'autoprefixer'
                ]
              }
            }
          }
        ]
      }

Please note that we also use AtlasKit packages in our P2 plugins and rely heavily on the Atlassian Webpack resource plugin. I assume this will continue to work as expected as well?

1 Like

We also use vite in a couple of our Cloud apps. Would be good if we can continue to use it without too much hazzle.

Hey @lkimmel @remie :wave:

In those cases in both of those cases, Compiled-enabled packages should work as normal.

If your bundler supports CSS imports (import 'styles.css'; ), packages using Compiled should continue to work out of the box. We expect this to be the case in most scenarios.

I should add here, “even if you’re using Vite and esbuild” since they come pre-configured with the css import syntax.

If/when you have time, give the new lozenge variant a try and let me know if you run into any issues :pray: If you observe any side-effects let me know and we can experiment with some other approaches.

I’ll look into this next week:

AtlasKit packages in our P2 plugins

Cheers!

2 Likes

I guess this will probably ease overriding component styles, since the specificity of inline styles usually was higher than the one of our CSS files. So if that works and the update works with all common bundlers this would be an improvement.

Is it possible to “export” the styles into a .css file, which we can use with standard html? I.e. a similar use like with AUI.

OK, I’ve tried using the Lozenge variant with compiled and Webpack is not giving any warnings/errors, but it also doesn’t look like a Lozenge.

This is the old version:

This is the compiled variant:

This is the compiled variant in our DC plugin. Webpack also does not give any errors/warnings here, but again the result does not look correct to me:

Hey @remie, if I had to guess, you had classes attached to the Lozenge that weren’t actually available on the page, eg. not in a <style> tag. I found a small bug on our side causing that stylesheet to not be resolved for all bundlers (and we’re putting in place tooling and config to make sure that’s applied to all packages in the near future).

Hasn’t landed as of writing yet, but the next patch bump to @atlaskit/lozenge (with a minor bump to @atlaskit/primitives) should fix this in the next 24 hours—if I’ve understood the problem.

1 Like

Vite users here, no problems testing 11.12.2.

1 Like

Next.js user here. It does not like that you are importing CSS as a node_modules dependency:

 ⨯ ./node_modules/@atlaskit/lozenge/dist/esm/compiled/index.compiled.css
Global CSS cannot be imported from within node_modules.
Read more: https://nextjs.org/docs/messages/css-npm
Location: node_modules/@atlaskit/lozenge/dist/esm/compiled/index.js

See also: CSS Imported by a Dependency | Next.js
Minimal reproducible example: https://codesandbox.io/p/devbox/tlkmrp

I’m going to try (in the near future) to see if switching over to webpack in Next + style-loader and css-loader described will get rid of this. Even if it does, we’ll see if the performance hit from switching away from swc is too annoying. This might be the kick in the behind I need to go away from Next.js…

PS: Sorry for being late to the party here, I was sick for 3 out of the last 4 weeks :grimacing:
PPS: I noticed you already started rolling this out to other Atlaskit modules (>.> empty-state) Might have to do some rollbacks for the time being.

1 Like

Hi @DanielDelCore,

other next.js app here (using webpack/babel); same error as reported by @tobitheo.

Very likely related, we also have a problem with @atlaskit/tokens/babel-plugin, where we’re getting a

Error: token() must have a string as the first argument

for a point in code where we’re wrapping a call to token. There might be other ways to do what we’re doing there, but I’m afraid this needs some investigation.

I can also confirm @tobitheo’s point that @compiled already seems to bleed into the published AK components. I’m not sure where exactly this came in, but I had to revert a bunch of dependency bumps to get my build going again. (I believe it might have been some transitive dependency on @atlaskit/heading, version 4?, but I’m not sure.)

@atlaskit/section-message@6.8.2 seems to bring in @compiled:

@atlaskit/section-message/node_modules/@atlaskit/heading/dist/esm/heading.partial.compiled.css

Hey @tobitheo @hannes-finesoftware, thanks for raising this and apologies for the inconvenience! I’m going to look into this today and see if there are any workarounds available to us!

Hi @DanielDelCore ,

Maybe you can help:

Hey all :wave:, (@tobitheo @hannes-finesoftware)

We did some digging yesterday, I was scratching my head because we tested with Nextjs and CRA before publishing these comms.

We found that this issue only happens when using Nextjs with the Pages Router. To work around it you can use the config shown here :point_down: Although, it unfortunately does require manually adding packages to the transpileModules array. Could you please give this a try and LMK if you run into any issues :pray:

https://codesandbox.io/p/devbox/next-compiled-css-pages-forked-n2k9gn?workspaceId=ws_KeNunwkWFCiNERoCoqw87D

Alternatively you can upgrade to App router and it should work out of the box, Vercel provides some docs and codemods to make this easier.

https://codesandbox.io/p/devbox/next-compiled-css-pages-forked-c8chnn?workspaceId=ws_KeNunwkWFCiNERoCoqw87D

Cheers!

Sorry for the late response @marc,

That should be possible via webpack by combining chunks matching node_modules/@atlaskit/**/dist/cjs/*.compiled.css or similar.
It’s a similar process to combining various libraries into a vendor.js file.

However, we don’t have direct control over how css files generated so there may be some nuances/gotchas to be conscious of. These styles also change often, whenever we touch our internal styles classnames will be regenerated (hashed) which may break your component implementations.

TL;DR It should be possible, but will be fragile if the right precautions aren’t taken, so do so at your own risk.

Hi @DanielDelCore ,
Thank you for the answer. This in effect means no CSS like AUI provided, as constantly changing CSS without documentation is not what we are looking for.

1 Like

@daniel

When I do either the transpileModules trick or use the app router, the build fails with e.g.

SyntaxError: importSource cannot be set when runtime is classic.
> 1 | /**
    | ^
  2 |  * @jsxRuntime classic
  3 |  * @jsx jsx
  4 |  */
Import trace for requested module:
../../../../node_modules/@atlaskit/lozenge/dist/esm/Lozenge/index.js

(If it’s not Lozenge failing, it’s somewhere else in AK.)

For context, we’re using @emotion for our app’s own styling, with

'preset-react': {
    runtime: 'automatic',
    importSource: '@emotion/react',
},

in babel.

Is this RFC related with the deprecation of styling on Select (https://developer.atlassian.com/changelog/#CHANGE-1953)?

If so, will there be alternatives? We’re heavily using components , formatOptionLabel, style props on Select to e.g correctly set z-index, menu width, icons on select options, actions like ‘Select all’ etc…

3 Likes