Codemod to convert storiesOf to module format

This commit is contained in:
Michael Shilman 2019-06-23 11:49:55 +08:00
parent 31ea16f87d
commit abdde1f012
8 changed files with 153 additions and 79 deletions

View File

@ -5,19 +5,21 @@ import Button from './Button';
import { storiesOf, configure } from '@storybook/react';
import { action } from '@storybook/addon-actions';
export default {
title: 'Button',
{
export default {
title: 'Button',
};
export const story1 = () => <Button label="Story 1" />;
export const story2 = () => <Button label="Story 2" onClick={action('click')} />;
story2.title = 'second story';
export const story3 = () => (
<div>
<Button label="The Button" onClick={action('onClick')} />
<br />
</div>
);
story3.title = 'complex story';
};
export const story1 = () => <Button label="Story 1" />;
export const story2 = () => <Button label="Story 2" onClick={action('click')} />;
story2.title = 'second story';
export const story3 = () => (
<div>
<Button label="The Button" onClick={action('onClick')} />
<br />
</div>
);
story3.title = 'complex story';

View File

@ -3,10 +3,12 @@ import React from 'react';
import Button from './Button';
// This isn't a valid story, but it tests the `import { comp } from ...` case
export default {
title: 'Some.Button',
decorators: [withKnobs, storyFn => <div className="foo">{storyFn}</div>],
};
{
export default {
title: 'Some.Button',
decorators: [withKnobs, storyFn => <div className="foo">{storyFn}</div>],
};
export const story1 = () => <Button label="The Button" />;
story1.title = 'with decorator';
export const story1 = () => <Button label="The Button" />;
story1.title = 'with decorator';
};

View File

@ -5,12 +5,20 @@ import Button from './Button';
// If we have multiple storiesOf calls, export multiple defaults. It's not valid
// JS but will still save the user time in converting.
export default {
title: 'Button1',
};
export const story1 = () => <Button label="Button1" />;
{
export default {
title: 'Button1',
};
export default {
title: 'Button2',
}
export const story1 = () => <Button label="Button2" />;
export const story1 = () => <Button label="Button1.1" />;
export const story2 = () => <Button label="Button1.2" />;
};
{
export default {
title: 'Button2',
};
export const story1 = () => <Button label="Button2.1" />;
export const story2 = () => <Button label="Button2.2" />;
};

View File

@ -6,4 +6,5 @@ import { storiesOf } from '@storybook/react';
storiesOf('Button', module)
.addParameters({ component: Button, foo: 1 })
.addParameters({ bar: 2 })
.add('with kind parameters', () => <Button label="The Button" />);

View File

@ -4,10 +4,17 @@ import Button from './Button';
import { storiesOf } from '@storybook/react';
export default {
title: 'Button',
parameters: { component: Button, foo: 1 },
};
{
export default {
title: 'Button',
export const story1 = () => <Button label="The Button" />;
story1.title = 'with kind parameters';
parameters: {
component: Button,
foo: 1,
bar: 2,
},
};
export const story1 = () => <Button label="The Button" />;
story1.title = 'with kind parameters';
};

View File

@ -2,18 +2,24 @@
import React from 'react';
import Button from './Button';
export default {
title: 'Button',
};
import { storiesOf } from '@storybook/react';
export const story1 = () => <Button label="The Button" />;
story1.title = 'with story parameters';
story1.parameters = {
header: false,
inline: true,
};
{
export default {
title: 'Button',
};
export const foo = () => <Button label="Foo" />;
foo.parameters = {
bar: 1,
export const story1 = () => <Button label="The Button" />;
story1.title = 'with story parameters';
story1.parameters = {
header: false,
inline: true,
};
export const foo = () => <Button label="Foo" />;
foo.parameters = {
bar: 1,
};
};

View File

@ -1,7 +1,6 @@
import { defineTest } from 'jscodeshift/dist/testUtils';
const testNames = ['multi'];
// const testNames = ['basic', 'decorators', 'parameters', 'story-parameters', 'module'];
const testNames = ['basic', 'decorators', 'parameters', 'story-parameters', 'module', 'multi'];
testNames.forEach(testName => {
defineTest(__dirname, `convert-to-module-format`, null, `convert-to-module-format/${testName}`);

View File

@ -1,3 +1,5 @@
import { stat } from 'fs';
/**
* Convert a legacy story file to module format
*
@ -23,36 +25,56 @@ export default function transformer(file, api) {
const j = api.jscodeshift;
const root = j(file.source);
const defaultExports = root
.find(j.ExportSpecifier)
.filter(path => path.node.local.name === 'default');
if (defaultExports && defaultExports.length > 0) {
return null; // keep original source
}
function convertToModuleDefault(call) {
const title = call.node.arguments[0].value;
const addParameters = j(call)
.find(j.CallExpression)
.filter(method => method.node.callee.name === 'addParameters');
const kindParams =
addParameters && addParameters.length
? j.property('parameters', addParameters.node.arguments[0].value)
: null;
const result = j.exportDefaultDeclaration(
j.objectExpression([j.property('init', j.identifier('title'), j.literal(title))])
);
return result;
const defaultExports = root.find(j.ExportDefaultDeclaration);
if (defaultExports.size() > 0) {
return root.toSource();
}
function convertToModuleExports(path) {
const base = j(path);
let counter = 1;
const statements = [];
const extraExports = [];
// .addDecorator
const decorators = [];
base
.find(j.CallExpression)
.filter(
call => call.node.callee.property && call.node.callee.property.name === 'addDecorator'
)
.forEach(add => {
const decorator = add.node.arguments[0];
decorators.push(decorator);
});
if (decorators.length > 0) {
decorators.reverse();
extraExports.push(
j.property('init', j.identifier('decorators'), j.arrayExpression(decorators))
);
}
// .addParameters
const parameters = [];
base
.find(j.CallExpression)
.filter(
call => call.node.callee.property && call.node.callee.property.name === 'addParameters'
)
.forEach(add => {
// jscodeshift gives us the find results in reverse, but these args come in
// order, so we double reverse here. ugh.
const params = [...add.node.arguments[0].properties];
params.reverse();
params.forEach(prop => parameters.push(prop));
});
if (parameters.length > 0) {
parameters.reverse();
extraExports.push(
j.property('init', j.identifier('parameters'), j.objectExpression(parameters))
);
}
// storiesOf(...)
base
@ -63,7 +85,10 @@ export default function transformer(file, api) {
const title = storiesOf.node.arguments[0].value;
statements.push(
j.exportDefaultDeclaration(
j.objectExpression([j.property('init', j.identifier('title'), j.literal(title))])
j.objectExpression([
j.property('init', j.identifier('title'), j.literal(title)),
...extraExports,
])
)
);
});
@ -76,19 +101,43 @@ export default function transformer(file, api) {
.filter(add => add.node.arguments.length >= 2 && add.node.arguments[0].type === 'Literal')
.forEach(add => adds.push(add));
adds.reverse();
adds.push(path);
adds.forEach(add => {
const name = add.node.arguments[0].value;
const val = add.node.arguments[1];
if (add.node.arguments.length > 2) {
const storyParams = add.node.arguments[2].value;
let name = add.node.arguments[0].value;
let title = null;
if (/\s/.exec(name)) {
title = name;
name = `story${counter}`;
}
const val = add.node.arguments[1];
statements.push(
j.exportDeclaration(
false,
j.variableDeclaration('const', [j.variableDeclarator(j.identifier(name), val)])
)
);
if (title) {
statements.push(
j.assignmentStatement(
'=',
j.memberExpression(j.identifier(name), j.identifier('title')),
j.literal(title)
)
);
}
if (add.node.arguments.length > 2) {
const storyParams = add.node.arguments[2];
statements.push(
j.assignmentStatement(
'=',
j.memberExpression(j.identifier(name), j.identifier('parameters')),
storyParams
)
);
}
counter += 1;
});
return j.blockStatement(statements);
@ -104,5 +153,5 @@ export default function transformer(file, api) {
topLevelAddExpressions.replaceWith(convertToModuleExports);
return root.toSource();
return root.toSource({ quote: 'single', trailingComma: 'true', tabWidth: 2 });
}