babel-plugin-storybook-csf-title
Version:
A Babel plugin to generate titles for Storybook CSF stories at compile time, typically based on the story file's file name.
121 lines (111 loc) • 4.68 kB
JavaScript
const TITLE_KEY = 'title';
const fixObjectDefaultExport = (path, t, title, ifTitleFound) => {
if (path.node.declaration.properties) {
const titleProperty = path.node.declaration.properties.find(node =>
node.key && node.key.name === TITLE_KEY
);
if (titleProperty) {
switch (ifTitleFound) {
case 'skip':
return;
default:
throw new Error(
`Default export object has a '${TITLE_KEY}' property; the title should, however, be generated. Please remove '${TITLE_KEY}'.`
);
}
}
path.get('declaration').pushContainer(
'properties',
t.objectProperty(t.identifier(TITLE_KEY), t.stringLiteral(title))
);
} else {
throw new Error('Default export object does not have properties.');
}
}
const replaceDefaultExportPathByNamedDefaultExportPath = (defaultExportPath, t, renameDefaultExportsTo) => {
defaultExportPath.replaceWith(
t.exportNamedDeclaration(
t.variableDeclaration(
'const',
[
t.variableDeclarator(
t.identifier(renameDefaultExportsTo),
defaultExportPath.node.declaration
)
],
),
[]
)
);
}
const insertDefaultExport = (programPath, t, title) => {
programPath.pushContainer(
'body',
t.exportDefaultDeclaration(t.objectExpression([
t.objectProperty(t.identifier(TITLE_KEY), t.stringLiteral(title))
]))
);
}
const plugin = babel => {
const { types: t } = babel;
return ({
name: 'Storybook CSF title generation',
visitor: {
ExportDefaultDeclaration: (path, state) => {
if (state.handled) {
return;
}
state.defaultExportPath = path;
},
ExportNamedDeclaration: (path, state) => {
if (state.handled) {
return;
}
if (
state.opts.renameDefaultExportsTo &&
path.node.declaration &&
path.node.declaration.type === 'VariableDeclaration' &&
path.node.declaration.declarations &&
path.node.declaration.declarations[0] &&
path.node.declaration.declarations[0].type === 'VariableDeclarator' &&
path.node.declaration.declarations[0].id &&
path.node.declaration.declarations[0].id.name === state.opts.renameDefaultExportsTo
) {
state.namedDefaultExportPath = path;
}
},
Program: {
exit: (path, state) => {
if (state.handled) {
return;
}
state.handled = true;
const title = state.opts.toTitle(state);
const renameDefaultExportsTo = state.opts.renameDefaultExportsTo;
const programPath = path;
if (state.defaultExportPath) {
if (state.defaultExportPath.node.declaration.type === 'ObjectExpression') {
fixObjectDefaultExport(state.defaultExportPath, t, title, state.opts.ifTitleFound);
} else {
if (renameDefaultExportsTo) {
if (!state.namedDefaultExportPath) {
replaceDefaultExportPathByNamedDefaultExportPath(state.defaultExportPath, t, renameDefaultExportsTo);
insertDefaultExport(programPath, t, title);
} else {
throw new Error(
`Default export can't be changed to '${renameDefaultExportsTo}', as a '${renameDefaultExportsTo}' export already exists. Please rename '${renameDefaultExportsTo}'.`
)
}
} else {
throw new Error(`Non-object default export found. Please change to named export.`)
}
}
} else {
insertDefaultExport(programPath, t, title);
}
}
}
}
});
};
module.exports = plugin;