Codemod to add component parameters to legacy stories

This commit is contained in:
Michael Shilman 2019-06-20 12:42:46 +08:00
parent 94cdf46c57
commit c89042966b
5 changed files with 227 additions and 25 deletions

View File

@ -9,8 +9,8 @@ It will help you migrate breaking changes & deprecations.
yarn add jscodeshift @storybook/codemod --dev
```
- `@storybook/codemod` is our collection of codemod scripts.
- `jscodeshift` is a tool we use to apply our codemods.
- `@storybook/codemod` is our collection of codemod scripts.
- `jscodeshift` is a tool we use to apply our codemods.
After running the migration commands, you can remove them from your `package.json`, if you added them.
@ -65,23 +65,18 @@ Replaces the Info addon's deprecated `addWithInfo` API with the standard `withIn
Simple example:
```js
storiesOf('Button').addWithInfo(
'simple usage',
'This is the basic usage of the button.',
() => (
<Button label="The Button" />
)
)
storiesOf('Button').addWithInfo('simple usage', 'This is the basic usage of the button.', () => (
<Button label="The Button" />
));
```
Becomes
```js
storiesOf('Button').add('simple usage', withInfo(
'This is the basic usage of the button.'
)(() => (
<Button label="The Button" />
)))
storiesOf('Button').add(
'simple usage',
withInfo('This is the basic usage of the button.')(() => <Button label="The Button" />)
);
```
With options example:
@ -90,21 +85,50 @@ With options example:
storiesOf('Button').addWithInfo(
'simple usage (disable source)',
'This is the basic usage of the button.',
() => (
<Button label="The Button" />
),
() => <Button label="The Button" />,
{ source: false, inline: true }
)
);
```
Becomes
```js
storiesOf('Button').add('simple usage (disable source)', withInfo({
text: 'This is the basic usage of the button.',
source: false,
inline: true
})(() => (
<Button label="The Button" />
)))
storiesOf('Button').add(
'simple usage (disable source)',
withInfo({
text: 'This is the basic usage of the button.',
source: false,
inline: true,
})(() => <Button label="The Button" />)
);
```
### add-component-parameters
This tries to smartly adds "component" parameters to all your existing stories
for use in SB Docs.
```sh
./node_modules/.bin/jscodeshift -t ./node_modules/@storybook/codemod/dist/transforms/add-component-parameters.js . --ignore-pattern "node_modules|dist"
```
For example:
```js
input { Button } from './Button';
storiesOf('Button', module).add('story', () => <Button label="The Button" />);
```
Becomes:
```js
input { Button } from './Button';
storiesOf('Button', module)
.addParameters({ component: Button })
.add('story', () => <Button label="The Button" />);
```
Heuristics:
- The storiesOf "kind" name must be Button
- Button must be imported in the file

View File

@ -0,0 +1,44 @@
/* eslint-disable */
import React from 'react';
import Button from './Button';
import { storiesOf, configure } from '@storybook/react';
import { action } from '@storybook/addon-actions';
storiesOf('Button', module).add('basic', () => <Button label="The Button" />);
storiesOf('Button').add('no module', () => <Button label="The Button" />);
storiesOf('Button', module).add('with story parameters', () => <Button label="The Button" />, {
header: false,
inline: true,
});
storiesOf('Button', module)
.addParameters({ foo: 1 })
.add('with kind parameters', () => <Button label="The Button" />);
storiesOf('Button', module)
.addParameters({ component: Button })
.add('with existing component parameters', () => <Button label="The Button" />);
storiesOf('Button', module).add('complex story', () => (
<div>
<Button label="The Button" onClick={action('onClick')} />
<br />
</div>
));
storiesOf('Root|Some/Button', module).add('with path', () => <Button label="The Button" />);
storiesOf('Some.Button', module).add('with dot-path', () => <Button label="The Button" />);
storiesOf('Some.Button', module)
.addDecorator(withKnobs)
.add('with decorator', () => <Button label="The Button" />);
// This isn't a valid story, but it tests the `import { comp } from ...` case
storiesOf('action', module).add('non-default component export', () => <action />);
// This shouldn't get modified since the story name doesn't match
storiesOf('something', module).add('non-matching story', () => <Button label="The Button" />);

View File

@ -0,0 +1,64 @@
/* eslint-disable */
import React from 'react';
import Button from './Button';
import { storiesOf, configure } from '@storybook/react';
import { action } from '@storybook/addon-actions';
storiesOf('Button', module).addParameters({
component: Button
}).add('basic', () => <Button label="The Button" />);
storiesOf('Button').addParameters({
component: Button
}).add('no module', () => <Button label="The Button" />);
storiesOf('Button', module).addParameters({
component: Button
}).add('with story parameters', () => <Button label="The Button" />, {
header: false,
inline: true,
});
storiesOf('Button', module).addParameters({
component: Button
})
.addParameters({ foo: 1 })
.add('with kind parameters', () => <Button label="The Button" />);
storiesOf('Button', module).addParameters({
component: Button
})
.addParameters({ component: Button })
.add('with existing component parameters', () => <Button label="The Button" />);
storiesOf('Button', module).addParameters({
component: Button
}).add('complex story', () => (
<div>
<Button label="The Button" onClick={action('onClick')} />
<br />
</div>
));
storiesOf('Root|Some/Button', module).addParameters({
component: Button
}).add('with path', () => <Button label="The Button" />);
storiesOf('Some.Button', module).addParameters({
component: Button
}).add('with dot-path', () => <Button label="The Button" />);
storiesOf('Some.Button', module).addParameters({
component: Button
})
.addDecorator(withKnobs)
.add('with decorator', () => <Button label="The Button" />);
// This isn't a valid story, but it tests the `import { comp } from ...` case
storiesOf('action', module).addParameters({
component: action
}).add('non-default component export', () => <action />);
// This shouldn't get modified since the story name doesn't match
storiesOf('something', module).add('non-matching story', () => <Button label="The Button" />);

View File

@ -0,0 +1,8 @@
import { defineTest } from 'jscodeshift/dist/testUtils';
defineTest(
__dirname,
'add-component-parameters',
null,
'add-component-parameters/add-component-parameters'
);

View File

@ -0,0 +1,62 @@
/**
* Adds a `component` parameter for each storiesOf(...) call.
*
* For example:
*
* input { Button } from './Button';
* storiesOf('Button', module).add('story', () => <Button label="The Button" />);
*
* Becomes:
*
* input { Button } from './Button';
* storiesOf('Button', module)
* .addParameters({ component: Button })
* .add('story', () => <Button label="The Button" />);
*
* Heuristics:
* - The storiesOf "kind" name must be Button
* - Button must be imported in the file
*/
export default function transformer(file, api) {
const j = api.jscodeshift;
const root = j(file.source);
// Create a dictionary whose keys are all the named imports in the file.
// For instance:
//
// import foo from 'foo';
// import { bar, baz } from 'zoo';
//
// => { foo: true, bar: true, baz: true }
const importMap = {};
root.find(j.ImportDeclaration).forEach(imp =>
imp.node.specifiers.forEach(spec => {
importMap[spec.local.name] = true;
})
);
function getLeafName(string) {
const parts = string.split(/\/|\.|\|/);
return parts[parts.length - 1];
}
function addComponentParameter(call) {
const { node } = call;
const leafName = getLeafName(node.arguments[0].value);
return j.callExpression(j.memberExpression(node, j.identifier('addParameters')), [
j.objectExpression([j.property('init', j.identifier('component'), j.identifier(leafName))]),
]);
}
const storiesOfCalls = root
.find(j.CallExpression)
.filter(call => call.node.callee.name === 'storiesOf')
.filter(call => call.node.arguments.length > 0 && call.node.arguments[0].type === 'Literal')
.filter(call => {
const leafName = getLeafName(call.node.arguments[0].value);
return importMap[leafName];
})
.replaceWith(addComponentParameter);
return root.toSource();
}