Polaris Migrator

Codemod transformations to help upgrade your Polaris codebase.

npm version

Demo of Polaris migrator

Usage

Example
npx @shopify/polaris-migrator <migration> <path>
  • migration - name of migration, see available migrations on the docs site below.
  • path - files or directory to perform migration
  • --dry Do a dry-run, no code will be edited
  • --print Prints the changed output for comparison

Migrations

v10

styles-insert-stylelint-disable

Insert stylelint disable comments for stylelint-polaris >= v5 so that existing failures are not blocking a codebase from initializing the linter.

Example
+ // stylelint-disable-next-line -- generated by polaris-migrator DO NOT COPY
padding: 1rem;

Example
npx @shopify/polaris-migrator styles-insert-stylelint-disable <path>

react-replace-text-component

Replace legacy text components DisplayText, Heading, Subheading, Caption, TextStyle, and VisuallyHidden with the new single Text component.

Example
- <DisplayText size="medium">Display text</DisplayText>
- <Heading>Heading</Heading>
+ <Text variant="headingXl" as="p">Display text</Text>
+ <Text variant="headingMd" as="h2">Heading</Text>

Example
npx @shopify/polaris-migrator react-replace-text-component <path>

react-rename-component-prop

A generic codemod to rename any component prop.

Example
- <MyComponent prop="value" />
- <MyComponent prop />
+ <MyComponent newProp="value" />
+ <MyComponent newProp />

Example
npx @shopify/polaris-migrator react-rename-component-prop <path> --component=MyComponent --from=prop --to=newProp

v9

For projects that use the @use rule, all Sass related migrations (ex: replace-sass-spacing) accept a namespace flag to target a specific <namespace>.<variable|function|mixin>.

Example
npx @shopify/polaris-migrator <scss-migration> <path> --namespace="legacy-polaris-v8"

scss-replace-breakpoints

Replace legacy static breakpoint mixins with the new Polaris media query variables.

Example
- @include page-content-when-layout-not-stacked {}
+ @media #{$p-breakpoints-md-up} {}

Example
npx @shopify/polaris-migrator scss-replace-breakpoints <path>

scss-replace-border

Replace usage of the legacy SCSS border()) function in border declarations with corresponding Polaris shape token.

Example
- border: border();
+ border: var(--p-border-base);

- border: border(divider);
+ border: var(--p-border-divider);

Example
npx @shopify/polaris-migrator scss-replace-border <path>

scss-replace-border-radius

Replace usage of the legacy SCSS border-radius()) function in border-radius declarations with corresponding Polaris shape tokens.

Example
- border-radius: border-radius();
+ border-radius: var(--p-border-radius-1);

- border-radius: border-radius(large);
+ border-radius: var(--p-border-radius-large);

Example
npx @shopify/polaris-migrator scss-replace-border-radius <path>

scss-replace-border-width

Replace usage of the legacy SCSS border-width()) function in border and border-width declarations with corresponding Polaris shape tokens.

Example
- border-width: border-width();
+ border-width: var(--p-border-width-1);

- border-width: border-width(thick);
+ border-width: var(--p-border-width-2);

Example
npx @shopify/polaris-migrator scss-replace-border-width <path>

scss-replace-color

Replace the legacy SCSS color() function with the supported CSS custom property token equivalent (ex: var(--p-surface)). This will only replace a limited subset of mapped values. See the color-maps.ts for a full list of color mappings based on the CSS property.

Example
- color: color('ink');
- background: color('white');
+ color: var(--p-text);
+ background: var(--p-surface);

Example
npx @shopify/polaris-migrator scss-replace-color <path>

scss-replace-duration

Replace the legacy SCSS duration() function with the corresponding Polaris motion token.

Example
- transition-duration: legacy-polaris-v8.duration('slow');
+ transition-duration: var(--p-duration-300);

- transition: opacity legacy-polaris-v8.duration('slow') linear;
+ transition: opacity var(--p-duration-300) linear;

Example
npx @shopify/polaris-migrator scss-replace-duration <path>

scss-replace-easing

Replace the legacy SCSS easing() function with the corresponding Polaris motion token.

Example
- transition-timing-function: legacy-polaris-v8.easing('in');
+ transition-timing-function: var(--p-ease-in);

- transition: opacity 300ms legacy-polaris-v8.easing('in');
+ transition: opacity 300ms var(--p-ease-in);

Example
npx @shopify/polaris-migrator scss-replace-easing <path>

scss-replace-font-family

Replace legacy SCSS font-family() function with the corresponding Polaris font token.

Example
- font-family: font-family(monospace);
+ font-family: var(--p-font-family-mono);

Example
npx @shopify/polaris-migrator scss-replace-font-family <path>

scss-replace-font-size

Replace legacy SCSS font-size() function with the corresponding Polaris font token.

Example
- font-size: font-size(input, base);
+ font-size: var(--p-font-size-200);;

Example
npx @shopify/polaris-migrator scss-replace-font-size <path>

scss-replace-line-height

Replace legacy SCSS line-height() function with the corresponding Polaris font token.

Example
- line-height: line-height(caption, base);
+ font-family: var(--p-font-line-height-2);

Example
npx @shopify/polaris-migrator scss-replace-line-height <path>

scss-replace-spacing

Replace the legacy SCSS spacing() function with the supported CSS custom property token equivalent (ex: var(--p-space-4)).

Example
- padding: spacing();
- margin: spacing(loose) spacing(tight);
+ padding: var(--p-space-4);
+ margin: var(--p-space-5) var(--p-space-2);

Example
npx @shopify/polaris-migrator scss-replace-spacing <path>

scss-replace-text-emphasis

Replace legacy static mixins with their corresponding declarations and CSS custom properties.

Example
- @include text-emphasis-normal;
+ color: var(--p-text);
+ font-weight: var(--p-font-weight-regular);

Example
npx @shopify/polaris-migrator scss-replace-text-emphasis <path>

scss-replace-z-index

Replace the legacy SCSS z-index() function with the supported CSS custom property token equivalent (ex: var(--p-z-1)).

Any invocations of z-index() that correspond to a z-index design-token i.e. --p-z-1 will be replaced with a css variable declaration. This includes invocations to the $fixed-element-stacking-order sass map i.e. z-index(modal, $fixed-element-stacking-order).

Example
- .decl-1 {
-   z-index: z-index(content);
- }
- .decl-2 {
-   z-index: z-index(modal, $fixed-element-stacking-order)
- }
+ decl-1 {
+   z-index: var(--p-z-1);
+ }
+ .decl-2 {
+   z-index: var(--p-z-11)
+ }

Invocations of z-index within an arithmetic expression will be appended with a comment for review and manual migration. Generally in these instances you'll want to wrap the suggested code change in a calc however this may defer on a case by case basis in your codebase.

Example
.decl-3 {
+  /* polaris-migrator: Unable to migrate the following expression. Please upgrade manually. */
+  /* z-index: var(--p-z-1) + 1 */
  z-index: z-index(content) + 1
}

Invocations of z-index with a custom sass map property, will also be appended with a comment for review and manual migration.

Example
.decl-3 {
+  /* polaris-migrator: Unable to migrate the following expression. Please upgrade manually. */
+  /* z-index: map.get($custom-sass-map, modal) */
  z-index: z-index(modal, $custom-sass-map)
}

In these cases you may also want to run npx sass-migrator module <path> --migrate-deps --load-path <load-path> to ensure that map.get is in scope**.

Be aware that this may also create additional code changes in your codebase, we recommend running this only if there are large number of instances of migrations from z-index to map.get. Otherwise it may be easier to add use 'sass:map' to the top of your .scss file manually.

Example
npx @shopify/polaris-migrator scss-replace-z-index <path>

styles-tokenize-font

Replace legacy static font values with Polaris custom properties for font-size, font-weight, and line-height declarations.

Example
- font-size: 16px;
+ font-size: var(--p-font-size-200);

- font-weight: 400;
+ font-weight: var(--p-font-weight-regular);

- line-height: 20px;
+ font-family: var(--p-font-line-height-2);

Example
npx @shopify/polaris-migrator styles-tokenize-font <path>

styles-tokenize-motion

Replace timings (ms, s) in transition declarations (transition, transition-duration, transition-delay, and transition-timing-function) and animation declarations (animation, animation-duration, animation-delay, and animation-timing-function) with the corresponding Polaris motion token.

Example
- transition-duration: 100ms;
+ transition-duration: var(--p-duration-100);

- transition-timing-function: linear;
+ transition-timing-function: var(--p-linear);

- transition: opacity 100ms linear;
+ transition: opacity var(--p-duration-100) linear;

- transition: opacity 100ms linear, left 100ms linear;
+ transition: opacity var(--p-duration-100) linear, left var(--p-duration-100) linear;

- animation-duration: 100ms;
+ animation-duration: var(--p-duration-100);

- animation-timing-function: linear;
+ animation-timing-function: var(--p-linear);

- animation: 100ms linear fadeIn;
+ animation: var(--p-duration-100) linear fadeIn;

- animation: 100ms linear slideIn, 100ms linear slideIn;
+ animation: var(--p-duration-100) linear slideIn, var(--p-duration-100) linear slideIn;

Example
npx @shopify/polaris-migrator styles-tokenize-motion <path>

styles-tokenize-shape

Replace usage of the legacy SCSS rem() function and hard-coded lengths (px, rem) in border, border-width, and border-radius declarations with corresponding Polaris shape token.

Example
- border: 1px solid transparent;
+ border: var(--p-border-width-1) solid transparent;

- border-width: 0.0625rem;
+ border-width: var(--p-border-width-1);

- border-radius: 4px;
+ border-radius: var(--p-border-radius-1);

Example
npx @shopify/polaris-migrator replace-border-declarations <path>

styles-tokenize-space

Replace lengths and functions (px, rem and rem()) in spacing declarations (padding, margin, and gap) with the corresponding Polaris spacing token.

Example
- padding: 16px;
+ padding: var(--p-space-4);

- margin: 1rem;
+ margin: var(--p-space-4);

- gap: rem(16px);
+ gap: var(--p-space-4);

Example
npx @shopify/polaris-migrator styles-tokenize-space <path>

Creating Migrations

Sometimes referred to as "codemods", migrations are JavaScript functions which modify some code from one form to another (eg; to move between breaking versions of @shopify/polaris). ASTs (Abstract Syntax Trees) are used to "walk" through the code in discreet, strongly typed steps, called "nodes". All changes made to nodes (and thus the AST) are then written out as the new/"migrated" version of the code.

polaris-migrator supports two types of migrations:

  • SCSS Migrations
  • Typescript Migrations

Creating a SCSS migration

Run yarn new-migration to generate a new migration from the scss-migration template:

Example
❯ yarn new-migration
$ yarn workspace @shopify/polaris-migrator generate
$ plop
? [PLOP] Please choose a generator. (Use arrow keys)
❯ scss-migration
  typescript-migration

Next, provide the name of your migration. For example; scss-replace-function:

Example
? [PLOP] Please choose a generator. sass-migration
? Name of the migration (e.g. scss-replace-function) scss-replace-function

The generator will create the following files in the migrations folder:

Example
migrations
└── scss-replace-function
    ├── scss-replace-function.ts
    └── tests
        ├── scss-replace-function.input.scss
        ├── scss-replace-function.output.scss
        └── scss-replace-function.test.ts

The SCSS migration function

Each migrator has a default export adhering to the Stylelint Rule API. A PostCSS AST is passed as the root and can be mutated inline, or emit warning/error reports.

Continuing the example, here is what the migration may look like if our goal is to replace the Sass function hello() with world().

Example
// polaris-migrator/src/migrations/replace-sass-function/replace-sass-function.ts
import {
  isSassFunction,
  StopWalkingFunctionNodes,
  createSassMigrator,
} from '../../utilities/sass';
import type {PolarisMigrator} from '../../utilities/sass';

const replaceHelloWorld: PolarisMigrator = (_, {methods}, context) => {
  return (root) => {
    methods.walkDecls(root, (decl) => {
      const parsedValue = valueParser(decl.value);
      parsedValue.walk((node) => {
        if (isSassFunction('hello', node)) {
          if (context.fix) {
            node.value = 'world';
          } else {
            methods.report({
              node: decl,
              severity: 'error',
              message:
                'Method hello() is no longer supported. Please migrate to world().',
            });
          }

          return StopWalkingFunctionNodes;
        }
      });
      if (context.fix) {
        decl.value = parsedValue.toString();
      }
    });
  };
};

export default createSassMigrator('replace-hello-world', replaceHelloWorld);

A more complete example can be seen in styles-tokenize-space.ts.

Testing

The template will also generate starting test files you can use to test your migration. In your migrations tests folder, you can see 3 files:

  • scss-replace-function.test.ts – Runs the fixtures and sets up additional migration options
  • scss-replace-function.input.scss – The starting source input
  • scss-replace-function.output.scss – The expected output after migration

The main test file will load the input/output fixtures to test your migration against. You can configure additional fixtures and test migration options (see the replace-sass-spacing.test.ts as an example).

Running Migrations

Run tests locally from workspace root by filtering to the migrations package:

Example
npx turbo run test --filter=polaris-migrator -- scss-replace-function

Testing in another codebase

Once you are confident the migration is ready, create a new pull request including your migration and a new changeset.

In your PR, you can add a comment with the text /snapit to create a new snapshot release. Once created, this snapshot can be used in a separate codebase:

Example
# example snapshot release
npx @shopify/polaris-migrator@0.0.0-snapshot-release-20220919213536 scss-replace-function "./app/**/*.scss"

Linting and formatting migrations

The migrator doesn't include a default formatter. It is recommended to run your own linter and formatter after running migrations. For example, if you are using ESLint and/or Prettier:

Example
npx eslint --fix .
npx prettier --write .

Checking migrations

Running a migration can potentially modify thousands of files. For more complex migrations, a comment may be added suggesting the change is manually checked. You can quickly perform a manual search for this comment in your text editor:

Example
polaris-migrator: Unable to migrate the following expression. Please upgrade manually.

After applying a migration, it might be helpful to commit the changes that do not need a manual check from those that do. You can do this a few different ways, but we suggest staging all your changes, then unstaging those that include the manual check comment:

Example
# Stash files with "polaris-migrator:" comments
git stash push $(grep -r -l "polaris-migrator:" $(git ls-files -m))

# Stage all files without "polaris-migrator:" comments
git add .

# Bring back the change with "polaris-migrator:" comments
git stash pop

# (optional) if there a files that have both "polaris-migrator:" comments
# _and_ complete fixes, add the complete fixes now
git add -p

# Commit all the complete fixes:
git commit

# Now you're left with changes that have "polaris-migrator:" comments only

Resources