Skip to content

Commit

Permalink
Allow stories exported in export blocks (#556)
Browse files Browse the repository at this point in the history
Co-authored-by: Vojtech Miksu <[email protected]>
  • Loading branch information
i-am-the-slime and tajo authored May 31, 2024
1 parent 55de74a commit 755fd68
Show file tree
Hide file tree
Showing 3 changed files with 110 additions and 48 deletions.
5 changes: 5 additions & 0 deletions .changeset/breezy-rice-run.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@ladle/react": patch
---

Allow export of stories in export blocks
119 changes: 71 additions & 48 deletions packages/ladle/lib/cli/vite-plugin/parse/get-named-exports.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,59 +24,82 @@ const getNamedExports = (
astPath,
) => {
/**
* @type {string}
* @param {any} namedExportDeclaration
* @param {string} namedExport
* @returns {import('../../../shared/types').StoryEntry} result
*/
let namedExport = "";
const namedExportDeclaration = astPath.node.declaration;
if (namedExportDeclaration.type === "ClassDeclaration") {
namedExport = namedExportDeclaration.id.name;
} else if (namedExportDeclaration.type === "VariableDeclaration") {
namedExport = namedExportDeclaration.declarations[0].id.name;
} else if (namedExportDeclaration.type === "FunctionDeclaration") {
namedExport = namedExportDeclaration.id.name;
} else {
throw new Error(
`Named export in ${entry} must be variable, class or function.`,
);
}
const namedExportToStory = (namedExportDeclaration, namedExport) => {
if (namedExport.includes("__")) {
throw new Error(
`Story named ${namedExport} can't contain "__". It's reserved for internal encoding. Please rename this export.`,
);
}

if (namedExport.includes("__")) {
throw new Error(
`Story named ${namedExport} can't contain "__". It's reserved for internal encoding. Please rename this export.`,
let storyNamespace = fileId;
if (exportDefaultProps && exportDefaultProps.title) {
storyNamespace = titleToFileId(exportDefaultProps.title);
}
const storyName = namedExportToStoryName[namedExport]
? namedExportToStoryName[namedExport]
: namedExport;
const storyId = `${kebabCase(
storyNamespace,
)}${storyDelimiter}${storyDelimiter}${kebabCase(storyName)}`;
// attach default meta to each story
if (exportDefaultProps && exportDefaultProps.meta) {
storyParams[storyId] = exportDefaultProps;
}
// add and merge story specific meta
if (namedExportToMeta[namedExport]) {
storyParams[storyId] = merge(cloneDeep(storyParams[storyId] || {}), {
meta: namedExportToMeta[namedExport],
});
}
const componentName = getEncodedStoryName(
kebabCase(storyNamespace),
kebabCase(storyName),
);
}
const story = {
storyId,
componentName,
namedExport,
locStart: namedExportDeclaration.loc.start.line,
locEnd: namedExportDeclaration.loc.end.line,
};
return story;
};

let storyNamespace = fileId;
if (exportDefaultProps && exportDefaultProps.title) {
storyNamespace = titleToFileId(exportDefaultProps.title);
}
const storyName = namedExportToStoryName[namedExport]
? namedExportToStoryName[namedExport]
: namedExport;
const storyId = `${kebabCase(
storyNamespace,
)}${storyDelimiter}${storyDelimiter}${kebabCase(storyName)}`;
// attach default meta to each story
if (exportDefaultProps && exportDefaultProps.meta) {
storyParams[storyId] = exportDefaultProps;
}
// add and merge story specific meta
if (namedExportToMeta[namedExport]) {
storyParams[storyId] = merge(cloneDeep(storyParams[storyId] || {}), {
meta: namedExportToMeta[namedExport],
});
/**
* @type {string}
*/
// Inline exports, such as: export const Story = () => <h1>Export List</h1>;
if (astPath.node?.declaration?.type) {
let namedExport = "";
const namedExportDeclaration = astPath.node?.declaration;
if (namedExportDeclaration.type === "ClassDeclaration") {
namedExport = namedExportDeclaration.id.name;
} else if (namedExportDeclaration.type === "VariableDeclaration") {
namedExport = namedExportDeclaration.declarations[0].id.name;
} else if (namedExportDeclaration.type === "FunctionDeclaration") {
namedExport = namedExportDeclaration.id.name;
} else {
throw new Error(
`Named export in ${entry} must be variable, class or function.`,
);
}
const story = namedExportToStory(namedExportDeclaration, namedExport);
stories.push(story);
} else if (astPath.node?.specifiers.length > 0) {
// It's an export block export, such as: { story, story as storyRenamed };
astPath.node?.specifiers.forEach(
/** type * @param {any} specifier */
(specifier) => {
const namedExport = specifier.exported.name;
const story = namedExportToStory(specifier, namedExport);
stories.push(story);
},
);
}
const componentName = getEncodedStoryName(
kebabCase(storyNamespace),
kebabCase(storyName),
);
stories.push({
storyId,
componentName,
namedExport,
locStart: namedExportDeclaration.loc.start.line,
locEnd: namedExportDeclaration.loc.end.line,
});
};

export default getNamedExports;
34 changes: 34 additions & 0 deletions packages/ladle/tests/parse/get-named-exports.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -283,3 +283,37 @@ test("Function Named Export", async () => {
}),
);
});

test("Export Block", async () => {
expect(
parseWithFn(
`var myStory = () => "Text";
export {
myStory,
myStory as renamed
};`,
{},
getNamedExports,
"ExportNamedDeclaration",
),
).toEqual(
getOutput({
stories: [
{
componentName: "file$$my$story",
namedExport: "myStory",
storyId: "file--my-story",
locEnd: 3,
locStart: 3,
},
{
componentName: "file$$renamed",
namedExport: "renamed",
storyId: "file--renamed",
locEnd: 4,
locStart: 4,
},
],
}),
);
});

0 comments on commit 755fd68

Please sign in to comment.