Polaris Migrator
Codemod transformations to help upgrade your Polaris codebase.
Usage
Examplenpx @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;
Examplenpx @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>
Examplenpx @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 />
Examplenpx @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>
.
Examplenpx @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} {}
Examplenpx @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);
Examplenpx @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);
Examplenpx @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);
Examplenpx @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);
Examplenpx @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;
Examplenpx @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);
Examplenpx @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);
Examplenpx @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);;
Examplenpx @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);
Examplenpx @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);
Examplenpx @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);
Examplenpx @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.
Examplenpx @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);
Examplenpx @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;
Examplenpx @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);
Examplenpx @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);
Examplenpx @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:
Examplemigrations └── 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 optionsscss-replace-function.input.scss
– The starting source inputscss-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:
Examplenpx 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:
Examplenpx 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:
Examplepolaris-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
- The jscodeshift API
- Writing a PostCSS plugin
- CodeshiftCommunity Recipes
- Common utilities: