Skip to content

Commit c8718f9

Browse files
authored
feat: Add codemod to migrate to subpaths (adobe#9839)
1 parent e4ea723 commit c8718f9

File tree

7 files changed

+456
-6
lines changed

7 files changed

+456
-6
lines changed

packages/dev/codemods/src/index.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
const {parseArgs} = require('node:util');
33
import {s1_to_s2} from './s1-to-s2/src';
44
import {use_monopackages} from './use-monopackages/src';
5+
import {use_subpaths} from './use-subpaths/src';
56

67
interface JSCodeshiftOptions {
78
/**
@@ -52,9 +53,12 @@ export interface UseMonopackagesCodemodOptions extends JSCodeshiftOptions {
5253
packages?: string
5354
}
5455

55-
const codemods: Record<string, (options: S1ToS2CodemodOptions | UseMonopackagesCodemodOptions) => void> = {
56+
export interface UseSubpathsCodemodOptions extends JSCodeshiftOptions {}
57+
58+
const codemods: Record<string, (options: S1ToS2CodemodOptions | UseMonopackagesCodemodOptions | UseSubpathsCodemodOptions) => void> = {
5659
's1-to-s2': s1_to_s2,
57-
'use-monopackages': use_monopackages
60+
'use-monopackages': use_monopackages,
61+
'use-subpaths': use_subpaths
5862
};
5963

6064
// https://github.com/facebook/jscodeshift?tab=readme-ov-file#usage-cli
@@ -103,6 +107,7 @@ async function main() {
103107
parser: 'tsx',
104108
ignorePattern: '**/node_modules/**',
105109
path: '.',
110+
extensions: 'js,jsx,mjs,cjs,ts,tsx',
106111
...values
107112
}));
108113
}

packages/dev/codemods/src/use-monopackages/src/codemod.ts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import {API, FileInfo, ImportSpecifier, Options} from 'jscodeshift';
22
const fs = require('fs');
33
const path = require('path');
4+
const Module = require('module');
5+
const url = require('url');
46

57
function areSpecifiersAlphabetized(specifiers: ImportSpecifier[]) {
68
const specifierNames = specifiers.map(
@@ -54,10 +56,16 @@ const transformer: Transformer = function transformer(file: FileInfo, api: API,
5456
const monopackageExports: Record<string, string[]> = {};
5557

5658
selectedPackages.forEach((pkg) => {
57-
const indexPath = path.join(
58-
process.cwd(),
59-
`node_modules/${packages[pkg].monopackage}/dist/types.d.ts`
60-
);
59+
let indexPath: string;
60+
try {
61+
let pkgPath = path.dirname(Module.findPackageJSON(packages[pkg].monopackage, url.pathToFileURL(file.path || `${process.cwd()}/index`))!);
62+
indexPath = `${pkgPath}/dist/types/exports/index.d.ts`;
63+
if (!fs.existsSync(indexPath)) {
64+
indexPath = `${pkgPath}/exports/index.ts`;
65+
}
66+
} catch {
67+
return;
68+
}
6169

6270
if (fs.existsSync(indexPath)) {
6371
anyIndexFound = true;
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Codemod to use subpath exports
2+
3+
Replaces monopackage imports with subpaths.
4+
5+
```diff
6+
- import {Button} from 'react-aria-components';
7+
+ import {Button} from 'react-aria-components/Button';
8+
```
9+
10+
## Usage
11+
12+
Run `npx @react-spectrum/codemods use-subpaths` from the directory you want to update imports in.
13+
14+
### Options
15+
16+
- `--parser` - The [parser](https://github.com/facebook/jscodeshift?tab=readme-ov-file#parser) to use for parsing the source files. Defaults to `tsx`.
17+
- `--ignore-pattern` - A [glob pattern](https://github.com/facebook/jscodeshift?tab=readme-ov-file#ignoring-files-and-directories) of files to ignore. Defaults to `**/node_modules/**`.
18+
- `--dry` - Run the codemod without making changes to the files.
19+
- `--path` - The path to the directory to run the codemod in. Defaults to the current directory.
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
// @ts-ignore
2+
import {defineInlineTest} from 'jscodeshift/dist/testUtils';
3+
import transform from './codemod';
4+
5+
function test(name: string, input: string, output: string) {
6+
defineInlineTest(transform, {}, input, output, name);
7+
}
8+
9+
test(
10+
'rewrites a uniquely mapped specifier',
11+
`
12+
import {Accordion} from '@adobe/react-spectrum';
13+
`,
14+
`
15+
import { Accordion } from '@adobe/react-spectrum/Accordion';
16+
`
17+
);
18+
19+
test(
20+
'splits mixed uniquely mapped specifiers across subpaths',
21+
`
22+
import {Accordion, Button} from '@adobe/react-spectrum';
23+
`,
24+
`
25+
import { Accordion } from '@adobe/react-spectrum/Accordion';
26+
import { Button } from '@adobe/react-spectrum/Button';
27+
`
28+
);
29+
30+
test(
31+
'groups multi-mapped specifiers with a uniquely mapped import from the same declaration',
32+
`
33+
import {ListView, Item} from '@adobe/react-spectrum';
34+
`,
35+
`
36+
import { ListView, Item } from '@adobe/react-spectrum/ListView';
37+
`
38+
);
39+
40+
test(
41+
'handles when Item is available in multiple existing imports',
42+
`
43+
import {ListView, ActionGroup, Item} from '@adobe/react-spectrum';
44+
`,
45+
`
46+
import { ListView, Item } from '@adobe/react-spectrum/ListView';
47+
import { ActionGroup } from '@adobe/react-spectrum/ActionGroup';
48+
`
49+
);
50+
51+
test(
52+
'handles multiple monopackage imports',
53+
`
54+
import {ListView} from '@adobe/react-spectrum';
55+
import {ActionGroup, Item} from '@adobe/react-spectrum';
56+
`,
57+
`
58+
import { ListView, Item } from '@adobe/react-spectrum/ListView';
59+
import { ActionGroup } from '@adobe/react-spectrum/ActionGroup';
60+
`
61+
);
62+
63+
test(
64+
'reuses an existing matching subpath import elsewhere in the file',
65+
`
66+
import {Item} from '@adobe/react-spectrum';
67+
import {ListView} from '@adobe/react-spectrum/ListView';
68+
`,
69+
`
70+
import { ListView, Item } from '@adobe/react-spectrum/ListView';
71+
`
72+
);
73+
74+
test(
75+
'preserves aliases when moving specifiers',
76+
`
77+
import {ListView as RSListView, Item as RSItem} from '@adobe/react-spectrum';
78+
`,
79+
`
80+
import { ListView as RSListView, Item as RSItem } from '@adobe/react-spectrum/ListView';
81+
`
82+
);
83+
84+
test(
85+
'keeps unsupported, default, and namespace imports untouched',
86+
`
87+
import ReactSpectrum, {Accordion, fakeThing} from '@adobe/react-spectrum';
88+
import * as RAC from 'react-aria-components';
89+
`,
90+
`
91+
import ReactSpectrum, { fakeThing } from '@adobe/react-spectrum';
92+
import { Accordion } from '@adobe/react-spectrum/Accordion';
93+
import * as RAC from 'react-aria-components';
94+
`
95+
);
96+
97+
test(
98+
'merges into an existing destination import',
99+
`
100+
import {Disclosure} from '@adobe/react-spectrum';
101+
import {Accordion} from '@adobe/react-spectrum/Accordion';
102+
`,
103+
`
104+
import { Accordion, Disclosure } from '@adobe/react-spectrum/Accordion';
105+
`
106+
);
107+
108+
test(
109+
'leaves already subpathed imports unchanged',
110+
`
111+
import {Accordion} from '@adobe/react-spectrum/Accordion';
112+
`,
113+
`
114+
import {Accordion} from '@adobe/react-spectrum/Accordion';
115+
`
116+
);
117+
118+
test(
119+
'groups by import kind',
120+
`
121+
import {Button, ButtonContext} from 'react-aria-components';
122+
import type {ButtonProps} from 'react-aria-components';
123+
`,
124+
`
125+
import { Button, ButtonContext } from 'react-aria-components/Button';
126+
import type { ButtonProps } from 'react-aria-components/Button';
127+
`
128+
);

0 commit comments

Comments
 (0)