Skip to content

Commit

Permalink
Merge pull request #16626 from storybookjs/15574-fix-named-exports-so…
Browse files Browse the repository at this point in the history
…rting

Core: Fix sorting by `__namedExportsOrder`
  • Loading branch information
shilman committed Nov 8, 2021
2 parents 8927a5e + 0787dfc commit 66cc58b
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 19 deletions.
11 changes: 11 additions & 0 deletions examples/react-ts/src/button.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ export default {

export const WithArgs: ComponentStory<typeof Button> = (args) => <Button {...args} />;
WithArgs.args = { label: 'With args' };

export const Basic = () => <Button label="Click me" />;

export const StoryObject = {
Expand All @@ -36,3 +37,13 @@ CSF2StoryWithPlay.play = () => {
console.log('play!!');
userEvent.click(screen.getByRole('button'));
};

// eslint-disable-next-line no-underscore-dangle
export const __namedExportsOrder = [
'Basic',
'WithArgs',
'StoryObject',
'StoryNoRender',
'StoryWithPlay',
'CSF2StoryWithPlay',
];
15 changes: 14 additions & 1 deletion lib/client-api/src/StoryStoreFacade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,20 @@ export class StoryStoreFacade<TFramework extends AnyFramework> {
default: { ...defaultExport, title },
};

Object.entries(namedExports)
let sortedExports = namedExports;

// prefer a user/loader provided `__namedExportsOrder` array if supplied
// we do this as es module exports are always ordered alphabetically
// see https://github.com/storybookjs/storybook/issues/9136
if (Array.isArray(__namedExportsOrder)) {
sortedExports = {};
__namedExportsOrder.forEach((name) => {
const namedExport = namedExports[name];
if (namedExport) sortedExports[name] = namedExport;
});
}

Object.entries(sortedExports)
.filter(([key]) => isExportStory(key, defaultExport))
.forEach(([key, storyExport]: [string, any]) => {
const exportName = storyNameFromExport(key);
Expand Down
31 changes: 31 additions & 0 deletions lib/csf-tools/src/CsfFile.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,34 @@ describe('CsfFile', () => {
name: B
`);
});

it('named exports order', () => {
expect(
parse(
dedent`
export default { title: 'foo/bar' };
export const A = () => {};
export const B = (args) => {};
export const __namedExportsOrder = ['B', 'A'];
`,
true
)
).toMatchInlineSnapshot(`
meta:
title: foo/bar
stories:
- id: foo-bar--b
name: B
parameters:
__isArgsStory: true
__id: foo-bar--b
- id: foo-bar--a
name: A
parameters:
__isArgsStory: false
__id: foo-bar--a
`);
});
});

describe('error handling', () => {
Expand All @@ -318,6 +346,7 @@ describe('CsfFile', () => {
)
).toThrow('CSF: missing default export');
});

it('no metadata', () => {
expect(() =>
parse(
Expand All @@ -329,6 +358,7 @@ describe('CsfFile', () => {
)
).toThrow('CSF: missing title/component');
});

it('dynamic titles', () => {
expect(() =>
parse(
Expand All @@ -340,6 +370,7 @@ describe('CsfFile', () => {
)
).toThrow('CSF: unexpected dynamic title');
});

it('storiesOf calls', () => {
expect(() =>
parse(
Expand Down
31 changes: 31 additions & 0 deletions lib/csf-tools/src/CsfFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,26 @@ const isArgsStory = (init: t.Expression, parent: t.Node, csf: CsfFile) => {
return false;
};

const parseExportsOrder = (init: t.Expression) => {
if (t.isArrayExpression(init)) {
return init.elements.map((item: t.Expression) => {
if (t.isStringLiteral(item)) {
return item.value;
}
throw new Error(`Expected string literal named export: ${item}`);
});
}
throw new Error(`Expected array of string literals: ${init}`);
};

const sortExports = (exportByName: Record<string, any>, order: string[]) => {
return order.reduce((acc, name) => {
const namedExport = exportByName[name];
if (namedExport) acc[name] = namedExport;
return acc;
}, {} as Record<string, any>);
};

export interface CsfOptions {
defaultTitle: string;
fileName?: string;
Expand Down Expand Up @@ -134,6 +154,8 @@ export class CsfFile {

_templates: Record<string, t.Expression> = {};

_namedExportsOrder?: string[];

constructor(ast: t.File, { defaultTitle, fileName }: CsfOptions) {
this._ast = ast;
this._defaultTitle = defaultTitle;
Expand Down Expand Up @@ -207,6 +229,10 @@ export class CsfFile {
node.declaration.declarations.forEach((decl) => {
if (t.isVariableDeclarator(decl) && t.isIdentifier(decl.id)) {
const { name: exportName } = decl.id;
if (exportName === '__namedExportsOrder') {
self._namedExportsOrder = parseExportsOrder(decl.init);
return;
}
self._storyExports[exportName] = decl;
let name = storyNameFromExport(exportName);
if (self._storyAnnotations[exportName]) {
Expand Down Expand Up @@ -344,6 +370,11 @@ export class CsfFile {
}
});

if (self._namedExportsOrder) {
self._storyExports = sortExports(self._storyExports, self._namedExportsOrder);
self._stories = sortExports(self._stories, self._namedExportsOrder);
}

return self;
}

Expand Down
6 changes: 3 additions & 3 deletions lib/store/src/processCSFFile.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ describe('processCSFFile', () => {
});
});

it('adds stories in the right order if __namedExportsOrder is supplied', () => {
it('ignores __namedExportsOrder', () => {
const { stories } = processCSFFile(
{
default: { title: 'Component' },
Expand All @@ -63,10 +63,10 @@ describe('processCSFFile', () => {
);

expect(Object.keys(stories)).toEqual([
'component--w',
'component--x',
'component--z',
'component--y',
'component--z',
'component--w',
]);
});

Expand Down
17 changes: 2 additions & 15 deletions lib/store/src/processCSFFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ export function processCSFFile<TFramework extends AnyFramework>(
title: ComponentTitle
): CSFFile<TFramework> {
const { default: defaultExport, __namedExportsOrder, ...namedExports } = moduleExports;
let exports = namedExports;

const { id, argTypes } = defaultExport;
const meta: NormalizedComponentAnnotations<TFramework> = {
Expand All @@ -54,23 +53,11 @@ export function processCSFFile<TFramework extends AnyFramework>(
};
checkDisallowedParameters(meta.parameters);

// prefer a user/loader provided `__namedExportsOrder` array if supplied
// we do this as es module exports are always ordered alphabetically
// see https://github.com/storybookjs/storybook/issues/9136
if (Array.isArray(__namedExportsOrder)) {
exports = {};
__namedExportsOrder.forEach((name) => {
if (namedExports[name]) {
exports[name] = namedExports[name];
}
});
}

const csfFile: CSFFile<TFramework> = { meta, stories: {} };

Object.keys(exports).forEach((key) => {
Object.keys(namedExports).forEach((key) => {
if (isExportStory(key, meta)) {
const storyMeta = normalizeStory(key, exports[key], meta);
const storyMeta = normalizeStory(key, namedExports[key], meta);
checkDisallowedParameters(storyMeta.parameters);

csfFile.stories[storyMeta.id] = storyMeta;
Expand Down

0 comments on commit 66cc58b

Please sign in to comment.